← 返回案例集合

Case 001 · Agent runtime feature

让 Agent Team 的队友使用不同模型

我把 spawn_teammate 从工具入口追到子 Session,补上模型路由缺失的数据链路,同时保留 fresh / fork 的上下文语义、部署白名单和重启恢复能力。

项目DeepSeek Harness
类型Feature / Runtime
实现提交b7db12d201
改动规模29 files · +416 / −88
当前状态Fork 分支已实现,未声明上游合入

先用大白话说

原来的 Agent Team 像一个只能给所有员工配同款电脑的团队:主 Agent 用什么模型,新建的队友就跟着用什么模型。我们补上的能力,是让主 Agent 在允许的清单里,为“查资料”“写代码”“做审计”等不同岗位选择不同模型,并且把这次选择存进子会话,重启以后也不会丢。

这个需求表面上像“给工具加三个参数”,实际牵涉四件事:模型能不能看到这些参数、部署是否允许这条路由、子 Session 是否真的收到选择、进程重启后是否还使用同一模型。漏掉任何一层,功能都会变成只在演示时看起来可用。

先认识六个词

Lead Agent

团队里的主 Agent,负责拆任务、创建队友和协调结果。

Teammate

由 Lead 创建的子 Agent,有自己的 Session,可以持续接收消息。

Runtime provider

决定子 Session “怎么出生”:全新上下文用 spawn,继承历史用 fork。

LLM provider

真正调用模型的适配器,例如某个 API 接入方;它和 spawn / fork 不是一回事。

AgentOptions

创建 Agent 时携带的执行选项,包括 LLM provider、model 和 reasoning effort。

Session descriptor

子会话的持久化说明书。恢复 Session 时,运行时从这里找回原来的模型路由。

最容易混淆的点:“provider”在这里有两类。runtime provider 负责会话启动方式;LLM provider 负责模型调用。名字相似,但职责不同。

原本的调用链为什么只能继承主模型?

我先从模型能调用的工具开始向下追。修复前,链路是这样的:

context 只能告诉系统使用 fresh 还是 fork。它没有表达“这个队友要使用哪个 LLM provider / model”的位置。到了 startContinuable 时,调用里又没有 agentOptions,运行时只能按既有规则从 Lead 继承。

所以这不是模型“不够聪明”,也不是调度提示词“不够强”。数据根本没有一条从工具参数通往子 Session 的合法道路。

这条链路里,每一层到底负责什么?

把职责拆开后,修复应该落在哪里就清楚了。上层负责表达意图,中间层负责授权和创建,底层负责执行与恢复。

层职责它不应该负责持久化结果
spawn_teammate 工具给模型公开可填写字段;把请求交给共享选择策略自己维护模型注册表,或接受任意字符串无
共享模型选择策略白名单、继承、推理强度归一化、适配器和 Session 预检创建 Team member 或写 Team 日志无
Team roster 服务预留成员身份、记录 provisioning、调用 continuable runtime重新解释 LLM 能力成员事件和 runtime provider
Continuable runtime按 spawn / fork 创建历史与子 Session,转交 agentOptions替部署决定哪些模型可用子 Session 与 inbox
Child Session descriptor保存最终生效的 provider、model、reasoning effort保存 Team 的业务 roster可冷恢复的 LLM 路由

根因不是一个,而是三个缺口叠在一起

1. 表达缺口

spawn_teammate 的 schema 只有身份、任务和上下文模式,没有 LLM provider、model、reasoning effort。Lead 即使知道应该分配专家模型,也没有字段可以表达。

2. 传递缺口

Team 服务调用 startContinuable({ prompt, parent }) 时没有传 agentOptions。因此就算上层临时拿到了模型名,也到不了创建子 Session 的位置。

3. 权威缺口

如果只把字符串一路透传,会出现更危险的问题:模型可能猜错 ID、请求部署未授权的模型,或选择运行时不支持的推理强度;重启时也不清楚该相信 Team 日志还是 Session 数据。

关键判断:正确修复必须同时解决“能表达、能传递、能授权、能恢复”。只给函数签名加参数,会得到一个不可控且不可恢复的半成品。

修复:把两种选择拆开,再复用同一套授权策略

我保留 context 负责 runtime 选择,新增可选的 provider、model 和 reasoning_effort 负责 LLM 路由。provider 与 model 必须同时提供;全部省略时仍然继承 Lead,保证兼容旧行为。

为什么先调用 list_subagent_models?

我们没有让 Lead 猜 provider / model 字符串。启用模型选择的部署会公开 list_subagent_models,返回当前 Session 被允许使用的精确路由。显式选择必须来自 Host 所有的 allowlist。

为什么复用普通 subagent 的选择逻辑?

普通 subagent 已经有 allowlist、继承、推理强度规范化、适配器预检和 Session 准入。我们把这部分整理为公开的 @deepseek-ai/dsh-tool-subagent/model-selection 入口,让 Team 复用同一套权威,而不是复制第二套“差不多”的规则。

为什么要在写 roster 之前预检?

如果先创建成员、再发现模型不可用,Team 会留下占名字、占槽位的半截状态。现在字段配对、白名单、适配器、模型、推理强度和 runtime 能力都先检查;只有全部通过才进入 provisioning。

const childAgentOptions = await resolveChildRoute({
  provider,
  model,
  reasoningEffort,
  parent: lead,
})

await team.createMember({
  runtimeProvider, // spawn / fork
  prompt,
  parent: lead,
  agentOptions: childAgentOptions, // LLM provider / model / effort
})

上面是概念代码,用来突出两条选择的分离;实际实现还包含 scope、能力检查、事件落盘和错误语义。

为什么重启后不会又退回主模型?

我们没有把 LLM 路由重复写进 Team roster。Team 日志继续只保存成员身份、上下文模式和 runtime provider;真正决定执行模型的 AgentOptions 保存到 child Session descriptor。

这样只有一个模型事实来源,不会出现“Team 日志说模型 A,Session descriptor 说模型 B”的冲突。roster 可以展示当前已加载成员的模型,但展示字段不承担恢复权威。

怎么证明它不只是“能跑一次”?

验证重点不是一个 happy path,而是那些最容易悄悄损坏状态的边角。

Passed

显式异构路由

队友使用允许列表中的另一 provider / model,而不是继承 Lead。

Passed

输入约束

只给 provider 或只给 model 会在成员创建前被拒绝。

Passed

能力与白名单

未授权路由、不支持 agentOptions 的 runtime、无效 effort 都不能落入 roster。

Passed

兼容旧部署

关闭 modelSelectionSettings 时,工具 schema 和原先一样,继续继承 Lead。

Passed

持久化与冷恢复

descriptor 保存所选 AgentOptions,恢复后继续使用同一路由。

Passed

工程门禁

受影响包测试、TypeScript、lint、目录生成、配置和翻译配对检查通过。

实现提交改动 29 个文件,包含 Team 类型与 roster 转发、共享 model-selection API、工具 schema、profile 配置、单元测试和中英文文档。重点测试记录在 tool-team.spec.ts 和 team.spec.ts。

做到哪里了,还有什么不能夸大?

调用链与根因已确认

原链路、职责分层和缺失数据路径都有代码对应。

Fork 分支已实现并推送

代码、测试、中英文设计文档和更自然的叙述均已提交。

本地相关门禁已通过

验证覆盖路由选择、失败语义、持久化和固定路由兼容性。

尚未声称上游合入

当前成果属于我们 Fork 中的完整候选实现;上游是否接受仍由维护者评审决定。

我学到的核心:一个跨层 feature 的完成标准不是 UI 多出几个字段,而是意图、授权、执行、持久化和恢复拥有同一套可解释的事实。

下一篇 · CASE 002vLLM T4 Triton 共享内存溢出→