やってみた 2026年7月20日

APIキーを1本も登録せずにOmniRouteを起動したら、LLMの応答が返ってきた話

XECIN TypeScriptAIゲートウェイLLM検証

GitHubのトレンドを眺めていたら、diegosouzapw/OmniRoute が3位に上がっていました。「268プロバイダをひとつのエンドポイントに束ねるAIゲートウェイ。90以上の無料枠、$0 to start」と書いてあります。

私はQAの立場なので、この手の「フォールバックで落ちません」と言い切る仕組みを見ると、まず確かめたくなるのは可用性の主張そのものではなく、失敗をどう記録しているかのほうです。落ちない仕組みは、裏でどれだけ失敗を握り潰しているかとセットでしか評価できません。測れないものは改善できない、というのが私の信条でして。

なので今回の関心はひとつに絞りました。プロバイダを1件も登録しない状態でリクエストを投げたら、何が起きて、それがどこまでログに残るのか。

試した環境

  • ホストはWindows + Docker Desktop。検証は使い捨ての node:22-bookworm コンテナの中だけで完結
  • コンテナ内: Node.js v22.23.1 / npm 10.9.8(Debian 12 bookworm)
  • 対象: omniroute v3.8.48(npm 版・GitHub の最新タグも v3.8.48)

インストールも起動も全部コンテナ内で、ホストには何も入れていません。ダッシュボードだけ、コンテナの20128番をホストの127.0.0.1に公開してブラウザから見ています。

インストールは1コマンド、67秒

$ npm install -g omniroute
npm warn deprecated prebuild-install@7.1.3: No longer maintained. ...
added 1182 packages in 1m
ELAPSED_SEC=67

1182パッケージ。ゲートウェイ・ダッシュボード・CLI・SQLiteのネイティブバイナリまで全部入りなので、まあこのくらいにはなります。

起動前に omniroute doctor があるのは good だと思いました。診断は 6 ok / 11 warning / 0 failure で、warningの中身は「まだDBが無い」「サーバがまだ起動していない」「Claude Code / Codex / Cline … が入っていない」といった、この時点では当たり前のものばかり。ネイティブ依存(better-sqlite3)の互換性をここで見てくれるのは、後からハマるより圧倒的に親切です。

OK   Node runtime: v22.23.1 is supported
OK   Native binary: better-sqlite3 native binary is compatible
WARN Database: SQLite database not found at /root/.omniroute/storage.sqlite
WARN Server liveness: Server health endpoint returned HTTP no-response ...
Summary: 6 ok, 11 warning(s), 0 failure(s)

サーバはデーモンで起動しました。

$ omniroute serve --daemon --no-open
 OmniRoute started in background (PID: 369)
  Dashboard:  http://localhost:20128
  API Base:   http://localhost:20128/v1

ここでひとつ、動かしてみないと分からなかったこと。案内される /health は404で、/api/health は401、生きているかを外から見るなら /v1/models が素直でした。監視を仕込むなら、この差は事前に確認しておいたほうがいいと思います。

プロバイダ0件なのに、モデルは99個ある

providers list の結果は当然こうなります。

$ omniroute providers list
No providers configured.

ところが /v1/models は99個返してきます。中身は auto/best-codingauto/best-reasoningauto/best-fast … といった仮想コンボで、実プロバイダのモデル一覧ではありません。ここは正直、最初に見たとき「接続していないのに、これは何を指しているんだ」と戸惑いました。

さらに引っかかったのが providers available(カタログ表示)です。

$ omniroute providers available
6 providers available.
Categories: api-key
ID          Category   Name
openai      api-key    OpenAI
anthropic   api-key    Anthropic
google      api-key    Google AI
openrouter  api-key    OpenRouter
groq        api-key    Groq
mistral     api-key    Mistral

268という数字とは、だいぶ様子が違います。このCLIが見せているのは「APIキーを登録して繋ぐタイプ」の6件だけで、後述する内蔵の無料プールはここには出てきませんでした。カタログの表示面とルーティング実体が別、というのは把握しておかないと、providers available を見て「6つしか使えないのか」と誤読しそうです。

キーを1本も入れずにリクエストを投げたら、通ってしまった

本題です。何も設定していない状態で、そのまま叩きました。

$ curl -s -X POST http://127.0.0.1:20128/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{"model":"auto","stream":false,
         "messages":[{"role":"user","content":"Reply with exactly: hello from omniroute"}]}'

返ってきました。hello from omniroute と、指示どおりの1行が。プロバイダは0件、APIキーも0本、サインアップもしていません。

レスポンスヘッダはこうです。

x-omniroute-provider: oc
x-omniroute-model: big-pickle
x-omniroute-latency-ms: 6598
x-omniroute-tokens-in: 256
x-omniroute-tokens-out: 103
x-omniroute-response-cost: 0.0000000000
x-omniroute-cache: MISS
x-omniroute-compression: off; source=off

課金額は 0.0000000000。ダッシュボードのホーム画面を見ると、接続済みとして DuckDuckGo AI Chat / Augment (Auggie CLI) / OpenCode Free の3つがトポロジ図に出ていました。キー不要で使える無料プロバイダが最初から組み込まれていて、auto がそこへ流していた、という仕掛けです。

OmniRouteのダッシュボード ホーム画面。左サイドバーにエンドポイント・プロバイダー・コンボ・Compression Settingsなどが並び、中央のプロバイダートポロジ図でOmniRouteノードからDuckDuckGo AI Chat、Augment (Auggie CLI)、OpenCode Freeの3プロバイダへ線が伸びている

「$0 to start」は誇張ではなかった、というのがここまでの結論です。ただ、QAとしての本題はこの先でした。

200が返っていても、成功とは限らない

リクエストログを開いて、正直ぞっとしました。私が投げた最初の1リクエストの裏で、7行のログが積み上がっていたからです。

OmniRouteのリクエストログ画面。7 Total / 4 Success / 3 Error / 7 Combo と集計され、auggie・duckduckgo-web・opencodeへの各試行が200/418/429のステータスとともに時系列で並び、opencode/big-pickleの行にはhealedバッジと6 attemptsの表示がある

データディレクトリに落ちているJSONログを整形すると、カスケードの中身がそのまま読めます。

12:07:57.597 | 200 | auggie/claude-sonnet-4.6-thinking     | ms=31    | in=0   | out=0
12:08:00.577 | 418 | duckduckgo-web/claude-3-5-haiku-...   | ms=2971  | in=0   | out=0
12:08:01.677 | 418 | duckduckgo-web/mistral-small-2501     | ms=1088  | in=0   | out=0
12:08:01.733 | 200 | auggie/claude-haiku-4.5               | ms=50    | in=0   | out=0
12:08:02.825 | 429 | duckduckgo-web/gpt-4o-mini            | ms=1085  | in=0   | out=0
12:08:18.510 | 200 | opencode/big-pickle                   | ms=15671 | in=253 | out=769

429の中身は DuckDuckGo AI Chat error: ERR_RATE_LIMIT でした。

注目したいのは1行目と4行目です。ステータスは200なのに、入出力トークンが両方0。つまりHTTPとしては成功扱いで返ってきているのに、中身が無い。それでもルータは「これは使えない」と判断して次のターゲットへ進み、6ステップ目の opencode/big-pickle でようやく本物の応答(in=253 / out=769)を得ています。UI上のバッジは healed6 attempts

動いた=品質OKではない、という話をふだんから口酸っぱく言っているのですが、これはその教材みたいな挙動でした。ステータスコードだけを成功指標にして監視を組むと、この経路は「200が4回返っている健全なサービス」に見えてしまいます。逆に言えば、OmniRoute側が200かつ空応答をちゃんと失敗と見なして次へ送っているのは、実装として真っ当だと思いました。

もうひとつ、2回目のリクエストの挙動が良かった。

12:08:36.330 | 200 | opencode/big-pickle | ms=6597 | in=256 | out=103 | combo=auto

1回目は6段踏んで20秒近くかかったのに、2回目は最初から opencode へ直行して6.6秒。READMEでいう LKGP(Last-Known-Good-Path)が効いている挙動です。ということは、この手のゲートウェイのレイテンシは1回測っても意味がないということでもあります。暖機前と暖機後で3倍違う値を、そのままSLOの根拠にはできません。

CLIからのワンショットも同じ経路で通りました。

$ omniroute chat "What is 17*23? Answer with the number only."
391
[big-pickle · 7769ms · 2485 tok]

答えは合っています。ただ「17×23」を聞くのに2485トークン。ルーティング先が推論モデルで、思考トークンが乗っているためです(実際、先ほどのログでも completion 103 のうち reasoning が 95 でした)。無料だから気にしない、という考え方もありますが、無料枠は月あたりのトークンで切られるので、何気ない一問が枠を削る速度は把握しておいたほうがいいと考えています。

ちなみに、応答本体が名乗ったモデル名は xiaomi/mimo-v2.5 で、ゲートウェイ上の表示名 big-pickle とは別物でした。上流の実体とゲートウェイ上のエイリアスは一致しない前提でログを読む必要があります。

無料枠は「ダッシュボードで測れる」ようになっている

Free-Tier Budget のページが個人的には一番好みでした。

OmniRouteのFree-Tier Budget画面。STEADY / MONTH が約1.37B、FIRST MONTH (+CREDITS) が約2B、USED THIS MONTH が0と表示され、プロバイダ別の無料枠テーブル(mistral各モデル1B/monthly、llm7 GPT-4o mini 150M/daily、gemini 2.5 Flash 60M/daily など)が並ぶ。上部に「88 models flagged as ToS-restricted — you decide」の警告が出ている

月あたり約1.37B(初月は特典込みで約2B)、今月の使用量0、という残量表示です。プールを重複排除して数えている旨が画面にも明記されていて、「レート制限を24時間×31日で掛け算した理論値」を出していないところに好感を持ちました。

同時に、88 models flagged as ToS-restricted — you decide(88モデルは利用規約上のリスクありとフラグ、判断は利用者側)という警告も同じ画面に出ています。無料枠を束ねる以上、規約の当たり判定はどうしても利用者持ちになるわけで、ここを隠さず出しているのは実務的だと感じました。業務で使うなら、この88件をどう扱うかを決めるのが最初の仕事になります。

引っかかった細かいところ

日本語ロケールで開いたダッシュボードには、__MISSING__:Free-Tier Budget __MISSING__:Clean history という未翻訳キーのプレースホルダがそのまま出ていました。動作には影響しませんが、翻訳が全ページ揃っているわけではないようです。

もう少し気になったのは権限まわりです。/v1/chat/completions は認証なしで叩けた一方、omniroute costcompression previewAuthentication required. Set OMNIROUTE_API_KEY / Error: 401 で弾かれました。推論を通す口が開いていて、集計を見る口が閉じている、という組み合わせです。加えてサーバは(コンテナ内で)0.0.0.0にバインドしていたので、LAN上のマシンで素で起動する場合は、そのままだと同一ネットワークから推論を投げられる想定で置く必要があります。ログイン画面にも Default password: CHANGEME と表示が出ていたので、omniroute setup --password ... は起動直後にやるものだと考えたほうがよさそうです。

なお、目玉機能であるRTK / Caveman圧縮は今回検証できていません。compression statusstrategy: standard と出るところまで見えましたが、compression preview がAPIキー必須で、キーの発行までは今回やらなかったためです。「15〜95%削減」がどのくらい効くかは未確認、と正直に書いておきます。

試してみての所感

キーを1本も持たずにLLMの応答が返る、という体験そのものは素直におもしろかったです。手元でエージェント系のツールを試したいけれど課金アカウントを作るほどではない、という場面の入口としては、かなり実用的だと思います。

ただ私の目線での収穫は別のところにありました。200が4回返っていても、実際に成功していたのは1回だけという経路が、ログを開かないと一切見えなかったこと。そしてそれをちゃんと healed / 6 attempts として記録していたこと。フォールバックを持つ仕組みを評価するときは、可用性の数字ではなく、失敗の記録が残っているかを先に見る——という自分の基準を、久しぶりに具体的な形で確認できました。

自分としては、まずログのエクスポートとリクエスト履歴の粒度をもう少し詰めてから、圧縮の効き方を測ってみたいところです。そこまで測れて初めて、業務で使うかどうかの判断材料になると考えています。