245 lines
15 KiB
Markdown
245 lines
15 KiB
Markdown
# `llm-worker` vs Rust LLM ライブラリ群 比較レポート
|
||
|
||
調査日: 2026-04-26
|
||
対象: `crates/llm-worker` (本プロジェクト) / `rig` / `genai` / `swiftide`
|
||
|
||
調査方法: 各リポジトリを ローカルに取得し、ソース(README, Cargo.toml, src/, examples/)を直接読解。
|
||
|
||
---
|
||
|
||
## 0. TL;DR
|
||
|
||
```
|
||
低レイヤ ←──────────────────────────────────────→ 高レイヤ
|
||
HTTP wrapper Worker/Loop Agent Framework Pipeline/RAG
|
||
───────────── ──────────── ──────────────── ──────────────
|
||
genai ●●●●● (out-of-scope)
|
||
llm-worker ●●●●● (一部)
|
||
rig ●●● ●●●●● ●●●
|
||
swiftide ●● ●●●● ●●●●● (主軸)
|
||
```
|
||
|
||
- **`genai`** … マルチプロバイダの「統一 chat client」。エージェントループ・状態管理は明示的にスコープ外。
|
||
- **`llm-worker`** … Worker = 「LLM 対話の中央実行器」。低レベル(HTTP)と高レベル(Agent / RAG / Pipeline)の中間に位置するレイヤを精緻に作っている。
|
||
- **`rig`** … Agent / Tool / Pipeline / VectorStore を一通り揃えた汎用フレームワーク。Provider 数とエコシステムが最大の武器。
|
||
- **`swiftide`** … 一次目的はストリーミング Indexing / Query パイプライン。Agent はあとから足された二の矢で、`async-openai` / `async-anthropic` をラップ。
|
||
|
||
要点: **`llm-worker` の独自性は「型状態でのキャッシュ保護」「Interceptor による上位層への決定委譲」「Prune を context projection として非破壊で扱う」の 3 点**。これらは他 3 者の誰も持っていない。一方、Provider カバレッジ・VectorStore・Pipeline DAG では rig に大差で負ける。
|
||
|
||
---
|
||
|
||
## 1. スコープ宣言(各プロジェクトが自分を何と呼んでいるか)
|
||
|
||
| プロジェクト | 自称 | 提供する | 提供しない(明示・暗黙) |
|
||
|---|---|---|---|
|
||
| **llm-worker** | "LLM との対話を管理する低レベル基盤クレート" | Worker による turn 実行、Tool 実行、stream イベント、Interceptor、cache 保護 | RAG / Embedding / VectorStore / Pipeline DAG / 永続化 |
|
||
| **rig** | "Ergonomic & modular library for building LLM applications" | Agent / Tool / Pipeline / Vector / Embedding / OTel | (ほぼ全部入り。明示的な non-goal は薄い) |
|
||
| **genai** | "Standardizing chat completion APIs across major AI services" | Chat (sync/stream)、Tool 定義、Embedding | **Agent loop / 会話状態管理を明示的に out-of-scope** |
|
||
| **swiftide** | "Fast, streaming indexing, query, and agentic LLM applications" / "data pipeline" | Indexing pipeline / Query state machine / Agent / Hook | (LLM HTTP は外部 SDK = `async-openai` 等に委譲) |
|
||
|
||
`llm-worker` のスコープ宣言は他より控えめで、「何かの上位層から呼ばれる前提」が読み取れる(`Interceptor` の存在がそれを裏付ける)。
|
||
|
||
---
|
||
|
||
## 2. レイヤ階層と被り
|
||
|
||
`Worker` が担っているレイヤを基準にすると、各プロジェクトの「同じ層」と「上下層」は次のように整理できる。
|
||
|
||
### 同じ層(== 競合)
|
||
|
||
| 機能 | llm-worker | rig | genai | swiftide |
|
||
|---|---|---|---|---|
|
||
| Provider 抽象 (HTTP) | ◎ 自前 (4) | ◎ 自前 (20+) | ◎ 自前 (18+) | ○ 外部 SDK ラップ |
|
||
| Streaming イベント正規化 | ◎ | ◎ | ◎ | ○ |
|
||
| Tool 定義 + 実行ループ | ◎ | ◎ | △ (定義のみ、loop は呼び出し側) | ◎ |
|
||
| Multi-turn loop | ◎ | ◎ | ✕ | ◎ |
|
||
| Hook / Interceptor | ◎ Interceptor + closure | ◎ PromptHook | △ Resolver のみ | ◎ 6+ Hook |
|
||
| 履歴管理 | ◎ Item / ContentPart | ◎ Message | △ ChatRequest を呼び出し側で append | ◎ MessageHistory trait |
|
||
|
||
### 上下層(== 補完関係)
|
||
|
||
| 機能 | llm-worker | rig | genai | swiftide |
|
||
|---|---|---|---|---|
|
||
| Embedding / VectorStore | ✕ | ◎ 10+ | △ (Embed のみ) | ◎ 9+ |
|
||
| RAG | ✕ | ◎ | ✕ | ◎ |
|
||
| Pipeline DAG | ✕ | ◎ `parallel!` / `try_op` | ✕ | ◎ Indexing pipeline |
|
||
| 永続化 | ✕ | △ | ✕ | ◎ MessageHistory swap |
|
||
| OTel / Observability | △ tracing のみ | ◎ GenAI Semantic Conv | △ | ◎ Langfuse 等 |
|
||
| 型状態でのキャッシュ保護 | **◎ 唯一** | ✕ | ✕ | ✕ |
|
||
|
||
---
|
||
|
||
## 3. コアの抽象を並べる
|
||
|
||
### `llm-worker::Worker<C, S>`
|
||
- `C: LlmClient`(プロバイダ)、`S: WorkerState`(`Mutable` | `Locked` の sealed trait)
|
||
- ライフサイクル: `Mutable` で履歴/プロンプト編集 → `lock()` → `Locked` で `run()` / `resume()` → `unlock()` で戻る
|
||
- `state.rs:1-61`: `Locked` 中は履歴 append-only、`set_system_prompt` 等は型レベルで呼べない
|
||
- `prune.rs:66-118`: `prunable_indices()` + `project()` で context のみ縮約。永続履歴は不変 = "context projection"
|
||
|
||
### `rig::Agent` + `PromptRequest`
|
||
- 状態機械型: `max_turns`、`extended_details()` で usage / message tracking
|
||
- `PromptHook` trait: `on_completion_call`, `on_completion_response`, `on_tool_call` (skip 可), `on_tool_result`, `on_text_delta`, …
|
||
- `ToolSet` + `ToolEmbedding` で RAG 可能なツール
|
||
- Pipeline DAG: `Op` trait + `parallel!` macro
|
||
|
||
### `genai::Client`
|
||
- `exec_chat(model, ChatRequest, options) -> ChatResponse`
|
||
- `ChatStreamEvent { Start, Chunk, ReasoningChunk, ToolCallChunk, End }`
|
||
- Adapter pattern: `to_web_request_data` / `to_chat_response` / `to_chat_stream`
|
||
- モデル名の prefix で provider 推論 (`gpt-` → OpenAI, `claude-` → Anthropic)
|
||
- 状態管理は呼び出し側(`ChatRequest` を毎ターン clone & append)
|
||
|
||
### `swiftide-agents::Agent`
|
||
- Builder: `llm`, `context`, `tools`, `toolboxes`, `hooks`
|
||
- `AgentContext` trait + `DefaultContext` (AtomicUsize で completions ポインタ管理)
|
||
- State enum: `Pending` / `Running` / `Stopped(StopReason)`
|
||
- Tool execution: `LocalExecutor` と MCP (Model Context Protocol)
|
||
- Provider 層は `async-openai` 0.33+ / `async-anthropic` 0.6 を流用
|
||
|
||
---
|
||
|
||
## 4. 三大独自点(`llm-worker` だけが持っているもの)
|
||
|
||
### 4.1 型状態によるキャッシュ保護 — `state::WorkerState`
|
||
|
||
```
|
||
Worker<C, Mutable> Worker<C, Locked>
|
||
├─ history_mut() ✓ ├─ history_mut() ✗ (compile error)
|
||
├─ set_system_prompt() ✓ ├─ set_system_prompt() ✗
|
||
├─ register_tool() ✓ ├─ run() / resume() ✓
|
||
└─ lock() ─────────────────→ │
|
||
└─ unlock() ──────────→ Mutable
|
||
```
|
||
|
||
- **何を防ぐか**: KV cache の prefix が変わるような編集を `Locked` 中に行うこと(=次の `run()` で cache miss を引く事故)
|
||
- **誰がやっているか**: rig / genai / swiftide のいずれもキャッシュ整合性は「呼び出し側のお気持ち」レベル。genai は `CacheControl::Ephemeral / Stable` enum を持つが、それはマーク用であって状態機械ではない
|
||
- **意義**: production の Anthropic / OpenAI prompt cache 利用で、誤った `set_system_prompt` が静かに課金を倍増させる事故を**コンパイル時に潰せる**
|
||
|
||
### 4.2 Interceptor による上位層への決定委譲 — `interceptor.rs`
|
||
|
||
`Interceptor` trait が持つフック点:
|
||
- `on_prompt_submit` → `PromptAction`
|
||
- `pre_llm_request` → `PreRequestAction`
|
||
- `pre_tool_call` → `PreToolAction`
|
||
- `post_tool_call` → `PostToolAction`
|
||
- `on_turn_end` → `TurnEndAction`
|
||
|
||
**rig の `PromptHook` との違い**: rig のフックは「観察 + skip/abort のフラグ」。`llm-worker` は **戻り値が ActionEnum** で、上位層が次の挙動を選択できる(例: `PreToolAction::Override(custom_result)`)。これは `Worker` を「自律ループ」と「上位 orchestrator から駆動される実行器」の両方として使える設計上の選択。
|
||
|
||
### 4.3 Context Projection による prune — `prune.rs`
|
||
|
||
- 削るのは `Item::ToolResult { content }` の中身だけ。`Item` 自体は履歴に残す(summary は維持)
|
||
- `project()` は **イミュータブル変換** で永続履歴を変えない
|
||
- savings 推定は意図的に上位層に委譲(`prune.rs` 末尾コメント)
|
||
- swiftide も MessageHistory に `Summary` メッセージで似たことをやるが、**永続履歴を直接置換**する方式。`llm-worker` の方が rollback / 再生 / debugging に強い
|
||
|
||
---
|
||
|
||
## 5. 各競合の「これは llm-worker に勝っている」点
|
||
|
||
### rig
|
||
- **Provider 数**: 20+(OpenAI / Anthropic / Gemini / Bedrock / Vertex / Azure / Groq / DeepSeek / Cohere / Mistral / OpenRouter / Together / xAI / Perplexity / HuggingFace …)
|
||
- **VectorStore**: MongoDB / LanceDB / Neo4j / Qdrant / SQLite / SurrealDB / Milvus / ScyllaDB / S3Vectors / HelixDB
|
||
- **Pipeline DAG**: `parallel!` macro / `try_op` / agent_ops(Agent を Op として組み立て可能)
|
||
- **OTel**: GenAI Semantic Convention に完全準拠
|
||
- **WASM**: `rig-core` のみ WASM 互換
|
||
- **更新頻度**: 直近 20 commit が 1〜2 週間以内
|
||
|
||
### genai
|
||
- **Provider 数**: 18+
|
||
- **薄さ**: 6,539 LOC / 35 deps で multi-provider client が完成している(`llm-worker` の `llm_client/` は 1.5k LOC で 4 provider)
|
||
- **Native protocol サポート**: Anthropic reasoning, Gemini thinking, OpenAI strict mode, OpenAI Responses API の `previous_response_id`/`store` を素通し
|
||
- **Tool call streaming**: `ToolCallChunk` が分離イベント
|
||
- **Resolver** で auth / model / endpoint をフレキシブルに差し替え
|
||
|
||
### swiftide
|
||
- **Indexing pipeline**: streaming loader → transformer → chunker → storage の fluent パイプライン
|
||
- **Query state machine**: Pending → Retrieved → Answered の型状態(くしくも型状態だが対象が違う)
|
||
- **Hook 粒度**: `before_all` / `before_completion` / `after_completion` / `before_tool` / `after_tool` / `on_new_message` / `on_stop` 等 6+
|
||
- **MCP サポート**: ToolExecutor として LocalExecutor と MCP の両方
|
||
- **MessageHistory trait** で永続化バックエンド(Redis 等)に差し替え可能
|
||
- **既存 SDK の活用**: `async-openai` / `async-anthropic` をベースに薄く乗せる戦略
|
||
|
||
---
|
||
|
||
## 6. 各競合の「これは llm-worker の方が良い」点
|
||
|
||
### vs rig
|
||
- **キャッシュ整合性**: rig は `extended_details()` で usage を観測できるが、prefix 変更を**防止**するメカニズムはない
|
||
- **依存の薄さ**: rig-core 25.5K LOC + 大量 sub-crate vs llm-worker 5.8K LOC
|
||
- **Worker の単一責務**: rig の `Agent` は LLM 呼び出し + Tool + (optional) RAG までを抱え込む。`llm-worker` は orchestrator を分離
|
||
|
||
### vs genai
|
||
- **Tool 実行ループ**: genai は呼び出し側で書く必要がある(README/example で明示)
|
||
- **会話履歴の構造化**: genai は `ChatRequest` を毎ターン clone する素朴な API
|
||
- **Interceptor**: genai は `Resolver` で初期化時のカスタマイズのみ。実行中フックなし
|
||
|
||
### vs swiftide
|
||
- **Provider 直接実装**: swiftide は `async-openai` 0.33 / `async-anthropic` 0.6 に依存しており、その上流に出来る/出来ないが縛られる。`llm-worker` は HTTP まで自前で握る
|
||
- **Agent loop の中核設計**: swiftide-agents は `DefaultContext` の AtomicUsize ベースで素朴。型状態保護なし
|
||
- **依存の少なさ**: swiftide-agents 単体でも `async-openai` を呼ぶ前提
|
||
|
||
---
|
||
|
||
## 7. 「ライブラリ化したとき競合するか」の解像度
|
||
|
||
| 相手 | 競合度 | 理由 |
|
||
|---|---|---|
|
||
| **rig** | 🟥 高 | 同じ "agent + tool + multi-provider" を狙う層。rig は既にエコシステムを持っており、正面衝突は不利 |
|
||
| **genai** | 🟨 中 | 層が違う(client vs worker)。むしろ `llm-worker` の `llm_client/` を捨てて genai に乗る選択肢がある |
|
||
| **swiftide** | 🟩 低 | 一次目的が違う(pipeline)。agent の中核実行を `llm-worker` に置き換える "embed" 戦略すら成立し得る |
|
||
|
||
---
|
||
|
||
## 8. ポジショニング案(仮にライブラリ化するなら)
|
||
|
||
`llm-worker` がぶつからない・かつ需要がある場所を考えると、次のような立ち位置が現実的:
|
||
|
||
> **"Production-grade LLM worker primitive — 型状態でキャッシュ整合性を守り、Interceptor で上位層 (rig / swiftide / 自社 orchestrator) から駆動できる低レベル実行器"**
|
||
|
||
具体的な打ち出し方:
|
||
1. **キャッシュ整合性をコンパイル時に保証する唯一のクレート** という明確な売り文句。Anthropic の prompt cache 課金事故で困った人を狙う
|
||
2. **Tool 実行ループ + Interceptor** をプリミティブとして提供し、上位フレームワークから plug できることを主張
|
||
3. **HTTP まで自前** はやめて、`llm_client/` を外す or feature 化し、`genai` バックエンドを optional 提供する選択肢も検討に値する(実装コストを 1.5k LOC 削れる)
|
||
4. **Pipeline / RAG / VectorStore は提供しない** ことを明示。rig / swiftide の代替を狙わないことで、共存ストーリーが書ける
|
||
|
||
---
|
||
|
||
## 9. ライブラリ化の判断材料(再)
|
||
|
||
- **需要がある層か**: yes。rig / swiftide / genai は揃って "もう一段下のキャッシュ整合性プリミティブ" を持っていない
|
||
- **既存と被るか**: 上記 3 案でかわせる
|
||
- **維持コスト**: API 安定化 + provider 追従が恒常的に乗る。`llm_client/` を外すか genai に寄せるかでだいぶ軽くなる
|
||
- **タイミング**: yoi 本体がリリースされ、`Worker` の API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。**先に yoi をリリースして、production 使用例として参照させてからライブラリ化する**方が安全
|
||
|
||
---
|
||
|
||
## 付録: 主要ファイルへの参照
|
||
|
||
### llm-worker
|
||
- `crates/llm-worker/src/lib.rs:1-59` — モジュール構成
|
||
- `crates/llm-worker/src/worker.rs:101-191` — Worker<C, S>
|
||
- `crates/llm-worker/src/state.rs:1-61` — Mutable / Locked 型状態
|
||
- `crates/llm-worker/src/interceptor.rs:1-147` — Interceptor trait
|
||
- `crates/llm-worker/src/prune.rs:66-118` — context projection
|
||
- `crates/llm-worker/src/llm_client/{auth,client,event,transport,types}.rs`
|
||
- `crates/llm-worker/src/tool_server.rs:27-52` — Deferred 登録
|
||
|
||
### rig
|
||
- `github.com/0xPlaygrounds/rig/rig/rig-core/src/{agent,completion,tool,pipeline,vector_store,streaming}/mod.rs`
|
||
|
||
### genai
|
||
- `github.com/jeremychone/rust-genai/src/lib.rs:1-25`
|
||
- `.../src/client/client_impl.rs:62-67`
|
||
- `.../src/chat/chat_request.rs:10-34`
|
||
- `.../src/chat/chat_stream.rs:11-86`
|
||
- `.../src/adapter/adapter_types.rs:11-59`
|
||
|
||
### swiftide
|
||
- `github.com/bosun-ai/swiftide/swiftide-agents/src/agent.rs:45-100`
|
||
- `.../swiftide-agents/src/hooks.rs`
|
||
- `.../swiftide-core/src/chat_completion/traits.rs`
|
||
- `.../swiftide-indexing/src/pipeline.rs`
|
||
- `.../swiftide-query/src/`
|