1. はじめに
前回の記事では、Codex CLIで任意のプロバイダーを利用する方法として、Ollama + gpt-ossを例に紹介しました。また、自作プロキシを介して複数のモデルを一元的に扱える仕組みも簡単に触れました。
本記事では、その「自作プロキシ」の詳細実装編として、具体的な構成・設計方針・実装例を解説します。
2. 自作プロキシを作る目的
- 複数モデルの統合管理: OpenAI, Ollama, gpt-oss, 社内モデルなどを一括で扱う
- 切替の自動化: 利用用途(精度優先・コスト優先)に応じて最適なモデルへ自動ルーティング
- セキュリティ: 認証・認可を自社環境で制御
- 監査性: ログ収集・利用状況の可視化
- 拡張性: 将来的に新しいモデルを追加してもCLI設定は変更不要
3. 設計方針
エンドポイント互換性
- Codex CLI は OpenAI API 互換の
/v1/chat/completions
を叩く前提 - プロキシはこのエンドポイントを受け、内部でOpenAIやOllamaに転送
ルーティングルール
-
ヘッダ指定 (
x-provider: ollama
/x-provider: openai
) -
model名判定 (
gpt-oss
を含むかどうか) - ビジネスロジック(利用時間帯、ユーザー権限、コスト制限など)
ミドルウェア層
- 認証(API Key/JWT)
- レート制御(Rate Limiter)
- ログ出力(利用履歴・監査用)
- エラーハンドリング(リトライ、タイムアウト)
4. プロジェクト構成例
codex-proxy/
├─ src/
│ ├─ server.ts # エントリポイント
│ ├─ routes.ts # ルーティング処理
│ ├─ middlewares.ts # 認証・ロギング・制御
│ └─ providers/
│ ├─ openai.ts # OpenAI API 呼び出し
│ ├─ ollama.ts # Ollama API 呼び出し
│ └─ custom.ts # 社内モデルAPI呼び出し
├─ package.json
├─ tsconfig.json
└─ .env
5. 詳細実装例(TypeScript)
server.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { routeCompletions } from './routes'
import { authMiddleware, logMiddleware } from './middlewares'
const app = new Hono()
app.use('*', cors())
app.use('*', logMiddleware)
app.use('*', authMiddleware)
app.post('/v1/chat/completions', routeCompletions)
const port = Number(process.env.PORT || 8787)
app.fire({ port })
console.log(`🚀 Proxy server running at http://localhost:${port}`)
routes.ts
import { Context } from 'hono'
import { callOpenAI } from './providers/openai'
import { callOllama } from './providers/ollama'
export async function routeCompletions(c: Context) {
const body = await c.req.json()
const provider = c.req.header('x-provider')
const model: string = body?.model || ''
if (provider === 'ollama' || model.includes('gpt-oss')) {
return await callOllama(c, body)
}
return await callOpenAI(c, body)
}
providers/openai.ts
export async function callOpenAI(c: any, body: any) {
const res = await fetch(`${process.env.OPENAI_BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify(body)
})
return c.body(await res.arrayBuffer(), res.status, Object.fromEntries(res.headers))
}
providers/ollama.ts
export async function callOllama(c: any, body: any) {
const res = await fetch(`${process.env.OLLAMA_BASE_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})
return c.body(await res.arrayBuffer(), res.status, Object.fromEntries(res.headers))
}
middlewares.ts
import { MiddlewareHandler } from 'hono'
export const logMiddleware: MiddlewareHandler = async (c, next) => {
console.log(`[REQ] ${c.req.method} ${c.req.url}`)
await next()
console.log(`[RES] ${c.res.status}`)
}
export const authMiddleware: MiddlewareHandler = async (c, next) => {
const token = c.req.header('authorization')
if (token !== `Bearer ${process.env.PROXY_API_KEY}`) {
return c.json({ error: 'Unauthorized' }, 401)
}
await next()
}
6. Codex CLI 側の設定例
{
"providers": [
{
"NAME": "internal-proxy",
"BASE_URL": "http://localhost:8787/v1",
"API_KEY": "your-proxy-key",
"MODEL": "gpt-oss"
}
]
}
CLIからの実行例:
codex --provider internal-proxy "Summarize project requirements."
7. 図解:全体構成イメージ
8. まとめ
このように、自作プロキシを用意することで Codex CLI から複数のモデルを統合的に利用でき、認証やルーティングも柔軟に制御できます。
次の記事では、ストリーミング対応(stream: true
)の実装方法や、エラーハンドリング/リトライ戦略をさらに詳しく解説します。