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