第 8 章 动手实战(四):进入 Harness,注册真实工具

本章对应官方教程第 7 章:向 harness 的 tools 服务注册一个模型可调用的工具,通过真实工具流水线执行它,并观察结果事件。整个示例无需密钥,也不会调用模型。

8.1 工具插件

创建 greet-tool.ts:

import type { Context } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { ToolCallId } from '@deepseek-ai/dsh-llm'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet the named person.',
    parameters: {
      name: { type: 'string', required: true, description: 'Who to greet' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))

  // 替代模型,把一次调用驱动到真实执行流水线中。
  // ToolCallId 品牌化了一个 provider 会发放的关联 id。
  void (async () => {
    const result = await ctx.tools.execute({
      callId: brandString<ToolCallId>('demo-1'),
      name: 'greet',
      arguments: { name: 'Cordis' },
      signal: new AbortController().signal,
    })
    console.log('tool replied:', JSON.stringify(result.content))
  })()
}

这里用到了前几章的每个模式:

  • inject: ['tools'](第 5 章):等待工具注册表就绪;

  • ctx.tools.register(...)(第 4 章):注册 disposer 附着到本插件,卸载时自动注销工具;

  • defineTool 把 parameters 规约转换为向模型展示的 JSON Schema,推导 args 类型,并在 execute 运行前校验模型提供的参数;

  • 工具返回由 output.schema 声明的规范值;output.render 作为 Native renderer(原生渲染器),另行生成可持久化的结果内容。

8.2 观察插件

创建 tool-logger.ts——一个独立插件,通过 harness 的 tools/result 事件观察应用中的每次工具调用:

import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-tools'

export const name = 'tool-logger'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.on('tools/result', (exec, result) => {
    const text = result.content
      .map(block => (block.type === 'text' ? block.text : ''))
      .join('')
    console.log(`[tool-logger] ${exec.name} -> ${text}`)
  })
}

import type {} from '@deepseek-ai/dsh-tools' 引入该包的声明合并,使 'tools/result' 及其 payload 带有类型——与第 6 章导入 stats.ts 的做法相同,只是扩展到了包级别。

8.3 组合并运行

- name: '@deepseek-ai/dsh-system-prompt'
- name: '@deepseek-ai/dsh-tools'
- name: './tool-logger.ts'
- name: './greet-tool.ts'

@deepseek-ai/dsh-tools 会注入 systemPrompt 服务(工具需要向系统提示词贡献 schema),所以组合中必须列出该服务的提供方。缺少时工具插件会像第 7 章描述的那样保持 PENDING。

node --import tsx ../../vendor/cordis/bin.js
[tool-logger] greet -> Hello, Cordis!
tool replied: [{"type":"text","text":"Hello, Cordis!"}]

logger 先触发:tools/result 在结果物化过程中发出,发生在 execute 返回的 promise 向调用方兑现之前。两个插件互不知晓对方存在——它们由注册表服务和事件连接。

8.4 从这里走向完整 Agent

真实 agent 就是这套组合再加更多插件:LLM 适配器、agent loop、持久化和应用入口。对照官方仓库中的两份文件:

读懂之后,通过一个小型 --patch overlay 加入 greet-tool.ts 即可运行在真实 dsh 之上。

官方推荐后续阅读

  • 构建工具:深入 defineTool,包括呈现和更丰富的 schema;

  • 三层能力设计:harness 如何组织可替换能力;

  • 子系统页面上生成的 cordis-surface 区块:可注入与可监听的一切,各在其所属页面上。


依据官方教程第 7 章(07-into-the-harness)整理。

DeepSeek Harness / 第 8 章 动手实战(四):进入 Harness,注册真实工具 0 字 0 行 cosolar
2026-09-17T10:12:14.001667106Z 2026-09-17T10:39:03.929679242Z