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({
    // 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実際の処理を担う関数入社後の仕事

最小構成のツールを一通り見てみましょう(公式チュートリアルそのまま、軽く日本語化):

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: '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 ツールを書いて、モデルに実際に呼び出させる

プラグインを配置したい作業ディレクトリで、ディレクトリを作ります:

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"   # replace with your directory
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: '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 と書くこと):

yaml
- insert:
    - id: greet
      name: 'file:///E:/your-workspace/tool-demo/src/greet.js'

headless で一度走らせ、モデルに実際に呼び出してもらいましょう:

powershell
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"

ターミナル出力には次のように表示されます:

text
[greet] called with Ada

これが execute がモデルから実際に呼ばれた動かぬ証拠です——みなさんのコードが、モデルによる呼び出しを通じて本当に動いたわけです。モデルの返答にはツールが返した Hello, Ada! がそのまま含まれます。

ターミナルで黄色くハイライトされた [greet] called with Adaexecute がモデルによって実際に呼ばれたログであり、その下にある Hello, Ada! がツールからモデルへ返された結果です。

dsh に任せる:プロンプト一発で完結

ツールを書く流れはプラグインを書くのとまったく同じで、dsh 自身でもこなせます。dsh は defineTool のフィールド構成も schema の書き方もみなさんより熟知しています。

Web UI の入力欄に次のプロンプトをそのまま貼り付けてください:

text
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.jsondefineTool を実装したエントリ 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 内のロジックが誤っているexecuteconsole.log を仕込んでログでデバッグする

本章で学んだこと

下記をクリアできていれば合格です:

  • [ ] defineTool の四点セットを言える:name / description / parameters / execute
  • [ ] parameters は JSON Schema であり、フレームワークが自動でバリデートしてくれることを理解している
  • [ ] ツールプラグインを書いて ctx.tools に登録できる
  • [ ] headless を使ってモデルが実際にツールを呼んだことを検証できる
  • [ ] description がモデルの「いつこのツールを使うか」を決めることを知っている

Open Source · MIT · Community Driven