chore: 从汽车AI客服项目拷贝骨架并清理(blog页/redis)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""公司事实卡: 拼接位置与墙安全性静态测试。"""
|
||||
from pathlib import Path
|
||||
|
||||
from app.services.ai_service import AIService, COMPANY_FACTS, SYSTEM_PROMPT
|
||||
|
||||
FACTS_PATH = Path("app/prompts/company_facts.md")
|
||||
|
||||
|
||||
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 "Hesu" in p # 事实在场
|
||||
assert p.index(SYSTEM_PROMPT) < p.index(COMPANY_FACTS) < p.index("⚠️ 提醒")
|
||||
|
||||
|
||||
def test_facts_absent_from_extractor_prompt():
|
||||
# extractor/evaluator 不拼事实卡(它们有自己的 prompt,不该看到公司事实)
|
||||
from app.services.ai_service import EVALUATOR_PROMPT, EXTRACTOR_PROMPT
|
||||
assert "Hesu" not in EXTRACTOR_PROMPT
|
||||
assert "Hesu" not in EVALUATOR_PROMPT
|
||||
|
||||
|
||||
from app.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()
|
||||
Reference in New Issue
Block a user