From 45c8457b71b5620118c810d3afaf329c2bd5839a Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 7 Jan 2026 22:15:29 +0900 Subject: [PATCH] feat: Cleaning up examples --- worker/examples/llm_client_anthropic.rs | 176 ------------------------ worker/examples/llm_client_gemini.rs | 176 ------------------------ worker/examples/timeline_basic.rs | 134 ------------------ worker/examples/worker_cli.rs | 78 ++++++++++- 4 files changed, 71 insertions(+), 493 deletions(-) delete mode 100644 worker/examples/llm_client_anthropic.rs delete mode 100644 worker/examples/llm_client_gemini.rs delete mode 100644 worker/examples/timeline_basic.rs diff --git a/worker/examples/llm_client_anthropic.rs b/worker/examples/llm_client_anthropic.rs deleted file mode 100644 index 64ee4cd..0000000 --- a/worker/examples/llm_client_anthropic.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! LLMクライアント + Timeline統合サンプル -//! -//! Anthropic Claude APIにリクエストを送信し、Timelineでイベントを処理するサンプル -//! -//! ## 使用方法 -//! -//! ```bash -//! # .envファイルにAPIキーを設定 -//! echo "ANTHROPIC_API_KEY=your-api-key" > .env -//! -//! # 実行 -//! cargo run --example llm_client_anthropic -//! ``` - -use std::sync::{Arc, Mutex}; - -use futures::StreamExt; -use worker::{ - Handler, TextBlockEvent, TextBlockKind, Timeline, ToolUseBlockEvent, ToolUseBlockKind, - UsageEvent, UsageKind, - llm_client::{LlmClient, Request, providers::anthropic::AnthropicClient}, -}; - -/// テキスト出力をリアルタイムで表示するハンドラー -struct PrintHandler; - -impl Handler for PrintHandler { - type Scope = (); - - fn on_event(&mut self, _scope: &mut (), event: &TextBlockEvent) { - match event { - TextBlockEvent::Start(_) => { - print!("\n🤖 Assistant: "); - } - TextBlockEvent::Delta(text) => { - print!("{}", text); - // 即時出力をフラッシュ - use std::io::Write; - std::io::stdout().flush().ok(); - } - TextBlockEvent::Stop(_) => { - println!("\n"); - } - } - } -} - -/// テキストを蓄積するハンドラー -struct TextCollector { - texts: Arc>>, -} - -impl Handler for TextCollector { - type Scope = String; - - fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) { - match event { - TextBlockEvent::Start(_) => {} - TextBlockEvent::Delta(text) => { - buffer.push_str(text); - } - TextBlockEvent::Stop(_) => { - let text = std::mem::take(buffer); - self.texts.lock().unwrap().push(text); - } - } - } -} - -/// ツール使用を検出するハンドラー -struct ToolUseDetector; - -impl Handler for ToolUseDetector { - type Scope = String; // JSON accumulator - - fn on_event(&mut self, json_buffer: &mut String, event: &ToolUseBlockEvent) { - match event { - ToolUseBlockEvent::Start(start) => { - println!("\n🔧 Tool Call: {} (id: {})", start.name, start.id); - } - ToolUseBlockEvent::InputJsonDelta(json) => { - json_buffer.push_str(json); - } - ToolUseBlockEvent::Stop(stop) => { - println!(" Arguments: {}", json_buffer); - println!(" Tool {} completed\n", stop.name); - } - } - } -} - -/// 使用量を追跡するハンドラー -struct UsageTracker { - total_input: Arc>, - total_output: Arc>, -} - -impl Handler for UsageTracker { - type Scope = (); - - fn on_event(&mut self, _scope: &mut (), event: &UsageEvent) { - if let Some(input) = event.input_tokens { - *self.total_input.lock().unwrap() += input; - } - if let Some(output) = event.output_tokens { - *self.total_output.lock().unwrap() += output; - } - } -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // APIキーを環境変数から取得 - let api_key = std::env::var("ANTHROPIC_API_KEY") - .expect("ANTHROPIC_API_KEY environment variable must be set"); - - println!("=== LLM Client + Timeline Integration Example ===\n"); - - // クライアントを作成 - let client = AnthropicClient::new(api_key, "claude-sonnet-4-20250514"); - - // 共有状態 - let collected_texts = Arc::new(Mutex::new(Vec::new())); - let total_input = Arc::new(Mutex::new(0u64)); - let total_output = Arc::new(Mutex::new(0u64)); - - // タイムラインを構築 - let mut timeline = Timeline::new(); - timeline - .on_text_block(PrintHandler) - .on_text_block(TextCollector { - texts: collected_texts.clone(), - }) - .on_tool_use_block(ToolUseDetector) - .on_usage(UsageTracker { - total_input: total_input.clone(), - total_output: total_output.clone(), - }); - - // リクエストを作成 - let request = Request::new() - .system("You are a helpful assistant. Be concise.") - .user("What is the capital of Japan? Answer in one sentence.") - .max_tokens(100); - - println!("📤 Sending request...\n"); - - // ストリーミングリクエストを送信 - let mut stream = client.stream(request).await?; - - // イベントを処理 - while let Some(result) = stream.next().await { - match result { - Ok(event) => { - timeline.dispatch(&event); - } - Err(e) => { - eprintln!("Error: {}", e); - break; - } - } - } - - // 結果を表示 - println!("=== Summary ==="); - println!( - "📊 Token Usage: {} input, {} output", - total_input.lock().unwrap(), - total_output.lock().unwrap() - ); - - let texts = collected_texts.lock().unwrap(); - println!("📝 Collected {} text block(s)", texts.len()); - - Ok(()) -} diff --git a/worker/examples/llm_client_gemini.rs b/worker/examples/llm_client_gemini.rs deleted file mode 100644 index 3c8fbe9..0000000 --- a/worker/examples/llm_client_gemini.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! LLMクライアント + Timeline統合サンプル (Gemini) -//! -//! Google Gemini APIにリクエストを送信し、Timelineでイベントを処理するサンプル -//! -//! ## 使用方法 -//! -//! ```bash -//! # .envファイルにAPIキーを設定 -//! echo "GEMINI_API_KEY=your-api-key" > .env -//! -//! # 実行 -//! cargo run --example llm_client_gemini -//! ``` - -use std::sync::{Arc, Mutex}; - -use futures::StreamExt; -use worker::{ - Handler, TextBlockEvent, TextBlockKind, Timeline, ToolUseBlockEvent, ToolUseBlockKind, - UsageEvent, UsageKind, - llm_client::{LlmClient, Request, providers::gemini::GeminiClient}, -}; - -/// テキスト出力をリアルタイムで表示するハンドラー -struct PrintHandler; - -impl Handler for PrintHandler { - type Scope = (); - - fn on_event(&mut self, _scope: &mut (), event: &TextBlockEvent) { - match event { - TextBlockEvent::Start(_) => { - print!("\n🤖 Assistant: "); - } - TextBlockEvent::Delta(text) => { - print!("{}", text); - // 即時出力をフラッシュ - use std::io::Write; - std::io::stdout().flush().ok(); - } - TextBlockEvent::Stop(_) => { - println!("\n"); - } - } - } -} - -/// テキストを蓄積するハンドラー -struct TextCollector { - texts: Arc>>, -} - -impl Handler for TextCollector { - type Scope = String; - - fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) { - match event { - TextBlockEvent::Start(_) => {} - TextBlockEvent::Delta(text) => { - buffer.push_str(text); - } - TextBlockEvent::Stop(_) => { - let text = std::mem::take(buffer); - self.texts.lock().unwrap().push(text); - } - } - } -} - -/// ツール使用を検出するハンドラー -struct ToolUseDetector; - -impl Handler for ToolUseDetector { - type Scope = String; // JSON accumulator - - fn on_event(&mut self, json_buffer: &mut String, event: &ToolUseBlockEvent) { - match event { - ToolUseBlockEvent::Start(start) => { - println!("\n🔧 Tool Call: {} (id: {})", start.name, start.id); - } - ToolUseBlockEvent::InputJsonDelta(json) => { - json_buffer.push_str(json); - } - ToolUseBlockEvent::Stop(stop) => { - println!(" Arguments: {}", json_buffer); - println!(" Tool {} completed\n", stop.name); - } - } - } -} - -/// 使用量を追跡するハンドラー -struct UsageTracker { - total_input: Arc>, - total_output: Arc>, -} - -impl Handler for UsageTracker { - type Scope = (); - - fn on_event(&mut self, _scope: &mut (), event: &UsageEvent) { - if let Some(input) = event.input_tokens { - *self.total_input.lock().unwrap() += input; - } - if let Some(output) = event.output_tokens { - *self.total_output.lock().unwrap() += output; - } - } -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // APIキーを環境変数から取得 - let api_key = - std::env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY environment variable must be set"); - - println!("=== Gemini LLM Client + Timeline Integration Example ===\n"); - - // クライアントを作成 - let client = GeminiClient::new(api_key, "gemini-2.0-flash"); - - // 共有状態 - let collected_texts = Arc::new(Mutex::new(Vec::new())); - let total_input = Arc::new(Mutex::new(0u64)); - let total_output = Arc::new(Mutex::new(0u64)); - - // タイムラインを構築 - let mut timeline = Timeline::new(); - timeline - .on_text_block(PrintHandler) - .on_text_block(TextCollector { - texts: collected_texts.clone(), - }) - .on_tool_use_block(ToolUseDetector) - .on_usage(UsageTracker { - total_input: total_input.clone(), - total_output: total_output.clone(), - }); - - // リクエストを作成 - let request = Request::new() - .system("You are a helpful assistant. Be concise.") - .user("What is the capital of Japan? Answer in one sentence.") - .max_tokens(100); - - println!("📤 Sending request...\n"); - - // ストリーミングリクエストを送信 - let mut stream = client.stream(request).await?; - - // イベントを処理 - while let Some(result) = stream.next().await { - match result { - Ok(event) => { - timeline.dispatch(&event); - } - Err(e) => { - eprintln!("Error: {}", e); - break; - } - } - } - - // 結果を表示 - println!("=== Summary ==="); - println!( - "📊 Token Usage: {} input, {} output", - total_input.lock().unwrap(), - total_output.lock().unwrap() - ); - - let texts = collected_texts.lock().unwrap(); - println!("📝 Collected {} text block(s)", texts.len()); - - Ok(()) -} diff --git a/worker/examples/timeline_basic.rs b/worker/examples/timeline_basic.rs deleted file mode 100644 index 7074a87..0000000 --- a/worker/examples/timeline_basic.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Timeline使用例 -//! -//! 設計ドキュメントに基づいたTimelineの使用パターンを示すサンプル - -use worker::{ - Event, Handler, TextBlockEvent, TextBlockKind, Timeline, ToolUseBlockEvent, ToolUseBlockKind, - UsageEvent, UsageKind, -}; - -fn main() { - // シミュレートされたイベントストリーム - let events = simulate_llm_response(); - - // Timelineを作成し、ハンドラーを登録 - let mut timeline = Timeline::new(); - - // Usage収集ハンドラー - timeline.on_usage(UsageAccumulator::new()); - - // テキスト収集ハンドラー - timeline.on_text_block(TextCollector::new()); - - // ツール呼び出し収集ハンドラー - timeline.on_tool_use_block(ToolCallCollector::new()); - - // イベントをディスパッチ - for event in &events { - timeline.dispatch(event); - } - - println!("Timeline example completed!"); - println!("Events processed: {}", events.len()); -} - -/// LLMレスポンスをシミュレート -fn simulate_llm_response() -> Vec { - vec![ - // テキストブロック - Event::text_block_start(0), - Event::text_delta(0, "Hello, "), - Event::text_delta(0, "I can help you with that."), - Event::text_block_stop(0, None), - // 使用量 - Event::usage(100, 50), - // ツール呼び出し - Event::tool_use_start(1, "call_abc123", "get_weather"), - Event::tool_input_delta(1, r#"{"city":"#), - Event::tool_input_delta(1, r#""Tokyo"}"#), - Event::tool_use_stop(1), - // 最終的な使用量 - Event::usage(100, 75), - ] -} - -// ============================================================================= -// Example Handlers (defined in example, not in library) -// ============================================================================= - -/// 使用量を累積するハンドラー -struct UsageAccumulator { - total_tokens: u64, -} - -impl UsageAccumulator { - fn new() -> Self { - Self { total_tokens: 0 } - } -} - -impl Handler for UsageAccumulator { - type Scope = (); - fn on_event(&mut self, _scope: &mut (), usage: &UsageEvent) { - self.total_tokens += usage.total_tokens.unwrap_or(0); - } -} - -/// テキストを収集するハンドラー -struct TextCollector { - results: Vec, -} - -impl TextCollector { - fn new() -> Self { - Self { - results: Vec::new(), - } - } -} - -impl Handler for TextCollector { - type Scope = String; - fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) { - match event { - TextBlockEvent::Start(_) => {} - TextBlockEvent::Delta(s) => buffer.push_str(s), - TextBlockEvent::Stop(_) => { - self.results.push(std::mem::take(buffer)); - } - } - } -} - -/// ツール呼び出しを収集するハンドラー -struct ToolCallCollector { - calls: Vec<(String, String)>, // (name, args) -} - -impl ToolCallCollector { - fn new() -> Self { - Self { calls: Vec::new() } - } -} - -#[derive(Default)] -struct ToolCallScope { - name: String, - args: String, -} - -impl Handler for ToolCallCollector { - type Scope = ToolCallScope; - fn on_event(&mut self, scope: &mut ToolCallScope, event: &ToolUseBlockEvent) { - match event { - ToolUseBlockEvent::Start(s) => scope.name = s.name.clone(), - ToolUseBlockEvent::InputJsonDelta(json) => scope.args.push_str(json), - ToolUseBlockEvent::Stop(_) => { - self.calls.push(( - std::mem::take(&mut scope.name), - std::mem::take(&mut scope.args), - )); - } - } - } -} diff --git a/worker/examples/worker_cli.rs b/worker/examples/worker_cli.rs index 8b6aea7..5866e7b 100644 --- a/worker/examples/worker_cli.rs +++ b/worker/examples/worker_cli.rs @@ -30,15 +30,18 @@ //! cargo run --example worker_cli -- --help //! ``` +use std::collections::HashMap; use std::io::{self, Write}; use std::sync::{Arc, Mutex}; +use async_trait::async_trait; use tracing::info; use tracing_subscriber::EnvFilter; use clap::{Parser, ValueEnum}; use worker::{ - Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind, Worker, + ControlFlow, Handler, HookError, TextBlockEvent, TextBlockKind, ToolResult, ToolUseBlockEvent, + ToolUseBlockKind, Worker, WorkerHook, llm_client::{ LlmClient, providers::{ @@ -224,26 +227,83 @@ impl Handler for StreamingPrinter { } /// ツール呼び出しを表示するハンドラー -struct ToolCallPrinter; +struct ToolCallPrinter { + call_names: Arc>>, +} + +impl ToolCallPrinter { + fn new(call_names: Arc>>) -> Self { + Self { call_names } + } +} + +#[derive(Default)] +struct ToolCallPrinterScope { + input_json: String, +} impl Handler for ToolCallPrinter { - type Scope = String; + type Scope = ToolCallPrinterScope; - fn on_event(&mut self, json_buffer: &mut String, event: &ToolUseBlockEvent) { + fn on_event(&mut self, scope: &mut Self::Scope, event: &ToolUseBlockEvent) { match event { ToolUseBlockEvent::Start(start) => { + scope.input_json.clear(); + self.call_names + .lock() + .unwrap() + .insert(start.id.clone(), start.name.clone()); println!("\n🔧 Calling tool: {}", start.name); } ToolUseBlockEvent::InputJsonDelta(json) => { - json_buffer.push_str(json); + scope.input_json.push_str(json); } ToolUseBlockEvent::Stop(_) => { - println!(" Args: {}", json_buffer); + if scope.input_json.is_empty() { + println!(" Args: {{}}"); + } else { + println!(" Args: {}", scope.input_json); + } + scope.input_json.clear(); } } } } +/// ツール実行結果を表示するHook +struct ToolResultPrinterHook { + call_names: Arc>>, +} + +impl ToolResultPrinterHook { + fn new(call_names: Arc>>) -> Self { + Self { call_names } + } +} + +#[async_trait] +impl WorkerHook for ToolResultPrinterHook { + async fn after_tool_call( + &self, + tool_result: &mut ToolResult, + ) -> Result { + let name = self + .call_names + .lock() + .unwrap() + .remove(&tool_result.tool_use_id) + .unwrap_or_else(|| tool_result.tool_use_id.clone()); + + if tool_result.is_error { + println!(" Result ({}): ❌ {}", name, tool_result.content); + } else { + println!(" Result ({}): ✅ {}", name, tool_result.content); + } + + Ok(ControlFlow::Continue) + } +} + // ============================================================================= // クライアント作成 // ============================================================================= @@ -371,6 +431,8 @@ async fn main() -> Result<(), Box> { // Worker作成 let mut worker = Worker::new(client); + let tool_call_names = Arc::new(Mutex::new(HashMap::new())); + // システムプロンプトを設定 if let Some(ref system_prompt) = args.system { worker.set_system_prompt(system_prompt); @@ -387,7 +449,9 @@ async fn main() -> Result<(), Box> { worker .timeline_mut() .on_text_block(StreamingPrinter::new()) - .on_tool_use_block(ToolCallPrinter); + .on_tool_use_block(ToolCallPrinter::new(tool_call_names.clone())); + + worker.add_hook(ToolResultPrinterHook::new(tool_call_names)); // 会話履歴 let mut history: Vec = Vec::new();