fix: 数据源审查小修(from_config 类型守卫/PROMPTS_KEYS 消重/契约措辞)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,13 @@ class PromptProvider:
|
|||||||
def from_config(cls, md: dict) -> "PromptProvider":
|
def from_config(cls, md: dict) -> "PromptProvider":
|
||||||
"""快照 prompts 组 → PromptProvider。缺键/非字符串显性炸带键名
|
"""快照 prompts 组 → PromptProvider。缺键/非字符串显性炸带键名
|
||||||
(DB 快照坏数据不留到 build_system 才炸)。"""
|
(DB 快照坏数据不留到 build_system 才炸)。"""
|
||||||
bad = [k for k in PROMPTS_KEYS if not isinstance((md or {}).get(k), str)]
|
if not isinstance(md, dict):
|
||||||
|
# 入口类型守卫(Task 6 的 PUT prompts 会接请求体): 字符串/
|
||||||
|
# 列表进来给 ValueError,不让 .get 的 AttributeError 漏成 500
|
||||||
|
raise ValueError(
|
||||||
|
f"prompts 必须是 dict: {{{', '.join(PROMPTS_KEYS)}}}: "
|
||||||
|
f"实际 {type(md).__name__}")
|
||||||
|
bad = [k for k in PROMPTS_KEYS if not isinstance(md.get(k), str)]
|
||||||
if bad:
|
if bad:
|
||||||
raise ValueError(f"prompts 配置缺少(或非字符串)键: {', '.join(bad)}")
|
raise ValueError(f"prompts 配置缺少(或非字符串)键: {', '.join(bad)}")
|
||||||
return cls(
|
return cls(
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ registry 只认本模块的 TenantSource 协议(async load_all / load_tenant),
|
|||||||
——旧路径 PromptProvider.from_dir 直读盘、不进快照;对齐后 DB 源(prompts
|
——旧路径 PromptProvider.from_dir 直读盘、不进快照;对齐后 DB 源(prompts
|
||||||
嵌 config_json)与文件源输出形态一致,registry 不再感知源差异。
|
嵌 config_json)与文件源输出形态一致,registry 不再感知源差异。
|
||||||
|
|
||||||
错误契约(两源一致,registry 依赖):
|
错误契约(registry 依赖,加载期显性失败不流入运行期):
|
||||||
- 未知 slug → LookupError(registry_reload 映射为 UnknownTenantError);
|
- 未知 slug → LookupError(registry_reload 映射为 UnknownTenantError);
|
||||||
- 数据坏(缺版本/坏 JSON/零站点/缺 wall·prompts 组)→ ValueError 带
|
- DB 源数据坏(缺版本/坏 JSON/非对象/零站点/缺 wall·prompts 组)→
|
||||||
租户名(加载期显性失败,不流入运行期)。
|
ValueError 带租户名(与版本号);
|
||||||
|
- 文件源数据坏:坏 JSON/slug 与目录不一致/零站点 → ValueError 带路径,
|
||||||
|
wall/prompts 文件缺失 → FileNotFoundError 带路径(read_text 原样)。
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import pytest
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.engine.services.ai_service import PromptProvider
|
from app.engine.services.ai_service import PROMPTS_KEYS, PromptProvider
|
||||||
from app.platform.db import (
|
from app.platform.db import (
|
||||||
Site,
|
Site,
|
||||||
SiteDomain,
|
SiteDomain,
|
||||||
@@ -36,8 +36,6 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
|||||||
SEED_DIR = REPO_ROOT / "seed_profiles"
|
SEED_DIR = REPO_ROOT / "seed_profiles"
|
||||||
JC_DOMAIN = "db.jc.example.com" # DB 源测试专属域名(不与文件源种子撞)
|
JC_DOMAIN = "db.jc.example.com" # DB 源测试专属域名(不与文件源种子撞)
|
||||||
|
|
||||||
_PROMPTS_KEYS = ("system", "company_facts", "extractor", "evaluator")
|
|
||||||
|
|
||||||
|
|
||||||
def _jiancai_snapshot(prefix="ai-kefu-jc"):
|
def _jiancai_snapshot(prefix="ai-kefu-jc"):
|
||||||
"""版本 config_json 构造助手:seed jiancai 的 config 组 + wall.json +
|
"""版本 config_json 构造助手:seed jiancai 的 config 组 + wall.json +
|
||||||
@@ -51,7 +49,7 @@ def _jiancai_snapshot(prefix="ai-kefu-jc"):
|
|||||||
(SEED_DIR / "jiancai" / "wall.json").read_text(encoding="utf-8"))
|
(SEED_DIR / "jiancai" / "wall.json").read_text(encoding="utf-8"))
|
||||||
snapshot["prompts"] = {
|
snapshot["prompts"] = {
|
||||||
name: (prompts_dir / f"{name}.md").read_text(encoding="utf-8")
|
name: (prompts_dir / f"{name}.md").read_text(encoding="utf-8")
|
||||||
for name in _PROMPTS_KEYS
|
for name in PROMPTS_KEYS
|
||||||
}
|
}
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,14 @@ def test_from_config_non_string_key_raises():
|
|||||||
"extractor": "e", "evaluator": "v"})
|
"extractor": "e", "evaluator": "v"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_config_non_dict_raises():
|
||||||
|
# 入口类型守卫: Task 6 的 PUT prompts 会把请求体直传这里——字符串/
|
||||||
|
# 列表进来必须是 ValueError,不能让 .get 的 AttributeError 漏成 500
|
||||||
|
for bad in ("system=...", ["system", "company_facts"], 42, None):
|
||||||
|
with pytest.raises(ValueError, match="dict"):
|
||||||
|
PromptProvider.from_config(bad)
|
||||||
|
|
||||||
|
|
||||||
def test_from_config_equals_from_dir():
|
def test_from_config_equals_from_dir():
|
||||||
d = JC_PROMPTS
|
d = JC_PROMPTS
|
||||||
cfg = {name: (d / f"{name}.md").read_text(encoding="utf-8")
|
cfg = {name: (d / f"{name}.md").read_text(encoding="utf-8")
|
||||||
|
|||||||
Reference in New Issue
Block a user