Workerの自動キャッシュロック
This commit is contained in:
@@ -46,8 +46,9 @@ async fn test_callback_text_block_events() {
|
||||
});
|
||||
});
|
||||
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let result = worker.run("Greet me").await;
|
||||
assert!(result.is_ok(), "Worker should complete: {:?}", result);
|
||||
assert!(result.is_ok(), "Worker should complete");
|
||||
|
||||
let deltas = text_deltas.lock().unwrap();
|
||||
assert_eq!(deltas.len(), 2);
|
||||
@@ -91,6 +92,7 @@ async fn test_callback_tool_call_complete() {
|
||||
});
|
||||
});
|
||||
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let _ = worker.run("Weather please").await;
|
||||
|
||||
let starts = tool_starts.lock().unwrap();
|
||||
@@ -133,6 +135,7 @@ async fn test_callback_turn_events() {
|
||||
ends.lock().unwrap().push(turn);
|
||||
});
|
||||
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let result = worker.run("Do something").await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
@@ -169,6 +172,7 @@ async fn test_callback_usage_events() {
|
||||
usages.lock().unwrap().push(event.clone());
|
||||
});
|
||||
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let _ = worker.run("Hello").await;
|
||||
|
||||
let usages = usage_events.lock().unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[test]
|
||||
fn compile_fail_state_constraints() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/cache_locked_register_tool.rs");
|
||||
t.compile_fail("tests/ui/locked_register_tool.rs");
|
||||
t.compile_fail("tests/ui/tool_server_handle_register_tool.rs");
|
||||
}
|
||||
|
||||
@@ -102,11 +102,12 @@ async fn test_parallel_tool_execution() {
|
||||
let tool2_clone = tool2.clone();
|
||||
let tool3_clone = tool3.clone();
|
||||
|
||||
worker.register_tool(tool1.definition()).unwrap();
|
||||
worker.register_tool(tool2.definition()).unwrap();
|
||||
worker.register_tool(tool3.definition()).unwrap();
|
||||
worker.register_tool(tool1.definition());
|
||||
worker.register_tool(tool2.definition());
|
||||
worker.register_tool(tool3.definition());
|
||||
|
||||
let start = Instant::now();
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let _result = worker.run("Run all tools").await;
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
@@ -150,8 +151,8 @@ async fn test_before_tool_call_skip() {
|
||||
let allowed_clone = allowed_tool.clone();
|
||||
let blocked_clone = blocked_tool.clone();
|
||||
|
||||
worker.register_tool(allowed_tool.definition()).unwrap();
|
||||
worker.register_tool(blocked_tool.definition()).unwrap();
|
||||
worker.register_tool(allowed_tool.definition());
|
||||
worker.register_tool(blocked_tool.definition());
|
||||
|
||||
// Policy to skip "blocked_tool"
|
||||
struct BlockingPolicy;
|
||||
@@ -169,6 +170,7 @@ async fn test_before_tool_call_skip() {
|
||||
|
||||
worker.set_interceptor(BlockingPolicy);
|
||||
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let _result = worker.run("Test hook").await;
|
||||
|
||||
// allowed_tool is called, but blocked_tool is not
|
||||
@@ -230,7 +232,7 @@ async fn test_post_tool_call_modification() {
|
||||
})
|
||||
}
|
||||
|
||||
worker.register_tool(simple_tool_definition()).unwrap();
|
||||
worker.register_tool(simple_tool_definition());
|
||||
|
||||
// Policy to modify results
|
||||
struct ModifyingPolicy {
|
||||
@@ -251,9 +253,10 @@ async fn test_post_tool_call_modification() {
|
||||
modified_content: modified_content.clone(),
|
||||
});
|
||||
|
||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
||||
let result = worker.run("Test modification").await;
|
||||
|
||||
assert!(result.is_ok(), "Worker should complete: {:?}", result);
|
||||
assert!(result.is_ok(), "Worker should complete");
|
||||
|
||||
// Verify hook was called and content was modified
|
||||
let content = modified_content.lock().unwrap().clone();
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
error[E0599]: no method named `register_tool` found for struct `Worker<OllamaClient, CacheLocked>` in the current scope
|
||||
--> tests/ui/cache_locked_register_tool.rs:10:20
|
||||
error[E0599]: no method named `register_tool` found for struct `Worker<OllamaClient, Locked>` in the current scope
|
||||
--> tests/ui/locked_register_tool.rs:10:20
|
||||
|
|
||||
10 | let _ = locked.register_tool(def);
|
||||
| ^^^^^^^^^^^^^ method not found in `Worker<OllamaClient, CacheLocked>`
|
||||
| ^^^^^^^^^^^^^ method not found in `Worker<OllamaClient, Locked>`
|
||||
|
|
||||
= note: the method was found for
|
||||
- `Worker<C>`
|
||||
@@ -1,13 +1,10 @@
|
||||
error[E0624]: method `register_tool` is private
|
||||
--> tests/ui/tool_server_handle_register_tool.rs:10:20
|
||||
|
|
||||
10 | let _ = handle.register_tool(def);
|
||||
| ^^^^^^^^^^^^^ private method
|
||||
10 | let _ = handle.register_tool(def);
|
||||
| ^^^^^^^^^^^^^ private method
|
||||
|
|
||||
::: src/tool_server.rs
|
||||
|
|
||||
| / pub(crate) fn register_tool(
|
||||
| | &self,
|
||||
| | factory: WorkerToolDefinition,
|
||||
| | ) -> Result<(), ToolServerError> {
|
||||
| |____________________________________- private method defined here
|
||||
| pub(crate) fn register_tool(&self, factory: WorkerToolDefinition) {
|
||||
| ----------------------------------------------------------------- private method defined here
|
||||
|
||||
@@ -129,9 +129,9 @@ async fn test_worker_simple_text_response() {
|
||||
}
|
||||
|
||||
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
|
||||
let mut worker = Worker::new(client);
|
||||
let worker = Worker::new(client);
|
||||
|
||||
// Send a simple message
|
||||
// Send a simple message (Mutable::run consumes self, returns tuple)
|
||||
let result = worker.run("Hello").await;
|
||||
|
||||
assert!(result.is_ok(), "Worker should complete successfully");
|
||||
@@ -156,9 +156,9 @@ async fn test_worker_tool_call() {
|
||||
// Register tool
|
||||
let weather_tool = MockWeatherTool::new();
|
||||
let tool_for_check = weather_tool.clone();
|
||||
worker.register_tool(weather_tool.definition()).unwrap();
|
||||
worker.register_tool(weather_tool.definition());
|
||||
|
||||
// Send message
|
||||
// Send message (Mutable::run consumes self, returns tuple)
|
||||
let _result = worker.run("What's the weather in Tokyo?").await;
|
||||
|
||||
// Verify tool was called
|
||||
@@ -190,8 +190,9 @@ async fn test_worker_with_programmatic_events() {
|
||||
];
|
||||
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
let worker = Worker::new(client);
|
||||
|
||||
// Mutable::run consumes self, returns tuple
|
||||
let result = worker.run("Greet me").await;
|
||||
|
||||
assert!(result.is_ok(), "Worker should complete successfully");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Worker state management tests
|
||||
//!
|
||||
//! Tests for state transitions using the Type-state pattern (Mutable/CacheLocked)
|
||||
//! Tests for state transitions using the Type-state pattern (Mutable/Locked)
|
||||
//! and state preservation between turns.
|
||||
|
||||
mod common;
|
||||
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use async_trait::async_trait;
|
||||
use common::MockLlmClient;
|
||||
use llm_worker::Item;
|
||||
use llm_worker::Worker;
|
||||
use llm_worker::{Worker, WorkerError};
|
||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta};
|
||||
|
||||
@@ -147,15 +147,15 @@ fn test_mutable_can_register_tool() {
|
||||
let mut worker = Worker::new(client);
|
||||
let tool = CountingTool::new("count_tool");
|
||||
|
||||
let result = worker.register_tool(tool.definition());
|
||||
assert!(result.is_ok(), "Mutable should allow tool registration");
|
||||
// register_tool is infallible (factory deferred to run-time flush)
|
||||
worker.register_tool(tool.definition());
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// State Transition Tests
|
||||
// =============================================================================
|
||||
|
||||
/// Verify that lock() transitions from Mutable -> CacheLocked state
|
||||
/// Verify that lock() transitions from Mutable -> Locked state
|
||||
#[test]
|
||||
fn test_lock_transition() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -168,13 +168,13 @@ fn test_lock_transition() {
|
||||
// Lock
|
||||
let locked_worker = worker.lock();
|
||||
|
||||
// History and system prompt are still accessible in CacheLocked state
|
||||
// History and system prompt are still accessible in Locked 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);
|
||||
}
|
||||
|
||||
/// Verify that unlock() transitions from CacheLocked -> Mutable state
|
||||
/// Verify that unlock() transitions from Locked -> Mutable state
|
||||
#[test]
|
||||
fn test_unlock_transition() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
@@ -198,7 +198,7 @@ fn test_unlock_transition() {
|
||||
|
||||
/// Verify that history is correctly updated after running a turn in Mutable state
|
||||
#[tokio::test]
|
||||
async fn test_mutable_run_updates_history() {
|
||||
async fn test_mutable_run_updates_history() -> Result<(), WorkerError> {
|
||||
let events = vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "Hello, I'm an assistant!"),
|
||||
@@ -209,11 +209,10 @@ async fn test_mutable_run_updates_history() {
|
||||
];
|
||||
|
||||
let client = MockLlmClient::new(events);
|
||||
let mut worker = Worker::new(client);
|
||||
let worker = Worker::new(client);
|
||||
|
||||
// Execute
|
||||
let result = worker.run("Hi there").await;
|
||||
assert!(result.is_ok());
|
||||
// Execute (Mutable::run consumes self, returns (Locked, WorkerResult))
|
||||
let (worker, _result) = worker.run("Hi there").await?;
|
||||
|
||||
// History is updated
|
||||
let history = worker.history();
|
||||
@@ -224,9 +223,11 @@ async fn test_mutable_run_updates_history() {
|
||||
|
||||
// Assistant message
|
||||
assert_eq!(history[1].as_text(), Some("Hello, I'm an assistant!"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify that history accumulates correctly over multiple turns in CacheLocked state
|
||||
/// Verify that history accumulates correctly over multiple turns in Locked state
|
||||
#[tokio::test]
|
||||
async fn test_locked_multi_turn_history_accumulation() {
|
||||
// Prepare responses for 2 requests
|
||||
@@ -327,7 +328,7 @@ async fn test_locked_prefix_len_tracking() {
|
||||
|
||||
/// Verify that turn count is correctly incremented
|
||||
#[tokio::test]
|
||||
async fn test_turn_count_increment() {
|
||||
async fn test_turn_count_increment() -> Result<(), WorkerError> {
|
||||
let client = MockLlmClient::with_responses(vec![
|
||||
vec![
|
||||
Event::text_block_start(0),
|
||||
@@ -347,15 +348,19 @@ async fn test_turn_count_increment() {
|
||||
],
|
||||
]);
|
||||
|
||||
let mut worker = Worker::new(client);
|
||||
let worker = Worker::new(client);
|
||||
|
||||
assert_eq!(worker.turn_count(), 0);
|
||||
|
||||
worker.run("First").await.unwrap();
|
||||
// First run consumes Mutable, returns Locked
|
||||
let (mut worker, _) = worker.run("First").await?;
|
||||
assert_eq!(worker.turn_count(), 1);
|
||||
|
||||
worker.run("Second").await.unwrap();
|
||||
// Subsequent runs on Locked take &mut self
|
||||
worker.run("Second").await?;
|
||||
assert_eq!(worker.turn_count(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify that history can be edited after unlock and re-locked
|
||||
@@ -430,9 +435,7 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
|
||||
|
||||
let mut worker = Worker::new(client);
|
||||
let tool_a = CountingTool::new("tool_a");
|
||||
worker
|
||||
.register_tool(tool_a.definition())
|
||||
.expect("register tool_a should succeed");
|
||||
worker.register_tool(tool_a.definition());
|
||||
|
||||
let mut locked = worker.lock();
|
||||
locked.run("first").await.expect("first run");
|
||||
@@ -440,9 +443,7 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
|
||||
|
||||
let mut unlocked = locked.unlock();
|
||||
let tool_b = CountingTool::new("tool_b");
|
||||
unlocked
|
||||
.register_tool(tool_b.definition())
|
||||
.expect("register tool_b after unlock should succeed");
|
||||
unlocked.register_tool(tool_b.definition());
|
||||
|
||||
let mut relocked = unlocked.lock();
|
||||
relocked.run("second").await.expect("second run");
|
||||
@@ -455,7 +456,7 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
|
||||
// System Prompt Preservation Tests
|
||||
// =============================================================================
|
||||
|
||||
/// Verify that system prompt is preserved in CacheLocked state
|
||||
/// Verify that system prompt is preserved in Locked state
|
||||
#[test]
|
||||
fn test_system_prompt_preserved_in_locked_state() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
|
||||
Reference in New Issue
Block a user