DeepSeek Harness: 最初のツール開発:defineTool

最終更新:2026-08-31

ツールは Agent の手——ツールを定義することは Agent に新しい能力を与えることです。defineTool は DSH のツール定義 DSL で、宣言的アプローチでツールの名前、パラメータ、実行ロジックを記述し、Agent がいつどのようにツールを呼び出すべきかを理解できるようにします。

💡 ヒント:defineTool の核心は「LLM にツールを理解させる」こと——name と description は LLM 向け、parameters は入力フォーマットを記述、execute は実際の実装です。良い description を書けば、Agent は適切なシナリオでツールを選択します。

📋 前提知識14-inject.md の完了、依存性注入を理解していること

1. 学習内容

ツールパイプライン


2. defineTool DSL 構文

▶ サンプル 1:

TYPESCRIPT
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 はツール定義オブジェクトを返し、デフォルトエクスポートとして直接使用できます:

TYPESCRIPT
// 方法1:デフォルトエクスポート
export default defineTool({ ... })

// 方法2:名前付きエクスポート
export const myTool = defineTool({ ... })

3. name と description

(1) 命名規則

ツール名は snake_case フォーマットを使用:

TYPESCRIPT
name: 'file_count'        // ✅
name: 'fileCount'         // ❌ 非推奨
name: 'FileCount'         // ❌ 非推奨
name: 'file-count'        // ❌ 非推奨

(2) description の書き方

description は LLM がツールを選択する根拠です。ポイント:

TYPESCRIPT
// ❌ 悪い説明
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 仕様に従います:

TYPESCRIPT
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) 文字列パラメータ

TYPESCRIPT
properties: {
  path: {
    type: 'string',
    description: 'ファイル数をカウントするディレクトリパス'
  },
  pattern: {
    type: 'string',
    description: 'ファイルをフィルタリングする Glob パターン(例:*.ts)',
    default: '*'
  }
}

(4) Enum パラメータ

TYPESCRIPT
properties: {
  sort_by: {
    type: 'string',
    enum: ['name', 'size', 'date'],
    description: 'ソート基準'
  }
}

(5) 配列パラメータ

TYPESCRIPT
properties: {
  extensions: {
    type: 'array',
    items: { type: 'string' },
    description: '含めるファイル拡張子(例:[".ts", ".js"])'
  }
}

(6) ネストオブジェクト

TYPESCRIPT
properties: {
  options: {
    type: 'object',
    properties: {
      recursive: { type: 'boolean', default: false },
      includeHidden: { type: 'boolean', default: false }
    }
  }
}

(7) required フィールド

TYPESCRIPT
required: ['path']          // path は必須
// pattern にはデフォルトがあるため必須ではない

5. execute 関数の実装

(1) 関数シグネチャ

TYPESCRIPT
async execute(params: Params, ctx: Context): Promise<Result>

▶ サンプル 2:

TYPESCRIPT
async execute({ path }, ctx) {
  const count = await countFiles(path)
  return { count, path }
}

(3) サービスへのアクセス

execute 内で ctx を通じて登録済みサービスにアクセス:

TYPESCRIPT
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) エラー処理

TYPESCRIPT
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) 戻り値フォーマット

構造化オブジェクトの返却を推奨:

TYPESCRIPT
// ✅ 構造化返却
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 に登録する必要があります:

TYPESCRIPT
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) 複数ツールの登録

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.tools.register(fileCountTool)
  ctx.tools.register(fileSizeTool)
  ctx.tools.register(fileSearchTool)
}

(3) 動的登録

TYPESCRIPT
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 のツールリストに表示:

TEXT 📖 参照専用
┌─────────────────────────────────────┐
│ 🔧 Tools                            │
├─────────────────────────────────────┤
│ file_count  │ Count files in a dir  │
│ file_size   │ Get file size info    │
│ file_search │ Search for files      │
└─────────────────────────────────────┘

(2) name と description の表示

(3) Agent 呼び出し表示

Agent がツールを呼び出すと、Web UI に表示:

TEXT 📖 参照専用
🤖 Agent:
🔧 Using tool:file_count
  → path:/home/alice/project
  
  Result:{ count:42, path:"//home/alice/project" }

▶ サンプル 8:ファイルカウントツール

すべての知識を組み合わせた完全なツールプラグイン:

TYPESCRIPT
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')
}

起動と確認:

BASH
# cordis.yml に登録
pnpm dsh web --patch

# Web UI でテスト
# 👤 Alice:/home/alice/project ディレクトリにファイルはいくつありますか?
# 🤖 Agent:🔧 file_count → { count:42, breakdown:{ ".ts":28, ".js":10, ".json":4 } }

❓ よくある質問

Q defineTool と ctx.tools.register の違いは?
A defineTool はツール定義オブジェクトを作成する DSL です。ctx.tools.register は定義をツールサービスに追加する登録メソッドです。両者は協働:まず defineTool で定義、次に register で登録。
Q ツール名は重複できますか?
A いいえ。同名ツールが後から登録されると前のものを上書きします。同名ツールの共存が必要な場合は、スコープ分離を使用してください(20-scope.md を参照)。
Q parameters の description を省略できますか?
A 技術的には可能ですが、強く非推奨。パラメータの description は LLM がパラメータの意味を理解するのに役立ち;省略すると Agent が誤ったパラメータを渡す原因になります。
Q execute はストリーミングデータを返せますか?
A 現在 defineTool の execute は完全な結果の返却のみサポート。ストリーミング出力は LLM アダプタの StreamChunk プロトコルで実現します(25-stream-error.md を参照)。
Q ツールから他のツールを呼び出せますか?
A はい、ctx.tools.execute('other_tool', params) で可能。ただし循環呼び出しに注意してください。
Q ツールのパラメータ解析をデバッグするには?
A execute の冒頭で params をログ出力:typescript async execute(params, ctx) { ctx.logger.info('params:', JSON.stringify(params)) // ... }

📖 まとめ


📝 練習問題

1. ⭐ 基礎:defineTool で current_time ツールを作成してください。オプションの timezone パラメータ(デフォルト UTC)を受け取り、現在時刻文字列を返す。DSH に登録し、Agent が正常に呼び出すことを確認すること。

2. ⭐⭐ 応用:file_count ツールを拡張し、min_sizemax_size パラメータ(バイト単位)を追加してファイルサイズ範囲でフィルタリングしてください。テスト:/tmp で 1KB より大きく 1MB より小さいファイルをカウント。

3. ⭐⭐⭐ チャレンジcode_stats ツールを作成し、指定ディレクトリのコード行数をカウントしてください。パラメータ:path(ディレクトリパス)、languages(言語フィルタ配列)。総行数、言語別行数、空行、コメント行を返す。正規表現でコード行/空行/コメント行を区別すること。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%