docs: Translate ja -> en
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! Anthropic フィクスチャベースの統合テスト
|
||||
//! Anthropic fixture-based integration tests
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Gemini フィクスチャベースの統合テスト
|
||||
//! Gemini fixture-based integration tests
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Ollama フィクスチャベースの統合テスト
|
||||
//! Ollama fixture-based integration tests
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! OpenAI フィクスチャベースの統合テスト
|
||||
//! OpenAI fixture-based integration tests
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 並列ツール実行のテスト
|
||||
//! Parallel tool execution tests
|
||||
//!
|
||||
//! Workerが複数のツールを並列に実行することを確認する。
|
||||
//! Verify that Worker executes multiple tools in parallel.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
@@ -22,7 +22,7 @@ use common::MockLlmClient;
|
||||
// Parallel Execution Test Tools
|
||||
// =============================================================================
|
||||
|
||||
/// 一定時間待機してから応答するツール
|
||||
/// Tool that waits for a specified time before responding
|
||||
#[derive(Clone)]
|
||||
struct SlowTool {
|
||||
name: String,
|
||||
@@ -43,7 +43,7 @@ impl SlowTool {
|
||||
self.call_count.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// ToolDefinition を作成
|
||||
/// Create ToolDefinition
|
||||
fn definition(&self) -> ToolDefinition {
|
||||
let tool = self.clone();
|
||||
Arc::new(move || {
|
||||
@@ -71,13 +71,13 @@ impl Tool for SlowTool {
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
||||
/// 複数のツールが並列に実行されることを確認
|
||||
/// Verify that multiple tools are executed in parallel
|
||||
///
|
||||
/// 各ツールが100msかかる場合、逐次実行なら300ms以上かかるが、
|
||||
/// 並列実行なら100ms程度で完了するはず。
|
||||
/// If each tool takes 100ms, sequential execution would take 300ms+,
|
||||
/// but parallel execution should complete in about 100ms.
|
||||
#[tokio::test]
|
||||
async fn test_parallel_tool_execution() {
|
||||
// 3つのツール呼び出しを含むイベントシーケンス
|
||||
// Event sequence containing 3 tool calls
|
||||
let events = vec![
|
||||
Event::tool_use_start(0, "call_1", "slow_tool_1"),
|
||||
Event::tool_input_delta(0, r#"{}"#),
|
||||
@@ -96,7 +96,7 @@ async fn test_parallel_tool_execution() {
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// 各ツールは100ms待機
|
||||
// Each tool waits 100ms
|
||||
let tool1 = SlowTool::new("slow_tool_1", 100);
|
||||
let tool2 = SlowTool::new("slow_tool_2", 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 elapsed = start.elapsed();
|
||||
|
||||
// 全ツールが呼び出されたことを確認
|
||||
// Verify all tools were called
|
||||
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!(tool3_clone.call_count(), 1, "Tool 3 should be called once");
|
||||
|
||||
// 並列実行なら200ms以下で完了するはず(逐次なら300ms以上)
|
||||
// マージン込みで250msをしきい値とする
|
||||
// Parallel execution should complete in under 200ms (sequential would be 300ms+)
|
||||
// Using 250ms as threshold with margin
|
||||
assert!(
|
||||
elapsed < Duration::from_millis(250),
|
||||
"Parallel execution should complete in ~100ms, but took {:?}",
|
||||
@@ -129,7 +129,7 @@ async fn test_parallel_tool_execution() {
|
||||
println!("Parallel execution completed in {:?}", elapsed);
|
||||
}
|
||||
|
||||
/// Hook: pre_tool_call でスキップされたツールは実行されないことを確認
|
||||
/// Hook: pre_tool_call - verify that skipped tools are not executed
|
||||
#[tokio::test]
|
||||
async fn test_before_tool_call_skip() {
|
||||
let events = vec![
|
||||
@@ -156,7 +156,7 @@ async fn test_before_tool_call_skip() {
|
||||
worker.register_tool(allowed_tool.definition()).unwrap();
|
||||
worker.register_tool(blocked_tool.definition()).unwrap();
|
||||
|
||||
// "blocked_tool" をスキップするHook
|
||||
// Hook to skip "blocked_tool"
|
||||
struct BlockingHook;
|
||||
|
||||
#[async_trait]
|
||||
@@ -174,7 +174,7 @@ async fn test_before_tool_call_skip() {
|
||||
|
||||
let _result = worker.run("Test hook").await;
|
||||
|
||||
// allowed_tool は呼び出されるが、blocked_tool は呼び出されない
|
||||
// allowed_tool is called, but blocked_tool is not
|
||||
assert_eq!(
|
||||
allowed_clone.call_count(),
|
||||
1,
|
||||
@@ -187,12 +187,12 @@ async fn test_before_tool_call_skip() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Hook: post_tool_call で結果が改変されることを確認
|
||||
/// Hook: post_tool_call - verify that results can be modified
|
||||
#[tokio::test]
|
||||
async fn test_post_tool_call_modification() {
|
||||
// 複数リクエストに対応するレスポンスを準備
|
||||
// Prepare responses for multiple requests
|
||||
let client = MockLlmClient::with_responses(vec![
|
||||
// 1回目のリクエスト: ツール呼び出し
|
||||
// First request: tool call
|
||||
vec![
|
||||
Event::tool_use_start(0, "call_1", "test_tool"),
|
||||
Event::tool_input_delta(0, r#"{}"#),
|
||||
@@ -201,7 +201,7 @@ async fn test_post_tool_call_modification() {
|
||||
status: ResponseStatus::Completed,
|
||||
}),
|
||||
],
|
||||
// 2回目のリクエスト: ツール結果を受けてテキストレスポンス
|
||||
// Second request: text response after receiving tool result
|
||||
vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "Done!"),
|
||||
@@ -235,7 +235,7 @@ async fn test_post_tool_call_modification() {
|
||||
|
||||
worker.register_tool(simple_tool_definition()).unwrap();
|
||||
|
||||
// 結果を改変するHook
|
||||
// Hook to modify results
|
||||
struct ModifyingHook {
|
||||
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);
|
||||
|
||||
// Hookが呼ばれて内容が改変されたことを確認
|
||||
// Verify hook was called and content was modified
|
||||
let content = modified_content.lock().unwrap().clone();
|
||||
assert!(content.is_some(), "Hook should have been called");
|
||||
assert!(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! WorkerSubscriberのテスト
|
||||
//! WorkerSubscriber tests
|
||||
//!
|
||||
//! WorkerSubscriberを使ってイベントを購読するテスト
|
||||
//! Tests for subscribing to events using WorkerSubscriber
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -18,9 +18,9 @@ use llm_worker::timeline::{TextBlockEvent, ToolUseBlockEvent};
|
||||
// Test Subscriber
|
||||
// =============================================================================
|
||||
|
||||
/// テスト用のシンプルなSubscriber実装
|
||||
/// Simple Subscriber implementation for testing
|
||||
struct TestSubscriber {
|
||||
// 記録用のバッファ
|
||||
// Recording buffers
|
||||
text_deltas: Arc<Mutex<Vec<String>>>,
|
||||
text_completes: Arc<Mutex<Vec<String>>>,
|
||||
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) {
|
||||
// 必要に応じて処理
|
||||
// Process as needed
|
||||
}
|
||||
|
||||
fn on_tool_call_complete(&mut self, call: &ToolCall) {
|
||||
@@ -76,7 +76,7 @@ impl WorkerSubscriber for TestSubscriber {
|
||||
}
|
||||
|
||||
fn on_error(&mut self, _event: &ErrorEvent) {
|
||||
// 必要に応じて処理
|
||||
// Process as needed
|
||||
}
|
||||
|
||||
fn on_turn_start(&mut self, turn: usize) {
|
||||
@@ -92,10 +92,10 @@ impl WorkerSubscriber for TestSubscriber {
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
||||
/// WorkerSubscriberがテキストブロックイベントを正しく受け取ることを確認
|
||||
/// Verify that WorkerSubscriber correctly receives text block events
|
||||
#[tokio::test]
|
||||
async fn test_subscriber_text_block_events() {
|
||||
// テキストレスポンスを含むイベントシーケンス
|
||||
// Event sequence containing text response
|
||||
let events = vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "Hello, "),
|
||||
@@ -109,33 +109,33 @@ async fn test_subscriber_text_block_events() {
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// Subscriberを登録
|
||||
// Register Subscriber
|
||||
let subscriber = TestSubscriber::new();
|
||||
let text_deltas = subscriber.text_deltas.clone();
|
||||
let text_completes = subscriber.text_completes.clone();
|
||||
worker.subscribe(subscriber);
|
||||
|
||||
// 実行
|
||||
// Execute
|
||||
let result = worker.run("Greet me").await;
|
||||
|
||||
assert!(result.is_ok(), "Worker should complete: {:?}", result);
|
||||
|
||||
// デルタが収集されていることを確認
|
||||
// Verify deltas were collected
|
||||
let deltas = text_deltas.lock().unwrap();
|
||||
assert_eq!(deltas.len(), 2);
|
||||
assert_eq!(deltas[0], "Hello, ");
|
||||
assert_eq!(deltas[1], "World!");
|
||||
|
||||
// 完了テキストが収集されていることを確認
|
||||
// Verify complete text was collected
|
||||
let completes = text_completes.lock().unwrap();
|
||||
assert_eq!(completes.len(), 1);
|
||||
assert_eq!(completes[0], "Hello, World!");
|
||||
}
|
||||
|
||||
/// WorkerSubscriberがツール呼び出し完了イベントを正しく受け取ることを確認
|
||||
/// Verify that WorkerSubscriber correctly receives tool call complete events
|
||||
#[tokio::test]
|
||||
async fn test_subscriber_tool_call_complete() {
|
||||
// ツール呼び出しを含むイベントシーケンス
|
||||
// Event sequence containing tool call
|
||||
let events = vec![
|
||||
Event::tool_use_start(0, "call_123", "get_weather"),
|
||||
Event::tool_input_delta(0, r#"{"city":"#),
|
||||
@@ -149,15 +149,15 @@ async fn test_subscriber_tool_call_complete() {
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// Subscriberを登録
|
||||
// Register Subscriber
|
||||
let subscriber = TestSubscriber::new();
|
||||
let tool_call_completes = subscriber.tool_call_completes.clone();
|
||||
worker.subscribe(subscriber);
|
||||
|
||||
// 実行
|
||||
// Execute
|
||||
let _ = worker.run("Weather please").await;
|
||||
|
||||
// ツール呼び出し完了が収集されていることを確認
|
||||
// Verify tool call complete was collected
|
||||
let completes = tool_call_completes.lock().unwrap();
|
||||
assert_eq!(completes.len(), 1);
|
||||
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");
|
||||
}
|
||||
|
||||
/// WorkerSubscriberがターンイベントを正しく受け取ることを確認
|
||||
/// Verify that WorkerSubscriber correctly receives turn events
|
||||
#[tokio::test]
|
||||
async fn test_subscriber_turn_events() {
|
||||
let events = vec![
|
||||
@@ -180,29 +180,29 @@ async fn test_subscriber_turn_events() {
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// Subscriberを登録
|
||||
// Register Subscriber
|
||||
let subscriber = TestSubscriber::new();
|
||||
let turn_starts = subscriber.turn_starts.clone();
|
||||
let turn_ends = subscriber.turn_ends.clone();
|
||||
worker.subscribe(subscriber);
|
||||
|
||||
// 実行
|
||||
// Execute
|
||||
let result = worker.run("Do something").await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
// ターンイベントが収集されていることを確認
|
||||
// Verify turn events were collected
|
||||
let starts = turn_starts.lock().unwrap();
|
||||
let ends = turn_ends.lock().unwrap();
|
||||
|
||||
assert_eq!(starts.len(), 1);
|
||||
assert_eq!(starts[0], 0); // 最初のターン
|
||||
assert_eq!(starts[0], 0); // First turn
|
||||
|
||||
assert_eq!(ends.len(), 1);
|
||||
assert_eq!(ends[0], 0);
|
||||
}
|
||||
|
||||
/// WorkerSubscriberがUsageイベントを正しく受け取ることを確認
|
||||
/// Verify that WorkerSubscriber correctly receives Usage events
|
||||
#[tokio::test]
|
||||
async fn test_subscriber_usage_events() {
|
||||
let events = vec![
|
||||
@@ -218,15 +218,15 @@ async fn test_subscriber_usage_events() {
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// Subscriberを登録
|
||||
// Register Subscriber
|
||||
let subscriber = TestSubscriber::new();
|
||||
let usage_events = subscriber.usage_events.clone();
|
||||
worker.subscribe(subscriber);
|
||||
|
||||
// 実行
|
||||
// Execute
|
||||
let _ = worker.run("Hello").await;
|
||||
|
||||
// Usageイベントが収集されていることを確認
|
||||
// Verify Usage events were collected
|
||||
let usages = usage_events.lock().unwrap();
|
||||
assert_eq!(usages.len(), 1);
|
||||
assert_eq!(usages[0].input_tokens, Some(100));
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! ツールマクロのテスト
|
||||
//! Tool macro tests
|
||||
//!
|
||||
//! `#[tool_registry]` と `#[tool]` マクロの動作を確認する。
|
||||
//! Verify the behavior of `#[tool_registry]` and `#[tool]` macros.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
// マクロ展開に必要なインポート
|
||||
// Imports needed for macro expansion
|
||||
use schemars;
|
||||
use serde;
|
||||
|
||||
@@ -15,7 +15,7 @@ use llm_worker_macros::tool_registry;
|
||||
// Test: Basic Tool Generation
|
||||
// =============================================================================
|
||||
|
||||
/// シンプルなコンテキスト構造体
|
||||
/// Simple context struct
|
||||
#[derive(Clone)]
|
||||
struct SimpleContext {
|
||||
prefix: String,
|
||||
@@ -23,21 +23,21 @@ struct SimpleContext {
|
||||
|
||||
#[tool_registry]
|
||||
impl SimpleContext {
|
||||
/// メッセージに挨拶を追加する
|
||||
/// Add greeting to message
|
||||
///
|
||||
/// 指定されたメッセージにプレフィックスを付けて返します。
|
||||
/// Returns the message with a prefix added.
|
||||
#[tool]
|
||||
async fn greet(&self, message: String) -> String {
|
||||
format!("{}: {}", self.prefix, message)
|
||||
}
|
||||
|
||||
/// 二つの数を足す
|
||||
/// Add two numbers
|
||||
#[tool]
|
||||
async fn add(&self, a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
/// 引数なしのツール
|
||||
/// Tool with no arguments
|
||||
#[tool]
|
||||
async fn get_prefix(&self) -> String {
|
||||
self.prefix.clone()
|
||||
@@ -50,16 +50,16 @@ async fn test_basic_tool_generation() {
|
||||
prefix: "Hello".to_string(),
|
||||
};
|
||||
|
||||
// ファクトリメソッドでToolDefinitionを取得
|
||||
// Get ToolDefinition from factory method
|
||||
let greet_definition = ctx.greet_definition();
|
||||
|
||||
// ファクトリを呼び出してMetaとToolを取得
|
||||
// Call factory to get Meta and Tool
|
||||
let (meta, tool) = greet_definition();
|
||||
|
||||
// メタ情報の確認
|
||||
// Verify meta information
|
||||
assert_eq!(meta.name, "greet");
|
||||
assert!(
|
||||
meta.description.contains("メッセージに挨拶を追加する"),
|
||||
meta.description.contains("Add greeting to message"),
|
||||
"Description should contain doc comment: {}",
|
||||
meta.description
|
||||
);
|
||||
@@ -73,7 +73,7 @@ async fn test_basic_tool_generation() {
|
||||
serde_json::to_string_pretty(&meta.input_schema).unwrap()
|
||||
);
|
||||
|
||||
// 実行テスト
|
||||
// Execution test
|
||||
let result = tool.execute(r#"{"message": "World"}"#).await;
|
||||
assert!(result.is_ok(), "Should execute successfully");
|
||||
let output = result.unwrap();
|
||||
@@ -107,7 +107,7 @@ async fn test_no_arguments() {
|
||||
|
||||
assert_eq!(meta.name, "get_prefix");
|
||||
|
||||
// 空のJSONオブジェクトで呼び出し
|
||||
// Call with empty JSON object
|
||||
let result = tool.execute(r#"{}"#).await;
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
@@ -126,7 +126,7 @@ async fn test_invalid_arguments() {
|
||||
|
||||
let (_, tool) = ctx.greet_definition()();
|
||||
|
||||
// 不正なJSON
|
||||
// Invalid JSON
|
||||
let result = tool.execute(r#"{"wrong_field": "value"}"#).await;
|
||||
assert!(result.is_err(), "Should fail with invalid arguments");
|
||||
}
|
||||
@@ -149,7 +149,7 @@ impl std::fmt::Display for MyError {
|
||||
|
||||
#[tool_registry]
|
||||
impl FallibleContext {
|
||||
/// 与えられた値を検証する
|
||||
/// Validate the given value
|
||||
#[tool]
|
||||
async fn validate(&self, value: i32) -> Result<String, MyError> {
|
||||
if value > 0 {
|
||||
@@ -198,7 +198,7 @@ struct SyncContext {
|
||||
|
||||
#[tool_registry]
|
||||
impl SyncContext {
|
||||
/// カウンターをインクリメントして返す (非async)
|
||||
/// Increment counter and return (non-async)
|
||||
#[tool]
|
||||
fn increment(&self) -> usize {
|
||||
self.counter.fetch_add(1, Ordering::SeqCst) + 1
|
||||
@@ -213,7 +213,7 @@ async fn test_sync_method() {
|
||||
|
||||
let (_, tool) = ctx.increment_definition()();
|
||||
|
||||
// 3回実行
|
||||
// Execute 3 times
|
||||
let result1 = tool.execute(r#"{}"#).await;
|
||||
let result2 = tool.execute(r#"{}"#).await;
|
||||
let result3 = tool.execute(r#"{}"#).await;
|
||||
@@ -222,7 +222,7 @@ async fn test_sync_method() {
|
||||
assert!(result2.is_ok());
|
||||
assert!(result3.is_ok());
|
||||
|
||||
// カウンターは3になっているはず
|
||||
// Counter should be 3
|
||||
assert_eq!(ctx.counter.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ async fn test_tool_meta_immutability() {
|
||||
prefix: "Test".to_string(),
|
||||
};
|
||||
|
||||
// 2回取得しても同じメタ情報が得られることを確認
|
||||
// Verify same meta info is returned on multiple calls
|
||||
let (meta1, _) = ctx.greet_definition()();
|
||||
let (meta2, _) = ctx.greet_definition()();
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@ use llm_worker::{Worker, WorkerError};
|
||||
|
||||
#[test]
|
||||
fn test_openai_top_k_warning() {
|
||||
// ダミーキーでクライアント作成(validate_configは通信しないため安全)
|
||||
// Create client with dummy key (validate_config doesn't make network calls, so safe)
|
||||
let client = OpenAIClient::new("dummy-key", "gpt-4o");
|
||||
|
||||
// top_kを設定したWorkerを作成
|
||||
let worker = Worker::new(client).top_k(50); // OpenAIはtop_k非対応
|
||||
// Create Worker with top_k set (OpenAI doesn't support top_k)
|
||||
let worker = Worker::new(client).top_k(50);
|
||||
|
||||
// validate()を実行
|
||||
// Run validate()
|
||||
let result = worker.validate();
|
||||
|
||||
// エラーが返り、ConfigWarningsが含まれていることを確認
|
||||
// Verify error is returned and ConfigWarnings is included
|
||||
match result {
|
||||
Err(WorkerError::ConfigWarnings(warnings)) => {
|
||||
assert_eq!(warnings.len(), 1);
|
||||
@@ -28,12 +28,12 @@ fn test_openai_top_k_warning() {
|
||||
fn test_openai_valid_config() {
|
||||
let client = OpenAIClient::new("dummy-key", "gpt-4o");
|
||||
|
||||
// validな設定(temperatureのみ)
|
||||
// Valid configuration (temperature only)
|
||||
let worker = Worker::new(client).temperature(0.7);
|
||||
|
||||
// validate()を実行
|
||||
// Run validate()
|
||||
let result = worker.validate();
|
||||
|
||||
// 成功を確認
|
||||
// Verify success
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Workerフィクスチャベースの統合テスト
|
||||
//! Worker fixture-based integration tests
|
||||
//!
|
||||
//! 記録されたAPIレスポンスを使ってWorkerの動作をテストする。
|
||||
//! APIキー不要でローカルで実行可能。
|
||||
//! Tests Worker behavior using recorded API responses.
|
||||
//! Can run locally without API keys.
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -14,12 +14,12 @@ use common::MockLlmClient;
|
||||
use llm_worker::Worker;
|
||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta};
|
||||
|
||||
/// フィクスチャディレクトリのパス
|
||||
/// Fixture directory path
|
||||
fn fixtures_dir() -> std::path::PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/anthropic")
|
||||
}
|
||||
|
||||
/// シンプルなテスト用ツール
|
||||
/// Simple test tool
|
||||
#[derive(Clone)]
|
||||
struct MockWeatherTool {
|
||||
call_count: Arc<AtomicUsize>,
|
||||
@@ -61,13 +61,13 @@ impl Tool for MockWeatherTool {
|
||||
async fn execute(&self, input_json: &str) -> Result<String, ToolError> {
|
||||
self.call_count.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// 入力をパース
|
||||
// Parse input
|
||||
let input: serde_json::Value = serde_json::from_str(input_json)
|
||||
.map_err(|e| ToolError::InvalidArgument(e.to_string()))?;
|
||||
|
||||
let city = input["city"].as_str().unwrap_or("Unknown");
|
||||
|
||||
// モックのレスポンスを返す
|
||||
// Return mock response
|
||||
Ok(format!("Weather in {}: Sunny, 22°C", city))
|
||||
}
|
||||
}
|
||||
@@ -76,12 +76,12 @@ impl Tool for MockWeatherTool {
|
||||
// Basic Fixture Tests
|
||||
// =============================================================================
|
||||
|
||||
/// MockLlmClientがJSONLフィクスチャファイルから正しくイベントをロードできることを確認
|
||||
/// Verify that MockLlmClient can correctly load events from JSONL fixture files
|
||||
///
|
||||
/// 既存のanthropic_*.jsonlファイルを使用し、イベントがパース・ロードされることを検証する。
|
||||
/// Uses existing anthropic_*.jsonl files to verify events are parsed and loaded.
|
||||
#[test]
|
||||
fn test_mock_client_from_fixture() {
|
||||
// 既存のフィクスチャをロード
|
||||
// Load existing fixture
|
||||
let fixture_path = fixtures_dir().join("anthropic_1767624445.jsonl");
|
||||
if !fixture_path.exists() {
|
||||
println!("Fixture not found, skipping test");
|
||||
@@ -93,14 +93,14 @@ fn test_mock_client_from_fixture() {
|
||||
println!("Loaded {} events from fixture", client.event_count());
|
||||
}
|
||||
|
||||
/// MockLlmClientが直接指定されたイベントリストで正しく動作することを確認
|
||||
/// Verify that MockLlmClient works correctly with directly specified event lists
|
||||
///
|
||||
/// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。
|
||||
/// Creates a client with programmatically constructed events instead of using fixture files.
|
||||
#[test]
|
||||
fn test_mock_client_from_events() {
|
||||
use llm_worker::llm_client::event::Event;
|
||||
|
||||
// 直接イベントを指定
|
||||
// Specify events directly
|
||||
let events = vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "Hello!"),
|
||||
@@ -115,10 +115,10 @@ fn test_mock_client_from_events() {
|
||||
// Worker Tests with Fixtures
|
||||
// =============================================================================
|
||||
|
||||
/// Workerがシンプルなテキストレスポンスを正しく処理できることを確認
|
||||
/// Verify that Worker can correctly process simple text responses
|
||||
///
|
||||
/// simple_text.jsonlフィクスチャを使用し、ツール呼び出しなしのシナリオをテストする。
|
||||
/// フィクスチャがない場合はスキップされる。
|
||||
/// Uses simple_text.jsonl fixture to test scenarios without tool calls.
|
||||
/// Skipped if fixture is not present.
|
||||
#[tokio::test]
|
||||
async fn test_worker_simple_text_response() {
|
||||
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 mut worker = Worker::new(client);
|
||||
|
||||
// シンプルなメッセージを送信
|
||||
// Send a simple message
|
||||
let result = worker.run("Hello").await;
|
||||
|
||||
assert!(result.is_ok(), "Worker should complete successfully");
|
||||
}
|
||||
|
||||
/// Workerがツール呼び出しを含むレスポンスを正しく処理できることを確認
|
||||
/// Verify that Worker can correctly process responses containing tool calls
|
||||
///
|
||||
/// tool_call.jsonlフィクスチャを使用し、MockWeatherToolが呼び出されることをテストする。
|
||||
/// max_turns=1に設定し、ツール実行後のループを防止。
|
||||
/// Uses tool_call.jsonl fixture to test that MockWeatherTool is called.
|
||||
/// Sets max_turns=1 to prevent loop after tool execution.
|
||||
#[tokio::test]
|
||||
async fn test_worker_tool_call() {
|
||||
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 mut worker = Worker::new(client);
|
||||
|
||||
// ツールを登録
|
||||
// Register tool
|
||||
let weather_tool = MockWeatherTool::new();
|
||||
let tool_for_check = weather_tool.clone();
|
||||
worker.register_tool(weather_tool.definition()).unwrap();
|
||||
|
||||
// メッセージを送信
|
||||
// Send message
|
||||
let _result = worker.run("What's the weather in Tokyo?").await;
|
||||
|
||||
// ツールが呼び出されたことを確認
|
||||
// Note: max_turns=1なのでツール結果後のリクエストは送信されない
|
||||
// Verify tool was called
|
||||
// Note: max_turns=1 so no request is sent after tool result
|
||||
let call_count = tool_for_check.get_call_count();
|
||||
println!("Tool was called {} times", call_count);
|
||||
|
||||
// フィクスチャにToolUseが含まれていればツールが呼び出されるはず
|
||||
// ただしmax_turns=1なので1回で終了
|
||||
// Tool should be called if fixture contains ToolUse
|
||||
// But ends after 1 turn due to max_turns=1
|
||||
}
|
||||
|
||||
/// fixtureファイルなしでWorkerが動作することを確認
|
||||
/// Verify that Worker works without fixture files
|
||||
///
|
||||
/// プログラムでイベントシーケンスを構築し、MockLlmClientに渡してテストする。
|
||||
/// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。
|
||||
/// Constructs event sequence programmatically and passes to MockLlmClient.
|
||||
/// Useful when test independence is needed and external file dependency should be eliminated.
|
||||
#[tokio::test]
|
||||
async fn test_worker_with_programmatic_events() {
|
||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||
|
||||
// プログラムでイベントシーケンスを構築
|
||||
// Construct event sequence programmatically
|
||||
let events = vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "Hello, "),
|
||||
@@ -197,16 +197,16 @@ async fn test_worker_with_programmatic_events() {
|
||||
assert!(result.is_ok(), "Worker should complete successfully");
|
||||
}
|
||||
|
||||
/// ToolCallCollectorがToolUseブロックイベントから正しくToolCallを収集することを確認
|
||||
/// Verify that ToolCallCollector correctly collects ToolCall from ToolUse block events
|
||||
///
|
||||
/// Timelineにイベントをディスパッチし、ToolCallCollectorが
|
||||
/// id, name, input(JSON)を正しく抽出できることを検証する。
|
||||
/// Dispatches events to Timeline and verifies ToolCallCollector
|
||||
/// correctly extracts id, name, and input (JSON).
|
||||
#[tokio::test]
|
||||
async fn test_tool_call_collector_integration() {
|
||||
use llm_worker::llm_client::event::Event;
|
||||
use llm_worker::timeline::{Timeline, ToolCallCollector};
|
||||
|
||||
// ToolUseブロックを含むイベントシーケンス
|
||||
// Event sequence containing ToolUse block
|
||||
let events = vec![
|
||||
Event::tool_use_start(0, "call_123", "get_weather"),
|
||||
Event::tool_input_delta(0, r#"{"city":"#),
|
||||
@@ -218,13 +218,13 @@ async fn test_tool_call_collector_integration() {
|
||||
let mut timeline = Timeline::new();
|
||||
timeline.on_tool_use_block(collector.clone());
|
||||
|
||||
// イベントをディスパッチ
|
||||
// Dispatch events
|
||||
for event in &events {
|
||||
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
|
||||
timeline.dispatch(&timeline_event);
|
||||
}
|
||||
|
||||
// 収集されたToolCallを確認
|
||||
// Verify collected ToolCall
|
||||
let calls = collector.take_collected();
|
||||
assert_eq!(calls.len(), 1, "Should collect one tool call");
|
||||
assert_eq!(calls[0].name, "get_weather");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Worker状態管理のテスト
|
||||
//! Worker state management tests
|
||||
//!
|
||||
//! Type-stateパターン(Mutable/CacheLocked)による状態遷移と
|
||||
//! ターン間の状態保持をテストする。
|
||||
//! Tests for state transitions using the Type-state pattern (Mutable/CacheLocked)
|
||||
//! and state preservation between turns.
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -11,10 +11,10 @@ use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||
use llm_worker::{Message, MessageContent};
|
||||
|
||||
// =============================================================================
|
||||
// Mutable状態のテスト
|
||||
// Mutable State Tests
|
||||
// =============================================================================
|
||||
|
||||
/// Mutable状態でシステムプロンプトを設定できることを確認
|
||||
/// Verify that system prompt can be set in Mutable state
|
||||
#[test]
|
||||
fn test_mutable_set_system_prompt() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -29,35 +29,35 @@ fn test_mutable_set_system_prompt() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Mutable状態で履歴を自由に編集できることを確認
|
||||
/// Verify that history can be freely edited in Mutable state
|
||||
#[test]
|
||||
fn test_mutable_history_manipulation() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// 初期状態は空
|
||||
// Initial state is empty
|
||||
assert!(worker.history().is_empty());
|
||||
|
||||
// 履歴を追加
|
||||
// Add to history
|
||||
worker.push_message(Message::user("Hello"));
|
||||
worker.push_message(Message::assistant("Hi there!"));
|
||||
assert_eq!(worker.history().len(), 2);
|
||||
|
||||
// 履歴への可変アクセス
|
||||
// Mutable access to history
|
||||
worker.history_mut().push(Message::user("How are you?"));
|
||||
assert_eq!(worker.history().len(), 3);
|
||||
|
||||
// 履歴をクリア
|
||||
// Clear history
|
||||
worker.clear_history();
|
||||
assert!(worker.history().is_empty());
|
||||
|
||||
// 履歴を設定
|
||||
// Set history
|
||||
let messages = vec![Message::user("Test"), Message::assistant("Response")];
|
||||
worker.set_history(messages);
|
||||
assert_eq!(worker.history().len(), 2);
|
||||
}
|
||||
|
||||
/// ビルダーパターンでWorkerを構築できることを確認
|
||||
/// Verify that Worker can be constructed using builder pattern
|
||||
#[test]
|
||||
fn test_mutable_builder_pattern() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -74,7 +74,7 @@ fn test_mutable_builder_pattern() {
|
||||
assert_eq!(worker.history().len(), 4);
|
||||
}
|
||||
|
||||
/// extend_historyで複数メッセージを追加できることを確認
|
||||
/// Verify that multiple messages can be added with extend_history
|
||||
#[test]
|
||||
fn test_mutable_extend_history() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -92,10 +92,10 @@ fn test_mutable_extend_history() {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 状態遷移テスト
|
||||
// State Transition Tests
|
||||
// =============================================================================
|
||||
|
||||
/// lock()でMutable -> CacheLocked状態に遷移することを確認
|
||||
/// Verify that lock() transitions from Mutable -> CacheLocked state
|
||||
#[test]
|
||||
fn test_lock_transition() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -105,16 +105,16 @@ fn test_lock_transition() {
|
||||
worker.push_message(Message::user("Hello"));
|
||||
worker.push_message(Message::assistant("Hi"));
|
||||
|
||||
// ロック
|
||||
// Lock
|
||||
let locked_worker = worker.lock();
|
||||
|
||||
// CacheLocked状態でも履歴とシステムプロンプトにアクセス可能
|
||||
// History and system prompt are still accessible in CacheLocked state
|
||||
assert_eq!(locked_worker.get_system_prompt(), Some("System"));
|
||||
assert_eq!(locked_worker.history().len(), 2);
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 2);
|
||||
}
|
||||
|
||||
/// unlock()でCacheLocked -> Mutable状態に遷移することを確認
|
||||
/// Verify that unlock() transitions from CacheLocked -> Mutable state
|
||||
#[test]
|
||||
fn test_unlock_transition() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -123,20 +123,20 @@ fn test_unlock_transition() {
|
||||
worker.push_message(Message::user("Hello"));
|
||||
let locked_worker = worker.lock();
|
||||
|
||||
// アンロック
|
||||
// Unlock
|
||||
let mut worker = locked_worker.unlock();
|
||||
|
||||
// Mutable状態に戻ったので履歴操作が可能
|
||||
// History operations are available again in Mutable state
|
||||
worker.push_message(Message::assistant("Hi"));
|
||||
worker.clear_history();
|
||||
assert!(worker.history().is_empty());
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ターン実行と状態保持のテスト
|
||||
// Turn Execution and State Preservation Tests
|
||||
// =============================================================================
|
||||
|
||||
/// Mutable状態でターンを実行し、履歴が正しく更新されることを確認
|
||||
/// Verify that history is correctly updated after running a turn in Mutable state
|
||||
#[tokio::test]
|
||||
async fn test_mutable_run_updates_history() {
|
||||
let events = vec![
|
||||
@@ -151,33 +151,33 @@ async fn test_mutable_run_updates_history() {
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// 実行
|
||||
// Execute
|
||||
let result = worker.run("Hi there").await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// 履歴が更新されている
|
||||
// History is updated
|
||||
let history = worker.history();
|
||||
assert_eq!(history.len(), 2); // user + assistant
|
||||
|
||||
// ユーザーメッセージ
|
||||
// User message
|
||||
assert!(matches!(
|
||||
&history[0].content,
|
||||
MessageContent::Text(t) if t == "Hi there"
|
||||
));
|
||||
|
||||
// アシスタントメッセージ
|
||||
// Assistant message
|
||||
assert!(matches!(
|
||||
&history[1].content,
|
||||
MessageContent::Text(t) if t == "Hello, I'm an assistant!"
|
||||
));
|
||||
}
|
||||
|
||||
/// CacheLocked状態で複数ターンを実行し、履歴が正しく累積することを確認
|
||||
/// Verify that history accumulates correctly over multiple turns in CacheLocked state
|
||||
#[tokio::test]
|
||||
async fn test_locked_multi_turn_history_accumulation() {
|
||||
// 2回のリクエストに対応するレスポンスを準備
|
||||
// Prepare responses for 2 requests
|
||||
let client = MockLlmClient::with_responses(vec![
|
||||
// 1回目のレスポンス
|
||||
// First response
|
||||
vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "Nice to meet you!"),
|
||||
@@ -186,7 +186,7 @@ async fn test_locked_multi_turn_history_accumulation() {
|
||||
status: ResponseStatus::Completed,
|
||||
}),
|
||||
],
|
||||
// 2回目のレスポンス
|
||||
// Second response
|
||||
vec![
|
||||
Event::text_block_start(0),
|
||||
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.");
|
||||
|
||||
// ロック(システムプロンプト設定後)
|
||||
// Lock (after setting system prompt)
|
||||
let mut locked_worker = worker.lock();
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 0); // メッセージはまだない
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 0); // No messages yet
|
||||
|
||||
// 1ターン目
|
||||
// Turn 1
|
||||
let result1 = locked_worker.run("Hello!").await;
|
||||
assert!(result1.is_ok());
|
||||
assert_eq!(locked_worker.history().len(), 2); // user + assistant
|
||||
|
||||
// 2ターン目
|
||||
// Turn 2
|
||||
let result2 = locked_worker.run("Can you help me?").await;
|
||||
assert!(result2.is_ok());
|
||||
assert_eq!(locked_worker.history().len(), 4); // 2 * (user + assistant)
|
||||
|
||||
// 履歴の内容を確認
|
||||
// Verify history contents
|
||||
let history = locked_worker.history();
|
||||
|
||||
// 1ターン目のユーザーメッセージ
|
||||
// Turn 1 user message
|
||||
assert!(matches!(&history[0].content, MessageContent::Text(t) if t == "Hello!"));
|
||||
|
||||
// 1ターン目のアシスタントメッセージ
|
||||
// Turn 1 assistant message
|
||||
assert!(matches!(&history[1].content, MessageContent::Text(t) if t == "Nice to meet you!"));
|
||||
|
||||
// 2ターン目のユーザーメッセージ
|
||||
// Turn 2 user message
|
||||
assert!(matches!(&history[2].content, MessageContent::Text(t) if t == "Can you help me?"));
|
||||
|
||||
// 2ターン目のアシスタントメッセージ
|
||||
// Turn 2 assistant message
|
||||
assert!(matches!(&history[3].content, MessageContent::Text(t) if t == "I can help with that."));
|
||||
}
|
||||
|
||||
/// locked_prefix_lenがロック時点の履歴長を正しく記録することを確認
|
||||
/// Verify that locked_prefix_len correctly records history length at lock time
|
||||
#[tokio::test]
|
||||
async fn test_locked_prefix_len_tracking() {
|
||||
let client = MockLlmClient::with_responses(vec![
|
||||
@@ -253,25 +253,25 @@ async fn test_locked_prefix_len_tracking() {
|
||||
|
||||
let mut worker = Worker::new(client);
|
||||
|
||||
// 事前にメッセージを追加
|
||||
// Add messages beforehand
|
||||
worker.push_message(Message::user("Pre-existing message 1"));
|
||||
worker.push_message(Message::assistant("Pre-existing response 1"));
|
||||
|
||||
assert_eq!(worker.history().len(), 2);
|
||||
|
||||
// ロック
|
||||
// Lock
|
||||
let mut locked_worker = worker.lock();
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 2); // ロック時点で2メッセージ
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 2); // 2 messages at lock time
|
||||
|
||||
// ターン実行
|
||||
// Execute turn
|
||||
locked_worker.run("New message").await.unwrap();
|
||||
|
||||
// 履歴は増えるが、locked_prefix_lenは変わらない
|
||||
// History grows but locked_prefix_len remains unchanged
|
||||
assert_eq!(locked_worker.history().len(), 4); // 2 + 2
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 2); // 変わらない
|
||||
assert_eq!(locked_worker.locked_prefix_len(), 2); // Unchanged
|
||||
}
|
||||
|
||||
/// ターンカウントが正しくインクリメントされることを確認
|
||||
/// Verify that turn count is correctly incremented
|
||||
#[tokio::test]
|
||||
async fn test_turn_count_increment() {
|
||||
let client = MockLlmClient::with_responses(vec![
|
||||
@@ -304,7 +304,7 @@ async fn test_turn_count_increment() {
|
||||
assert_eq!(worker.turn_count(), 2);
|
||||
}
|
||||
|
||||
/// unlock後に履歴を編集し、再度lockできることを確認
|
||||
/// Verify that history can be edited after unlock and re-locked
|
||||
#[tokio::test]
|
||||
async fn test_unlock_edit_relock() {
|
||||
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::assistant("Hi"));
|
||||
|
||||
// ロック -> アンロック
|
||||
// Lock -> Unlock
|
||||
let locked = worker.lock();
|
||||
assert_eq!(locked.locked_prefix_len(), 2);
|
||||
|
||||
let mut unlocked = locked.unlock();
|
||||
|
||||
// 履歴を編集
|
||||
// Edit history
|
||||
unlocked.clear_history();
|
||||
unlocked.push_message(Message::user("Fresh start"));
|
||||
|
||||
// 再ロック
|
||||
// Re-lock
|
||||
let relocked = unlocked.lock();
|
||||
assert_eq!(relocked.history().len(), 1);
|
||||
assert_eq!(relocked.locked_prefix_len(), 1);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// システムプロンプト保持のテスト
|
||||
// System Prompt Preservation Tests
|
||||
// =============================================================================
|
||||
|
||||
/// CacheLocked状態でもシステムプロンプトが保持されることを確認
|
||||
/// Verify that system prompt is preserved in CacheLocked state
|
||||
#[test]
|
||||
fn test_system_prompt_preserved_in_locked_state() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -356,7 +356,7 @@ fn test_system_prompt_preserved_in_locked_state() {
|
||||
);
|
||||
}
|
||||
|
||||
/// unlock -> 再lock でシステムプロンプトを変更できることを確認
|
||||
/// Verify that system prompt can be changed after unlock -> re-lock
|
||||
#[test]
|
||||
fn test_system_prompt_change_after_unlock() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
|
||||
Reference in New Issue
Block a user