主题
AI 角色注册表与模型矩阵
本章目标:
- 理解"指挥室"——为什么 a-cdm 要在
config.yaml之外再造一个ai_roles.yaml注册表- 掌握 provider/role 二级模型、
managed_by边界、五个错误码各自语义- 分清启动期 fail-fast(结构错)与 graceful degradation(凭据空)两种失败模式
TL;DR
acdm-backend/app/util/ai_registry.py + acdm-backend/app/ai_roles.yaml 是 a-cdm 所有 AI 功能的"指挥室":每个 AI 用途登记成一个 role,指定走哪个 provider(LLM 网关)+ 哪个 model。注册表在 FastAPI 启动期一次性校验:结构/引用错误直接 fail-fast 让进程退出,而凭据缺失只标 unavailable、请求时返 503(graceful degradation,避免"代码先于 .env 部署"拖垮服务)。managed_by: deer-flow 的 role 只是"全景视图",acdm-backend 拒绝为它取 client(错误码 50403)——真实控制点在引擎 config.yaml。
Overview(为什么要有"指挥室")
引擎 config.yaml 的 models[] 解决的是"引擎自己用哪些模型"。但 a-cdm 里调用 LLM 的不止引擎:acdm-backend 还有 wiki AI 改写、截图建会日历解析、会议描述生成、SOW 解析、单会议 atom 抽取等一堆业务侧 LLM 调用。如果这些散落在各 service 里各自 new OpenAI(...),会出现:
- 换网关/换模型要改 N 处代码;
- 没人说得清"全项目到底有多少处在调 LLM、各用什么模型";
- 凭据缺失要等运行到那行代码才报错。
ai_roles.yaml 的设计哲学是把"谁在用 AI、用什么"集中成一张声明式清单(文件头注释 acdm-backend/app/ai_roles.yaml:1-17):provider 段声明可用网关及凭据,roles 段声明每个 AI 功能用哪个 provider+model。代码只认 role_id,换模型只改 YAML。这与引擎 config.yaml 是两套并行体系,通过 managed_by 字段标注边界互不越权。
Architecture:provider/role 二级模型
| 概念 | 定义 | 字段 | Source |
|---|---|---|---|
| provider | 一个 LLM 网关(凭据 + base_url + 启用状态 + 参数适配) | base_url api_key enabled payload_constraints | acdm-backend/app/ai_roles.yaml:19-54 |
| role | 一个具体 AI 用途,绑定一个 provider + model | id provider model capabilities managed_by enabled | acdm-backend/app/ai_roles.yaml:57-164 |
当前三个 provider:jointpilot(公司主网关,默认,2026-05-08 起)、aicc(备用,embedding/MiniMax,默认禁用)、ark(火山方舟,已封存,公司项目永久禁用)。当前 13 个 role,分三类:
| 类别 | 数量 | managed_by | acdm-backend 能否取 client | 示例 | Source |
|---|---|---|---|---|---|
| acdm-backend 直管 | 6 | 无 | ✓ | wiki_ai_rewrite vision_calendar meeting_atom_extract | ai_roles.yaml:59-97 |
| deer-flow 间管 | 6 | deer-flow / acdm-backend | ✗(50403)/✓ | chat_lead_agent weekly_report title_generation | ai_roles.yaml:101-151 |
| future 占位 | 1 | 无,enabled: false | ✗(50404) | insight_embedding(bge-m3) | ai_roles.yaml:157-163 |
managed_by: deer-flow 的 role 在 yaml 里只是"指挥室全景视图"——让运维一眼看全项目所有 AI 用途,但 acdm-backend 调 get_chat_client() 时若命中这类 role,_resolve_role() 直接抛 BusinessException(50403) 并提示"改 model 请去 deer-flow/config.yaml.models[] 段"(ai_registry.py:302-308)。这是一道防止跨边界误配置的护栏。
Components:工厂与生命周期
AIRegistry 单例(ai_registry.py:144)
启动期由 FastAPI lifespan 调一次 init_registry()(acdm-backend/app/main.py:148-153),加载 + 校验 yaml。核心工厂方法:
| 方法 | 返回 | 用途 | Source |
|---|---|---|---|
get_chat_client(role_id) | (AsyncOpenAI, model_id) | 非流式 chat/vision | ai_registry.py:327 |
get_streaming_client(role_id) | (timeout, base_url, api_key, model) | SSE 流式 | ai_registry.py:342 |
_resolve_role(role_id) | (role, provider) | 内部:校验并解析 role | ai_registry.py:281 |
list_ai_roles() / list_providers() | list/dict(含 health) | admin 清单端点 | ai_registry.py:551 :595 |
record_success/error() | — | LLMClient 调用后回写健康状态 | ai_registry.py:414 :443 |
probe_streaming_roles() | dict | 启动期 + 按需健康探测 | ai_registry.py:472 |
_resolve_env(value)(ai_registry.py:74)处理 $VARNAME:优先 os.environ,否则 fallback 到 pydantic settings 默认,避免"env 没设→解析空→URL 没 scheme"的隐性失败。
两种失败模式(关键设计)
启动期校验在 AIRegistry.load()(ai_registry.py:162)里区分两类失败,处理方式截然不同:
- fail-fast(结构错):role 引用不存在的 provider、role id 重复、payload_constraints 结构非法等 → 抛
AIRegistryConfigError,进程启动失败。这是"代码/配置写错"的硬错误,不能带病上线。 - graceful degradation(凭据空):启用的 role 所依赖 provider 的
api_keyenv 为空 → 不 fail-fast,登记_unavailable_providers,请求时返 503(ai_registry.py:153-154、:219、:244-250)。这是 2026-05-08 L94 教训的修复:历史上段 1 上线时JOINTPILOT_API_KEY没就位 → ai_registry fail-fast → acdm-backend 停摆 8 分钟。改成 graceful 后,凭据空只让相关 role 503,不拖垮整个后端。
红线
CLAUDE.md:新增/rotatexxx_API_KEY等 env 时,必须先 ssh prod 加 .env → 再 merge → 再 force-recreate,不可"先 merge 后改 .env"。graceful degradation 是兜底,SOP 仍是先改 env。
错误码速查
| 码 | 常量 | 含义 | 触发 | Source |
|---|---|---|---|---|
| 50401 | ERR_REGISTRY_NOT_LOADED | 注册表未加载 | 未调 init_registry | ai_registry.py:64 |
| 50402 | ERR_ROLE_NOT_FOUND | role 不存在 | role_id 拼错 | ai_registry.py:65 |
| 50403 | ERR_ROLE_DEER_FLOW_MANAGED | role 由 deer-flow 管理 | acdm-backend 试图取 deer-flow 直管 role 的 client | ai_registry.py:66 |
| 50404 | ERR_ROLE_DISABLED | role enabled=false | 调用未启用 role(如 insight_embedding) | ai_registry.py:67 |
| 50405 | ERR_PROVIDER_UNAVAILABLE | provider 凭据缺失 | graceful degradation 命中 | ai_registry.py:68 |
Implementation:provider 参数适配
不同模型对 payload 参数容忍度不同,payload_constraints 让 registry 在请求发出前统一处理(ai_roles.yaml:30-40):
yaml
# 摘自 acdm-backend/app/ai_roles.yaml:30-40 — jointpilot.payload_constraints
unsupported_params: [temperature] # kimi-k2.6 强制 temperature=1,caller 传别的会被 server 400;registry 删掉
force_overrides:
thinking:
type: disabled # acdm-backend 直管 4 role 都是工具型短任务,thinking 没价值且会挤光 contentapply_payload_constraints(role_id, payload):删掉unsupported_params里的 key;get_extra_body(role_id):把force_overrides注入 OpenAI SDK 的extra_body,覆盖 caller 传入值。
这解决了一个真实坑:kimi-k2.6 默认开 thinking,reasoning 走 reasoning_content,普通 content 字段会被挤光 —— 短任务(会议描述/wiki 改写)拿到空字符串就失败。统一在 registry 层禁掉,业务代码无需关心。
模型矩阵(引擎侧 config.yaml)
注册表的 chat_lead_agent 等 deer-flow 直管 role 真实跑哪个模型,由引擎 deer-flow/config.yaml:37-211 的 models[] 决定。火山方舟 Coding Plan 矩阵(已封存为备份,当前主网关 jointpilot)按评测排序:
| 模型 | 评测/用途 | thinking | vision | Source |
|---|---|---|---|---|
doubao-seed-2.0-pro | 评测第 1,综合 73.9 | ✓ | ✓ | deer-flow/config.yaml:47-63 |
doubao-seed-2.0-lite | 性价比王,速度最快 | ✓ | deer-flow/config.yaml:66-81 | |
glm-4.7 | 评测第 3,人名抓得最全 | ✓ | deer-flow/config.yaml:119-134 | |
kimi-k2.6 | 公司网关(jointpilot),当前主力 | ✓ | ✓ | deer-flow/config.yaml:177-193 |
前端切模型走 /api/models(引擎 Gateway)不走本注册表(ai_roles.yaml:112),这就是为什么 chat_lead_agent 标 managed_by: deer-flow。
Common Pitfalls / 实战 Tips
- 改
ai_roles.yaml必须--force-recreate:文件头注释ai_roles.yaml:17明确,docker compose restart不重读。 - 想给 acdm-backend 业务换模型:改
ai_roles.yaml对应 role 的model;想给引擎 chat 换模型:改deer-flow/config.yaml的models[]并通过前端模型卡片切换。别在ai_roles.yaml改chat_lead_agent期望生效(50403)。 - 凭据 503 不是 bug:graceful degradation 下 provider 凭据空时 role 返 503 是预期,查
.env是否就位、是否--force-recreate过。 - admin 看全景:
GET /admin/ai-roles返回所有 role + health,POST /admin/ai-roles/probe主动探测 streaming role 健康。
References
acdm-backend/app/ai_roles.yaml:1-164— 注册表配置全文(provider/role 声明)acdm-backend/app/util/ai_registry.py:144-685— AIRegistry 实现(load/工厂/健康/错误码)acdm-backend/app/util/ai_registry.py:281-326—_resolve_role五种异常分支acdm-backend/app/main.py:148-180— lifespan 中 init_registry + probe_streaming_rolesacdm-backend/app/api/admin_ai_roles_router.py— admin 清单与探测端点deer-flow/config.yaml:37-211— 引擎侧模型矩阵(deer-flow 直管 role 真实控制点)
Related Pages
| Page | Relationship |
|---|---|
| deer-flow 引擎配置体系 | 本章注册表与该章 config.yaml models 是两套并行模型配置,via managed_by 划界 |
| 环境变量、凭据与降级开关 | 本章 graceful degradation 依赖该章的 env 凭据与部署 SOP |
| acdm-backend 控制面架构 | 本章注册表由该章 lifespan 启动序列初始化 |
| 审计、SSE 与后台任务 | 本章 meeting_atom_extract 等 role 被该章后台 worker 调用 |
| 控制面与引擎的耦合契约 | 本章 managed_by: deer-flow 边界是该章耦合契约的一部分 |