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}