yoi/crates/llm-worker/docs/spec/worker.md
2026-04-04 04:27:46 +09:00

461 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Worker 設計
## 概要
`Worker`はアプリケーションの「ターン」を制御する高レベルコンポーネントです。
`LlmClient`と`Timeline`を内包し、ユーザー定義の`Tool`と`Hook`を用いて自律的なインタラクションを行います。
Type-stateパターンにより、`Mutable`(編集可能)と`CacheLocked`キャッシュ保護の2つの状態を持ちます。
## アーキテクチャ
```mermaid
graph TD
User[Application / User] -->|1. Run| Worker
Worker -->|2. Event Loop| Timeline
Timeline -->|3. Dispatch| Handler[Handlers (TextBlockCollector, ToolCallCollector)]
subgraph "Worker Layer"
Worker
Hook[HookRegistry]
ToolServer[ToolServerHandle]
end
subgraph "Core Layer"
Timeline
LlmClient
end
Worker -.->|Intervene| Hook
ToolServer -.->|Execute| Tool[User Defined Tools]
Worker -.->|Subscribe| Subscriber[WorkerSubscriber]
```
## Worker 構造体
```rust
pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
client: C,
timeline: Timeline,
text_block_collector: TextBlockCollector,
tool_call_collector: ToolCallCollector,
tool_server: ToolServerHandle,
hooks: HookRegistry,
system_prompt: Option<String>,
history: Vec<Item>,
locked_prefix_len: usize,
turn_count: usize,
turn_notifiers: Vec<Box<dyn TurnNotifier>>,
request_config: RequestConfig,
last_run_interrupted: bool,
cancel_tx: mpsc::Sender<()>,
cancel_rx: mpsc::Receiver<()>,
_state: PhantomData<S>,
}
```
## 状態遷移 (Type-state)
| 状態 | 説明 | 利用可能な操作 |
| --- | --- | --- |
| `Mutable` | 初期状態。自由に編集可能 | system_prompt設定、history編集、tool/hook登録、lock() |
| `CacheLocked` | キャッシュ保護状態 | historyへの追記のみ、unlock() |
```rust
// Mutable → CacheLocked
let locked = worker.lock();
// CacheLocked → Mutable
let mutable = locked.unlock();
```
`CacheLocked`状態ではLLM APIのKVキャッシュヒットを確保するため、
システムプロンプトと既存の履歴(ロック時点のプレフィックス)は不変となります。
## ライフサイクル (ターン制御)
Workerは以下のループターンを実行します。
1. **Start Turn**: `Worker::run(user_input)` 呼び出し
2. **Hook: OnPromptSubmit**:
- ユーザーメッセージ(`Item`)の改変、バリデーション、キャンセルが可能。
- `Cancel` を返すと `WorkerError::Aborted` で終了。
3. **Resume Check**:
- 未応答のToolCallPauseから復帰した場合等があれば、先にツール実行を行う。
4. **Turn Loop**:
1. **Hook: PreLlmRequest**:
- LLMリクエスト送信前にコンテキスト`Vec<Item>`)を改変可能。
- `Cancel` を返すとターン中断。
2. **Request & Stream**:
- LLMへリクエスト送信。イベントストリーム開始。
- `Timeline`によるイベント処理(`TextBlockCollector`, `ToolCallCollector`)。
- ストリーム中に **Hook: OnStreamChunk**, **OnTextDelta**, **OnToolCallDelta** を実行。
- ストリーム完了時に **Hook: OnStreamComplete** を実行。
3. **Tool Handling (Parallel)**:
- レスポンス内に含まれる全てのTool Callを収集。
- 各Toolに対して **Hook: PreToolCall** を実行実行可否、引数改変、Pause
- 許可されたToolを**並列実行 (`join_all`)**`ToolServerHandle`経由)。
- 各Tool実行後に **Hook: PostToolCall** を実行(結果の確認、加工)。
4. **Next Request Decision**:
- Tool実行結果がある場合 -> 結果を`Item::FunctionCallOutput`としてhistoryに追加し、**Step 4.1へ戻る**(自動ループ)。
- Tool実行がない場合 -> Step 5へ。
5. **Hook: OnTurnEnd**:
- 最終的な応答に対するチェックLint/Fmtなど
- `ContinueWithMessages(items)`: 追加メッセージをhistoryに追加して**Step 4.1へ戻る**ことで自己修正を促せる。
- `Paused`: `WorkerResult::Paused` を返し、後で `resume()` で再開可能。
- `Finish`: ターン正常終了。
## キャンセル機構
`cancel_tx` / `cancel_rx` チャネルによる非同期キャンセルをサポートします。
```rust
// キャンセルトリガーの取得
let sender = worker.cancel_sender();
// 別タスクからキャンセル
sender.try_send(()).ok();
// または直接
worker.cancel();
```
キャンセルはストリーム開始前、ストリーム受信中、ツール実行中のいずれのタイミングでも
`tokio::select!` により検出され、`WorkerError::Cancelled` を返します。
## 実行結果
```rust
pub enum WorkerResult {
/// 完了(ユーザー入力待ち)
Finished,
/// 一時停止resume()で再開可能)
Paused,
}
```
`resume()` メソッドにより、Paused状態からユーザーメッセージを追加せずにターン処理を再開できます。
## Hook 設計
### Hook Trait
```rust
#[async_trait]
pub trait Hook<E: HookEventKind>: Send + Sync {
async fn call(&self, input: &mut E::Input) -> Result<E::Output, HookError>;
}
pub trait HookEventKind: Send + Sync + 'static {
type Input;
type Output;
}
```
### Hook イベント一覧
| Hook | Input | Output | タイミング |
| --- | --- | --- | --- |
| `OnPromptSubmit` | `Item` | `OnPromptSubmitResult` | `run()` 直後、ユーザーメッセージ受信時 |
| `PreLlmRequest` | `Vec<Item>` | `PreLlmRequestResult` | 各ターンのLLMリクエスト送信前 |
| `PreToolCall` | `ToolCallContext` | `PreToolCallResult` | 各ツール実行前 |
| `PostToolCall` | `PostToolCallContext` | `PostToolCallResult` | 各ツール実行後 |
| `OnTurnEnd` | `Vec<Item>` | `OnTurnEndResult` | ツール呼び出しなしでターン終了時 |
| `OnAbort` | `String` | `()` | エラーまたはキャンセルで中断時 |
| `OnTextDelta` | `TextDeltaContext` | `StreamHookResult` | テキストデルタ受信時 |
| `OnToolCallDelta` | `ToolCallDeltaContext` | `StreamHookResult` | ツール呼び出しJSON断片受信時 |
| `OnStreamChunk` | `StreamChunkContext` | `StreamHookResult` | ストリームイベント受信時 |
| `OnStreamComplete` | `StreamCompleteContext` | `StreamHookResult` | ストリーム完了時 |
### Hook 結果型
```rust
pub enum OnPromptSubmitResult {
Continue,
Cancel(String),
}
pub enum PreLlmRequestResult {
Continue,
Cancel(String),
}
pub enum PreToolCallResult {
Continue,
Skip, // ツール実行をスキップ
Abort(String), // 処理中断
Pause, // 一時停止resume()で再開)
}
pub enum PostToolCallResult {
Continue,
Abort(String),
}
pub enum OnTurnEndResult {
Finish,
ContinueWithMessages(Vec<Item>), // メッセージを追加してターン継続
Paused,
}
pub enum StreamHookResult {
Continue,
Abort(String),
Pause,
}
```
### Tool Call Context
`PreToolCall` / `PostToolCall` は、ツール実行の文脈を含むコンテキストを受け取ります。
```rust
pub struct ToolCallContext {
pub call: ToolCall, // 呼び出し情報(改変可能)
pub meta: ToolMeta, // メタ情報(読み取り専用)
pub tool: Arc<dyn Tool>, // インスタンス(状態アクセス用)
}
pub struct PostToolCallContext {
pub call: ToolCall, // 呼び出し情報
pub result: ToolResult, // 実行結果(改変可能)
pub meta: ToolMeta, // メタ情報(読み取り専用)
pub tool: Arc<dyn Tool>, // インスタンス(状態アクセス用)
}
```
### ストリーミングHookのコンテキスト
```rust
pub struct TextDeltaContext {
pub index: usize,
pub delta: String,
}
pub struct ToolCallDeltaContext {
pub index: usize,
pub delta_json_fragment: String,
}
pub struct StreamChunkContext {
pub event: crate::event::Event,
}
pub struct StreamCompleteContext {
pub turn: usize,
pub event_count: usize,
}
```
### Hook 登録API
```rust
worker.add_on_prompt_submit_hook(my_hook);
worker.add_pre_llm_request_hook(my_hook);
worker.add_pre_tool_call_hook(my_hook);
worker.add_post_tool_call_hook(my_hook);
worker.add_on_turn_end_hook(my_hook);
worker.add_on_abort_hook(my_hook);
worker.add_on_text_delta_hook(my_hook);
worker.add_on_tool_call_delta_hook(my_hook);
worker.add_on_stream_chunk_hook(my_hook);
worker.add_on_stream_complete_hook(my_hook);
```
## Worker Event API (Subscriber)
### 背景と目的
Workerは内部でイベントを処理し結果を返しますが、UIへのストリーミング表示やリアルタイムフィードバックには、イベントを外部に公開する仕組みが必要です。
### WorkerSubscriber Trait
`WorkerSubscriber`トレイトを実装し、`worker.subscribe()` で一括登録します。
```rust
pub trait WorkerSubscriber: Send {
// スコープ型(ブロックイベント用)
type TextBlockScope: Default + Send + Sync;
type ToolUseBlockScope: Default + Send + Sync;
// === ブロックイベント(スコープ管理あり)===
fn on_text_block(
&mut self,
scope: &mut Self::TextBlockScope,
event: &TextBlockEvent,
) {}
fn on_tool_use_block(
&mut self,
scope: &mut Self::ToolUseBlockScope,
event: &ToolUseBlockEvent,
) {}
// === 単発イベント ===
fn on_usage(&mut self, event: &UsageEvent) {}
fn on_status(&mut self, event: &StatusEvent) {}
fn on_error(&mut self, event: &ErrorEvent) {}
// === 累積イベントWorker層で追加===
fn on_text_complete(&mut self, text: &str) {}
fn on_tool_call_complete(&mut self, call: &ToolCall) {}
// === ターン制御 ===
fn on_turn_start(&mut self, turn: usize) {}
fn on_turn_end(&mut self, turn: usize) {}
}
```
### 内部実装
SubscriberはWorker内部でAdapter群に分解され、各KindのHandlerとしてTimelineに登録されます。
累積イベント(`on_text_complete`, `on_tool_call_complete`はAdapter内でブロック終了時にバッファから合成されます。
- `TextBlockSubscriberAdapter`: テキストデルタを蓄積し、Stop時に`on_text_complete`を呼ぶ
- `ToolUseBlockSubscriberAdapter`: ID・名前・JSONを蓄積し、Stop時に`on_tool_call_complete`を呼ぶ
- `UsageSubscriberAdapter`, `StatusSubscriberAdapter`, `ErrorSubscriberAdapter`: 単純な転送
- `SubscriberTurnNotifier`: ターン開始・終了の通知
### 使用例
```rust
struct StreamPrinter;
impl WorkerSubscriber for StreamPrinter {
type TextBlockScope = ();
type ToolUseBlockScope = ();
fn on_text_block(&mut self, _: &mut (), event: &TextBlockEvent) {
if let TextBlockEvent::Delta(text) = event {
print!("{}", text);
}
}
fn on_text_complete(&mut self, text: &str) {
println!("\n--- Complete: {} chars ---", text.len());
}
}
worker.subscribe(StreamPrinter);
let result = worker.run("Hello!").await?;
```
## Worker API
### 生成と設定 (Mutable状態のみ)
```rust
// 生成(ビルダーパターン)
let worker = Worker::new(client)
.system_prompt("You are a helpful assistant.")
.max_tokens(4096)
.temperature(0.7)
.top_p(0.9)
.top_k(40)
.stop_sequence("\n\n")
.with_config(request_config)
.with_item(item)
.with_items(items);
// バリデーション(プロバイダの対応確認)
let worker = worker.validate()?;
// ミューテーション
worker.set_system_prompt("...");
worker.set_max_tokens(4096);
worker.set_temperature(0.7);
worker.set_top_p(0.9);
worker.set_top_k(40);
worker.add_stop_sequence("\n\n");
worker.clear_stop_sequences();
worker.set_request_config(config);
// 履歴操作
worker.push_item(item);
worker.extend_history(items);
worker.set_history(items);
worker.clear_history();
worker.history_mut(); // &mut Vec<Item>
// ツール登録
worker.register_tool(factory)?;
worker.register_tools(factories)?;
```
### 全状態で利用可能
```rust
// 実行
let result = worker.run("user input").await?;
let result = worker.resume().await?;
// キャンセル
worker.cancel();
let sender = worker.cancel_sender();
// 参照
worker.history(); // &[Item]
worker.get_system_prompt(); // Option<&str>
worker.turn_count(); // usize
worker.request_config(); // &RequestConfig
worker.last_run_interrupted(); // bool
worker.is_cancelled(); // bool
// ツールサーバー
worker.tool_server_handle(); // ToolServerHandle
// Timeline直接アクセス
worker.timeline_mut(); // &mut Timeline
// イベント購読
worker.subscribe(my_subscriber);
// Hook登録
worker.add_on_prompt_submit_hook(hook);
worker.add_pre_llm_request_hook(hook);
worker.add_pre_tool_call_hook(hook);
worker.add_post_tool_call_hook(hook);
worker.add_on_turn_end_hook(hook);
worker.add_on_abort_hook(hook);
worker.add_on_text_delta_hook(hook);
worker.add_on_tool_call_delta_hook(hook);
worker.add_on_stream_chunk_hook(hook);
worker.add_on_stream_complete_hook(hook);
```
## エラー型
```rust
pub enum WorkerError {
Client(ClientError),
Tool(ToolError),
Hook(HookError),
Aborted(String),
Cancelled,
ConfigWarnings(Vec<ConfigWarning>),
}
pub enum ToolRegistryError {
DuplicateName(String),
}
```
## 公開エクスポート
`lib.rs` から以下がre-exportされています。
```rust
pub use message::{ContentPart, Item, Message, Role};
pub use worker::{ToolRegistryError, Worker, WorkerConfig, WorkerError, WorkerResult};
```
モジュール:
- `pub mod event`
- `pub mod hook`
- `pub mod llm_client`
- `pub mod state`
- `pub mod subscriber`
- `pub mod timeline`
- `pub mod tool`
- `pub mod tool_server`