update: Merge worker-types crate into worker crate
This commit is contained in:
parent
a01f19b112
commit
7d398fa6de
|
|
@ -1,109 +0,0 @@
|
||||||
---
|
|
||||||
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`で警告が出ないか
|
|
||||||
29
AGENTS.md
29
AGENTS.md
|
|
@ -4,32 +4,3 @@
|
||||||
|
|
||||||
- クレートに依存関係を追加・更新する際は必ず
|
- クレートに依存関係を追加・更新する際は必ず
|
||||||
`cargo`コマンドを使い、`Cargo.toml`を直接手で書き換えず、必ずコマンド経由で管理すること。
|
`cargo`コマンドを使い、`Cargo.toml`を直接手で書き換えず、必ずコマンド経由で管理すること。
|
||||||
|
|
||||||
## worker-types
|
|
||||||
|
|
||||||
`worker-types` クレートには次の条件を満たす型だけを置く。
|
|
||||||
|
|
||||||
1. **共有セマンティクスの源泉**
|
|
||||||
- ランタイム(`worker`)、proc-macro(`worker-macros`)、外部利用者のすべてで同じ定義を共有したい値型。
|
|
||||||
- 例: `BlockId`, `ProviderEvent`, `ToolArgumentsDelta` などイベント/DTO群。
|
|
||||||
|
|
||||||
2. **シリアライズ境界を越えるもの**
|
|
||||||
- serde経由でプロセス外へ渡したり、APIレスポンスとして公開するもの。
|
|
||||||
- ロジックを持たない純粋なデータキャリアに限定する。
|
|
||||||
|
|
||||||
3. **依存の最小化が必要な型**
|
|
||||||
- `serde`, `serde_json` 程度の軽量依存で収まる。
|
|
||||||
|
|
||||||
4. **マクロが直接参照する型**
|
|
||||||
- 属性/derive/proc-macro が型に対してコード生成する場合は `worker-macros` ->
|
|
||||||
`worker-types` の単方向依存を維持するため、対象型を `worker-types` に置く。
|
|
||||||
|
|
||||||
5. **副作用を伴わないこと**
|
|
||||||
- `worker-types` 内では I/O・状態保持・スレッド操作などの副作用を禁止。
|
|
||||||
- 振る舞いを持つ場合でも `impl`
|
|
||||||
は純粋な計算か軽量ユーティリティのみに留める。
|
|
||||||
|
|
||||||
この基準に当てはまらない型(例えばクライアント状態管理、エラー型で追加依存が必要、プロバイダ固有ロジックなど)は
|
|
||||||
`worker` クレート側に配置し、どうしても公開が必要なら `worker`
|
|
||||||
経由で再エクスポートする。 何にせよ、`worker` ->
|
|
||||||
`worker-types`の片方向依存を維持すること。
|
|
||||||
|
|
|
||||||
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -2167,7 +2167,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"worker-macros",
|
"worker-macros",
|
||||||
"worker-types",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2177,18 +2176,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
"worker-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "worker-types"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror 2.0.17",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,5 @@
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"worker",
|
"worker",
|
||||||
"worker-types",
|
|
||||||
"worker-macros",
|
"worker-macros",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1 +1,5 @@
|
||||||
# llm-worker-rs
|
# llm-worker-rs
|
||||||
|
|
||||||
|
Rusty, Efficient, and Agentic LLM Client Library
|
||||||
|
|
||||||
|
`llm-worker-rs` is a Rust library designed for building robust and efficient LLM applications. It unifies interactions with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama) under a single abstraction, with type-safe state management, efficient context caching, and a powerful event-driven architecture.
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,3 @@ proc-macro = true
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full"] }
|
syn = { version = "2", features = ["full"] }
|
||||||
worker-types = { path = "../worker-types" }
|
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
quote! {
|
quote! {
|
||||||
match result {
|
match result {
|
||||||
Ok(val) => Ok(format!("{:?}", val)),
|
Ok(val) => Ok(format!("{:?}", val)),
|
||||||
Err(e) => Err(worker_types::ToolError::ExecutionFailed(format!("{}", e))),
|
Err(e) => Err(::worker::tool::ToolError::ExecutionFailed(format!("{}", e))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -230,7 +230,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
let args: #args_struct_name = serde_json::from_str(input_json)
|
let args: #args_struct_name = serde_json::from_str(input_json)
|
||||||
.map_err(|e| worker_types::ToolError::InvalidArgument(e.to_string()))?;
|
.map_err(|e| ::worker::tool::ToolError::InvalidArgument(e.to_string()))?;
|
||||||
|
|
||||||
let result = self.ctx.#method_name(#(#arg_names),*)#awaiter;
|
let result = self.ctx.#method_name(#(#arg_names),*)#awaiter;
|
||||||
#result_handling
|
#result_handling
|
||||||
|
|
@ -246,7 +246,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl worker_types::Tool for #tool_struct_name {
|
impl ::worker::tool::Tool for #tool_struct_name {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
#tool_name
|
#tool_name
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +260,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
serde_json::to_value(schema).unwrap_or(serde_json::json!({}))
|
serde_json::to_value(schema).unwrap_or(serde_json::json!({}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&self, input_json: &str) -> Result<String, worker_types::ToolError> {
|
async fn execute(&self, input_json: &str) -> Result<String, ::worker::tool::ToolError> {
|
||||||
#execute_body
|
#execute_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "worker-types"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
async-trait = "0.1.89"
|
|
||||||
schemars = "1.2.0"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
thiserror = "2.0.17"
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
//! worker-types - LLMワーカーの型定義
|
|
||||||
//!
|
|
||||||
//! このクレートは`worker`クレートで使用される型を提供します。
|
|
||||||
//! 通常は直接使用せず、`worker`クレート経由で利用してください。
|
|
||||||
//!
|
|
||||||
//! ```ignore
|
|
||||||
//! use worker::{Event, Message, Tool, WorkerHook};
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
mod event;
|
|
||||||
mod handler;
|
|
||||||
mod hook;
|
|
||||||
mod message;
|
|
||||||
mod state;
|
|
||||||
mod subscriber;
|
|
||||||
mod tool;
|
|
||||||
|
|
||||||
pub use event::*;
|
|
||||||
pub use handler::*;
|
|
||||||
pub use hook::*;
|
|
||||||
pub use message::*;
|
|
||||||
pub use state::*;
|
|
||||||
pub use subscriber::*;
|
|
||||||
pub use tool::*;
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
//! イベント購読
|
|
||||||
//!
|
|
||||||
//! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。
|
|
||||||
//! UIへのストリーム表示やプログレス表示に使用します。
|
|
||||||
|
|
||||||
use crate::{ErrorEvent, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent, UsageEvent};
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// WorkerSubscriber Trait
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/// LLMからのストリーミングイベントを購読するトレイト
|
|
||||||
///
|
|
||||||
/// Workerに登録すると、テキスト生成やツール呼び出しのイベントを
|
|
||||||
/// リアルタイムで受信できます。UIへのストリーム表示に最適です。
|
|
||||||
///
|
|
||||||
/// # 受信できるイベント
|
|
||||||
///
|
|
||||||
/// - **ブロックイベント**: テキスト、ツール使用(スコープ付き)
|
|
||||||
/// - **メタイベント**: 使用量、ステータス、エラー
|
|
||||||
/// - **完了イベント**: テキスト完了、ツール呼び出し完了
|
|
||||||
/// - **ターン制御**: ターン開始、ターン終了
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// use worker::{WorkerSubscriber, TextBlockEvent};
|
|
||||||
///
|
|
||||||
/// 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に登録
|
|
||||||
/// worker.subscribe(StreamPrinter);
|
|
||||||
/// ```
|
|
||||||
pub trait WorkerSubscriber: Send {
|
|
||||||
// =========================================================================
|
|
||||||
// スコープ型(ブロックイベント用)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// テキストブロック処理用のスコープ型
|
|
||||||
///
|
|
||||||
/// ブロック開始時にDefault::default()で生成され、
|
|
||||||
/// ブロック終了時に破棄される。
|
|
||||||
type TextBlockScope: Default + Send;
|
|
||||||
|
|
||||||
/// ツール使用ブロック処理用のスコープ型
|
|
||||||
type ToolUseBlockScope: Default + Send;
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// ブロックイベント(スコープ管理あり)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// テキストブロックイベント
|
|
||||||
///
|
|
||||||
/// Start/Delta/Stopのライフサイクルを持つ。
|
|
||||||
/// scopeはブロック開始時に生成され、終了時に破棄される。
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_text_block(&mut self, scope: &mut Self::TextBlockScope, event: &TextBlockEvent) {}
|
|
||||||
|
|
||||||
/// ツール使用ブロックイベント
|
|
||||||
///
|
|
||||||
/// Start/InputJsonDelta/Stopのライフサイクルを持つ。
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_tool_use_block(
|
|
||||||
&mut self,
|
|
||||||
scope: &mut Self::ToolUseBlockScope,
|
|
||||||
event: &ToolUseBlockEvent,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// 単発イベント(スコープ不要)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// 使用量イベント
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_usage(&mut self, event: &UsageEvent) {}
|
|
||||||
|
|
||||||
/// ステータスイベント
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_status(&mut self, event: &StatusEvent) {}
|
|
||||||
|
|
||||||
/// エラーイベント
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_error(&mut self, event: &ErrorEvent) {}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// 累積イベント(Worker層で追加)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// テキスト完了イベント
|
|
||||||
///
|
|
||||||
/// テキストブロックが完了した時点で、累積されたテキスト全体が渡される。
|
|
||||||
/// ブロック処理後の最終結果を受け取るのに便利。
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_text_complete(&mut self, text: &str) {}
|
|
||||||
|
|
||||||
/// ツール呼び出し完了イベント
|
|
||||||
///
|
|
||||||
/// ツール使用ブロックが完了した時点で、完全なToolCallが渡される。
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_tool_call_complete(&mut self, call: &ToolCall) {}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// ターン制御
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// ターン開始時
|
|
||||||
///
|
|
||||||
/// `turn`は0から始まるターン番号。
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_turn_start(&mut self, turn: usize) {}
|
|
||||||
|
|
||||||
/// ターン終了時
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn on_turn_end(&mut self, turn: usize) {}
|
|
||||||
}
|
|
||||||
|
|
@ -13,8 +13,7 @@ serde_json = "1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
worker-macros = { path = "../worker-macros" }
|
worker-macros = { path = "../worker-macros", version = "0.1" }
|
||||||
worker-types = { path = "../worker-types" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = { version = "4.5.54", features = ["derive", "env"] }
|
clap = { version = "4.5.54", features = ["derive", "env"] }
|
||||||
|
|
|
||||||
446
worker/src/event.rs
Normal file
446
worker/src/event.rs
Normal file
|
|
@ -0,0 +1,446 @@
|
||||||
|
//! Worker層の公開イベント型
|
||||||
|
//!
|
||||||
|
//! 外部利用者に公開するためのイベント表現。
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Core Event Types (from llm_client layer)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// LLMからのストリーミングイベント
|
||||||
|
///
|
||||||
|
/// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして
|
||||||
|
/// 統一的に処理されます。
|
||||||
|
///
|
||||||
|
/// # イベントの種類
|
||||||
|
///
|
||||||
|
/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error`
|
||||||
|
/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort`
|
||||||
|
///
|
||||||
|
/// # ブロックのライフサイクル
|
||||||
|
///
|
||||||
|
/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
|
||||||
|
/// の順序でイベントが発生します。
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
/// ハートビート
|
||||||
|
Ping(PingEvent),
|
||||||
|
/// トークン使用量
|
||||||
|
Usage(UsageEvent),
|
||||||
|
/// ストリームのステータス変化
|
||||||
|
Status(StatusEvent),
|
||||||
|
/// エラー発生
|
||||||
|
Error(ErrorEvent),
|
||||||
|
|
||||||
|
/// ブロック開始(テキスト、ツール使用等)
|
||||||
|
BlockStart(BlockStart),
|
||||||
|
/// ブロックの差分データ
|
||||||
|
BlockDelta(BlockDelta),
|
||||||
|
/// ブロック正常終了
|
||||||
|
BlockStop(BlockStop),
|
||||||
|
/// ブロック中断
|
||||||
|
BlockAbort(BlockAbort),
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Meta Events
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Pingイベント(ハートビート)
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct PingEvent {
|
||||||
|
pub timestamp: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用量イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct UsageEvent {
|
||||||
|
/// 入力トークン数
|
||||||
|
pub input_tokens: Option<u64>,
|
||||||
|
/// 出力トークン数
|
||||||
|
pub output_tokens: Option<u64>,
|
||||||
|
/// 合計トークン数
|
||||||
|
pub total_tokens: Option<u64>,
|
||||||
|
/// キャッシュ読み込みトークン数
|
||||||
|
pub cache_read_input_tokens: Option<u64>,
|
||||||
|
/// キャッシュ作成トークン数
|
||||||
|
pub cache_creation_input_tokens: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ステータスイベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct StatusEvent {
|
||||||
|
pub status: ResponseStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// レスポンスステータス
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum ResponseStatus {
|
||||||
|
/// ストリーム開始
|
||||||
|
Started,
|
||||||
|
/// 正常完了
|
||||||
|
Completed,
|
||||||
|
/// キャンセルされた
|
||||||
|
Cancelled,
|
||||||
|
/// エラー発生
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// エラーイベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ErrorEvent {
|
||||||
|
pub code: Option<String>,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Block Types
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// ブロックの種別
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum BlockType {
|
||||||
|
/// テキスト生成
|
||||||
|
Text,
|
||||||
|
/// 思考 (Claude Extended Thinking等)
|
||||||
|
Thinking,
|
||||||
|
/// ツール呼び出し
|
||||||
|
ToolUse,
|
||||||
|
/// ツール結果
|
||||||
|
ToolResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロック開始イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockStart {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// ブロックの種別
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// ブロック固有のメタデータ
|
||||||
|
pub metadata: BlockMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockStart {
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
self.block_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロックのメタデータ
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum BlockMetadata {
|
||||||
|
Text,
|
||||||
|
Thinking,
|
||||||
|
ToolUse { id: String, name: String },
|
||||||
|
ToolResult { tool_use_id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロックデルタイベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockDelta {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// デルタの内容
|
||||||
|
pub delta: DeltaContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// デルタの内容
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum DeltaContent {
|
||||||
|
/// テキストデルタ
|
||||||
|
Text(String),
|
||||||
|
/// 思考デルタ
|
||||||
|
Thinking(String),
|
||||||
|
/// ツール引数のJSON部分文字列
|
||||||
|
InputJson(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaContent {
|
||||||
|
/// デルタのブロック種別を取得
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
match self {
|
||||||
|
DeltaContent::Text(_) => BlockType::Text,
|
||||||
|
DeltaContent::Thinking(_) => BlockType::Thinking,
|
||||||
|
DeltaContent::InputJson(_) => BlockType::ToolUse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロック停止イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockStop {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// ブロックの種別
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// 停止理由
|
||||||
|
pub stop_reason: Option<StopReason>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockStop {
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
self.block_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロック中断イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockAbort {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// ブロックの種別
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// 中断理由
|
||||||
|
pub reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockAbort {
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
self.block_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止理由
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum StopReason {
|
||||||
|
/// 自然終了
|
||||||
|
EndTurn,
|
||||||
|
/// 最大トークン数到達
|
||||||
|
MaxTokens,
|
||||||
|
/// ストップシーケンス到達
|
||||||
|
StopSequence,
|
||||||
|
/// ツール使用
|
||||||
|
ToolUse,
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Builder / Factory helpers
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
/// テキストブロック開始イベントを作成
|
||||||
|
pub fn text_block_start(index: usize) -> Self {
|
||||||
|
Event::BlockStart(BlockStart {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::Text,
|
||||||
|
metadata: BlockMetadata::Text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// テキストデルタイベントを作成
|
||||||
|
pub fn text_delta(index: usize, text: impl Into<String>) -> Self {
|
||||||
|
Event::BlockDelta(BlockDelta {
|
||||||
|
index,
|
||||||
|
delta: DeltaContent::Text(text.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// テキストブロック停止イベントを作成
|
||||||
|
pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self {
|
||||||
|
Event::BlockStop(BlockStop {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::Text,
|
||||||
|
stop_reason,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ツール使用ブロック開始イベントを作成
|
||||||
|
pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> Self {
|
||||||
|
Event::BlockStart(BlockStart {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::ToolUse,
|
||||||
|
metadata: BlockMetadata::ToolUse {
|
||||||
|
id: id.into(),
|
||||||
|
name: name.into(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ツール引数デルタイベントを作成
|
||||||
|
pub fn tool_input_delta(index: usize, json: impl Into<String>) -> Self {
|
||||||
|
Event::BlockDelta(BlockDelta {
|
||||||
|
index,
|
||||||
|
delta: DeltaContent::InputJson(json.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ツール使用ブロック停止イベントを作成
|
||||||
|
pub fn tool_use_stop(index: usize) -> Self {
|
||||||
|
Event::BlockStop(BlockStop {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::ToolUse,
|
||||||
|
stop_reason: Some(StopReason::ToolUse),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用量イベントを作成
|
||||||
|
pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
|
||||||
|
Event::Usage(UsageEvent {
|
||||||
|
input_tokens: Some(input_tokens),
|
||||||
|
output_tokens: Some(output_tokens),
|
||||||
|
total_tokens: Some(input_tokens + output_tokens),
|
||||||
|
cache_read_input_tokens: None,
|
||||||
|
cache_creation_input_tokens: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pingイベントを作成
|
||||||
|
pub fn ping() -> Self {
|
||||||
|
Event::Ping(PingEvent { timestamp: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Conversions: timeline::event -> worker::event
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::ResponseStatus> for ResponseStatus {
|
||||||
|
fn from(value: crate::timeline::event::ResponseStatus) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::timeline::event::ResponseStatus::Started => ResponseStatus::Started,
|
||||||
|
crate::timeline::event::ResponseStatus::Completed => ResponseStatus::Completed,
|
||||||
|
crate::timeline::event::ResponseStatus::Cancelled => ResponseStatus::Cancelled,
|
||||||
|
crate::timeline::event::ResponseStatus::Failed => ResponseStatus::Failed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::BlockType> for BlockType {
|
||||||
|
fn from(value: crate::timeline::event::BlockType) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::timeline::event::BlockType::Text => BlockType::Text,
|
||||||
|
crate::timeline::event::BlockType::Thinking => BlockType::Thinking,
|
||||||
|
crate::timeline::event::BlockType::ToolUse => BlockType::ToolUse,
|
||||||
|
crate::timeline::event::BlockType::ToolResult => BlockType::ToolResult,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::BlockMetadata> for BlockMetadata {
|
||||||
|
fn from(value: crate::timeline::event::BlockMetadata) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::timeline::event::BlockMetadata::Text => BlockMetadata::Text,
|
||||||
|
crate::timeline::event::BlockMetadata::Thinking => BlockMetadata::Thinking,
|
||||||
|
crate::timeline::event::BlockMetadata::ToolUse { id, name } => {
|
||||||
|
BlockMetadata::ToolUse { id, name }
|
||||||
|
}
|
||||||
|
crate::timeline::event::BlockMetadata::ToolResult { tool_use_id } => {
|
||||||
|
BlockMetadata::ToolResult { tool_use_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::DeltaContent> for DeltaContent {
|
||||||
|
fn from(value: crate::timeline::event::DeltaContent) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::timeline::event::DeltaContent::Text(text) => DeltaContent::Text(text),
|
||||||
|
crate::timeline::event::DeltaContent::Thinking(text) => DeltaContent::Thinking(text),
|
||||||
|
crate::timeline::event::DeltaContent::InputJson(json) => DeltaContent::InputJson(json),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::StopReason> for StopReason {
|
||||||
|
fn from(value: crate::timeline::event::StopReason) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::timeline::event::StopReason::EndTurn => StopReason::EndTurn,
|
||||||
|
crate::timeline::event::StopReason::MaxTokens => StopReason::MaxTokens,
|
||||||
|
crate::timeline::event::StopReason::StopSequence => StopReason::StopSequence,
|
||||||
|
crate::timeline::event::StopReason::ToolUse => StopReason::ToolUse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::PingEvent> for PingEvent {
|
||||||
|
fn from(value: crate::timeline::event::PingEvent) -> Self {
|
||||||
|
PingEvent {
|
||||||
|
timestamp: value.timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::UsageEvent> for UsageEvent {
|
||||||
|
fn from(value: crate::timeline::event::UsageEvent) -> Self {
|
||||||
|
UsageEvent {
|
||||||
|
input_tokens: value.input_tokens,
|
||||||
|
output_tokens: value.output_tokens,
|
||||||
|
total_tokens: value.total_tokens,
|
||||||
|
cache_read_input_tokens: value.cache_read_input_tokens,
|
||||||
|
cache_creation_input_tokens: value.cache_creation_input_tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::StatusEvent> for StatusEvent {
|
||||||
|
fn from(value: crate::timeline::event::StatusEvent) -> Self {
|
||||||
|
StatusEvent {
|
||||||
|
status: value.status.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::ErrorEvent> for ErrorEvent {
|
||||||
|
fn from(value: crate::timeline::event::ErrorEvent) -> Self {
|
||||||
|
ErrorEvent {
|
||||||
|
code: value.code,
|
||||||
|
message: value.message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::BlockStart> for BlockStart {
|
||||||
|
fn from(value: crate::timeline::event::BlockStart) -> Self {
|
||||||
|
BlockStart {
|
||||||
|
index: value.index,
|
||||||
|
block_type: value.block_type.into(),
|
||||||
|
metadata: value.metadata.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::BlockDelta> for BlockDelta {
|
||||||
|
fn from(value: crate::timeline::event::BlockDelta) -> Self {
|
||||||
|
BlockDelta {
|
||||||
|
index: value.index,
|
||||||
|
delta: value.delta.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::BlockStop> for BlockStop {
|
||||||
|
fn from(value: crate::timeline::event::BlockStop) -> Self {
|
||||||
|
BlockStop {
|
||||||
|
index: value.index,
|
||||||
|
block_type: value.block_type.into(),
|
||||||
|
stop_reason: value.stop_reason.map(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::BlockAbort> for BlockAbort {
|
||||||
|
fn from(value: crate::timeline::event::BlockAbort) -> Self {
|
||||||
|
BlockAbort {
|
||||||
|
index: value.index,
|
||||||
|
block_type: value.block_type.into(),
|
||||||
|
reason: value.reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::timeline::event::Event> for Event {
|
||||||
|
fn from(value: crate::timeline::event::Event) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::timeline::event::Event::Ping(p) => Event::Ping(p.into()),
|
||||||
|
crate::timeline::event::Event::Usage(u) => Event::Usage(u.into()),
|
||||||
|
crate::timeline::event::Event::Status(s) => Event::Status(s.into()),
|
||||||
|
crate::timeline::event::Event::Error(e) => Event::Error(e.into()),
|
||||||
|
crate::timeline::event::Event::BlockStart(s) => Event::BlockStart(s.into()),
|
||||||
|
crate::timeline::event::Event::BlockDelta(d) => Event::BlockDelta(d.into()),
|
||||||
|
crate::timeline::event::Event::BlockStop(s) => Event::BlockStop(s.into()),
|
||||||
|
crate::timeline::event::Event::BlockAbort(a) => Event::BlockAbort(a.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
//! カスタムハンドラを実装してTimelineに登録することで、
|
//! カスタムハンドラを実装してTimelineに登録することで、
|
||||||
//! ストリームイベントを受信できます。
|
//! ストリームイベントを受信できます。
|
||||||
|
|
||||||
use crate::event::*;
|
use crate::timeline::event::*;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Kind Trait
|
// Kind Trait
|
||||||
|
|
@ -32,7 +32,7 @@ pub trait Kind {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use worker::{Handler, TextBlockKind, TextBlockEvent};
|
/// use worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
|
||||||
///
|
///
|
||||||
/// struct TextCollector {
|
/// struct TextCollector {
|
||||||
/// texts: Vec<String>,
|
/// texts: Vec<String>,
|
||||||
|
|
@ -109,7 +109,8 @@ pub enum HookError {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use worker::{WorkerHook, ControlFlow, HookError, ToolCall, TurnResult, Message};
|
/// use worker::hook::{ControlFlow, HookError, ToolCall, TurnResult, WorkerHook};
|
||||||
|
/// use worker::Message;
|
||||||
///
|
///
|
||||||
/// struct ValidationHook;
|
/// struct ValidationHook;
|
||||||
///
|
///
|
||||||
|
|
@ -39,55 +39,18 @@
|
||||||
pub mod llm_client;
|
pub mod llm_client;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
|
|
||||||
mod subscriber_adapter;
|
pub mod event;
|
||||||
|
mod handler;
|
||||||
|
pub mod hook;
|
||||||
|
mod message;
|
||||||
|
pub mod state;
|
||||||
|
pub mod subscriber;
|
||||||
|
pub mod tool;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// トップレベル公開(最も頻繁に使う型)
|
// トップレベル公開(最も頻繁に使う型)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
pub use message::{ContentPart, Message, MessageContent, Role};
|
||||||
pub use worker::{Worker, WorkerConfig, WorkerError};
|
pub use worker::{Worker, WorkerConfig, WorkerError};
|
||||||
pub use worker_types::{ContentPart, Message, MessageContent, Role};
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// 意味のあるモジュールとして公開
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/// ツール定義
|
|
||||||
///
|
|
||||||
/// LLMから呼び出し可能なツールを定義するためのトレイトと型。
|
|
||||||
pub mod tool {
|
|
||||||
pub use worker_types::{Tool, ToolError};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hook機能
|
|
||||||
///
|
|
||||||
/// ターンの進行・ツール実行に介入するためのトレイトと型。
|
|
||||||
pub mod hook {
|
|
||||||
pub use worker_types::{ControlFlow, HookError, ToolCall, ToolResult, TurnResult, WorkerHook};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// イベント購読
|
|
||||||
///
|
|
||||||
/// LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。
|
|
||||||
pub mod subscriber {
|
|
||||||
pub use worker_types::WorkerSubscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// イベント型
|
|
||||||
///
|
|
||||||
/// LLMからのストリーミングレスポンスを表現するイベント型。
|
|
||||||
/// Timeline層を直接使用する場合に必要です。
|
|
||||||
pub mod event {
|
|
||||||
pub use worker_types::{
|
|
||||||
BlockAbort, BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent,
|
|
||||||
ErrorEvent, Event, PingEvent, ResponseStatus, StatusEvent, StopReason, UsageEvent,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Worker状態
|
|
||||||
///
|
|
||||||
/// Type-stateパターンによるキャッシュ保護のための状態マーカー型。
|
|
||||||
pub mod state {
|
|
||||||
pub use worker_types::{Locked, Mutable, WorkerState};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@
|
||||||
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::llm_client::{ClientError, Request, event::Event};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use worker_types::Event;
|
|
||||||
|
|
||||||
use crate::llm_client::{ClientError, Request};
|
|
||||||
|
|
||||||
/// LLMクライアントのtrait
|
/// LLMクライアントのtrait
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//! イベント型
|
//! LLMクライアント層のイベント型
|
||||||
//!
|
//!
|
||||||
//! LLMからのストリーミングレスポンスを表現するイベント型。
|
//! 各LLMプロバイダからのストリーミングレスポンスを表現するイベント型。
|
||||||
//! Timeline層がこのイベントを受信し、ハンドラにディスパッチします。
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! LLMクライアント層
|
//! LLMクライアント層
|
||||||
//!
|
//!
|
||||||
//! 各LLMプロバイダと通信し、統一された[`Event`](crate::event::Event)ストリームを出力します。
|
//! 各LLMプロバイダと通信し、統一された[`Event`](crate::llm_client::event::Event)
|
||||||
|
//! ストリームを出力します。
|
||||||
//!
|
//!
|
||||||
//! # サポートするプロバイダ
|
//! # サポートするプロバイダ
|
||||||
//!
|
//!
|
||||||
|
|
@ -17,6 +18,7 @@
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod event;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub mod providers;
|
pub mod providers;
|
||||||
|
|
@ -24,4 +26,5 @@ pub mod scheme;
|
||||||
|
|
||||||
pub use client::*;
|
pub use client::*;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
pub use event::*;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::llm_client::{
|
||||||
|
ClientError, LlmClient, Request, event::Event, scheme::anthropic::AnthropicScheme,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use eventsource_stream::Eventsource;
|
use eventsource_stream::Eventsource;
|
||||||
use futures::{Stream, StreamExt, TryStreamExt, future::ready};
|
use futures::{Stream, StreamExt, TryStreamExt, future::ready};
|
||||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||||
use worker_types::Event;
|
|
||||||
|
|
||||||
use crate::llm_client::{ClientError, LlmClient, Request, scheme::anthropic::AnthropicScheme};
|
|
||||||
|
|
||||||
/// Anthropic クライアント
|
/// Anthropic クライアント
|
||||||
pub struct AnthropicClient {
|
pub struct AnthropicClient {
|
||||||
|
|
@ -156,10 +156,11 @@ impl LlmClient for AnthropicClient {
|
||||||
if let Some(block_type) = current_block_type.take() {
|
if let Some(block_type) = current_block_type.take() {
|
||||||
// 正しいブロックタイプで上書き
|
// 正しいブロックタイプで上書き
|
||||||
// (Event::BlockStopの中身を置換)
|
// (Event::BlockStopの中身を置換)
|
||||||
evt = Event::BlockStop(worker_types::BlockStop {
|
evt =
|
||||||
block_type,
|
Event::BlockStop(crate::llm_client::event::BlockStop {
|
||||||
..stop.clone()
|
block_type,
|
||||||
});
|
..stop.clone()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::llm_client::{
|
||||||
|
ClientError, LlmClient, Request, event::Event, scheme::gemini::GeminiScheme,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use eventsource_stream::Eventsource;
|
use eventsource_stream::Eventsource;
|
||||||
use futures::{Stream, StreamExt, TryStreamExt};
|
use futures::{Stream, StreamExt, TryStreamExt};
|
||||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||||
use worker_types::Event;
|
|
||||||
|
|
||||||
use crate::llm_client::{ClientError, LlmClient, Request, scheme::gemini::GeminiScheme};
|
|
||||||
|
|
||||||
/// Gemini クライアント
|
/// Gemini クライアント
|
||||||
pub struct GeminiClient {
|
pub struct GeminiClient {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@
|
||||||
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::llm_client::{
|
||||||
|
ClientError, LlmClient, Request, event::Event, providers::openai::OpenAIClient,
|
||||||
|
scheme::openai::OpenAIScheme,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use worker_types::Event;
|
|
||||||
|
|
||||||
use crate::llm_client::{
|
|
||||||
ClientError, LlmClient, Request, providers::openai::OpenAIClient, scheme::openai::OpenAIScheme,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Ollama クライアント
|
/// Ollama クライアント
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::llm_client::{
|
||||||
|
ClientError, LlmClient, Request, event::Event, scheme::openai::OpenAIScheme,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use eventsource_stream::Eventsource;
|
use eventsource_stream::Eventsource;
|
||||||
use futures::{Stream, StreamExt, TryStreamExt};
|
use futures::{Stream, StreamExt, TryStreamExt};
|
||||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||||
use worker_types::Event;
|
|
||||||
|
|
||||||
use crate::llm_client::{ClientError, LlmClient, Request, scheme::openai::OpenAIScheme};
|
|
||||||
|
|
||||||
/// OpenAI クライアント
|
/// OpenAI クライアント
|
||||||
pub struct OpenAIClient {
|
pub struct OpenAIClient {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@
|
||||||
//!
|
//!
|
||||||
//! Anthropic Messages APIのSSEイベントをパースし、統一Event型に変換
|
//! Anthropic Messages APIのSSEイベントをパースし、統一Event型に変換
|
||||||
|
|
||||||
use serde::Deserialize;
|
use crate::llm_client::{
|
||||||
use worker_types::{
|
ClientError,
|
||||||
BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent, Event,
|
event::{
|
||||||
PingEvent, ResponseStatus, StatusEvent, UsageEvent,
|
BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent,
|
||||||
|
Event, PingEvent, ResponseStatus, StatusEvent, UsageEvent,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use crate::llm_client::ClientError;
|
|
||||||
|
|
||||||
use super::AnthropicScheme;
|
use super::AnthropicScheme;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
//!
|
//!
|
||||||
//! Google Gemini APIのSSEイベントをパースし、統一Event型に変換
|
//! Google Gemini APIのSSEイベントをパースし、統一Event型に変換
|
||||||
|
|
||||||
use serde::Deserialize;
|
use crate::llm_client::{
|
||||||
use worker_types::{
|
ClientError,
|
||||||
BlockMetadata, BlockStart, BlockStop, BlockType, Event, StopReason, UsageEvent,
|
event::{BlockMetadata, BlockStart, BlockStop, BlockType, Event, StopReason, UsageEvent},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use crate::llm_client::ClientError;
|
|
||||||
|
|
||||||
use super::GeminiScheme;
|
use super::GeminiScheme;
|
||||||
|
|
||||||
|
|
@ -231,7 +230,7 @@ impl GeminiScheme {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use worker_types::DeltaContent;
|
use crate::llm_client::event::DeltaContent;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_text_response() {
|
fn test_parse_text_response() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
//! OpenAI SSEイベントパース
|
//! OpenAI SSEイベントパース
|
||||||
|
|
||||||
|
use crate::llm_client::{
|
||||||
|
ClientError,
|
||||||
|
event::{Event, StopReason, UsageEvent},
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use worker_types::{Event, StopReason, UsageEvent};
|
|
||||||
|
|
||||||
use crate::llm_client::ClientError;
|
|
||||||
|
|
||||||
use super::OpenAIScheme;
|
use super::OpenAIScheme;
|
||||||
|
|
||||||
|
|
@ -155,7 +156,7 @@ impl OpenAIScheme {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use worker_types::DeltaContent;
|
use crate::llm_client::event::DeltaContent;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_text_delta() {
|
fn test_parse_text_delta() {
|
||||||
|
|
@ -188,7 +189,7 @@ mod tests {
|
||||||
assert_eq!(events.len(), 1);
|
assert_eq!(events.len(), 1);
|
||||||
if let Event::BlockStart(start) = &events[0] {
|
if let Event::BlockStart(start) = &events[0] {
|
||||||
assert_eq!(start.index, 0);
|
assert_eq!(start.index, 0);
|
||||||
if let worker_types::BlockMetadata::ToolUse { id, name } = &start.metadata {
|
if let crate::llm_client::event::BlockMetadata::ToolUse { id, name } = &start.metadata {
|
||||||
assert_eq!(id, "call_abc");
|
assert_eq!(id, "call_abc");
|
||||||
assert_eq!(name, "get_weather");
|
assert_eq!(name, "get_weather");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,145 @@
|
||||||
//! WorkerSubscriber統合
|
//! イベント購読
|
||||||
//!
|
//!
|
||||||
//! WorkerSubscriberをTimeline層のHandlerとしてブリッジする実装
|
//! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。
|
||||||
|
//! UIへのストリーム表示やプログレス表示に使用します。
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use worker_types::{
|
use crate::{
|
||||||
ErrorEvent, ErrorKind, Handler, StatusEvent, StatusKind, TextBlockEvent, TextBlockKind,
|
handler::{
|
||||||
ToolCall, ToolUseBlockEvent, ToolUseBlockKind, UsageEvent, UsageKind, WorkerSubscriber,
|
ErrorKind, Handler, StatusKind, TextBlockEvent, TextBlockKind, ToolUseBlockEvent,
|
||||||
|
ToolUseBlockKind, UsageKind,
|
||||||
|
},
|
||||||
|
hook::ToolCall,
|
||||||
|
timeline::event::{ErrorEvent, StatusEvent, UsageEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WorkerSubscriber Trait
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// LLMからのストリーミングイベントを購読するトレイト
|
||||||
|
///
|
||||||
|
/// Workerに登録すると、テキスト生成やツール呼び出しのイベントを
|
||||||
|
/// リアルタイムで受信できます。UIへのストリーム表示に最適です。
|
||||||
|
///
|
||||||
|
/// # 受信できるイベント
|
||||||
|
///
|
||||||
|
/// - **ブロックイベント**: テキスト、ツール使用(スコープ付き)
|
||||||
|
/// - **メタイベント**: 使用量、ステータス、エラー
|
||||||
|
/// - **完了イベント**: テキスト完了、ツール呼び出し完了
|
||||||
|
/// - **ターン制御**: ターン開始、ターン終了
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use worker::subscriber::WorkerSubscriber;
|
||||||
|
/// use worker::timeline::TextBlockEvent;
|
||||||
|
///
|
||||||
|
/// 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に登録
|
||||||
|
/// worker.subscribe(StreamPrinter);
|
||||||
|
/// ```
|
||||||
|
pub trait WorkerSubscriber: Send {
|
||||||
|
// =========================================================================
|
||||||
|
// スコープ型(ブロックイベント用)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// テキストブロック処理用のスコープ型
|
||||||
|
///
|
||||||
|
/// ブロック開始時にDefault::default()で生成され、
|
||||||
|
/// ブロック終了時に破棄される。
|
||||||
|
type TextBlockScope: Default + Send;
|
||||||
|
|
||||||
|
/// ツール使用ブロック処理用のスコープ型
|
||||||
|
type ToolUseBlockScope: Default + Send;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// ブロックイベント(スコープ管理あり)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// テキストブロックイベント
|
||||||
|
///
|
||||||
|
/// Start/Delta/Stopのライフサイクルを持つ。
|
||||||
|
/// scopeはブロック開始時に生成され、終了時に破棄される。
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_text_block(&mut self, scope: &mut Self::TextBlockScope, event: &TextBlockEvent) {}
|
||||||
|
|
||||||
|
/// ツール使用ブロックイベント
|
||||||
|
///
|
||||||
|
/// Start/InputJsonDelta/Stopのライフサイクルを持つ。
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_tool_use_block(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Self::ToolUseBlockScope,
|
||||||
|
event: &ToolUseBlockEvent,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 単発イベント(スコープ不要)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// 使用量イベント
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_usage(&mut self, event: &UsageEvent) {}
|
||||||
|
|
||||||
|
/// ステータスイベント
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_status(&mut self, event: &StatusEvent) {}
|
||||||
|
|
||||||
|
/// エラーイベント
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_error(&mut self, event: &ErrorEvent) {}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 累積イベント(Worker層で追加)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// テキスト完了イベント
|
||||||
|
///
|
||||||
|
/// テキストブロックが完了した時点で、累積されたテキスト全体が渡される。
|
||||||
|
/// ブロック処理後の最終結果を受け取るのに便利。
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_text_complete(&mut self, text: &str) {}
|
||||||
|
|
||||||
|
/// ツール呼び出し完了イベント
|
||||||
|
///
|
||||||
|
/// ツール使用ブロックが完了した時点で、完全なToolCallが渡される。
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_tool_call_complete(&mut self, call: &ToolCall) {}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// ターン制御
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// ターン開始時
|
||||||
|
///
|
||||||
|
/// `turn`は0から始まるターン番号。
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_turn_start(&mut self, turn: usize) {}
|
||||||
|
|
||||||
|
/// ターン終了時
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn on_turn_end(&mut self, turn: usize) {}
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// SubscriberAdapter - WorkerSubscriberをTimelineハンドラにブリッジ
|
// SubscriberAdapter - WorkerSubscriberをTimelineハンドラにブリッジ
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
448
worker/src/timeline/event.rs
Normal file
448
worker/src/timeline/event.rs
Normal file
|
|
@ -0,0 +1,448 @@
|
||||||
|
//! Timeline層のイベント型
|
||||||
|
//!
|
||||||
|
//! Timelineが受け取り、各Handlerへディスパッチするイベント表現。
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Core Event Types (from llm_client layer)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// LLMからのストリーミングイベント
|
||||||
|
///
|
||||||
|
/// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして
|
||||||
|
/// 統一的に処理されます。
|
||||||
|
///
|
||||||
|
/// # イベントの種類
|
||||||
|
///
|
||||||
|
/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error`
|
||||||
|
/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort`
|
||||||
|
///
|
||||||
|
/// # ブロックのライフサイクル
|
||||||
|
///
|
||||||
|
/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
|
||||||
|
/// の順序でイベントが発生します。
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
/// ハートビート
|
||||||
|
Ping(PingEvent),
|
||||||
|
/// トークン使用量
|
||||||
|
Usage(UsageEvent),
|
||||||
|
/// ストリームのステータス変化
|
||||||
|
Status(StatusEvent),
|
||||||
|
/// エラー発生
|
||||||
|
Error(ErrorEvent),
|
||||||
|
|
||||||
|
/// ブロック開始(テキスト、ツール使用等)
|
||||||
|
BlockStart(BlockStart),
|
||||||
|
/// ブロックの差分データ
|
||||||
|
BlockDelta(BlockDelta),
|
||||||
|
/// ブロック正常終了
|
||||||
|
BlockStop(BlockStop),
|
||||||
|
/// ブロック中断
|
||||||
|
BlockAbort(BlockAbort),
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Meta Events
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Pingイベント(ハートビート)
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct PingEvent {
|
||||||
|
pub timestamp: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用量イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct UsageEvent {
|
||||||
|
/// 入力トークン数
|
||||||
|
pub input_tokens: Option<u64>,
|
||||||
|
/// 出力トークン数
|
||||||
|
pub output_tokens: Option<u64>,
|
||||||
|
/// 合計トークン数
|
||||||
|
pub total_tokens: Option<u64>,
|
||||||
|
/// キャッシュ読み込みトークン数
|
||||||
|
pub cache_read_input_tokens: Option<u64>,
|
||||||
|
/// キャッシュ作成トークン数
|
||||||
|
pub cache_creation_input_tokens: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ステータスイベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct StatusEvent {
|
||||||
|
pub status: ResponseStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// レスポンスステータス
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum ResponseStatus {
|
||||||
|
/// ストリーム開始
|
||||||
|
Started,
|
||||||
|
/// 正常完了
|
||||||
|
Completed,
|
||||||
|
/// キャンセルされた
|
||||||
|
Cancelled,
|
||||||
|
/// エラー発生
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// エラーイベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ErrorEvent {
|
||||||
|
pub code: Option<String>,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Block Types
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// ブロックの種別
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum BlockType {
|
||||||
|
/// テキスト生成
|
||||||
|
Text,
|
||||||
|
/// 思考 (Claude Extended Thinking等)
|
||||||
|
Thinking,
|
||||||
|
/// ツール呼び出し
|
||||||
|
ToolUse,
|
||||||
|
/// ツール結果
|
||||||
|
ToolResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロック開始イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockStart {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// ブロックの種別
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// ブロック固有のメタデータ
|
||||||
|
pub metadata: BlockMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockStart {
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
self.block_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロックのメタデータ
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum BlockMetadata {
|
||||||
|
Text,
|
||||||
|
Thinking,
|
||||||
|
ToolUse { id: String, name: String },
|
||||||
|
ToolResult { tool_use_id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロックデルタイベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockDelta {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// デルタの内容
|
||||||
|
pub delta: DeltaContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// デルタの内容
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum DeltaContent {
|
||||||
|
/// テキストデルタ
|
||||||
|
Text(String),
|
||||||
|
/// 思考デルタ
|
||||||
|
Thinking(String),
|
||||||
|
/// ツール引数のJSON部分文字列
|
||||||
|
InputJson(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaContent {
|
||||||
|
/// デルタのブロック種別を取得
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
match self {
|
||||||
|
DeltaContent::Text(_) => BlockType::Text,
|
||||||
|
DeltaContent::Thinking(_) => BlockType::Thinking,
|
||||||
|
DeltaContent::InputJson(_) => BlockType::ToolUse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロック停止イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockStop {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// ブロックの種別
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// 停止理由
|
||||||
|
pub stop_reason: Option<StopReason>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockStop {
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
self.block_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ブロック中断イベント
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct BlockAbort {
|
||||||
|
/// ブロックのインデックス
|
||||||
|
pub index: usize,
|
||||||
|
/// ブロックの種別
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// 中断理由
|
||||||
|
pub reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockAbort {
|
||||||
|
pub fn block_type(&self) -> BlockType {
|
||||||
|
self.block_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止理由
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum StopReason {
|
||||||
|
/// 自然終了
|
||||||
|
EndTurn,
|
||||||
|
/// 最大トークン数到達
|
||||||
|
MaxTokens,
|
||||||
|
/// ストップシーケンス到達
|
||||||
|
StopSequence,
|
||||||
|
/// ツール使用
|
||||||
|
ToolUse,
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Builder / Factory helpers
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
/// テキストブロック開始イベントを作成
|
||||||
|
pub fn text_block_start(index: usize) -> Self {
|
||||||
|
Event::BlockStart(BlockStart {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::Text,
|
||||||
|
metadata: BlockMetadata::Text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// テキストデルタイベントを作成
|
||||||
|
pub fn text_delta(index: usize, text: impl Into<String>) -> Self {
|
||||||
|
Event::BlockDelta(BlockDelta {
|
||||||
|
index,
|
||||||
|
delta: DeltaContent::Text(text.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// テキストブロック停止イベントを作成
|
||||||
|
pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self {
|
||||||
|
Event::BlockStop(BlockStop {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::Text,
|
||||||
|
stop_reason,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ツール使用ブロック開始イベントを作成
|
||||||
|
pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> Self {
|
||||||
|
Event::BlockStart(BlockStart {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::ToolUse,
|
||||||
|
metadata: BlockMetadata::ToolUse {
|
||||||
|
id: id.into(),
|
||||||
|
name: name.into(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ツール引数デルタイベントを作成
|
||||||
|
pub fn tool_input_delta(index: usize, json: impl Into<String>) -> Self {
|
||||||
|
Event::BlockDelta(BlockDelta {
|
||||||
|
index,
|
||||||
|
delta: DeltaContent::InputJson(json.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ツール使用ブロック停止イベントを作成
|
||||||
|
pub fn tool_use_stop(index: usize) -> Self {
|
||||||
|
Event::BlockStop(BlockStop {
|
||||||
|
index,
|
||||||
|
block_type: BlockType::ToolUse,
|
||||||
|
stop_reason: Some(StopReason::ToolUse),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用量イベントを作成
|
||||||
|
pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
|
||||||
|
Event::Usage(UsageEvent {
|
||||||
|
input_tokens: Some(input_tokens),
|
||||||
|
output_tokens: Some(output_tokens),
|
||||||
|
total_tokens: Some(input_tokens + output_tokens),
|
||||||
|
cache_read_input_tokens: None,
|
||||||
|
cache_creation_input_tokens: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pingイベントを作成
|
||||||
|
pub fn ping() -> Self {
|
||||||
|
Event::Ping(PingEvent { timestamp: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Conversions: llm_client::event -> timeline::event
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::ResponseStatus> for ResponseStatus {
|
||||||
|
fn from(value: crate::llm_client::event::ResponseStatus) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::llm_client::event::ResponseStatus::Started => ResponseStatus::Started,
|
||||||
|
crate::llm_client::event::ResponseStatus::Completed => ResponseStatus::Completed,
|
||||||
|
crate::llm_client::event::ResponseStatus::Cancelled => ResponseStatus::Cancelled,
|
||||||
|
crate::llm_client::event::ResponseStatus::Failed => ResponseStatus::Failed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::BlockType> for BlockType {
|
||||||
|
fn from(value: crate::llm_client::event::BlockType) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::llm_client::event::BlockType::Text => BlockType::Text,
|
||||||
|
crate::llm_client::event::BlockType::Thinking => BlockType::Thinking,
|
||||||
|
crate::llm_client::event::BlockType::ToolUse => BlockType::ToolUse,
|
||||||
|
crate::llm_client::event::BlockType::ToolResult => BlockType::ToolResult,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::BlockMetadata> for BlockMetadata {
|
||||||
|
fn from(value: crate::llm_client::event::BlockMetadata) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::llm_client::event::BlockMetadata::Text => BlockMetadata::Text,
|
||||||
|
crate::llm_client::event::BlockMetadata::Thinking => BlockMetadata::Thinking,
|
||||||
|
crate::llm_client::event::BlockMetadata::ToolUse { id, name } => {
|
||||||
|
BlockMetadata::ToolUse { id, name }
|
||||||
|
}
|
||||||
|
crate::llm_client::event::BlockMetadata::ToolResult { tool_use_id } => {
|
||||||
|
BlockMetadata::ToolResult { tool_use_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::DeltaContent> for DeltaContent {
|
||||||
|
fn from(value: crate::llm_client::event::DeltaContent) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::llm_client::event::DeltaContent::Text(text) => DeltaContent::Text(text),
|
||||||
|
crate::llm_client::event::DeltaContent::Thinking(text) => DeltaContent::Thinking(text),
|
||||||
|
crate::llm_client::event::DeltaContent::InputJson(json) => {
|
||||||
|
DeltaContent::InputJson(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::StopReason> for StopReason {
|
||||||
|
fn from(value: crate::llm_client::event::StopReason) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::llm_client::event::StopReason::EndTurn => StopReason::EndTurn,
|
||||||
|
crate::llm_client::event::StopReason::MaxTokens => StopReason::MaxTokens,
|
||||||
|
crate::llm_client::event::StopReason::StopSequence => StopReason::StopSequence,
|
||||||
|
crate::llm_client::event::StopReason::ToolUse => StopReason::ToolUse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::PingEvent> for PingEvent {
|
||||||
|
fn from(value: crate::llm_client::event::PingEvent) -> Self {
|
||||||
|
PingEvent {
|
||||||
|
timestamp: value.timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::UsageEvent> for UsageEvent {
|
||||||
|
fn from(value: crate::llm_client::event::UsageEvent) -> Self {
|
||||||
|
UsageEvent {
|
||||||
|
input_tokens: value.input_tokens,
|
||||||
|
output_tokens: value.output_tokens,
|
||||||
|
total_tokens: value.total_tokens,
|
||||||
|
cache_read_input_tokens: value.cache_read_input_tokens,
|
||||||
|
cache_creation_input_tokens: value.cache_creation_input_tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::StatusEvent> for StatusEvent {
|
||||||
|
fn from(value: crate::llm_client::event::StatusEvent) -> Self {
|
||||||
|
StatusEvent {
|
||||||
|
status: value.status.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::ErrorEvent> for ErrorEvent {
|
||||||
|
fn from(value: crate::llm_client::event::ErrorEvent) -> Self {
|
||||||
|
ErrorEvent {
|
||||||
|
code: value.code,
|
||||||
|
message: value.message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::BlockStart> for BlockStart {
|
||||||
|
fn from(value: crate::llm_client::event::BlockStart) -> Self {
|
||||||
|
BlockStart {
|
||||||
|
index: value.index,
|
||||||
|
block_type: value.block_type.into(),
|
||||||
|
metadata: value.metadata.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::BlockDelta> for BlockDelta {
|
||||||
|
fn from(value: crate::llm_client::event::BlockDelta) -> Self {
|
||||||
|
BlockDelta {
|
||||||
|
index: value.index,
|
||||||
|
delta: value.delta.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::BlockStop> for BlockStop {
|
||||||
|
fn from(value: crate::llm_client::event::BlockStop) -> Self {
|
||||||
|
BlockStop {
|
||||||
|
index: value.index,
|
||||||
|
block_type: value.block_type.into(),
|
||||||
|
stop_reason: value.stop_reason.map(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::BlockAbort> for BlockAbort {
|
||||||
|
fn from(value: crate::llm_client::event::BlockAbort) -> Self {
|
||||||
|
BlockAbort {
|
||||||
|
index: value.index,
|
||||||
|
block_type: value.block_type.into(),
|
||||||
|
reason: value.reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::llm_client::event::Event> for Event {
|
||||||
|
fn from(value: crate::llm_client::event::Event) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::llm_client::event::Event::Ping(p) => Event::Ping(p.into()),
|
||||||
|
crate::llm_client::event::Event::Usage(u) => Event::Usage(u.into()),
|
||||||
|
crate::llm_client::event::Event::Status(s) => Event::Status(s.into()),
|
||||||
|
crate::llm_client::event::Event::Error(e) => Event::Error(e.into()),
|
||||||
|
crate::llm_client::event::Event::BlockStart(s) => Event::BlockStart(s.into()),
|
||||||
|
crate::llm_client::event::Event::BlockDelta(d) => Event::BlockDelta(d.into()),
|
||||||
|
crate::llm_client::event::Event::BlockStop(s) => Event::BlockStop(s.into()),
|
||||||
|
crate::llm_client::event::Event::BlockAbort(a) => Event::BlockAbort(a.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,25 +9,39 @@
|
||||||
//! - [`TextBlockCollector`] - テキストブロックを収集するHandler
|
//! - [`TextBlockCollector`] - テキストブロックを収集するHandler
|
||||||
//! - [`ToolCallCollector`] - ツール呼び出しを収集するHandler
|
//! - [`ToolCallCollector`] - ツール呼び出しを収集するHandler
|
||||||
|
|
||||||
|
pub mod event;
|
||||||
mod text_block_collector;
|
mod text_block_collector;
|
||||||
mod timeline;
|
mod timeline;
|
||||||
mod tool_call_collector;
|
mod tool_call_collector;
|
||||||
|
|
||||||
// 公開API
|
// 公開API
|
||||||
|
pub use event::*;
|
||||||
pub use text_block_collector::TextBlockCollector;
|
pub use text_block_collector::TextBlockCollector;
|
||||||
pub use timeline::{ErasedHandler, HandlerWrapper, Timeline};
|
pub use timeline::{ErasedHandler, HandlerWrapper, Timeline};
|
||||||
pub use tool_call_collector::ToolCallCollector;
|
pub use tool_call_collector::ToolCallCollector;
|
||||||
|
|
||||||
// worker-typesからのre-export
|
// 型定義からのre-export
|
||||||
pub use worker_types::{
|
pub use crate::handler::{
|
||||||
// Core traits
|
|
||||||
Handler, Kind,
|
|
||||||
// Block Kinds
|
|
||||||
TextBlockKind, ThinkingBlockKind, ToolUseBlockKind,
|
|
||||||
// Block Events
|
|
||||||
TextBlockEvent, TextBlockStart, TextBlockStop,
|
|
||||||
ThinkingBlockEvent, ThinkingBlockStart, ThinkingBlockStop,
|
|
||||||
ToolUseBlockEvent, ToolUseBlockStart, ToolUseBlockStop,
|
|
||||||
// Meta Kinds
|
// Meta Kinds
|
||||||
ErrorKind, PingKind, StatusKind, UsageKind,
|
ErrorKind,
|
||||||
|
// Core traits
|
||||||
|
Handler,
|
||||||
|
Kind,
|
||||||
|
PingKind,
|
||||||
|
StatusKind,
|
||||||
|
// Block Events
|
||||||
|
TextBlockEvent,
|
||||||
|
// Block Kinds
|
||||||
|
TextBlockKind,
|
||||||
|
TextBlockStart,
|
||||||
|
TextBlockStop,
|
||||||
|
ThinkingBlockEvent,
|
||||||
|
ThinkingBlockKind,
|
||||||
|
ThinkingBlockStart,
|
||||||
|
ThinkingBlockStop,
|
||||||
|
ToolUseBlockEvent,
|
||||||
|
ToolUseBlockKind,
|
||||||
|
ToolUseBlockStart,
|
||||||
|
ToolUseBlockStop,
|
||||||
|
UsageKind,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
//! TimelineのTextBlockHandler として登録され、
|
//! TimelineのTextBlockHandler として登録され、
|
||||||
//! ストリーム中のテキストブロックを収集する。
|
//! ストリーム中のテキストブロックを収集する。
|
||||||
|
|
||||||
|
use crate::handler::{Handler, TextBlockEvent, TextBlockKind};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use worker_types::{Handler, TextBlockEvent, TextBlockKind};
|
|
||||||
|
|
||||||
/// TextBlockから収集したテキスト情報を保持
|
/// TextBlockから収集したテキスト情報を保持
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -85,7 +85,7 @@ impl Handler<TextBlockKind> for TextBlockCollector {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::timeline::Timeline;
|
use crate::timeline::Timeline;
|
||||||
use worker_types::Event;
|
use crate::timeline::event::Event;
|
||||||
|
|
||||||
/// TextBlockCollectorが単一のテキストブロックを正しく収集することを確認
|
/// TextBlockCollectorが単一のテキストブロックを正しく収集することを確認
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use worker_types::*;
|
use super::event::*;
|
||||||
|
use crate::handler::*;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Type-erased Handler
|
// Type-erased Handler
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@
|
||||||
//! TimelineのToolUseBlockHandler として登録され、
|
//! TimelineのToolUseBlockHandler として登録され、
|
||||||
//! ストリーム中のToolUseブロックを収集する。
|
//! ストリーム中のToolUseブロックを収集する。
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handler::{Handler, ToolUseBlockEvent, ToolUseBlockKind},
|
||||||
|
hook::ToolCall,
|
||||||
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use worker_types::{Handler, ToolCall, ToolUseBlockEvent, ToolUseBlockKind};
|
|
||||||
|
|
||||||
/// ToolUseブロックから収集したツール呼び出し情報を保持
|
/// ToolUseブロックから収集したツール呼び出し情報を保持
|
||||||
///
|
///
|
||||||
|
|
@ -98,7 +101,7 @@ impl Handler<ToolUseBlockKind> for ToolCallCollector {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::timeline::Timeline;
|
use crate::timeline::Timeline;
|
||||||
use worker_types::Event;
|
use crate::timeline::event::Event;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collect_single_tool_call() {
|
fn test_collect_single_tool_call() {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub enum ToolError {
|
||||||
/// # 手動実装
|
/// # 手動実装
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use worker::{Tool, ToolError};
|
/// use worker::tool::{Tool, ToolError};
|
||||||
/// use serde_json::{json, Value};
|
/// use serde_json::{json, Value};
|
||||||
///
|
///
|
||||||
/// struct MyTool;
|
/// struct MyTool;
|
||||||
|
|
@ -5,15 +5,17 @@ use std::sync::{Arc, Mutex};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use tracing::{debug, info, trace, warn};
|
use tracing::{debug, info, trace, warn};
|
||||||
|
|
||||||
use crate::timeline::{TextBlockCollector, Timeline, ToolCallCollector};
|
use crate::{
|
||||||
use crate::llm_client::{ClientError, LlmClient, Request, ToolDefinition};
|
ContentPart, Message, MessageContent, Role,
|
||||||
use crate::subscriber_adapter::{
|
hook::{ControlFlow, HookError, ToolCall, ToolResult, TurnResult, WorkerHook},
|
||||||
ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter,
|
llm_client::{ClientError, LlmClient, Request, ToolDefinition},
|
||||||
ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter,
|
state::{Locked, Mutable, WorkerState},
|
||||||
};
|
subscriber::{
|
||||||
use worker_types::{
|
ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter,
|
||||||
ContentPart, ControlFlow, HookError, Locked, Message, MessageContent, Mutable, Tool, ToolCall,
|
ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, WorkerSubscriber,
|
||||||
ToolError, ToolResult, TurnResult, WorkerHook, WorkerState, WorkerSubscriber,
|
},
|
||||||
|
timeline::{TextBlockCollector, Timeline, ToolCallCollector},
|
||||||
|
tool::{Tool, ToolError},
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -321,7 +323,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Message {
|
Some(Message {
|
||||||
role: worker_types::Role::Assistant,
|
role: Role::Assistant,
|
||||||
content: MessageContent::Parts(parts),
|
content: MessageContent::Parts(parts),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -337,49 +339,45 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
|
|
||||||
// メッセージを追加
|
// メッセージを追加
|
||||||
for msg in &self.history {
|
for msg in &self.history {
|
||||||
// worker-types::Message から llm_client::Message への変換
|
// Message から llm_client::Message への変換
|
||||||
request = request.message(crate::llm_client::Message {
|
request = request.message(crate::llm_client::Message {
|
||||||
role: match msg.role {
|
role: match msg.role {
|
||||||
worker_types::Role::User => crate::llm_client::Role::User,
|
Role::User => crate::llm_client::Role::User,
|
||||||
worker_types::Role::Assistant => crate::llm_client::Role::Assistant,
|
Role::Assistant => crate::llm_client::Role::Assistant,
|
||||||
},
|
},
|
||||||
content: match &msg.content {
|
content: match &msg.content {
|
||||||
worker_types::MessageContent::Text(t) => {
|
MessageContent::Text(t) => crate::llm_client::MessageContent::Text(t.clone()),
|
||||||
crate::llm_client::MessageContent::Text(t.clone())
|
MessageContent::ToolResult {
|
||||||
}
|
|
||||||
worker_types::MessageContent::ToolResult {
|
|
||||||
tool_use_id,
|
tool_use_id,
|
||||||
content,
|
content,
|
||||||
} => crate::llm_client::MessageContent::ToolResult {
|
} => crate::llm_client::MessageContent::ToolResult {
|
||||||
tool_use_id: tool_use_id.clone(),
|
tool_use_id: tool_use_id.clone(),
|
||||||
content: content.clone(),
|
content: content.clone(),
|
||||||
},
|
},
|
||||||
worker_types::MessageContent::Parts(parts) => {
|
MessageContent::Parts(parts) => crate::llm_client::MessageContent::Parts(
|
||||||
crate::llm_client::MessageContent::Parts(
|
parts
|
||||||
parts
|
.iter()
|
||||||
.iter()
|
.map(|p| match p {
|
||||||
.map(|p| match p {
|
ContentPart::Text { text } => {
|
||||||
worker_types::ContentPart::Text { text } => {
|
crate::llm_client::ContentPart::Text { text: text.clone() }
|
||||||
crate::llm_client::ContentPart::Text { text: text.clone() }
|
}
|
||||||
|
ContentPart::ToolUse { id, name, input } => {
|
||||||
|
crate::llm_client::ContentPart::ToolUse {
|
||||||
|
id: id.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
input: input.clone(),
|
||||||
}
|
}
|
||||||
worker_types::ContentPart::ToolUse { id, name, input } => {
|
}
|
||||||
crate::llm_client::ContentPart::ToolUse {
|
ContentPart::ToolResult {
|
||||||
id: id.clone(),
|
tool_use_id,
|
||||||
name: name.clone(),
|
content,
|
||||||
input: input.clone(),
|
} => crate::llm_client::ContentPart::ToolResult {
|
||||||
}
|
tool_use_id: tool_use_id.clone(),
|
||||||
}
|
content: content.clone(),
|
||||||
worker_types::ContentPart::ToolResult {
|
},
|
||||||
tool_use_id,
|
})
|
||||||
content,
|
.collect(),
|
||||||
} => crate::llm_client::ContentPart::ToolResult {
|
),
|
||||||
tool_use_id: tool_use_id.clone(),
|
|
||||||
content: content.clone(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +548,8 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let event = event_result?;
|
let event = event_result?;
|
||||||
self.timeline.dispatch(&event);
|
let timeline_event: crate::timeline::event::Event = event.into();
|
||||||
|
self.timeline.dispatch(&timeline_event);
|
||||||
}
|
}
|
||||||
debug!(event_count = event_count, "Stream completed");
|
debug!(event_count = event_count, "Stream completed");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
use worker::llm_client::event::{BlockType, DeltaContent, Event};
|
||||||
use worker::llm_client::{ClientError, LlmClient, Request};
|
use worker::llm_client::{ClientError, LlmClient, Request};
|
||||||
use worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
|
use worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
|
||||||
use worker_types::{BlockType, DeltaContent, Event};
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
|
@ -267,7 +267,8 @@ pub fn assert_timeline_integration(subdir: &str) {
|
||||||
});
|
});
|
||||||
|
|
||||||
for event in &events {
|
for event in &events {
|
||||||
timeline.dispatch(event);
|
let timeline_event: worker::timeline::event::Event = event.clone().into();
|
||||||
|
timeline.dispatch(&timeline_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
let texts = collected.lock().unwrap();
|
let texts = collected.lock().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,9 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use worker::Worker;
|
use worker::Worker;
|
||||||
use worker_types::{
|
use worker::hook::{ControlFlow, HookError, ToolCall, ToolResult, WorkerHook};
|
||||||
ControlFlow, Event, HookError, ResponseStatus, StatusEvent, Tool, ToolCall, ToolError,
|
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||||
ToolResult, WorkerHook,
|
use worker::tool::{Tool, ToolError};
|
||||||
};
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ mod common;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use worker::subscriber::WorkerSubscriber;
|
|
||||||
use worker::Worker;
|
use worker::Worker;
|
||||||
use worker_types::{
|
use worker::hook::ToolCall;
|
||||||
ErrorEvent, Event, ResponseStatus, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent,
|
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
|
||||||
UsageEvent,
|
use worker::subscriber::WorkerSubscriber;
|
||||||
};
|
use worker::timeline::event::{ErrorEvent, StatusEvent, UsageEvent};
|
||||||
|
use worker::timeline::{TextBlockEvent, ToolUseBlockEvent};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Test Subscriber
|
// Test Subscriber
|
||||||
|
|
@ -101,7 +101,7 @@ async fn test_subscriber_text_block_events() {
|
||||||
Event::text_delta(0, "Hello, "),
|
Event::text_delta(0, "Hello, "),
|
||||||
Event::text_delta(0, "World!"),
|
Event::text_delta(0, "World!"),
|
||||||
Event::text_block_stop(0, None),
|
Event::text_block_stop(0, None),
|
||||||
Event::Status(StatusEvent {
|
Event::Status(ClientStatusEvent {
|
||||||
status: ResponseStatus::Completed,
|
status: ResponseStatus::Completed,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
@ -141,7 +141,7 @@ async fn test_subscriber_tool_call_complete() {
|
||||||
Event::tool_input_delta(0, r#"{"city":"#),
|
Event::tool_input_delta(0, r#"{"city":"#),
|
||||||
Event::tool_input_delta(0, r#""Tokyo"}"#),
|
Event::tool_input_delta(0, r#""Tokyo"}"#),
|
||||||
Event::tool_use_stop(0),
|
Event::tool_use_stop(0),
|
||||||
Event::Status(StatusEvent {
|
Event::Status(ClientStatusEvent {
|
||||||
status: ResponseStatus::Completed,
|
status: ResponseStatus::Completed,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
@ -172,7 +172,7 @@ async fn test_subscriber_turn_events() {
|
||||||
Event::text_block_start(0),
|
Event::text_block_start(0),
|
||||||
Event::text_delta(0, "Done!"),
|
Event::text_delta(0, "Done!"),
|
||||||
Event::text_block_stop(0, None),
|
Event::text_block_stop(0, None),
|
||||||
Event::Status(StatusEvent {
|
Event::Status(ClientStatusEvent {
|
||||||
status: ResponseStatus::Completed,
|
status: ResponseStatus::Completed,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
@ -210,7 +210,7 @@ async fn test_subscriber_usage_events() {
|
||||||
Event::text_delta(0, "Hello"),
|
Event::text_delta(0, "Hello"),
|
||||||
Event::text_block_stop(0, None),
|
Event::text_block_stop(0, None),
|
||||||
Event::usage(100, 50),
|
Event::usage(100, 50),
|
||||||
Event::Status(StatusEvent {
|
Event::Status(ClientStatusEvent {
|
||||||
status: ResponseStatus::Completed,
|
status: ResponseStatus::Completed,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use schemars;
|
use schemars;
|
||||||
use serde;
|
use serde;
|
||||||
|
|
||||||
|
use worker::tool::Tool;
|
||||||
use worker_macros::tool_registry;
|
use worker_macros::tool_registry;
|
||||||
use worker_types::Tool;
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Test: Basic Tool Generation
|
// Test: Basic Tool Generation
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use worker::Worker;
|
use worker::Worker;
|
||||||
use worker_types::{Tool, ToolError};
|
use worker::tool::{Tool, ToolError};
|
||||||
|
|
||||||
/// フィクスチャディレクトリのパス
|
/// フィクスチャディレクトリのパス
|
||||||
fn fixtures_dir() -> std::path::PathBuf {
|
fn fixtures_dir() -> std::path::PathBuf {
|
||||||
|
|
@ -100,7 +100,7 @@ fn test_mock_client_from_fixture() {
|
||||||
/// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。
|
/// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mock_client_from_events() {
|
fn test_mock_client_from_events() {
|
||||||
use worker_types::Event;
|
use worker::llm_client::event::Event;
|
||||||
|
|
||||||
// 直接イベントを指定
|
// 直接イベントを指定
|
||||||
let events = vec![
|
let events = vec![
|
||||||
|
|
@ -178,7 +178,7 @@ async fn test_worker_tool_call() {
|
||||||
/// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。
|
/// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_worker_with_programmatic_events() {
|
async fn test_worker_with_programmatic_events() {
|
||||||
use worker_types::{Event, ResponseStatus, StatusEvent};
|
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||||
|
|
||||||
// プログラムでイベントシーケンスを構築
|
// プログラムでイベントシーケンスを構築
|
||||||
let events = vec![
|
let events = vec![
|
||||||
|
|
@ -205,8 +205,8 @@ async fn test_worker_with_programmatic_events() {
|
||||||
/// id, name, input(JSON)を正しく抽出できることを検証する。
|
/// id, name, input(JSON)を正しく抽出できることを検証する。
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_tool_call_collector_integration() {
|
async fn test_tool_call_collector_integration() {
|
||||||
|
use worker::llm_client::event::Event;
|
||||||
use worker::timeline::{Timeline, ToolCallCollector};
|
use worker::timeline::{Timeline, ToolCallCollector};
|
||||||
use worker_types::Event;
|
|
||||||
|
|
||||||
// ToolUseブロックを含むイベントシーケンス
|
// ToolUseブロックを含むイベントシーケンス
|
||||||
let events = vec![
|
let events = vec![
|
||||||
|
|
@ -222,7 +222,8 @@ async fn test_tool_call_collector_integration() {
|
||||||
|
|
||||||
// イベントをディスパッチ
|
// イベントをディスパッチ
|
||||||
for event in &events {
|
for event in &events {
|
||||||
timeline.dispatch(event);
|
let timeline_event: worker::timeline::event::Event = event.clone().into();
|
||||||
|
timeline.dispatch(&timeline_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 収集されたToolCallを確認
|
// 収集されたToolCallを確認
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ mod common;
|
||||||
|
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use worker::Worker;
|
use worker::Worker;
|
||||||
use worker_types::{Event, Message, MessageContent, ResponseStatus, StatusEvent};
|
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||||
|
use worker::{Message, MessageContent};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Mutable状態のテスト
|
// Mutable状態のテスト
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user