feat: 服务层配置接线——email/push/rate_limit/AI凭据/行为开关/映射表/种子键
Task 9: 服务层对全局 settings 的业务引用清零,每个 DEFAULT_CONFIG 组 都有真实消费方。resolve_*_settings(email/push/ai)以空槽回退 env (平台默认层);contact 行为开关/种子白名单/国名扩表/限流上限经 manager 的 runtime.config 接线。收口: lead_push session_factory 必填、 config.py 死代码清理、PromptProvider frozen、no_contact 视图直取、 AIService wall 参数定型 CompiledWall。403 测试全绿(389+14)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import AsyncGenerator
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from app.engine.providers.base import AIProvider
|
||||
from app.engine.providers.glm import GLMProvider
|
||||
from app.engine.config import settings
|
||||
from app.engine.services.boundary_filter import DEFAULT_WALL
|
||||
from app.engine.wall_schema import CompiledWall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,9 +47,13 @@ def _format_deflection_pool(deflections) -> str:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PromptProvider:
|
||||
"""租户提示词四件套持有者(Task 6, R1 话术单一事实源)。
|
||||
|
||||
frozen(Task 9 收口, Task 6 审查遗留): 实例跨租户运行时/模块级
|
||||
DEFAULT_PROVIDER 共享, 不可变防共享单例被毒化。
|
||||
|
||||
- from_dir(dir): 读 system/company_facts/extractor/evaluator 四个 md,
|
||||
存实例字段——提示词属于租户(seed_profiles/<slug>/prompts/),不再有
|
||||
代码级 app/engine/prompts 副本;
|
||||
@@ -57,13 +63,10 @@ class PromptProvider:
|
||||
- build_extractor/build_evaluator: $existing_info/$messages/$profile/
|
||||
$recent_messages 替换逻辑自 AIService 原样搬入(行为等价)。
|
||||
"""
|
||||
|
||||
def __init__(self, system: str, company_facts: str,
|
||||
extractor: str, evaluator: str):
|
||||
self.system = system
|
||||
self.company_facts = company_facts
|
||||
self.extractor = extractor
|
||||
self.evaluator = evaluator
|
||||
system: str
|
||||
company_facts: str
|
||||
extractor: str
|
||||
evaluator: str
|
||||
|
||||
@classmethod
|
||||
def from_dir(cls, prompts_dir) -> "PromptProvider":
|
||||
@@ -100,25 +103,54 @@ DEFAULT_PROVIDER = PromptProvider.from_dir(
|
||||
Path(__file__).resolve().parents[3] / "seed_profiles" / "jiancai" / "prompts")
|
||||
|
||||
|
||||
def create_provider() -> AIProvider:
|
||||
if settings.AI_PROVIDER == "glm":
|
||||
# model 的出厂默认值——视同"未设置"哨兵: 租户未定制(merged 值 == 出厂默认)
|
||||
# 时回退 env AI_MODEL,部署级模型选择不因接线失效。显式配了其他值则用租户的。
|
||||
_FACTORY_DEFAULT_MODEL = "glm-4-flash"
|
||||
|
||||
|
||||
def resolve_ai_settings(config_data: dict | None) -> dict:
|
||||
"""租户 config.ai → provider 实例化参数(Task 9 接线; env 回退)。
|
||||
|
||||
api_key/base_url 空槽(None/空串)→ env 智谱凭据(平台默认集团 key,
|
||||
spec §5.5 "用平台默认"状态下租户侧不存密钥);model 空或等于出厂默认
|
||||
→ env AI_MODEL。
|
||||
"""
|
||||
ai = ((config_data or {}).get("ai")) or {}
|
||||
model = ai.get("model")
|
||||
if not model or model == _FACTORY_DEFAULT_MODEL:
|
||||
model = settings.AI_MODEL or _FACTORY_DEFAULT_MODEL
|
||||
return {
|
||||
"provider": ai.get("provider") or settings.AI_PROVIDER,
|
||||
"api_key": ai.get("api_key") or settings.ZHIPU_API_KEY,
|
||||
"base_url": ai.get("base_url") or settings.ZHIPU_BASE_URL,
|
||||
"model": model,
|
||||
}
|
||||
|
||||
|
||||
def create_provider(config_data: dict | None = None) -> AIProvider:
|
||||
"""provider 工厂(Task 9: 按租户配置实例化 glm;None 走 env 平台默认)。"""
|
||||
cfg = resolve_ai_settings(config_data)
|
||||
if cfg["provider"] == "glm":
|
||||
return GLMProvider(
|
||||
api_key=settings.ZHIPU_API_KEY,
|
||||
model=settings.AI_MODEL,
|
||||
base_url=settings.ZHIPU_BASE_URL,
|
||||
api_key=cfg["api_key"],
|
||||
model=cfg["model"],
|
||||
base_url=cfg["base_url"],
|
||||
)
|
||||
raise ValueError(f"Unknown AI provider: {settings.AI_PROVIDER}")
|
||||
raise ValueError(f"Unknown AI provider: {cfg['provider']}")
|
||||
|
||||
|
||||
class AIService:
|
||||
def __init__(self, provider: AIProvider | None = None,
|
||||
prompts: PromptProvider | None = None,
|
||||
wall_deflections: dict | None = None):
|
||||
self.provider = provider or create_provider()
|
||||
wall: CompiledWall | None = None,
|
||||
config_data: dict | None = None):
|
||||
self.provider = provider or create_provider(config_data)
|
||||
# Task 6: 提示词经 PromptProvider 注入(租户四件套);默认 = jiancai
|
||||
# 出厂 + 出厂墙话术池(与 DEFAULT_PROVIDER 同源,单一事实源 wall.json)
|
||||
self.prompts = prompts or DEFAULT_PROVIDER
|
||||
self.wall_deflections = (wall_deflections if wall_deflections is not None
|
||||
# Task 9 收口: wall 参数定型为 CompiledWall(话术池只取 .deflections,
|
||||
# 与 prompts 同为租户运行时产物);缺省 = jiancai 出厂墙
|
||||
self.wall_deflections = (wall.deflections if wall is not None
|
||||
else DEFAULT_WALL.deflections)
|
||||
# 对话基座: 行为 prompt 在前(红线显著性),事实卡居中(参考资料),
|
||||
# 当轮 context 提醒最后拼接(在 chat/stream_chat 内,保持最新鲜)
|
||||
|
||||
Reference in New Issue
Block a user