DeepSeek Harness: 最初のツール開発:defineTool
最終更新:2026-08-31
ツールは Agent の手——ツールを定義することは Agent に新しい能力を与えることです。defineTool は DSH のツール定義 DSL で、宣言的アプローチでツールの名前、パラメータ、実行ロジックを記述し、Agent がいつどのようにツールを呼び出すべきかを理解できるようにします。
📋 前提知識:14-inject.md の完了、依存性注入を理解していること
1. 学習内容
- defineTool DSL 構文
- name/description/parameters 宣言
- JSON Schema パラメータ定義
- execute 関数の実装
- ctx.tools へのツール登録
- Web UI でのツール表示
- 完全例:ファイルカウントツール
2. defineTool DSL 構文
▶ サンプル 1:
import { defineTool } from '@deepseek-ai/dsh'
export default defineTool({
name: 'tool_name',
description: 'このツールが何をするか',
parameters: {
type: 'object',
properties: { /* ... */ },
required: [ /* ... */ ]
},
async execute(params, ctx) {
// ツールロジック
return result
}
})
(2) フィールド説明
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
name |
string | ✅ | ツールの一意識別子、小文字 + アンダースコア |
description |
string | ✅ | ツール機能の説明、LLM が読むために書く |
parameters |
JSON Schema | ✅ | パラメータ定義 |
execute |
function | ✅ | 実行ロジック |
(3) エクスポート方法
defineTool はツール定義オブジェクトを返し、デフォルトエクスポートとして直接使用できます:
// 方法1:デフォルトエクスポート
export default defineTool({ ... })
// 方法2:名前付きエクスポート
export const myTool = defineTool({ ... })
3. name と description
(1) 命名規則
ツール名は snake_case フォーマットを使用:
name: 'file_count' // ✅
name: 'fileCount' // ❌ 非推奨
name: 'FileCount' // ❌ 非推奨
name: 'file-count' // ❌ 非推奨
(2) description の書き方
description は LLM がツールを選択する根拠です。ポイント:
- ツールが何をするかを説明
- いつ使うべきかを説明
- 「テストツール」「サンプルツール」のような無意味な説明は避ける
// ❌ 悪い説明
description: 'テスト用のツール'
// ✅ 良い説明
description: 'Count the number of files in a directory. Returns total count and breakdown by file extension. Use when user asks about file statistics or directory contents.'
(3) 多言語 description
description は現在英語のみサポート。ユーザー入力が他の言語であっても、LLM は description からツールの用途を理解します。
4. JSON Schema パラメータ定義
(1) 基本構造
parameters は JSON Schema 仕様に従います:
parameters: {
type: 'object',
properties: {
param_name: {
type: 'string',
description: 'パラメータの説明'
}
},
required: ['param_name']
}
(2) サポートされる型
| JSON Schema 型 | TypeScript 型 | 説明 |
|---|---|---|
string |
string | 文字列 |
number |
number | 数値 |
integer |
number | 整数 |
boolean |
boolean | 真偽値 |
object |
object | ネストオブジェクト |
array |
array | 配列 |
(3) 文字列パラメータ
properties: {
path: {
type: 'string',
description: 'ファイル数をカウントするディレクトリパス'
},
pattern: {
type: 'string',
description: 'ファイルをフィルタリングする Glob パターン(例:*.ts)',
default: '*'
}
}
(4) Enum パラメータ
properties: {
sort_by: {
type: 'string',
enum: ['name', 'size', 'date'],
description: 'ソート基準'
}
}
(5) 配列パラメータ
properties: {
extensions: {
type: 'array',
items: { type: 'string' },
description: '含めるファイル拡張子(例:[".ts", ".js"])'
}
}
(6) ネストオブジェクト
properties: {
options: {
type: 'object',
properties: {
recursive: { type: 'boolean', default: false },
includeHidden: { type: 'boolean', default: false }
}
}
}
(7) required フィールド
required: ['path'] // path は必須
// pattern にはデフォルトがあるため必須ではない
5. execute 関数の実装
(1) 関数シグネチャ
async execute(params: Params, ctx: Context): Promise<Result>
params:ユーザー(Agent)が渡すパラメータ、型は parameters 定義から推論ctx:Cordis コンテキスト、注入されたサービスにアクセス可能- 戻り値:JSON シリアライズ可能な任意のデータ
▶ サンプル 2:
async execute({ path }, ctx) {
const count = await countFiles(path)
return { count, path }
}
(3) サービスへのアクセス
execute 内で ctx を通じて登録済みサービスにアクセス:
export const inject = ['fs']
export default defineTool({
name: 'file_count',
// ...
async execute({ path }, ctx) {
const files = await ctx.fs.readdir(path)
return { count: files.length }
}
})
(4) エラー処理
async execute({ path }, ctx) {
try {
const files = await ctx.fs.readdir(path)
return { count: files.length, path }
} catch (error) {
return {
error: true,
message: `Failed to read directory: ${error.message}`
}
}
}
ツールは未キャッチの例外をスローすべきではありません——エラーオブジェクトを返すことで Agent が失敗理由を理解でき、クラッシュより親切です。
(5) 戻り値フォーマット
構造化オブジェクトの返却を推奨:
// ✅ 構造化返却
return {
count: 42,
path: '/home/alice/project',
breakdown: {
typescript: 28,
javascript: 10,
other: 4
}
}
// ❌ プレーンテキスト返却
return 'Found 42 files in /home/alice/project'
構造化返却により、Agent は結果をプログラマティックに利用でき、テキストを表示するだけにとどまりません。
6. ctx.tools へのツール登録
(1) プラグイン内での登録
defineTool で定義したツールはプラグインを通じて ctx.tools に登録する必要があります:
import { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh'
const fileCountTool = defineTool({
name: 'file_count',
description: 'Count files in a directory',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Directory path' }
},
required: ['path']
},
async execute({ path }, ctx) {
const files = await ctx.fs.readdir(path)
return { count: files.length }
}
})
export const name = 'tool-file-count'
export const inject = ['tools', 'fs']
export function apply(ctx: Context) {
ctx.tools.register(fileCountTool)
}
(2) 複数ツールの登録
export function apply(ctx: Context) {
ctx.tools.register(fileCountTool)
ctx.tools.register(fileSizeTool)
ctx.tools.register(fileSearchTool)
}
(3) 動的登録
export function apply(ctx: Context) {
const tools = [fileCountTool, fileSizeTool]
for (const tool of tools) {
ctx.tools.register(tool)
ctx.logger.info(`registered tool: ${tool.name}`)
}
}
7. Web UI でのツール表示
(1) 自動表示
ctx.tools に登録されたツールは自動的に Web UI のツールリストに表示:
┌─────────────────────────────────────┐
│ 🔧 Tools │
├─────────────────────────────────────┤
│ file_count │ Count files in a dir │
│ file_size │ Get file size info │
│ file_search │ Search for files │
└─────────────────────────────────────┘
(2) name と description の表示
name:ツール識別子として表示description:ツール説明として表示
(3) Agent 呼び出し表示
Agent がツールを呼び出すと、Web UI に表示:
🤖 Agent:
🔧 Using tool:file_count
→ path:/home/alice/project
Result:{ count:42, path:"//home/alice/project" }
▶ サンプル 8:ファイルカウントツール
すべての知識を組み合わせた完全なツールプラグイン:
import { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh'
export const name = 'tool-file-count'
export const inject = ['tools', 'fs']
const fileCountTool = defineTool({
name: 'file_count',
description: 'Count the number of files in a directory. Returns total count and breakdown by file extension. Use when user asks about file statistics or directory contents.',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Absolute or relative path to the directory'
},
recursive: {
type: 'boolean',
description: 'Whether to count files in subdirectories',
default: false
},
extensions: {
type: 'array',
items: { type: 'string' },
description: 'Filter by file extensions (e.g. [".ts", ".js"]). Count all if omitted.'
}
},
required: ['path']
},
async execute({ path, recursive, extensions }, ctx) {
try {
const entries = recursive
? await ctx.fs.readdirRecursive(path)
: await ctx.fs.readdir(path)
let files = entries.filter(e => !e.isDirectory)
if (extensions && extensions.length > 0) {
files = files.filter(f =>
extensions.some(ext => f.name.endsWith(ext))
)
}
const breakdown: Record<string, number> = {}
for (const f of files) {
const ext = f.name.includes('.')
? '.' + f.name.split('.').pop()
: '(no extension)'
breakdown[ext] = (breakdown[ext] || 0) + 1
}
return {
count: files.length,
path,
recursive,
breakdown
}
} catch (error: any) {
return {
error: true,
message: `Failed to count files: ${error.message}`
}
}
}
})
export function apply(ctx: Context) {
ctx.tools.register(fileCountTool)
ctx.logger.info('file_count tool registered')
}
起動と確認:
# cordis.yml に登録
pnpm dsh web --patch
# Web UI でテスト
# 👤 Alice:/home/alice/project ディレクトリにファイルはいくつありますか?
# 🤖 Agent:🔧 file_count → { count:42, breakdown:{ ".ts":28, ".js":10, ".json":4 } }
❓ よくある質問
defineTool はツール定義オブジェクトを作成する DSL です。ctx.tools.register は定義をツールサービスに追加する登録メソッドです。両者は協働:まず defineTool で定義、次に register で登録。ctx.tools.execute('other_tool', params) で可能。ただし循環呼び出しに注意してください。typescript async execute(params, ctx) { ctx.logger.info('params:', JSON.stringify(params)) // ... } 📖 まとめ
- defineTool は DSH のツール定義 DSL:name + description + parameters + execute
- name は snake_case;description はツールの用途とユースケースを明確に説明
- parameters は JSON Schema に従い、string/number/boolean/object/array をサポート
- execute は params と ctx を受け取り、構造化結果を返す
- ツールは
ctx.tools.register()で登録し、Web UI に自動表示 - プレーンテキストより構造化オブジェクトを返却;エラー時は例外をスローせずエラーオブジェクトを返す
📝 練習問題
1. ⭐ 基礎:defineTool で current_time ツールを作成してください。オプションの timezone パラメータ(デフォルト UTC)を受け取り、現在時刻文字列を返す。DSH に登録し、Agent が正常に呼び出すことを確認すること。
2. ⭐⭐ 応用:file_count ツールを拡張し、min_size と max_size パラメータ(バイト単位)を追加してファイルサイズ範囲でフィルタリングしてください。テスト:/tmp で 1KB より大きく 1MB より小さいファイルをカウント。
3. ⭐⭐⭐ チャレンジ:code_stats ツールを作成し、指定ディレクトリのコード行数をカウントしてください。パラメータ:path(ディレクトリパス)、languages(言語フィルタ配列)。総行数、言語別行数、空行、コメント行を返す。正規表現でコード行/空行/コメント行を区別すること。