DeepSeek Harness 插件挂载成功却注册 0 个工具?output schema 静默失败排查
插件显示已挂载、状态正常,但工具列表是空的,console.warn 一次都没触发 —— 先检查手写 register() 的 output.schema 是不是写了 { type: 'json' }。 据讨论 #9875,json 不在输出白名单 SCHEMA_TYPES 里,注册期校验会抛错,而抛错被 cordis fiber 接住送进 context.logger.error,部署没有对应日志出口时就表现为「零工具、零提示」。这不是插件没加载,而是工具被拒绝注册。
DeepSeek Harness 插件挂载成功却注册 0 个工具的三个事实
同时满足这三条,才说明你遇到的是注册被拒,而不是插件根本没启用。 逐条确认,避免和「插件未加载」混为一谈:
- 插件在已加载列表里可见,profile 没有报加载失败,预期
apply已经被调用。 - 调用
ctx.tools.schemas()或查看工具列表,预期返回数量为 0,说明注册没有留下任何工具。 - 统计
console.warn调用次数为 0,界面上也没有红色报错,预期失败被日志层吞掉了。 - 如果插件注册了 4 个工具却全部消失,预期更像是整批注册在第一个坏 schema 处中断,而不是单个工具被跳过。
DeepSeek Harness 零工具时模型侧看到什么
工具没有进注册表,模型连「有这个工具」都不会知道,所以不会报错、只会绕开。 这解释了为什么用户感知是「功能没生效」而不是「插件坏了」:
- 系统提示里的工具清单为空,预期模型没有可调用的目标。
- 用户要求执行对应操作时,预期模型只能用文本回答或改用其他工具。
- 插件详情页仍显示已加载,预期排查方向会被带到权限或网络。
- 因为没有异常抛出,预期的重装插件也不会改变结果,除非改掉 schema。
- 只有把 logger.error 接到可见出口,预期启动时才会看到「工具被拒绝」而不是一片安静。
DeepSeek Harness 调用链:register 在哪一步抛错,错误被谁吞掉
据讨论中社区核对源码,失败发生在注册期的 schema 断言,而不是工具执行期。 工具注册的完整契约可以对照 工具插件开发说明 看,这里只留五步:
register()先检查output.render是否存在,预期缺失时直接拒绝。- 随后调用
assertSupportedJsonSchema(output.schema),预期对输出 schema 做白名单校验。 SCHEMA_TYPES只列了object、array、string、number、integer、boolean、null,预期json不在其中,校验抛出违规。- 抛错向上冒泡到插件的
apply,预期被 cordis fiber 捕获并交给context.logger.error。 - 部署没有配置该级别日志出口时,预期插件挂载完成、注册表为空、控制台安静,三者同时成立。
DSH插件修法:defineTool、空 schema 还是提升错误
三条路都能让工具重新注册,差别在改的是作者写法、单个 schema,还是整个错误的可见性。 先改写法,再补错误出口,避免下一次又静默。
路径一:改用 defineTool,让 json 走编译
这是作者侧最省心的修法:把「编译 schema」这一步交回给 DSH,而不是手写 wire schema。 据讨论,defineTool 的 valueSchemaSpec 路径本来就接受 json,并把它编译成 {}。
- 找到插件里手写的
ctx.tools.register({...})调用,预期能定位到工具定义处。 - 改用
defineTool包装,把工具名、描述、参数与输出 spec 传进去,预期工具通过包装器定义。 - 输出仍可写
{ type: 'json' },预期defineTool在编译阶段转成无约束 schema。 - 重新加载插件并调用
ctx.tools.schemas(),预期工具数量恢复,输出不再触发SCHEMA_TYPES违规。
const tool = defineTool({
name: "read_meta",
description: "读取插件元数据",
parameters: { type: "object", properties: {} },
output: { type: "json" },
})
路径二:手写 register 时用 {} 作无约束输出
如果必须保留手写 register(),就把 { type: 'json' } 换成 {},这是编译后的等价形式。 空对象表示「任意 JSON 值」,也是校验器接受的形态。
- 定位
output.schema字段,预期能看到当前的输出契约。 - 删除
type: 'json',改成空对象{},预期 schema 变成空对象。 - 如果原本靠
json表达嵌套结构,改用type: 'object'加properties,或保留{}并在render里自行收窄,预期 schema 要么表达结构、要么不带约束。 - 重新加载并确认
ctx.tools.schemas()数量正确,预期注册通过,模型侧收到的是不带类型约束的合法 schema。
ctx.tools.register({
name: "read_meta",
render: renderMeta,
parameters: { type: "object", properties: {} },
output: { schema: {} },
})
路径三:把注册拒绝提升到 UI 或启动日志
前两条修的是单个插件,这一条修的是「以后还会静默」的问题。 据讨论回复,注册期已经把契约违规当成 throw 处理,调用点能区分「被拒绝」和「本来就没有工具」。
- 在
register()的调用侧统计被拒绝的次数,预期能拿到「N 个工具在注册期被拒」。 - 在插件启动完成时输出一行汇总,预期启动日志里出现这个计数。
- 把该计数接到 DSH 插件市场 的详情页或系统日志面板,预期用户能看到插件「已加载但零工具」。
- 在抛错信息里补一句可用替代:无约束输出写
{},预期错误文本直接告诉作者该写什么。 - 预期结果:同类错误不再需要翻源码才能定位,插件市场页也能提示异常状态。
DeepSeek Harness 插件注册的自检清单
用真实注册路径测一遍,比给 stub 写 282 个绿测试更有用。 讨论里提到,stub 版 register 什么都接受,正是「测试全绿、插件零工具」能共存的原因。
- 测试里创建真实的 cordis Context 与 ToolRuntime,预期走的是生产注册路径。
- 走
defineTool或register()的真实路径注册工具,预期 stub 不挡在工具与运行时之间。 - 断言
ctx.tools.schemas()的数量与预期一致,预期数量不符时测试失败。 - 单独构造一个
{ type: 'json' }的用例,预期它要么被编译、要么给出可读错误。 - 把注册拒绝的计数与错误文本一起断言,预期同类问题在 CI 里失败,而不是等用户装上插件才发现。
来源:Discussion #9875。
常见问题
在 DeepSeek Harness 里先看插件是不是手写 register() 且 output schema 写了 type: json。据讨论 #9875,SCHEMA_TYPES 白名单只包含 object、array、string、number、integer、boolean、null,json 不在其中,注册时会抛错;插件挂载本身成功,但工具一个也没注册。用 ctx.tools.schemas() 数一下就能确认是 0。
DeepSeek Harness 里抛错点在 register() 内部,捕获点在插件 apply 之外。cordis 的 fiber 会接住抛出的 apply,把错误交给 context.logger.error,是否可见取决于部署有没有配置对应日志出口。没有出口时就是「挂载成功、零工具、零提示」,不是 register 静默返回。
DeepSeek Harness 的 register() 接收的是已经编译好的 wire schema,校验器只认 JSON Schema 标准类型。按讨论中维护者的说明,json 是 defineTool 作者侧 spec 的语法糖,会编译成 {};手写 register() 没有这一步编译,直接写 json 就越界。无约束输出应写成空对象 {}。
DeepSeek Harness 的 SCHEMA_TYPES 是发给模型 API 的 wire schema 类型表,json 不是 JSON Schema 的关键字。据讨论回复,往这张表里加 json 等于让校验器接受一个不存在的类型,会把「检查外部线格式」这一层弄坏;正确做法是让作者侧语法糖在编译阶段落到 {}。
相关术语
- SCHEMA_TYPES
- SCHEMA_TYPES 是 DSH 工具运行时允许出现在 wire schema 里的 JSON Schema 类型白名单。当前包含 object、array、string、number、integer、boolean、null,不包含 json,因此手写 schema 里的 json 会在注册时被拒绝。— https://github.com/deepseek-ai/deepseek-harness/discussions/9875
- register()(工具注册)
- register() 是 DSH 把工具定义挂到 ToolRuntime 的入口,接收已编译的 parameters 与 output.schema。它在注册期调用 assertSupportedJsonSchema 校验输出契约,违规直接抛出。— https://github.com/deepseek-ai/deepseek-harness/discussions/9875
- defineTool
- defineTool 是 DSH 面向插件作者的工具体包装,接受 valueSchemaSpec 形式的作者侧 schema 并负责编译。它允许 json 这类语法糖,编译结果是无约束的 {}。— https://github.com/deepseek-ai/deepseek-harness/discussions/9875
- cordis fiber
- cordis fiber 是插件生命周期的执行单元,负责调用插件的 apply 并捕获其中抛出的异常。异常会被路由到 context.logger.error,而不是让插件加载失败。— https://github.com/deepseek-ai/deepseek-harness/discussions/9875