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

14 KiB
Raw Blame History

Worker 設計

概要

Workerはアプリケーションの「ターン」を制御する高レベルコンポーネントです。 LlmClientTimelineを内包し、ユーザー定義のToolHookを用いて自律的なインタラクションを行います。

Type-stateパターンにより、Mutable(編集可能)とCacheLockedキャッシュ保護の2つの状態を持ちます。

アーキテクチャ

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 構造体

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()
// 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 チャネルによる非同期キャンセルをサポートします。

// キャンセルトリガーの取得
let sender = worker.cancel_sender();

// 別タスクからキャンセル
sender.try_send(()).ok();

// または直接
worker.cancel();

キャンセルはストリーム開始前、ストリーム受信中、ツール実行中のいずれのタイミングでも tokio::select! により検出され、WorkerError::Cancelled を返します。

実行結果

pub enum WorkerResult {
    /// 完了(ユーザー入力待ち)
    Finished,
    /// 一時停止resume()で再開可能)
    Paused,
}

resume() メソッドにより、Paused状態からユーザーメッセージを追加せずにターン処理を再開できます。

Hook 設計

Hook Trait

#[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 結果型

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 は、ツール実行の文脈を含むコンテキストを受け取ります。

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のコンテキスト

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

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() で一括登録します。

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: ターン開始・終了の通知

使用例

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状態のみ)

// 生成(ビルダーパターン)
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)?;

全状態で利用可能

// 実行
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);

エラー型

pub enum WorkerError {
    Client(ClientError),
    Tool(ToolError),
    Hook(HookError),
    Aborted(String),
    Cancelled,
    ConfigWarnings(Vec<ConfigWarning>),
}

pub enum ToolRegistryError {
    DuplicateName(String),
}

公開エクスポート

lib.rs から以下がre-exportされています。

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