Skip to content

CH 21 · フックプラグインとインターセプト:ツール実行前に手を加える

全文字数約 2,560 字所要時間約 22 分前提CH 20(defineTool)難易度再現可能

本章のゴール

CH 20 ではモデルに自分のツールを呼び出させました。本章ではさらに一歩踏み込みます:ツール実行の前後で自分のロジックを差し込む——誰が何を呼んだかを記録し、呼ぶべきでないツールを遮断し、通すか拒否するかを決定する。

これが「フックプラグイン」です。dsh の権限システム、サンドボックス、監査機能の基礎であり、「すべてはプラグイン」を最も典型的に体現したものです。

ツール呼び出しは一直線ではない

CH 11 で一つの概念に触れました:モデルがツールを呼ぶと言っても直接実行されるのではなく、拡張可能なパイプラインを通ります。公式はこれを「守られたパイプライン」としました——各段階はプラグインがインターセプト・拡張できます。

ツール呼び出しは次の段階を経ます:

text
モデルがツールを呼ぶ

pre-execute   → ポリシーゲート:許可 / 拒否 / 確認(権限・サンドボックス・インターセプトはすべてこの層)

guard         → 単調ガード:一度拒否したら後続リスナーで覆せない(最終防衛線)

execute       → ツールを実際に実行

post-execute  → 結果変換:戻り値の書き換え、内容の追加

result        → 読み取り専用の観測:結果をちらっと見る、変更は不可

結果がモデルへ戻る

「フック」はある段階に取り付けられるプラグインです:ctx.on('tools/xxx', ...) で対応イベントを購読し、イベント内でやりたいことを実行します。

各拡張ポイントが何ができるか、公式ドキュメントに明確な表があります:

拡張ポイント役割典型的な用途
tools/pre-executeツール実行前の意思決定層許可 / 拒否 / 確認、権限ゲート
ctx.tools.guard()単調な最終拒否後続リスナーで覆せないハードリミット
tools/executeディスパッチ周期全体を包むタイムアウト・リトライ・指標収集の追加
tools/post-execute結果を明示的に変換表示内容の置換、モデル可視コンテキストの追加
tools/result不変結果を読み取り専用で観測監査ログ・統計、変更不可

pre-execute はウォーターフォール型イベントです:リスナーは next()(許可)または { kind: 'deny', reason: '...' }(拒否)を返せます。

ハンズオン:「監査 + インターセプト」フックプラグインを書く

記録と、指定ツールの拒否を両方行うフックプラグインを書きます。拒否リストは設定可能にします——「プラグインは設定可能」という機能も同時に練習します。

ステップ 1:ディレクトリ作成、依存パッケージインストール

プラグインを配置したいワークスペースで:

powershell
New-Item -ItemType Directory -Path "hook-demo\src" -Force
cd "E:\software-workspace\DeepSeek harness demo\hook-demo"   # 自分のディレクトリに置き換え
npm init -y
npm install @deepseek-ai/schemastery

schemastery は設定スキーマを定義するライブラリです(プラグインを設定可能にするには、これで設定の形とデフォルト値を宣言します)。package.json"type": "module" を追加するのを忘れずに。

ステップ 2:フックプラグインを書く

hook-demo\src\audit.js を作成:

js
import Schema from '@deepseek-ai/schemastery'

export const name = 'audit-hook'

export const Config = Schema.object({
  denyTools: Schema.array(Schema.string()).default([]),
})

export function apply(ctx, config) {
  ctx.on('tools/pre-execute', (exec, next) => {
    console.log(`[audit] ツールが呼び出されます: ${exec.name}`)
    if (config.denyTools.includes(exec.name)) {
      return { kind: 'deny', reason: `ポリシー:本セッションでは ${exec.name} の呼び出しを禁止します` }
    }
    return next()
  })
}

一行ずつ分解:

  • Config:プラグインの設定可能項目を宣言。denyTools は文字列配列、デフォルトは空配列。apply(ctx, config)config はユーザー設定とデフォルトをマージした結果です。
  • ctx.on('tools/pre-execute', ...):ツール実行前イベントを購読。ツールが呼ばれるたびに必ずここを経由します。
  • console.log:監査——このツールが呼び出されようとしていることを記録します。
  • config.denyTools.includes(exec.name):このツールが拒否リストにある場合、{ kind: 'deny', reason } を返して遮断します。
  • return next():それ以外なら許可して、パイプラインを続行させます。

ステップ 3:cordis.yml で設定を渡す

hook-demo\cordis.yml を作成(パスは自分のものに置き換え、スペースは %20 に変換):

yaml
- insert:
    - id: audit
      name: 'file:///E:/your-workspace/hook-demo/src/audit.js'
      config:
        denyTools: ['pwsh']

ここで configpwsh(PowerShell)を拒否リストに追加しています。注目:プラグインコードは一文字も変えていないのに、振る舞いが変わりました——これがまさに設定の意味であり、公式の設計原則である「ハードコードされた調整可能パラメータは持たない」の体現です:cordis.yml で変更できる値はコードに書き込むべきではありません。

ステップ 4:実行して効果を確認

headless で一度実行し、モデルに pwsh を呼ばせます:

powershell
cd 自分のワークスペースディレクトリ
dsh --profile headless --patch "./hook-demo/cordis.yml" "pwsh で Get-ChildItem を実行して現在のディレクトリを一覧表示してください"

実際にターミナルに出力された内容:

二行の黄色い [audit] ログが、我々のフックが記録している様子です:モデルはまず skill を呼び、次に pwsh を呼びました——ツール呼び出しはすべて我々が仕掛けたフックを通過しています。pwsh は拒否リストにあるので deny され、モデルは拒否を認識して「本セッションでは pwsh の呼び出しが禁止されている」と自発的に報告し、代替案まで提示しました。

一つのプラグインで監査(見える)とインターセプト(制御できる)を両立。これがフックの力です。

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

このプラグインは dsh に書かせる方が早いです。Web UI の入力欄に直接送ります:

text
現在のワークスペースでフックプラグインを一つ書いてください:ツールが呼び出される前にログを一行出力し、設定で指定したツールを拒否できるようにします。公式仕様に従って実装し、headless を一度走らせて記録とインターセプトの両方が動作することを検証し、最後に結果を教えてください。

dsh は自分で公式ドキュメントを読み、プラグインを書き、記録とインターセプトの両方が機能することを検証します。あなたがやるのは確認だけです。

これが実際にこのプロンプトを走らせた結果です:まず実装要点を自力で整理しました——プラグインの名前付きエクスポートは name / inject / apply のみ、inject: ['tools'] でツールレジストリを確実に用意する、ctx.on('tools/pre-execute', ...) でフックを仕掛ける(公式の permission-gate 例と一致)、拒否は { kind: 'deny', reason } を返す、許可は await next() で通す——「拒否リストは設定経由、コード変更不要」まで気が回っています。

この軌跡図はその作業の全工程です:write でプラグインファイル作成、pwsh で自身の検証スクリプト実行(8 項目すべてパス)、todo_write でタスクリスト更新……一つひとつがツール呼び出しです。

よくある落とし穴

問題何が起きているか対処方法
return next() を忘れたウォーターフォールイベントで next が呼ばれず、パイプラインが停滞pre-execute / post-execute は必ず next() または判断を返す
deny 後もモデルがリトライし続けるモデルはこのツールが永続的に利用不可と分かっていないreason を明確に書くと、モデルがそれを見て別案に切り替える
設定が効かないcordis.yml の config の書き方が誤りフィールド名と schema の型が一致しているか確認
Cannot find package '@deepseek-ai/schemastery' が出るプラグインディレクトリで依存をインストールしていないnpm install @deepseek-ai/schemastery を実行

この章で学んだこと

  • [ ] ツール呼び出しパイプラインの主な段階(pre-execute / execute / post-execute / result)を説明できる
  • [ ] ctx.on('tools/pre-execute', ...) を使ってフックプラグインを書ける
  • [ ] { kind: 'deny', reason } を返してツールを遮断し、next() で許可できる
  • [ ] Config + Schemastery を使ってプラグインを設定可能にできる(拒否リスト)
  • [ ] 「ハードコードされた調整可能パラメータを持たない」という設計原則を説明できる

Open Source · MIT · Community Driven