yoi/docs/research/llm-worker-vs-rust-llm-libs.md

15 KiB
Raw Blame History

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: WorkerStateMutable | Locked の sealed trait
  • ライフサイクル: Mutable で履歴/プロンプト編集 → lock()Lockedrun() / 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_turnsextended_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_submitPromptAction
  • pre_llm_requestPreRequestAction
  • pre_tool_callPreToolAction
  • post_tool_callPostToolAction
  • on_turn_endTurnEndAction

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_opsAgent を 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-workerllm_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-workerllm_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 に寄せるかでだいぶ軽くなる
  • タイミング: 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 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/