Skip to content

模型选择决定了一个智能体的下一次请求使用哪个 provider 路由 和 provider 拥有的 模型 id(外加可选的 reasoning effort)。它是一条在请求时运行的解析链,坐落在多个包的汇合点上:默认模型提供者、智能体循环的 agent/request waterfall、LLM 适配器注册表、会话中记录的请求头,以及 UI 的选择界面。

负责
@deepseek-ai/dsh-agent-default-modelctx.agentDefaultModel —— 全局默认选择,经 settings 持久化
@deepseek-ai/dsh-agentModelSelectioninstallModelSelectionagent/request waterfall
@deepseek-ai/dsh-agent-loop解析并冻结配置的请求构建路径
@deepseek-ai/dsh-llmLlmRuntime 适配器注册表、prepareCallstream
@deepseek-ai/dsh-session记录的 request/headerrequest/context
@deepseek-ai/dsh-llm-retryagent/request-error 上的重试策略
@deepseek-ai/dsh-client-ui-model-selection浏览器端;选择界面

解析链

具体接线可见于 headless bundle(packages/bundle/headless/src/index.ts)——最小的可运行组合:

会话特定选择(ModelSelectionRef,节点端)
        │  installModelSelection(agentCtx, ref)

智能体默认  ←  agentDefaultModel.currentSelection()
        │  创建时写入 AgentOptions { provider, model }

agent/request waterfall(智能体循环 buildRequest)
        │  冻结的 LlmCallConfig

LlmRuntime.prepareCall(config) → 适配器 + 默认值


provider.stream(request)

最终请求由三层共同铸成:

  1. 全局默认——ctx.agentDefaultModelAgentDefaultModelConfig 服务。它的 currentSelection() 返回从组合入口读取的 { provider, model, reasoningEffort? };当挂载了 settings 提供者时,读的是实时的 settings 文档。
  2. 智能体默认——默认值在智能体创建时被复制进 AgentOptions.provider/AgentOptions.model,因此 ctx.agents.create({ agentOptions }) 已经携带了具体路由。
  3. 会话/step 选择——ctx.agentDefaultModel 给一个 ModelSelectionRef 播种,其 current 值会话可以替换;installModelSelection(agentCtx, ref) 把这个可变选择耦合到提示组装与请求路由。

默认模型提供者

在源代码里,任务所称的"默认模型提供者"就是 AgentDefaultModelConfig 服务(packages/core/agent-default-model/src/index.ts),挂载为 ctx.agentDefaultModel。它持有一份可变的、settings 支撑的默认值:

ts
export class AgentDefaultModelConfig extends Service {
  private source: () => AgentDefaultModelSettings

  constructor(ctx, config: Config) {
    super(ctx, 'agentDefaultModel')
    const entry: AgentDefaultModelSettings = { provider: config.provider, model: config.model }
    this.source = () => entry
    installSettingsSection(ctx, AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE,
      AGENT_DEFAULT_MODEL_SETTINGS_SCHEMA, entry, { setSource: (current) => { this.source = current }, onChange: () => {} })
  }

  currentSelection(): ModelSelection {
    return selection(this.source())
  }

  async saveSelection(next: ModelSelection): Promise<void> {
    await this.ctx.get('settings')?.replace(AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE, {
      provider: next.provider, model: next.model,
      ...next.reasoningEffort === undefined ? {} : { reasoningEffort: String(next.reasoningEffort) },
    })
  }
}

settings 命名空间是 agent-default-model,schema 携带必需的 provider、必需的 model,以及可选的 reasoningEffortcurrentSelection() 始终经 this.source() 读取,因此 settings 变更会实时反映,无需重建任何注册层事实。

注入的消费者(agentDefaultModelagentssessions)为活跃智能体播种:

ts
const selection = defaultModel.currentSelection()
const { agent } = await agents.create({
  sessionId: SessionId(`session-${randomUUID()}`),
  meta: { cwd: process.cwd() },
  agentOptions: { provider: selection.provider, model: selection.model },
  setup: (agentCtx) => {
    const selected: ModelSelectionRef = { current: selection, assembled: undefined }
    installModelSelection(agentCtx, selected)
  },
})

会话选择如何覆盖

installModelSelection(位于 packages/core/agent/src/model-selection.ts)安装两个作用域 waterfall 监听器。第一个在提示组装前快照所选模型,并把 provider/model 设为提示变量;第二个在 agent/request 边界把 provider/model(及 effort)强加到 LlmCallConfig 上。current/assembled 的切分意味着并发切换作用于后续 step,而不是把两个 surface 撕开:

ts
const disposeRequest = agentCtx.on('agent/request', async (_payload, next): Promise<LlmCallConfig> => {
  const resolved = await next()
  const selected = selection.assembled
  if (selected === undefined) return resolved
  const { reasoningEffort: _inheritedEffort, ...withoutInheritedEffort } = resolved
  return {
    ...withoutInheritedEffort,
    provider: selected.provider,
    model: selected.model,
    ...selected.reasoningEffort === undefined ? {} : { reasoningEffort: selected.reasoningEffort },
  }
})

循环如何冻结一次请求

智能体循环里的 buildRequest 读取持久化的请求头,经 agent/request waterfall 得到一个最终的 LlmCallConfig,并强制要求 provider 与 model 都有值(否则抛出 "has no provider/model")。随后它把 request/header(reason 为 initialresumechange)与 request/context(provider/model/contextWindow)记到会话上,并绑定 prepareCall,从而把适配器默认值与重试策略与该精确路由一起捕获:

ts
const proposedConfig = await this.dispatch.waterfall(
  'agent/request', { turn, step, signal }, () => Promise.resolve(seedConfig),
)
...
preparedCall = await this.loopCtx.llm.prepareCall(proposedConfig, signal)
config = preparedCall.config
...
const header = canonicalHeader({ config, ...adapterDefaults, ...system, ...tools })

提供方注册与模型

ctx.llm.registerAdapter(providers, adapter)packages/llm/llm/src/index.ts 里的 LlmRuntime)注册一个 provider 路由;它全有或全无抛出 DUPLICATE_ADAPTER,并返回一个携带 AdapterRegistrationHandle.replace 的释放器用于原子替换路由。每个适配器还暴露 providerRetryPolicy(provider)listModels(provider)resolveModel(info) 元数据,因此模型 id 是 provider 拥有的不透明字符串——core 从不解释它们。prepareCall 捕获 PreparedLlmCall(冻结配置,含已物化的默认值、retryPolicycontext.contextWindow)。请求表面是 GenerateOptions(provider、model、messages、system、tools、reasoningEffortmaxTokens);请求经 LlmAdapter.stream(options) 发出。

重试钩子

重试策略绑定于注册:llm-retryagent/request-error 上实现。每次计划的重试在其可取消等待之前就已持久化,策略来自适配器注册的 providerRetryPolicy。模型请求失败以结构化 LlmFailure 事实的形式经同一个 waterfall 上浮。

API 密钥从何而来

模型调用把凭据解析委托给凭据接缝,而不是 core。deepseek provider 适配器(packages/llm/llm-deepseek/src/adapter.ts)在每次流式调用时通过可选的 ctx.credentials 接缝——一个 CredentialRef——解析 API 密钥,并在其 HTTP 边界注入 authorization: Bearer <apiKey>(每次调用解析一次,以尊重已变更的凭据)。core 的 LlmRuntime 与传输无关;部署要么挂载凭据接缝,要么经配置提供该密钥。

延伸阅读

  • 智能体循环 —— agent/request waterfall 与 buildRequest 的冻结步骤。
  • 系统提示组装 —— 所选模型如何成为 provider/model 提示变量。
  • 会话管理 —— 记录的 request/headerrequest/context
  • 作用域系统 —— installModelSelection 注册的作用域监听器。
  • 仓库源码:packages/core/agent-default-model/src/index.tspackages/core/agent/src/model-selection.tspackages/bundle/headless/src/index.tspackages/llm/llm/src/index.ts
  • 官方脚手架:docs/config-catalog.md(模型键)、docs/subsystems/credentials.md