🔖

Codex CLI用 自作プロキシ拡張編 〜ストリーミング対応とリトライ戦略〜

に公開

1. はじめに

前回の記事では、自作プロキシを用意して Codex CLI から複数のモデル(OpenAI, Ollama, gpt-oss, 社内モデルなど)を統合的に扱う方法を解説しました。
本記事ではその拡張編として、ストリーミング対応(stream: trueリトライ/エラーハンドリング戦略 を詳しく紹介します。これにより、より実用的で安定したプロキシを構築できます。


2. ストリーミング対応の必要性

なぜ必要か

  • Codex CLIはOpenAI互換APIを利用するため、stream: true を指定するとサーバーから部分的にレスポンスが返される
  • 大規模テキスト出力時に全体を待たずに結果を受け取れる
  • 対話的な利用体験を向上させる

実装のポイント

  • HTTPサーバーは「逐次レスポンス」を返せる仕組みが必要
  • Node.js では ReadableStream を活用して転送
  • OpenAI ↔ Ollama でレスポンス形式の差異があるため変換が必要になる場合あり

3. ストリーミング対応 実装例

routes.ts

import { Context } from 'hono'

export async function routeCompletionsStream(c: Context) {
  const body = await c.req.json()
  const model: string = body?.model || ''
  const isOllama = model.includes('gpt-oss')
  const upstream = isOllama ? process.env.OLLAMA_BASE_URL : process.env.OPENAI_BASE_URL

  const res = await fetch(`${upstream}/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...(isOllama ? {} : { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` })
    },
    body: JSON.stringify({ ...body, stream: true })
  })

  // ストリームをそのまま返す
  return new Response(res.body, {
    status: res.status,
    headers: { 'Content-Type': 'text/event-stream' }
  })
}

CLI側の使い方

codex --provider internal-proxy --stream "Generate a long article outline"

4. リトライ戦略の必要性

よくある失敗パターン

  • ネットワークタイムアウト
  • 上流API(OpenAI / Ollama)の一時的なエラー
  • レート制限(429エラー)

実装の考え方

  • 指数バックオフリトライ: 1秒 → 2秒 → 4秒 … のように待機時間を伸ばす
  • 最大試行回数を設定(例: 3〜5回)
  • エラー種別ごとの分岐: 認証エラー(401)はリトライ不要、429/500系はリトライ対象

5. リトライ実装例

async function fetchWithRetry(url: string, options: any, retries = 3, backoff = 1000): Promise<Response> {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url, options)
      if (res.ok) return res

      if (res.status === 401) throw new Error('Unauthorized - check API key')
      if (res.status === 429 || res.status >= 500) {
        console.warn(`Retrying after error ${res.status}...`)
      } else {
        throw new Error(`Unexpected status: ${res.status}`)
      }
    } catch (err) {
      console.error(`Attempt ${i + 1} failed:`, err)
      if (i === retries - 1) throw err
      await new Promise(r => setTimeout(r, backoff * (2 ** i)))
    }
  }
  throw new Error('All retries failed')
}

これを providers/openai.tsproviders/ollama.ts 内の fetch の代わりに利用すれば、リトライが適用されます。


6. 図解:拡張後のプロキシ構成


7. まとめ

本記事では、Codex CLI 用自作プロキシの拡張として以下を解説しました:

  • stream: true によるストリーミング対応
  • 指数バックオフを用いたリトライ戦略

これらを実装することで、より実用的かつ堅牢なプロキシが完成します。
次回は、監視とメトリクス収集(Prometheus/Grafana連携) をテーマに、運用フェーズでの安定性を高める方法を解説します。

関連記事一覧(シリーズ全体)

本シリーズは OpenAI Codex CLI を「任意のプロバイダー」で利用するために
自作プロキシを実装・拡張・運用していく流れを段階的に解説したものです。
最初はOllamaでのgpt-oss利用から始まり、最終的にはエンタープライズ運用を意識した
マルチテナント対応やアクセス制御までカバーしています。

Discussion