
DeepSeek Harness 结构化错误分类体系基于 HarnessError 的跨 Seam 错误路由与分类实践【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness导读本文深入解析 DeepSeek Harness 中一项已落地implemented的架构决策——结构化错误分类体系Structured Error Taxonomy。该方案以deepseek-ai/dsh-llm叶子包中的HarnessError基类为枢纽为工具执行、Agent 循环与会话事件提供端到端可机器路由的稳定code让插件无需对消息做子串匹配即可分支处理 ENOENT 与 EACCES 这类差异。阅读本文后你将掌握HarnessError的设计动机与源码实现、LlmError/ToolArgsError的继承方式、结构化错误如何穿过工具注册表与tool/result会话事件保留在日志中以及不规范throw如何被包装为携带UNKNOWNcode 的可路由错误。该设计记录源自仓库 Agent Note 2026-06-11-structured-error-taxonomy.zh.md。背景故障跨越 Seam 时的信息丢失问题在引入分类体系之前Harness 中的故障在跨越系统边界seam时退化为裸字符串。具体表现为三类问题工具错误被扁平化工具抛出的错误被压缩成一个文本块name、code和stack全部丢失。这让未来的沙箱/重试插件无法区分ENOENT文件不存在与EACCES权限拒绝模型也得不到本可提供的更具可操作性的反馈。非 Error 的 throw 退化更严重Agent 循环agent loop将不规范抛出值包装为new Error(String(x))丢弃了其中携带的全部 code。缺少共享基类LlmError是系统中唯一的类型化错误没有公共基类消费方无法对其做通用的instanceof判断也就无法编写跨模块的通用错误处理逻辑。这一问题的本质是错误信息在传输过程中丢失了结构机器无法路由只能靠人类读文本。核心决策在 dsh-llm 叶子包中引入 HarnessError 基类决策的关键约束是不引入新的依赖边。方案选择在dsh-llm一个所有其他包都已依赖的叶子包中定义HarnessError extends Error基类使全仓库包只需一条 import 语句即可共享错误分类代价几乎为零。HarnessError 的源码实现基类位于 packages/llm/llm/src/error.tsexport class HarnessError extends Error { /** 稳定的、可机器路由的失败类别例如 RATE_LIMIT永远基于此路由绝不解析 message */ readonly code: string constructor(message: string, code: string, options?: ErrorOptions) { super(message, options) this.code code this.name new.target.name } }基类承载三个核心语义稳定的code与人类可读的message分离作为程序化路由的稳定标识。源码注释明确要求route on this, never by parsingmessage。cause链通过标准ErrorOptions支持错误原因链式连接便于保留底层失败上下文。name默认取子类名通过new.target.name自动获得无需子类手动设置。类型收窄函数 isHarnessError同文件还导出了类型守卫 isHarnessErrorexport function isHarnessError(value: unknown): value is HarnessError { return value instanceof HarnessError }它只在 seam运行时边界处做instanceof收窄且注释明确只有真实实例才收窄duck-typing 或跨 realm 的错误不会通过避免误判。配套的规范错误码常量同文件定义了一组提供商中立provider-neutral的规范 code 常量用于统一不同提供商在同类失败上的差异常量code 值语义CONTEXT_WINDOW_EXCEEDED_CODECONTEXT_WINDOW_EXCEEDED请求超出模型上下文窗口QUOTA_EXCEEDED_CODEQUOTA账户配额或余额耗尽终态区别于瞬时限流EMPTY_RESPONSE_CODEEMPTY_RESPONSE响应正常结束但无任何内容块适配器将其归类为失败而非空消息INVALID_CREDENTIAL_CODEINVALID_CREDENTIAL凭据存在但格式错误/不可用区别于缺失修复方式是更正存储值而非补充这些常量定义于 packages/llm/llm/src/error.ts。与之配套的isContextWindowExceededError/isQuotaExceededError分类器同文件第 80、94 行通过正则识别 OpenAI 兼容提供商与库适配器的措辞将文本分类到上述稳定 code——这正是文本分类只在边界做一次、之后全部路由 code的设计体现。子类实践LlmError 与 ToolArgsErrorLlmError携带可序列化失败事实的 LLM 专属错误LlmError定义于 packages/llm/llm/src/index.ts继承HarnessError并保留了既有的 code如AUTH、RATE_LIMIT、NO_ADAPTER。它在基类之上增加了可序列化的事实字段failure并通过LlmErrorOptions接受三个经过严格校验的提供商边界事实status提供商边界观测到的合法 HTTP 状态码必须是 100~599 的整数否则构造即抛错providerRetryAfterMs提供商要求的重试延迟毫秒数必须是正有限数requestId非空的提供商请求 ID。构造时若message或code为空字符串同样直接抛错。failure对象通过Object.freeze冻结确保错误事实在跨 seam 传递中不可变。ToolArgsError工具参数校验错误ToolArgsError定义于 packages/core/tools/src/schema.ts继承HarnessError并携带固定 codeINVALID_ARGSexport class ToolArgsError extends HarnessError { readonly violations: string[] constructor(violations: string[]) { super(invalid arguments: ${violations.join(; )}, INVALID_ARGS) this.name ToolArgsError this.violations violations } }它保留了既有 codeINVALID_ARGS和行为同时将逐条 schema 违规明细保存在violations数组中供代码消费。结构化错误如何穿过工具注册表ToolExecutionResult 新增可选 error 字段工具执行的结果类型ToolExecutionResult是判别联合类型定义于 packages/core/tools/src/index.ts。失败分支ToolExecutionFailure固定携带error: ToolFailure而成功分支则通过error?: never保证二者互斥export interface ToolExecutionFailure { readonly isError: true readonly error: ToolFailure readonly value?: never readonly content: ContentBlock[] // ... }注册表 catch 中的填充逻辑当工具抛出值为HarnessError时注册表的 catch 分支会将其结构化为error字段。核心实现是 toolErrorResultfunction toolErrorResult(error: unknown): ToolExecutionResult { const info errorInfo(error) const message errorMessage(error) return { content: [{ type: text, text: Error: ${message} }], isError: true, error: { message, ...info ? { info } : {} }, } }这里体现了本设计的双轨原则面向模型的content文本块保持不变模型仍然看到文本而结构化信息则进入error.info供代码与回放消费。会话事件 tool/result 保留结构化失败Agent 循环将ToolExecutionResult转发到tool/result会话事件。该事件新增了同一可选字段其类型定义于 packages/core/session/src/types.tserror?: { name: string; code: string }正是这一字段使结构化失败信息得以保留进日志供重试/沙箱插件和回放使用。从测试断言可看到真实场景中的形态例如 packages/core/tools/tests/tools.spec.ts 中的error: { info: { name: HarnessError, code: TOOL_FAILURE } }以及WRAPPER_FAILURE、POST_FAILURE、BOOM、DENIED等 code均以{ name, code }结构存在于结果中会话/轨迹层测试如 packages/core/session/tests/invariant.spec.ts 也验证了error: { name: ToolNotStartedError, code: TOOL_NOT_STARTED }这类形态在事件中的保留。toError把不规范 throw 变成可路由错误Agent 循环中的toError转换逻辑将非 Error 的 throw 包装为HarnessError而非裸Error包装后 code 固定为UNKNOWN原始值作为cause链接保留便于errorChain渲染时回溯根因这样即使是不规范的 throw也能携带可路由的 code 进入会话的error事件该事件此前已暴露code字段。配套的渲染工具是 errorChain它递归渲染完整 cause 链与AggregateError成员使 undici 的TypeError: fetch failed这类传输包装层暴露底层真实失败同时以Set追踪递归路径防止循环 cause并对恶意访问器做了防御性兜底返回unrenderable value。注释明确其定位——仅用于诊断面消息、通知、日志渲染绝不用于解析路由路由只能基于HarnessError.code。设计后果与取舍端到端可机器路由错误从抛出处到日志全程携带稳定code插件可以基于error.code分支而无需对消息做子串匹配。例如沙箱插件可以区分ENOENT与EACCES重试插件可以依据RATE_LIMIT/QUOTA的差异决定重试策略——QUOTA是终态重试无意义而EMPTY_RESPONSE被明确标记为可以安全重试。零新增依赖边一个基类被广泛导入但它位于所有包已经依赖的包中代价仅是一条 import 语句而非新的依赖边。这保证了架构的依赖方向不被破坏。面向模型的文本不变deriveMessages不会将error暴露到模型历史中——模型仍然看到文本块结构化字段服务于代码和回放。这一双轨设计确保了模型提示的稳定性不受影响。参数校验与不变式保持独立参数校验保留其既有的 code 和行为如INVALID_ARGS包自有的诊断不变式独立携带稳定 code使不变式注册表无需导入产品包共享基类只增加跨 seam 的路由元数据不改变面向模型的文本。总结DeepSeek Harness 的结构化错误分类体系用一个位于叶子包、被全仓库依赖的HarnessError基类解决了错误跨 seam 退化为裸字符串的架构问题。其核心价值在于在错误抛出处分类一次通过显式 code 或边界文本分类器之后全程以结构化{ name, code }路由模型看文本、代码看 code、日志保真完整 cause 链。这一设计为沙箱/重试插件、回放和会话诊断提供了稳定的机器可读基础是Everything is a Plugin理念在错误处理维度的落地体现。如需深入可继续阅读基类与分类器实现packages/llm/llm/src/error.ts子类 LlmErrorpackages/llm/llm/src/index.ts子类 ToolArgsErrorpackages/core/tools/src/schema.ts结果类型与填充逻辑packages/core/tools/src/index.ts会话事件 error 字段packages/core/session/src/types.ts工具子系统全景docs/subsystems/tools.md【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考