5.5 KiB
5.5 KiB
Tool 設計
概要
llm-workerのツールシステムは、LLMが外部リソースにアクセスしたり計算を実行するための仕組みを提供する。
メタ情報の不変性とセッションスコープの状態管理を両立させる設計となっている。
主要な型
type ToolDefinition
Fn() -> (ToolMeta, Arc<dyn Tool>)
worker.register_tool() で呼び出し
▼
- struct ToolMeta (name, desc, schema)
不変・登録時固定
- trait Tool (executer)
登録時生成・セッション中再利用
ToolMeta
ツールのメタ情報を保持する不変構造体。登録時に固定され、Worker内で変更されない。
pub struct ToolMeta {
pub name: String,
pub description: String,
pub input_schema: Value,
}
目的:
- LLM へのツール定義として送信
- Hook からの参照(読み取り専用)
- 登録後の不変性を保証
Tool trait
ツールの実行ロジックのみを定義するトレイト。
#[async_trait]
pub trait Tool: Send + Sync {
async fn execute(&self, input_json: &str) -> Result<String, ToolError>;
}
設計方針:
- メタ情報(name, description, schema)は含まない
- 状態を持つことが可能(セッション中のカウンターなど)
Send + Syncで並列実行に対応
インスタンスのライフサイクル:
register_tool()呼び出し時にファクトリが実行され、インスタンスが生成される- LLM がツールを呼び出すと、既存インスタンスの
execute()が実行される - 同じセッション中は同一インスタンスが再利用される
※ 「最初に呼ばれたとき」の遅延初期化ではなく、登録時の即時初期化である。
ToolDefinition
メタ情報とツールインスタンスを生成するファクトリ。
pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Sync>;
なぜファクトリか:
- Worker への登録時に一度だけ呼び出される
- メタ情報とインスタンスを同時に生成し、整合性を保証
- クロージャでコンテキスト(
self.clone())をキャプチャ可能
Worker でのツール管理
// Worker 内部
tools: HashMap<String, (ToolMeta, Arc<dyn Tool>)>
// 登録 API
pub fn register_tool(&mut self, factory: ToolDefinition) -> Result<(), ToolRegistryError>
登録時の処理:
- ファクトリを呼び出し
(meta, instance)を取得 - 同名ツールが既に登録されていればエラー
- HashMap に
(meta, instance)を保存
マクロによる自動生成
#[tool_registry] マクロは {method}_definition() メソッドを生成する。
#[tool_registry]
impl MyApp {
/// 検索を実行する
#[tool]
async fn search(&self, query: String) -> String {
// 実装
}
}
// 生成されるコード:
impl MyApp {
pub fn search_definition(&self) -> ToolDefinition {
let ctx = self.clone();
Arc::new(move || {
let meta = ToolMeta::new("search")
.description("検索を実行する")
.input_schema(/* schemars で生成 */);
let tool = Arc::new(ToolSearch { ctx: ctx.clone() });
(meta, tool)
})
}
}
Hook との連携
Hook は ToolCallContext / AfterToolCallContext
を通じてメタ情報とインスタンスにアクセスできる。
pub struct ToolCallContext {
pub call: ToolCall, // 呼び出し情報(改変可能)
pub meta: ToolMeta, // メタ情報(読み取り専用)
pub tool: Arc<dyn Tool>, // インスタンス(状態アクセス用)
}
用途:
metaで名前やスキーマを確認toolでツールの内部状態を読み取り(ダウンキャスト必要)callの引数を改変してツールに渡す
使用例
手動実装
struct Counter { count: AtomicUsize }
impl Tool for Counter {
async fn execute(&self, _: &str) -> Result<String, ToolError> {
let n = self.count.fetch_add(1, Ordering::SeqCst);
Ok(format!("count: {}", n))
}
}
let def: ToolDefinition = Arc::new(|| {
let meta = ToolMeta::new("counter")
.description("カウンターを増加")
.input_schema(json!({"type": "object"}));
(meta, Arc::new(Counter { count: AtomicUsize::new(0) }))
});
worker.register_tool(def)?;
マクロ使用(推奨)
#[tool_registry]
impl App {
#[tool]
async fn greet(&self, name: String) -> String {
format!("Hello, {}!", name)
}
}
let app = App;
worker.register_tool(app.greet_definition())?;
設計上の決定
| 問題 | 決定 | 理由 |
|---|---|---|
| メタ情報の変更可能性 | ToolMeta を分離・不変化 | 登録後の整合性を保証 |
| 状態管理 | 登録時にインスタンス生成 | セッション中の状態保持、同一インスタンス再利用 |
| Factory vs Instance | Factory + 登録時即時呼び出し | コンテキストキャプチャと登録時検証 |
| Hook からのアクセス | Context に meta と tool を含む | 柔軟な介入を可能に |