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