Files
deepseek-harness/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md
2026-07-24 14:55:54 +08:00

6.9 KiB
Raw Blame History

Agent Note: 建议性 LLM 目录与 ACP 会话级模型选择

Status: implemented

English | 中文

问题

基于提供方路由的适配器允许每次请求选择 provider + model,但 LlmService 只暴露路由和流式调用。UI 无法发现已注册的提供方也无法知道适配器愿意推荐哪些模型。因此ACP 客户端收不到 model 会话配置项即使请求接缝已经支持运行时切换Zed、JetBrains 和 VS Code 集成仍没有模型列表。

模型发现不能变成请求校验。手写 DeepSeek 适配器会把任意模型 ID 原样转发给公开或私有端点,而 pi-ai 的有限安装目录则是其自身请求解析的权威依据。将共享目录视为白名单,会破坏提供方路由需要保留的私有端点能力。

ACP 选择还必须保留提供方维度。同一个模型 ID 可能存在于多个路由下;切换全局适配器或 agent 模板会让一个编辑器会话的选择泄漏到其他会话。Prompt 变量与请求路由必须同时变化;如果选择发生在异步 prompt 组装期间,不能让 {{model}} 表示一个模型、实际请求却到达另一个模型。

决策

提供方中立的建议性发现

LlmAdapter 增加 providerInfo(provider) 与异步 listModels(provider) 方法。其提供方中立结果分别为 LlmProviderInfo { id, name }LlmModelInfo { provider, id, name, description? }。默认实现以路由名称作为提供方名称,并且不展示模型,从而保持现有适配器行为。

LlmService.listProviders() 按注册顺序返回分离后的元数据。LlmService.listModels(provider) 委托给路由所有者,校验非空 ID 和名称,并在提供方不匹配或模型 ID 重复时以 INVALID_CATALOG 失败,最后返回分离后的值。未知提供方仍以 NO_ADAPTER 失败。提供方元数据在 registerAdapter() 期间进行原子校验,错误展示记录不会留下部分注册。

目录成员关系仅提供建议。它驱动选择器与诊断,但不会改变 stream() 路由,也不会拒绝原本有效的请求。提供方所有权仍然具有排他性并绑定生命周期;模型 ID 仍是请求时传给适配器的输入。

dsh-llm-pi-ai 将已配置提供方的安装目录 getModels(provider) 映射为中立目录。其现有请求时目录查询仍是权威依据,未知模型仍以 UNKNOWN_MODEL 失败。dsh-llm-deepseek 接受可选的 models 配置作为展示条目,默认包含名为 DeepSeek-V4-Flashdeepseek-v4-flash 和名为 DeepSeek-V4-Prodeepseek-v4-pro。显式列表会替换这些默认值,空列表则关闭发现。这些条目改善已知公开或私有模型的选择体验,而所有未列出的模型 ID 仍会原样透传。

ACP 会话配置项

当会话具有完整目标且目标提供方已注册时ACP bridge 会在 session/newsession/load 中展示一个 id: modelcategory: model 的选择项。每个不透明选项值都编码完整的提供方/模型字段组合。存在多个非空提供方分组时按提供方分组;只有一个分组时将其展开,以便对简单选择器支持更好的客户端展示。

如果适配器目录未包含会话当前目标,该目标仍会加入展示选项。这能保留自定义 DeepSeek 与私有端点模型,同时维持目录的建议性。提供方未注册的目标不会展示;缺少模型的 agent 仍可由其他 agent/request 提供者补齐。

session/set_config_option 只接受当前目录快照中的值,并更新该 ACP 会话独占的目标引用。它不会修改全局 LlmServiceAgentOptions 状态,因此并发会话可以选择不同的提供方和模型。现有权限选择项保持独立,每次响应都返回完整的刷新后配置项状态。

Prompt/请求一致性与持久化

Agent setup 会安装作用域内的 system-prompt/assembleagent/request 监听器。Prompt 组装为每个 step 只快照一次选中的字段组合,在下游 prompt 监听器完成后覆盖组装结果中的 providermodel 变量;请求监听器则在下游请求监听器完成后应用同一个快照。因此,异步组装期间发生的选择会从下一个 step 生效,不会导致 prompt 文本与路由分裂。其他调用配置字段保持不变。

请求头仍是持久化事实来源。当选中目标被实际使用时,现有的完整 request/header 快照会记录它。session/load 先从折叠后的最后请求头初始化 ACP 选择,再回退到 bridge 配置。一个从未被请求使用的选择只保留在内存中,因为它从未成为模型可见状态。

本功能不使用 ACP 的实验性 providers/* 能力。该草案接口配置提供方 base URL、协议和 headers其中可能包含密钥它不枚举模型并且会赋予 UI 改写部署所有的适配器配置的权力。

考虑过的替代方案

只返回模型字符串。 仅模型值会丢失提供方路由;两个提供方暴露相同 ID 时立刻产生歧义。

将目录设为强制白名单。 这与手写适配器的任意模型透传和私有部署冲突。请求的权威校验本就属于被选中的适配器。

将选择存入 AgentOptionsLlmService 这些对象分别面向创建过程或整个部署。修改它们会耦合并发 ACP 会话,并绕开带日志归因的 agent/request 替换路径。

立即写入新的模型选择会话事件。 尚未使用的 UI 选择没有影响模型请求。目标被消费时记录现有请求头,既满足“模型可见当且仅当已记录”的规则,也不会引入第二个事实来源。

使用 ACP providers/* 该不稳定 API 用于修改端点与认证配置,而不是为单个会话选择模型;其生命周期和密钥处理语义都不适合本功能。

结果

  • 任意适配器都能暴露动态模型列表,无需把提供方库类型泄漏到核心接缝。
  • 目录消费者必须把缺失理解为“未展示”,而不是“请求无效”。
  • 基于 pi-ai 的 ACP 部署会自动继承已安装的 pi-ai 提供方目录;手写 DeepSeek 部署显式列出已知选项,同时保留任意模型能力。
  • ACP 客户端会收到稳定标准的模型配置项,其中的值保留提供方信息,并按会话隔离。
  • 请求头继续使用基于提供方路由的会话结构;不需要增加 JSONL 事件或格式版本。
  • 目录读取可以是异步的。ACP 在创建或恢复 agent 前读取分离后的快照,因此发现失败不会留下部分发布的会话。

测试

单元测试覆盖目录分离与错误元数据、pi-ai 和 DeepSeek 目录投影、ACP 提供方分组、自定义当前模型补入、无效值、提供方/模型请求路由、prompt 变量一致性、并发会话隔离、无模型回退,以及从请求头恢复选择。现有 ACP 传输测试验证新增配置项不会改变 prompt、取消、回放、审批或工具展示行为。