DeepSeek Harness 结构化错误分类体系:基于 HarnessError 的跨 Seam 错误路由与分类实践
2026/9/19 19:51:01 网站建设 项目流程

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)时退化为裸字符串。具体表现为三类问题:

  • 工具错误被扁平化:工具抛出的错误被压缩成一个文本块,namecodestack全部丢失。这让未来的沙箱/重试插件无法区分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.ts:

export 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

同文件还导出了类型守卫 isHarnessError:

export 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 与 ToolArgsError

LlmError:携带可序列化失败事实的 LLM 专属错误

LlmError定义于 packages/llm/llm/src/index.ts,继承HarnessError并保留了既有的 code(如AUTHRATE_LIMITNO_ADAPTER)。它在基类之上增加了可序列化的事实字段failure,并通过LlmErrorOptions接受三个经过严格校验的提供商边界事实:

  • status:提供商边界观测到的合法 HTTP 状态码(必须是 100~599 的整数,否则构造即抛错);
  • providerRetryAfterMs:提供商要求的重试延迟毫秒数(必须是正有限数);
  • requestId:非空的提供商请求 ID。

构造时若messagecode为空字符串同样直接抛错。failure对象通过Object.freeze冻结,确保错误事实在跨 seam 传递中不可变。

ToolArgsError:工具参数校验错误

ToolArgsError定义于 packages/core/tools/src/schema.ts,继承HarnessError并携带固定 codeINVALID_ARGS

export class ToolArgsError extends HarnessError { readonly violations: string[] constructor(violations: string[]) { super(`invalid arguments: ${violations.join('; ')}`, 'INVALID_ARGS') this.name = 'ToolArgsError' this.violations = violations } }

它保留了既有 code(INVALID_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字段。核心实现是 toolErrorResult:

function 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.ts:

error?: { name: string; code: string }

正是这一字段使结构化失败信息得以保留进日志,供重试/沙箱插件和回放使用。从测试断言可看到真实场景中的形态,例如 packages/core/tools/tests/tools.spec.ts 中的error: { info: { name: 'HarnessError', code: 'TOOL_FAILURE' } },以及WRAPPER_FAILUREPOST_FAILUREBOOMDENIED等 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分支,而无需对消息做子串匹配。例如沙箱插件可以区分ENOENTEACCES,重试插件可以依据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
  • 子类 LlmError:packages/llm/llm/src/index.ts
  • 子类 ToolArgsError:packages/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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询