llm_worker/tool.rs
1//! ツール定義
2//!
3//! LLMから呼び出し可能なツールを定義するためのトレイト。
4//! 通常は`#[tool]`マクロを使用して自動実装します。
5
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use serde_json::Value;
10use thiserror::Error;
11
12/// ツール実行時のエラー
13#[derive(Debug, Error)]
14pub enum ToolError {
15 /// 引数が不正
16 #[error("Invalid argument: {0}")]
17 InvalidArgument(String),
18 /// 実行に失敗
19 #[error("Execution failed: {0}")]
20 ExecutionFailed(String),
21 /// 内部エラー
22 #[error("Internal error: {0}")]
23 Internal(String),
24}
25
26// =============================================================================
27// ToolMeta - 不変のメタ情報
28// =============================================================================
29
30/// ツールのメタ情報(登録時に固定、不変)
31///
32/// `ToolDefinition` ファクトリから生成され、Worker に登録後は変更されません。
33/// LLM へのツール定義送信に使用されます。
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct ToolMeta {
36 /// ツール名(LLMが識別に使用)
37 pub name: String,
38 /// ツールの説明(LLMへのプロンプトに含まれる)
39 pub description: String,
40 /// 引数のJSON Schema
41 pub input_schema: Value,
42}
43
44impl ToolMeta {
45 /// 新しい ToolMeta を作成
46 pub fn new(name: impl Into<String>) -> Self {
47 Self {
48 name: name.into(),
49 description: String::new(),
50 input_schema: Value::Object(Default::default()),
51 }
52 }
53
54 /// 説明を設定
55 pub fn description(mut self, desc: impl Into<String>) -> Self {
56 self.description = desc.into();
57 self
58 }
59
60 /// 引数スキーマを設定
61 pub fn input_schema(mut self, schema: Value) -> Self {
62 self.input_schema = schema;
63 self
64 }
65}
66
67// =============================================================================
68// ToolDefinition - ファクトリ型
69// =============================================================================
70
71/// ツール定義ファクトリ
72///
73/// 呼び出すと `(ToolMeta, Arc<dyn Tool>)` を返します。
74/// Worker への登録時に一度だけ呼び出され、メタ情報とインスタンスが
75/// セッションスコープでキャッシュされます。
76///
77/// # Examples
78///
79/// ```ignore
80/// let def: ToolDefinition = Arc::new(|| {
81/// (
82/// ToolMeta::new("my_tool")
83/// .description("My tool description")
84/// .input_schema(json!({"type": "object"})),
85/// Arc::new(MyToolImpl { state: 0 }) as Arc<dyn Tool>,
86/// )
87/// });
88/// worker.register_tool(def)?;
89/// ```
90pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Sync>;
91
92// =============================================================================
93// Tool trait
94// =============================================================================
95
96/// LLMから呼び出し可能なツールを定義するトレイト
97///
98/// ツールはLLMが外部リソースにアクセスしたり、
99/// 計算を実行したりするために使用します。
100/// セッション中の状態を保持できます。
101///
102/// # 実装方法
103///
104/// 通常は`#[tool_registry]`マクロを使用して自動実装します:
105///
106/// ```ignore
107/// #[tool_registry]
108/// impl MyApp {
109/// #[tool]
110/// async fn search(&self, query: String) -> String {
111/// format!("Results for: {}", query)
112/// }
113/// }
114///
115/// // 登録
116/// worker.register_tool(app.search_definition())?;
117/// ```
118///
119/// # 手動実装
120///
121/// ```ignore
122/// use llm_worker::tool::{Tool, ToolError, ToolMeta, ToolDefinition};
123/// use std::sync::Arc;
124///
125/// struct MyTool { counter: std::sync::atomic::AtomicUsize }
126///
127/// #[async_trait::async_trait]
128/// impl Tool for MyTool {
129/// async fn execute(&self, input: &str) -> Result<String, ToolError> {
130/// self.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
131/// Ok("result".to_string())
132/// }
133/// }
134///
135/// let def: ToolDefinition = Arc::new(|| {
136/// (
137/// ToolMeta::new("my_tool")
138/// .description("My custom tool")
139/// .input_schema(serde_json::json!({"type": "object"})),
140/// Arc::new(MyTool { counter: Default::default() }) as Arc<dyn Tool>,
141/// )
142/// });
143/// ```
144#[async_trait]
145pub trait Tool: Send + Sync {
146 /// ツールを実行する
147 ///
148 /// # Arguments
149 /// * `input_json` - LLMが生成したJSON形式の引数
150 ///
151 /// # Returns
152 /// 実行結果の文字列。この内容がLLMに返されます。
153 async fn execute(&self, input_json: &str) -> Result<String, ToolError>;
154}