Bump llm-worker to 0.2.1

This commit is contained in:
Keisuke Hirata 2026-01-14 16:30:41 +09:00
parent 1fd7a4c698
commit 6d87da90d1
3 changed files with 70 additions and 52 deletions

2
Cargo.lock generated
View File

@ -708,7 +708,7 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]] [[package]]
name = "llm-worker" name = "llm-worker"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap", "clap",

View File

@ -31,41 +31,48 @@ graph TD
Workerは以下のループターンを実行します。 Workerは以下のループターンを実行します。
1. **Start Turn**: `Worker::run(messages)` 呼び出し 1. **Start Turn**: `Worker::run(messages)` 呼び出し
2. **Hook: OnMessageSend**: 2. **Hook: OnMessageSend**:
* ユーザーメッセージの改変、バリデーション、キャンセルが可能。 - ユーザーメッセージの改変、バリデーション、キャンセルが可能。
* コンテキストへのシステムプロンプト注入などもここで行う想定。 - コンテキストへのシステムプロンプト注入などもここで行う想定。
3. **Request & Stream**: 3. **Request & Stream**:
* LLMへリクエスト送信。イベントストリーム開始。 - LLMへリクエスト送信。イベントストリーム開始。
* `Timeline`によるイベント処理。 - `Timeline`によるイベント処理。
4. **Tool Handling (Parallel)**: 4. **Tool Handling (Parallel)**:
* レスポンス内に含まれる全てのTool Callを収集。 - レスポンス内に含まれる全てのTool Callを収集。
* 各Toolに対して **Hook: BeforeToolCall** を実行(実行可否、引数改変)。 - 各Toolに対して **Hook: BeforeToolCall** を実行(実行可否、引数改変)。
* 許可されたToolを**並列実行 (`join_all`)**。 - 許可されたToolを**並列実行 (`join_all`)**。
* 各Tool実行後に **Hook: AfterToolCall** を実行(結果の確認、加工)。 - 各Tool実行後に **Hook: AfterToolCall** を実行(結果の確認、加工)。
5. **Next Request Decision**: 5. **Next Request Decision**:
* Tool実行結果がある場合 -> 結果をMessageとしてContextに追加し、**Step 3へ戻る** (自動ループ)。 - Tool実行結果がある場合 -> 結果をMessageとしてContextに追加し、**Step
* Tool実行がない場合 -> Step 6へ。 3へ戻る** (自動ループ)。
6. **Hook: OnTurnEnd**: - Tool実行がない場合 -> Step 6へ。
* 最終的な応答に対するチェックLint/Fmt 6. **Hook: OnTurnEnd**:
* エラーがある場合、エラーメッセージをContextに追加して **Step 3へ戻る** ことで自己修正を促せる。 - 最終的な応答に対するチェックLint/Fmt
* 問題なければターン終了。 - エラーがある場合、エラーメッセージをContextに追加して **Step 3へ戻る**
ことで自己修正を促せる。
- 問題なければターン終了。
## Tool 設計 ## Tool 設計
### アーキテクチャ概要 ### アーキテクチャ概要
Rustの静的型付けシステムとLLMの動的なツール呼び出し文字列による指定を、**Trait Object** と **動的ディスパッチ** を用いて接続します。 Rustの静的型付けシステムとLLMの動的なツール呼び出し文字列による指定を、**Trait
Object** と **動的ディスパッチ** を用いて接続します。
1. **共通インターフェース (`Tool` Trait)**: 全てのツールが実装すべき共通の振る舞い(メタデータ取得と実行)を定義します。 1. **共通インターフェース (`Tool` Trait)**:
2. **ラッパー生成 (`#[tool]` Macro)**: ユーザー定義のメソッドをラップし、`Tool` Traitを実装した構造体を自動生成します。 全てのツールが実装すべき共通の振る舞い(メタデータ取得と実行)を定義します。
3. **レジストリ (`HashMap`)**: Workerは動的ディスパッチ用に `HashMap<String, Box<dyn Tool>>` でツールを管理します。 2. **ラッパー生成 (`#[tool]` Macro)**: ユーザー定義のメソッドをラップし、`Tool`
Traitを実装した構造体を自動生成します。
3. **レジストリ (`HashMap`)**: Workerは動的ディスパッチ用に
`HashMap<String, Box<dyn Tool>>` でツールを管理します。
この仕組みにより、「名前からツールを探し、JSON引数を型変換して関数を実行する」フローを安全に実現します。 この仕組みにより、「名前からツールを探し、JSON引数を型変換して関数を実行する」フローを安全に実現します。
### 1. Tool Trait 定義 ### 1. Tool Trait 定義
ツールが最低限持つべきインターフェースです。`Send + Sync` を必須とし、マルチスレッド(並列実行)に対応します。 ツールが最低限持つべきインターフェースです。`Send + Sync`
を必須とし、マルチスレッド(並列実行)に対応します。
```rust ```rust
#[async_trait] #[async_trait]
@ -113,7 +120,8 @@ impl MyApp {
**マクロ展開後のイメージ (擬似コード):** **マクロ展開後のイメージ (擬似コード):**
マクロは、元のメソッドに対応する**ラッパー構造体**を生成します。このラッパーが `Tool` Trait を実装します。 マクロは、元のメソッドに対応する**ラッパー構造体**を生成します。このラッパーが
`Tool` Trait を実装します。
```rust ```rust
// 1. 引数をデシリアライズ用の中間構造体に変換 // 1. 引数をデシリアライズ用の中間構造体に変換
@ -155,15 +163,18 @@ impl Tool for GetUserTool {
### 3. Workerによる実行フロー ### 3. Workerによる実行フロー
Workerは生成されたラッパー構造体を `Box<dyn Tool>` として保持し、以下のフローで実行します。 Workerは生成されたラッパー構造体を `Box<dyn Tool>`
として保持し、以下のフローで実行します。
1. **登録**: アプリケーション開始時、コンテキスト(`MyApp`)から各ツールのラッパー(`GetUserTool`)を生成し、WorkerのMapに登録。 1. **登録**:
2. **解決**: LLMからのレスポンスに含まれる `ToolUse { name: "get_user", ... }` を受け取る。 アプリケーション開始時、コンテキスト(`MyApp`)から各ツールのラッパー(`GetUserTool`)を生成し、WorkerのMapに登録。
3. **検索**: `name` をキーに Map から `Box<dyn Tool>` を取得。 2. **解決**: LLMからのレスポンスに含まれる `ToolUse { name: "get_user", ... }`
4. **実行**: を受け取る。
* `tool.execute(json)` を呼び出す。 3. **検索**: `name` をキーに Map から `Box<dyn Tool>` を取得。
* 内部で `serde_json` による型変換とメソッド実行が行われる。 4. **実行**:
* 結果が返る。 - `tool.execute(json)` を呼び出す。
- 内部で `serde_json` による型変換とメソッド実行が行われる。
- 結果が返る。
これにより、型安全性を保ちつつ、動的なツール実行が可能になります。 これにより、型安全性を保ちつつ、動的なツール実行が可能になります。
@ -171,8 +182,9 @@ Workerは生成されたラッパー構造体を `Box<dyn Tool>` として保持
### コンセプト ### コンセプト
* **制御の介入**: ターンの進行、メッセージの内容、ツールの実行に対して介入します。 - **制御の介入**:
* **Contextへのアクセス**: メッセージ履歴Contextを読み書きできます。 ターンの進行、メッセージの内容、ツールの実行に対して介入します。
- **Contextへのアクセス**: メッセージ履歴Contextを読み書きできます。
### Hook Trait ### Hook Trait
@ -219,7 +231,8 @@ pub enum OnTurnEndResult {
### Tool Call Context ### Tool Call Context
`before_tool_call` / `after_tool_call` は、ツール実行の文脈を含む入力を受け取る。 `before_tool_call` / `after_tool_call`
は、ツール実行の文脈を含む入力を受け取る。
```rust ```rust
pub struct ToolCallContext { pub struct ToolCallContext {
@ -237,17 +250,20 @@ pub struct ToolResultContext {
## 実装方針 ## 実装方針
1. **Worker Struct**: 1. **Worker Struct**:
* `Timeline`を所有。 - `Timeline`を所有。
* `Handler`として「ToolCallCollector」をTimelineに登録。 - `Handler`として「ToolCallCollector」をTimelineに登録。
* `stream`終了後に収集したToolCallを処理するロジックを持つ。 - `stream`終了後に収集したToolCallを処理するロジックを持つ。
- **履歴管理**: `set_history`, `with_messages`, `history_mut`
等を通じて、会話履歴の注入や編集を可能にする。
2. **Tool Executor Handler**: 2. **Tool Executor Handler**:
* Timeline上ではツール実行を行わず、あくまで「ToolCallブロックの収集」に徹するToolの実行は非同期かつ並列で、ストリーム終了後あるいはブロック確定後に行うため - Timeline上ではツール実行を行わず、あくまで「ToolCallブロックの収集」に徹するToolの実行は非同期かつ並列で、ストリーム終了後あるいはブロック確定後に行うため
* ただし、リアルタイム性を重視する場合ストリーミング中にToolを実行開始等は将来的な拡張とするが、現状は「結果が揃うのを待って」という要件に従い、収集フェーズと実行フェーズを分ける。 - ただし、リアルタイム性を重視する場合ストリーミング中にToolを実行開始等は将来的な拡張とするが、現状は「結果が揃うのを待って」という要件に従い、収集フェーズと実行フェーズを分ける。
3. **worker-macros**: 3. **worker-macros**:
* `syn`, `quote` を用いて、関数定義から `Tool` トレイト実装と `InputSchema` (schemars利用) を生成。 - `syn`, `quote` を用いて、関数定義から `Tool` トレイト実装と `InputSchema`
(schemars利用) を生成。
## Worker Event API 設計 ## Worker Event API 設計
@ -256,6 +272,7 @@ pub struct ToolResultContext {
Workerは内部でイベントを処理し結果を返しますが、UIへのストリーミング表示やリアルタイムフィードバックには、イベントを外部に公開する仕組みが必要です。 Workerは内部でイベントを処理し結果を返しますが、UIへのストリーミング表示やリアルタイムフィードバックには、イベントを外部に公開する仕組みが必要です。
**要件**: **要件**:
1. テキストデルタをリアルタイムでUIに表示 1. テキストデルタをリアルタイムでUIに表示
2. ツール呼び出しの進行状況を表示 2. ツール呼び出しの進行状況を表示
3. ブロック完了時に累積結果を受け取る 3. ブロック完了時に累積結果を受け取る
@ -264,10 +281,10 @@ Workerは内部でイベントを処理し結果を返しますが、UIへのス
Worker APIは **Timeline層のHandler機構の薄いラッパー** として設計します。 Worker APIは **Timeline層のHandler機構の薄いラッパー** として設計します。
| 層 | 目的 | 提供するもの | | 層 | 目的 | 提供するもの |
|---|------|-------------| | ------------------------ | ------------------ | ---------------------------------- |
| **Handler (Timeline層)** | 内部実装、役割分離 | スコープ管理 + Deltaイベント | | **Handler (Timeline層)** | 内部実装、役割分離 | スコープ管理 + Deltaイベント |
| **Worker Event API** | ユーザー向け利便性 | Handler露出 + Completeイベント追加 | | **Worker Event API** | ユーザー向け利便性 | Handler露出 + Completeイベント追加 |
Handlerのスコープ管理パターンStart→Delta→Endをそのまま活かしつつ、累積済みのCompleteイベントも追加提供します。 Handlerのスコープ管理パターンStart→Delta→Endをそのまま活かしつつ、累積済みのCompleteイベントも追加提供します。
@ -448,7 +465,8 @@ impl<C: LlmClient> Worker<C> {
### 設計上のポイント ### 設計上のポイント
1. **Handlerの再利用**: 既存のHandler traitをそのまま活用 1. **Handlerの再利用**: 既存のHandler traitをそのまま活用
2. **スコープ管理の維持**: ブロックイベントはStart→Delta→Endのライフサイクルを保持 2. **スコープ管理の維持**:
ブロックイベントはStart→Delta→Endのライフサイクルを保持
3. **選択的購読**: on_*で必要なイベントだけ、またはSubscriberで一括 3. **選択的購読**: on_*で必要なイベントだけ、またはSubscriberで一括
4. **累積イベントの追加**: Worker層でComplete系イベントを追加提供 4. **累積イベントの追加**: Worker層でComplete系イベントを追加提供
5. **後方互換性**: 従来の`run()`も引き続き使用可能 5. **後方互換性**: 従来の`run()`も引き続き使用可能

View File

@ -1,7 +1,7 @@
[package] [package]
name = "llm-worker" name = "llm-worker"
description = "A library for building autonomous LLM-powered systems" description = "A library for building autonomous LLM-powered systems"
version = "0.2.0" version = "0.2.1"
publish.workspace = true publish.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true