feat: TenantSource 抽象——File源(测试/档案)与DB源(生产),registry 数据源可切换+reload upsert

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-13 08:50:16 +08:00
parent 386b5eb90a
commit c7fd533320
9 changed files with 720 additions and 85 deletions
+30 -9
View File
@@ -47,6 +47,11 @@ def _format_deflection_pool(deflections) -> str:
)
# 四件套键名(单一事实源): PromptProvider 构造校验 / FileTenantSource 读盘
# / DbTenantSource 快照组形态, 三处消费同一清单
PROMPTS_KEYS = ("system", "company_facts", "extractor", "evaluator")
@dataclass(frozen=True)
class PromptProvider:
"""租户提示词四件套持有者(Task 6, R1 话术单一事实源)。
@@ -54,9 +59,12 @@ class PromptProvider:
frozen(Task 9 收口, Task 6 审查遗留): 实例跨租户运行时/模块级
DEFAULT_PROVIDER 共享, 不可变防共享单例被毒化。
- from_dir(dir): 读 system/company_facts/extractor/evaluator 四个 md,
存实例字段——提示词属于租户(seed_profiles/<slug>/prompts/),不再有
代码级 app/engine/prompts 副本;
- from_dir(dir): 读 system/company_facts/extractor/evaluator 四个 md
委托 from_config——提示词属于租户(seed_profiles/<slug>/prompts/),
不再有代码级 app/engine/prompts 副本;
- from_config(md: dict)(Phase 2 Task 4): 快照 prompts 组(dict 四键)
直接构造——registry 对文件/DB 两源的统一入口(文件源读盘入快照,
DB 源嵌 config_json);缺键/值非字符串 → 显性 ValueError 带键名;
- build_system(wall_deflections): system.md 的 $DEFLECTION_POOL 占位符
替换为按语言枚举的话术清单 + "\\n\\n" + company_facts(行为 prompt
在前、事实卡居中、当轮提醒最后拼接——拼接位在 AIService 内);
@@ -68,15 +76,28 @@ class PromptProvider:
extractor: str
evaluator: str
@classmethod
def from_config(cls, md: dict) -> "PromptProvider":
"""快照 prompts 组 → PromptProvider。缺键/非字符串显性炸带键名
(DB 快照坏数据不留到 build_system 才炸)。"""
bad = [k for k in PROMPTS_KEYS if not isinstance((md or {}).get(k), str)]
if bad:
raise ValueError(f"prompts 配置缺少(或非字符串)键: {', '.join(bad)}")
return cls(
system=md["system"],
company_facts=md["company_facts"],
extractor=md["extractor"],
evaluator=md["evaluator"])
@classmethod
def from_dir(cls, prompts_dir) -> "PromptProvider":
d = Path(prompts_dir)
return cls(
system=(d / "system.md").read_text(encoding="utf-8"),
company_facts=(d / "company_facts.md").read_text(encoding="utf-8"),
extractor=(d / "extractor.md").read_text(encoding="utf-8"),
evaluator=(d / "evaluator.md").read_text(encoding="utf-8"),
)
return cls.from_config({
"system": (d / "system.md").read_text(encoding="utf-8"),
"company_facts": (d / "company_facts.md").read_text(encoding="utf-8"),
"extractor": (d / "extractor.md").read_text(encoding="utf-8"),
"evaluator": (d / "evaluator.md").read_text(encoding="utf-8"),
})
def build_system(self, wall_deflections=None) -> str:
system = self.system