07 · 进入 Harness:注册真实工具

目标:把前六课的模式用到真实系统——向 harness 的 tools 服务注册一个模型可调用的工具,跑通真实执行流水线并观察结果事件。无需 API 密钥。 环境:官方教程环境(deepseek-harness 仓库 tmp/cordis-tutorial/,见 README 方式二)

核心认知

前面学的所有模式在这里一一对应:

模式出处在 harness 中的体现
inject: ['tools']第 03 课等工具注册表就绪才启动
ctx.tools.register(...)第 02 课注册 disposer 附着到插件,卸载即注销工具
ctx.on('tools/result', ...)第 04 课独立插件观察应用中的每次工具调用

两个插件都不知道对方存在,由注册表服务和事件连接——这就是组合式架构的终点形态。

实例 A:工具插件

// greet-tool.ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { CallId } 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}!`
    },
  }))
 
  // 用真实执行流水线驱动一次调用(代替模型发起)。
  // CallId 给关联 id 打上提供方风格的烙印。
  void (async () => {
    const result = await ctx.tools.execute({
      callId: CallId('demo-1'),
      name: 'greet',
      arguments: { name: 'Cordis' },
      signal: new AbortController().signal,
    })
    console.log('tool replied:', JSON.stringify(result.content))
  })()
}

defineTool 的三个作用:

  • 把 parameters 规约转换成向模型展示的 JSON Schema
  • 推导 args 类型,并在 execute 运行前校验模型提供的参数
  • 工具返回由 output.schema 声明的规范值;output.render 作为 Native renderer 另行生成可持久化的结果内容

实例 B:观察插件(事件监听)

// tool-logger.ts
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-tools'   // 引入包级声明合并,让 'tools/result' 有类型
 
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' 与第 04 课导入 stats.ts 的做法相同,只是扩展到了包级别。

组合并运行

# cordis.yml
- name: '@deepseek-ai/dsh-system-prompt'   # dsh-tools inject systemPrompt,必须列出提供方
- name: '@deepseek-ai/dsh-tools'
- name: './tool-logger.ts'
- name: './greet-tool.ts'
node --import tsx ../../vendor/cordis/bin.js
[tool-logger] greet -> Hello, Cordis!
tool replied: [{"type":"text","text":"Hello, Cordis!"}]

注意顺序:logger 先触发——tools/result 在结果物化过程中发出,发生在 execute 的 promise 兑现之前。

从这里走向完整 Agent

真实 agent 就是这套组合再加上更多插件:LLM 适配器、agent loop、持久化和运行入口。对照 examples/headless-agent/cordis.yml,学完本教程你已经能读懂其中每个配置项——把 greet-tool.ts 加入该文件副本即可给自己的 agent 加工具。

进阶阅读:

  • 构建工具:defineTool 深入,含呈现与更丰富 schema
  • 三层能力设计:harness 如何组织可替换能力
  • 各子系统页面的 cordis-surface 区块:可以注入和监听的所有服务与事件清单

上一课:06-composition-hmr | 总览:README | 概念页:cordis