ストリーミング API レスポンスを SSE として中継する Python 実装例です。
このアプリは、ストリーミング LLM API レスポンスを中継する FastAPI ベースの
BFF です。クライアントは LLM API を直接呼び出さず、このアプリを呼び出します。
このアプリはサーバー側に保持した API key を使って、OpenAI 互換の
/responses endpoint へリクエストを転送します。
3つの proxy endpoint は同じ request body を受け取り、いずれも
text/event-stream を返します。違いは、上流 LLM API を呼び出す Python
client の実装です。
| 上流 client | Endpoint | 確認できること |
|---|---|---|
| OpenAI Python SDK | /openai-python/responses |
公式 SDK の stream event を SSE に戻す実装 |
| httpx | /httpx/responses |
汎用 async HTTP client で上流 SSE bytes をそのまま中継する実装 |
| aiohttp | /aiohttp/responses |
aiohttp の async client streaming API で上流 SSE bytes をそのまま中継する実装 |
LLM 接続設定はサーバー側の環境変数から読みます。ブラウザや request body から API key は受け取りません。
依存関係をインストールします。
uv syncOllama などのローカル OpenAI 互換 server を使う場合は、先に Ollama server と model を準備してから、このアプリを起動します。
ollama serve別の terminal で model を pull し、このアプリを起動します。
ollama pull llama3.2
LLM_BASE_URL=http://localhost:11434/v1/ \
LLM_API_KEY=ollama \
LLM_MODEL=llama3.2 \
uv run uvicorn sse_proxy_python_example.app:app --reloadOpenAI API を使う場合は、次のように起動します。
LLM_BASE_URL=https://api.openai.com/v1/ \
LLM_API_KEY="$OPENAI_API_KEY" \
LLM_MODEL=gpt-5-mini \
uv run uvicorn sse_proxy_python_example.app:app --reload別の terminal から SSE chunk が流れることを確認します。
curl -N http://127.0.0.1:8000/httpx/responses \
-H 'Content-Type: application/json' \
-d '{"input":"Write one short sentence about SSE."}'| 環境変数 | 既定値 | 説明 |
|---|---|---|
LLM_BASE_URL |
http://localhost:11434/v1/ |
OpenAI 互換 API の base URL |
LLM_API_KEY |
ollama |
上流 server へ送る API key |
LLM_MODEL |
llama3.2 |
/responses へ送る model 名 |
LLM_REQUEST_TIMEOUT |
120.0 |
上流 request timeout 秒数 |
LLM_PUBLIC_BASE_URL |
http://127.0.0.1:8000 |
起動ログに表示する公開 base URL |
uv sync
LLM_BASE_URL=http://localhost:11434/v1/ \
LLM_API_KEY=ollama \
LLM_MODEL=llama3.2 \
uv run uvicorn sse_proxy_python_example.app:app --reload既定では http://127.0.0.1:8000 で待ち受けます。
docker build -t sse-proxy-python-example .
docker run --rm -p 8000:8000 \
-e LLM_BASE_URL=http://host.docker.internal:11434/v1/ \
-e LLM_API_KEY=ollama \
-e LLM_MODEL=llama3.2 \
sse-proxy-python-exampleOpenAI API を使う場合は、API key を image に埋め込まず、環境変数として渡します。
docker run --rm -p 8000:8000 \
-e LLM_BASE_URL=https://api.openai.com/v1/ \
-e LLM_API_KEY="$OPENAI_API_KEY" \
-e LLM_MODEL=gpt-5-mini \
sse-proxy-python-example同じ payload を3つの endpoint に投げて、SSE の出力を比較できます。
curl -N を使うと chunk が到着したタイミングで逐次表示されます。
payload='{"input":"Write one short sentence about SSE."}'
curl -N http://127.0.0.1:8000/openai-python/responses \
-H 'Content-Type: application/json' \
-d "$payload"
curl -N http://127.0.0.1:8000/httpx/responses \
-H 'Content-Type: application/json' \
-d "$payload"
curl -N http://127.0.0.1:8000/aiohttp/responses \
-H 'Content-Type: application/json' \
-d "$payload"Ollama などのローカル server に向けて、同じ request body を各 endpoint へ 繰り返し送ると、end-to-end の所要時間を比較できます。OpenAI API に対して 大量に実行すると費用や rate limit の影響があるため、まずローカル環境で確認します。
順序による偏りを避けるため、3つの endpoint の実行順を入れ替えながら測ります。
/bin/bash <<'BASH'
payload='{"input":"Write one short sentence about SSE."}'
per_round=20
orders=(
"openai-python httpx aiohttp"
"openai-python aiohttp httpx"
"httpx openai-python aiohttp"
"httpx aiohttp openai-python"
"aiohttp openai-python httpx"
"aiohttp httpx openai-python"
)
for order in "${orders[@]}"; do
echo "== ${order} =="
for endpoint in $order; do
i=0
while [ "$i" -lt "$per_round" ]; do
curl -sS -o /dev/null "http://127.0.0.1:8000/$endpoint/responses" \
-w "${endpoint} %{time_total}\n" \
-H "Content-Type: application/json" \
-d "$payload" || exit 1
i=$((i + 1))
done
done
done
BASH結果を見るときは、平均値だけでなく失敗回数、最小値、最大値も確認します。 上流 model の速度、初回 load、CPU/GPU、同時実行数の影響を受けるため、 client 実装だけを比較したい場合は、実 LLM ではなく固定応答を返す OpenAI 互換 mock server を使ってください。
1万回など大きい回数で見る場合は、per_round を増やします。
per_round=16676通りの順序で実行するため、per_round=1667 では各 endpoint 約1万回になります。
2026-06-10 にローカル Ollama (llama3.2) へ warmup を各 endpoint 10回ずつ
投げたあと、各 endpoint 120回ずつ投げた結果は次のとおりです。
| Endpoint | 回数 | 失敗 | 平均秒 | 最小秒 | 最大秒 |
|---|---|---|---|---|---|
/openai-python/responses |
120 | 0 | 0.491 | 0.372 | 0.850 |
/aiohttp/responses |
120 | 0 | 0.491 | 0.304 | 0.797 |
/httpx/responses |
120 | 0 | 0.500 | 0.319 | 0.768 |
固定順で簡単に見るだけなら、次のようにも実行できます。
payload='{"input":"Write one short sentence about SSE."}'
count=100
for endpoint in openai-python httpx aiohttp; do
echo "== ${endpoint} =="
i=0
while [ "$i" -lt "$count" ]; do
curl -sS -o /dev/null "http://127.0.0.1:8000/$endpoint/responses" \
-H "Content-Type: application/json" \
-d "$payload" || exit 1
i=$((i + 1))
done
donecurl -N http://127.0.0.1:8000/httpx/responses \
-H 'Content-Type: application/json' \
-d '{
"input": "Return a one sentence answer.",
"schema_name": "answer",
"schema": {
"type": "object",
"properties": {
"answer": { "type": "string" }
},
"required": ["answer"],
"additionalProperties": false
}
}'OpenAI 互換 API として動く Ollama server を起動してから、このアプリを起動します。
1つ目の terminal で Ollama server を起動し、そのまま起動しておきます。
ollama serve2つ目の terminal で、使用する model を pull します。
ollama pull llama3.2同じ terminal で Python アプリを起動します。
LLM_BASE_URL=http://localhost:11434/v1/ \
LLM_API_KEY=ollama \
LLM_MODEL=llama3.2 \
uv run uvicorn sse_proxy_python_example.app:app --reload起動後、上記の curl -N コマンドを実行します。
LLM_BASE_URL=https://api.openai.com/v1/ \
LLM_API_KEY="$OPENAI_API_KEY" \
LLM_MODEL=gpt-5-mini \
uv run uvicorn sse_proxy_python_example.app:app --reload起動後、Request 例の3つの endpoint を同じ payload で確認します。
通常のテストでは実際の LLM API を呼びません。
uv run pytest
uv run ruff check .
uv run pyright
markdownlint-cli2 README.md task.md .markdownlint-cli2.jsoncLLM API へ到達するために社内プロキシを経由する必要があり、そのプロキシが 社内独自の root CA を使う場合は、この手順で起動します。上流 HTTP client は 標準の proxy 環境変数と Python SSL 環境変数を使います。
このアプリは TLS 証明書検証を有効にしたまま動作します。Python 3.13 では、
Missing Authority Key Identifier などのエラーで一部の社内プロキシ証明書が
拒否されることがあります。そのため、このアプリは Python 3.13 の厳格な
X.509 検証 flag だけを外し、証明書検証自体は維持します。
proxy の環境変数を設定します。
export HTTPS_PROXY=http://proxy.example.com:8080
export HTTP_PROXY=http://proxy.example.com:8080
export NO_PROXY=localhost,127.0.0.1社内 root CA を PEM file として用意し、Python/httpx が読む SSL_CERT_FILE
に指定します。
SSL_CERT_FILE=/path/to/corporate-ca.pem \
LLM_BASE_URL=https://api.openai.com/v1/ \
LLM_API_KEY="$OPENAI_API_KEY" \
LLM_MODEL=gpt-5-mini \
uv run uvicorn sse_proxy_python_example.app:app --reload証明書が hashed certificate directory として提供される環境では、
SSL_CERT_DIR を使います。
Docker では CA file を container に mount し、同じ環境変数を渡します。
docker run --rm -p 8000:8000 \
-e LLM_BASE_URL=https://api.openai.com/v1/ \
-e LLM_API_KEY="$OPENAI_API_KEY" \
-e LLM_MODEL=gpt-5-mini \
-e SSL_CERT_FILE=/etc/ssl/certs/corporate-ca.pem \
-e HTTPS_PROXY \
-e HTTP_PROXY \
-e NO_PROXY \
-v /path/to/corporate-ca.pem:/etc/ssl/certs/corporate-ca.pem:ro \
sse-proxy-python-example起動ログには SSL_CERT_FILE のパスが表示されます。event: error が返る場合は
server log を確認してください。上流接続の例外は server log に出力し、client
向けの SSE error body には API key や proxy 認証情報を出しません。