diff --git a/app/engine/services/ai_service.py b/app/engine/services/ai_service.py index 9bedf3b..161b255 100644 --- a/app/engine/services/ai_service.py +++ b/app/engine/services/ai_service.py @@ -32,6 +32,8 @@ def _format_deflection_pool(deflections) -> str: 紧凑不膨胀(设计 R1: 话术单一事实源,提示词侧只引用不复制)。 只渲染主池(deflections);deflections_contact 是已留资态的墙替换池, 本就不属于 system.md 教学内容,不注入。 + 已知边界(审查 Minor-1 注记): 条目自身含双引号/换行时暂不转义—— + 现网池线均为单行纯文本,Phase 2 后台编辑器批次统一处理(输入校验+转义)。 """ pools = dict(deflections or {}) langs = [l for l in _DEFLECTION_LANG_ORDER if pools.get(l)] diff --git a/app/platform/registry.py b/app/platform/registry.py index 9978db6..9cfc77a 100644 --- a/app/platform/registry.py +++ b/app/platform/registry.py @@ -135,7 +135,9 @@ class TenantRegistry: 加载期显性失败(不流入运行期):目录无任何 tenant.json(seed_dir 路径拼错→静默空注册表→全量 404,必须炸)、无效 JSON、slug 与目录 - 名不一致、零站点、wall 编译错误、域名重复绑定。 + 名不一致、零站点、wall 编译错误、域名重复绑定、prompts 读取失败 + (Task 6: prompts/ 目录缺失或四件套不齐 → PromptProvider.from_dir + 的 FileNotFoundError,加载即炸,不流入运行期)。 """ reg = cls() reg._seed_dir = pathlib.Path(seed_dir) diff --git a/seed_profiles/README.md b/seed_profiles/README.md index 83d1c01..b58ee43 100644 --- a/seed_profiles/README.md +++ b/seed_profiles/README.md @@ -21,7 +21,7 @@ seed_profiles// - **空域名 + `data-site`**:`es` 站 `"domains": []`,与 main 共用挂件域名,嵌入代码以 `data-site="es"` 参数选择站点。 - **`site.brand` 是覆盖层**:缺省项继承 `config.brand`(深合并),只覆盖门面(标题/欢迎语等);AI 脑子与线索管道留在租户级。 - **wall 不写在 tenant.json 里**:加载器会把同目录 `wall.json` **隐式并入** `config["wall"]`(原始词表,供后台编辑/回滚),编译产物挂 runtime 的 `compiled_wall` 供引擎消费——词表数据源只有 wall.json 一份。 -- **`config` 内不含 prompts**:提示词由目录内 `prompts/*.md` 提供(Task 6)。 +- **`config` 内不含 prompts**:提示词由目录内 `prompts/*.md` 提供(Task 6);**system.md 必须包含 `$DEFLECTION_POOL` 占位符**——组装时注入 wall.json 的 deflections 话术池(缺失 = 话术池静默不注入;test_prompt_provider 以 glob 遍历守护全部租户)。 - 加载期显性失败(不流入运行期):无效 JSON、slug 与目录名不一致、零站点、wall 编译错误、域名跨站点/跨租户重复绑定。 ## 纪律 diff --git a/tests/conftest.py b/tests/conftest.py index fbf6707..547252d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -82,7 +82,8 @@ def build_jiancai_runtime(): engine/session_factory 保持 None: 直连测试自管 async_db(NullPool 跨 循环纪律),manager.session_factory 为 None 时 lead_push_service 回退 模块级兜底,与旧 runtime=None 行为一致。无 engine 即无句柄,无需清理; - data_dir 仅拼 db_path 字符串(无 IO),模块级缓存单例跨测试复用。 + data_dir 仅占位(只拼 db_path 字符串,无任何 IO——从不 ensure_ready, + 不会创建 tenants/ 目录或库文件),模块级缓存单例跨测试复用。 """ global _JIANCAI_RUNTIME if _JIANCAI_RUNTIME is None: diff --git a/tests/test_company_facts.py b/tests/test_company_facts.py index dfafb95..1c30711 100644 --- a/tests/test_company_facts.py +++ b/tests/test_company_facts.py @@ -35,6 +35,10 @@ def test_facts_concatenated_between_system_and_context(): 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]) diff --git a/tests/test_prompt_provider.py b/tests/test_prompt_provider.py index 9211bc4..6458c11 100644 --- a/tests/test_prompt_provider.py +++ b/tests/test_prompt_provider.py @@ -30,7 +30,7 @@ def _wall(slug: str) -> dict: def test_provider_loads_from_tenant_dir(): p = PromptProvider.from_dir(JC_PROMPTS) - assert "HESUPIPE" in p.company_facts or "管道" in p.system + assert "HESUPIPE" in p.company_facts # jiancai 事实卡身份标记(审查 Minor-5: 删恒真 or 侧) def test_provider_reads_all_four_prompts(): @@ -57,9 +57,13 @@ def test_system_prompt_injects_deflection_pool(): def test_system_md_carries_placeholder(): - # 防漂移锚: 两租户 system.md 必须保留占位符,丢了它话术池就静默失联 - for prompts_dir in (JC_PROMPTS, AUTO_PROMPTS): - assert "$DEFLECTION_POOL" in PromptProvider.from_dir(prompts_dir).system + # 防漂移锚(审查 I-2): glob 遍历全部租户 system.md——第三租户落地即自动 + # 纳管(约定同步落档 seed_profiles/README.md: 缺占位符 = 话术池静默不注入); + # 空 glob 恒绿 = 假守护,先断言至少一份 + system_mds = sorted((REPO / "seed_profiles").glob("*/prompts/system.md")) + assert system_mds + for md in system_mds: + assert "$DEFLECTION_POOL" in md.read_text(encoding="utf-8"), md def test_build_system_appends_facts_after_system():