test(customer-service): 补交检索问句的断言更新

把检索问句从"拼上一轮原话"改成"只补产品名"时同步更新了本文件,但漏了提交。
新增的断言锁住两件事:上一轮的问句原话一个字都不能进检索词(拼整句会让检索词变宽、
只能命中粗粒度的整节块),以及上一轮是兜底话术时取不出主语应退化为只查当前这一句。
This commit is contained in:
2026-09-10 22:29:28 +08:00
parent 4c2b147793
commit a7135d2549
@@ -1,14 +1,22 @@
"""客服检索问句构造的单元测试:什么时候该带上文,什么时候绝不能带。
"""客服检索问句构造的单元测试:什么时候带上文、带什么、绝不带什么。
背景是实测出来的:**无条件**拼接上文会让客户换话题时答非所问——同一会话先问
「季季盈90天的起投金额是多少」,再问「基金赎回几天到账」,第二问的检索词变成两句话的
混合,结果命中季季盈的产品块、答出产品介绍,而客户问的是赎回到账时间。
背景全部来自实测,不是设想:
1. **不能无条件带上文**。同一会话先问「季季盈90天的起投金额是多少」、再问
「基金赎回几天到账」,无条件拼接会让第二问命中季季盈的产品块、答出产品介绍。
2. **带上文时不能带上一轮的原话**。把整句拼进来会让检索词语义变"宽",反而只命中
粗粒度的整节块:实测「季季盈90天起投多少 那它风险高吗」拿到的是整个产品小节,
客户问的"风险"完全没被聚焦;换成「南方季季盈90天 那它风险高吗」才命中"风险等级"那一行。
上文的作用是**补主语**,不是**补内容**。
"""
from app.core.contracts import AgentRequest, ConversationTurn
from app.service.agent.implementations.customer_service import CustomerServiceAgent
PREVIOUS = "季季盈90天的起投金额是多少"
ANSWER = "南方季季盈90天:起投金额 1万元"
TOPIC = "南方季季盈90天"
FALLBACK = "抱歉,这个问题我暂时无法给出准确答复。建议您拨打客服热线 400-XXX-XXXX 转人工客服咨询。"
def _request(message: str, *history: str) -> AgentRequest:
@@ -25,26 +33,59 @@ def _request(message: str, *history: str) -> AgentRequest:
)
def test_topic_extracted_from_row_block() -> None:
"""行级块的回答:首个冒号前就是产品名。"""
assert CustomerServiceAgent._topic_of(ANSWER) == TOPIC
def test_topic_extracted_from_section_block() -> None:
"""整节块的回答:首个标题行去掉 # 号。"""
assert CustomerServiceAgent._topic_of(
"### 2.1 南方季季盈90天\n\n| 项目 | 详情 |\n|------|------|"
) == "2.1 南方季季盈90天"
def test_fallback_answer_yields_no_topic() -> None:
"""兜底话术不是主题:拿它当检索主语只会把问题带偏,宁可退化。"""
assert CustomerServiceAgent._topic_of(FALLBACK) == ""
assert CustomerServiceAgent._topic_of("") == ""
def test_topic_change_does_not_drag_previous_question_in() -> None:
"""客户换话题时,检索词里不能出现上一轮的问题(实测的答非所问场景)。"""
query = CustomerServiceAgent._search_query(_request("基金赎回几天到账", PREVIOUS))
"""客户换话题时,检索词里不能出现上一轮的痕迹(实测的答非所问场景)。"""
query = CustomerServiceAgent._search_query(_request("基金赎回几天到账", PREVIOUS, ANSWER))
assert query == "基金赎回几天到账"
assert "季季盈" not in query
def test_referring_phrase_brings_context_in() -> None:
"""带明确指代词的问句要靠上文补全主语。"""
query = CustomerServiceAgent._search_query(_request("这个产品的费率是多少", PREVIOUS))
def test_referring_phrase_brings_topic_in() -> None:
"""带明确指代词的问句要靠上文补主语。"""
query = CustomerServiceAgent._search_query(_request("这个产品的费率是多少", PREVIOUS, ANSWER))
assert PREVIOUS in query
assert query == f"{TOPIC} 这个产品的费率是多少"
def test_very_short_question_brings_context_in() -> None:
"""短到不构成完整意图的问句同样需要上文("那它风险高吗"只有 6 个字)。"""
query = CustomerServiceAgent._search_query(_request("那它风险高吗", PREVIOUS))
def test_very_short_question_brings_topic_in() -> None:
"""短到不构成完整意图的问句同样需要主语("那它风险高吗"只有 6 个字)。"""
query = CustomerServiceAgent._search_query(_request("那它风险高吗", PREVIOUS, ANSWER))
assert PREVIOUS in query
assert query == f"{TOPIC} 那它风险高吗"
def test_previous_question_text_is_not_carried_in() -> None:
"""只补主语、不补内容:上一轮的问句原话一个字都不能进检索词。"""
query = CustomerServiceAgent._search_query(_request("那它风险高吗", PREVIOUS, ANSWER))
assert PREVIOUS not in query
assert "起投金额" not in query
def test_fallback_history_degrades_to_current_message_only() -> None:
"""上一轮是兜底话术时取不出主语,退化为只查当前这一句,而不是瞎猜。"""
query = CustomerServiceAgent._search_query(_request("那它风险高吗", PREVIOUS, FALLBACK))
assert query == "那它风险高吗"
def test_first_turn_has_no_history_to_add() -> None: