15 KiB
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 PromptHooktrait:on_completion_call,on_completion_response,on_tool_call(skip 可),on_tool_result,on_text_delta, …ToolSet+ToolEmbeddingで RAG 可能なツール- Pipeline DAG:
Optrait +parallel!macro
genai::Client
exec_chat(model, ChatRequest, options) -> ChatResponseChatStreamEvent { 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 AgentContexttrait +DefaultContext(AtomicUsize で completions ポインタ管理)- State enum:
Pending/Running/Stopped(StopReason) - Tool execution:
LocalExecutorと MCP (Model Context Protocol) - Provider 層は
async-openai0.33+ /async-anthropic0.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 / Stableenum を持つが、それはマーク用であって状態機械ではない - 意義: production の Anthropic / OpenAI prompt cache 利用で、誤った
set_system_promptが静かに課金を倍増させる事故をコンパイル時に潰せる
4.2 Interceptor による上位層への決定委譲 — interceptor.rs
Interceptor trait が持つフック点:
on_prompt_submit→PromptActionpre_llm_request→PreRequestActionpre_tool_call→PreToolActionpost_tool_call→PostToolActionon_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-openai0.33 /async-anthropic0.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) から駆動できる低レベル実行器"
具体的な打ち出し方:
- キャッシュ整合性をコンパイル時に保証する唯一のクレート という明確な売り文句。Anthropic の prompt cache 課金事故で困った人を狙う
- Tool 実行ループ + Interceptor をプリミティブとして提供し、上位フレームワークから plug できることを主張
- HTTP まで自前 はやめて、
llm_client/を外す or feature 化し、genaiバックエンドを optional 提供する選択肢も検討に値する(実装コストを 1.5k LOC 削れる) - Pipeline / RAG / VectorStore は提供しない ことを明示。rig / swiftide の代替を狙わないことで、共存ストーリーが書ける
9. ライブラリ化の判断材料(再)
- 需要がある層か: yes。rig / swiftide / genai は揃って "もう一段下のキャッシュ整合性プリミティブ" を持っていない
- 既存と被るか: 上記 3 案でかわせる
- 維持コスト: API 安定化 + provider 追従が恒常的に乗る。
llm_client/を外すか genai に寄せるかでだいぶ軽くなる - タイミング: insomnia 本体がリリースされ、
Workerの API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。先に insomnia をリリースして、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 traitcrates/llm-worker/src/prune.rs:66-118— context projectioncrates/llm-worker/src/llm_client/{auth,client,event,transport,types}.rscrates/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/