2026/9/19 5:57:34

DeepSeek Harness 中 Adapter 自治的 maxTokens 默认值设计:defaultMaxTokens 从模型路由到持久请求头的完整链路

DeepSeek Harness 中 Adapter 自治的 maxTokens 默认值设计:defaultMaxTokens 从模型路由到持久请求头的完整链路 DeepSeek Harness 中 Adapter 自治的 maxTokens 默认值设计defaultMaxTokens 从模型路由到持久请求头的完整链路【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness本文基于仓库内的架构决策记录 2026-07-30-adapter-owned-max-token-defaults.md讲解 DeepSeek HarnessEverything is a Plugin 的 LLM Agent 框架如何把「每请求输出 token 上限的默认值」的决策权交给 LLM Adapter 自身而非塞进 Provider 序列化或 Agent Loop 驱动层。读完你可以掌握LlmResolvedModelInfo.defaultMaxTokens的定义、校验与物化链路理解adapterDefaults标记如何让「Adapter 默认值」与「调用方显式值」在持久化的request/header中互不混淆并学会通过llm-deepseek.config.maxTokens调整部署级默认预算。问题背景一个默认值不该放在哪里的三重困境该架构笔记描述的起点是一个看似简单的问题当调用方没有显式传入GenerateOptions.maxTokens时请求到底该带上什么输出上限笔记指出两条直觉路线都有结构性缺陷只在 Provider 序列化阶段兜底如果默认值只加在 DeepSeek 的请求序列化里那么实际发往 Provider 的 wire 请求会携带一个在持久化request/header中不存在的值——日志、审计与重放看到的请求和线上请求不一致把每个 Provider 的默认值写进 Agent LoopAgent Loop 是 provider 中立的驱动层若由它内置各 Provider 的模型策略等于把部署策略与模型策略从 Adapter 中「搬运」进了中立层破坏了「一切皆插件」的职责边界。因此决策是由 Adapter 自己声明默认值。LlmResolvedModelInfo.defaultMaxTokens承载「针对某个精确 provider/model 路由的、由 Adapter 配置的每请求输出上限」LlmRuntime只负责校验并在调用方省略时物化不夹带任何 Provider 特定的策略。核心机制defaultMaxTokens 的定义、校验与物化类型定义defaultMaxTokens是精确路由模型元数据的一部分定义在 types.ts/** Exact-route model metadata resolved by its owning adapter. */ export interface LlmResolvedModelInfo extends LlmModelInfo { /** Provider-owned context capacity when known. */ context?: LlmModelContext /** Adapter-configured per-request output cap materialized when callers omit one. */ defaultMaxTokens?: number /** Adapter-owned selectable reasoning levels when exposed. */ reasoning?: LlmModelReasoningInfo }注意字段注释与笔记措辞一致它是「调用方省略时才被物化的每请求输出上限」而不是模型硬上限。选择省略该字段的 Adapter 即表示「保留 Provider 自己的默认行为」这正是决策中强调的「adapters that preserve provider-owned defaults omit it」。LlmRuntime 的校验在 LlmRuntime.normalizeModelInfo 中Runtime 对 Adapter 返回的defaultMaxTokens做防御性校验const defaultMaxTokens resolved.defaultMaxTokens if (defaultMaxTokens ! undefined (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens 0)) { throw new LlmError( adapter returned invalid default maxTokens for provider ${provider} model ${model}, INVALID_MODEL_MAX_TOKENS, ) }即该字段必须是正的安全整数否则以INVALID_MODEL_MAX_TOKENS错误拒绝——与笔记中「LlmRuntimevalidates it as a positive safe integer」逐字对应。校验通过后该字段随LlmResolvedModelInfo一起被 detached 返回与 Adapter 内部对象解耦。物化只有调用方省略时才生效默认值的物化发生在 resolveCallWithInfoconst defaulted config.maxTokens undefined info.defaultMaxTokens ! undefined ? { ...config, maxTokens: info.defaultMaxTokens } : config三个条件共同保证了优先级语义调用方显式值永远胜出config.maxTokens ! undefined时原样保留Runtime 不做任何夹逼clamping或改写显式请求与 Agent 选项「remain unmarked and therefore win without clamping」Adapter 未发布默认值时不干预info.defaultMaxTokens undefined时配置原样通过Provider 自有默认行为保持不变物化产生的是副本{ ...config, maxTokens: ... }不修改调用方传入的原始对象。prepareCall 与 adapterDefaults 标记让默认值成为持久请求事实笔记中的关键设计是Agent Loop 在记录request/header之前先 prepare 调用因此「哪些字段是 Adapter 默认值补上的」会成为持久请求事实的一部分。这一机制在 LlmRuntime.prepareCall 中实现const resolvedConfig deepFreeze(structuredClone(resolved.config)) const adapterDefaults deepFreezeLlmCallConfigAdapterDefaults({ ...config.reasoningEffort undefined resolvedConfig.reasoningEffort ! undefined ? { reasoningEffort: true } : {}, ...config.maxTokens undefined resolvedConfig.maxTokens ! undefined ? { maxTokens: true } : {}, })标记逻辑精确区分了两种来源物化前的config.maxTokens是undefined而物化后的resolvedConfig.maxTokens有值说明这个值来自defaultMaxTokens于是打上maxTokens: true标记调用方显式传入的值则在物化前后一致不会被打标。返回的PreparedLlmCall是绑定到当前 Adapter 注册的一次性句柄deepFreeze 单次 dispatch 约束保证 header 日志与后续 dispatch 使用的是同一份能力解析结果——从源码结构看注释明确说明这是为了防止 HMR 场景下「一个 Adapter 的能力结果被拼接到另一个 Adapter 上」。Agent Loop 侧的消费切换路由前先清除标记字段在 agent.ts 中Loop 把上一轮持久 header 转换为「下一轮请求的提案」时会先剥离 Adapter 派生值/** Remove adapter-derived values before plugins propose the next request config. */ function requestProposal(header: EpochHeader): LlmCallConfig { if (header.adapterDefaults undefined) return header.config const proposal { ...header.config } if (header.adapterDefaults.reasoningEffort true) delete proposal.reasoningEffort if (header.adapterDefaults.maxTokens true) delete proposal.maxTokens while (true) return proposal }注意真实实现中该函数没有while结构上面最后一行while (true)是笔误请以仓库源码为准其实际行为就是被标记的字段从提案中删除随后「exact-model resolution」会用当前路由的 Adapter 默认值重新物化。这一「先删后补」的顺序保证了笔记描述的核心不变量切换 Provider/模型时新 Adapter 的默认值会重新物化而不会把上一个 Adapter 的派生值误当作显式覆盖沿用到新路由相反会话中显式设置的maxTokens因为从未被打标会持续保留。该行为有测试固化request-reconstruction.spec.ts 对多轮adapterDefaults的持久化与剥离做了断言如第 229、302、336 行附近对header.data.header.adapterDefaults序列的toEqual校验覆盖了「标记字段被持久化后在新提案中消失、显式字段保留」的路径。DeepSeek 原生 Adapter 的实现256,000 默认与 1,000,000 上下文笔记对原生 DeepSeek 部署的结论——「默认发送max_tokens: 256000、默认上下文容量 1,000,000」——可以直接在 llm-deepseek 源码中逐条印证。常量与每模型元数据adapter.ts 定义了两个关键默认常量/** Default combined request/response context capacity. */ export const DEFAULT_CONTEXT_WINDOW 1_000_000 /** Default per-request output-token cap. */ export const DEFAULT_MAX_TOKENS 256_000在 modelInfoFor 中Adapter 为每个精确 provider/model 路由组装LlmResolvedModelInfodefaultMaxTokens与contextWindow的回退链是const contextWindow configured?.contextWindow ?? connection.defaultContextWindow return { ... context: { contextWindow }, defaultMaxTokens: configured?.maxTokens ?? connection.maxTokens, ... }这里对应笔记中两条规则每模型maxTokens优先于 profile 级maxTokensDeepSeekCatalogModel接口adapter.ts中的maxTokens?: number注释明确写着「omission falls back to the profilesDeepSeekConnectionOptions.maxTokens」未列出的直通pass-through模型 id 与未配置容量的条目继承同一 Adapter 级回退对未在 catalog 中登记的模型 idconfigured为undefineddefaultMaxTokens直接取connection.maxTokens即 256,000 默认而contextWindow取connection.defaultContextWindow1,000,000 默认。同时catalog 中两条内置 V4 模型条目在 index.ts 中显式发布了contextWindow: DEFAULT_CONTEXT_WINDOW与笔记「both built-in V4 entries publish that exact capacity」吻合。Cordis 配置侧的校验llm-deepseek/src/index.ts 中插件配置即 Cordis 插件配置对这两个值都做了正整数约束且 profile 级maxTokens的 zod schema 直接以DEFAULT_MAX_TOKENS为默认contextWindow: z.number().step(1).min(1), maxTokens: z.number().step(1).min(1), // profile 级 maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_TOKENS),配置解析阶段还有二次防御第 303-305 行maxTokens不是正安全整数时抛出llm-deepseek: maxTokens must be a positive safe integer连接选项组装时回退为config.maxTokens ?? DEFAULT_MAX_TOKENS第 384 行。因此「部署可通过llm-deepseek.config.maxTokens改变默认值」这条结论有配置 schema 与运行时解析双重依据。序列化边界effective 值映射为 max_tokens最终 wire 请求在 serialize.ts 生成...options.maxTokens undefined ? {} : { max_tokens: options.maxTokens },由于LlmRuntime在 prepare 阶段已把defaultMaxTokens物化进config.maxTokens序列化时看到的已是「有效值」max_tokens字段必然与持久request/header中的值一致——这正是拒绝「只在序列化处兜底」这一备选方案的直接目的wire 请求不再携带任何请求头中不存在的模型可见值。备选方案对比为什么其他位置都不合适笔记完整记录了四个被否决的备选它们从不同角度框定了这套设计备选方案否决理由工程含义仅在 DeepSeek 序列化处应用默认值Provider wire 会包含一个缺失于持久请求头的模型可见值日志/重放/审计与实际请求必须一致默认值必须发生在「请求事实」形成之前在每个发行应用里设置AgentOptions.maxTokens应用重复 Adapter 部署策略直接 LLM 调用行为不同切换到其他 Provider 后仍残留 DeepSeek 专属上限部署策略属于 Adapter 配置不属于应用代码LlmRuntime.stream()直连路径必须走同一套物化把 256,000 表示为硬模型上限配置值是「期望的请求预算」并非「每个配置端点都会拒绝更大输出」的证据显式调用方保持权威地位系统不做 clamp完全交由 Provider 默认值决定原生 DeepSeek 部署的产品要求是跨兼容端点稳定的 256,000 token 会话预算「保留 Provider 默认」对不发布defaultMaxTokens的 Adapter 仍然是合法行为值得强调的是第三行defaultMaxTokens是请求默认值而非硬上限。resolveCallWithInfo中没有任何 min/max 夹逼逻辑显式传入 300,000 时系统不会替你改回 256,000——「explicit callers remain authoritative」。结论与运维含义综合以上链路这套「Adapter 自治默认值」设计在仓库中的最终形态是默认链路DeepSeek 会话在未显式配置时发送max_tokens: 256000且request/header同时记录该值与adapterDefaults.maxTokens true标记即「值」与「值的来源」都是持久事实优先级链从高到低per-request 显式值 /AgentOptions.maxTokens→ per-model catalogmaxTokens→ profile 级llm-deepseek.config.maxTokens→ 内置默认 256,000路由切换安全requestProposal先删除标记字段、再按当前路由重新物化切换 Provider 不会把 DeepSeek 派生的 256,000 误当成显式覆盖带到新 Adapter预算权衡256,000 的输出预算在「预分配请求输出」的端点上会占用 1,000,000 token 上下文中的很大一块。若你的网关或模型只支持更小输出预算应显式调低maxTokens——笔记的原话是「explicit configuration is preferable to an undocumented provider fallback」显式配置优于未文档化的 Provider 兜底扩展方式其他 Adapter 保持既有行为直到它们「有意发布defaultMaxTokens」即该字段是纯增量能力不改变未声明默认值的 Adapter 的语义。如果你要为自己的 Adapter 接入同类能力只需在resolveModel/prepareCall返回的LlmResolvedModelInfo中发布正整数defaultMaxTokensRuntime 的校验、物化、标记与 Loop 的剥离逻辑即自动生效——这正是「Everything is a Plugin」在请求参数层的一个具体实例。中文对照版决策记录见 2026-07-30-adapter-owned-max-token-defaults.zh.md。【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考