tokio-prompt-orchestrator

Rust クレート · Tokio · MIT

ユーザーとLLMの間に、上限付きのパイプラインを。

重複したプロンプトは一度のモデル呼び出しを共有します。プロバイダーが落ちるとサーキットブレーカーが作動し、リクエストは溜まり続けずに即座に失敗します。パイプラインが落としたリクエストはすべてデッドレターキューに入り、中身を確認して再実行できます。Anthropic、OpenAI、llama.cpp、vLLM、自前のバックエンドに対応します。

Windows版をダウンロード(.exe) macOS, Linux, Homebrew, Scoop, cargo
cargo run --example llm_pipeline
APIキー不要。下の図とまったく同じシナリオを実行します。

実際の実行を一回分、再生

4人のユーザーが12件のリクエストで3つの質問をし、その後、擬似的なプロバイダー障害の最中にさらに8件が届きます。点の一つひとつがリクエストで、タイムスタンプはすべて記録した実行のものです。五分の一の速度で再生します。

経過時間0.0 ms
モデル呼び出し0
応答済み0
ブレーカーclosed
DLQ行き0

出典:llm_pipeline_trace.json。内蔵のモックモデル(呼び出しごとに300 ms)でcargo run --example llm_pipelineを実行、デバッグビルド、Windows x86_64。

重複排除

同一のプロンプトはキーでまとめられます。この実行では12件のリクエストが3回のモデル呼び出しになり、9件の重複は重複排除キャッシュから合計0.3 msで応答しました。

enhanced::Deduplicator

サーキットブレーカー

ステージ3のすべてのワーカー呼び出しを包みます。5回失敗すると開き、以降のリクエストはプロバイダーに触れずに拒否されます。60 s後に再び様子を見ます。

enhanced::CircuitBreaker

デッドレターキュー

黙って捨てられるものはありません。チャネルの満杯、タイムアウト、期限切れ、失敗、ブレーカーによる拒否は、すべて理由付きで1000件のリングバッファに入ります。

handles.dlq.drain()

バックプレッシャー

五つのTokioタスクを上限付きチャネル(512, 512, 512, 1024, 512, 256)でつないでいます。モデルが遅いとプロデューサーは待つか負荷を落とすので、高負荷でもメモリは一定のままです。

spawn_pipeline(worker)

その実行の出力

このサンプルは約200行で、自前のバックエンドをつなぐテンプレートにちょうどいい作りです。キーを用意してPROVIDER=anthropicまたはPROVIDER=openaiを設定すると、実際のモデルに対して同じ3回の呼び出しを行います。障害はプロバイダーの手前で擬似的に起こすので、費用はかかりません。

examples/llm_pipeline.rs を読む

cargo run --example llm_pipeline

  

リクエストの流れ

耐障害性を担うのはステージ3です。ModelWorkerの周りで期限チェック、サーキットブレーカー、タイムアウトを行います。重複排除、リトライ、キャッシュ、レート制限、負荷分散はワーカーの外側に重ねる部品で、サンプルがDeduplicatorでやっているのとまったく同じ形で使います。

上限付きチャネルでつながった五つのステージ:取得、組み立て、推論、後処理、ストリーミング。ステージ3はモデルワーカーの周りで期限チェック、サーキットブレーカー、タイムアウトを実行します。落とされたリクエストは理由付きでデッドレターキューに入ります。

クイックスタート

1動かしてみる

サンプルをクローンして実行します。キーもネットワークも不要です。

git clone https://gitlab.com/mattbusel/tokio-prompt-orchestrator
cd tokio-prompt-orchestrator
cargo run --example llm_pipeline

2サーバーをインストールして起動

対話型ターミナルと、ポート8080のHTTP API。echoならキー不要です。Windows(PowerShellの一行)、Homebrew、Scoop、cargo binstallについては ダウンロード を参照してください。

curl -fsSL https://gitlab.com/mattbusel/tokio-prompt-orchestrator/-/raw/main/install.sh | sh
orchestrator --provider echo

# then, from another terminal
curl -s -X POST http://127.0.0.1:8080/api/v1/infer \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"What is backpressure?"}'

3ライブラリとして使う

EchoWorkerをAnthropicWorker、OpenAiWorker、LlamaCppWorker、VllmWorkerのいずれかに差し替えます。

[dependencies]
tokio-prompt-orchestrator = "1.4"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use std::{collections::HashMap, sync::Arc};
use tokio_prompt_orchestrator::{spawn_pipeline, EchoWorker, ModelWorker, PromptRequest, SessionId};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let worker: Arc<dyn ModelWorker> = Arc::new(EchoWorker::new());
    let handles = spawn_pipeline(worker);
    let mut output = handles.take_output_rx().await.ok_or("output taken")?;

    handles.input_tx.send(PromptRequest {
        session: SessionId::new("demo"),
        request_id: "req-1".into(),
        input: "Hello, pipeline!".into(),
        meta: HashMap::new(),
        deadline: None,
    }).await?;

    if let Some(out) = output.recv().await { println!("{}", out.text); }
    for dropped in handles.dlq.drain() { println!("dropped {}: {}", dropped.request_id, dropped.reason); }
    Ok(())
}

エージェントやツール向けのサーバー

orchestratorバイナリは、ターミナルのプロンプトとREST、SSE、WebSocket APIを並行して動かします。プロンプトをPOSTしてリクエストIDを受け取り、結果を取得します。/healthはブレーカーの状態とキューの深さを返します。

Claude DesktopとClaude Codeは、別のmcpバイナリを介してstdioで接続します:MCPの設定

Windows:.exeをダウンロードしてダブルクリック。セットアップでechoを選べば、キーなしで試せます。

--provider echo で起動したorchestratorが、ターミナルに入力されたプロンプトと、curlで /api/v1/infer に送られたプロンプトの両方に応答している様子

ほかにも入っている機能

レートリミッター

モデルごとのスライディングウィンドウとトークンバケット。毎分10リクエストに制限したときの実際の出力:

req-01 ok
...
req-10 ok
req-11 throttled: sliding window limit exceeded; window resets in 60001 ms
req-12 throttled: sliding window limit exceeded; window resets in 60001 ms

rate_limiter::RateLimiterRegistry

バックエンド

Anthropic、OpenAI、llama.cpp、vLLMのワーカー、オフラインのEchoWorker、ラウンドロビンや最小負荷のプール向けのLoadBalancedWorker。それ以外はasync traitを一つ実装するだけです。

trait ModelWorker

ターミナルダッシュボード

キューの深さ、ブレーカーの状態、重複排除による節約、ログパネルをRatatuiのTUIで表示します。モックデータモード付きなので、パイプラインを動かさなくても見た目を確認できます。

cargo run --bin tui --features tui

オプション機能

feature flagでPrometheusメトリクス、OpenTelemetryトレーシング、Redisを使った分散重複排除、プロンプトテンプレート、A/Bテスト、自動チューニングの制御ループを追加できます。デフォルトのビルドにはオプションの依存関係は含まれません。

--features full

オーバーヘッド

BENCHMARKS.md に記録された最新の実行結果です(Windows、x86_64、EchoWorkerのため、ネットワークやモデルの時間は含みません)。

計測項目p50
レートリミッターのチェック110 ns
send_with_shed (負荷を落とす非ブロッキング送信)204 ns
サーキットブレーカーのチェック(閉状態)~0.4 µs
重複排除のチェック(キャッシュ済み)~1.5 µs
同一プロンプト100件を同時送信、重複排除あり、推論は一回合計 52.6 µs
EchoWorkerの同時呼び出し1000回合計 7.1 ms