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.
- 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.
- 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

View File

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

View File

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

View File

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

View File

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

View File

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