Files
ai-kefu-admin/tests/test_company_facts.py
T
浅念 1824bb1590 test: PromptProvider 审查修复(组装缝断言/占位符约定文档与glob守护/边界注记)
- I-1: test_company_facts 捕获完整 prompt 的用例补话术池注入断言
  ("- EN:"/"- ZH:" 分组行在场)——守 AIService 组装缝,防 Task 9
  重接提示词线时静默丢池
- I-2: seed_profiles/README.md 落档 $DEFLECTION_POOL 占位符约定
  (缺失 = 话术池静默不注入);test_prompt_provider 占位符防漂移改
  glob 遍历 seed_profiles/*/prompts/system.md,第三租户自动纳管
  (空 glob 显性兜底,防假守护)
- Minor-1 注记: _format_deflection_pool docstring 记已知边界——条目
  含引号/换行暂不转义,Phase 2 编辑器批次处理
- Minor-5: test_provider_loads_from_tenant_dir 删恒真 or 侧,改单一
  明确断言;registry 加载期显性失败清单补 prompts 读取失败
  (FileNotFoundError 说明);conftest build_jiancai_runtime 注明
  data_dir 仅占位、从不 ensure_ready

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 20:16:40 +08:00

98 lines
4.2 KiB
Python

"""公司事实卡: 拼接位置与墙安全性静态测试。
Task 6 迁移: 提示词四件套随租户化落位 seed_profiles/<slug>/prompts/,
代码级默认 = DEFAULT_PROVIDER(jiancai 出厂,与 seed_profiles/jiancai/
prompts 逐字节同源)——本文件断言对象从模块常量改为 DEFAULT_PROVIDER
字段,断言语义等价。
"""
from app.engine.services.ai_service import AIService, DEFAULT_PROVIDER
SYSTEM_PROMPT = DEFAULT_PROVIDER.system
COMPANY_FACTS = DEFAULT_PROVIDER.company_facts
EXTRACTOR_PROMPT = DEFAULT_PROVIDER.extractor
EVALUATOR_PROMPT = DEFAULT_PROVIDER.evaluator
class _CapturingProvider:
def __init__(self):
self.prompts = []
async def chat(self, messages, system_prompt):
self.prompts.append(system_prompt)
return "ok"
async def stream_chat(self, messages, system_prompt):
self.prompts.append(system_prompt)
for t in ("ok",):
yield t
def test_facts_concatenated_between_system_and_context():
# 顺序: 行为 prompt → 事实卡 → 当轮提醒(提醒必须最后,保持最新鲜)
svc = AIService(provider=_CapturingProvider())
svc.provider.prompts.clear()
import asyncio
asyncio.run(svc.chat([{"role": "user", "content": "hi"}], context="⚠️ 提醒"))
p = svc.provider.prompts[0]
assert "HESUPIPE" in p and "HDPE" in p # 建材事实在场
# 审查 I-1: AIService 组装缝守护——默认服务的 $DEFLECTION_POOL 话术池
# 必须真实注入(锚分组行格式而非具体话术,话术内容随墙演进而变);
# 防 Task 9 重接提示词线时静默丢池
assert "- EN:" in p and "- ZH:" in p
# Task 6: system 组装含 $DEFLECTION_POOL 话术池注入,SYSTEM_PROMPT 原文
# 不再是组装结果的连续子串——改以首行/事实卡标题锚定顺序(语义等价)
assert (p.index(SYSTEM_PROMPT.splitlines()[0])
< p.index("# HESUPIPE 公司事实卡") < p.index("⚠️ 提醒"))
def test_facts_absent_from_extractor_prompt():
# extractor/evaluator 不拼事实卡(它们有自己的 prompt,不该看到公司事实)
# Task 4 起 extractor/evaluator 含通用品类词(HDPE/PVC等字段示例)——
# 隔离断言只针对品牌/公司事实标记(HESUPIPE),不再卡通用品类词
assert "HESUPIPE" not in EXTRACTOR_PROMPT
assert "HESUPIPE" not in EVALUATOR_PROMPT
# 河南和硕/和硕集团 等公司主体信息同样不得泄入
assert "和硕" not in EXTRACTOR_PROMPT
assert "和硕" not in EVALUATOR_PROMPT
def test_facts_no_auto_words():
# 建材化红线: 事实卡不得残留汽车项目词汇
# (只查 COMPANY_FACTS——SYSTEM_PROMPT 属 Task 3,不在本测试管辖)
# 全部 lowercase 匹配: 大小写混杂的 "BYD"/"bYd" 一网打尽,不分策略
for word in ("byd", "hesu auto", "toyota", "vehicle", "sedan", "suv",
"zeeker", "nissan", "geely"):
assert word not in COMPANY_FACTS.lower(), word
from app.engine.services.boundary_filter import (
no_contact_value, split_sentences, violates_boundary,
)
def _fact_sentences():
# 注: 事实卡是无句读的 markdown 列表,多行会合并成大句块——无害,
# 检测是 search 式,违规在任何块内都能命中(草案已实证过两墙全清)
complete, tail = split_sentences(COMPANY_FACTS)
return complete + ([tail] if tail else [])
def test_facts_pass_both_output_walls():
# AI 原样引用事实也必须安全: 每句过价格墙+零号码墙
# (陷阱实证: "五万"会被裸预算模式击杀、"累计出口五万台"→整句被替换成要号话术)
for s in _fact_sentences():
assert not violates_boundary(s), s[:60]
assert not no_contact_value(s), s[:60]
def test_facts_length_capped():
# 防营销手册化: 事实卡是卡片不是 brochure——超限=该精简或走检索方案
assert len(COMPANY_FACTS) < 2500
def test_facts_no_contact_channel():
# 我方联系方式绝不入卡(红线: web 指按钮/App 反向留资)
# 只查确定性标记: 邮箱@与 wa.me 链接("WhatsApp"品牌词是合法内容,不作标记)
assert "@" not in COMPANY_FACTS
assert "wa.me" not in COMPANY_FACTS.lower()