Compare commits

..

2 Commits

29 changed files with 811 additions and 830 deletions

View File

@ -1,7 +1,6 @@
# llm-worker-rs Development Instructions # llm-worker-rs 開発instruction
## Package Management Rules ## パッケージ管理ルール
- When adding or updating crate dependencies, always use the `cargo` command. Do - クレートに依存関係を追加・更新する際は必ず
not manually edit `Cargo.toml` directly; always manage dependencies via `cargo`コマンドを使い、`Cargo.toml`を直接手で書き換えず、必ずコマンド経由で管理すること。
commands.

2
Cargo.lock generated
View File

@ -708,7 +708,7 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]] [[package]]
name = "llm-worker" name = "llm-worker"
version = "0.2.1" version = "0.2.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap", "clap",

View File

@ -31,48 +31,41 @@ graph TD
Workerは以下のループターンを実行します。 Workerは以下のループターンを実行します。
1. **Start Turn**: `Worker::run(messages)` 呼び出し 1. **Start Turn**: `Worker::run(messages)` 呼び出し
2. **Hook: OnMessageSend**: 2. **Hook: OnMessageSend**:
- ユーザーメッセージの改変、バリデーション、キャンセルが可能。 * ユーザーメッセージの改変、バリデーション、キャンセルが可能。
- コンテキストへのシステムプロンプト注入などもここで行う想定。 * コンテキストへのシステムプロンプト注入などもここで行う想定。
3. **Request & Stream**: 3. **Request & Stream**:
- LLMへリクエスト送信。イベントストリーム開始。 * LLMへリクエスト送信。イベントストリーム開始。
- `Timeline`によるイベント処理。 * `Timeline`によるイベント処理。
4. **Tool Handling (Parallel)**: 4. **Tool Handling (Parallel)**:
- レスポンス内に含まれる全てのTool Callを収集。 * レスポンス内に含まれる全てのTool Callを収集。
- 各Toolに対して **Hook: BeforeToolCall** を実行(実行可否、引数改変)。 * 各Toolに対して **Hook: BeforeToolCall** を実行(実行可否、引数改変)。
- 許可されたToolを**並列実行 (`join_all`)**。 * 許可されたToolを**並列実行 (`join_all`)**。
- 各Tool実行後に **Hook: AfterToolCall** を実行(結果の確認、加工)。 * 各Tool実行後に **Hook: AfterToolCall** を実行(結果の確認、加工)。
5. **Next Request Decision**: 5. **Next Request Decision**:
- Tool実行結果がある場合 -> 結果をMessageとしてContextに追加し、**Step * Tool実行結果がある場合 -> 結果をMessageとしてContextに追加し、**Step 3へ戻る** (自動ループ)。
3へ戻る** (自動ループ)。 * Tool実行がない場合 -> Step 6へ。
- Tool実行がない場合 -> Step 6へ。 6. **Hook: OnTurnEnd**:
6. **Hook: OnTurnEnd**: * 最終的な応答に対するチェックLint/Fmt
- 最終的な応答に対するチェックLint/Fmt * エラーがある場合、エラーメッセージをContextに追加して **Step 3へ戻る** ことで自己修正を促せる。
- エラーがある場合、エラーメッセージをContextに追加して **Step 3へ戻る** * 問題なければターン終了。
ことで自己修正を促せる。
- 問題なければターン終了。
## Tool 設計 ## Tool 設計
### アーキテクチャ概要 ### アーキテクチャ概要
Rustの静的型付けシステムとLLMの動的なツール呼び出し文字列による指定を、**Trait Rustの静的型付けシステムとLLMの動的なツール呼び出し文字列による指定を、**Trait Object** と **動的ディスパッチ** を用いて接続します。
Object** と **動的ディスパッチ** を用いて接続します。
1. **共通インターフェース (`Tool` Trait)**: 1. **共通インターフェース (`Tool` Trait)**: 全てのツールが実装すべき共通の振る舞い(メタデータ取得と実行)を定義します。
全てのツールが実装すべき共通の振る舞い(メタデータ取得と実行)を定義します。 2. **ラッパー生成 (`#[tool]` Macro)**: ユーザー定義のメソッドをラップし、`Tool` Traitを実装した構造体を自動生成します。
2. **ラッパー生成 (`#[tool]` Macro)**: ユーザー定義のメソッドをラップし、`Tool` 3. **レジストリ (`HashMap`)**: Workerは動的ディスパッチ用に `HashMap<String, Box<dyn Tool>>` でツールを管理します。
Traitを実装した構造体を自動生成します。
3. **レジストリ (`HashMap`)**: Workerは動的ディスパッチ用に
`HashMap<String, Box<dyn Tool>>` でツールを管理します。
この仕組みにより、「名前からツールを探し、JSON引数を型変換して関数を実行する」フローを安全に実現します。 この仕組みにより、「名前からツールを探し、JSON引数を型変換して関数を実行する」フローを安全に実現します。
### 1. Tool Trait 定義 ### 1. Tool Trait 定義
ツールが最低限持つべきインターフェースです。`Send + Sync` ツールが最低限持つべきインターフェースです。`Send + Sync` を必須とし、マルチスレッド(並列実行)に対応します。
を必須とし、マルチスレッド(並列実行)に対応します。
```rust ```rust
#[async_trait] #[async_trait]
@ -120,8 +113,7 @@ impl MyApp {
**マクロ展開後のイメージ (擬似コード):** **マクロ展開後のイメージ (擬似コード):**
マクロは、元のメソッドに対応する**ラッパー構造体**を生成します。このラッパーが マクロは、元のメソッドに対応する**ラッパー構造体**を生成します。このラッパーが `Tool` Trait を実装します。
`Tool` Trait を実装します。
```rust ```rust
// 1. 引数をデシリアライズ用の中間構造体に変換 // 1. 引数をデシリアライズ用の中間構造体に変換
@ -163,18 +155,15 @@ impl Tool for GetUserTool {
### 3. Workerによる実行フロー ### 3. Workerによる実行フロー
Workerは生成されたラッパー構造体を `Box<dyn Tool>` Workerは生成されたラッパー構造体を `Box<dyn Tool>` として保持し、以下のフローで実行します。
として保持し、以下のフローで実行します。
1. **登録**: 1. **登録**: アプリケーション開始時、コンテキスト(`MyApp`)から各ツールのラッパー(`GetUserTool`)を生成し、WorkerのMapに登録。
アプリケーション開始時、コンテキスト(`MyApp`)から各ツールのラッパー(`GetUserTool`)を生成し、WorkerのMapに登録。 2. **解決**: LLMからのレスポンスに含まれる `ToolUse { name: "get_user", ... }` を受け取る。
2. **解決**: LLMからのレスポンスに含まれる `ToolUse { name: "get_user", ... }` 3. **検索**: `name` をキーに Map から `Box<dyn Tool>` を取得。
を受け取る。 4. **実行**:
3. **検索**: `name` をキーに Map から `Box<dyn Tool>` を取得。 * `tool.execute(json)` を呼び出す。
4. **実行**: * 内部で `serde_json` による型変換とメソッド実行が行われる。
- `tool.execute(json)` を呼び出す。 * 結果が返る。
- 内部で `serde_json` による型変換とメソッド実行が行われる。
- 結果が返る。
これにより、型安全性を保ちつつ、動的なツール実行が可能になります。 これにより、型安全性を保ちつつ、動的なツール実行が可能になります。
@ -182,9 +171,8 @@ Workerは生成されたラッパー構造体を `Box<dyn Tool>`
### コンセプト ### コンセプト
- **制御の介入**: * **制御の介入**: ターンの進行、メッセージの内容、ツールの実行に対して介入します。
ターンの進行、メッセージの内容、ツールの実行に対して介入します。 * **Contextへのアクセス**: メッセージ履歴Contextを読み書きできます。
- **Contextへのアクセス**: メッセージ履歴Contextを読み書きできます。
### Hook Trait ### Hook Trait
@ -231,8 +219,7 @@ pub enum OnTurnEndResult {
### Tool Call Context ### Tool Call Context
`before_tool_call` / `after_tool_call` `before_tool_call` / `after_tool_call` は、ツール実行の文脈を含む入力を受け取る。
は、ツール実行の文脈を含む入力を受け取る。
```rust ```rust
pub struct ToolCallContext { pub struct ToolCallContext {
@ -250,20 +237,17 @@ pub struct ToolResultContext {
## 実装方針 ## 実装方針
1. **Worker Struct**: 1. **Worker Struct**:
- `Timeline`を所有。 * `Timeline`を所有。
- `Handler`として「ToolCallCollector」をTimelineに登録。 * `Handler`として「ToolCallCollector」をTimelineに登録。
- `stream`終了後に収集したToolCallを処理するロジックを持つ。 * `stream`終了後に収集したToolCallを処理するロジックを持つ。
- **履歴管理**: `set_history`, `with_messages`, `history_mut`
等を通じて、会話履歴の注入や編集を可能にする。
2. **Tool Executor Handler**: 2. **Tool Executor Handler**:
- Timeline上ではツール実行を行わず、あくまで「ToolCallブロックの収集」に徹するToolの実行は非同期かつ並列で、ストリーム終了後あるいはブロック確定後に行うため * Timeline上ではツール実行を行わず、あくまで「ToolCallブロックの収集」に徹するToolの実行は非同期かつ並列で、ストリーム終了後あるいはブロック確定後に行うため
- ただし、リアルタイム性を重視する場合ストリーミング中にToolを実行開始等は将来的な拡張とするが、現状は「結果が揃うのを待って」という要件に従い、収集フェーズと実行フェーズを分ける。 * ただし、リアルタイム性を重視する場合ストリーミング中にToolを実行開始等は将来的な拡張とするが、現状は「結果が揃うのを待って」という要件に従い、収集フェーズと実行フェーズを分ける。
3. **worker-macros**: 3. **worker-macros**:
- `syn`, `quote` を用いて、関数定義から `Tool` トレイト実装と `InputSchema` * `syn`, `quote` を用いて、関数定義から `Tool` トレイト実装と `InputSchema` (schemars利用) を生成。
(schemars利用) を生成。
## Worker Event API 設計 ## Worker Event API 設計
@ -272,7 +256,6 @@ pub struct ToolResultContext {
Workerは内部でイベントを処理し結果を返しますが、UIへのストリーミング表示やリアルタイムフィードバックには、イベントを外部に公開する仕組みが必要です。 Workerは内部でイベントを処理し結果を返しますが、UIへのストリーミング表示やリアルタイムフィードバックには、イベントを外部に公開する仕組みが必要です。
**要件**: **要件**:
1. テキストデルタをリアルタイムでUIに表示 1. テキストデルタをリアルタイムでUIに表示
2. ツール呼び出しの進行状況を表示 2. ツール呼び出しの進行状況を表示
3. ブロック完了時に累積結果を受け取る 3. ブロック完了時に累積結果を受け取る
@ -281,10 +264,10 @@ Workerは内部でイベントを処理し結果を返しますが、UIへのス
Worker APIは **Timeline層のHandler機構の薄いラッパー** として設計します。 Worker APIは **Timeline層のHandler機構の薄いラッパー** として設計します。
| 層 | 目的 | 提供するもの | | 層 | 目的 | 提供するもの |
| ------------------------ | ------------------ | ---------------------------------- | |---|------|-------------|
| **Handler (Timeline層)** | 内部実装、役割分離 | スコープ管理 + Deltaイベント | | **Handler (Timeline層)** | 内部実装、役割分離 | スコープ管理 + Deltaイベント |
| **Worker Event API** | ユーザー向け利便性 | Handler露出 + Completeイベント追加 | | **Worker Event API** | ユーザー向け利便性 | Handler露出 + Completeイベント追加 |
Handlerのスコープ管理パターンStart→Delta→Endをそのまま活かしつつ、累積済みのCompleteイベントも追加提供します。 Handlerのスコープ管理パターンStart→Delta→Endをそのまま活かしつつ、累積済みのCompleteイベントも追加提供します。
@ -465,8 +448,7 @@ impl<C: LlmClient> Worker<C> {
### 設計上のポイント ### 設計上のポイント
1. **Handlerの再利用**: 既存のHandler traitをそのまま活用 1. **Handlerの再利用**: 既存のHandler traitをそのまま活用
2. **スコープ管理の維持**: 2. **スコープ管理の維持**: ブロックイベントはStart→Delta→Endのライフサイクルを保持
ブロックイベントはStart→Delta→Endのライフサイクルを保持
3. **選択的購読**: on_*で必要なイベントだけ、またはSubscriberで一括 3. **選択的購読**: on_*で必要なイベントだけ、またはSubscriberで一括
4. **累積イベントの追加**: Worker層でComplete系イベントを追加提供 4. **累積イベントの追加**: Worker層でComplete系イベントを追加提供
5. **後方互換性**: 従来の`run()`も引き続き使用可能 5. **後方互換性**: 従来の`run()`も引き続き使用可能

View File

@ -1,7 +1,7 @@
//! llm-worker-macros - Procedural macros for Tool generation //! llm-worker-macros - Tool生成用手続きマクロ
//! //!
//! Provides `#[tool_registry]` and `#[tool]` macros to //! `#[tool_registry]` と `#[tool]` マクロを提供し、
//! automatically generate `Tool` trait implementations from user-defined methods. //! ユーザー定義のメソッドから `Tool` トレイト実装を自動生成する。
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -9,22 +9,22 @@ use syn::{
Attribute, FnArg, ImplItem, ItemImpl, Lit, Meta, Pat, ReturnType, Type, parse_macro_input, Attribute, FnArg, ImplItem, ItemImpl, Lit, Meta, Pat, ReturnType, Type, parse_macro_input,
}; };
/// Macro applied to an `impl` block that generates tools from methods marked with `#[tool]`. /// `impl` ブロックに付与し、内部の `#[tool]` 属性がついたメソッドからツールを生成するマクロ。
/// ///
/// # Example /// # Example
/// ```ignore /// ```ignore
/// #[tool_registry] /// #[tool_registry]
/// impl MyApp { /// impl MyApp {
/// /// Get user information /// /// ユーザー情報を取得する
/// /// Retrieves a user from the database by their ID. /// /// 指定されたIDのユーザーをDBから検索します。
/// #[tool] /// #[tool]
/// async fn get_user(&self, user_id: String) -> Result<User, Error> { ... } /// async fn get_user(&self, user_id: String) -> Result<User, Error> { ... }
/// } /// }
/// ``` /// ```
/// ///
/// This generates: /// これにより以下が生成されます:
/// - `GetUserArgs` struct (for arguments) /// - `GetUserArgs` 構造体(引数用)
/// - `Tool_get_user` struct (Tool wrapper) /// - `Tool_get_user` 構造体Toolラッパー
/// - `impl Tool for Tool_get_user` /// - `impl Tool for Tool_get_user`
/// - `impl MyApp { fn get_user_tool(&self) -> Tool_get_user }` /// - `impl MyApp { fn get_user_tool(&self) -> Tool_get_user }`
#[proc_macro_attribute] #[proc_macro_attribute]
@ -36,14 +36,14 @@ pub fn tool_registry(_attr: TokenStream, item: TokenStream) -> TokenStream {
for item in &mut impl_block.items { for item in &mut impl_block.items {
if let ImplItem::Fn(method) = item { if let ImplItem::Fn(method) = item {
// Look for #[tool] attribute // #[tool] 属性を探す
let mut is_tool = false; let mut is_tool = false;
// Iterate through attributes to check for tool and remove it // 属性を走査してtoolがあるか確認し、削除する
method.attrs.retain(|attr| { method.attrs.retain(|attr| {
if attr.path().is_ident("tool") { if attr.path().is_ident("tool") {
is_tool = true; is_tool = true;
false // Remove the attribute false // 属性を削除
} else { } else {
true true
} }
@ -65,7 +65,7 @@ pub fn tool_registry(_attr: TokenStream, item: TokenStream) -> TokenStream {
TokenStream::from(expanded) TokenStream::from(expanded)
} }
/// Extract description from doc comments /// ドキュメントコメントから説明文を抽出
fn extract_doc_comment(attrs: &[Attribute]) -> String { fn extract_doc_comment(attrs: &[Attribute]) -> String {
let mut lines = Vec::new(); let mut lines = Vec::new();
@ -75,7 +75,7 @@ fn extract_doc_comment(attrs: &[Attribute]) -> String {
if let syn::Expr::Lit(expr_lit) = &meta.value { if let syn::Expr::Lit(expr_lit) = &meta.value {
if let Lit::Str(lit_str) = &expr_lit.lit { if let Lit::Str(lit_str) = &expr_lit.lit {
let line = lit_str.value(); let line = lit_str.value();
// Remove only the leading space (after ///) // 先頭の空白を1つだけ除去/// の後のスペース)
let trimmed = line.strip_prefix(' ').unwrap_or(&line); let trimmed = line.strip_prefix(' ').unwrap_or(&line);
lines.push(trimmed.to_string()); lines.push(trimmed.to_string());
} }
@ -87,7 +87,7 @@ fn extract_doc_comment(attrs: &[Attribute]) -> String {
lines.join("\n") lines.join("\n")
} }
/// Extract description from #[description = "..."] attribute /// #[description = "..."] 属性から説明を抽出
fn extract_description_attr(attrs: &[syn::Attribute]) -> Option<String> { fn extract_description_attr(attrs: &[syn::Attribute]) -> Option<String> {
for attr in attrs { for attr in attrs {
if attr.path().is_ident("description") { if attr.path().is_ident("description") {
@ -103,19 +103,19 @@ fn extract_description_attr(attrs: &[syn::Attribute]) -> Option<String> {
None None
} }
/// Generate Tool implementation from a method /// メソッドからTool実装を生成
fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::TokenStream { fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::TokenStream {
let sig = &method.sig; let sig = &method.sig;
let method_name = &sig.ident; let method_name = &sig.ident;
let tool_name = method_name.to_string(); let tool_name = method_name.to_string();
// Generate struct names (convert to PascalCase) // 構造体名を生成PascalCase変換
let pascal_name = to_pascal_case(&method_name.to_string()); let pascal_name = to_pascal_case(&method_name.to_string());
let tool_struct_name = format_ident!("Tool{}", pascal_name); let tool_struct_name = format_ident!("Tool{}", pascal_name);
let args_struct_name = format_ident!("{}Args", pascal_name); let args_struct_name = format_ident!("{}Args", pascal_name);
let definition_name = format_ident!("{}_definition", method_name); let definition_name = format_ident!("{}_definition", method_name);
// Get description from doc comments // ドキュメントコメントから説明を取得
let description = extract_doc_comment(&method.attrs); let description = extract_doc_comment(&method.attrs);
let description = if description.is_empty() { let description = if description.is_empty() {
format!("Tool: {}", tool_name) format!("Tool: {}", tool_name)
@ -123,7 +123,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
description description
}; };
// Parse arguments (excluding self) // 引数を解析selfを除く
let args: Vec<_> = sig let args: Vec<_> = sig
.inputs .inputs
.iter() .iter()
@ -131,12 +131,12 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
if let FnArg::Typed(pat_type) = arg { if let FnArg::Typed(pat_type) = arg {
Some(pat_type) Some(pat_type)
} else { } else {
None // Exclude self None // selfを除外
} }
}) })
.collect(); .collect();
// Generate argument struct fields // 引数構造体のフィールドを生成
let arg_fields: Vec<_> = args let arg_fields: Vec<_> = args
.iter() .iter()
.map(|pat_type| { .map(|pat_type| {
@ -144,14 +144,14 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
let ty = &pat_type.ty; let ty = &pat_type.ty;
let desc = extract_description_attr(&pat_type.attrs); let desc = extract_description_attr(&pat_type.attrs);
// Extract identifier from pattern // パターンから識別子を抽出
let field_name = if let Pat::Ident(pat_ident) = pat.as_ref() { let field_name = if let Pat::Ident(pat_ident) = pat.as_ref() {
&pat_ident.ident &pat_ident.ident
} else { } else {
panic!("Only simple identifiers are supported for tool arguments"); panic!("Only simple identifiers are supported for tool arguments");
}; };
// Convert #[description] to schemars doc if present // #[description] があればschemarsのdocに変換
if let Some(desc_str) = desc { if let Some(desc_str) = desc {
quote! { quote! {
#[schemars(description = #desc_str)] #[schemars(description = #desc_str)]
@ -165,7 +165,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
}) })
.collect(); .collect();
// Code to expand arguments in execute // execute内で引数を展開するコード
let arg_names: Vec<_> = args let arg_names: Vec<_> = args
.iter() .iter()
.map(|pat_type| { .map(|pat_type| {
@ -178,17 +178,17 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
}) })
.collect(); .collect();
// Check if method is async // メソッドが非同期かどうか
let is_async = sig.asyncness.is_some(); let is_async = sig.asyncness.is_some();
// Parse return type and determine if Result // 戻り値の型を解析してResult判定
let awaiter = if is_async { let awaiter = if is_async {
quote! { .await } quote! { .await }
} else { } else {
quote! {} quote! {}
}; };
// Determine if return type is Result // 戻り値がResultかどうかを判定
let result_handling = if is_result_type(&sig.output) { let result_handling = if is_result_type(&sig.output) {
quote! { quote! {
match result { match result {
@ -202,7 +202,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} }
}; };
// Create empty Args struct if no arguments // 引数がない場合は空のArgs構造体を作成
let args_struct_def = if arg_fields.is_empty() { let args_struct_def = if arg_fields.is_empty() {
quote! { quote! {
#[derive(serde::Deserialize, schemars::JsonSchema)] #[derive(serde::Deserialize, schemars::JsonSchema)]
@ -217,10 +217,10 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} }
}; };
// Execute body handling for no arguments case // 引数がない場合のexecute処理
let execute_body = if args.is_empty() { let execute_body = if args.is_empty() {
quote! { quote! {
// Allow empty JSON object even with no arguments // 引数なしでも空のJSONオブジェクトを許容
let _: #args_struct_name = serde_json::from_str(input_json) let _: #args_struct_name = serde_json::from_str(input_json)
.unwrap_or(#args_struct_name {}); .unwrap_or(#args_struct_name {});
@ -253,7 +253,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} }
impl #self_ty { impl #self_ty {
/// Get ToolDefinition (for registering with Worker) /// ToolDefinition を取得Worker への登録用)
pub fn #definition_name(&self) -> ::llm_worker::tool::ToolDefinition { pub fn #definition_name(&self) -> ::llm_worker::tool::ToolDefinition {
let ctx = self.clone(); let ctx = self.clone();
::std::sync::Arc::new(move || { ::std::sync::Arc::new(move || {
@ -270,12 +270,12 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} }
} }
/// Determine if return type is Result /// 戻り値の型がResultかどうかを判定
fn is_result_type(return_type: &ReturnType) -> bool { fn is_result_type(return_type: &ReturnType) -> bool {
match return_type { match return_type {
ReturnType::Default => false, ReturnType::Default => false,
ReturnType::Type(_, ty) => { ReturnType::Type(_, ty) => {
// For Type::Path, check if last segment is "Result" // Type::Pathの場合、最後のセグメントが"Result"かチェック
if let Type::Path(type_path) = ty.as_ref() { if let Type::Path(type_path) = ty.as_ref() {
if let Some(segment) = type_path.path.segments.last() { if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Result"; return segment.ident == "Result";
@ -286,7 +286,7 @@ fn is_result_type(return_type: &ReturnType) -> bool {
} }
} }
/// Convert snake_case to PascalCase /// snake_case を PascalCase に変換
fn to_pascal_case(s: &str) -> String { fn to_pascal_case(s: &str) -> String {
s.split('_') s.split('_')
.map(|part| { .map(|part| {
@ -299,20 +299,20 @@ fn to_pascal_case(s: &str) -> String {
.collect() .collect()
} }
/// Marker attribute. Does nothing here as it's processed by `tool_registry`. /// マーカー属性。`tool_registry` によって処理されるため、ここでは何もしない。
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn tool(_attr: TokenStream, item: TokenStream) -> TokenStream { pub fn tool(_attr: TokenStream, item: TokenStream) -> TokenStream {
item item
} }
/// Marker for argument attributes. Interpreted by `tool_registry` during parsing. /// 引数属性用のマーカー。パース時に`tool_registry`で解釈される。
/// ///
/// # Example /// # Example
/// ```ignore /// ```ignore
/// #[tool] /// #[tool]
/// async fn get_user( /// async fn get_user(
/// &self, /// &self,
/// #[description = "The ID of the user to retrieve"] user_id: String /// #[description = "取得したいユーザーのID"] user_id: String
/// ) -> Result<User, Error> { ... } /// ) -> Result<User, Error> { ... }
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]

View File

@ -1,7 +1,7 @@
[package] [package]
name = "llm-worker" name = "llm-worker"
description = "A library for building autonomous LLM-powered systems" description = "A library for building autonomous LLM-powered systems"
version = "0.2.1" version = "0.2.0"
publish.workspace = true publish.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true

View File

@ -1,18 +1,18 @@
//! Test fixture recording tool //! テストフィクスチャ記録ツール
//! //!
//! Records API responses for defined scenarios. //! 定義されたシナリオのAPIレスポンスを記録する。
//! //!
//! ## Usage //! ## 使用方法
//! //!
//! ```bash //! ```bash
//! # Show available scenarios //! # 利用可能なシナリオを表示
//! cargo run --example record_test_fixtures //! cargo run --example record_test_fixtures
//! //!
//! # Record specific scenario //! # 特定のシナリオを記録
//! ANTHROPIC_API_KEY=your-key cargo run --example record_test_fixtures -- simple_text //! ANTHROPIC_API_KEY=your-key cargo run --example record_test_fixtures -- simple_text
//! ANTHROPIC_API_KEY=your-key cargo run --example record_test_fixtures -- tool_call //! ANTHROPIC_API_KEY=your-key cargo run --example record_test_fixtures -- tool_call
//! //!
//! # Record all scenarios //! # 全シナリオを記録
//! ANTHROPIC_API_KEY=your-key cargo run --example record_test_fixtures -- --all //! ANTHROPIC_API_KEY=your-key cargo run --example record_test_fixtures -- --all
//! ``` //! ```
@ -193,8 +193,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
ClientType::Ollama => "ollama", ClientType::Ollama => "ollama",
}; };
// Scenario filtering is already done in main.rs logic // シナリオのフィルタリングは main.rs のロジックで実行済み
// Here we just execute in a simple loop // ここでは単純なループで実行
for scenario in scenarios_to_run { for scenario in scenarios_to_run {
match args.client { match args.client {
ClientType::Anthropic => { ClientType::Anthropic => {

View File

@ -1,6 +1,6 @@
//! Test fixture recording mechanism //! テストフィクスチャ記録機構
//! //!
//! Saves events to files in JSONL format //! イベントをJSONLフォーマットでファイルに保存する
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
@ -10,7 +10,7 @@ use std::time::{Instant, SystemTime, UNIX_EPOCH};
use futures::StreamExt; use futures::StreamExt;
use llm_worker::llm_client::{LlmClient, Request}; use llm_worker::llm_client::{LlmClient, Request};
/// Recorded event /// 記録されたイベント
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RecordedEvent { pub struct RecordedEvent {
pub elapsed_ms: u64, pub elapsed_ms: u64,
@ -18,7 +18,7 @@ pub struct RecordedEvent {
pub data: String, pub data: String,
} }
/// Session metadata /// セッションメタデータ
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SessionMetadata { pub struct SessionMetadata {
pub timestamp: u64, pub timestamp: u64,
@ -26,7 +26,7 @@ pub struct SessionMetadata {
pub description: String, pub description: String,
} }
/// Save event sequence to file /// イベントシーケンスをファイルに保存
pub fn save_fixture( pub fn save_fixture(
path: impl AsRef<Path>, path: impl AsRef<Path>,
metadata: &SessionMetadata, metadata: &SessionMetadata,
@ -43,7 +43,7 @@ pub fn save_fixture(
Ok(()) Ok(())
} }
/// Send request and record events /// リクエストを送信してイベントを記録
pub async fn record_request<C: LlmClient>( pub async fn record_request<C: LlmClient>(
client: &C, client: &C,
request: Request, request: Request,
@ -78,7 +78,7 @@ pub async fn record_request<C: LlmClient>(
} }
} }
// Save // 保存
let fixtures_dir = Path::new("worker/tests/fixtures").join(subdir); let fixtures_dir = Path::new("worker/tests/fixtures").join(subdir);
fs::create_dir_all(&fixtures_dir)?; fs::create_dir_all(&fixtures_dir)?;

View File

@ -1,20 +1,20 @@
//! Test fixture request definitions //! テストフィクスチャ用リクエスト定義
//! //!
//! Defines requests and output file names for each scenario //! 各シナリオのリクエストと出力ファイル名を定義
use llm_worker::llm_client::{Request, ToolDefinition}; use llm_worker::llm_client::{Request, ToolDefinition};
/// Test scenario /// テストシナリオ
pub struct TestScenario { pub struct TestScenario {
/// Scenario name (description) /// シナリオ名(説明)
pub name: &'static str, pub name: &'static str,
/// Output file name (without extension) /// 出力ファイル名(拡張子なし)
pub output_name: &'static str, pub output_name: &'static str,
/// Request /// リクエスト
pub request: Request, pub request: Request,
} }
/// Get all test scenarios /// 全てのテストシナリオを取得
pub fn scenarios() -> Vec<TestScenario> { pub fn scenarios() -> Vec<TestScenario> {
vec![ vec![
simple_text_scenario(), simple_text_scenario(),
@ -23,7 +23,7 @@ pub fn scenarios() -> Vec<TestScenario> {
] ]
} }
/// Simple text response /// シンプルなテキストレスポンス
fn simple_text_scenario() -> TestScenario { fn simple_text_scenario() -> TestScenario {
TestScenario { TestScenario {
name: "Simple text response", name: "Simple text response",
@ -35,7 +35,7 @@ fn simple_text_scenario() -> TestScenario {
} }
} }
/// Response with tool call /// ツール呼び出しを含むレスポンス
fn tool_call_scenario() -> TestScenario { fn tool_call_scenario() -> TestScenario {
let get_weather_tool = ToolDefinition::new("get_weather") let get_weather_tool = ToolDefinition::new("get_weather")
.description("Get the current weather for a city") .description("Get the current weather for a city")
@ -61,7 +61,7 @@ fn tool_call_scenario() -> TestScenario {
} }
} }
/// Long text generation scenario /// 長文生成シナリオ
fn long_text_scenario() -> TestScenario { fn long_text_scenario() -> TestScenario {
TestScenario { TestScenario {
name: "Long text response", name: "Long text response",

View File

@ -1,6 +1,6 @@
//! Worker cancellation demo //! Worker のキャンセル機能のデモンストレーション
//! //!
//! Example of cancelling from another thread during streaming //! ストリーミング受信中に別スレッドからキャンセルする例
use llm_worker::llm_client::providers::anthropic::AnthropicClient; use llm_worker::llm_client::providers::anthropic::AnthropicClient;
use llm_worker::{Worker, WorkerResult}; use llm_worker::{Worker, WorkerResult};
@ -10,10 +10,10 @@ use tokio::sync::Mutex;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load .env file // .envファイルを読み込む
dotenv::dotenv().ok(); dotenv::dotenv().ok();
// Initialize logging // ロギング初期化
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()
@ -30,13 +30,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 Starting Worker..."); println!("🚀 Starting Worker...");
println!("💡 Will cancel after 2 seconds\n"); println!("💡 Will cancel after 2 seconds\n");
// Get cancel sender first (without holding lock) // キャンセルSenderを先に取得ロックを保持しない
let cancel_tx = { let cancel_tx = {
let w = worker.lock().await; let w = worker.lock().await;
w.cancel_sender() w.cancel_sender()
}; };
// Task 1: Run Worker // タスク1: Workerを実行
let worker_clone = worker.clone(); let worker_clone = worker.clone();
let task = tokio::spawn(async move { let task = tokio::spawn(async move {
let mut w = worker_clone.lock().await; let mut w = worker_clone.lock().await;
@ -55,14 +55,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}); });
// Task 2: Cancel after 2 seconds // タスク2: 2秒後にキャンセル
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await; tokio::time::sleep(Duration::from_secs(2)).await;
println!("\n🛑 Cancelling worker..."); println!("\n🛑 Cancelling worker...");
let _ = cancel_tx.send(()).await; let _ = cancel_tx.send(()).await;
}); });
// Wait for task completion // タスク完了を待つ
task.await?; task.await?;
println!("\n✨ Demo complete!"); println!("\n✨ Demo complete!");

View File

@ -1,17 +1,17 @@
//! Interactive CLI client using Worker //! Worker を用いた対話型 CLI クライアント
//! //!
//! A CLI application for interacting with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama). //! 複数のLLMプロバイダAnthropic, Gemini, OpenAI, Ollamaと対話するCLIアプリケーション。
//! Demonstrates tool registration and execution, and streaming response display. //! ツールの登録と実行、ストリーミングレスポンスの表示をデモする。
//! //!
//! ## Usage //! ## 使用方法
//! //!
//! ```bash //! ```bash
//! # Set API keys in .env file //! # .envファイルにAPIキーを設定
//! echo "ANTHROPIC_API_KEY=your-api-key" > .env //! echo "ANTHROPIC_API_KEY=your-api-key" > .env
//! echo "GEMINI_API_KEY=your-api-key" >> .env //! echo "GEMINI_API_KEY=your-api-key" >> .env
//! echo "OPENAI_API_KEY=your-api-key" >> .env //! echo "OPENAI_API_KEY=your-api-key" >> .env
//! //!
//! # Anthropic (default) //! # Anthropic (デフォルト)
//! cargo run --example worker_cli //! cargo run --example worker_cli
//! //!
//! # Gemini //! # Gemini
@ -20,13 +20,13 @@
//! # OpenAI //! # OpenAI
//! cargo run --example worker_cli -- --provider openai --model gpt-4o //! cargo run --example worker_cli -- --provider openai --model gpt-4o
//! //!
//! # Ollama (local) //! # Ollama (ローカル)
//! cargo run --example worker_cli -- --provider ollama --model llama3.2 //! cargo run --example worker_cli -- --provider ollama --model llama3.2
//! //!
//! # With options //! # オプション指定
//! cargo run --example worker_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant." //! cargo run --example worker_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
//! //!
//! # Show help //! # ヘルプ表示
//! cargo run --example worker_cli -- --help //! cargo run --example worker_cli -- --help
//! ``` //! ```
@ -53,15 +53,15 @@ use llm_worker::{
}; };
use llm_worker_macros::tool_registry; use llm_worker_macros::tool_registry;
// Required imports for macro expansion // 必要なマクロ展開用インポート
use schemars; use schemars;
use serde; use serde;
// ============================================================================= // =============================================================================
// Provider Definition // プロバイダ定義
// ============================================================================= // =============================================================================
/// Available LLM providers /// 利用可能なLLMプロバイダ
#[derive(Debug, Clone, Copy, ValueEnum, Default)] #[derive(Debug, Clone, Copy, ValueEnum, Default)]
enum Provider { enum Provider {
/// Anthropic Claude /// Anthropic Claude
@ -71,12 +71,12 @@ enum Provider {
Gemini, Gemini,
/// OpenAI GPT /// OpenAI GPT
Openai, Openai,
/// Ollama (local) /// Ollama (ローカル)
Ollama, Ollama,
} }
impl Provider { impl Provider {
/// Default model for the provider /// プロバイダのデフォルトモデル
fn default_model(&self) -> &'static str { fn default_model(&self) -> &'static str {
match self { match self {
Provider::Anthropic => "claude-sonnet-4-20250514", Provider::Anthropic => "claude-sonnet-4-20250514",
@ -86,7 +86,7 @@ impl Provider {
} }
} }
/// Display name for the provider /// プロバイダの表示名
fn display_name(&self) -> &'static str { fn display_name(&self) -> &'static str {
match self { match self {
Provider::Anthropic => "Anthropic Claude", Provider::Anthropic => "Anthropic Claude",
@ -96,78 +96,78 @@ impl Provider {
} }
} }
/// Environment variable name for API key /// APIキーの環境変数名
fn env_var_name(&self) -> Option<&'static str> { fn env_var_name(&self) -> Option<&'static str> {
match self { match self {
Provider::Anthropic => Some("ANTHROPIC_API_KEY"), Provider::Anthropic => Some("ANTHROPIC_API_KEY"),
Provider::Gemini => Some("GEMINI_API_KEY"), Provider::Gemini => Some("GEMINI_API_KEY"),
Provider::Openai => Some("OPENAI_API_KEY"), Provider::Openai => Some("OPENAI_API_KEY"),
Provider::Ollama => None, // Ollama is local, no key needed Provider::Ollama => None, // Ollamaはローカルなので不要
} }
} }
} }
// ============================================================================= // =============================================================================
// CLI Argument Definition // CLI引数定義
// ============================================================================= // =============================================================================
/// Interactive CLI client supporting multiple LLM providers /// 複数のLLMプロバイダに対応した対話型CLIクライアント
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "worker-cli")] #[command(name = "worker-cli")]
#[command(about = "Interactive CLI client for multiple LLM providers using Worker")] #[command(about = "Interactive CLI client for multiple LLM providers using Worker")]
#[command(version)] #[command(version)]
struct Args { struct Args {
/// Provider to use /// 使用するプロバイダ
#[arg(long, value_enum, default_value_t = Provider::Anthropic)] #[arg(long, value_enum, default_value_t = Provider::Anthropic)]
provider: Provider, provider: Provider,
/// Model name to use (defaults to provider's default if not specified) /// 使用するモデル名(未指定時はプロバイダのデフォルト)
#[arg(short, long)] #[arg(short, long)]
model: Option<String>, model: Option<String>,
/// System prompt /// システムプロンプト
#[arg(short, long)] #[arg(short, long)]
system: Option<String>, system: Option<String>,
/// Disable tools /// ツールを無効化
#[arg(long, default_value = "false")] #[arg(long, default_value = "false")]
no_tools: bool, no_tools: bool,
/// Initial message (if specified, sends it and exits) /// 最初のメッセージ(指定するとそれを送信して終了)
#[arg(short = 'p', long)] #[arg(short = 'p', long)]
prompt: Option<String>, prompt: Option<String>,
/// API key (takes precedence over environment variable) /// APIキー(環境変数より優先)
#[arg(long)] #[arg(long)]
api_key: Option<String>, api_key: Option<String>,
} }
// ============================================================================= // =============================================================================
// Tool Definition // ツール定義
// ============================================================================= // =============================================================================
/// Application context /// アプリケーションコンテキスト
#[derive(Clone)] #[derive(Clone)]
struct AppContext; struct AppContext;
#[tool_registry] #[tool_registry]
impl AppContext { impl AppContext {
/// Get the current date and time /// 現在の日時を取得する
/// ///
/// Returns the system's current date and time. /// システムの現在の日付と時刻を返します。
#[tool] #[tool]
fn get_current_time(&self) -> String { fn get_current_time(&self) -> String {
let now = std::time::SystemTime::now() let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs(); .as_secs();
// Simple conversion from Unix timestamp // シンプルなUnixタイムスタンプからの変換
format!("Current Unix timestamp: {}", now) format!("Current Unix timestamp: {}", now)
} }
/// Perform a simple calculation /// 簡単な計算を行う
/// ///
/// Executes arithmetic operations on two numbers. /// 2つの数値の四則演算を実行します。
#[tool] #[tool]
fn calculate(&self, a: f64, b: f64, operation: String) -> Result<String, String> { fn calculate(&self, a: f64, b: f64, operation: String) -> Result<String, String> {
let result = match operation.as_str() { let result = match operation.as_str() {
@ -187,10 +187,10 @@ impl AppContext {
} }
// ============================================================================= // =============================================================================
// Streaming Display Handlers // ストリーミング表示用ハンドラー
// ============================================================================= // =============================================================================
/// Handler that outputs text in real-time /// テキストをリアルタイムで出力するハンドラー
struct StreamingPrinter { struct StreamingPrinter {
is_first_delta: Arc<Mutex<bool>>, is_first_delta: Arc<Mutex<bool>>,
} }
@ -226,7 +226,7 @@ impl Handler<TextBlockKind> for StreamingPrinter {
} }
} }
/// Handler that displays tool calls /// ツール呼び出しを表示するハンドラー
struct ToolCallPrinter { struct ToolCallPrinter {
call_names: Arc<Mutex<HashMap<String, String>>>, call_names: Arc<Mutex<HashMap<String, String>>>,
} }
@ -270,7 +270,7 @@ impl Handler<ToolUseBlockKind> for ToolCallPrinter {
} }
} }
/// Hook that displays tool execution results /// ツール実行結果を表示するHook
struct ToolResultPrinterHook { struct ToolResultPrinterHook {
call_names: Arc<Mutex<HashMap<String, String>>>, call_names: Arc<Mutex<HashMap<String, String>>>,
} }
@ -302,17 +302,17 @@ impl Hook<PostToolCall> for ToolResultPrinterHook {
} }
// ============================================================================= // =============================================================================
// Client Creation // クライアント作成
// ============================================================================= // =============================================================================
/// Get API key based on provider /// プロバイダに応じたAPIキーを取得
fn get_api_key(args: &Args) -> Result<String, String> { fn get_api_key(args: &Args) -> Result<String, String> {
// CLI argument API key takes precedence // CLI引数のAPIキーが優先
if let Some(ref key) = args.api_key { if let Some(ref key) = args.api_key {
return Ok(key.clone()); return Ok(key.clone());
} }
// Check environment variable based on provider // プロバイダに応じた環境変数を確認
if let Some(env_var) = args.provider.env_var_name() { if let Some(env_var) = args.provider.env_var_name() {
std::env::var(env_var).map_err(|_| { std::env::var(env_var).map_err(|_| {
format!( format!(
@ -321,12 +321,12 @@ fn get_api_key(args: &Args) -> Result<String, String> {
) )
}) })
} else { } else {
// Ollama etc. don't need a key // Ollamaなどはキー不要
Ok(String::new()) Ok(String::new())
} }
} }
/// Create client based on provider /// プロバイダに応じたクライアントを作成
fn create_client(args: &Args) -> Result<Box<dyn LlmClient>, String> { fn create_client(args: &Args) -> Result<Box<dyn LlmClient>, String> {
let model = args let model = args
.model .model
@ -356,17 +356,17 @@ fn create_client(args: &Args) -> Result<Box<dyn LlmClient>, String> {
} }
// ============================================================================= // =============================================================================
// Main // メイン
// ============================================================================= // =============================================================================
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load .env file // .envファイルを読み込む
dotenv::dotenv().ok(); dotenv::dotenv().ok();
// Initialize logging // ロギング初期化
// Use RUST_LOG=debug cargo run --example worker_cli ... for detailed logs // RUST_LOG=debug cargo run --example worker_cli ... で詳細ログ表示
// Default is warn level, can be overridden with RUST_LOG environment variable // デフォルトは warn レベル、RUST_LOG 環境変数で上書き可能
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn")); let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
tracing_subscriber::fmt() tracing_subscriber::fmt()
@ -374,7 +374,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with_target(true) .with_target(true)
.init(); .init();
// Parse CLI arguments // CLI引数をパース
let args = Args::parse(); let args = Args::parse();
info!( info!(
@ -383,10 +383,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"Starting worker CLI" "Starting worker CLI"
); );
// Interactive mode or one-shot mode // 対話モードかワンショットモードか
let is_interactive = args.prompt.is_none(); let is_interactive = args.prompt.is_none();
// Model name (for display) // モデル名(表示用)
let model_name = args let model_name = args
.model .model
.clone() .clone()
@ -416,7 +416,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("─────────────────────────────────────────────────"); println!("─────────────────────────────────────────────────");
} }
// Create client // クライアント作成
let client = match create_client(&args) { let client = match create_client(&args) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
@ -425,17 +425,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}; };
// Create Worker // Worker作成
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
let tool_call_names = Arc::new(Mutex::new(HashMap::new())); let tool_call_names = Arc::new(Mutex::new(HashMap::new()));
// Set system prompt // システムプロンプトを設定
if let Some(ref system_prompt) = args.system { if let Some(ref system_prompt) = args.system {
worker.set_system_prompt(system_prompt); worker.set_system_prompt(system_prompt);
} }
// Register tools (unless --no-tools) // ツール登録(--no-tools でなければ)
if !args.no_tools { if !args.no_tools {
let app = AppContext; let app = AppContext;
worker worker
@ -444,7 +444,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
worker.register_tool(app.calculate_definition()).unwrap(); worker.register_tool(app.calculate_definition()).unwrap();
} }
// Register streaming display handlers // ストリーミング表示用ハンドラーを登録
worker worker
.timeline_mut() .timeline_mut()
.on_text_block(StreamingPrinter::new()) .on_text_block(StreamingPrinter::new())
@ -452,7 +452,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
worker.add_post_tool_call_hook(ToolResultPrinterHook::new(tool_call_names)); worker.add_post_tool_call_hook(ToolResultPrinterHook::new(tool_call_names));
// One-shot mode // ワンショットモード
if let Some(prompt) = args.prompt { if let Some(prompt) = args.prompt {
match worker.run(&prompt).await { match worker.run(&prompt).await {
Ok(_) => {} Ok(_) => {}
@ -465,7 +465,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(()); return Ok(());
} }
// Interactive loop // 対話ループ
loop { loop {
print!("\n👤 You: "); print!("\n👤 You: ");
io::stdout().flush()?; io::stdout().flush()?;
@ -483,7 +483,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
break; break;
} }
// Run Worker (Worker manages history) // Workerを実行Workerが履歴を管理
match worker.run(input).await { match worker.run(input).await {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {

View File

@ -1,6 +1,6 @@
//! Public event types for Worker layer //! Worker層の公開イベント型
//! //!
//! Event representation exposed to external users. //! 外部利用者に公開するためのイベント表現。
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,38 +8,38 @@ use serde::{Deserialize, Serialize};
// Core Event Types (from llm_client layer) // Core Event Types (from llm_client layer)
// ============================================================================= // =============================================================================
/// Streaming events from LLM /// LLMからのストリーミングイベント
/// ///
/// Responses from each LLM provider are processed uniformly /// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして
/// as a stream of `Event`. /// 統一的に処理されます。
/// ///
/// # Event Types /// # イベントの種類
/// ///
/// - **Meta events**: `Ping`, `Usage`, `Status`, `Error` /// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error`
/// - **Block events**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort` /// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort`
/// ///
/// # Block Lifecycle /// # ブロックのライフサイクル
/// ///
/// Text and tool calls have events in the order of /// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
/// `BlockStart` → `BlockDelta`(multiple) → `BlockStop`. /// の順序でイベントが発生します。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Event { pub enum Event {
/// Heartbeat /// ハートビート
Ping(PingEvent), Ping(PingEvent),
/// Token usage /// トークン使用量
Usage(UsageEvent), Usage(UsageEvent),
/// Stream status change /// ストリームのステータス変化
Status(StatusEvent), Status(StatusEvent),
/// Error occurred /// エラー発生
Error(ErrorEvent), Error(ErrorEvent),
/// Block start (text, tool use, etc.) /// ブロック開始(テキスト、ツール使用等)
BlockStart(BlockStart), BlockStart(BlockStart),
/// Block delta data /// ブロックの差分データ
BlockDelta(BlockDelta), BlockDelta(BlockDelta),
/// Block normal end /// ブロック正常終了
BlockStop(BlockStop), BlockStop(BlockStop),
/// Block abort /// ブロック中断
BlockAbort(BlockAbort), BlockAbort(BlockAbort),
} }
@ -47,47 +47,47 @@ pub enum Event {
// Meta Events // Meta Events
// ============================================================================= // =============================================================================
/// Ping event (heartbeat) /// Pingイベント(ハートビート)
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct PingEvent { pub struct PingEvent {
pub timestamp: Option<u64>, pub timestamp: Option<u64>,
} }
/// Usage event /// 使用量イベント
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct UsageEvent { pub struct UsageEvent {
/// Input token count /// 入力トークン数
pub input_tokens: Option<u64>, pub input_tokens: Option<u64>,
/// Output token count /// 出力トークン数
pub output_tokens: Option<u64>, pub output_tokens: Option<u64>,
/// Total token count /// 合計トークン数
pub total_tokens: Option<u64>, pub total_tokens: Option<u64>,
/// Cache read token count /// キャッシュ読み込みトークン数
pub cache_read_input_tokens: Option<u64>, pub cache_read_input_tokens: Option<u64>,
/// Cache creation token count /// キャッシュ作成トークン数
pub cache_creation_input_tokens: Option<u64>, pub cache_creation_input_tokens: Option<u64>,
} }
/// Status event /// ステータスイベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatusEvent { pub struct StatusEvent {
pub status: ResponseStatus, pub status: ResponseStatus,
} }
/// Response status /// レスポンスステータス
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ResponseStatus { pub enum ResponseStatus {
/// Stream started /// ストリーム開始
Started, Started,
/// Completed normally /// 正常完了
Completed, Completed,
/// Cancelled /// キャンセルされた
Cancelled, Cancelled,
/// Error occurred /// エラー発生
Failed, Failed,
} }
/// Error event /// エラーイベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ErrorEvent { pub struct ErrorEvent {
pub code: Option<String>, pub code: Option<String>,
@ -98,27 +98,27 @@ pub struct ErrorEvent {
// Block Types // Block Types
// ============================================================================= // =============================================================================
/// Block type /// ブロックの種別
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BlockType { pub enum BlockType {
/// Text generation /// テキスト生成
Text, Text,
/// Thinking (Claude Extended Thinking, etc.) /// 思考 (Claude Extended Thinking等)
Thinking, Thinking,
/// Tool call /// ツール呼び出し
ToolUse, ToolUse,
/// Tool result /// ツール結果
ToolResult, ToolResult,
} }
/// Block start event /// ブロック開始イベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockStart { pub struct BlockStart {
/// Block index /// ブロックのインデックス
pub index: usize, pub index: usize,
/// Block type /// ブロックの種別
pub block_type: BlockType, pub block_type: BlockType,
/// Block-specific metadata /// ブロック固有のメタデータ
pub metadata: BlockMetadata, pub metadata: BlockMetadata,
} }
@ -128,7 +128,7 @@ impl BlockStart {
} }
} }
/// Block metadata /// ブロックのメタデータ
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BlockMetadata { pub enum BlockMetadata {
Text, Text,
@ -137,28 +137,28 @@ pub enum BlockMetadata {
ToolResult { tool_use_id: String }, ToolResult { tool_use_id: String },
} }
/// Block delta event /// ブロックデルタイベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockDelta { pub struct BlockDelta {
/// Block index /// ブロックのインデックス
pub index: usize, pub index: usize,
/// Delta content /// デルタの内容
pub delta: DeltaContent, pub delta: DeltaContent,
} }
/// Delta content /// デルタの内容
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DeltaContent { pub enum DeltaContent {
/// Text delta /// テキストデルタ
Text(String), Text(String),
/// Thinking delta /// 思考デルタ
Thinking(String), Thinking(String),
/// JSON substring of tool arguments /// ツール引数のJSON部分文字列
InputJson(String), InputJson(String),
} }
impl DeltaContent { impl DeltaContent {
/// Get block type of the delta /// デルタのブロック種別を取得
pub fn block_type(&self) -> BlockType { pub fn block_type(&self) -> BlockType {
match self { match self {
DeltaContent::Text(_) => BlockType::Text, DeltaContent::Text(_) => BlockType::Text,
@ -168,14 +168,14 @@ impl DeltaContent {
} }
} }
/// Block stop event /// ブロック停止イベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockStop { pub struct BlockStop {
/// Block index /// ブロックのインデックス
pub index: usize, pub index: usize,
/// Block type /// ブロックの種別
pub block_type: BlockType, pub block_type: BlockType,
/// Stop reason /// 停止理由
pub stop_reason: Option<StopReason>, pub stop_reason: Option<StopReason>,
} }
@ -185,14 +185,14 @@ impl BlockStop {
} }
} }
/// Block abort event /// ブロック中断イベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockAbort { pub struct BlockAbort {
/// Block index /// ブロックのインデックス
pub index: usize, pub index: usize,
/// Block type /// ブロックの種別
pub block_type: BlockType, pub block_type: BlockType,
/// Abort reason /// 中断理由
pub reason: String, pub reason: String,
} }
@ -202,16 +202,16 @@ impl BlockAbort {
} }
} }
/// Stop reason /// 停止理由
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StopReason { pub enum StopReason {
/// Natural end /// 自然終了
EndTurn, EndTurn,
/// Max tokens reached /// 最大トークン数到達
MaxTokens, MaxTokens,
/// Stop sequence reached /// ストップシーケンス到達
StopSequence, StopSequence,
/// Tool use /// ツール使用
ToolUse, ToolUse,
} }
@ -220,7 +220,7 @@ pub enum StopReason {
// ============================================================================= // =============================================================================
impl Event { impl Event {
/// Create text block start event /// テキストブロック開始イベントを作成
pub fn text_block_start(index: usize) -> Self { pub fn text_block_start(index: usize) -> Self {
Event::BlockStart(BlockStart { Event::BlockStart(BlockStart {
index, index,
@ -229,7 +229,7 @@ impl Event {
}) })
} }
/// Create text delta event /// テキストデルタイベントを作成
pub fn text_delta(index: usize, text: impl Into<String>) -> Self { pub fn text_delta(index: usize, text: impl Into<String>) -> Self {
Event::BlockDelta(BlockDelta { Event::BlockDelta(BlockDelta {
index, index,
@ -237,7 +237,7 @@ impl Event {
}) })
} }
/// Create text block stop event /// テキストブロック停止イベントを作成
pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self { pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self {
Event::BlockStop(BlockStop { Event::BlockStop(BlockStop {
index, index,
@ -246,7 +246,7 @@ impl Event {
}) })
} }
/// Create tool use block start event /// ツール使用ブロック開始イベントを作成
pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> Self { pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> Self {
Event::BlockStart(BlockStart { Event::BlockStart(BlockStart {
index, index,
@ -258,7 +258,7 @@ impl Event {
}) })
} }
/// Create tool input delta event /// ツール引数デルタイベントを作成
pub fn tool_input_delta(index: usize, json: impl Into<String>) -> Self { pub fn tool_input_delta(index: usize, json: impl Into<String>) -> Self {
Event::BlockDelta(BlockDelta { Event::BlockDelta(BlockDelta {
index, index,
@ -266,7 +266,7 @@ impl Event {
}) })
} }
/// Create tool use block stop event /// ツール使用ブロック停止イベントを作成
pub fn tool_use_stop(index: usize) -> Self { pub fn tool_use_stop(index: usize) -> Self {
Event::BlockStop(BlockStop { Event::BlockStop(BlockStop {
index, index,
@ -275,7 +275,7 @@ impl Event {
}) })
} }
/// Create usage event /// 使用量イベントを作成
pub fn usage(input_tokens: u64, output_tokens: u64) -> Self { pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
Event::Usage(UsageEvent { Event::Usage(UsageEvent {
input_tokens: Some(input_tokens), input_tokens: Some(input_tokens),
@ -286,7 +286,7 @@ impl Event {
}) })
} }
/// Create ping event /// Pingイベントを作成
pub fn ping() -> Self { pub fn ping() -> Self {
Event::Ping(PingEvent { timestamp: None }) Event::Ping(PingEvent { timestamp: None })
} }

View File

@ -1,8 +1,8 @@
//! Handler/Kind Types //! Handler/Kind
//! //!
//! Traits for processing events in the Timeline layer. //! Timeline層でイベントを処理するためのトレイト。
//! By implementing custom handlers and registering them with Timeline, //! カスタムハンドラを実装してTimelineに登録することで、
//! you can receive stream events. //! ストリームイベントを受信できます。
use crate::timeline::event::*; use crate::timeline::event::*;
@ -10,13 +10,13 @@ use crate::timeline::event::*;
// Kind Trait // Kind Trait
// ============================================================================= // =============================================================================
/// Marker trait defining event types /// イベント種別を定義するマーカートレイト
/// ///
/// Each Kind specifies its corresponding event type. /// 各Kindは対応するイベント型を指定します。
/// Handlers are implemented for this Kind, and multiple Handlers /// HandlerはこのKindに対して実装され、同じKindに対して
/// with different Scope types can be registered for the same Kind. /// 異なるScope型を持つ複数のHandlerを登録できます。
pub trait Kind { pub trait Kind {
/// Event type corresponding to this Kind /// このKindに対応するイベント型
type Event; type Event;
} }
@ -24,10 +24,10 @@ pub trait Kind {
// Handler Trait // Handler Trait
// ============================================================================= // =============================================================================
/// Handler trait for processing events /// イベントを処理するハンドラトレイト
/// ///
/// Defines event processing for a specific `Kind`. /// 特定の`Kind`に対するイベント処理を定義します。
/// `Scope` is state held during the block's lifecycle. /// `Scope`はブロックのライフサイクル中に保持される状態です。
/// ///
/// # Examples /// # Examples
/// ///
@ -39,7 +39,7 @@ pub trait Kind {
/// } /// }
/// ///
/// impl Handler<TextBlockKind> for TextCollector { /// impl Handler<TextBlockKind> for TextCollector {
/// type Scope = String; // Buffer per block /// type Scope = String; // ブロックごとのバッファ
/// ///
/// fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) { /// fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) {
/// match event { /// match event {
@ -53,13 +53,13 @@ pub trait Kind {
/// } /// }
/// ``` /// ```
pub trait Handler<K: Kind> { pub trait Handler<K: Kind> {
/// Handler-specific scope type /// Handler固有のスコープ型
/// ///
/// Generated with `Default::default()` at block start, /// ブロック開始時に`Default::default()`で生成され、
/// and destroyed at block end. /// ブロック終了時に破棄されます。
type Scope: Default; type Scope: Default;
/// Process the event /// イベントを処理する
fn on_event(&mut self, scope: &mut Self::Scope, event: &K::Event); fn on_event(&mut self, scope: &mut Self::Scope, event: &K::Event);
} }
@ -67,25 +67,25 @@ pub trait Handler<K: Kind> {
// Meta Kind Definitions // Meta Kind Definitions
// ============================================================================= // =============================================================================
/// Usage Kind - for usage events /// Usage Kind - 使用量イベント用
pub struct UsageKind; pub struct UsageKind;
impl Kind for UsageKind { impl Kind for UsageKind {
type Event = UsageEvent; type Event = UsageEvent;
} }
/// Ping Kind - for ping events /// Ping Kind - Pingイベント用
pub struct PingKind; pub struct PingKind;
impl Kind for PingKind { impl Kind for PingKind {
type Event = PingEvent; type Event = PingEvent;
} }
/// Status Kind - for status events /// Status Kind - ステータスイベント用
pub struct StatusKind; pub struct StatusKind;
impl Kind for StatusKind { impl Kind for StatusKind {
type Event = StatusEvent; type Event = StatusEvent;
} }
/// Error Kind - for error events /// Error Kind - エラーイベント用
pub struct ErrorKind; pub struct ErrorKind;
impl Kind for ErrorKind { impl Kind for ErrorKind {
type Event = ErrorEvent; type Event = ErrorEvent;
@ -95,13 +95,13 @@ impl Kind for ErrorKind {
// Block Kind Definitions // Block Kind Definitions
// ============================================================================= // =============================================================================
/// TextBlock Kind - for text blocks /// TextBlock Kind - テキストブロック用
pub struct TextBlockKind; pub struct TextBlockKind;
impl Kind for TextBlockKind { impl Kind for TextBlockKind {
type Event = TextBlockEvent; type Event = TextBlockEvent;
} }
/// Text block events /// テキストブロックのイベント
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum TextBlockEvent { pub enum TextBlockEvent {
Start(TextBlockStart), Start(TextBlockStart),
@ -120,13 +120,13 @@ pub struct TextBlockStop {
pub stop_reason: Option<StopReason>, pub stop_reason: Option<StopReason>,
} }
/// ThinkingBlock Kind - for thinking blocks /// ThinkingBlock Kind - 思考ブロック用
pub struct ThinkingBlockKind; pub struct ThinkingBlockKind;
impl Kind for ThinkingBlockKind { impl Kind for ThinkingBlockKind {
type Event = ThinkingBlockEvent; type Event = ThinkingBlockEvent;
} }
/// Thinking block events /// 思考ブロックのイベント
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum ThinkingBlockEvent { pub enum ThinkingBlockEvent {
Start(ThinkingBlockStart), Start(ThinkingBlockStart),
@ -144,17 +144,17 @@ pub struct ThinkingBlockStop {
pub index: usize, pub index: usize,
} }
/// ToolUseBlock Kind - for tool use blocks /// ToolUseBlock Kind - ツール使用ブロック用
pub struct ToolUseBlockKind; pub struct ToolUseBlockKind;
impl Kind for ToolUseBlockKind { impl Kind for ToolUseBlockKind {
type Event = ToolUseBlockEvent; type Event = ToolUseBlockEvent;
} }
/// Tool use block events /// ツール使用ブロックのイベント
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum ToolUseBlockEvent { pub enum ToolUseBlockEvent {
Start(ToolUseBlockStart), Start(ToolUseBlockStart),
/// JSON substring of tool arguments /// ツール引数のJSON部分文字列
InputJsonDelta(String), InputJsonDelta(String),
Stop(ToolUseBlockStop), Stop(ToolUseBlockStop),
} }

View File

@ -1,6 +1,6 @@
//! Hook-related type definitions //! Hook関連の型定義
//! //!
//! Types used for turn control and intervention in the Worker layer //! Worker層でのターン制御・介入に使用される型
use async_trait::async_trait; use async_trait::async_trait;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -60,25 +60,25 @@ use std::sync::Arc;
use crate::tool::{Tool, ToolMeta}; use crate::tool::{Tool, ToolMeta};
/// Input context for PreToolCall /// PreToolCall の入力コンテキスト
pub struct ToolCallContext { pub struct ToolCallContext {
/// Tool call information (modifiable) /// ツール呼び出し情報(改変可能)
pub call: ToolCall, pub call: ToolCall,
/// Tool meta information (immutable) /// ツールメタ情報(不変)
pub meta: ToolMeta, pub meta: ToolMeta,
/// Tool instance (for state access) /// ツールインスタンス(状態アクセス用)
pub tool: Arc<dyn Tool>, pub tool: Arc<dyn Tool>,
} }
/// Input context for PostToolCall /// PostToolCall の入力コンテキスト
pub struct PostToolCallContext { pub struct PostToolCallContext {
/// Tool call information /// ツール呼び出し情報
pub call: ToolCall, pub call: ToolCall,
/// Tool execution result (modifiable) /// ツール実行結果(改変可能)
pub result: ToolResult, pub result: ToolResult,
/// Tool meta information (immutable) /// ツールメタ情報(不変)
pub meta: ToolMeta, pub meta: ToolMeta,
/// Tool instance (for state access) /// ツールインスタンス(状態アクセス用)
pub tool: Arc<dyn Tool>, pub tool: Arc<dyn Tool>,
} }
@ -116,35 +116,35 @@ impl HookEventKind for OnAbort {
// Tool Call / Result Types // Tool Call / Result Types
// ============================================================================= // =============================================================================
/// Tool call information /// ツール呼び出し情報
/// ///
/// Represents a ToolUse block from LLM, modifiable in Hook processing /// LLMからのToolUseブロックを表現し、Hook処理で改変可能
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall { pub struct ToolCall {
/// Tool call ID (used for linking with response) /// ツール呼び出しIDレスポンスとの紐付けに使用
pub id: String, pub id: String,
/// Tool name /// ツール名
pub name: String, pub name: String,
/// Input arguments (JSON) /// 入力引数JSON
pub input: Value, pub input: Value,
} }
/// Tool execution result /// ツール実行結果
/// ///
/// Represents the result after tool execution, modifiable in Hook processing /// ツール実行後の結果を表現し、Hook処理で改変可能
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult { pub struct ToolResult {
/// Corresponding tool call ID /// 対応するツール呼び出しID
pub tool_use_id: String, pub tool_use_id: String,
/// Result content /// 結果コンテンツ
pub content: String, pub content: String,
/// Whether this is an error /// エラーかどうか
#[serde(default)] #[serde(default)]
pub is_error: bool, pub is_error: bool,
} }
impl ToolResult { impl ToolResult {
/// Create a success result /// 成功結果を作成
pub fn success(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self { pub fn success(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
Self { Self {
tool_use_id: tool_use_id.into(), tool_use_id: tool_use_id.into(),
@ -153,7 +153,7 @@ impl ToolResult {
} }
} }
/// Create an error result /// エラー結果を作成
pub fn error(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self { pub fn error(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
Self { Self {
tool_use_id: tool_use_id.into(), tool_use_id: tool_use_id.into(),
@ -167,13 +167,13 @@ impl ToolResult {
// Hook Error // Hook Error
// ============================================================================= // =============================================================================
/// Hook error /// Hookエラー
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum HookError { pub enum HookError {
/// Processing was aborted /// 処理が中断された
#[error("Aborted: {0}")] #[error("Aborted: {0}")]
Aborted(String), Aborted(String),
/// Internal error /// 内部エラー
#[error("Hook error: {0}")] #[error("Hook error: {0}")]
Internal(String), Internal(String),
} }
@ -182,9 +182,9 @@ pub enum HookError {
// Hook Trait // Hook Trait
// ============================================================================= // =============================================================================
/// Trait for handling Hook events /// Hookイベントの処理を行うトレイト
/// ///
/// Each event type has a different return type, constrained via `HookEventKind`. /// 各イベント種別は戻り値型が異なるため、`HookEventKind`を介して型を制約する。
#[async_trait] #[async_trait]
pub trait Hook<E: HookEventKind>: Send + Sync { pub trait Hook<E: HookEventKind>: Send + Sync {
async fn call(&self, input: &mut E::Input) -> Result<E::Output, HookError>; async fn call(&self, input: &mut E::Input) -> Result<E::Output, HookError>;
@ -194,9 +194,9 @@ pub trait Hook<E: HookEventKind>: Send + Sync {
// Hook Registry // Hook Registry
// ============================================================================= // =============================================================================
/// Registry holding all Hooks /// 全 Hook を保持するレジストリ
/// ///
/// Used internally by Worker to manage all Hook types. /// Worker 内部で使用され、各種 Hook を一括管理する。
pub struct HookRegistry { pub struct HookRegistry {
/// on_prompt_submit Hook /// on_prompt_submit Hook
pub(crate) on_prompt_submit: Vec<Box<dyn Hook<OnPromptSubmit>>>, pub(crate) on_prompt_submit: Vec<Box<dyn Hook<OnPromptSubmit>>>,
@ -219,7 +219,7 @@ impl Default for HookRegistry {
} }
impl HookRegistry { impl HookRegistry {
/// Create an empty HookRegistry /// 空の HookRegistry を作成
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
on_prompt_submit: Vec::new(), on_prompt_submit: Vec::new(),

View File

@ -1,34 +1,34 @@
//! llm-worker - LLM Worker Library //! llm-worker - LLMワーカーライブラリ
//! //!
//! Provides components for managing interactions with LLMs. //! LLMとの対話を管理するコンポーネントを提供します。
//! //!
//! # Main Components //! # 主要なコンポーネント
//! //!
//! - [`Worker`] - Central component for managing LLM interactions //! - [`Worker`] - LLMとの対話を管理する中心コンポーネント
//! - [`tool::Tool`] - Tools that can be invoked by the LLM //! - [`tool::Tool`] - LLMから呼び出し可能なツール
//! - [`hook::Hook`] - Hooks for intercepting turn progression //! - [`hook::Hook`] - ターン進行への介入
//! - [`subscriber::WorkerSubscriber`] - Subscribing to streaming events //! - [`subscriber::WorkerSubscriber`] - ストリーミングイベントの購読
//! //!
//! # Quick Start //! # Quick Start
//! //!
//! ```ignore //! ```ignore
//! use llm_worker::{Worker, Message}; //! use llm_worker::{Worker, Message};
//! //!
//! // Create a Worker //! // Workerを作成
//! let mut worker = Worker::new(client) //! let mut worker = Worker::new(client)
//! .system_prompt("You are a helpful assistant."); //! .system_prompt("You are a helpful assistant.");
//! //!
//! // Register tools (optional) //! // ツールを登録(オプション)
//! // worker.register_tool(my_tool_definition)?; //! // worker.register_tool(my_tool_definition)?;
//! //!
//! // Run the interaction //! // 対話を実行
//! let history = worker.run("Hello!").await?; //! let history = worker.run("Hello!").await?;
//! ``` //! ```
//! //!
//! # Cache Protection //! # キャッシュ保護
//! //!
//! To maximize KV cache hit rate, transition to the locked state //! KVキャッシュのヒット率を最大化するには、[`Worker::lock()`]で
//! with [`Worker::lock()`] before execution. //! ロック状態に遷移してから実行してください。
//! //!
//! ```ignore //! ```ignore
//! let mut locked = worker.lock(); //! let mut locked = worker.lock();

View File

@ -1,71 +1,71 @@
//! Message Types //! メッセージ型
//! //!
//! Message structure used in conversations with LLM. //! LLMとの会話で使用されるメッセージ構造。
//! Can be easily created using [`Message::user`] or [`Message::assistant`]. //! [`Message::user`]や[`Message::assistant`]で簡単に作成できます。
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Message role /// メッセージのロール
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Role { pub enum Role {
/// User /// ユーザー
User, User,
/// Assistant /// アシスタント
Assistant, Assistant,
} }
/// Conversation message /// 会話のメッセージ
/// ///
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```ignore
/// use llm_worker::Message; /// use llm_worker::Message;
/// ///
/// // User message /// // ユーザーメッセージ
/// let user_msg = Message::user("Hello!"); /// let user_msg = Message::user("Hello!");
/// ///
/// // Assistant message /// // アシスタントメッセージ
/// let assistant_msg = Message::assistant("Hi there!"); /// let assistant_msg = Message::assistant("Hi there!");
/// ``` /// ```
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message { pub struct Message {
/// Role /// ロール
pub role: Role, pub role: Role,
/// Content /// コンテンツ
pub content: MessageContent, pub content: MessageContent,
} }
/// Message content /// メッセージコンテンツ
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum MessageContent { pub enum MessageContent {
/// Text content /// テキストコンテンツ
Text(String), Text(String),
/// Tool result /// ツール結果
ToolResult { ToolResult {
tool_use_id: String, tool_use_id: String,
content: String, content: String,
}, },
/// Composite content (text + tool use, etc.) /// 複合コンテンツ (テキスト + ツール使用等)
Parts(Vec<ContentPart>), Parts(Vec<ContentPart>),
} }
/// Content part /// コンテンツパーツ
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum ContentPart { pub enum ContentPart {
/// Text /// テキスト
#[serde(rename = "text")] #[serde(rename = "text")]
Text { text: String }, Text { text: String },
/// Tool use /// ツール使用
#[serde(rename = "tool_use")] #[serde(rename = "tool_use")]
ToolUse { ToolUse {
id: String, id: String,
name: String, name: String,
input: serde_json::Value, input: serde_json::Value,
}, },
/// Tool result /// ツール結果
#[serde(rename = "tool_result")] #[serde(rename = "tool_result")]
ToolResult { ToolResult {
tool_use_id: String, tool_use_id: String,
@ -74,13 +74,13 @@ pub enum ContentPart {
} }
impl Message { impl Message {
/// Create a user message /// ユーザーメッセージを作成
/// ///
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```ignore
/// use llm_worker::Message; /// use llm_worker::Message;
/// let msg = Message::user("Hello"); /// let msg = Message::user("こんにちは");
/// ``` /// ```
pub fn user(content: impl Into<String>) -> Self { pub fn user(content: impl Into<String>) -> Self {
Self { Self {
@ -89,10 +89,10 @@ impl Message {
} }
} }
/// Create an assistant message /// アシスタントメッセージを作成
/// ///
/// Usually auto-generated inside Worker, /// 通常はWorker内部で自動生成されますが、
/// but can be manually created for history initialization, etc. /// 履歴の初期化などで手動作成も可能です。
pub fn assistant(content: impl Into<String>) -> Self { pub fn assistant(content: impl Into<String>) -> Self {
Self { Self {
role: Role::Assistant, role: Role::Assistant,
@ -100,10 +100,10 @@ impl Message {
} }
} }
/// Create a tool result message /// ツール結果メッセージを作成
/// ///
/// Auto-generated inside Worker after tool execution. /// Worker内部でツール実行後に自動生成されます。
/// Usually no need to create manually. /// 通常は直接作成する必要はありません。
pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self { pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
Self { Self {
role: Role::User, role: Role::User,

View File

@ -1,25 +1,25 @@
//! Worker State //! Worker状態
//! //!
//! State marker types for cache protection using the Type-state pattern. //! Type-stateパターンによるキャッシュ保護のための状態マーカー型。
//! Worker has state transitions from `Mutable` → `CacheLocked`. //! Workerは`Mutable` → `CacheLocked`の状態遷移を持ちます。
/// Marker trait representing Worker state /// Worker状態を表すマーカートレイト
/// ///
/// This trait is sealed and cannot be implemented externally. /// このトレイトはシールされており、外部から実装することはできません。
pub trait WorkerState: private::Sealed + Send + Sync + 'static {} pub trait WorkerState: private::Sealed + Send + Sync + 'static {}
mod private { mod private {
pub trait Sealed {} pub trait Sealed {}
} }
/// Mutable state (editable) /// 編集可能状態
/// ///
/// In this state, the following operations are available: /// この状態では以下の操作が可能です:
/// - Setting/changing system prompt /// - システムプロンプトの設定・変更
/// - Editing message history (add, delete, clear) /// - メッセージ履歴の編集(追加、削除、クリア)
/// - Registering tools and hooks /// - ツール・Hookの登録
/// ///
/// Can transition to [`CacheLocked`] state via `Worker::lock()`. /// `Worker::lock()`により[`CacheLocked`]状態へ遷移できます。
/// ///
/// # Examples /// # Examples
/// ///
@ -29,11 +29,11 @@ mod private {
/// let mut worker = Worker::new(client) /// let mut worker = Worker::new(client)
/// .system_prompt("You are helpful."); /// .system_prompt("You are helpful.");
/// ///
/// // History can be edited /// // 履歴を編集可能
/// worker.push_message(Message::user("Hello")); /// worker.push_message(Message::user("Hello"));
/// worker.clear_history(); /// worker.clear_history();
/// ///
/// // Lock to protected state /// // ロックして保護状態へ
/// let locked = worker.lock(); /// let locked = worker.lock();
/// ``` /// ```
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
@ -42,17 +42,17 @@ pub struct Mutable;
impl private::Sealed for Mutable {} impl private::Sealed for Mutable {}
impl WorkerState for Mutable {} impl WorkerState for Mutable {}
/// Cache locked state (cache protected) /// キャッシュロック状態(キャッシュ保護)
/// ///
/// In this state, the following restrictions apply: /// この状態では以下の制限があります:
/// - System prompt cannot be changed /// - システムプロンプトの変更不可
/// - Existing message history cannot be modified (only appending to the end) /// - 既存メッセージ履歴の変更不可(末尾への追記のみ)
/// ///
/// To ensure LLM API KV cache hits, /// LLM APIのKVキャッシュヒットを保証するため、
/// using this state during execution is recommended. /// 実行時にはこの状態の使用が推奨されます。
/// ///
/// Can return to [`Mutable`] state via `Worker::unlock()`, /// `Worker::unlock()`により[`Mutable`]状態へ戻せますが、
/// but note that cache protection will be released. /// キャッシュ保護が解除されることに注意してください。
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct CacheLocked; pub struct CacheLocked;

View File

@ -1,7 +1,7 @@
//! Event Subscription //! イベント購読
//! //!
//! Trait for receiving streaming events from LLM in real-time. //! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。
//! Used for stream display to UI and progress display. //! UIへのストリーム表示やプログレス表示に使用します。
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -18,17 +18,17 @@ use crate::{
// WorkerSubscriber Trait // WorkerSubscriber Trait
// ============================================================================= // =============================================================================
/// Trait for subscribing to streaming events from LLM /// LLMからのストリーミングイベントを購読するトレイト
/// ///
/// When registered with Worker, you can receive events from text generation /// Workerに登録すると、テキスト生成やツール呼び出しのイベントを
/// and tool calls in real-time. Ideal for stream display to UI. /// リアルタイムで受信できます。UIへのストリーム表示に最適です。
/// ///
/// # Available Events /// # 受信できるイベント
/// ///
/// - **Block events**: Text, tool use (with scope) /// - **ブロックイベント**: テキスト、ツール使用(スコープ付き)
/// - **Meta events**: Usage, status, error /// - **メタイベント**: 使用量、ステータス、エラー
/// - **Completion events**: Text complete, tool call complete /// - **完了イベント**: テキスト完了、ツール呼び出し完了
/// - **Turn control**: Turn start, turn end /// - **ターン制御**: ターン開始、ターン終了
/// ///
/// # Examples /// # Examples
/// ///
@ -44,7 +44,7 @@ use crate::{
/// ///
/// fn on_text_block(&mut self, _: &mut (), event: &TextBlockEvent) { /// fn on_text_block(&mut self, _: &mut (), event: &TextBlockEvent) {
/// if let TextBlockEvent::Delta(text) = event { /// if let TextBlockEvent::Delta(text) = event {
/// print!("{}", text); // Real-time output /// print!("{}", text); // リアルタイム出力
/// } /// }
/// } /// }
/// ///
@ -53,37 +53,37 @@ use crate::{
/// } /// }
/// } /// }
/// ///
/// // Register with Worker /// // Workerに登録
/// worker.subscribe(StreamPrinter); /// worker.subscribe(StreamPrinter);
/// ``` /// ```
pub trait WorkerSubscriber: Send { pub trait WorkerSubscriber: Send {
// ========================================================================= // =========================================================================
// Scope Types (for block events) // スコープ型(ブロックイベント用)
// ========================================================================= // =========================================================================
/// Scope type for text block processing /// テキストブロック処理用のスコープ型
/// ///
/// Generated with Default::default() at block start, /// ブロック開始時にDefault::default()で生成され、
/// destroyed at block end. /// ブロック終了時に破棄される。
type TextBlockScope: Default + Send + Sync; type TextBlockScope: Default + Send + Sync;
/// Scope type for tool use block processing /// ツール使用ブロック処理用のスコープ型
type ToolUseBlockScope: Default + Send + Sync; type ToolUseBlockScope: Default + Send + Sync;
// ========================================================================= // =========================================================================
// Block Events (with scope management) // ブロックイベント(スコープ管理あり)
// ========================================================================= // =========================================================================
/// Text block event /// テキストブロックイベント
/// ///
/// Has Start/Delta/Stop lifecycle. /// Start/Delta/Stopのライフサイクルを持つ。
/// Scope is generated at block start and destroyed at end. /// scopeはブロック開始時に生成され、終了時に破棄される。
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_text_block(&mut self, scope: &mut Self::TextBlockScope, event: &TextBlockEvent) {} fn on_text_block(&mut self, scope: &mut Self::TextBlockScope, event: &TextBlockEvent) {}
/// Tool use block event /// ツール使用ブロックイベント
/// ///
/// Has Start/InputJsonDelta/Stop lifecycle. /// Start/InputJsonDelta/Stopのライフサイクルを持つ。
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_tool_use_block( fn on_tool_use_block(
&mut self, &mut self,
@ -93,62 +93,62 @@ pub trait WorkerSubscriber: Send {
} }
// ========================================================================= // =========================================================================
// Single Events (no scope needed) // 単発イベント(スコープ不要)
// ========================================================================= // =========================================================================
/// Usage event /// 使用量イベント
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_usage(&mut self, event: &UsageEvent) {} fn on_usage(&mut self, event: &UsageEvent) {}
/// Status event /// ステータスイベント
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_status(&mut self, event: &StatusEvent) {} fn on_status(&mut self, event: &StatusEvent) {}
/// Error event /// エラーイベント
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_error(&mut self, event: &ErrorEvent) {} fn on_error(&mut self, event: &ErrorEvent) {}
// ========================================================================= // =========================================================================
// Accumulated Events (added in Worker layer) // 累積イベントWorker層で追加
// ========================================================================= // =========================================================================
/// Text complete event /// テキスト完了イベント
/// ///
/// When a text block completes, the entire accumulated text is passed. /// テキストブロックが完了した時点で、累積されたテキスト全体が渡される。
/// Convenient for receiving the final result after block processing. /// ブロック処理後の最終結果を受け取るのに便利。
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_text_complete(&mut self, text: &str) {} fn on_text_complete(&mut self, text: &str) {}
/// Tool call complete event /// ツール呼び出し完了イベント
/// ///
/// When a tool use block completes, the complete ToolCall is passed. /// ツール使用ブロックが完了した時点で、完全なToolCallが渡される。
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_tool_call_complete(&mut self, call: &ToolCall) {} fn on_tool_call_complete(&mut self, call: &ToolCall) {}
// ========================================================================= // =========================================================================
// Turn Control // ターン制御
// ========================================================================= // =========================================================================
/// On turn start /// ターン開始時
/// ///
/// `turn` is a 0-based turn number. /// `turn`は0から始まるターン番号。
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_turn_start(&mut self, turn: usize) {} fn on_turn_start(&mut self, turn: usize) {}
/// On turn end /// ターン終了時
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_turn_end(&mut self, turn: usize) {} fn on_turn_end(&mut self, turn: usize) {}
} }
// ============================================================================= // =============================================================================
// SubscriberAdapter - Bridge WorkerSubscriber to Timeline handlers // SubscriberAdapter - WorkerSubscriberをTimelineハンドラにブリッジ
// ============================================================================= // =============================================================================
// ============================================================================= // =============================================================================
// TextBlock Handler Adapter // TextBlock Handler Adapter
// ============================================================================= // =============================================================================
/// Subscriber adapter for TextBlockKind /// TextBlockKind用のSubscriberアダプター
pub(crate) struct TextBlockSubscriberAdapter<S: WorkerSubscriber> { pub(crate) struct TextBlockSubscriberAdapter<S: WorkerSubscriber> {
subscriber: Arc<Mutex<S>>, subscriber: Arc<Mutex<S>>,
} }
@ -167,10 +167,10 @@ impl<S: WorkerSubscriber> Clone for TextBlockSubscriberAdapter<S> {
} }
} }
/// Wrapper for TextBlock scope /// TextBlockのスコープをラップ
pub struct TextBlockScopeWrapper<S: WorkerSubscriber> { pub struct TextBlockScopeWrapper<S: WorkerSubscriber> {
inner: S::TextBlockScope, inner: S::TextBlockScope,
buffer: String, // Buffer for on_text_complete buffer: String, // on_text_complete用のバッファ
} }
impl<S: WorkerSubscriber> Default for TextBlockScopeWrapper<S> { impl<S: WorkerSubscriber> Default for TextBlockScopeWrapper<S> {
@ -186,16 +186,16 @@ impl<S: WorkerSubscriber + 'static> Handler<TextBlockKind> for TextBlockSubscrib
type Scope = TextBlockScopeWrapper<S>; type Scope = TextBlockScopeWrapper<S>;
fn on_event(&mut self, scope: &mut Self::Scope, event: &TextBlockEvent) { fn on_event(&mut self, scope: &mut Self::Scope, event: &TextBlockEvent) {
// Accumulate deltas into buffer // Deltaの場合はバッファに蓄積
if let TextBlockEvent::Delta(text) = event { if let TextBlockEvent::Delta(text) = event {
scope.buffer.push_str(text); scope.buffer.push_str(text);
} }
// Call Subscriber's TextBlock event handler // SubscriberのTextBlockイベントハンドラを呼び出し
if let Ok(mut subscriber) = self.subscriber.lock() { if let Ok(mut subscriber) = self.subscriber.lock() {
subscriber.on_text_block(&mut scope.inner, event); subscriber.on_text_block(&mut scope.inner, event);
// Also call on_text_complete on Stop // Stopの場合はon_text_completeも呼び出し
if matches!(event, TextBlockEvent::Stop(_)) { if matches!(event, TextBlockEvent::Stop(_)) {
subscriber.on_text_complete(&scope.buffer); subscriber.on_text_complete(&scope.buffer);
} }
@ -207,7 +207,7 @@ impl<S: WorkerSubscriber + 'static> Handler<TextBlockKind> for TextBlockSubscrib
// ToolUseBlock Handler Adapter // ToolUseBlock Handler Adapter
// ============================================================================= // =============================================================================
/// Subscriber adapter for ToolUseBlockKind /// ToolUseBlockKind用のSubscriberアダプター
pub(crate) struct ToolUseBlockSubscriberAdapter<S: WorkerSubscriber> { pub(crate) struct ToolUseBlockSubscriberAdapter<S: WorkerSubscriber> {
subscriber: Arc<Mutex<S>>, subscriber: Arc<Mutex<S>>,
} }
@ -226,12 +226,12 @@ impl<S: WorkerSubscriber> Clone for ToolUseBlockSubscriberAdapter<S> {
} }
} }
/// Wrapper for ToolUseBlock scope /// ToolUseBlockのスコープをラップ
pub struct ToolUseBlockScopeWrapper<S: WorkerSubscriber> { pub struct ToolUseBlockScopeWrapper<S: WorkerSubscriber> {
inner: S::ToolUseBlockScope, inner: S::ToolUseBlockScope,
id: String, id: String,
name: String, name: String,
input_json: String, // JSON accumulation input_json: String, // JSON蓄積用
} }
impl<S: WorkerSubscriber> Default for ToolUseBlockScopeWrapper<S> { impl<S: WorkerSubscriber> Default for ToolUseBlockScopeWrapper<S> {
@ -249,22 +249,22 @@ impl<S: WorkerSubscriber + 'static> Handler<ToolUseBlockKind> for ToolUseBlockSu
type Scope = ToolUseBlockScopeWrapper<S>; type Scope = ToolUseBlockScopeWrapper<S>;
fn on_event(&mut self, scope: &mut Self::Scope, event: &ToolUseBlockEvent) { fn on_event(&mut self, scope: &mut Self::Scope, event: &ToolUseBlockEvent) {
// Save metadata on Start // Start時にメタデータを保存
if let ToolUseBlockEvent::Start(start) = event { if let ToolUseBlockEvent::Start(start) = event {
scope.id = start.id.clone(); scope.id = start.id.clone();
scope.name = start.name.clone(); scope.name = start.name.clone();
} }
// Accumulate InputJsonDelta into buffer // InputJsonDeltaの場合はバッファに蓄積
if let ToolUseBlockEvent::InputJsonDelta(json) = event { if let ToolUseBlockEvent::InputJsonDelta(json) = event {
scope.input_json.push_str(json); scope.input_json.push_str(json);
} }
// Call Subscriber's ToolUseBlock event handler // SubscriberのToolUseBlockイベントハンドラを呼び出し
if let Ok(mut subscriber) = self.subscriber.lock() { if let Ok(mut subscriber) = self.subscriber.lock() {
subscriber.on_tool_use_block(&mut scope.inner, event); subscriber.on_tool_use_block(&mut scope.inner, event);
// Also call on_tool_call_complete on Stop // Stopの場合はon_tool_call_completeも呼び出し
if matches!(event, ToolUseBlockEvent::Stop(_)) { if matches!(event, ToolUseBlockEvent::Stop(_)) {
let input: serde_json::Value = let input: serde_json::Value =
serde_json::from_str(&scope.input_json).unwrap_or_default(); serde_json::from_str(&scope.input_json).unwrap_or_default();
@ -283,7 +283,7 @@ impl<S: WorkerSubscriber + 'static> Handler<ToolUseBlockKind> for ToolUseBlockSu
// Meta Event Handler Adapters // Meta Event Handler Adapters
// ============================================================================= // =============================================================================
/// Subscriber adapter for UsageKind /// UsageKind用のSubscriberアダプター
pub(crate) struct UsageSubscriberAdapter<S: WorkerSubscriber> { pub(crate) struct UsageSubscriberAdapter<S: WorkerSubscriber> {
subscriber: Arc<Mutex<S>>, subscriber: Arc<Mutex<S>>,
} }
@ -312,7 +312,7 @@ impl<S: WorkerSubscriber + 'static> Handler<UsageKind> for UsageSubscriberAdapte
} }
} }
/// Subscriber adapter for StatusKind /// StatusKind用のSubscriberアダプター
pub(crate) struct StatusSubscriberAdapter<S: WorkerSubscriber> { pub(crate) struct StatusSubscriberAdapter<S: WorkerSubscriber> {
subscriber: Arc<Mutex<S>>, subscriber: Arc<Mutex<S>>,
} }
@ -341,7 +341,7 @@ impl<S: WorkerSubscriber + 'static> Handler<StatusKind> for StatusSubscriberAdap
} }
} }
/// Subscriber adapter for ErrorKind /// ErrorKind用のSubscriberアダプター
pub(crate) struct ErrorSubscriberAdapter<S: WorkerSubscriber> { pub(crate) struct ErrorSubscriberAdapter<S: WorkerSubscriber> {
subscriber: Arc<Mutex<S>>, subscriber: Arc<Mutex<S>>,
} }

View File

@ -1,7 +1,7 @@
//! Tool Definition //! ツール定義
//! //!
//! Traits for defining tools callable by LLM. //! LLMから呼び出し可能なツールを定義するためのトレイト。
//! Usually auto-implemented using the `#[tool]` macro. //! 通常は`#[tool]`マクロを使用して自動実装します。
use std::sync::Arc; use std::sync::Arc;
@ -9,40 +9,40 @@ use async_trait::async_trait;
use serde_json::Value; use serde_json::Value;
use thiserror::Error; use thiserror::Error;
/// Error during tool execution /// ツール実行時のエラー
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ToolError { pub enum ToolError {
/// Invalid argument /// 引数が不正
#[error("Invalid argument: {0}")] #[error("Invalid argument: {0}")]
InvalidArgument(String), InvalidArgument(String),
/// Execution failed /// 実行に失敗
#[error("Execution failed: {0}")] #[error("Execution failed: {0}")]
ExecutionFailed(String), ExecutionFailed(String),
/// Internal error /// 内部エラー
#[error("Internal error: {0}")] #[error("Internal error: {0}")]
Internal(String), Internal(String),
} }
// ============================================================================= // =============================================================================
// ToolMeta - Immutable Meta Information // ToolMeta - 不変のメタ情報
// ============================================================================= // =============================================================================
/// Tool meta information (fixed at registration, immutable) /// ツールのメタ情報(登録時に固定、不変)
/// ///
/// Generated from `ToolDefinition` factory and does not change after registration with Worker. /// `ToolDefinition` ファクトリから生成され、Worker に登録後は変更されません。
/// Used for sending tool definitions to LLM. /// LLM へのツール定義送信に使用されます。
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolMeta { pub struct ToolMeta {
/// Tool name (used by LLM for identification) /// ツール名LLMが識別に使用
pub name: String, pub name: String,
/// Tool description (included in prompt to LLM) /// ツールの説明LLMへのプロンプトに含まれる
pub description: String, pub description: String,
/// JSON Schema for arguments /// 引数のJSON Schema
pub input_schema: Value, pub input_schema: Value,
} }
impl ToolMeta { impl ToolMeta {
/// Create a new ToolMeta /// 新しい ToolMeta を作成
pub fn new(name: impl Into<String>) -> Self { pub fn new(name: impl Into<String>) -> Self {
Self { Self {
name: name.into(), name: name.into(),
@ -51,13 +51,13 @@ impl ToolMeta {
} }
} }
/// Set the description /// 説明を設定
pub fn description(mut self, desc: impl Into<String>) -> Self { pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into(); self.description = desc.into();
self self
} }
/// Set the argument schema /// 引数スキーマを設定
pub fn input_schema(mut self, schema: Value) -> Self { pub fn input_schema(mut self, schema: Value) -> Self {
self.input_schema = schema; self.input_schema = schema;
self self
@ -65,14 +65,14 @@ impl ToolMeta {
} }
// ============================================================================= // =============================================================================
// ToolDefinition - Factory Type // ToolDefinition - ファクトリ型
// ============================================================================= // =============================================================================
/// Tool definition factory /// ツール定義ファクトリ
/// ///
/// When called, returns `(ToolMeta, Arc<dyn Tool>)`. /// 呼び出すと `(ToolMeta, Arc<dyn Tool>)` を返します。
/// Called once during Worker registration, and the meta information and instance /// Worker への登録時に一度だけ呼び出され、メタ情報とインスタンスが
/// are cached at session scope. /// セッションスコープでキャッシュされます。
/// ///
/// # Examples /// # Examples
/// ///
@ -93,15 +93,15 @@ pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Syn
// Tool trait // Tool trait
// ============================================================================= // =============================================================================
/// Trait for defining tools callable by LLM /// LLMから呼び出し可能なツールを定義するトレイト
/// ///
/// Tools are used by LLM to access external resources /// ツールはLLMが外部リソースにアクセスしたり、
/// or execute computations. /// 計算を実行したりするために使用します。
/// Can maintain state during the session. /// セッション中の状態を保持できます。
/// ///
/// # How to Implement /// # 実装方法
/// ///
/// Usually auto-implemented using the `#[tool_registry]` macro: /// 通常は`#[tool_registry]`マクロを使用して自動実装します:
/// ///
/// ```ignore /// ```ignore
/// #[tool_registry] /// #[tool_registry]
@ -112,11 +112,11 @@ pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Syn
/// } /// }
/// } /// }
/// ///
/// // Register /// // 登録
/// worker.register_tool(app.search_definition())?; /// worker.register_tool(app.search_definition())?;
/// ``` /// ```
/// ///
/// # Manual Implementation /// # 手動実装
/// ///
/// ```ignore /// ```ignore
/// use llm_worker::tool::{Tool, ToolError, ToolMeta, ToolDefinition}; /// use llm_worker::tool::{Tool, ToolError, ToolMeta, ToolDefinition};
@ -143,12 +143,12 @@ pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Syn
/// ``` /// ```
#[async_trait] #[async_trait]
pub trait Tool: Send + Sync { pub trait Tool: Send + Sync {
/// Execute the tool /// ツールを実行する
/// ///
/// # Arguments /// # Arguments
/// * `input_json` - JSON-formatted arguments generated by LLM /// * `input_json` - LLMが生成したJSON形式の引数
/// ///
/// # Returns /// # Returns
/// Result string from execution. This content is returned to LLM. /// 実行結果の文字列。この内容がLLMに返されます。
async fn execute(&self, input_json: &str) -> Result<String, ToolError>; async fn execute(&self, input_json: &str) -> Result<String, ToolError>;
} }

View File

@ -30,33 +30,33 @@ use crate::{
// Worker Error // Worker Error
// ============================================================================= // =============================================================================
/// Worker errors /// Workerエラー
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum WorkerError { pub enum WorkerError {
/// Client error /// クライアントエラー
#[error("Client error: {0}")] #[error("Client error: {0}")]
Client(#[from] ClientError), Client(#[from] ClientError),
/// Tool error /// ツールエラー
#[error("Tool error: {0}")] #[error("Tool error: {0}")]
Tool(#[from] ToolError), Tool(#[from] ToolError),
/// Hook error /// Hookエラー
#[error("Hook error: {0}")] #[error("Hook error: {0}")]
Hook(#[from] HookError), Hook(#[from] HookError),
/// Execution was aborted /// 処理が中断された
#[error("Aborted: {0}")] #[error("Aborted: {0}")]
Aborted(String), Aborted(String),
/// Cancelled by CancellationToken /// Cancellation Tokenによって中断された
#[error("Cancelled")] #[error("Cancelled")]
Cancelled, Cancelled,
/// Config warnings (unsupported options) /// 設定に関する警告(未サポートのオプション)
#[error("Config warnings: {}", .0.iter().map(|w| w.to_string()).collect::<Vec<_>>().join(", "))] #[error("Config warnings: {}", .0.iter().map(|w| w.to_string()).collect::<Vec<_>>().join(", "))]
ConfigWarnings(Vec<ConfigWarning>), ConfigWarnings(Vec<ConfigWarning>),
} }
/// Tool registration error /// ツール登録エラー
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ToolRegistryError { pub enum ToolRegistryError {
/// A tool with the same name is already registered /// 同名のツールが既に登録されている
#[error("Tool with name '{0}' already registered")] #[error("Tool with name '{0}' already registered")]
DuplicateName(String), DuplicateName(String),
} }
@ -65,10 +65,10 @@ pub enum ToolRegistryError {
// Worker Config // Worker Config
// ============================================================================= // =============================================================================
/// Worker configuration /// Worker設定
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct WorkerConfig { pub struct WorkerConfig {
// Reserved for future extensions (currently empty) // 将来の拡張用(現在は空)
_private: (), _private: (),
} }
@ -76,26 +76,26 @@ pub struct WorkerConfig {
// Worker Result Types // Worker Result Types
// ============================================================================= // =============================================================================
/// Worker execution result (status) /// Workerの実行結果(ステータス)
#[derive(Debug)] #[derive(Debug)]
pub enum WorkerResult { pub enum WorkerResult {
/// Completed (waiting for user input) /// 完了(ユーザー入力待ち状態)
Finished, Finished,
/// Paused (can be resumed) /// 一時停止(再開可能)
Paused, Paused,
} }
/// Internal: tool execution result /// 内部用: ツール実行結果
enum ToolExecutionResult { enum ToolExecutionResult {
Completed(Vec<ToolResult>), Completed(Vec<ToolResult>),
Paused, Paused,
} }
// ============================================================================= // =============================================================================
// Turn Control Callback Storage // ターン制御用コールバック保持
// ============================================================================= // =============================================================================
/// Callback for notifying turn events (type-erased) /// ターンイベントを通知するためのコールバック (型消去)
trait TurnNotifier: Send + Sync { trait TurnNotifier: Send + Sync {
fn on_turn_start(&self, turn: usize); fn on_turn_start(&self, turn: usize);
fn on_turn_end(&self, turn: usize); fn on_turn_end(&self, turn: usize);
@ -123,76 +123,76 @@ impl<S: WorkerSubscriber + 'static> TurnNotifier for SubscriberTurnNotifier<S> {
// Worker // Worker
// ============================================================================= // =============================================================================
/// Central component for managing LLM interactions /// LLMとの対話を管理する中心コンポーネント
/// ///
/// Receives input from the user, sends requests to the LLM, and /// ユーザーからの入力を受け取り、LLMにリクエストを送信し、
/// automatically executes tool calls if any, advancing the turn. /// ツール呼び出しがあれば自動的に実行してターンを進行させます。
/// ///
/// # State Transitions (Type-state) /// # 状態遷移Type-state
/// ///
/// - [`Mutable`]: Initial state. System prompt and history can be freely edited. /// - [`Mutable`]: 初期状態。システムプロンプトや履歴を自由に編集可能。
/// - [`CacheLocked`]: Cache-protected state. Transition via `lock()`. Prefix context is immutable. /// - [`CacheLocked`]: キャッシュ保護状態。`lock()`で遷移。前方コンテキストは不変。
/// ///
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```ignore
/// use llm_worker::{Worker, Message}; /// use llm_worker::{Worker, Message};
/// ///
/// // Create a Worker and register tools /// // Workerを作成してツールを登録
/// let mut worker = Worker::new(client) /// let mut worker = Worker::new(client)
/// .system_prompt("You are a helpful assistant."); /// .system_prompt("You are a helpful assistant.");
/// worker.register_tool(my_tool); /// worker.register_tool(my_tool);
/// ///
/// // Run the interaction /// // 対話を実行
/// let history = worker.run("Hello!").await?; /// let history = worker.run("Hello!").await?;
/// ``` /// ```
/// ///
/// # When Cache Protection is Needed /// # キャッシュ保護が必要な場合
/// ///
/// ```ignore /// ```ignore
/// let mut worker = Worker::new(client) /// let mut worker = Worker::new(client)
/// .system_prompt("..."); /// .system_prompt("...");
/// ///
/// // After setting history, lock to protect cache /// // 履歴を設定後、ロックしてキャッシュを保護
/// let mut locked = worker.lock(); /// let mut locked = worker.lock();
/// locked.run("user input").await?; /// locked.run("user input").await?;
/// ``` /// ```
pub struct Worker<C: LlmClient, S: WorkerState = Mutable> { pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
/// LLM client /// LLMクライアント
client: C, client: C,
/// Event timeline /// イベントタイムライン
timeline: Timeline, timeline: Timeline,
/// Text block collector (Timeline handler) /// テキストブロックコレクターTimeline用ハンドラ
text_block_collector: TextBlockCollector, text_block_collector: TextBlockCollector,
/// Tool call collector (Timeline handler) /// ツールコールコレクターTimeline用ハンドラ
tool_call_collector: ToolCallCollector, tool_call_collector: ToolCallCollector,
/// Registered tools (meta, instance) /// 登録されたツール (meta, instance)
tools: HashMap<String, (ToolMeta, Arc<dyn Tool>)>, tools: HashMap<String, (ToolMeta, Arc<dyn Tool>)>,
/// Hook registry /// Hook レジストリ
hooks: HookRegistry, hooks: HookRegistry,
/// System prompt /// システムプロンプト
system_prompt: Option<String>, system_prompt: Option<String>,
/// Message history (owned by Worker) /// メッセージ履歴Workerが所有
history: Vec<Message>, history: Vec<Message>,
/// History length at lock time (only meaningful in CacheLocked state) /// ロック時点での履歴長CacheLocked状態でのみ意味を持つ
locked_prefix_len: usize, locked_prefix_len: usize,
/// Turn count /// ターンカウント
turn_count: usize, turn_count: usize,
/// Turn notification callbacks /// ターン通知用のコールバック
turn_notifiers: Vec<Box<dyn TurnNotifier>>, turn_notifiers: Vec<Box<dyn TurnNotifier>>,
/// Request configuration (max_tokens, temperature, etc.) /// リクエスト設定max_tokens, temperature等
request_config: RequestConfig, request_config: RequestConfig,
/// Whether the previous run was interrupted /// 前回の実行が中断されたかどうか
last_run_interrupted: bool, last_run_interrupted: bool,
/// Cancel notification channel (for interrupting execution) /// キャンセル通知用チャネル(実行中断用)
cancel_tx: mpsc::Sender<()>, cancel_tx: mpsc::Sender<()>,
cancel_rx: mpsc::Receiver<()>, cancel_rx: mpsc::Receiver<()>,
/// State marker /// 状態マーカー
_state: PhantomData<S>, _state: PhantomData<S>,
} }
// ============================================================================= // =============================================================================
// Common Implementation (available in all states) // 共通実装(全状態で利用可能)
// ============================================================================= // =============================================================================
impl<C: LlmClient, S: WorkerState> Worker<C, S> { impl<C: LlmClient, S: WorkerState> Worker<C, S> {
@ -200,10 +200,10 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.last_run_interrupted = false; self.last_run_interrupted = false;
} }
/// Execute a turn /// ターンを実行
/// ///
/// Adds a new user message to history and sends a request to the LLM. /// 新しいユーザーメッセージを履歴に追加し、LLMにリクエストを送信する。
/// Automatically loops if there are tool calls. /// ツール呼び出しがある場合は自動的にループする。
pub async fn run( pub async fn run(
&mut self, &mut self,
user_input: impl Into<String>, user_input: impl Into<String>,
@ -247,17 +247,17 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
/// Register an event subscriber /// イベント購読者を登録する
/// ///
/// Registered subscribers receive streaming events from the LLM /// 登録したSubscriberは、LLMからのストリーミングイベントを
/// in real-time. Useful for streaming display to UI. /// リアルタイムで受信できます。UIへのストリーム表示などに利用します。
/// ///
/// # Available Events /// # 受信できるイベント
/// ///
/// - **Block events**: `on_text_block`, `on_tool_use_block` /// - **ブロックイベント**: `on_text_block`, `on_tool_use_block`
/// - **Meta events**: `on_usage`, `on_status`, `on_error` /// - **メタイベント**: `on_usage`, `on_status`, `on_error`
/// - **Completion events**: `on_text_complete`, `on_tool_call_complete` /// - **完了イベント**: `on_text_complete`, `on_tool_call_complete`
/// - **Turn control**: `on_turn_start`, `on_turn_end` /// - **ターン制御**: `on_turn_start`, `on_turn_end`
/// ///
/// # Examples /// # Examples
/// ///
@ -281,15 +281,15 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
pub fn subscribe<Sub: WorkerSubscriber + 'static>(&mut self, subscriber: Sub) { pub fn subscribe<Sub: WorkerSubscriber + 'static>(&mut self, subscriber: Sub) {
let subscriber = Arc::new(Mutex::new(subscriber)); let subscriber = Arc::new(Mutex::new(subscriber));
// Register TextBlock handler // TextBlock用ハンドラを登録
self.timeline self.timeline
.on_text_block(TextBlockSubscriberAdapter::new(subscriber.clone())); .on_text_block(TextBlockSubscriberAdapter::new(subscriber.clone()));
// Register ToolUseBlock handler // ToolUseBlock用ハンドラを登録
self.timeline self.timeline
.on_tool_use_block(ToolUseBlockSubscriberAdapter::new(subscriber.clone())); .on_tool_use_block(ToolUseBlockSubscriberAdapter::new(subscriber.clone()));
// Register meta handlers // Meta系ハンドラを登録
self.timeline self.timeline
.on_usage(UsageSubscriberAdapter::new(subscriber.clone())); .on_usage(UsageSubscriberAdapter::new(subscriber.clone()));
self.timeline self.timeline
@ -297,15 +297,15 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.timeline self.timeline
.on_error(ErrorSubscriberAdapter::new(subscriber.clone())); .on_error(ErrorSubscriberAdapter::new(subscriber.clone()));
// Register turn control callback // ターン制御用コールバックを登録
self.turn_notifiers self.turn_notifiers
.push(Box::new(SubscriberTurnNotifier { subscriber })); .push(Box::new(SubscriberTurnNotifier { subscriber }));
} }
/// Register a tool /// ツールを登録する
/// ///
/// Registered tools are automatically executed when called by the LLM. /// 登録されたツールはLLMからの呼び出しで自動的に実行されます。
/// Registering a tool with the same name will result in an error. /// 同名のツールを登録するとエラーになります。
/// ///
/// # Examples /// # Examples
/// ///
@ -327,7 +327,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
Ok(()) Ok(())
} }
/// Register multiple tools /// 複数のツールを登録
pub fn register_tools( pub fn register_tools(
&mut self, &mut self,
factories: impl IntoIterator<Item = ToolDefinition>, factories: impl IntoIterator<Item = ToolDefinition>,
@ -338,68 +338,68 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
Ok(()) Ok(())
} }
/// Add an on_prompt_submit Hook /// on_prompt_submit Hookを追加する
/// ///
/// Called immediately after receiving a user message in `run()`. /// `run()` でユーザーメッセージを受け取った直後に呼び出される。
pub fn add_on_prompt_submit_hook(&mut self, hook: impl Hook<OnPromptSubmit> + 'static) { pub fn add_on_prompt_submit_hook(&mut self, hook: impl Hook<OnPromptSubmit> + 'static) {
self.hooks.on_prompt_submit.push(Box::new(hook)); self.hooks.on_prompt_submit.push(Box::new(hook));
} }
/// Add a pre_llm_request Hook /// pre_llm_request Hookを追加する
/// ///
/// Called before sending an LLM request for each turn. /// 各ターンのLLMリクエスト送信前に呼び出される。
pub fn add_pre_llm_request_hook(&mut self, hook: impl Hook<PreLlmRequest> + 'static) { pub fn add_pre_llm_request_hook(&mut self, hook: impl Hook<PreLlmRequest> + 'static) {
self.hooks.pre_llm_request.push(Box::new(hook)); self.hooks.pre_llm_request.push(Box::new(hook));
} }
/// Add a pre_tool_call Hook /// pre_tool_call Hookを追加する
pub fn add_pre_tool_call_hook(&mut self, hook: impl Hook<PreToolCall> + 'static) { pub fn add_pre_tool_call_hook(&mut self, hook: impl Hook<PreToolCall> + 'static) {
self.hooks.pre_tool_call.push(Box::new(hook)); self.hooks.pre_tool_call.push(Box::new(hook));
} }
/// Add a post_tool_call Hook /// post_tool_call Hookを追加する
pub fn add_post_tool_call_hook(&mut self, hook: impl Hook<PostToolCall> + 'static) { pub fn add_post_tool_call_hook(&mut self, hook: impl Hook<PostToolCall> + 'static) {
self.hooks.post_tool_call.push(Box::new(hook)); self.hooks.post_tool_call.push(Box::new(hook));
} }
/// Add an on_turn_end Hook /// on_turn_end Hookを追加する
pub fn add_on_turn_end_hook(&mut self, hook: impl Hook<OnTurnEnd> + 'static) { pub fn add_on_turn_end_hook(&mut self, hook: impl Hook<OnTurnEnd> + 'static) {
self.hooks.on_turn_end.push(Box::new(hook)); self.hooks.on_turn_end.push(Box::new(hook));
} }
/// Add an on_abort Hook /// on_abort Hookを追加する
pub fn add_on_abort_hook(&mut self, hook: impl Hook<OnAbort> + 'static) { pub fn add_on_abort_hook(&mut self, hook: impl Hook<OnAbort> + 'static) {
self.hooks.on_abort.push(Box::new(hook)); self.hooks.on_abort.push(Box::new(hook));
} }
/// Get a mutable reference to the timeline (for additional handler registration) /// タイムラインへの可変参照を取得(追加ハンドラ登録用)
pub fn timeline_mut(&mut self) -> &mut Timeline { pub fn timeline_mut(&mut self) -> &mut Timeline {
&mut self.timeline &mut self.timeline
} }
/// Get a reference to the history /// 履歴への参照を取得
pub fn history(&self) -> &[Message] { pub fn history(&self) -> &[Message] {
&self.history &self.history
} }
/// Get a reference to the system prompt /// システムプロンプトへの参照を取得
pub fn get_system_prompt(&self) -> Option<&str> { pub fn get_system_prompt(&self) -> Option<&str> {
self.system_prompt.as_deref() self.system_prompt.as_deref()
} }
/// Get the current turn count /// 現在のターンカウントを取得
pub fn turn_count(&self) -> usize { pub fn turn_count(&self) -> usize {
self.turn_count self.turn_count
} }
/// Get a reference to the current request configuration /// 現在のリクエスト設定への参照を取得
pub fn request_config(&self) -> &RequestConfig { pub fn request_config(&self) -> &RequestConfig {
&self.request_config &self.request_config
} }
/// Set maximum tokens /// 最大トークン数を設定
/// ///
/// This setting is independent of cache lock and applies to each request. /// この設定はキャッシュロックとは独立しており、各リクエストに適用されます。
/// ///
/// # Examples /// # Examples
/// ///
@ -410,10 +410,10 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.request_config.max_tokens = Some(max_tokens); self.request_config.max_tokens = Some(max_tokens);
} }
/// Set temperature /// temperatureを設定
/// ///
/// Set in the range of 0.0 to 1.0 (or 2.0). /// 0.0から1.0または2.0)の範囲で設定します。
/// Lower values produce more deterministic output, higher values produce more diverse output. /// 低い値はより決定的な出力を、高い値はより多様な出力を生成します。
/// ///
/// # Examples /// # Examples
/// ///
@ -424,7 +424,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.request_config.temperature = Some(temperature); self.request_config.temperature = Some(temperature);
} }
/// Set top_p (nucleus sampling) /// top_pを設定nucleus sampling
/// ///
/// # Examples /// # Examples
/// ///
@ -435,9 +435,9 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.request_config.top_p = Some(top_p); self.request_config.top_p = Some(top_p);
} }
/// Set top_k /// top_kを設定
/// ///
/// Specifies the top k tokens to consider when selecting tokens. /// トークン選択時に考慮する上位k個のトークンを指定します。
/// ///
/// # Examples /// # Examples
/// ///
@ -448,7 +448,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.request_config.top_k = Some(top_k); self.request_config.top_k = Some(top_k);
} }
/// Add a stop sequence /// ストップシーケンスを追加
/// ///
/// # Examples /// # Examples
/// ///
@ -459,25 +459,25 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.request_config.stop_sequences.push(sequence.into()); self.request_config.stop_sequences.push(sequence.into());
} }
/// Clear stop sequences /// ストップシーケンスをクリア
pub fn clear_stop_sequences(&mut self) { pub fn clear_stop_sequences(&mut self) {
self.request_config.stop_sequences.clear(); self.request_config.stop_sequences.clear();
} }
/// Get the cancel notification sender /// キャンセル通知用Senderを取得する
pub fn cancel_sender(&self) -> mpsc::Sender<()> { pub fn cancel_sender(&self) -> mpsc::Sender<()> {
self.cancel_tx.clone() self.cancel_tx.clone()
} }
/// Set request configuration at once /// リクエスト設定を一括で設定
pub fn set_request_config(&mut self, config: RequestConfig) { pub fn set_request_config(&mut self, config: RequestConfig) {
self.request_config = config; self.request_config = config;
} }
/// Cancel execution /// 実行をキャンセルする
/// ///
/// Interrupts currently running streaming or tool execution. /// 現在実行中のストリーミングやツール実行を中断します。
/// WorkerError::Cancelled is returned at the next event loop checkpoint. /// 次のイベントループのチェックポイントでWorkerError::Cancelledが返されます。
/// ///
/// # Examples /// # Examples
/// ///
@ -485,31 +485,31 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// use std::sync::Arc; /// use std::sync::Arc;
/// let worker = Arc::new(Mutex::new(Worker::new(client))); /// let worker = Arc::new(Mutex::new(Worker::new(client)));
/// ///
/// // Run in another thread /// // 別スレッドで実行
/// let worker_clone = worker.clone(); /// let worker_clone = worker.clone();
/// tokio::spawn(async move { /// tokio::spawn(async move {
/// let mut w = worker_clone.lock().unwrap(); /// let mut w = worker_clone.lock().unwrap();
/// w.run("Long task...").await /// w.run("Long task...").await
/// }); /// });
/// ///
/// // Cancel /// // キャンセル
/// worker.lock().unwrap().cancel(); /// worker.lock().unwrap().cancel();
/// ``` /// ```
pub fn cancel(&self) { pub fn cancel(&self) {
let _ = self.cancel_tx.try_send(()); let _ = self.cancel_tx.try_send(());
} }
/// Check if cancelled /// キャンセルされているかチェック
pub fn is_cancelled(&mut self) -> bool { pub fn is_cancelled(&mut self) -> bool {
self.try_cancelled() self.try_cancelled()
} }
/// Whether the previous run was interrupted /// 前回の実行が中断されたかどうか
pub fn last_run_interrupted(&self) -> bool { pub fn last_run_interrupted(&self) -> bool {
self.last_run_interrupted self.last_run_interrupted
} }
/// Generate list of ToolDefinitions for LLM from registered tools /// 登録されたツールからLLM用ToolDefinitionのリストを生成
fn build_tool_definitions(&self) -> Vec<LlmToolDefinition> { fn build_tool_definitions(&self) -> Vec<LlmToolDefinition> {
self.tools self.tools
.values() .values()
@ -521,34 +521,34 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
.collect() .collect()
} }
/// Build assistant message from text blocks and tool calls /// テキストブロックとツール呼び出しからアシスタントメッセージを構築
fn build_assistant_message( fn build_assistant_message(
&self, &self,
text_blocks: &[String], text_blocks: &[String],
tool_calls: &[ToolCall], tool_calls: &[ToolCall],
) -> Option<Message> { ) -> Option<Message> {
// Return None if no text or tool calls // テキストもツール呼び出しもない場合はNone
if text_blocks.is_empty() && tool_calls.is_empty() { if text_blocks.is_empty() && tool_calls.is_empty() {
return None; return None;
} }
// Simple text message if text only // テキストのみの場合はシンプルなテキストメッセージ
if tool_calls.is_empty() { if tool_calls.is_empty() {
let text = text_blocks.join(""); let text = text_blocks.join("");
return Some(Message::assistant(text)); return Some(Message::assistant(text));
} }
// Build as Parts if tool calls are present // ツール呼び出しがある場合は Parts として構築
let mut parts = Vec::new(); let mut parts = Vec::new();
// Add text parts // テキストパーツを追加
for text in text_blocks { for text in text_blocks {
if !text.is_empty() { if !text.is_empty() {
parts.push(ContentPart::Text { text: text.clone() }); parts.push(ContentPart::Text { text: text.clone() });
} }
} }
// Add tool call parts // ツール呼び出しパーツを追加
for call in tool_calls { for call in tool_calls {
parts.push(ContentPart::ToolUse { parts.push(ContentPart::ToolUse {
id: call.id.clone(), id: call.id.clone(),
@ -563,7 +563,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}) })
} }
/// Build a request /// リクエストを構築
fn build_request( fn build_request(
&self, &self,
tool_definitions: &[LlmToolDefinition], tool_definitions: &[LlmToolDefinition],
@ -571,14 +571,14 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
) -> Request { ) -> Request {
let mut request = Request::new(); let mut request = Request::new();
// Set system prompt // システムプロンプトを設定
if let Some(ref system) = self.system_prompt { if let Some(ref system) = self.system_prompt {
request = request.system(system); request = request.system(system);
} }
// Add messages // メッセージを追加
for msg in context { for msg in context {
// Convert Message to llm_client::Message // Message から llm_client::Message への変換
request = request.message(crate::llm_client::Message { request = request.message(crate::llm_client::Message {
role: match msg.role { role: match msg.role {
Role::User => crate::llm_client::Role::User, Role::User => crate::llm_client::Role::User,
@ -621,12 +621,12 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}); });
} }
// Add tool definitions // ツール定義を追加
for tool_def in tool_definitions { for tool_def in tool_definitions {
request = request.tool(tool_def.clone()); request = request.tool(tool_def.clone());
} }
// Apply request configuration // リクエスト設定を適用
request = request.config(self.request_config.clone()); request = request.config(self.request_config.clone());
request request
@ -634,7 +634,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// Hooks: on_prompt_submit /// Hooks: on_prompt_submit
/// ///
/// Called immediately after receiving a user message in `run()` (first time only). /// `run()` でユーザーメッセージを受け取った直後に呼び出される(最初だけ)。
async fn run_on_prompt_submit_hooks( async fn run_on_prompt_submit_hooks(
&self, &self,
message: &mut Message, message: &mut Message,
@ -653,7 +653,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// Hooks: pre_llm_request /// Hooks: pre_llm_request
/// ///
/// Called before sending an LLM request for each turn. /// 各ターンのLLMリクエスト送信前に呼び出される毎ターン
async fn run_pre_llm_request_hooks( async fn run_pre_llm_request_hooks(
&self, &self,
) -> Result<(PreLlmRequestResult, Vec<Message>), WorkerError> { ) -> Result<(PreLlmRequestResult, Vec<Message>), WorkerError> {
@ -717,7 +717,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
/// Check for pending tool calls (for resuming from Pause) /// 未実行のツール呼び出しがあるかチェックPauseからの復帰用
fn get_pending_tool_calls(&self) -> Option<Vec<ToolCall>> { fn get_pending_tool_calls(&self) -> Option<Vec<ToolCall>> {
let last_msg = self.history.last()?; let last_msg = self.history.last()?;
if last_msg.role != Role::Assistant { if last_msg.role != Role::Assistant {
@ -740,26 +740,26 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
if calls.is_empty() { None } else { Some(calls) } if calls.is_empty() { None } else { Some(calls) }
} }
/// Execute tools in parallel /// ツールを並列実行
/// ///
/// After running pre_tool_call hooks on all tools, /// 全てのツールに対してpre_tool_callフックを実行後、
/// executes approved tools in parallel and applies post_tool_call hooks to results. /// 許可されたツールを並列に実行し、結果にpost_tool_callフックを適用する。
async fn execute_tools( async fn execute_tools(
&mut self, &mut self,
tool_calls: Vec<ToolCall>, tool_calls: Vec<ToolCall>,
) -> Result<ToolExecutionResult, WorkerError> { ) -> Result<ToolExecutionResult, WorkerError> {
use futures::future::join_all; use futures::future::join_all;
// Map from tool call ID to (ToolCall, Meta, Tool) // ツール呼び出しIDから (ToolCall, Meta, Tool) へのマップ
// Retained because it's needed for PostToolCall hooks // PostToolCallフックで必要になるため保持する
let mut call_info_map = HashMap::new(); let mut call_info_map = HashMap::new();
// Phase 1: Apply pre_tool_call hooks (determine skip/abort) // Phase 1: pre_tool_call フックを適用(スキップ/中断を判定)
let mut approved_calls = Vec::new(); let mut approved_calls = Vec::new();
for mut tool_call in tool_calls { for mut tool_call in tool_calls {
// Get tool definition // ツール定義を取得
if let Some((meta, tool)) = self.tools.get(&tool_call.name) { if let Some((meta, tool)) = self.tools.get(&tool_call.name) {
// Create context // コンテキストを作成
let mut context = ToolCallContext { let mut context = ToolCallContext {
call: tool_call.clone(), call: tool_call.clone(),
meta: meta.clone(), meta: meta.clone(),
@ -789,10 +789,10 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
// Reflect changes made by hooks // フックで変更された内容を反映
tool_call = context.call; tool_call = context.call;
// Save to map (only if executing) // マップに保存(実行する場合のみ)
if !skip { if !skip {
call_info_map.insert( call_info_map.insert(
tool_call.id.clone(), tool_call.id.clone(),
@ -801,13 +801,13 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
approved_calls.push(tool_call); approved_calls.push(tool_call);
} }
} else { } else {
// Unknown tools go into approved list as-is (will error at execution) // 未知のツールはそのまま承認リストに入れる(実行時にエラーになる)
// Hooks are not applied (no Meta available) // Hookは適用しないMetaがないため
approved_calls.push(tool_call); approved_calls.push(tool_call);
} }
} }
// Phase 2: Execute approved tools in parallel (cancellable) // Phase 2: 許可されたツールを並列実行(キャンセル可能)
let futures: Vec<_> = approved_calls let futures: Vec<_> = approved_calls
.into_iter() .into_iter()
.map(|tool_call| { .map(|tool_call| {
@ -830,7 +830,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}) })
.collect(); .collect();
// Make tool execution cancellable // ツール実行をキャンセル可能にする
let mut results = tokio::select! { let mut results = tokio::select! {
results = join_all(futures) => results, results = join_all(futures) => results,
cancel = self.cancel_rx.recv() => { cancel = self.cancel_rx.recv() => {
@ -843,9 +843,9 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
}; };
// Phase 3: Apply post_tool_call hooks // Phase 3: post_tool_call フックを適用
for tool_result in &mut results { for tool_result in &mut results {
// Get saved information // 保存しておいた情報を取得
if let Some((tool_call, meta, tool)) = call_info_map.get(&tool_result.tool_use_id) { if let Some((tool_call, meta, tool)) = call_info_map.get(&tool_result.tool_use_id) {
let mut context = PostToolCallContext { let mut context = PostToolCallContext {
call: tool_call.clone(), call: tool_call.clone(),
@ -867,7 +867,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
} }
// Reflect hook-modified results // フックで変更された結果を反映
*tool_result = context.result; *tool_result = context.result;
} }
} }
@ -875,7 +875,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
Ok(ToolExecutionResult::Completed(results)) Ok(ToolExecutionResult::Completed(results))
} }
/// Internal turn execution logic /// 内部で使用するターン実行ロジック
async fn run_turn_loop(&mut self) -> Result<WorkerResult, WorkerError> { async fn run_turn_loop(&mut self) -> Result<WorkerResult, WorkerError> {
self.reset_interruption_state(); self.reset_interruption_state();
self.drain_cancel_queue(); self.drain_cancel_queue();
@ -910,7 +910,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
loop { loop {
// Check for cancellation // キャンセルチェック
if self.try_cancelled() { if self.try_cancelled() {
info!("Execution cancelled"); info!("Execution cancelled");
self.timeline.abort_current_block(); self.timeline.abort_current_block();
@ -918,7 +918,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
return Err(WorkerError::Cancelled); return Err(WorkerError::Cancelled);
} }
// Notify turn start // ターン開始を通知
let current_turn = self.turn_count; let current_turn = self.turn_count;
debug!(turn = current_turn, "Turn start"); debug!(turn = current_turn, "Turn start");
for notifier in &self.turn_notifiers { for notifier in &self.turn_notifiers {
@ -942,7 +942,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
PreLlmRequestResult::Continue => {} PreLlmRequestResult::Continue => {}
} }
// Build request // リクエスト構築
let request = self.build_request(&tool_definitions, &request_context); let request = self.build_request(&tool_definitions, &request_context);
debug!( debug!(
message_count = request.messages.len(), message_count = request.messages.len(),
@ -951,11 +951,11 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
"Sending request to LLM" "Sending request to LLM"
); );
// Stream processing // ストリーム処理
debug!("Starting stream..."); debug!("Starting stream...");
let mut event_count = 0; let mut event_count = 0;
// Get stream (cancellable) // ストリームを取得(キャンセル可能)
let mut stream = tokio::select! { let mut stream = tokio::select! {
stream_result = self.client.stream(request) => stream_result stream_result = self.client.stream(request) => stream_result
.inspect_err(|_| self.last_run_interrupted = true)?, .inspect_err(|_| self.last_run_interrupted = true)?,
@ -971,7 +971,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
loop { loop {
tokio::select! { tokio::select! {
// Receive event from stream // ストリームからイベントを受信
event_result = stream.next() => { event_result = stream.next() => {
match event_result { match event_result {
Some(result) => { Some(result) => {
@ -989,10 +989,10 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
let timeline_event: crate::timeline::event::Event = event.into(); let timeline_event: crate::timeline::event::Event = event.into();
self.timeline.dispatch(&timeline_event); self.timeline.dispatch(&timeline_event);
} }
None => break, // Stream ended None => break, // ストリーム終了
} }
} }
// Wait for cancellation // キャンセル待機
cancel = self.cancel_rx.recv() => { cancel = self.cancel_rx.recv() => {
if cancel.is_some() { if cancel.is_some() {
info!("Stream cancelled"); info!("Stream cancelled");
@ -1005,24 +1005,24 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
debug!(event_count = event_count, "Stream completed"); debug!(event_count = event_count, "Stream completed");
// Notify turn end // ターン終了を通知
for notifier in &self.turn_notifiers { for notifier in &self.turn_notifiers {
notifier.on_turn_end(current_turn); notifier.on_turn_end(current_turn);
} }
self.turn_count += 1; self.turn_count += 1;
// Get collected results // 収集結果を取得
let text_blocks = self.text_block_collector.take_collected(); let text_blocks = self.text_block_collector.take_collected();
let tool_calls = self.tool_call_collector.take_collected(); let tool_calls = self.tool_call_collector.take_collected();
// Add assistant message to history // アシスタントメッセージを履歴に追加
let assistant_message = self.build_assistant_message(&text_blocks, &tool_calls); let assistant_message = self.build_assistant_message(&text_blocks, &tool_calls);
if let Some(msg) = assistant_message { if let Some(msg) = assistant_message {
self.history.push(msg); self.history.push(msg);
} }
if tool_calls.is_empty() { if tool_calls.is_empty() {
// No tool calls → determine turn end // ツール呼び出しなし → ターン終了判定
let turn_result = self let turn_result = self
.run_on_turn_end_hooks() .run_on_turn_end_hooks()
.await .await
@ -1043,7 +1043,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
// Execute tools // ツール実行
match self.execute_tools(tool_calls).await { match self.execute_tools(tool_calls).await {
Ok(ToolExecutionResult::Paused) => { Ok(ToolExecutionResult::Paused) => {
self.last_run_interrupted = true; self.last_run_interrupted = true;
@ -1063,9 +1063,9 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
/// Resume execution (from Paused state) /// 実行を再開Pause状態からの復帰
/// ///
/// Resumes turn processing from current state without adding a new user message to history. /// 新しいユーザーメッセージを履歴に追加せず、現在の状態からターン処理を再開する。
pub async fn resume(&mut self) -> Result<WorkerResult, WorkerError> { pub async fn resume(&mut self) -> Result<WorkerResult, WorkerError> {
self.reset_interruption_state(); self.reset_interruption_state();
let result = self.run_turn_loop().await; let result = self.run_turn_loop().await;
@ -1074,18 +1074,18 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
// ============================================================================= // =============================================================================
// Mutable State-Specific Implementation // Mutable状態専用の実装
// ============================================================================= // =============================================================================
impl<C: LlmClient> Worker<C, Mutable> { impl<C: LlmClient> Worker<C, Mutable> {
/// Create a new Worker (in Mutable state) /// 新しいWorkerを作成Mutable状態
pub fn new(client: C) -> Self { pub fn new(client: C) -> Self {
let text_block_collector = TextBlockCollector::new(); let text_block_collector = TextBlockCollector::new();
let tool_call_collector = ToolCallCollector::new(); let tool_call_collector = ToolCallCollector::new();
let mut timeline = Timeline::new(); let mut timeline = Timeline::new();
let (cancel_tx, cancel_rx) = mpsc::channel(1); let (cancel_tx, cancel_rx) = mpsc::channel(1);
// Register collectors with Timeline // コレクターをTimelineに登録
timeline.on_text_block(text_block_collector.clone()); timeline.on_text_block(text_block_collector.clone());
timeline.on_tool_use_block(tool_call_collector.clone()); timeline.on_tool_use_block(tool_call_collector.clone());
@ -1109,18 +1109,18 @@ impl<C: LlmClient> Worker<C, Mutable> {
} }
} }
/// Set system prompt (builder pattern) /// システムプロンプトを設定(ビルダーパターン)
pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self { pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into()); self.system_prompt = Some(prompt.into());
self self
} }
/// Set system prompt (mutable reference version) /// システムプロンプトを設定(可変参照版)
pub fn set_system_prompt(&mut self, prompt: impl Into<String>) { pub fn set_system_prompt(&mut self, prompt: impl Into<String>) {
self.system_prompt = Some(prompt.into()); self.system_prompt = Some(prompt.into());
} }
/// Set maximum tokens (builder pattern) /// 最大トークン数を設定(ビルダーパターン)
/// ///
/// # Examples /// # Examples
/// ///
@ -1134,7 +1134,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
self self
} }
/// Set temperature (builder pattern) /// temperatureを設定ビルダーパターン
/// ///
/// # Examples /// # Examples
/// ///
@ -1147,25 +1147,25 @@ impl<C: LlmClient> Worker<C, Mutable> {
self self
} }
/// Set top_p (builder pattern) /// top_pを設定ビルダーパターン
pub fn top_p(mut self, top_p: f32) -> Self { pub fn top_p(mut self, top_p: f32) -> Self {
self.request_config.top_p = Some(top_p); self.request_config.top_p = Some(top_p);
self self
} }
/// Set top_k (builder pattern) /// top_kを設定ビルダーパターン
pub fn top_k(mut self, top_k: u32) -> Self { pub fn top_k(mut self, top_k: u32) -> Self {
self.request_config.top_k = Some(top_k); self.request_config.top_k = Some(top_k);
self self
} }
/// Add stop sequence (builder pattern) /// ストップシーケンスを追加(ビルダーパターン)
pub fn stop_sequence(mut self, sequence: impl Into<String>) -> Self { pub fn stop_sequence(mut self, sequence: impl Into<String>) -> Self {
self.request_config.stop_sequences.push(sequence.into()); self.request_config.stop_sequences.push(sequence.into());
self self
} }
/// Set request configuration at once (builder pattern) /// リクエスト設定をまとめて設定(ビルダーパターン)
/// ///
/// # Examples /// # Examples
/// ///
@ -1183,10 +1183,10 @@ impl<C: LlmClient> Worker<C, Mutable> {
self self
} }
/// Validate current configuration against the provider /// 現在の設定をプロバイダに対してバリデーションする
/// ///
/// Returns an error if there are unsupported settings. /// 未サポートの設定があればエラーを返す。
/// Call at the end of the chain to detect configuration issues early. /// チェーンの最後で呼び出すことで、設定の問題を早期に検出できる。
/// ///
/// # Examples /// # Examples
/// ///
@ -1194,12 +1194,12 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// let worker = Worker::new(client) /// let worker = Worker::new(client)
/// .temperature(0.7) /// .temperature(0.7)
/// .top_k(40) /// .top_k(40)
/// .validate()?; // Error if using OpenAI since top_k is not supported /// .validate()?; // OpenAIならtop_kがサポートされないためエラー
/// ``` /// ```
/// ///
/// # Returns /// # Returns
/// * `Ok(Self)` - Validation successful /// * `Ok(Self)` - バリデーション成功
/// * `Err(WorkerError::ConfigWarnings)` - Has unsupported settings /// * `Err(WorkerError::ConfigWarnings)` - 未サポートの設定がある
pub fn validate(self) -> Result<Self, WorkerError> { pub fn validate(self) -> Result<Self, WorkerError> {
let warnings = self.client.validate_config(&self.request_config); let warnings = self.client.validate_config(&self.request_config);
if warnings.is_empty() { if warnings.is_empty() {
@ -1209,55 +1209,55 @@ impl<C: LlmClient> Worker<C, Mutable> {
} }
} }
/// Get a mutable reference to history /// 履歴への可変参照を取得
/// ///
/// Available only in Mutable state. History can be freely edited. /// Mutable状態でのみ利用可能。履歴を自由に編集できる。
pub fn history_mut(&mut self) -> &mut Vec<Message> { pub fn history_mut(&mut self) -> &mut Vec<Message> {
&mut self.history &mut self.history
} }
/// Set history /// 履歴を設定
pub fn set_history(&mut self, messages: Vec<Message>) { pub fn set_history(&mut self, messages: Vec<Message>) {
self.history = messages; self.history = messages;
} }
/// Add a message to history (builder pattern) /// 履歴にメッセージを追加(ビルダーパターン)
pub fn with_message(mut self, message: Message) -> Self { pub fn with_message(mut self, message: Message) -> Self {
self.history.push(message); self.history.push(message);
self self
} }
/// Add a message to history /// 履歴にメッセージを追加
pub fn push_message(&mut self, message: Message) { pub fn push_message(&mut self, message: Message) {
self.history.push(message); self.history.push(message);
} }
/// Add multiple messages to history (builder pattern) /// 複数のメッセージを履歴に追加(ビルダーパターン)
pub fn with_messages(mut self, messages: impl IntoIterator<Item = Message>) -> Self { pub fn with_messages(mut self, messages: impl IntoIterator<Item = Message>) -> Self {
self.history.extend(messages); self.history.extend(messages);
self self
} }
/// Add multiple messages to history /// 複数のメッセージを履歴に追加
pub fn extend_history(&mut self, messages: impl IntoIterator<Item = Message>) { pub fn extend_history(&mut self, messages: impl IntoIterator<Item = Message>) {
self.history.extend(messages); self.history.extend(messages);
} }
/// Clear history /// 履歴をクリア
pub fn clear_history(&mut self) { pub fn clear_history(&mut self) {
self.history.clear(); self.history.clear();
} }
/// Apply configuration (reserved for future extensions) /// 設定を適用(将来の拡張用)
#[allow(dead_code)] #[allow(dead_code)]
pub fn config(self, _config: WorkerConfig) -> Self { pub fn config(self, _config: WorkerConfig) -> Self {
self self
} }
/// Lock and transition to CacheLocked state /// ロックしてCacheLocked状態へ遷移
/// ///
/// This operation fixes the current system prompt and history as a "committed prefix". /// この操作により、現在のシステムプロンプトと履歴が「確定済みプレフィックス」として
/// After this, only appending to history is allowed, ensuring cache hits. /// 固定される。以降は履歴への追記のみが可能となり、キャッシュヒットが保証される。
pub fn lock(self) -> Worker<C, CacheLocked> { pub fn lock(self) -> Worker<C, CacheLocked> {
let locked_prefix_len = self.history.len(); let locked_prefix_len = self.history.len();
Worker { Worker {
@ -1283,19 +1283,19 @@ impl<C: LlmClient> Worker<C, Mutable> {
} }
// ============================================================================= // =============================================================================
// CacheLocked State-Specific Implementation // CacheLocked状態専用の実装
// ============================================================================= // =============================================================================
impl<C: LlmClient> Worker<C, CacheLocked> { impl<C: LlmClient> Worker<C, CacheLocked> {
/// Get the prefix length at lock time /// ロック時点のプレフィックス長を取得
pub fn locked_prefix_len(&self) -> usize { pub fn locked_prefix_len(&self) -> usize {
self.locked_prefix_len self.locked_prefix_len
} }
/// Unlock and return to Mutable state /// ロックを解除してMutable状態へ戻す
/// ///
/// Note: After this operation, subsequent requests may not hit the cache. /// 注意: この操作を行うと、以降のリクエストでキャッシュがヒットしなくなる可能性がある。
/// Use only when you need to edit history. /// 履歴を編集する必要がある場合にのみ使用すること。
pub fn unlock(self) -> Worker<C, Mutable> { pub fn unlock(self) -> Worker<C, Mutable> {
Worker { Worker {
client: self.client, client: self.client,
@ -1320,5 +1320,5 @@ impl<C: LlmClient> Worker<C, CacheLocked> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// Basic tests only. Tests using LlmClient are done in integration tests. // 基本的なテストのみ。LlmClientを使ったテストは統合テストで行う。
} }

View File

@ -1,4 +1,4 @@
//! Anthropic fixture-based integration tests //! Anthropic フィクスチャベースの統合テスト
mod common; mod common;

View File

@ -1,4 +1,4 @@
//! Gemini fixture-based integration tests //! Gemini フィクスチャベースの統合テスト
mod common; mod common;

View File

@ -1,4 +1,4 @@
//! Ollama fixture-based integration tests //! Ollama フィクスチャベースの統合テスト
mod common; mod common;

View File

@ -1,4 +1,4 @@
//! OpenAI fixture-based integration tests //! OpenAI フィクスチャベースの統合テスト
mod common; mod common;

View File

@ -1,6 +1,6 @@
//! Parallel tool execution tests //! 並列ツール実行のテスト
//! //!
//! Verify that Worker executes multiple tools in parallel. //! Workerが複数のツールを並列に実行することを確認する。
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -22,7 +22,7 @@ use common::MockLlmClient;
// Parallel Execution Test Tools // Parallel Execution Test Tools
// ============================================================================= // =============================================================================
/// Tool that waits for a specified time before responding /// 一定時間待機してから応答するツール
#[derive(Clone)] #[derive(Clone)]
struct SlowTool { struct SlowTool {
name: String, name: String,
@ -43,7 +43,7 @@ impl SlowTool {
self.call_count.load(Ordering::SeqCst) self.call_count.load(Ordering::SeqCst)
} }
/// Create ToolDefinition /// ToolDefinition を作成
fn definition(&self) -> ToolDefinition { fn definition(&self) -> ToolDefinition {
let tool = self.clone(); let tool = self.clone();
Arc::new(move || { Arc::new(move || {
@ -71,13 +71,13 @@ impl Tool for SlowTool {
// Tests // Tests
// ============================================================================= // =============================================================================
/// Verify that multiple tools are executed in parallel /// 複数のツールが並列に実行されることを確認
/// ///
/// If each tool takes 100ms, sequential execution would take 300ms+, /// 各ツールが100msかかる場合、逐次実行なら300ms以上かかるが、
/// but parallel execution should complete in about 100ms. /// 並列実行なら100ms程度で完了するはず。
#[tokio::test] #[tokio::test]
async fn test_parallel_tool_execution() { async fn test_parallel_tool_execution() {
// Event sequence containing 3 tool calls // 3つのツール呼び出しを含むイベントシーケンス
let events = vec![ let events = vec![
Event::tool_use_start(0, "call_1", "slow_tool_1"), Event::tool_use_start(0, "call_1", "slow_tool_1"),
Event::tool_input_delta(0, r#"{}"#), Event::tool_input_delta(0, r#"{}"#),
@ -96,7 +96,7 @@ async fn test_parallel_tool_execution() {
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Each tool waits 100ms // 各ツールは100ms待機
let tool1 = SlowTool::new("slow_tool_1", 100); let tool1 = SlowTool::new("slow_tool_1", 100);
let tool2 = SlowTool::new("slow_tool_2", 100); let tool2 = SlowTool::new("slow_tool_2", 100);
let tool3 = SlowTool::new("slow_tool_3", 100); let tool3 = SlowTool::new("slow_tool_3", 100);
@ -113,13 +113,13 @@ async fn test_parallel_tool_execution() {
let _result = worker.run("Run all tools").await; let _result = worker.run("Run all tools").await;
let elapsed = start.elapsed(); let elapsed = start.elapsed();
// Verify all tools were called // 全ツールが呼び出されたことを確認
assert_eq!(tool1_clone.call_count(), 1, "Tool 1 should be called once"); assert_eq!(tool1_clone.call_count(), 1, "Tool 1 should be called once");
assert_eq!(tool2_clone.call_count(), 1, "Tool 2 should be called once"); assert_eq!(tool2_clone.call_count(), 1, "Tool 2 should be called once");
assert_eq!(tool3_clone.call_count(), 1, "Tool 3 should be called once"); assert_eq!(tool3_clone.call_count(), 1, "Tool 3 should be called once");
// Parallel execution should complete in under 200ms (sequential would be 300ms+) // 並列実行なら200ms以下で完了するはず逐次なら300ms以上
// Using 250ms as threshold with margin // マージン込みで250msをしきい値とする
assert!( assert!(
elapsed < Duration::from_millis(250), elapsed < Duration::from_millis(250),
"Parallel execution should complete in ~100ms, but took {:?}", "Parallel execution should complete in ~100ms, but took {:?}",
@ -129,7 +129,7 @@ async fn test_parallel_tool_execution() {
println!("Parallel execution completed in {:?}", elapsed); println!("Parallel execution completed in {:?}", elapsed);
} }
/// Hook: pre_tool_call - verify that skipped tools are not executed /// Hook: pre_tool_call でスキップされたツールは実行されないことを確認
#[tokio::test] #[tokio::test]
async fn test_before_tool_call_skip() { async fn test_before_tool_call_skip() {
let events = vec![ let events = vec![
@ -156,7 +156,7 @@ async fn test_before_tool_call_skip() {
worker.register_tool(allowed_tool.definition()).unwrap(); worker.register_tool(allowed_tool.definition()).unwrap();
worker.register_tool(blocked_tool.definition()).unwrap(); worker.register_tool(blocked_tool.definition()).unwrap();
// Hook to skip "blocked_tool" // "blocked_tool" をスキップするHook
struct BlockingHook; struct BlockingHook;
#[async_trait] #[async_trait]
@ -174,7 +174,7 @@ async fn test_before_tool_call_skip() {
let _result = worker.run("Test hook").await; let _result = worker.run("Test hook").await;
// allowed_tool is called, but blocked_tool is not // allowed_tool は呼び出されるが、blocked_tool は呼び出されない
assert_eq!( assert_eq!(
allowed_clone.call_count(), allowed_clone.call_count(),
1, 1,
@ -187,12 +187,12 @@ async fn test_before_tool_call_skip() {
); );
} }
/// Hook: post_tool_call - verify that results can be modified /// Hook: post_tool_call で結果が改変されることを確認
#[tokio::test] #[tokio::test]
async fn test_post_tool_call_modification() { async fn test_post_tool_call_modification() {
// Prepare responses for multiple requests // 複数リクエストに対応するレスポンスを準備
let client = MockLlmClient::with_responses(vec![ let client = MockLlmClient::with_responses(vec![
// First request: tool call // 1回目のリクエスト: ツール呼び出し
vec![ vec![
Event::tool_use_start(0, "call_1", "test_tool"), Event::tool_use_start(0, "call_1", "test_tool"),
Event::tool_input_delta(0, r#"{}"#), Event::tool_input_delta(0, r#"{}"#),
@ -201,7 +201,7 @@ async fn test_post_tool_call_modification() {
status: ResponseStatus::Completed, status: ResponseStatus::Completed,
}), }),
], ],
// Second request: text response after receiving tool result // 2回目のリクエスト: ツール結果を受けてテキストレスポンス
vec![ vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Done!"), Event::text_delta(0, "Done!"),
@ -235,7 +235,7 @@ async fn test_post_tool_call_modification() {
worker.register_tool(simple_tool_definition()).unwrap(); worker.register_tool(simple_tool_definition()).unwrap();
// Hook to modify results // 結果を改変するHook
struct ModifyingHook { struct ModifyingHook {
modified_content: Arc<std::sync::Mutex<Option<String>>>, modified_content: Arc<std::sync::Mutex<Option<String>>>,
} }
@ -261,7 +261,7 @@ async fn test_post_tool_call_modification() {
assert!(result.is_ok(), "Worker should complete: {:?}", result); assert!(result.is_ok(), "Worker should complete: {:?}", result);
// Verify hook was called and content was modified // Hookが呼ばれて内容が改変されたことを確認
let content = modified_content.lock().unwrap().clone(); let content = modified_content.lock().unwrap().clone();
assert!(content.is_some(), "Hook should have been called"); assert!(content.is_some(), "Hook should have been called");
assert!( assert!(

View File

@ -1,6 +1,6 @@
//! WorkerSubscriber tests //! WorkerSubscriberのテスト
//! //!
//! Tests for subscribing to events using WorkerSubscriber //! WorkerSubscriberを使ってイベントを購読するテスト
mod common; mod common;
@ -18,9 +18,9 @@ use llm_worker::timeline::{TextBlockEvent, ToolUseBlockEvent};
// Test Subscriber // Test Subscriber
// ============================================================================= // =============================================================================
/// Simple Subscriber implementation for testing /// テスト用のシンプルなSubscriber実装
struct TestSubscriber { struct TestSubscriber {
// Recording buffers // 記録用のバッファ
text_deltas: Arc<Mutex<Vec<String>>>, text_deltas: Arc<Mutex<Vec<String>>>,
text_completes: Arc<Mutex<Vec<String>>>, text_completes: Arc<Mutex<Vec<String>>>,
tool_call_completes: Arc<Mutex<Vec<ToolCall>>>, tool_call_completes: Arc<Mutex<Vec<ToolCall>>>,
@ -60,7 +60,7 @@ impl WorkerSubscriber for TestSubscriber {
} }
fn on_tool_use_block(&mut self, _scope: &mut (), _event: &ToolUseBlockEvent) { fn on_tool_use_block(&mut self, _scope: &mut (), _event: &ToolUseBlockEvent) {
// Process as needed // 必要に応じて処理
} }
fn on_tool_call_complete(&mut self, call: &ToolCall) { fn on_tool_call_complete(&mut self, call: &ToolCall) {
@ -76,7 +76,7 @@ impl WorkerSubscriber for TestSubscriber {
} }
fn on_error(&mut self, _event: &ErrorEvent) { fn on_error(&mut self, _event: &ErrorEvent) {
// Process as needed // 必要に応じて処理
} }
fn on_turn_start(&mut self, turn: usize) { fn on_turn_start(&mut self, turn: usize) {
@ -92,10 +92,10 @@ impl WorkerSubscriber for TestSubscriber {
// Tests // Tests
// ============================================================================= // =============================================================================
/// Verify that WorkerSubscriber correctly receives text block events /// WorkerSubscriberがテキストブロックイベントを正しく受け取ることを確認
#[tokio::test] #[tokio::test]
async fn test_subscriber_text_block_events() { async fn test_subscriber_text_block_events() {
// Event sequence containing text response // テキストレスポンスを含むイベントシーケンス
let events = vec![ let events = vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Hello, "), Event::text_delta(0, "Hello, "),
@ -109,33 +109,33 @@ async fn test_subscriber_text_block_events() {
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Register Subscriber // Subscriberを登録
let subscriber = TestSubscriber::new(); let subscriber = TestSubscriber::new();
let text_deltas = subscriber.text_deltas.clone(); let text_deltas = subscriber.text_deltas.clone();
let text_completes = subscriber.text_completes.clone(); let text_completes = subscriber.text_completes.clone();
worker.subscribe(subscriber); worker.subscribe(subscriber);
// Execute // 実行
let result = worker.run("Greet me").await; let result = worker.run("Greet me").await;
assert!(result.is_ok(), "Worker should complete: {:?}", result); assert!(result.is_ok(), "Worker should complete: {:?}", result);
// Verify deltas were collected // デルタが収集されていることを確認
let deltas = text_deltas.lock().unwrap(); let deltas = text_deltas.lock().unwrap();
assert_eq!(deltas.len(), 2); assert_eq!(deltas.len(), 2);
assert_eq!(deltas[0], "Hello, "); assert_eq!(deltas[0], "Hello, ");
assert_eq!(deltas[1], "World!"); assert_eq!(deltas[1], "World!");
// Verify complete text was collected // 完了テキストが収集されていることを確認
let completes = text_completes.lock().unwrap(); let completes = text_completes.lock().unwrap();
assert_eq!(completes.len(), 1); assert_eq!(completes.len(), 1);
assert_eq!(completes[0], "Hello, World!"); assert_eq!(completes[0], "Hello, World!");
} }
/// Verify that WorkerSubscriber correctly receives tool call complete events /// WorkerSubscriberがツール呼び出し完了イベントを正しく受け取ることを確認
#[tokio::test] #[tokio::test]
async fn test_subscriber_tool_call_complete() { async fn test_subscriber_tool_call_complete() {
// Event sequence containing tool call // ツール呼び出しを含むイベントシーケンス
let events = vec![ let events = vec![
Event::tool_use_start(0, "call_123", "get_weather"), Event::tool_use_start(0, "call_123", "get_weather"),
Event::tool_input_delta(0, r#"{"city":"#), Event::tool_input_delta(0, r#"{"city":"#),
@ -149,15 +149,15 @@ async fn test_subscriber_tool_call_complete() {
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Register Subscriber // Subscriberを登録
let subscriber = TestSubscriber::new(); let subscriber = TestSubscriber::new();
let tool_call_completes = subscriber.tool_call_completes.clone(); let tool_call_completes = subscriber.tool_call_completes.clone();
worker.subscribe(subscriber); worker.subscribe(subscriber);
// Execute // 実行
let _ = worker.run("Weather please").await; let _ = worker.run("Weather please").await;
// Verify tool call complete was collected // ツール呼び出し完了が収集されていることを確認
let completes = tool_call_completes.lock().unwrap(); let completes = tool_call_completes.lock().unwrap();
assert_eq!(completes.len(), 1); assert_eq!(completes.len(), 1);
assert_eq!(completes[0].name, "get_weather"); assert_eq!(completes[0].name, "get_weather");
@ -165,7 +165,7 @@ async fn test_subscriber_tool_call_complete() {
assert_eq!(completes[0].input["city"], "Tokyo"); assert_eq!(completes[0].input["city"], "Tokyo");
} }
/// Verify that WorkerSubscriber correctly receives turn events /// WorkerSubscriberがターンイベントを正しく受け取ることを確認
#[tokio::test] #[tokio::test]
async fn test_subscriber_turn_events() { async fn test_subscriber_turn_events() {
let events = vec![ let events = vec![
@ -180,29 +180,29 @@ async fn test_subscriber_turn_events() {
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Register Subscriber // Subscriberを登録
let subscriber = TestSubscriber::new(); let subscriber = TestSubscriber::new();
let turn_starts = subscriber.turn_starts.clone(); let turn_starts = subscriber.turn_starts.clone();
let turn_ends = subscriber.turn_ends.clone(); let turn_ends = subscriber.turn_ends.clone();
worker.subscribe(subscriber); worker.subscribe(subscriber);
// Execute // 実行
let result = worker.run("Do something").await; let result = worker.run("Do something").await;
assert!(result.is_ok()); assert!(result.is_ok());
// Verify turn events were collected // ターンイベントが収集されていることを確認
let starts = turn_starts.lock().unwrap(); let starts = turn_starts.lock().unwrap();
let ends = turn_ends.lock().unwrap(); let ends = turn_ends.lock().unwrap();
assert_eq!(starts.len(), 1); assert_eq!(starts.len(), 1);
assert_eq!(starts[0], 0); // First turn assert_eq!(starts[0], 0); // 最初のターン
assert_eq!(ends.len(), 1); assert_eq!(ends.len(), 1);
assert_eq!(ends[0], 0); assert_eq!(ends[0], 0);
} }
/// Verify that WorkerSubscriber correctly receives Usage events /// WorkerSubscriberがUsageイベントを正しく受け取ることを確認
#[tokio::test] #[tokio::test]
async fn test_subscriber_usage_events() { async fn test_subscriber_usage_events() {
let events = vec![ let events = vec![
@ -218,15 +218,15 @@ async fn test_subscriber_usage_events() {
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Register Subscriber // Subscriberを登録
let subscriber = TestSubscriber::new(); let subscriber = TestSubscriber::new();
let usage_events = subscriber.usage_events.clone(); let usage_events = subscriber.usage_events.clone();
worker.subscribe(subscriber); worker.subscribe(subscriber);
// Execute // 実行
let _ = worker.run("Hello").await; let _ = worker.run("Hello").await;
// Verify Usage events were collected // Usageイベントが収集されていることを確認
let usages = usage_events.lock().unwrap(); let usages = usage_events.lock().unwrap();
assert_eq!(usages.len(), 1); assert_eq!(usages.len(), 1);
assert_eq!(usages[0].input_tokens, Some(100)); assert_eq!(usages[0].input_tokens, Some(100));

View File

@ -1,11 +1,11 @@
//! Tool macro tests //! ツールマクロのテスト
//! //!
//! Verify the behavior of `#[tool_registry]` and `#[tool]` macros. //! `#[tool_registry]` と `#[tool]` マクロの動作を確認する。
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
// Imports needed for macro expansion // マクロ展開に必要なインポート
use schemars; use schemars;
use serde; use serde;
@ -15,7 +15,7 @@ use llm_worker_macros::tool_registry;
// Test: Basic Tool Generation // Test: Basic Tool Generation
// ============================================================================= // =============================================================================
/// Simple context struct /// シンプルなコンテキスト構造体
#[derive(Clone)] #[derive(Clone)]
struct SimpleContext { struct SimpleContext {
prefix: String, prefix: String,
@ -23,21 +23,21 @@ struct SimpleContext {
#[tool_registry] #[tool_registry]
impl SimpleContext { impl SimpleContext {
/// Add greeting to message /// メッセージに挨拶を追加する
/// ///
/// Returns the message with a prefix added. /// 指定されたメッセージにプレフィックスを付けて返します。
#[tool] #[tool]
async fn greet(&self, message: String) -> String { async fn greet(&self, message: String) -> String {
format!("{}: {}", self.prefix, message) format!("{}: {}", self.prefix, message)
} }
/// Add two numbers /// 二つの数を足す
#[tool] #[tool]
async fn add(&self, a: i32, b: i32) -> i32 { async fn add(&self, a: i32, b: i32) -> i32 {
a + b a + b
} }
/// Tool with no arguments /// 引数なしのツール
#[tool] #[tool]
async fn get_prefix(&self) -> String { async fn get_prefix(&self) -> String {
self.prefix.clone() self.prefix.clone()
@ -50,16 +50,16 @@ async fn test_basic_tool_generation() {
prefix: "Hello".to_string(), prefix: "Hello".to_string(),
}; };
// Get ToolDefinition from factory method // ファクトリメソッドでToolDefinitionを取得
let greet_definition = ctx.greet_definition(); let greet_definition = ctx.greet_definition();
// Call factory to get Meta and Tool // ファクトリを呼び出してMetaとToolを取得
let (meta, tool) = greet_definition(); let (meta, tool) = greet_definition();
// Verify meta information // メタ情報の確認
assert_eq!(meta.name, "greet"); assert_eq!(meta.name, "greet");
assert!( assert!(
meta.description.contains("Add greeting to message"), meta.description.contains("メッセージに挨拶を追加する"),
"Description should contain doc comment: {}", "Description should contain doc comment: {}",
meta.description meta.description
); );
@ -73,7 +73,7 @@ async fn test_basic_tool_generation() {
serde_json::to_string_pretty(&meta.input_schema).unwrap() serde_json::to_string_pretty(&meta.input_schema).unwrap()
); );
// Execution test // 実行テスト
let result = tool.execute(r#"{"message": "World"}"#).await; let result = tool.execute(r#"{"message": "World"}"#).await;
assert!(result.is_ok(), "Should execute successfully"); assert!(result.is_ok(), "Should execute successfully");
let output = result.unwrap(); let output = result.unwrap();
@ -107,7 +107,7 @@ async fn test_no_arguments() {
assert_eq!(meta.name, "get_prefix"); assert_eq!(meta.name, "get_prefix");
// Call with empty JSON object // 空のJSONオブジェクトで呼び出し
let result = tool.execute(r#"{}"#).await; let result = tool.execute(r#"{}"#).await;
assert!(result.is_ok()); assert!(result.is_ok());
let output = result.unwrap(); let output = result.unwrap();
@ -126,7 +126,7 @@ async fn test_invalid_arguments() {
let (_, tool) = ctx.greet_definition()(); let (_, tool) = ctx.greet_definition()();
// Invalid JSON // 不正なJSON
let result = tool.execute(r#"{"wrong_field": "value"}"#).await; let result = tool.execute(r#"{"wrong_field": "value"}"#).await;
assert!(result.is_err(), "Should fail with invalid arguments"); assert!(result.is_err(), "Should fail with invalid arguments");
} }
@ -149,7 +149,7 @@ impl std::fmt::Display for MyError {
#[tool_registry] #[tool_registry]
impl FallibleContext { impl FallibleContext {
/// Validate the given value /// 与えられた値を検証する
#[tool] #[tool]
async fn validate(&self, value: i32) -> Result<String, MyError> { async fn validate(&self, value: i32) -> Result<String, MyError> {
if value > 0 { if value > 0 {
@ -198,7 +198,7 @@ struct SyncContext {
#[tool_registry] #[tool_registry]
impl SyncContext { impl SyncContext {
/// Increment counter and return (non-async) /// カウンターをインクリメントして返す (非async)
#[tool] #[tool]
fn increment(&self) -> usize { fn increment(&self) -> usize {
self.counter.fetch_add(1, Ordering::SeqCst) + 1 self.counter.fetch_add(1, Ordering::SeqCst) + 1
@ -213,7 +213,7 @@ async fn test_sync_method() {
let (_, tool) = ctx.increment_definition()(); let (_, tool) = ctx.increment_definition()();
// Execute 3 times // 3回実行
let result1 = tool.execute(r#"{}"#).await; let result1 = tool.execute(r#"{}"#).await;
let result2 = tool.execute(r#"{}"#).await; let result2 = tool.execute(r#"{}"#).await;
let result3 = tool.execute(r#"{}"#).await; let result3 = tool.execute(r#"{}"#).await;
@ -222,7 +222,7 @@ async fn test_sync_method() {
assert!(result2.is_ok()); assert!(result2.is_ok());
assert!(result3.is_ok()); assert!(result3.is_ok());
// Counter should be 3 // カウンターは3になっているはず
assert_eq!(ctx.counter.load(Ordering::SeqCst), 3); assert_eq!(ctx.counter.load(Ordering::SeqCst), 3);
} }
@ -236,7 +236,7 @@ async fn test_tool_meta_immutability() {
prefix: "Test".to_string(), prefix: "Test".to_string(),
}; };
// Verify same meta info is returned on multiple calls // 2回取得しても同じメタ情報が得られることを確認
let (meta1, _) = ctx.greet_definition()(); let (meta1, _) = ctx.greet_definition()();
let (meta2, _) = ctx.greet_definition()(); let (meta2, _) = ctx.greet_definition()();

View File

@ -3,16 +3,16 @@ use llm_worker::{Worker, WorkerError};
#[test] #[test]
fn test_openai_top_k_warning() { fn test_openai_top_k_warning() {
// Create client with dummy key (validate_config doesn't make network calls, so safe) // ダミーキーでクライアント作成validate_configは通信しないため安全
let client = OpenAIClient::new("dummy-key", "gpt-4o"); let client = OpenAIClient::new("dummy-key", "gpt-4o");
// Create Worker with top_k set (OpenAI doesn't support top_k) // top_kを設定したWorkerを作成
let worker = Worker::new(client).top_k(50); let worker = Worker::new(client).top_k(50); // OpenAIはtop_k非対応
// Run validate() // validate()を実行
let result = worker.validate(); let result = worker.validate();
// Verify error is returned and ConfigWarnings is included // エラーが返り、ConfigWarningsが含まれていることを確認
match result { match result {
Err(WorkerError::ConfigWarnings(warnings)) => { Err(WorkerError::ConfigWarnings(warnings)) => {
assert_eq!(warnings.len(), 1); assert_eq!(warnings.len(), 1);
@ -28,12 +28,12 @@ fn test_openai_top_k_warning() {
fn test_openai_valid_config() { fn test_openai_valid_config() {
let client = OpenAIClient::new("dummy-key", "gpt-4o"); let client = OpenAIClient::new("dummy-key", "gpt-4o");
// Valid configuration (temperature only) // validな設定temperatureのみ
let worker = Worker::new(client).temperature(0.7); let worker = Worker::new(client).temperature(0.7);
// Run validate() // validate()を実行
let result = worker.validate(); let result = worker.validate();
// Verify success // 成功を確認
assert!(result.is_ok()); assert!(result.is_ok());
} }

View File

@ -1,7 +1,7 @@
//! Worker fixture-based integration tests //! Workerフィクスチャベースの統合テスト
//! //!
//! Tests Worker behavior using recorded API responses. //! 記録されたAPIレスポンスを使ってWorkerの動作をテストする。
//! Can run locally without API keys. //! APIキー不要でローカルで実行可能。
mod common; mod common;
@ -14,12 +14,12 @@ use common::MockLlmClient;
use llm_worker::Worker; use llm_worker::Worker;
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta}; use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta};
/// Fixture directory path /// フィクスチャディレクトリのパス
fn fixtures_dir() -> std::path::PathBuf { fn fixtures_dir() -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/anthropic") Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/anthropic")
} }
/// Simple test tool /// シンプルなテスト用ツール
#[derive(Clone)] #[derive(Clone)]
struct MockWeatherTool { struct MockWeatherTool {
call_count: Arc<AtomicUsize>, call_count: Arc<AtomicUsize>,
@ -61,13 +61,13 @@ impl Tool for MockWeatherTool {
async fn execute(&self, input_json: &str) -> Result<String, ToolError> { async fn execute(&self, input_json: &str) -> Result<String, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst); self.call_count.fetch_add(1, Ordering::SeqCst);
// Parse input // 入力をパース
let input: serde_json::Value = serde_json::from_str(input_json) let input: serde_json::Value = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(e.to_string()))?; .map_err(|e| ToolError::InvalidArgument(e.to_string()))?;
let city = input["city"].as_str().unwrap_or("Unknown"); let city = input["city"].as_str().unwrap_or("Unknown");
// Return mock response // モックのレスポンスを返す
Ok(format!("Weather in {}: Sunny, 22°C", city)) Ok(format!("Weather in {}: Sunny, 22°C", city))
} }
} }
@ -76,12 +76,12 @@ impl Tool for MockWeatherTool {
// Basic Fixture Tests // Basic Fixture Tests
// ============================================================================= // =============================================================================
/// Verify that MockLlmClient can correctly load events from JSONL fixture files /// MockLlmClientがJSONLフィクスチャファイルから正しくイベントをロードできることを確認
/// ///
/// Uses existing anthropic_*.jsonl files to verify events are parsed and loaded. /// 既存のanthropic_*.jsonlファイルを使用し、イベントがパース・ロードされることを検証する。
#[test] #[test]
fn test_mock_client_from_fixture() { fn test_mock_client_from_fixture() {
// Load existing fixture // 既存のフィクスチャをロード
let fixture_path = fixtures_dir().join("anthropic_1767624445.jsonl"); let fixture_path = fixtures_dir().join("anthropic_1767624445.jsonl");
if !fixture_path.exists() { if !fixture_path.exists() {
println!("Fixture not found, skipping test"); println!("Fixture not found, skipping test");
@ -93,14 +93,14 @@ fn test_mock_client_from_fixture() {
println!("Loaded {} events from fixture", client.event_count()); println!("Loaded {} events from fixture", client.event_count());
} }
/// Verify that MockLlmClient works correctly with directly specified event lists /// MockLlmClientが直接指定されたイベントリストで正しく動作することを確認
/// ///
/// Creates a client with programmatically constructed events instead of using fixture files. /// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。
#[test] #[test]
fn test_mock_client_from_events() { fn test_mock_client_from_events() {
use llm_worker::llm_client::event::Event; use llm_worker::llm_client::event::Event;
// Specify events directly // 直接イベントを指定
let events = vec![ let events = vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Hello!"), Event::text_delta(0, "Hello!"),
@ -115,10 +115,10 @@ fn test_mock_client_from_events() {
// Worker Tests with Fixtures // Worker Tests with Fixtures
// ============================================================================= // =============================================================================
/// Verify that Worker can correctly process simple text responses /// Workerがシンプルなテキストレスポンスを正しく処理できることを確認
/// ///
/// Uses simple_text.jsonl fixture to test scenarios without tool calls. /// simple_text.jsonlフィクスチャを使用し、ツール呼び出しなしのシナリオをテストする。
/// Skipped if fixture is not present. /// フィクスチャがない場合はスキップされる。
#[tokio::test] #[tokio::test]
async fn test_worker_simple_text_response() { async fn test_worker_simple_text_response() {
let fixture_path = fixtures_dir().join("simple_text.jsonl"); let fixture_path = fixtures_dir().join("simple_text.jsonl");
@ -131,16 +131,16 @@ async fn test_worker_simple_text_response() {
let client = MockLlmClient::from_fixture(&fixture_path).unwrap(); let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Send a simple message // シンプルなメッセージを送信
let result = worker.run("Hello").await; let result = worker.run("Hello").await;
assert!(result.is_ok(), "Worker should complete successfully"); assert!(result.is_ok(), "Worker should complete successfully");
} }
/// Verify that Worker can correctly process responses containing tool calls /// Workerがツール呼び出しを含むレスポンスを正しく処理できることを確認
/// ///
/// Uses tool_call.jsonl fixture to test that MockWeatherTool is called. /// tool_call.jsonlフィクスチャを使用し、MockWeatherToolが呼び出されることをテストする。
/// Sets max_turns=1 to prevent loop after tool execution. /// max_turns=1に設定し、ツール実行後のループを防止。
#[tokio::test] #[tokio::test]
async fn test_worker_tool_call() { async fn test_worker_tool_call() {
let fixture_path = fixtures_dir().join("tool_call.jsonl"); let fixture_path = fixtures_dir().join("tool_call.jsonl");
@ -153,32 +153,32 @@ async fn test_worker_tool_call() {
let client = MockLlmClient::from_fixture(&fixture_path).unwrap(); let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Register tool // ツールを登録
let weather_tool = MockWeatherTool::new(); let weather_tool = MockWeatherTool::new();
let tool_for_check = weather_tool.clone(); let tool_for_check = weather_tool.clone();
worker.register_tool(weather_tool.definition()).unwrap(); worker.register_tool(weather_tool.definition()).unwrap();
// Send message // メッセージを送信
let _result = worker.run("What's the weather in Tokyo?").await; let _result = worker.run("What's the weather in Tokyo?").await;
// Verify tool was called // ツールが呼び出されたことを確認
// Note: max_turns=1 so no request is sent after tool result // Note: max_turns=1なのでツール結果後のリクエストは送信されない
let call_count = tool_for_check.get_call_count(); let call_count = tool_for_check.get_call_count();
println!("Tool was called {} times", call_count); println!("Tool was called {} times", call_count);
// Tool should be called if fixture contains ToolUse // フィクスチャにToolUseが含まれていればツールが呼び出されるはず
// But ends after 1 turn due to max_turns=1 // ただしmax_turns=1なので1回で終了
} }
/// Verify that Worker works without fixture files /// fixtureファイルなしでWorkerが動作することを確認
/// ///
/// Constructs event sequence programmatically and passes to MockLlmClient. /// プログラムでイベントシーケンスを構築し、MockLlmClientに渡してテストする。
/// Useful when test independence is needed and external file dependency should be eliminated. /// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。
#[tokio::test] #[tokio::test]
async fn test_worker_with_programmatic_events() { async fn test_worker_with_programmatic_events() {
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
// Construct event sequence programmatically // プログラムでイベントシーケンスを構築
let events = vec![ let events = vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Hello, "), Event::text_delta(0, "Hello, "),
@ -197,16 +197,16 @@ async fn test_worker_with_programmatic_events() {
assert!(result.is_ok(), "Worker should complete successfully"); assert!(result.is_ok(), "Worker should complete successfully");
} }
/// Verify that ToolCallCollector correctly collects ToolCall from ToolUse block events /// ToolCallCollectorがToolUseブロックイベントから正しくToolCallを収集することを確認
/// ///
/// Dispatches events to Timeline and verifies ToolCallCollector /// Timelineにイベントをディスパッチし、ToolCallCollectorが
/// correctly extracts id, name, and input (JSON). /// id, name, inputJSONを正しく抽出できることを検証する。
#[tokio::test] #[tokio::test]
async fn test_tool_call_collector_integration() { async fn test_tool_call_collector_integration() {
use llm_worker::llm_client::event::Event; use llm_worker::llm_client::event::Event;
use llm_worker::timeline::{Timeline, ToolCallCollector}; use llm_worker::timeline::{Timeline, ToolCallCollector};
// Event sequence containing ToolUse block // ToolUseブロックを含むイベントシーケンス
let events = vec![ let events = vec![
Event::tool_use_start(0, "call_123", "get_weather"), Event::tool_use_start(0, "call_123", "get_weather"),
Event::tool_input_delta(0, r#"{"city":"#), Event::tool_input_delta(0, r#"{"city":"#),
@ -218,13 +218,13 @@ async fn test_tool_call_collector_integration() {
let mut timeline = Timeline::new(); let mut timeline = Timeline::new();
timeline.on_tool_use_block(collector.clone()); timeline.on_tool_use_block(collector.clone());
// Dispatch events // イベントをディスパッチ
for event in &events { for event in &events {
let timeline_event: llm_worker::timeline::event::Event = event.clone().into(); let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event); timeline.dispatch(&timeline_event);
} }
// Verify collected ToolCall // 収集されたToolCallを確認
let calls = collector.take_collected(); let calls = collector.take_collected();
assert_eq!(calls.len(), 1, "Should collect one tool call"); assert_eq!(calls.len(), 1, "Should collect one tool call");
assert_eq!(calls[0].name, "get_weather"); assert_eq!(calls[0].name, "get_weather");

View File

@ -1,7 +1,7 @@
//! Worker state management tests //! Worker状態管理のテスト
//! //!
//! Tests for state transitions using the Type-state pattern (Mutable/CacheLocked) //! Type-stateパターンMutable/CacheLockedによる状態遷移と
//! and state preservation between turns. //! ターン間の状態保持をテストする。
mod common; mod common;
@ -11,10 +11,10 @@ use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::{Message, MessageContent}; use llm_worker::{Message, MessageContent};
// ============================================================================= // =============================================================================
// Mutable State Tests // Mutable状態のテスト
// ============================================================================= // =============================================================================
/// Verify that system prompt can be set in Mutable state /// Mutable状態でシステムプロンプトを設定できることを確認
#[test] #[test]
fn test_mutable_set_system_prompt() { fn test_mutable_set_system_prompt() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -29,35 +29,35 @@ fn test_mutable_set_system_prompt() {
); );
} }
/// Verify that history can be freely edited in Mutable state /// Mutable状態で履歴を自由に編集できることを確認
#[test] #[test]
fn test_mutable_history_manipulation() { fn test_mutable_history_manipulation() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Initial state is empty // 初期状態は空
assert!(worker.history().is_empty()); assert!(worker.history().is_empty());
// Add to history // 履歴を追加
worker.push_message(Message::user("Hello")); worker.push_message(Message::user("Hello"));
worker.push_message(Message::assistant("Hi there!")); worker.push_message(Message::assistant("Hi there!"));
assert_eq!(worker.history().len(), 2); assert_eq!(worker.history().len(), 2);
// Mutable access to history // 履歴への可変アクセス
worker.history_mut().push(Message::user("How are you?")); worker.history_mut().push(Message::user("How are you?"));
assert_eq!(worker.history().len(), 3); assert_eq!(worker.history().len(), 3);
// Clear history // 履歴をクリア
worker.clear_history(); worker.clear_history();
assert!(worker.history().is_empty()); assert!(worker.history().is_empty());
// Set history // 履歴を設定
let messages = vec![Message::user("Test"), Message::assistant("Response")]; let messages = vec![Message::user("Test"), Message::assistant("Response")];
worker.set_history(messages); worker.set_history(messages);
assert_eq!(worker.history().len(), 2); assert_eq!(worker.history().len(), 2);
} }
/// Verify that Worker can be constructed using builder pattern /// ビルダーパターンでWorkerを構築できることを確認
#[test] #[test]
fn test_mutable_builder_pattern() { fn test_mutable_builder_pattern() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -74,7 +74,7 @@ fn test_mutable_builder_pattern() {
assert_eq!(worker.history().len(), 4); assert_eq!(worker.history().len(), 4);
} }
/// Verify that multiple messages can be added with extend_history /// extend_historyで複数メッセージを追加できることを確認
#[test] #[test]
fn test_mutable_extend_history() { fn test_mutable_extend_history() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -92,10 +92,10 @@ fn test_mutable_extend_history() {
} }
// ============================================================================= // =============================================================================
// State Transition Tests // 状態遷移テスト
// ============================================================================= // =============================================================================
/// Verify that lock() transitions from Mutable -> CacheLocked state /// lock()でMutable -> CacheLocked状態に遷移することを確認
#[test] #[test]
fn test_lock_transition() { fn test_lock_transition() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -105,16 +105,16 @@ fn test_lock_transition() {
worker.push_message(Message::user("Hello")); worker.push_message(Message::user("Hello"));
worker.push_message(Message::assistant("Hi")); worker.push_message(Message::assistant("Hi"));
// Lock // ロック
let locked_worker = worker.lock(); let locked_worker = worker.lock();
// History and system prompt are still accessible in CacheLocked state // CacheLocked状態でも履歴とシステムプロンプトにアクセス可能
assert_eq!(locked_worker.get_system_prompt(), Some("System")); assert_eq!(locked_worker.get_system_prompt(), Some("System"));
assert_eq!(locked_worker.history().len(), 2); assert_eq!(locked_worker.history().len(), 2);
assert_eq!(locked_worker.locked_prefix_len(), 2); assert_eq!(locked_worker.locked_prefix_len(), 2);
} }
/// Verify that unlock() transitions from CacheLocked -> Mutable state /// unlock()でCacheLocked -> Mutable状態に遷移することを確認
#[test] #[test]
fn test_unlock_transition() { fn test_unlock_transition() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -123,20 +123,20 @@ fn test_unlock_transition() {
worker.push_message(Message::user("Hello")); worker.push_message(Message::user("Hello"));
let locked_worker = worker.lock(); let locked_worker = worker.lock();
// Unlock // アンロック
let mut worker = locked_worker.unlock(); let mut worker = locked_worker.unlock();
// History operations are available again in Mutable state // Mutable状態に戻ったので履歴操作が可能
worker.push_message(Message::assistant("Hi")); worker.push_message(Message::assistant("Hi"));
worker.clear_history(); worker.clear_history();
assert!(worker.history().is_empty()); assert!(worker.history().is_empty());
} }
// ============================================================================= // =============================================================================
// Turn Execution and State Preservation Tests // ターン実行と状態保持のテスト
// ============================================================================= // =============================================================================
/// Verify that history is correctly updated after running a turn in Mutable state /// Mutable状態でターンを実行し、履歴が正しく更新されることを確認
#[tokio::test] #[tokio::test]
async fn test_mutable_run_updates_history() { async fn test_mutable_run_updates_history() {
let events = vec![ let events = vec![
@ -151,33 +151,33 @@ async fn test_mutable_run_updates_history() {
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Execute // 実行
let result = worker.run("Hi there").await; let result = worker.run("Hi there").await;
assert!(result.is_ok()); assert!(result.is_ok());
// History is updated // 履歴が更新されている
let history = worker.history(); let history = worker.history();
assert_eq!(history.len(), 2); // user + assistant assert_eq!(history.len(), 2); // user + assistant
// User message // ユーザーメッセージ
assert!(matches!( assert!(matches!(
&history[0].content, &history[0].content,
MessageContent::Text(t) if t == "Hi there" MessageContent::Text(t) if t == "Hi there"
)); ));
// Assistant message // アシスタントメッセージ
assert!(matches!( assert!(matches!(
&history[1].content, &history[1].content,
MessageContent::Text(t) if t == "Hello, I'm an assistant!" MessageContent::Text(t) if t == "Hello, I'm an assistant!"
)); ));
} }
/// Verify that history accumulates correctly over multiple turns in CacheLocked state /// CacheLocked状態で複数ターンを実行し、履歴が正しく累積することを確認
#[tokio::test] #[tokio::test]
async fn test_locked_multi_turn_history_accumulation() { async fn test_locked_multi_turn_history_accumulation() {
// Prepare responses for 2 requests // 2回のリクエストに対応するレスポンスを準備
let client = MockLlmClient::with_responses(vec![ let client = MockLlmClient::with_responses(vec![
// First response // 1回目のレスポンス
vec![ vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Nice to meet you!"), Event::text_delta(0, "Nice to meet you!"),
@ -186,7 +186,7 @@ async fn test_locked_multi_turn_history_accumulation() {
status: ResponseStatus::Completed, status: ResponseStatus::Completed,
}), }),
], ],
// Second response // 2回目のレスポンス
vec![ vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "I can help with that."), Event::text_delta(0, "I can help with that."),
@ -199,37 +199,37 @@ async fn test_locked_multi_turn_history_accumulation() {
let worker = Worker::new(client).system_prompt("You are helpful."); let worker = Worker::new(client).system_prompt("You are helpful.");
// Lock (after setting system prompt) // ロック(システムプロンプト設定後)
let mut locked_worker = worker.lock(); let mut locked_worker = worker.lock();
assert_eq!(locked_worker.locked_prefix_len(), 0); // No messages yet assert_eq!(locked_worker.locked_prefix_len(), 0); // メッセージはまだない
// Turn 1 // 1ターン目
let result1 = locked_worker.run("Hello!").await; let result1 = locked_worker.run("Hello!").await;
assert!(result1.is_ok()); assert!(result1.is_ok());
assert_eq!(locked_worker.history().len(), 2); // user + assistant assert_eq!(locked_worker.history().len(), 2); // user + assistant
// Turn 2 // 2ターン目
let result2 = locked_worker.run("Can you help me?").await; let result2 = locked_worker.run("Can you help me?").await;
assert!(result2.is_ok()); assert!(result2.is_ok());
assert_eq!(locked_worker.history().len(), 4); // 2 * (user + assistant) assert_eq!(locked_worker.history().len(), 4); // 2 * (user + assistant)
// Verify history contents // 履歴の内容を確認
let history = locked_worker.history(); let history = locked_worker.history();
// Turn 1 user message // 1ターン目のユーザーメッセージ
assert!(matches!(&history[0].content, MessageContent::Text(t) if t == "Hello!")); assert!(matches!(&history[0].content, MessageContent::Text(t) if t == "Hello!"));
// Turn 1 assistant message // 1ターン目のアシスタントメッセージ
assert!(matches!(&history[1].content, MessageContent::Text(t) if t == "Nice to meet you!")); assert!(matches!(&history[1].content, MessageContent::Text(t) if t == "Nice to meet you!"));
// Turn 2 user message // 2ターン目のユーザーメッセージ
assert!(matches!(&history[2].content, MessageContent::Text(t) if t == "Can you help me?")); assert!(matches!(&history[2].content, MessageContent::Text(t) if t == "Can you help me?"));
// Turn 2 assistant message // 2ターン目のアシスタントメッセージ
assert!(matches!(&history[3].content, MessageContent::Text(t) if t == "I can help with that.")); assert!(matches!(&history[3].content, MessageContent::Text(t) if t == "I can help with that."));
} }
/// Verify that locked_prefix_len correctly records history length at lock time /// locked_prefix_lenがロック時点の履歴長を正しく記録することを確認
#[tokio::test] #[tokio::test]
async fn test_locked_prefix_len_tracking() { async fn test_locked_prefix_len_tracking() {
let client = MockLlmClient::with_responses(vec![ let client = MockLlmClient::with_responses(vec![
@ -253,25 +253,25 @@ async fn test_locked_prefix_len_tracking() {
let mut worker = Worker::new(client); let mut worker = Worker::new(client);
// Add messages beforehand // 事前にメッセージを追加
worker.push_message(Message::user("Pre-existing message 1")); worker.push_message(Message::user("Pre-existing message 1"));
worker.push_message(Message::assistant("Pre-existing response 1")); worker.push_message(Message::assistant("Pre-existing response 1"));
assert_eq!(worker.history().len(), 2); assert_eq!(worker.history().len(), 2);
// Lock // ロック
let mut locked_worker = worker.lock(); let mut locked_worker = worker.lock();
assert_eq!(locked_worker.locked_prefix_len(), 2); // 2 messages at lock time assert_eq!(locked_worker.locked_prefix_len(), 2); // ロック時点で2メッセージ
// Execute turn // ターン実行
locked_worker.run("New message").await.unwrap(); locked_worker.run("New message").await.unwrap();
// History grows but locked_prefix_len remains unchanged // 履歴は増えるが、locked_prefix_lenは変わらない
assert_eq!(locked_worker.history().len(), 4); // 2 + 2 assert_eq!(locked_worker.history().len(), 4); // 2 + 2
assert_eq!(locked_worker.locked_prefix_len(), 2); // Unchanged assert_eq!(locked_worker.locked_prefix_len(), 2); // 変わらない
} }
/// Verify that turn count is correctly incremented /// ターンカウントが正しくインクリメントされることを確認
#[tokio::test] #[tokio::test]
async fn test_turn_count_increment() { async fn test_turn_count_increment() {
let client = MockLlmClient::with_responses(vec![ let client = MockLlmClient::with_responses(vec![
@ -304,7 +304,7 @@ async fn test_turn_count_increment() {
assert_eq!(worker.turn_count(), 2); assert_eq!(worker.turn_count(), 2);
} }
/// Verify that history can be edited after unlock and re-locked /// unlock後に履歴を編集し、再度lockできることを確認
#[tokio::test] #[tokio::test]
async fn test_unlock_edit_relock() { async fn test_unlock_edit_relock() {
let client = MockLlmClient::with_responses(vec![vec![ let client = MockLlmClient::with_responses(vec![vec![
@ -320,27 +320,27 @@ async fn test_unlock_edit_relock() {
.with_message(Message::user("Hello")) .with_message(Message::user("Hello"))
.with_message(Message::assistant("Hi")); .with_message(Message::assistant("Hi"));
// Lock -> Unlock // ロック -> アンロック
let locked = worker.lock(); let locked = worker.lock();
assert_eq!(locked.locked_prefix_len(), 2); assert_eq!(locked.locked_prefix_len(), 2);
let mut unlocked = locked.unlock(); let mut unlocked = locked.unlock();
// Edit history // 履歴を編集
unlocked.clear_history(); unlocked.clear_history();
unlocked.push_message(Message::user("Fresh start")); unlocked.push_message(Message::user("Fresh start"));
// Re-lock // 再ロック
let relocked = unlocked.lock(); let relocked = unlocked.lock();
assert_eq!(relocked.history().len(), 1); assert_eq!(relocked.history().len(), 1);
assert_eq!(relocked.locked_prefix_len(), 1); assert_eq!(relocked.locked_prefix_len(), 1);
} }
// ============================================================================= // =============================================================================
// System Prompt Preservation Tests // システムプロンプト保持のテスト
// ============================================================================= // =============================================================================
/// Verify that system prompt is preserved in CacheLocked state /// CacheLocked状態でもシステムプロンプトが保持されることを確認
#[test] #[test]
fn test_system_prompt_preserved_in_locked_state() { fn test_system_prompt_preserved_in_locked_state() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -356,7 +356,7 @@ fn test_system_prompt_preserved_in_locked_state() {
); );
} }
/// Verify that system prompt can be changed after unlock -> re-lock /// unlock -> 再lock でシステムプロンプトを変更できることを確認
#[test] #[test]
fn test_system_prompt_change_after_unlock() { fn test_system_prompt_change_after_unlock() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);