
DeepSeek Harness 结构化错误分类体系基于HarnessError的端到端机器可路由错误设计【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness导读本篇文章围绕 DeepSeek Harness 仓库中的架构 Agent Note《Structured error taxonomy》展开讲解项目如何用一个统一的HarnessError基类取代跨越各能力 seam 的裸字符串错误让工具错误、LLM 调用失败、非 Error throw 等各类故障都携带稳定、可机器路由的code。读完你将掌握HarnessError的字段与构造函数设计、错误分类树LlmError、ToolArgsError等、错误跨工具执行结果与会话事件传播的完整链路以及插件如何基于error.code分支处理沙箱、重试、回放而不必对消息文本做子串匹配。问题背景故障跨越 seam 时沦为裸字符串在引入本方案之前DeepSeek Harness 中的失败信息在跨模块边界时会被扁平化工具错误被压成文本块工具执行失败时name、code和stack全部丢失只留下一段面向模型的文本。这意味着一个未来的沙箱/重试插件无法区分ENOENT和EACCES——两类错误需要的处理策略完全不同但消费方只能看到同一段文字。非 Error 的 throw 退化更严重当某处throw一个非Error值例如throw { reason: denied }时agent loop智能体循环会用new Error(String(x))包装它code信息被彻底丢弃连可读的cause链也丢了。缺少共享基类当时LlmError是系统中唯一的类型化错误没有公共基类消费方无法对任意错误做通用的instanceof判断也就无法在统一出口seam做类型收窄。于是决策落地为在dsh-llm包中引入一个HarnessError extends Error基类作为全系统错误的公共祖先。核心设计HarnessError基类基类实现位于 packages/llm/llm/src/error.ts核心代码如下export class HarnessError extends Error { /** Stable machine-routable failure class (e.g. RATE_LIMIT); route on this, never by parsing message. */ readonly code: string constructor(message: string, code: string, options?: ErrorOptions) { super(message, options) this.code code this.name new.target.name } }设计要点有三稳定的code与人类可读的message分离code是程序化的、稳定的失败类别标识如NO_ADAPTER、INVALID_ARGS、RATE_LIMIT、UNKNOWN与面向人的message彻底解耦。注释明确要求按code路由永远不要解析message——因为消息文本可能随供应商措辞变化而code是分类契约。通过标准ErrorOptions支持cause链构造函数透传ErrorOptions使得new HarnessError(msg, code, { cause: originalError })能保留底层根因配合errorChain()工具可以渲染完整因果链。name默认取子类构造器名通过new.target.name自动设置子类无需显式声明当然也可以像LlmError、ToolArgsError那样显式覆盖以固化名称。同文件配套的两个关键函数error.ts还导出了与错误体系配套的两个函数isHarnessError(value)error.ts#L161-L163类型守卫value instanceof HarnessError。注释特别强调只收窄真实实例duck-typed 或跨 realm 的错误不会误判用于各 seam 处的运行时边界收窄。errorChain(value)error.ts#L114-L154把任意抛出值渲染为带完整cause链的文本并处理AggregateError成员与循环引用渲染circular cause。它专门用于诊断面消息、通知、日志的渲染——注释明确只渲染绝不解析路由请走HarnessError.code。为什么放在dsh-llm叶子包决策的约束是不引入新的依赖边dsh-llm是所有其他包都已经依赖的叶子包把基类放这里任何包想继承或instanceof它都只需一条import语句而不是新增一个包依赖。正如 Agent Note 所述一个基类被广泛导入但它位于所有包已经依赖的包中代价仅是一条 import而非新的依赖边。这一点可从 packages/llm/llm/src/index.ts#L36-L39 看到export * from ./error.ts使基类从deepseek-ai/dsh-llm公开导出。供应商中立的规范 codeerror.ts中还定义了一批供应商中立的规范 code 常量与分类器进一步夯实稳定 code的语义CONTEXT_WINDOW_EXCEEDED_CODE CONTEXT_WINDOW_EXCEEDED请求超出模型上下文窗口。QUOTA_EXCEEDED_CODE QUOTA账户配额/余额耗尽区别于瞬时限流。EMPTY_RESPONSE_CODE EMPTY_RESPONSE响应正常结束但没有任何内容块视为可安全重试。INVALID_CREDENTIAL_CODE INVALID_CREDENTIAL凭据存在但不可用格式错误修复方式是修正存储值而非补充凭据且刻意不放入默认可重试集合——格式错误的凭据每次尝试都会同样失败。配套的isContextWindowExceededError(detail)error.ts#L80-L86和isQuotaExceededError(detail)error.ts#L94-L100用正则识别各 OpenAI 兼容供应商的措辞把某个供应商到底算不算上下文超限/配额耗尽统一收敛为规范 code。这些分类器与 adapter-failure.ts 的normalizeLlmFailure协同——后者只信任 Harness 自有的 codeerror instanceof HarnessError ? error.code : UNKNOWN第三方 SDK 的 code 不属于本分类体系。错误分类树现有子类HarnessError作为公共基类被两类既有错误继承且都保留了各自既有的 codeLlmErrorLLM 相关失败位于 packages/llm/llm/src/index.ts#L86-L120构造时强制校验message与code非空、status为 100–599 的整数、providerRetryAfterMs为正有限数、requestId非空并把可序列化的事实status、providerRetryAfterMs、requestId冻结进readonly failure: LlmFailure字段export class LlmError extends HarnessError { readonly failure: LlmFailure constructor(message: string, code: string, options?: LlmErrorOptions) { // ...参数校验... super(message, code, options) this.name LlmError this.failure Object.freeze({ message, code, /* status? providerRetryAfterMs? requestId? */ }) } }LlmError的code属于共享分类如AUTH、RATE_LIMIT、NO_ADAPTER携带的failure事实则供持久化与策略层使用。ToolArgsError工具参数校验失败位于 packages/core/tools/src/schema.ts#L461-L470是dsh-tools中的参数校验错误固定使用INVALID_ARGScode并保留逐条违规列表export class ToolArgsError extends HarnessError { readonly violations: string[] constructor(violations: string[]) { super(invalid arguments: ${violations.join(; )}, INVALID_ARGS) this.name ToolArgsError this.violations violations } }除此之外从 packages/core/tools/src/index.ts#L489-L491 附近可看到未知工具错误也继承了HarnessErrorcode: UNKNOWN_TOOL说明该分类树会随各包继续生长。跨 seam 的传播链路Agent Note 强调错误端到端可机器路由其关键在于三处 seam 的打通1. 工具执行结果ToolExecutionResult.errordsh-tools定义了结构化失败元数据 ToolErrorInfoexport interface ToolErrorInfo { name: string code: string } export interface ToolFailure { message: string // 人类可读消息无 Native Error: 前缀 info?: ToolErrorInfo // 供策略与持久化诊断使用的内部错误类别 }失败结果 ToolExecutionFailure 通过error: ToolFailure携带该信息与成功结果的value互斥readonly error?: never。注册表 catch 处toolErrorResult在抛出值为HarnessError时填充结构化信息function toolErrorResult(error: unknown): ToolExecutionResult { const info errorInfo(error) // error instanceof HarnessError ? { name, code } : undefined const message errorMessage(error) return { content: [{ type: text, text: Error: ${message} }], // 模型面文本块不变 isError: true, error: { message, ...info ? { info } : {} }, } }注意errorInfoindex.ts#L642-L647只对HarnessError实例产出结构化字段并用 try/catch 兜底——即使抛出值充满敌意instanceof被陷阱化也不会让错误归一化边界本身崩溃。2. 会话事件tool/result与turn/endagent loop 将结构化失败转发到tool/result会话事件该事件同样新增可选error字段使结构化失败信息进入持久化日志供重试/沙箱插件与回放使用。这一点有端到端测试直接印证crash-recovery.e2e.ts 验证崩溃恢复后重放的tool/result事件携带{ name: ToolOutcomeUnknownError, code: TOOL_OUTCOME_UNKNOWN }且模型面文本仍含Do not retry blindly.——证明结构化字段与模型文本并存的设计。3. agent loop 的toError归一化非 Error throw →UNKNOWNAgent Note 提到的toError归一化逻辑在 packages/core/agent-loop/src/agent.ts#L309-L322 的 turn catch 中可以看到对应实现// Every failure is structured: an LlmError keeps its facts, anything // else flattens to errorChain text under the UNKNOWN code. turnEnds { kind: error, error: error instanceof LlmError ? error.failure : { message: errorChain(error), code: UNKNOWN }, }即LlmError保留其结构化failure事实其他任意抛出值包括非 Error统一通过errorChain渲染为因果链文本并打上UNKNOWN兜底 code。会话的error事件此前已暴露code因此即使是糟糕的 throw也能携带可路由 code 进入日志而不是被new Error(String(x))抹掉全部类别信息。消费端实践插件如何基于error.code路由整个设计的收益在消费端兑现沙箱/重试插件从tool/result事件读出error.info.code直接分支处理。例如ENOENT类文件系统错误与EACCES类权限错误走不同策略QUOTA、INVALID_CREDENTIAL等 code 依据 error.ts 的规范语义决定是否可重试INVALID_CREDENTIAL刻意不在默认可重试集合内。通用消费方在任意 seam 用isHarnessError(value)收窄后读取.code无需知道具体是哪个子类抛出的。回放/崩溃恢复持久化的会话日志保留了结构化error字段重放时依然可以拿到失败类别见上文 crash-recovery 测试而不仅仅是一段不可解析的文本。边界与约束模型面与代码面分离Agent Note 在 Consequences 中明确了三个重要边界deriveMessages不把error暴露进模型历史——模型始终看到文本块Error: ...形式结构化字段服务于代码与回放。这保证模型的输入面保持稳定同时不牺牲机器可路由性。参数校验保留既有 code 与行为ToolArgsError的INVALID_ARGS与违规列表行为不变只是继承了共享基类。包自有诊断不变式独立携带稳定 code不变式注册表不导入产品包各包如llm-deepseek/src/invariant.ts、llm-retry/src/invariant.ts、token-meter/src/invariant.ts自有的诊断不变式各自携带稳定 code。共享基类只增加跨 seam 的路由元数据不改变模型面文本。总结HarnessError用最小的架构代价单一公共基类 一个code字段把 DeepSeek Harness 的错误从跨 seam 即失真的裸字符串升级为端到端机器可路由的结构化分类统一基类放dsh-llm叶子包零新增依赖边任何包一条 import 即可参与分类树code与message分离、ErrorOptions承载cause、name默认子类名isHarnessError在 seam 收窄工具失败经ToolExecutionResult.error与tool/result事件进入日志非 Error throw 归一化为UNKNOWNcode模型看到的文本块保持不变结构化字段只服务代码、策略与回放。对于希望扩展 Harness 生态沙箱策略、重试策略、回放分析的开发者这条链路给出了清晰的接入点读会话事件中的结构化error.info.code按 code 分支而不是解析消息文本。相关实现与验证可继续查阅packages/llm/llm/src/error.ts、packages/llm/llm/src/index.ts、packages/core/tools/src/schema.ts、packages/core/tools/src/index.ts、packages/core/agent-loop/src/agent.ts、packages/session/session-checkpoint-policy/tests/crash-recovery.e2e.ts。【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考