alpha-release: 0.0.1 #1

Merged
Hare merged 18 commits from develop into master 2026-01-08 20:40:25 +09:00
13 changed files with 554 additions and 114 deletions
Showing only changes of commit 9233bb9163 - Show all commits

View File

@ -0,0 +1,109 @@
---
description: ドキュメントコメントの書き方ガイドライン
---
# ドキュメントコメント スタイルガイド
## 基本原則
1. **利用者視点で書く**: 「何をするものか」「どう使うか」を先に、「なぜそう実装したか」は後に
2. **型パラメータはバッククォートで囲む**: `Handler<K>` ✓ / Handler<K>
3. **Examplesは`worker::`パスで書く**: re-export先のパスを使用
## 構造テンプレート
```rust
/// [1行目: 何をするものか - 利用者が最初に知りたいこと]
///
/// [詳細説明: いつ使うか、なぜ使うか、注意点など]
///
/// # Examples
///
/// ```
/// use worker::SomeType;
///
/// let instance = SomeType::new();
/// instance.do_something();
/// ```
///
/// # Notes (オプション)
///
/// 実装上の注意事項や制限があれば記載
pub struct SomeType { ... }
```
## 良い例・悪い例
### 構造体/Trait
```rust
// ❌ 悪い例(実装視点)
/// Handler<K>からErasedHandler<K>へのラッパー
/// 各Handlerは独自のScope型を持つため、Timelineで保持するには型消去が必要
// ✅ 良い例(利用者視点)
/// `Handler<K>`を`ErasedHandler<K>`として扱うためのラッパー
///
/// 通常は直接使用せず、`Timeline::on_text_block()`などのメソッド経由で
/// 自動的にラップされます。
```
### メソッド
```rust
// ❌ 悪い例(処理内容の説明のみ)
/// ツールを登録する
// ✅ 良い例(何が起きるか、どう使うか)
/// ツールを登録する
///
/// 登録されたツールはLLMからの呼び出しで自動的に実行されます。
/// 同名のツールを登録した場合、後から登録したものが優先されます。
///
/// # Examples
///
/// ```
/// use worker::{Worker, Tool};
///
/// worker.register_tool(MyTool::new());
/// ```
```
### 型パラメータ
```rust
// ❌ HTMLタグとして解釈されてしまう
/// Handler<K>を保持するフィールド
// ✅ バッククォートで囲む
/// `Handler<K>`を保持するフィールド
```
## ドキュメントの配置
| 項目 | 配置場所 |
|-----|---------|
| 型/trait/関数のdoc | 定義元のクレートworker-types等 |
| モジュールdoc (`//!`) | 各クレートのlib.rsに書く |
| 実装詳細 | 実装コメント (`//`) を使用 |
| 利用者向けでない内部型 | `#[doc(hidden)]`または`pub(crate)` |
## Examplesのuseパス
re-exportされる型のExamplesでは、最終的な公開パスを使用:
```rust
// worker-types/src/tool.rs でも
/// # Examples
/// ```
/// use worker::Tool; // ✓ worker_types::Tool ではなく
/// ```
```
## チェックリスト
- [ ] 1行目は「何をするものか」を利用者視点で説明しているか
- [ ] 型パラメータ (`<T>`, `<K>` 等) はバッククォートで囲んでいるか
- [ ] 主要なpub APIにはExamplesがあるか
- [ ] Examplesの`use`パスは`worker::`になっているか
- [ ] `cargo doc --no-deps`で警告が出ないか

View File

@ -1,6 +1,7 @@
//! イベント型定義
//! イベント型
//!
//! llm_client層が出力するフラットなイベント列挙と関連型
//! LLMからのストリーミングレスポンスを表現するイベント型。
//! Timeline層がこのイベントを受信し、ハンドラにディスパッチします。
use serde::{Deserialize, Serialize};
@ -8,21 +9,38 @@ use serde::{Deserialize, Serialize};
// Core Event Types (from llm_client layer)
// =============================================================================
/// llm_client層が出力するフラットなイベント列挙
/// LLMからのストリーミングイベント
///
/// Timeline層がこのイベントストリームを受け取り、ブロック構造化を行う
/// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして
/// 統一的に処理されます。
///
/// # イベントの種類
///
/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error`
/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort`
///
/// # ブロックのライフサイクル
///
/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
/// の順序でイベントが発生します。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Event {
// Meta events (not tied to a block)
/// ハートビート
Ping(PingEvent),
/// トークン使用量
Usage(UsageEvent),
/// ストリームのステータス変化
Status(StatusEvent),
/// エラー発生
Error(ErrorEvent),
// Block lifecycle events
/// ブロック開始(テキスト、ツール使用等)
BlockStart(BlockStart),
/// ブロックの差分データ
BlockDelta(BlockDelta),
/// ブロック正常終了
BlockStop(BlockStop),
/// ブロック中断
BlockAbort(BlockAbort),
}

View File

@ -1,6 +1,8 @@
//! Handler/Kind関連の定義
//! Handler/Kind
//!
//! Timeline層でのイベント処理に使用するトレイトとKind定義
//! Timeline層でイベントを処理するためのトレイト。
//! カスタムハンドラを実装してTimelineに登録することで、
//! ストリームイベントを受信できます。
use crate::event::*;
@ -8,10 +10,11 @@ use crate::event::*;
// Kind Trait
// =============================================================================
/// Kindはイベント型のみを定義する
/// イベント種別を定義するマーカートレイト
///
/// スコープはHandler側で定義するため、同じKindに対して
/// 異なるスコープを持つHandlerを登録できる
/// 各Kindは対応するイベント型を指定します。
/// HandlerはこのKindに対して実装され、同じKindに対して
/// 異なるScope型を持つ複数のHandlerを登録できます。
pub trait Kind {
/// このKindに対応するイベント型
type Event;
@ -21,9 +24,39 @@ pub trait Kind {
// Handler Trait
// =============================================================================
/// Kindに対する処理を定義し、自身のスコープ型も決定する
/// イベントを処理するハンドラトレイト
///
/// 特定の`Kind`に対するイベント処理を定義します。
/// `Scope`はブロックのライフサイクル中に保持される状態です。
///
/// # Examples
///
/// ```ignore
/// use worker::{Handler, TextBlockKind, TextBlockEvent};
///
/// struct TextCollector {
/// texts: Vec<String>,
/// }
///
/// impl Handler<TextBlockKind> for TextCollector {
/// type Scope = String; // ブロックごとのバッファ
///
/// fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) {
/// match event {
/// TextBlockEvent::Delta(text) => buffer.push_str(text),
/// TextBlockEvent::Stop(_) => {
/// self.texts.push(std::mem::take(buffer));
/// }
/// _ => {}
/// }
/// }
/// }
/// ```
pub trait Handler<K: Kind> {
/// Handler固有のスコープ型
///
/// ブロック開始時に`Default::default()`で生成され、
/// ブロック終了時に破棄されます。
type Scope: Default;
/// イベントを処理する

View File

@ -101,15 +101,50 @@ pub enum HookError {
// WorkerHook Trait
// =============================================================================
/// Worker Hook trait
/// ターンの進行・ツール実行に介入するためのトレイト
///
/// ターンの進行・メッセージ・ツール実行に対して介入するためのトレイト。
/// デフォルト実装では何も行わずContinueを返す。
/// Hookを使うと、メッセージ送信前、ツール実行前後、ターン終了時に
/// 処理を挟んだり、実行をキャンセルしたりできます。
///
/// # Examples
///
/// ```ignore
/// use worker::{WorkerHook, ControlFlow, HookError, ToolCall, TurnResult, Message};
///
/// struct ValidationHook;
///
/// #[async_trait::async_trait]
/// impl WorkerHook for ValidationHook {
/// async fn before_tool_call(&self, call: &mut ToolCall) -> Result<ControlFlow, HookError> {
/// // 危険なツールをブロック
/// if call.name == "delete_all" {
/// return Ok(ControlFlow::Skip);
/// }
/// Ok(ControlFlow::Continue)
/// }
///
/// async fn on_turn_end(&self, messages: &[Message]) -> Result<TurnResult, HookError> {
/// // 条件を満たさなければ追加メッセージで継続
/// if messages.len() < 3 {
/// return Ok(TurnResult::ContinueWithMessages(vec![
/// Message::user("Please elaborate.")
/// ]));
/// }
/// Ok(TurnResult::Finish)
/// }
/// }
/// ```
///
/// # デフォルト実装
///
/// すべてのメソッドにはデフォルト実装があり、何も行わず`Continue`を返します。
/// 必要なメソッドのみオーバーライドしてください。
#[async_trait]
pub trait WorkerHook: Send + Sync {
/// メッセージ送信前
/// メッセージ送信前に呼ばれる
///
/// リクエストに含まれるメッセージリストを改変できる。
/// リクエストに含まれるメッセージリストを参照・改変できます。
/// `ControlFlow::Abort`を返すとターンが中断されます。
async fn on_message_send(
&self,
_context: &mut Vec<crate::Message>,
@ -117,16 +152,17 @@ pub trait WorkerHook: Send + Sync {
Ok(ControlFlow::Continue)
}
/// ツール実行前
/// ツール実行前に呼ばれる
///
/// 実行をキャンセルしたり、引数を書き換えることができる。
/// ツール呼び出しの引数を書き換えたり、実行をスキップしたりできます。
/// `ControlFlow::Skip`を返すとこのツールの実行がスキップされます。
async fn before_tool_call(&self, _tool_call: &mut ToolCall) -> Result<ControlFlow, HookError> {
Ok(ControlFlow::Continue)
}
/// ツール実行後
/// ツール実行後に呼ばれる
///
/// 結果を書き換えたり、隠蔽したりでき
/// ツールの実行結果を書き換えたり、隠蔽したりできます
async fn after_tool_call(
&self,
_tool_result: &mut ToolResult,
@ -134,9 +170,11 @@ pub trait WorkerHook: Send + Sync {
Ok(ControlFlow::Continue)
}
/// ターン終了時
/// ターン終了時に呼ばれる
///
/// 生成されたメッセージを検査し、必要ならリトライを指示できる。
/// 生成されたメッセージを検査し、必要なら追加メッセージで継続を指示できます。
/// `TurnResult::ContinueWithMessages`を返すと、指定したメッセージを追加して
/// 次のターンに進みます。
async fn on_turn_end(&self, _messages: &[crate::Message]) -> Result<TurnResult, HookError> {
Ok(TurnResult::Finish)
}

View File

@ -1,12 +1,11 @@
//! worker-types - LLMワーカーで使用される型定義
//! worker-types - LLMワーカー型定義
//!
//! このクレートは以下を提供します:
//! - Event: llm_client層からのフラットなイベント列挙
//! - Kind/Handler: タイムライン層でのイベント処理トレイト
//! - Tool: ツール定義トレイト
//! - Hook: Worker層での介入用トレイト
//! - Message: メッセージ型
//! - 各種イベント構造体
//! このクレートは`worker`クレートで使用される型を提供します。
//! 通常は直接使用せず、`worker`クレート経由で利用してください。
//!
//! ```ignore
//! use worker::{Event, Message, Tool, WorkerHook};
//! ```
mod event;
mod handler;

View File

@ -1,6 +1,7 @@
//! メッセージ型定義
//! メッセージ型
//!
//! LLM会話で使用されるメッセージ構造
//! LLMとの会話で使用されるメッセージ構造。
//! [`Message::user`]や[`Message::assistant`]で簡単に作成できます。
use serde::{Deserialize, Serialize};
@ -14,7 +15,19 @@ pub enum Role {
Assistant,
}
/// メッセージ
/// 会話のメッセージ
///
/// # Examples
///
/// ```ignore
/// use worker::Message;
///
/// // ユーザーメッセージ
/// let user_msg = Message::user("Hello!");
///
/// // アシスタントメッセージ
/// let assistant_msg = Message::assistant("Hi there!");
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
/// ロール
@ -62,6 +75,13 @@ pub enum ContentPart {
impl Message {
/// ユーザーメッセージを作成
///
/// # Examples
///
/// ```ignore
/// use worker::Message;
/// let msg = Message::user("こんにちは");
/// ```
pub fn user(content: impl Into<String>) -> Self {
Self {
role: Role::User,
@ -70,6 +90,9 @@ impl Message {
}
/// アシスタントメッセージを作成
///
/// 通常はWorker内部で自動生成されますが、
/// 履歴の初期化などで手動作成も可能です。
pub fn assistant(content: impl Into<String>) -> Self {
Self {
role: Role::Assistant,
@ -78,6 +101,9 @@ impl Message {
}
/// ツール結果メッセージを作成
///
/// Worker内部でツール実行後に自動生成されます。
/// 通常は直接作成する必要はありません。
pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
role: Role::User,

View File

@ -1,24 +1,41 @@
//! Worker状態マーカー型
//! Worker状態
//!
//! Type-stateパターンによるキャッシュ保護のための状態定義
//! Type-stateパターンによるキャッシュ保護のための状態マーカー型。
//! Workerは`Mutable` → `Locked`の状態遷移を持ちます。
/// Worker状態を表すマーカートレイト
///
/// このトレイトはシールされており、外部から実装することはできない
/// このトレイトはシールされており、外部から実装することはできません
pub trait WorkerState: private::Sealed + Send + Sync + 'static {}
mod private {
pub trait Sealed {}
}
/// 変更可能状態
/// 編集可能状態
///
/// この状態では以下の操作が可能:
/// この状態では以下の操作が可能です:
/// - システムプロンプトの設定・変更
/// - メッセージ履歴の編集(追加、削除、クリア)
/// - ツール・Hookの登録
///
/// `lock()` によって `Locked` 状態へ遷移できる。
/// `Worker::lock()`により[`Locked`]状態へ遷移できます。
///
/// # Examples
///
/// ```ignore
/// use worker::Worker;
///
/// let mut worker = Worker::new(client)
/// .system_prompt("You are helpful.");
///
/// // 履歴を編集可能
/// worker.push_message(Message::user("Hello"));
/// worker.clear_history();
///
/// // ロックして保護状態へ
/// let locked = worker.lock();
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct Mutable;
@ -27,12 +44,15 @@ impl WorkerState for Mutable {}
/// ロック状態(キャッシュ保護)
///
/// この状態では以下の制限がある:
/// この状態では以下の制限があります:
/// - システムプロンプトの変更不可
/// - 既存メッセージ履歴の変更不可(末尾への追記のみ)
///
/// 実行(`run`)はこの状態で行うことが推奨される。
/// キャッシュヒットを保証するため、前方のコンテキストは不変となる。
/// LLM APIのKVキャッシュヒットを保証するため、
/// 実行時にはこの状態の使用が推奨されます。
///
/// `Worker::unlock()`により[`Mutable`]状態へ戻せますが、
/// キャッシュ保護が解除されることに注意してください。
#[derive(Debug, Clone, Copy, Default)]
pub struct Locked;

View File

@ -1,7 +1,7 @@
//! WorkerSubscriber - Worker層のイベント購読トレイト
//! イベント購読
//!
//! Timeline層のHandler機構の薄いラッパーとして設計され、
//! UIへのストリーミング表示やリアルタイムフィードバックを可能にする
//! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。
//! UIへのストリーム表示やプログレス表示に使用します
use crate::{ErrorEvent, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent, UsageEvent};
@ -9,39 +9,42 @@ use crate::{ErrorEvent, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent
// WorkerSubscriber Trait
// =============================================================================
/// Worker層の統合Subscriberトレイト
/// LLMからのストリーミングイベントを購読するトレイト
///
/// Timeline層のHandler機構をラップし、以下のイベントを一括で購読できる:
/// - ブロックイベント(スコープ管理あり): Text, ToolUse
/// - 単発イベント: Usage, Status, Error
/// - 累積イベントWorker層で追加: TextComplete, ToolCallComplete
/// - ターン制御: TurnStart, TurnEnd
/// Workerに登録すると、テキスト生成やツール呼び出しのイベントを
/// リアルタイムで受信できます。UIへのストリーム表示に最適です。
///
/// # 使用例
/// # 受信できるイベント
///
/// - **ブロックイベント**: テキスト、ツール使用(スコープ付き)
/// - **メタイベント**: 使用量、ステータス、エラー
/// - **完了イベント**: テキスト完了、ツール呼び出し完了
/// - **ターン制御**: ターン開始、ターン終了
///
/// # Examples
///
/// ```ignore
/// struct MyUI {
/// chat_view: ChatView,
/// }
/// use worker::{WorkerSubscriber, TextBlockEvent};
///
/// impl WorkerSubscriber for MyUI {
/// type TextBlockScope = String;
/// type ToolUseBlockScope = ToolComponent;
/// struct StreamPrinter;
///
/// fn on_text_block(&mut self, buffer: &mut String, event: &TextBlockEvent) {
/// match event {
/// TextBlockEvent::Delta(text) => {
/// buffer.push_str(text);
/// self.chat_view.update(buffer);
/// }
/// _ => {}
/// 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) {
/// self.chat_view.add_to_history(text);
/// println!("\n--- Complete: {} chars ---", text.len());
/// }
/// }
///
/// // Workerに登録
/// worker.subscribe(StreamPrinter);
/// ```
pub trait WorkerSubscriber: Send {
// =========================================================================

View File

@ -1,33 +1,90 @@
//! ツール定義
//!
//! LLMから呼び出し可能なツールを定義するためのトレイト。
//! 通常は`#[tool]`マクロを使用して自動実装します。
use async_trait::async_trait;
use serde_json::Value;
use thiserror::Error;
/// ツール実行時のエラー
#[derive(Debug, Error)]
pub enum ToolError {
/// 引数が不正
#[error("Invalid argument: {0}")]
InvalidArgument(String),
/// 実行に失敗
#[error("Execution failed: {0}")]
ExecutionFailed(String),
/// 内部エラー
#[error("Internal error: {0}")]
Internal(String),
}
/// ツール定義トレイト
/// LLMから呼び出し可能なツール定義するトレイト
///
/// ユーザー定義のツールはこれを実装し、Workerに登録される。
/// 通常は `#[tool]` マクロによって自動生成される。
/// ツールはLLMが外部リソースにアクセスしたり、
/// 計算を実行したりするために使用します。
///
/// # 実装方法
///
/// 通常は`#[tool]`マクロを使用して自動実装します:
///
/// ```ignore
/// use worker::tool;
///
/// #[tool(description = "Search the web for information")]
/// async fn search(query: String) -> String {
/// // 検索処理
/// format!("Results for: {}", query)
/// }
/// ```
///
/// # 手動実装
///
/// ```ignore
/// use worker::{Tool, ToolError};
/// use serde_json::{json, Value};
///
/// struct MyTool;
///
/// #[async_trait::async_trait]
/// impl Tool for MyTool {
/// fn name(&self) -> &str { "my_tool" }
/// fn description(&self) -> &str { "My custom tool" }
/// fn input_schema(&self) -> Value {
/// json!({
/// "type": "object",
/// "properties": {
/// "query": { "type": "string" }
/// },
/// "required": ["query"]
/// })
/// }
/// async fn execute(&self, input: &str) -> Result<String, ToolError> {
/// Ok("result".to_string())
/// }
/// }
/// ```
#[async_trait]
pub trait Tool: Send + Sync {
/// ツール名 (LLMが識別に使用)
/// ツール名LLMが識別に使用
fn name(&self) -> &str;
/// ツールの説明 (LLMへのプロンプトに含まれる)
/// ツールの説明LLMへのプロンプトに含まれる
fn description(&self) -> &str;
/// 引数のJSON Schema
///
/// LLMはこのスキーマに従って引数を生成します。
fn input_schema(&self) -> Value;
/// 実行関数
/// JSON文字列を受け取り、デシリアライズして元のメソッドを実行し、結果を返す
/// ツールを実行する
///
/// # Arguments
/// * `input_json` - LLMが生成したJSON形式の引数
///
/// # Returns
/// 実行結果の文字列。この内容がLLMに返されます。
async fn execute(&self, input_json: &str) -> Result<String, ToolError>;
}

View File

@ -1,10 +1,39 @@
//! worker - LLMワーカーのメイン実装
//! worker - LLMワーカーライブラリ
//!
//! このクレートは以下を提供します:
//! - Worker: ターン制御を行う高レベルコンポーネント
//! - Timeline: イベントストリームの状態管理とハンドラーへのディスパッチ
//! - LlmClient: LLMプロバイダとの通信
//! - 型消去されたHandler実装
//! LLMとの対話を管理するコンポーネントを提供します。
//!
//! # 主要なコンポーネント
//!
//! - [`Worker`] - LLMとの対話を管理する中心コンポーネント
//! - [`Tool`] - LLMから呼び出し可能なツール
//! - [`WorkerHook`] - ターン進行への介入
//! - [`WorkerSubscriber`] - ストリーミングイベントの購読
//!
//! # Quick Start
//!
//! ```ignore
//! use worker::{Worker, Message};
//!
//! // Workerを作成
//! let mut worker = Worker::new(client)
//! .system_prompt("You are a helpful assistant.");
//!
//! // ツールを登録(オプション)
//! worker.register_tool(my_tool);
//!
//! // 対話を実行
//! let history = worker.run("Hello!").await?;
//! ```
//!
//! # キャッシュ保護
//!
//! KVキャッシュのヒット率を最大化するには、[`Worker::lock()`]で
//! ロック状態に遷移してから実行してください。
//!
//! ```ignore
//! let mut locked = worker.lock();
//! locked.run("user input").await?;
//! ```
pub mod llm_client;
mod subscriber_adapter;

View File

@ -1,12 +1,19 @@
//! LLMクライアント層
//!
//! LLMプロバイダと通信し、統一された`Event`ストリームを出力する。
//! 各LLMプロバイダと通信し、統一された[`Event`](crate::Event)ストリームを出力します。
//!
//! # サポートするプロバイダ
//!
//! - Anthropic (Claude)
//! - OpenAI (GPT-4, etc.)
//! - Google (Gemini)
//! - Ollama (ローカルLLM)
//!
//! # アーキテクチャ
//!
//! - **client**: `LlmClient` trait定義
//! - **scheme**: APIスキーマリクエスト/レスポンス変換)
//! - **providers**: プロバイダ固有のHTTPクライアント実装
//! - [`LlmClient`] - プロバイダ共通のtrait
//! - `providers`: プロバイダ固有のクライアント実装
//! - `scheme`: APIスキーマリクエスト/レスポンス変換)
pub mod client;
pub mod error;

View File

@ -1,6 +1,7 @@
//! Timeline層の実装
//! Timeline層
//!
//! イベントストリームを受信し、登録されたHandlerへディスパッチする
//! LLMからのイベントストリームを受信し、登録されたHandlerにディスパッチします。
//! 通常はWorker経由で使用しますが、直接使用することも可能です。
use std::marker::PhantomData;
@ -10,9 +11,11 @@ use worker_types::*;
// Type-erased Handler
// =============================================================================
/// 型消去されたHandler trait
/// 型消去された`Handler` trait
///
/// 各Handlerは独自のScope型を持つため、Timelineで保持するには型消去が必要
/// 各Handlerは独自のScope型を持つため、Timelineで保持するには型消去が必要です。
/// 通常は直接使用せず、`Timeline::on_text_block()`などのメソッド経由で
/// 自動的にラップされます。
pub trait ErasedHandler<K: Kind>: Send {
/// イベントをディスパッチ
fn dispatch(&mut self, event: &K::Event);
@ -22,7 +25,7 @@ pub trait ErasedHandler<K: Kind>: Send {
fn end_scope(&mut self);
}
/// Handler<K>からErasedHandler<K>へのラッパー
/// `Handler<K>`を`ErasedHandler<K>`として扱うためのラッパー
pub struct HandlerWrapper<H, K>
where
H: Handler<K>,
@ -316,13 +319,34 @@ where
// Timeline
// =============================================================================
/// Timeline - イベントストリームの状態管理とディスパッチ
/// イベントストリームの管理とハンドラへのディスパッチ
///
/// # 責務
/// 1. Eventストリームを受信
/// 2. Block系イベントをBlockKindごとのライフサイクルイベントに変換
/// 3. 各Handlerごとのスコープの生成・管理
/// 4. 登録されたHandlerへの登録順ディスパッチ
/// LLMからのイベントを受信し、登録されたハンドラに振り分けます。
/// ブロック系イベントはスコープ管理付きで処理されます。
///
/// # Examples
///
/// ```ignore
/// use worker::{Timeline, Handler, TextBlockKind, TextBlockEvent};
///
/// struct MyHandler;
/// impl Handler<TextBlockKind> for MyHandler {
/// type Scope = String;
/// fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) {
/// if let TextBlockEvent::Delta(text) = event {
/// buffer.push_str(text);
/// }
/// }
/// }
///
/// let mut timeline = Timeline::new();
/// timeline.on_text_block(MyHandler);
/// ```
///
/// # サポートするイベント種別
///
/// - **メタ系**: Usage, Ping, Status, Error
/// - **ブロック系**: TextBlock, ThinkingBlock, ToolUseBlock
pub struct Timeline {
// Meta系ハンドラー
usage_handlers: Vec<Box<dyn ErasedHandler<UsageKind>>>,

View File

@ -82,20 +82,40 @@ impl<S: WorkerSubscriber + 'static> TurnNotifier for SubscriberTurnNotifier<S> {
// Worker
// =============================================================================
/// Worker - ターン制御コンポーネント
/// LLMとの対話を管理する中心コンポーネント
///
/// Type-stateパターンによりキャッシュ保護を実現する。
/// ユーザーからの入力を受け取り、LLMにリクエストを送信し、
/// ツール呼び出しがあれば自動的に実行してターンを進行させます。
///
/// # 状態
/// - `Mutable`: 初期状態。システムプロンプトや履歴を自由に編集可能。
/// - `Locked`: キャッシュ保護状態。前方コンテキストは不変となり、追記のみ可能。
/// # 状態遷移Type-state
///
/// # 責務
/// - LLMへのリクエスト送信とレスポンス処理
/// - ツール呼び出しの収集と実行
/// - Hookによる介入の提供
/// - ターンループの制御
/// - 履歴の所有と管理
/// - [`Mutable`]: 初期状態。システムプロンプトや履歴を自由に編集可能。
/// - [`Locked`]: キャッシュ保護状態。`lock()`で遷移。前方コンテキストは不変。
///
/// # Examples
///
/// ```ignore
/// use worker::{Worker, Message};
///
/// // Workerを作成してツールを登録
/// let mut worker = Worker::new(client)
/// .system_prompt("You are a helpful assistant.");
/// worker.register_tool(my_tool);
///
/// // 対話を実行
/// let history = worker.run("Hello!").await?;
/// ```
///
/// # キャッシュ保護が必要な場合
///
/// ```ignore
/// let mut worker = Worker::new(client)
/// .system_prompt("...");
///
/// // 履歴を設定後、ロックしてキャッシュを保護
/// let mut locked = worker.lock();
/// locked.run("user input").await?;
/// ```
pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
/// LLMクライアント
client: C,
@ -128,13 +148,37 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
// =============================================================================
impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// WorkerSubscriberを登録
/// イベント購読者を登録する
///
/// Subscriberは以下のイベントを受け取ることができる:
/// - ブロックイベント: on_text_block, on_tool_use_block
/// - 単発イベント: on_usage, on_status, on_error
/// - 累積イベント: on_text_complete, on_tool_call_complete
/// - ターン制御: on_turn_start, on_turn_end
/// 登録したSubscriberは、LLMからのストリーミングイベントを
/// リアルタイムで受信できます。UIへのストリーム表示などに利用します。
///
/// # 受信できるイベント
///
/// - **ブロックイベント**: `on_text_block`, `on_tool_use_block`
/// - **メタイベント**: `on_usage`, `on_status`, `on_error`
/// - **完了イベント**: `on_text_complete`, `on_tool_call_complete`
/// - **ターン制御**: `on_turn_start`, `on_turn_end`
///
/// # Examples
///
/// ```ignore
/// use worker::{Worker, WorkerSubscriber, TextBlockEvent};
///
/// struct MyPrinter;
/// impl WorkerSubscriber for MyPrinter {
/// type TextBlockScope = ();
/// type ToolUseBlockScope = ();
///
/// fn on_text_block(&mut self, _: &mut (), event: &TextBlockEvent) {
/// if let TextBlockEvent::Delta(text) = event {
/// print!("{}", text);
/// }
/// }
/// }
///
/// worker.subscribe(MyPrinter);
/// ```
pub fn subscribe<Sub: WorkerSubscriber + 'static>(&mut self, subscriber: Sub) {
let subscriber = Arc::new(Mutex::new(subscriber));
@ -159,7 +203,19 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
.push(Box::new(SubscriberTurnNotifier { subscriber }));
}
/// ツールを登録
/// ツールを登録する
///
/// 登録されたツールはLLMからの呼び出しで自動的に実行されます。
/// 同名のツールを登録した場合、後から登録したものが優先されます。
///
/// # Examples
///
/// ```ignore
/// use worker::Worker;
/// use my_tools::SearchTool;
///
/// worker.register_tool(SearchTool::new());
/// ```
pub fn register_tool(&mut self, tool: impl Tool + 'static) {
let name = tool.name().to_string();
self.tools.insert(name, Arc::new(tool));
@ -172,7 +228,28 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
}
/// Hookを追加
/// Hookを追加する
///
/// Hookはターンの進行・ツール実行に介入できます。
/// 複数のHookを登録した場合、登録順に実行されます。
///
/// # Examples
///
/// ```ignore
/// use worker::{Worker, WorkerHook, ControlFlow, ToolCall};
///
/// struct LoggingHook;
///
/// #[async_trait::async_trait]
/// impl WorkerHook for LoggingHook {
/// async fn before_tool_call(&self, call: &mut ToolCall) -> Result<ControlFlow, HookError> {
/// println!("Calling tool: {}", call.name);
/// Ok(ControlFlow::Continue)
/// }
/// }
///
/// worker.add_hook(LoggingHook);
/// ```
pub fn add_hook(&mut self, hook: impl WorkerHook + 'static) {
self.hooks.push(Box::new(hook));
}