第 1 卷
张嘉熙 · DeepSeek Harness 前沿工程实践



你好,我是张嘉熙。
前四讲我们把 Cordis 这台机器拆开又装回去了:注册即可撤销的副作用、靠 inject 自动编排的依赖、五种事件分发、由一份 YAML 生成的插件树。机制齐了,从这一讲起我们进入核
心篇,看看前面学到的这些知识到底能做什么。
第一个要回答的是,在 dsh 里,一个 Agent“动手做事”的能力,是从哪儿来的?
本讲围绕两件事展开:
Tool:模型唯一看得见、能调用的东⻄,能力的入口。
seam:当一项能力的实现需要可替换时(比如 Bash 要在本地、沙箱、远程都能跑), dsh 用的组织方式。
我们先从 Tool 说起,用真实的 dsh 写一个工具并跑通它。
关于环境:从本讲起要用 dsh 的包。它们已列在本项目的 package.json 里,装一次即可 ——注意要带 --legacy-peer-deps:
复制代码 1npm install --legacy-peer-deps
为什么需要这个参数?这些包把 peer 依赖写成 caret 区间(比如 ^0.1.0-rc.7),并不锁死;一旦 registry 上出现更高的版本,npm 就会选它,而它又回头要求别的包也升到同一版
本——和 package.json 里写死的那套对不上,于是报 ERESOLVE。带上 --legacypeer-deps,就是让 npm 跳过 peer 的自动解析,按我们锁好的这套装。
关于源码对照:这一讲里所有 packages/... 路径均指 deepseek-harness 仓库(这一讲基于 0.1.0-rc.7),不在本项目里。本讲示例则是真实可跑的:npm run
start:ch05 跑工具,npm run start:ch05:seam 跑可替换能力。
用 Tool 接入能力
官方文档「开发一个工具」的最小示例长这样,注册一个 greet 工具,模型调它就回一句问候:
复制代码 1// src/ch05/greet-tool.ts
| 2 | importtype{Context}from'@deepseek-ai/cordis' |
|---|---|
| 3 | import{ defineTool }from'@deepseek-ai/dsh-tools' |
| 5 | exportconstname ='greet-tool' |
| 6 | exportconstinject = ['tools'] //等工具注册表就绪;少了它Cordis会抛错 |
| 13 | description:'Greet someone by name.', |
|---|---|
| 14 | parameters: { |
| 15 | name: {type:'string',required:true,description:'The name to greet' |
| 16 | }, |
17 18// ── 模型看不见,给框架用的 ──
19output: { 20schema: { type: 'string' },
21render: (_args, value) => [{ type: 'text', text: value as string }], 22},
23 24async execute(args) {
25return `Hello, ${args.name}!` 26},
27}), 28)
29}
跑起来需要三步,注意顺序(完整代码见 src/ch05/tool-main.ts):
复制代码 1// src/ch05/tool-main.ts(节选)
| 2 | import{Context}from'@deepseek-ai/cordis' |
|---|---|
| 3 | import{ToolRuntime}from'@deepseek-ai/dsh-tools' |
| 4 | importSystemPromptfrom'@deepseek-ai/dsh-system-prompt' |
| 5 | import*asgreetToolfrom'./greet-tool.ts' |
6 7const root = new Context()
| 8 | awaitroot.plugin(SystemPrompt) | //①ToolRuntime依赖systemPrompt,先提供它 |
|---|---|---|
| 9 | awaitroot.plugin(ToolRuntime) | //②挂上工具注册表本身 |
| 10 | constfiber =awaitroot.plugin(greetTool) | //③挂消费方插件,工具随之注册 |
第 1 步容易漏:ToolRuntime.inject = ['systemPrompt'],不先挂 SystemPrompt,工具注册表压根不会启动,ctx.tools 直接是 undefined(第二讲讲过
的:依赖没就绪,插件代码一行都不执行)。