Skip to content

nncdevel-io/sse-proxy-java-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SSE Proxy Java Example

ストリーミング LLM API レスポンスを Server-Sent Events (SSE) として中継する Java 実装例です。

概要

このアプリは、ストリーミング LLM レスポンスを中継する Spring Boot ベースの BFF です。クライアントは LLM API を直接呼び出さず、このアプリを呼び出します。 このアプリはサーバー側に保持した API key を使って、OpenAI 互換の /responses endpoint へリクエストを転送します。

3つの proxy endpoint は同じ request body を受け取り、いずれも text/event-stream を返します。違いは、上流 LLM API を呼び出す Java client の実装です。

上流 client Endpoint 確認できること
OpenAI Java SDK /openai-java/responses 公式 SDK の stream event を SSE に戻す実装
JDK HttpClient /httpclient/responses JDK HTTP client で上流 SSE bytes をそのまま中継する実装
Spring RestClient /restclient/responses Spring の blocking HTTP client で上流 SSE bytes をそのまま中継する実装

LLM 接続設定はサーバー側の環境変数から読みます。ブラウザや request body から API key は受け取りません。

はじめかた

Ollama などのローカル OpenAI 互換 server を使う場合は、次のように起動します。

LLM_BASE_URL=http://localhost:11434/v1 \
LLM_API_KEY=ollama \
LLM_MODEL=llama3.2 \
mvn spring-boot:run

OpenAI API を使う場合は、API key を環境変数として渡します。

LLM_BASE_URL=https://api.openai.com/v1 \
LLM_API_KEY="$OPENAI_API_KEY" \
LLM_MODEL=gpt-5-mini \
mvn spring-boot:run

別の terminal から SSE chunk が流れることを確認します。

curl -N http://localhost:8080/httpclient/responses \
  -H 'Content-Type: application/json' \
  -d '{"input":"Write one short sentence about SSE."}'

要件

  • Java 21 以降
  • Maven 3.9 以降
  • 任意: Docker
  • 任意: Ollama などの OpenAI 互換 Responses API server

設定

環境変数 既定値 説明
LLM_BASE_URL http://localhost:11434/v1 OpenAI 互換 API の base URL
LLM_API_KEY ollama 上流 server へ送る API key
LLM_MODEL llama3.2 request が model を省略したときに /responses へ送る model 名

Spring Boot virtual threads は src/main/resources/application.yml で有効化しています。

ローカル起動

LLM_BASE_URL=http://localhost:11434/v1 \
LLM_API_KEY=ollama \
LLM_MODEL=llama3.2 \
mvn spring-boot:run

既定では http://localhost:8080 で待ち受けます。

Docker 起動

mvn package
docker build -t sse-proxy-java-example .
docker run --rm -p 8080:8080 \
  -e LLM_BASE_URL=http://host.docker.internal:11434/v1 \
  -e LLM_API_KEY=ollama \
  -e LLM_MODEL=llama3.2 \
  sse-proxy-java-example

OpenAI API を使う場合は、API key を image に埋め込まず、環境変数として渡します。

docker run --rm -p 8080:8080 \
  -e LLM_BASE_URL=https://api.openai.com/v1 \
  -e LLM_API_KEY="$OPENAI_API_KEY" \
  -e LLM_MODEL=gpt-5-mini \
  sse-proxy-java-example

Request 例

同じ payload を3つの endpoint に投げて、SSE の出力を比較できます。 curl -N を使うと chunk が到着したタイミングで逐次表示されます。

curl -N http://localhost:8080/openai-java/responses \
  -H 'Content-Type: application/json' \
  -d '{"input":"Write one short sentence about SSE."}'

curl -N http://localhost:8080/httpclient/responses \
  -H 'Content-Type: application/json' \
  -d '{"input":"Write one short sentence about SSE."}'

curl -N http://localhost:8080/restclient/responses \
  -H 'Content-Type: application/json' \
  -d '{"input":"Write one short sentence about SSE."}'

HttpClientRestClient の endpoint は、上流 event を受け取った形のまま 中継します。openai-java endpoint は typed SDK stream event を受け取り、 SSE の data: event として返します。

性能比較

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-java httpclient restclient"
  "openai-java restclient httpclient"
  "httpclient openai-java restclient"
  "httpclient restclient openai-java"
  "restclient openai-java httpclient"
  "restclient httpclient openai-java"
)

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://localhost:8080/$endpoint/responses" \
        -w "${endpoint} %{time_total}\n" \
        -H "Content-Type: application/json" \
        -d "$payload" || exit 1
      i=$((i + 1))
    done
  done
done
BASH

2026-06-10 にローカル Ollama (llama3.2) へ各 endpoint 120回ずつ投げた結果は 次のとおりです。

Endpoint 回数 失敗 平均秒 最小秒 最大秒
/restclient/responses 120 0 0.465 0.296 0.734
/httpclient/responses 120 0 0.494 0.334 1.868
/openai-java/responses 120 0 0.930 0.313 24.083

この実測では RestClient と JDK HttpClient はほぼ同等で、OpenAI Java SDK 経由は 外れ値の影響で平均が大きくなりました。結果は上流 model の速度、初回 load、 CPU/GPU、同時実行数の影響を受けます。client 実装だけを比較したい場合は、実 LLM ではなく固定応答を返す OpenAI 互換 mock server を使ってください。

1万回など大きい回数で見る場合は、per_round を増やします。

per_round=1667

6通りの順序で実行するため、per_round=1667 では各 endpoint 約1万回になります。

固定順で簡単に見るだけなら、次のようにも実行できます。

payload='{"input":"Write one short sentence about SSE."}'
count=100

for endpoint in openai-java httpclient restclient; do
  echo "== ${endpoint} =="
  i=0
  while [ "$i" -lt "$count" ]; do
    curl -sS -o /dev/null "http://localhost:8080/$endpoint/responses" \
      -H "Content-Type: application/json" \
      -d "$payload" || exit 1
    i=$((i + 1))
  done
done

Structured Outputs 例

text object を渡すと、Responses API の Structured Outputs request を転送できます。

curl -N http://localhost:8080/restclient/responses \
  -H 'Content-Type: application/json' \
  -d '{
    "input": "Return a greeting.",
    "text": {
      "format": {
        "type": "json_schema",
        "name": "greeting",
        "schema": {
          "type": "object",
          "properties": {
            "message": { "type": "string" }
          },
          "required": ["message"],
          "additionalProperties": false
        },
        "strict": true
      }
    }
  }'

Ollama 確認

OpenAI 互換 API として動く Ollama server を起動してから、このアプリを起動します。

1つ目の terminal で Ollama server を起動し、そのまま起動しておきます。

ollama serve

2つ目の terminal で、使用する model を pull します。

ollama pull llama3.2

同じ terminal で Java アプリを起動します。

LLM_BASE_URL=http://localhost:11434/v1 \
LLM_API_KEY=ollama \
LLM_MODEL=llama3.2 \
mvn spring-boot:run

起動後、上記の curl -N コマンドを実行します。

OpenAI API 確認

LLM_BASE_URL=https://api.openai.com/v1 \
LLM_API_KEY="$OPENAI_API_KEY" \
LLM_MODEL=gpt-5-mini \
mvn spring-boot:run

起動後、Request 例の3つの endpoint を同じ request body で確認します。

About

Java example of an SSE proxy that relays OpenAI streaming responses to clients.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors