From c564494f84059888f66bdca64d04f74737bd0133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=BF=E4=BA=91=E7=A7=8B=E6=9C=88?= <15273589815@163.com> Date: Thu, 10 Sep 2026 22:46:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(customer-service):=20=E8=BF=BD=E9=97=AE?= =?UTF-8?q?=E9=80=82=E5=BD=93=E6=80=A7=E7=BB=93=E8=AE=BA=E6=97=B6=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E4=B8=A2=E6=8E=89=E4=BA=A7=E5=93=81=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测三连问:「季季盈90天起投多少」→「C1 客户能买它吗」→「那我能买它吗」, 第三问转人工。原因是 _topic_of 不认识适当性回答的格式——它那一行是 "南方季季盈90天为 R2(中低风险),与您当前的风险测评结果不匹配…", 既没有冒号也不是标题行,于是被判成"取不出主语",第三问就失去了指代对象。 补上这种格式:产品名在"为 R{1-5}"之前,且必须在冒号判断**之前**匹配(那一行没有冒号)。 以"为 R2"开头的行说明前面没有产品名,返回空串,而不是把等级本身当成主语。 修好后第三问给出与第二问一致的结论,这是对的:裁决是确定性的,同一个客户问同一只 产品,结论就该一样。新增两条单测钉住这两种情形。 --- .../agent/implementations/customer_service.py | 7 +++++++ .../test_customer_service_search_query.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/app/service/agent/implementations/customer_service.py b/app/service/agent/implementations/customer_service.py index fc7dfa0..3bb641f 100644 --- a/app/service/agent/implementations/customer_service.py +++ b/app/service/agent/implementations/customer_service.py @@ -402,6 +402,13 @@ class CustomerServiceAgent(BaseAgent): """ first = next((line.strip() for line in answer.splitlines() if line.strip()), "") first = first.lstrip("#").strip() + # 适当性回答的首行是「{产品名}为 R2(中低风险),与您当前的风险测评结果不匹配…」, + # 产品名在"为 R"之前。这一条必须在冒号判断**之前**:那一行里没有冒号。 + for level in range(1, 6): + index = first.find(f"为 R{level}") + if index >= 0: + candidate = first[:index].strip() + return candidate if 0 < len(candidate) <= 20 else "" topic = first.split(":", 1)[0].strip() if ":" in first else first # FAQ 型回答的首行是"问:C1 客户能买什么产品?",冒号前只有一个"问"字; # 拿它当主语只会把检索词污染成"问 那它风险高吗"。 diff --git a/tests/unit/service/test_customer_service_search_query.py b/tests/unit/service/test_customer_service_search_query.py index 745ec71..f9b307c 100644 --- a/tests/unit/service/test_customer_service_search_query.py +++ b/tests/unit/service/test_customer_service_search_query.py @@ -51,6 +51,23 @@ def test_fallback_answer_yields_no_topic() -> None: assert CustomerServiceAgent._topic_of("") == "" +def test_topic_extracted_from_suitability_answer() -> None: + """适当性回答的首行是「{产品名}为 R2(中低风险),…」,产品名在"为 R"之前。 + + 这一行既没有冒号也不是标题行。漏掉这种格式的后果是实测到的:客户问 + 「那我能买它吗」时取不出主语,整句被当成没有指代对象,直接转人工。 + """ + assert CustomerServiceAgent._topic_of( + "南方季季盈90天为 R2(中低风险),与您当前的风险测评结果不匹配," + "按照投资者适当性管理规定暂时无法购买。" + ) == "南方季季盈90天" + + +def test_topic_from_suitability_answer_requires_a_name_before_the_marker() -> None: + """以"为 R2"开头说明前面没有产品名,不能把空串或等级本身当成主语。""" + assert CustomerServiceAgent._topic_of("为 R2(中低风险),与您的测评结果不匹配。") == "" + + def test_faq_style_answer_yields_no_topic() -> None: """FAQ 型回答首行是"问:…",冒号前只有一个"问"字,不能当主语。""" assert CustomerServiceAgent._topic_of(