Open-source release of MAESTRO, an agent orchestration platform that runs LLM-driven tasks through sandboxed tools, with a web UI. Apache-2.0. See README.md and docs/ (getting-started, configuration, architecture).
13 KiB
AAO Gateway モード — 機能概要 (2026-05-20 時点)
このドキュメントは「LiteLLM Proxy 代替として AAO に追加された Gateway 機能」が今回の一連の作業でどう変わったかを日本語で解説します。技術詳細の設計 doc は末尾の関連ドキュメントセクションを参照。
TL;DR (3 行)
- AAO 自身が OpenAI 互換 LLM Gateway として動けるようになりました。 他の AAO や任意の OpenAI クライアントから
/v1/chat/completionsを叩けます。 - 設定 UI のトグル 1 つで on/off (同一プロセス内で起動・停止)。別プロセスで動かす運用も従来通り可能 (advanced)。
- 仮想 API キーごとに 月次 token 予算 + RPM レート制限、Prometheus でリアルタイム監視。Telemetry 外部送信ゼロ。
なぜこれを作ったか
LiteLLM Proxy には次の懸念がありました:
- Telemetry: デフォルトで匿名利用データが外部送信される (opt-out 可だが要設定)。
- License 変更リスク: MIT → BSL/SSPL への変更前例があり、組織で長期運用するには不確実性が高い。
- 追加依存: Python サービス + Redis (任意) + DB を、AAO とは別に運用する必要がある。
これらを 「AAO 単一バイナリ + 既存 SQLite + telemetry 完全ゼロ」 で代替するのが本機能の目的です。LiteLLM 固有の高度機能 (sticky routing / canary / cost USD / OpenTelemetry / Slack アラート / multi-region federation 等) は 意図的にスコープ外にして、シンプルな in-house ゲートウェイに振り切りました。
どの機能が、どのタイミングで入ったか
時系列に沿って、追加された機能とユーザーへの影響を解説します。
Phase 1: Gateway モードの土台 (PR #326)
| 項目 | 内容 |
|---|---|
| 起動方法 | AAO_MODE=gateway を環境変数指定すると、worker / scheduler / UI を起動せず、軽量に gateway サーバだけ立つ |
| エンドポイント | /v1/chat/completions (SSE ストリーミング) + /v1/models + /health (LiteLLM 互換 JSON) |
| 認証 | Bearer Token (config.yaml に static な virtual keys) |
| ルーティング | 複数 backend (llama-server / vLLM など) を least-busy (進行中リクエスト数が一番少ない backend) で振り分け |
| 安全停止 | SIGTERM 時に進行中 SSE を shutdown_graceful_sec (デフォルト 30s) 待って drain、gateway_shutdown SSE event でクライアントに retry を促す |
Phase 2a: 仮想キーを DB で管理 + Admin REST API (PR #330)
- API キー形式:
sk-aao-<base62-32>(32 文字、23 bytes エントロピー、SHA-256 hash で DB 保存) - raw key は発行直後の 1 回だけ表示、以降は prefix しか見えない (LiteLLM 同様の安全設計)
- Admin REST API で発行 / 一覧 / rotate / soft delete (revoked_at)
- config.yaml の static key は起動時に自動で DB に移行
teamフィールドでテナント分離
Phase 2b: 予算とレート制限 + UI 一式 (PR #332)
| 機能 | 実装 |
|---|---|
| 月次 token 予算 | キーごとに tokens_budget。UTC 月初に counter リセット。超過した次のリクエストで 429 を返す (post-hoc enforcement) |
| RPM レート制限 | キーごとに rate_limit_rpm。sliding 60s window、in-memory カウンタ + 30s 周期で DB flush |
| 使用量集計 | gateway_key_usage テーブルに (key_id, period_start) 単位で tokens_in / tokens_out / requests を記録 |
| UI | Settings → Tools → "Gateway Keys" タブ。発行・無効化・rotate・月次グラフ |
Phase 3a: Polish bundle (PR #333)
Phase 1-2b で残った INVESTIGATE 8 件をまとめて解消:
- Orphan key 検出時の警告ログ
Authorization: Bearer ...の regex を RFC 6750 厳密に- 5 秒 LRU の key auth cache (DB 負荷削減)
- config 由来 key の drift resync (config.yaml を書き換えた時の DB 整合)
- PATCH で revoked key を編集しようとしたら 409 conflict
- Dead field cleanup
- MAX_TRACKED_KEYS LRU eviction (rate limiter)
- SSE error の sentinel 化:
gateway_shutdown/gateway_timeout/budget_exhausted/rate_limitedの 4 種をクライアントが識別可能に
Phase 3b: Prometheus exporter (PR #335)
- Gateway 11 metrics: requests_total / tokens_total / backend_busy_slots / key_cache_size / latency histogram など
- Worker 6 metrics: jobs_total / piece_runs / queue depth など
- 全 metric に per-team label を載せて、テナント単位の利用量を分離可視化
/metricsは デフォルトで localhost 限定 (127.0.0.1/::1allowlist)、外部 Prometheus 用に bearer token opt-in- cardinality 暴走を防ぐため、label を 1 桁オーダーに抑える discipline
cleanup (PR #337)
- AAO の LLM クライアント (
openai-compat.ts) で gateway sentinel SSE error を parse
ops (PR #340)
scripts/gateway.sh start | stop | status | logs(.gateway.pid管理、logs/gateway.log)AAO_CONFIGenv で config.yaml path を override 可能 (両モード共通)GATEWAY_PORTenv で listen port を override 可能- DB 共有設計 (worker と gateway は同一 SQLite を WAL で共有可能) を doc に明記
Phase 3c: UI 制御 + 同プロセス default ← 本セッションで完了 (PR #341)
ここが今回の最大の変化です。これまで「gateway 専用プロセスを別 port で起動する」モデルだったのを抜本的に変更:
| Before (Phase 1-3b) | After (Phase 3c) |
|---|---|
AAO_MODE=gateway で gateway 専用プロセスを別 port (例: 9877) に起動 |
通常の worker AAO の 同一プロセス・同一 port で動作 |
| Worker UI と gateway を物理的に分離 | UI のトグル 1 つで gateway を mount / unmount |
| 各 AAO に gateway 用の追加デプロイが必要 | 既存 AAO を起動して UI から有効化するだけ |
新しい設定 UI (Settings → Tools → "Gateway Server")
- Enable Gateway トグル: チェックで gateway 起動、外して停止
- Backends list (フォーム編集):
- Endpoint URL (http/https)
- Backend ID (任意の文字列、ルーティング識別子)
- Max slots (並列処理上限)
- API key (backend 側に必要な場合)
- 編集 + Save で hot reload: 進行中 SSE は
gateway_shutdownevent で drain、新接続から新 config で動作 - リアルタイム状態 badge:
running/disabled/misconfiguredを 3 秒周期で表示
動作モード
- Gateway 有効時:
/v1/*paths が gateway sub-app にルーティングされ、/healthは LiteLLM 互換 JSON を返す - Gateway 無効時:
/v1/*は 404、/healthは bridge の{status:'ok'}(Docker healthcheck 等の既存利用に影響なし)
Phase 3c で重要だった 8 件のバグ修正
レビューで critical な問題が見つかり、すべて修正済み:
- prom-client メトリクス重複登録クラッシュ: gateway 設定変更 (= bounce) 1 回ごとに
Counterを新規登録 → 2 回目で throw して bridge プロセスごと死ぬ問題。WeakMap<Registry, Map<prefix, GatewayMetrics>>で memoize して解決 - BackendStatusRegistry が worker 用の list を見ていた: 同プロセス mode で gateway も worker と同じ registry を共有していたため、gateway 専用の backend が status 不明 → routing blind /
/health空 / metrics 0。Gateway は自前 registry をgateway.backends[]上に build する設計に /healthLiteLLM 互換が壊れていた: bridge の既存/health({status:'ok'}) が mountGateway より先に登録されていて、LiteLLM 互換 JSON が返らない問題。classifyGatewayPathを 3 値 (gateway-only/gateway-when-enabled/false) に拡張し、Express middleware の登録順序を修正- transition 中の config が dropped される:
state === 'starting' | 'stopping'中に新規 config 適用が落ちる問題。pendingConfigqueue + mutex chain replay で対応 (実用上は mutex serialize で到達不能だが、防御として実装) - stop() が state を misconfigured のまま残す: 公開 stop API が state を
disabledにリセットしない問題 - configsEquivalent の比較が不安定:
JSON.stringifyの key 順依存で偽 bounce が起きる問題。stable stringify に - listenPort が env 依存: 実 listen port と表示 port が乖離する可能性。
CoreServerOptions.listenPortで配線 api_key入力時に${VAR}env 参照が黙って literal 保存される: amber warning text を form に表示
これらは round-1 / round-2 の adversarial review で発見・修正。テストでは隠蔽されやすいバグ (例: 1 番は beforeEach で fresh Registry を使うと検出できない) を含むため、shared-registry pattern の test を追加で書いて検証しています。
どう使うか (運用ガイド)
パターン A: 単一 AAO で gateway を有効化 (Phase 3c 以降の推奨)
1. scripts/server.sh start で AAO を通常起動
2. UI に admin としてログイン
3. Settings → Tools → "Gateway Server" を開く
4. "Enable Gateway" にチェック → Backends list を入力 → Save
5. Settings → Tools → "Gateway Keys" で API キーを発行 (raw key は発行直後のみ表示)
6. 他の AAO や OpenAI クライアントから:
base_url: http://this-aao:9876/v1
api_key: sk-aao-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
パターン B: Gateway 専用サーバとしてデプロイ (advanced)
GPU サーバが多い、または worker UI を運用したくない場合:
AAO_MODE=gateway scripts/gateway.sh start
# AAO_CONFIG=/etc/aao/gateway.yaml で別 config を指定可能
# worker mode と同じ DB を共有可能 (キー管理が両モードで一貫)
パターン C: 既存 LiteLLM Proxy からの乗り換え
互換性のポイント:
- Endpoint path:
/v1/chat/completions・/v1/models・/health同じ - Response header
x-litellm-model-idをそのまま発行 (vendor-neutral なx-aao-backend-idも同値で同時発行) - Key format:
sk-aao-*(LiteLLM のsk-*から prefix を変更、長さ・エントロピー同等) /healthの JSON shape:{healthy_endpoints, unhealthy_endpoints, healthy_count, unhealthy_count}で LiteLLM 互換
クライアント側の parseLiteLLMHealth 等の既存コードは無修正で動作します。
監視
/metrics で Prometheus 形式メトリクスを取得:
# Gateway 系
aao_gateway_requests_total{backend="llm-a",team="ops",status="ok"} 142
aao_gateway_tokens_total{backend="llm-a",team="ops",direction="out"} 95821
aao_gateway_backend_busy_slots{backend="llm-a"} 2
# Worker 系 (same-process mode 時に同じ endpoint から)
aao_worker_jobs_total{piece="chat",status="succeeded"} 38
外部 Prometheus からスクレイプする場合は config で provider.metrics.bearer_token を設定し、IP allowlist を緩和。
意図的にやっていない機能 (実需確認まで保留)
| 機能 | 保留理由 |
|---|---|
| Sticky routing (cache hit 最適化) | 1 backend 構成 + cache hit 率 実測無しでは ROI 不明 |
| Canary routing (model rollout) | 現状 model A→B 切り替え予定なし |
| Token → USD コスト換算 | tokens 数で十分か admin の要望次第 |
| Audit log テーブル | compliance 要件無し時点では不要 |
| Pre-reserve budget (並列 burst) | 実際に N×max_tokens overshoot が観測されたら検討 |
| OpenTelemetry trace | 単一 org deploy では ROI 低い |
| Slack / Email アラート | Prometheus Alertmanager で代用想定 |
| Multi-AAO federation | Prometheus federation で代用想定 |
次のステップ
- Backend ルーティングの動作 (least-busy の精度)
- SSE drain の挙動 (大量同時接続 + graceful shutdown)
- Budget / RPM の境界値 (オフバイワン無いか)
- Prometheus metrics の cardinality / scrape duration
- UI hot reload の race condition
- LiteLLM 互換性 (既存 client コードが無修正で動くか)
を検証。dogfooding で観測した問題から Phase 4 のスコープを決めます。
関連ドキュメント
- In-product help:
ui/src/content/help/11-llm-gateway.md - INVESTIGATE backlog (open follow-up issues): Gitea issue #338
マージ済み PR 一覧
| Phase | PR | merge commit | 日付 |
|---|---|---|---|
| 1 | #326 | 178baa9 |
2026-05-18 |
| 2a | #330 | 78a796d |
2026-05-18 |
| 2b | #332 | b0569c7 |
2026-05-19 |
| 3a | #333 | 30e9d78 |
2026-05-19 |
| 3b | #335 | 9efc60f |
2026-05-19 |
| cleanup | #337 | bcfdd41 |
2026-05-20 |
| ops | #340 | 13bd3cd |
2026-05-20 |
| 3c | #341 | 561ff24 |
2026-05-20 (本セッション) |
累計コード追加: 約 12,500 行 (テスト含む)、テスト件数 2720 (ベースライン 2707 から +13)。