fix(customer-service): 检索问句只在客户这一句说不清楚时才带上文

实测的答非所问:同一会话先问「季季盈90天的起投金额是多少」,再问「基金赎回几天到账」,
第二问答出的是季季盈的产品介绍。原因是 _search_query 无条件把上一轮客户问题拼进检索词,
客户换话题时旧话题的检索结果被带了回来。在金融场景里这比"引导转人工"糟得多:客户问 A
得到 B 的答案会直接失去对客服的信任,而"答不了"至少是诚实的。

改为只在两种真正需要上文的情况下拼接:句子里有明确指代词("这个产品""该基金"),
或短到不构成完整意图("那它风险高吗"只有 6 个字)。指代词刻意不收单字"它/他"——中文里
"其他产品"会被误判,而这类短句已经由长度规则覆盖。

验证:换话题场景第二问回到 FAQ-0016 的到账时间,指代追问仍命中季季盈产品块;
ruff / mypy(113 文件) / 457 unit+contract 全绿。
This commit is contained in:
2026-09-10 22:17:34 +08:00
parent 570493e71c
commit a6c09fa3c4
2 changed files with 85 additions and 9 deletions
@@ -0,0 +1,54 @@
"""客服检索问句构造的单元测试:什么时候该带上文,什么时候绝不能带。
背景是实测出来的:**无条件**拼接上文会让客户换话题时答非所问——同一会话先问
「季季盈90天的起投金额是多少」,再问「基金赎回几天到账」,第二问的检索词变成两句话的
混合,结果命中季季盈的产品块、答出产品介绍,而客户问的是赎回到账时间。
"""
from app.core.contracts import AgentRequest, ConversationTurn
from app.service.agent.implementations.customer_service import CustomerServiceAgent
PREVIOUS = "季季盈90天的起投金额是多少"
def _request(message: str, *history: str) -> AgentRequest:
turns = tuple(
ConversationTurn(role="user" if index % 2 == 0 else "assistant", content=text)
for index, text in enumerate(history)
)
return AgentRequest(
agent_type="customer_service",
session_id="s",
message=message,
idempotency_key="1234567890123456",
history=turns,
)
def test_topic_change_does_not_drag_previous_question_in() -> None:
"""客户换话题时,检索词里不能出现上一轮的问题(实测的答非所问场景)。"""
query = CustomerServiceAgent._search_query(_request("基金赎回几天到账", PREVIOUS))
assert query == "基金赎回几天到账"
assert "季季盈" not in query
def test_referring_phrase_brings_context_in() -> None:
"""带明确指代词的问句要靠上文补全主语。"""
query = CustomerServiceAgent._search_query(_request("这个产品的费率是多少", PREVIOUS))
assert PREVIOUS in query
def test_very_short_question_brings_context_in() -> None:
"""短到不构成完整意图的问句同样需要上文("那它风险高吗"只有 6 个字)。"""
query = CustomerServiceAgent._search_query(_request("那它风险高吗", PREVIOUS))
assert PREVIOUS in query
def test_first_turn_has_no_history_to_add() -> None:
"""首轮没有历史可拼,检索词就是客户这一句。"""
assert CustomerServiceAgent._search_query(
_request("基金赎回几天到账")
) == "基金赎回几天到账"