update: Locked -> CacheLocked

This commit is contained in:
Keisuke Hirata 2026-01-10 22:46:08 +09:00
parent a2f53d7879
commit c281248bf8
6 changed files with 24 additions and 24 deletions

View File

@ -11,7 +11,7 @@ Rusty, Efficient, and Agentic LLM Client Library
- Tool System: Define tools as async functions. The Worker automatically parses LLM tool calls, executes them in parallel, and feeds results back. - Tool System: Define tools as async functions. The Worker automatically parses LLM tool calls, executes them in parallel, and feeds results back.
- Hook System: Intercept execution flow with `before_tool_call`, `after_tool_call`, and `on_turn_end` hooks for validation, logging, or self-correction. - Hook System: Intercept execution flow with `before_tool_call`, `after_tool_call`, and `on_turn_end` hooks for validation, logging, or self-correction.
- Event-Driven Streaming: Subscribe to real-time events (text deltas, tool calls, usage) for responsive UIs. - Event-Driven Streaming: Subscribe to real-time events (text deltas, tool calls, usage) for responsive UIs.
- Cache-Aware State Management: Type-state pattern (`Mutable` → `Locked`) ensures KV cache efficiency by protecting the conversation prefix. - Cache-Aware State Management: Type-state pattern (`Mutable` → `CacheLocked`) ensures KV cache efficiency by protecting the conversation prefix.
## Quick Start ## Quick Start

View File

@ -27,7 +27,7 @@ RustのType-stateパターンを利用し、Workerの状態によって利用可
* 自由な編集が可能な状態。 * 自由な編集が可能な状態。
* システムプロンプトの設定・変更が可能。 * システムプロンプトの設定・変更が可能。
* メッセージ履歴の初期構築(ロード、編集)が可能。 * メッセージ履歴の初期構築(ロード、編集)が可能。
* **`Locked` (キャッシュ保護状態)** * **`CacheLocked` (キャッシュ保護状態)**
* キャッシュの有効活用を目的とした、前方不変状態。 * キャッシュの有効活用を目的とした、前方不変状態。
* **システムプロンプトの変更不可**。 * **システムプロンプトの変更不可**。
* **既存メッセージ履歴の変更不可**(追記のみ許可)。 * **既存メッセージ履歴の変更不可**(追記のみ許可)。
@ -47,7 +47,7 @@ worker.history_mut().push(initial_message);
// 3. ロックしてLocked状態へ遷移 // 3. ロックしてLocked状態へ遷移
// これにより、ここまでのコンテキストが "Fixed Prefix" として扱われる // これにより、ここまでのコンテキストが "Fixed Prefix" として扱われる
let mut locked_worker: Worker<Locked> = worker.lock(); let mut locked_worker: Worker<CacheLocked> = worker.lock();
// 4. 利用 (Locked状態) // 4. 利用 (Locked状態)
// 実行は可能。新しいメッセージは履歴の末尾に追記される。 // 実行は可能。新しいメッセージは履歴の末尾に追記される。
@ -65,4 +65,4 @@ locked_worker.run(new_user_input).await?;
* **状態パラメータの導入**: `Worker<S: WorkerState>` の導入。 * **状態パラメータの導入**: `Worker<S: WorkerState>` の導入。
* **コンテキスト所有権の委譲**: `run` メソッドの引数でコンテキストを受け取るのではなく、`Worker` 内部に `history: Vec<Message>` を保持し管理する形へ移行する。 * **コンテキスト所有権の委譲**: `run` メソッドの引数でコンテキストを受け取るのではなく、`Worker` 内部に `history: Vec<Message>` を保持し管理する形へ移行する。
* **APIの分離**: `Mutable` 特有のメソッドsetter等と、`Locked` でも使えるメソッド(実行、参照等)をトレイト境界で分離する。 * **APIの分離**: `Mutable` 特有のメソッドsetter等と、`CacheLocked` でも使えるメソッド(実行、参照等)をトレイト境界で分離する。

View File

@ -1,6 +1,6 @@
//! LLMクライアント層 //! LLMクライアント層
//! //!
//! 各LLMプロバイダと通信し、統一された[`Event`](crate::llm_client::event::Event) //! 各LLMプロバイダと通信し、統一された[`Event`]
//! ストリームを出力します。 //! ストリームを出力します。
//! //!
//! # サポートするプロバイダ //! # サポートするプロバイダ

View File

@ -1,7 +1,7 @@
//! Worker状態 //! Worker状態
//! //!
//! Type-stateパターンによるキャッシュ保護のための状態マーカー型。 //! Type-stateパターンによるキャッシュ保護のための状態マーカー型。
//! Workerは`Mutable` → `Locked`の状態遷移を持ちます。 //! Workerは`Mutable` → `CacheLocked`の状態遷移を持ちます。
/// Worker状態を表すマーカートレイト /// Worker状態を表すマーカートレイト
/// ///
@ -19,7 +19,7 @@ mod private {
/// - メッセージ履歴の編集(追加、削除、クリア) /// - メッセージ履歴の編集(追加、削除、クリア)
/// - ツール・Hookの登録 /// - ツール・Hookの登録
/// ///
/// `Worker::lock()`により[`Locked`]状態へ遷移できます。 /// `Worker::lock()`により[`CacheLocked`]状態へ遷移できます。
/// ///
/// # Examples /// # Examples
/// ///
@ -42,7 +42,7 @@ pub struct Mutable;
impl private::Sealed for Mutable {} impl private::Sealed for Mutable {}
impl WorkerState for Mutable {} impl WorkerState for Mutable {}
/// ロック状態(キャッシュ保護) /// キャッシュロック状態(キャッシュ保護)
/// ///
/// この状態では以下の制限があります: /// この状態では以下の制限があります:
/// - システムプロンプトの変更不可 /// - システムプロンプトの変更不可
@ -54,7 +54,7 @@ impl WorkerState for Mutable {}
/// `Worker::unlock()`により[`Mutable`]状態へ戻せますが、 /// `Worker::unlock()`により[`Mutable`]状態へ戻せますが、
/// キャッシュ保護が解除されることに注意してください。 /// キャッシュ保護が解除されることに注意してください。
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Locked; pub struct CacheLocked;
impl private::Sealed for Locked {} impl private::Sealed for CacheLocked {}
impl WorkerState for Locked {} impl WorkerState for CacheLocked {}

View File

@ -17,7 +17,7 @@ use crate::{
ClientError, ConfigWarning, LlmClient, Request, RequestConfig, ClientError, ConfigWarning, LlmClient, Request, RequestConfig,
ToolDefinition as LlmToolDefinition, ToolDefinition as LlmToolDefinition,
}, },
state::{Locked, Mutable, WorkerState}, state::{CacheLocked, Mutable, WorkerState},
subscriber::{ subscriber::{
ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter, ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter,
ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, WorkerSubscriber, ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, WorkerSubscriber,
@ -131,7 +131,7 @@ impl<S: WorkerSubscriber + 'static> TurnNotifier for SubscriberTurnNotifier<S> {
/// # 状態遷移Type-state /// # 状態遷移Type-state
/// ///
/// - [`Mutable`]: 初期状態。システムプロンプトや履歴を自由に編集可能。 /// - [`Mutable`]: 初期状態。システムプロンプトや履歴を自由に編集可能。
/// - [`Locked`]: キャッシュ保護状態。`lock()`で遷移。前方コンテキストは不変。 /// - [`CacheLocked`]: キャッシュ保護状態。`lock()`で遷移。前方コンテキストは不変。
/// ///
/// # Examples /// # Examples
/// ///
@ -174,7 +174,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
system_prompt: Option<String>, system_prompt: Option<String>,
/// メッセージ履歴Workerが所有 /// メッセージ履歴Workerが所有
history: Vec<Message>, history: Vec<Message>,
/// ロック時点での履歴長(Locked状態でのみ意味を持つ /// ロック時点での履歴長(CacheLocked状態でのみ意味を持つ
locked_prefix_len: usize, locked_prefix_len: usize,
/// ターンカウント /// ターンカウント
turn_count: usize, turn_count: usize,
@ -1254,11 +1254,11 @@ impl<C: LlmClient> Worker<C, Mutable> {
self self
} }
/// ロックしてLocked状態へ遷移 /// ロックしてCacheLocked状態へ遷移
/// ///
/// この操作により、現在のシステムプロンプトと履歴が「確定済みプレフィックス」として /// この操作により、現在のシステムプロンプトと履歴が「確定済みプレフィックス」として
/// 固定される。以降は履歴への追記のみが可能となり、キャッシュヒットが保証される。 /// 固定される。以降は履歴への追記のみが可能となり、キャッシュヒットが保証される。
pub fn lock(self) -> Worker<C, Locked> { pub fn lock(self) -> Worker<C, CacheLocked> {
let locked_prefix_len = self.history.len(); let locked_prefix_len = self.history.len();
Worker { Worker {
client: self.client, client: self.client,
@ -1283,10 +1283,10 @@ impl<C: LlmClient> Worker<C, Mutable> {
} }
// ============================================================================= // =============================================================================
// Locked状態専用の実装 // CacheLocked状態専用の実装
// ============================================================================= // =============================================================================
impl<C: LlmClient> Worker<C, Locked> { impl<C: LlmClient> Worker<C, CacheLocked> {
/// ロック時点のプレフィックス長を取得 /// ロック時点のプレフィックス長を取得
pub fn locked_prefix_len(&self) -> usize { pub fn locked_prefix_len(&self) -> usize {
self.locked_prefix_len self.locked_prefix_len

View File

@ -1,6 +1,6 @@
//! Worker状態管理のテスト //! Worker状態管理のテスト
//! //!
//! Type-stateパターンMutable/Lockedによる状態遷移と //! Type-stateパターンMutable/CacheLockedによる状態遷移と
//! ターン間の状態保持をテストする。 //! ターン間の状態保持をテストする。
mod common; mod common;
@ -95,7 +95,7 @@ fn test_mutable_extend_history() {
// 状態遷移テスト // 状態遷移テスト
// ============================================================================= // =============================================================================
/// lock()でMutable -> Locked状態に遷移することを確認 /// lock()でMutable -> CacheLocked状態に遷移することを確認
#[test] #[test]
fn test_lock_transition() { fn test_lock_transition() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -108,13 +108,13 @@ fn test_lock_transition() {
// ロック // ロック
let locked_worker = worker.lock(); let locked_worker = worker.lock();
// Locked状態でも履歴とシステムプロンプトにアクセス可能 // 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);
} }
/// unlock()でLocked -> Mutable状態に遷移することを確認 /// unlock()でCacheLocked -> Mutable状態に遷移することを確認
#[test] #[test]
fn test_unlock_transition() { fn test_unlock_transition() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
@ -172,7 +172,7 @@ async fn test_mutable_run_updates_history() {
)); ));
} }
/// Locked状態で複数ターンを実行し、履歴が正しく累積することを確認 /// CacheLocked状態で複数ターンを実行し、履歴が正しく累積することを確認
#[tokio::test] #[tokio::test]
async fn test_locked_multi_turn_history_accumulation() { async fn test_locked_multi_turn_history_accumulation() {
// 2回のリクエストに対応するレスポンスを準備 // 2回のリクエストに対応するレスポンスを準備
@ -340,7 +340,7 @@ async fn test_unlock_edit_relock() {
// システムプロンプト保持のテスト // システムプロンプト保持のテスト
// ============================================================================= // =============================================================================
/// Locked状態でもシステムプロンプトが保持されることを確認 /// 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![]);