mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
4.5 KiB
4.5 KiB
Agent Note: 在每个诊断边界渲染错误 cause 链
Status: implemented
English | 中文
问题
TUI 连接不可达的 DeepSeek 端点时,失败只显示一条 fetch failed 通知,没有任何进一步细节。两个独立缺口共同造成了这个死胡同:
- undici 的
fetch把所有传输层失败(DNS、连接被拒、TLS、代理)包装成裸的TypeError: fetch failed,可操作的细节——ECONNREFUSED、bad port、Happy Eyeballs 的 AggregateError——都在error.cause上。harness 里的每个诊断边界都只渲染error.message(或对 Error 等价的String(error)),于是包装层在 TUI 通知、持久化的turn/endreason 和所有日志行里都掩盖了诊断信息。 - readline 入口(
dsh-stdio)完全不渲染失败原因:reason.kind === 'error'的turn/end只打印下一个>提示符,同样的失败在demo:repl里就是纯粹的沉默。
决策
dsh-llm导出errorChain(value):渲染抛出值及其完整cause链(outer: inner: …)与 AggregateError 成员(msg [m1; m2]),并容错循环 cause 和恶意强制转换。它只是用于诊断界面的渲染器;路由仍然基于HarnessError.code。- DeepSeek 适配器把拿到响应之前的传输失败包装成
LlmError('TRANSPORT'),写明配置的baseURL并将原始拒绝值作为cause串入错误链。被中止的请求变为LlmError('ABORTED');由于轮次信号已处于中止状态,agent loop(智能体循环)仍将该轮次归类为取消而非恢复。 - 每个诊断边界改用
errorChain而非error.message/String(error):agent-loop 的持久化turn/end错误消息(errorData)、其日志警告、TUI 的agent/error通知与启动失败行、以及dsh-stdio的启动失败日志行。实时agent/error事件与SettleReason以unknown原样保留抛出值;各诊断 Consumer 自行渲染,而不是由循环把它包装成另一个错误。dsh-agent-loop、dsh-stdio、dsh-tui里各自的renderThrown副本被删除,统一使用这一个共享渲染器。 dsh-stdio渲染失败的turn/endreason:[turn failed <code>] <message>、[turn aborted] <reason>、[turn rejected] <reason>、[turn interrupted by a previous process exit]以及输出 token 上限通知。通过声明合并扩展出的未知 kind 按普通 turn 结束处理。
errorChain 与 HarnessError 一样放在 dsh-llm 里,理由相同:它是每个消费方都已导入的叶子包,共享不增加新的依赖边。
考虑过的替代方案
在每个错误的构造函数里渲染链(把 cause 写入 message)。 否决:当消费方同时遍历 cause 时会双重渲染(适配器修复的第一版产出了 … fetch failed: bad port: fetch failed: bad port),并且破坏了想按内层错误路由的消费方所需的结构化链。
只做一个感知 cause 的日志导出器。 否决:持久化的 turn/end reason 和 TUI 通知不是日志行;被掩盖的消息会留在会话日志——轮次内失败的唯一持久记录——以及主要 UI 表面里。
逐包升级 renderThrown。 否决:三个包已经各自持有几乎相同的私有副本;分别升级只会固化共享渲染器所要消除的重复。
后果
- 传输失败现在在 TUI 通知、readline transcript(文本记录)和持久化会话日志里显示为
DeepSeek API request to <baseURL> failed: fetch failed: connect ECONNREFUSED …,代价是更长的诊断字符串。 - 持久化的
turn/end错误消息包含 cause 细节。现有快照 fixture(测试前置数据)字节级一致地回放,因为其脚本化错误不带cause(对这类错误errorChain(err)等于err.message);只有单元测试的期望字符串有变化。从真实传输失败录制的 fixture 会携带完整链。 errorChain渲染message而不带类名(String(error)会渲染Error: <message>),因此日志行里的裸TypeError会丢失类型标签,除非消息为空(此时回退到类名)。在这些诊断边界上,链细节被判断为比类名更有价值。dsh-stdio对失败轮次的输出不再沉默;解析 transcript 的管道消费方会看到新的[turn …]行。dsh-subagent、dsh-workflow、dsh-skill、dsh-workflow-workerthread里剩余的renderThrown副本仍不渲染链;它们包装的是自带消息的包内错误,等诊断信息证明不足时再采用errorChain。