0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-09-18

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連携) をテーマに、運用フェーズでの安定性を高める方法を解説します。

関連記事一覧

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?