CH 20 · defineTool:Agent のためのツールを仕立てる
本章目標
CH 18 と 19 で書いたプラグインはログを出すだけでした——あれは「プラグインが読み込まれた」ことを確認するためだけのものでした。本章で扱うのは、プラグインにおいて本当に価値を持つ存在:ツールです。
ツールは Agent の「手足」です。モデルがある指示を出せば、みなさんが書いたコードが呼び出され、実際の作業を行い、その結果をモデルへ返します。これまでの章で dsh 組み込みのツール(ファイル読み込み、コマンド実行、検索)は使い倒してきましたが、本章では自分たちでその一つを仕立て、モデルに本当に手を伸ばさせて呼び出してもらいます。
ツールはプラグインの魂
日頃使っているものを思い返してみてください。dsh の Agent がファイルを読み、ファイルを書き、コマンドを実行できるのは、すべてツールのおかげです(ctx.tools に登録された一つ一つの機能です)。モデル本体は「話す」ことしかできません。ツールが「手を動かす」ことを可能にします。
ツールプラグインの最小スケルトンはこうなります:
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'greet-tool'
export const inject = ['tools']
export function apply(ctx) {
ctx.tools.register(defineTool({
// The four parts of a tool, broken down one by one below
}))
}inject: ['tools'] は「ツール登録表を使いたい」という宣言で、CH 19 で学んだとおり。ctx.tools.register(...) によってツールを登録表にぶら下げます。
defineTool の四点セット
defineTool はオブジェクトを受け取り、dsh に対して「このツールは何という名前で、いつ使うべきで、どんなパラメータが必要で、どうやって動くか」を伝えます。ツール = 「Agent に向けた採用 JD」と言い換えてもよいでしょう:
| フィールド | 意味 | 比喩で言うと |
|---|---|---|
name | ツールの名前、モデルが呼び出すときに使う | 募集職種 |
description | モデルに対して「これは何のツールか、いつ使うか」を伝える | 職務内容 |
parameters | どのパラメータが必須で、どれが任意かを宣言する | 提出書類一覧 |
execute | 実際の処理を担う関数 | 入社後の仕事 |
最小構成のツールを一通り見てみましょう(公式チュートリアルそのまま、軽く日本語化):
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: 'Greet by name',
parameters: {
name: { type: 'string', required: true, description: 'Name of the person to greet' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
return `Hello, ${args.name}!`
},
}))
}押さえておきたい四つのポイント:
parametersは JSON Schema で書く:type: 'string'で型を宣言し、required: trueで必須であることを示します。フレームワークがモデルから渡されたパラメータを自動でバリデートし、規約違反ならエラーを返します——executeのなかで型チェックを手書きする必要はありません。execute(args)が実際の処理を担う関数:argsはすでにバリデーション済みなので、そのまま安心して使えます。戻り値は「正規化された値」(ここでは文字列)です。- **
output.schemaは戻り値の形状を宣言し、output.renderはその戻り値をモデルが読めるコンテンツ(ここではテキストブロック)に変換します。戻り値はまず正規化された形で存在し、レンダリング層がそれをモデル向けに「翻訳」する役割を担います。 descriptionの重要性は極めて高い:モデルはこのフィールドを読んで「今このツールを使うべきか」を判断します。具体的かつ明確に書くほど、モデルは適切なタイミングで呼び出せるようになります。
実機演習:greet ツールを書いて、モデルに実際に呼び出させる
プラグインを配置したい作業ディレクトリで、ディレクトリを作ります:
New-Item -ItemType Directory -Path "tool-demo\src" -Force第 1 ステップ:依存をインストール
defineTool は @deepseek-ai/dsh-tools に含まれています。まずプラグインディレクトリへインストールします:
cd "E:\software-workspace\DeepSeek harness demo\tool-demo" # replace with your directory
npm init -y
npm install @deepseek-ai/dsh-toolspackage.json に "type": "module" を一行追加するのを忘れずに(CH 19 でも触れましたが、入れないと warning が大量に出ます)。
第 2 ステップ:ツールを書く
tool-demo\src\greet.js を新規作成し、中身は先ほどの greet ツールと同じです。execute のなかにログを一行追加しており、ターミナルで本当に呼ばれたか確認しやすくしています:
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: 'Greet by name',
parameters: {
name: { type: 'string', required: true, description: 'Name of the person to greet' },
},
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 と書くこと):
- insert:
- id: greet
name: 'file:///E:/your-workspace/tool-demo/src/greet.js'headless で一度走らせ、モデルに実際に呼び出してもらいましょう:
cd your-workspace-directory
dsh --profile headless --patch "./tool-demo/cordis.yml" "You must call the greet tool, say hi to Ada, then tell me verbatim what the tool returned"ターミナル出力には次のように表示されます:
[greet] called with Adaこれが execute がモデルから実際に呼ばれた動かぬ証拠です——みなさんのコードが、モデルによる呼び出しを通じて本当に動いたわけです。モデルの返答にはツールが返した Hello, Ada! がそのまま含まれます。

ターミナルで黄色くハイライトされた [greet] called with Ada が execute がモデルによって実際に呼ばれたログであり、その下にある Hello, Ada! がツールからモデルへ返された結果です。
dsh に任せる:プロンプト一発で完結
ツールを書く流れはプラグインを書くのとまったく同じで、dsh 自身でもこなせます。dsh は defineTool のフィールド構成も schema の書き方もみなさんより熟知しています。
Web UI の入力欄に次のプロンプトをそのまま貼り付けてください:
In my current workspace, help me write a tool plugin: use defineTool to define the simplest tool (with one required parameter, returning some text in execute). First read the dsh official docs to understand defineTool's fields and parameter validation rules, implement per the official spec, then run a headless to have the model actually call it, and finally tell me the result and where the files are.dsh は自分でドキュメントを調べ、依存をインストールし、ツールを書き、モデルが本当に呼んでくれたかを検証してくれます。みなさんの仕事は確認だけです。

これは実際にこのプロンプトを走らせた結果です:dsh は自分でドキュメントを読み、プラグイン三点セット(bundle を宣言する package.json、defineTool を実装したエントリ index.js、一行の insert を持つ cordis.patch.yml)を構築しました。さらに Windows でパスに空白が含まれるとインストールコマンドの解析が壊れることを自分で発見し、プラグインを空白のないパスへ移動してからインストールしています——自分で落とし穴を踏み、自分で回避したわけです。

実行後、dsh は踏みやすい落とし穴まで一覧に整理してくれました:プラグイン export の形態要件、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がモデルの「いつこのツールを使うか」を決めることを知っている
