Files
ai-kefu-admin/tests/test_extractor_evaluator_prompts.py
浅念 e34378257f feat: PromptProvider 化 + $DEFLECTION_POOL 单一事实源注入 + runtime 必填化
- PromptProvider(app/engine/services/ai_service.py): 租户提示词四件套
  from_dir 加载;build_system 把 system.md 的 $DEFLECTION_POOL 占位符替换为
  wall.json deflections 话术池(按语言分组一行,EN/ZH/ES)再拼事实卡;
  extractor/evaluator 的 $existing_info/$messages/$profile/$recent_messages
  替换逻辑自 AIService 原样搬入(行为等价);删模块级 import 时读文件的
  PROMPTS_DIR 与四个常量,保留 DEFAULT_PROVIDER(jiancai 出厂,__file__ 锚定)
- 两租户 system.md「三类请求的处理」第2条的迂回话术枚举 → $DEFLECTION_POOL
  占位符(保留段落说明文字),话术只存 wall.json 一份,改一处两边同步变
- TenantRuntime 加 prompts 字段(加载期 from_dir(seed_dir/prompts));
  ConversationManager 的 runtime 转必填(keyword-only),默认 AIService 用
  runtime.prompts + compiled_wall.deflections 组装
- app/engine/prompts/ 删除(grep 无代码消费方,jiancai 出厂由
  seed_profiles/jiancai/prompts 承接);test_company_facts/
  test_system_prompt_dual_contact 的 cwd 相对路径改 Path(__file__) 锚定,
  test_extractor_evaluator_prompts 断言对象改 DEFAULT_PROVIDER(语义等价)
- 新增 tests/test_prompt_provider.py(14 用例): 加载/注入/格式/占位符防漂移/
  extractor-evaluator 组装/registry 接线/runtime 必填/池线过两墙

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 19:54:47 +08:00

113 lines
4.5 KiB
Python

"""提取/评估提示词建材化: 字段与占位符静态断言(Task 4)。
Task 6 迁移: 模块常量 EXTRACTOR_PROMPT/EVALUATOR_PROMPT 随 PromptProvider 化
删除,断言对象改为 DEFAULT_PROVIDER 字段(jiancai 出厂,与
seed_profiles/jiancai/prompts 同源)——断言语义等价。
"""
from app.engine.services.ai_service import DEFAULT_PROVIDER
EXTRACTOR_PROMPT = DEFAULT_PROVIDER.extractor
EVALUATOR_PROMPT = DEFAULT_PROVIDER.evaluator
# 建材字段清单(与 system.md "关注的信息"节口径一致)
PIPE_FIELDS = (
"意向品类", # HDPE给水管/PVC排水管/PPR…
"规格要求", # 自由文本保留客户原话
"标准要求", # ISO4427/ASTM/EN/SCH 单列
"采购数量",
"用途", # 给水/燃气/排污/灌溉…
"项目类型",
"目标国家", # email_service.send_lead_email 按此键取国家
"预算提及",
"联系方式",
)
# 汽车项目遗留字段——建材化后不得残留
AUTO_WORDS = ("车型", "意向品牌", "车况要求", "年份要求", "运输方式", "购车", "试驾")
def test_extractor_has_pipe_fields():
for field in PIPE_FIELDS:
assert field in EXTRACTOR_PROMPT, field
def test_extractor_no_auto_fields():
for word in AUTO_WORDS:
assert word not in EXTRACTOR_PROMPT, word
def test_extractor_spec_is_free_text():
# 规格要求 = 自由文本保留客户原话,不拆分不过度结构化
assert "原话" in EXTRACTOR_PROMPT
assert "不拆分" in EXTRACTOR_PROMPT
def test_extractor_standard_only_when_mentioned():
# 标准要求客户提到才记,不猜测
assert "客户提到才记" in EXTRACTOR_PROMPT
def test_extractor_placeholders_and_contact_contract_intact():
# 占位符(ai_service.replace 依赖)与联系方式四层合并的 extractor 层契约
assert "$existing_info" in EXTRACTOR_PROMPT
assert "$messages" in EXTRACTOR_PROMPT
assert '{"手机": "xxx", "WhatsApp": "xxx", "邮箱": "xxx", "微信": "xxx"}' in EXTRACTOR_PROMPT
assert "整条消息只有号码" in EXTRACTOR_PROMPT
def test_evaluator_keeps_three_levels_and_placeholders():
for level in ("interested", "not_interested", "pending"):
assert level in EVALUATOR_PROMPT, level
assert "$profile" in EVALUATOR_PROMPT
assert "$recent_messages" in EVALUATOR_PROMPT
# _normalize_evaluation 消费的输出字段
for key in ("intent_level", "reasoning", "contact_info", "info_sufficient"):
assert key in EVALUATOR_PROMPT, key
def test_evaluator_no_auto_fields():
for word in AUTO_WORDS:
assert word not in EVALUATOR_PROMPT, word
def test_evaluator_pipe_procurement_examples():
# 管道采购语义示例: 品类+规格+数量=interested; 泛泛浏览=pending; 无采购计划=not_interested
assert "采购需求" in EVALUATOR_PROMPT
assert "随便看看" in EVALUATOR_PROMPT # not_interested: 明确不买
assert "泛泛" in EVALUATOR_PROMPT # pending: 仅泛泛浏览
# ---- Task 5/6b: 质量审查移交的提示词微修 ----
def test_extractor_field_values_chinese_summary():
# 字段值统一中文概括;唯一例外是规格要求原样保留
assert "中文概括" in EXTRACTOR_PROMPT
assert "绝不翻译" in EXTRACTOR_PROMPT
def test_extractor_standard_example_with_number_kept():
# 标准要求示例 ASTM D3035(具体编号)+ 编号原样保留
assert "ASTM D3035" in EXTRACTOR_PROMPT
assert "编号原样保留" in EXTRACTOR_PROMPT
def test_evaluator_positive_signals_are_bonus_not_required():
# 询价/索联系方式移出 interested 必要条件,单独"积极信号"小节
assert "积极信号(加分项,非必要条件)" in EVALUATOR_PROMPT
assert "非必要条件" in EVALUATOR_PROMPT
def test_evaluator_interested_example_mentions_rounds():
# interested 示例补"聊了几轮后"(轮次语境)
assert "聊了几轮后" in EVALUATOR_PROMPT
# ---- Task 5(spec §2/§4b): extractor cc_reply 单轮答语分类 ----
def test_extractor_cc_reply_field_and_double_gating():
"""cc_reply 可选字段说明 + 双门控声明(提示词侧; Python 侧等待态门控在
conversation._cc_round_end)——防止对无关问题的 yes 被误当号码确认。"""
assert "cc_reply" in EXTRACTOR_PROMPT
assert '{"affirm": true}' in EXTRACTOR_PROMPT
assert '{"affirm": false, "country": "Egypt"}' in EXTRACTOR_PROMPT
assert "仅当客户在回应号码确认时填写" in EXTRACTOR_PROMPT # 双门控声明
assert "自由文本国名" in EXTRACTOR_PROMPT # country 中英皆可