Skip to content

CH 20 · defineTool:给 Agent 造一个工具

全文字数2429 字预估耗时约 20 分钟前置CH 19(插件三形态)难度可照做

本章目标

CH 18、19 我们写的插件只会打印日志——那只是验证"插件被加载了"。这一章写插件里真正值钱的东西:工具

工具是 Agent 的"手":模型说一句话,你写好的代码就被调用、真实干活、把结果送回给模型。前面几章你一直在用 dsh 内置的工具(读文件、跑命令、搜索),这一章我们自己造一个,让模型真的伸手进来调用它。

工具是插件的灵魂

回顾一下你每天都在用的东西:dsh 里那个 Agent 之所以能读文件、写文件、跑命令,靠的是工具(ctx.tools 里注册的一个个能力)。模型本身只会"说话",是工具让它能"动手"。

工具插件的最简骨架:

js
import { defineTool } from '@deepseek-ai/dsh-tools'

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

export function apply(ctx) {
  ctx.tools.register(defineTool({
    // 工具的四个部分,下面逐个拆
  }))
}

inject: ['tools'] 声明"我要用工具注册表",CH 19 学过;ctx.tools.register(...) 就是往注册表里挂一个工具。

defineTool 的四件套

defineTool 接收一个对象,告诉 dsh"这个工具叫什么、什么时候用、要什么参数、怎么干活"。一个工具 = 一份"给 Agent 的招聘 JD":

字段含义打个比方
name工具的名字,模型用它来调用岗位名
description告诉模型"这工具是干嘛的、什么时候该用"岗位职责
parameters声明要哪些参数、哪些必填需要递交的材料
execute真正干活的函数入职后干的活

再看一个完整的最小工具(官方教程同款,我们稍作翻译):

js
import { defineTool } from '@deepseek-ai/dsh-tools'

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

export function apply(ctx) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: '按名字打招呼',
    parameters: {
      name: { type: 'string', required: true, description: '要打招呼的人名' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

四个重点:

  • parameters 写的是 JSON Schematype: 'string' 声明参数类型,required: true 声明必填。框架会自动校验模型传来的参数,不合规会报错——你不用在 execute 里手动做类型检查。
  • execute(args) 是真正干活的函数args 已经被校验过,直接放心用。它返回一个"规范值"(这里是一段字符串)。
  • output.schema 声明返回值的形状output.render 把返回值转成模型能看到的内容(这里是文本块)。返回值先以规范形态存在,渲染层负责"翻译"给模型。
  • description 极其重要:模型靠它判断"这个工具现在该不该用"。写清楚、写具体,模型才懂得在合适的时机调用它。

动手:写一个 greet 工具,让模型真的调用

在一个你想放插件的工作区里建目录:

powershell
New-Item -ItemType Directory -Path "tool-demo\src" -Force

第 1 步:装依赖

defineTool 来自 @deepseek-ai/dsh-tools,先在插件目录装上:

powershell
cd "E:\software-workspace\DeepSeek harness demo\tool-demo"   # 换成你自己的目录
npm init -y
npm install @deepseek-ai/dsh-tools

记得在 package.json 里加一行 "type": "module"(CH 19 说过,不加会打一堆 warning)。

第 2 步:写工具

新建 tool-demo\src\greet.js,内容就是上面那个 greet 工具。execute 里我加了一行日志,方便在终端确认它真的被调用了:

js
import { defineTool } from '@deepseek-ai/dsh-tools'

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

export function apply(ctx) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: '按名字打招呼',
    parameters: {
      name: { type: 'string', required: true, description: '要打招呼的人名' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      console.log('[greet] called with', args.name)
      return `Hello, ${args.name}!`
    },
  }))
}

第 3 步:声明并验证

新建 tool-demo\cordis.yml(路径换成你自己的,空格记得 %20):

yaml
- insert:
    - id: greet
      name: 'file:///E:/你的工作区/tool-demo/src/greet.js'

用 headless 跑一次,让模型真的调用它:

powershell
cd 你的工作区目录
dsh --profile headless --patch "./tool-demo/cordis.yml" "你必须调用 greet 工具,跟 Ada 打个招呼,然后原样告诉我工具的返回结果"

终端输出里你会看到:

text
[greet] called with Ada

这就是 execute 被模型真实调用的证据——你的代码真的被模型伸手进来执行了。而模型的回复里会包含工具返回的 Hello, Ada!

终端里黄色的 [greet] called with Ada 就是 execute 被模型真实调用的日志,下面的 Hello, Ada! 是工具返回给模型的结果。

让 dsh 自己搭:一条提示词搞定

写工具的流程和写插件完全一样,dsh 自己就能干,而且它比你更清楚 defineTool 有哪些字段、schema 该怎么写。

直接在 Web UI 的输入框发这条提示词:

text
在我当前的工作区帮我写一个工具插件:用 defineTool 定义一个最简单的工具(带一个必填参数,execute 里返回一段文字)。你先去 dsh 官方文档读清楚 defineTool 的字段和参数校验规则,按官方规范实现,再跑一次 headless 让模型真实调用它,最后告诉我结果和文件放在哪。

它会自己去查文档、装依赖、写工具、验证模型真的调用了它。你只负责验收。

这是我实际跑这条提示词的结果:它自己读文档、建好了插件的三件套(声明 bundle 的 package.json、defineTool 的入口 index.js、一行 insert 的 cordis.patch.yml),还发现 Windows 下路径带空格会让安装命令解析出错,主动把插件挪到无空格路径再装——自己踩到坑,自己绕过去了。

跑完之后,它还把踩的坑整理成了一张清单:插件导出的形态要求、inject 必须显式声明、workspace 包要构建产物才能跑、路径空格的坑。

常见坑

问题怎么回事怎么处理
Cannot find package '@deepseek-ai/dsh-tools'插件目录没装依赖npm install @deepseek-ai/dsh-tools
模型一直不调用你的工具description 写得不清楚,模型不知道何时用把 description 写具体:"当用户要求 X 时使用"
参数没传对schema 和模型理解不一致parameters 里给每个参数写清 description
工具报了参数错误模型传了非法参数required: true 标好必填项,类型写对
模型调用了但结果不对execute 里逻辑有误execute 里加 console.log 调试,看日志

这一章你学到了什么

能自己完成下面几条,就算过关:

  • [ ] 能说出 defineTool 四件套:name / description / parameters / execute
  • [ ] 知道 parameters 是 JSON Schema,框架会自动校验参数
  • [ ] 会写一个工具插件并注册到 ctx.tools
  • [ ] 会用 headless 验证模型真的调用了你的工具
  • [ ] 知道 description 决定模型什么时候用你的工具

Open Source · MIT · Community Driven