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?

curl だけで OpenAI 互換ルーターの疎通確認をする

0
Posted at

はじめに

LLM API の接続で詰まったとき、私はつい SDK の書き方から疑いがちです。base_url の渡し方、client の初期化、環境変数の読み込み順など、見る場所が一気に増えます。

ただ、OpenAI 互換のルーターを使っているなら、SDK を見る前に HTTP レイヤーだけでかなり切り分けられます。今回は Flatkey AI の OpenAI 互換エンドポイントを例に、curl だけで 200 OK まで確認した手順をメモします。

この記事でやること

  • API key と base URL が届いているかを見る
  • model が今の key で使えるかを見る
  • stream なしの Chat Completions で最小応答を見る
  • stream ありの SSE 応答を curl で見る
  • SDK に進む前に、どこで詰まっているかを分ける

なぜ curl から始めるか

SDK は便利ですが、失敗したときに見る層が多いです。依存ライブラリの version、client の生成方法、環境変数の読み込み、retry 設定、timeout 設定が同時に関係します。

curl なら、まず HTTP request と response だけに寄せられます。header が足りないのか、JSON が壊れているのか、model 名が違うのか、stream の読み方だけが違うのかを、かなり素朴に分けられます。私はここを飛ばすと、実装ではなく前提条件の方で時間を溶かしがちです。

環境

今回確認した前提は次です。

項目
shell zsh
tool curl
base URL https://router.flatkey.ai/v1
endpoint /models, /chat/completions
model gemini-2.5-flash-lite

base URL と model は、自分の環境に合わせて置き換えてください。Flatkey AI の場合も、画面やドキュメントで表示されている最新の base URL を見るのが安全だと思います。

なお、今回はあえて Python や Node.js の SDK は使いません。SDK の問題を否定したいのではなく、まず router が素の HTTP request にどう返すかを見たいからです。この段階で失敗するなら、アプリコードに入る前に直すべき前提があります。この段階で成功するなら、次は SDK の初期化や環境変数の渡し方を見ればよい、という分け方です。

まず変数を置く

直書きすると履歴に残るので、私は最低限これだけ環境変数に逃がしています。

export FLATKEY_API_KEY="<your_api_key>"
export BASE_URL="https://router.flatkey.ai/v1"
export MODEL="gemini-2.5-flash-lite"

以降の curl は、この 3 つが入っている前提です。記事用には placeholder を書いていますが、実行時は本物の key を入れます。

/v1/models で key と base URL を見る

最初に Chat Completions を叩くより、/models を見る方が切り分けやすいです。ここで 200 が返るなら、少なくとも key と base URL の組み合わせはサーバーまで届いています。

curl -sS "$BASE_URL/models" \
  -H "Authorization: Bearer $FLATKEY_API_KEY"

今回の環境では HTTP 200 が返り、モデル一覧の中に使いたい model がありました。抜粋するとこうです。

{
  "id": "gemini-2.5-flash-lite",
  "object": "model",
  "owned_by": "google gemini"
}

この段階で model が見つからない場合、私は Chat Completions の payload をいじる前に、まず model 名を疑います。公開カタログにある名前と、今の API key で使える名前が同じとは限らないためです。

もう一つ見ておきたいのは、/models が返した一覧をその日のログとして残すことです。昨日使えた model が今日も同じ route で使えるとは限らないので、後から見返せる証拠があると会話が早くなります。

stream なしで /v1/chat/completions を見る

次に、stream なしで一番小さい会話を投げます。ここでは返答を固定しやすいように、短い文だけ返してもらいます。

curl -sS "$BASE_URL/chat/completions" \
  -H "Authorization: Bearer $FLATKEY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$MODEL"'",
    "messages": [
      { "role": "user", "content": "Reply with only: flatkey-curl-ok" }
    ],
    "temperature": 0,
    "max_tokens": 16
  }'

私の環境では HTTP 200 で、本文の抜粋はこうでした。

{
  "model": "gemini-2.5-flash-lite",
  "choices": [
    {
      "message": {
        "content": "flatkey-curl-ok"
      }
    }
  ],
  "usage": {
    "total_tokens": 17
  }
}

ここまで通れば、少なくとも次の 4 点は確認できています。

  • Authorization header がサーバーに届いている
  • JSON body と Content-Type が最低限正しい
  • model がこの endpoint で使えている
  • 非 stream の response body を受け取れている

SDK 側の client 初期化を見るのは、この後でよいと思います。

-w で HTTP ステータスを分けて見る

curl の出力だけを眺めていると、JSON body と HTTP status が混ざって見づらいです。私は一度ファイルに落として、status を別に出すことが多いです。

curl -sS -o /tmp/flatkey-chat.json -w '%{http_code}\n' \
  "$BASE_URL/chat/completions" \
  -H "Authorization: Bearer $FLATKEY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$MODEL"'",
    "messages": [
      { "role": "user", "content": "Reply with only: flatkey-curl-ok" }
    ],
    "temperature": 0,
    "max_tokens": 16
  }'

これで標準出力には 200 だけが出ます。body は /tmp/flatkey-chat.json に残るので、あとで jq などで見ればよいです。

jq '.choices[0].message.content, .usage.total_tokens' /tmp/flatkey-chat.json

この形にしておくと、CI やサポート用の一時確認でも扱いやすいです。雑に全部貼るより、status と body を分けた方が人間にもログにも優しい気がします。

stream ありは -N で見る

stream の確認では stream: true を付け、curl には -N を付けます。-N は buffering を抑えて、SSE の data: 行をその場で見たいときに使っています。

curl -sS -N "$BASE_URL/chat/completions" \
  -H "Authorization: Bearer $FLATKEY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$MODEL"'",
    "messages": [
      { "role": "user", "content": "Reply with only: stream-ok" }
    ],
    "temperature": 0,
    "max_tokens": 16,
    "stream": true
  }'

今回の環境では、だいたい次のような SSE が返りました。

data: {"choices":[{"delta":{"content":"","role":"assistant"},"finish_reason":null,"index":0}],"usage":null}
data: {"choices":[{"delta":{"content":"stream-ok"},"finish_reason":null,"index":0}],"usage":null}
data: {"choices":[{"delta":{},"finish_reason":"stop","index":0}],"usage":null}
data: {"choices":[],"usage":{"total_tokens":11}}
data: [DONE]

ここで見たいのは、きれいな文章ではなく、data: が分割して流れてくることと、最後に [DONE] まで届くことです。stream なしは通るのに stream ありだけ詰まる場合は、SDK より先に proxy、timeout、buffering、SSE の読み取り側を疑う余地があります。

どこで詰まったかを分ける

私の中では、curl の確認はだいたいこの順番です。

見る場所 curl で確認すること 次に疑うもの
key /models が 200 になるか key の値、環境変数、Bearer の付け忘れ
base URL /v1/models に到達するか host、path、末尾の /v1
model 一覧に model があるか model 名、権限、利用可能な route
body 非 stream chat が 200 になるか messages、JSON、Content-Type
stream data:[DONE] が見えるか -N、proxy buffering、timeout

この表の上から順に潰すと、SDK の issue なのか、HTTP の issue なのかを分けやすくなります。逆に /models が通っていない状態で SDK のオプションを変え続けると、私はかなり遠回りしがちです。

実行ログとして残すもの

あとで誰かに相談する可能性があるなら、curl の結果はその場で少し整えて残しておくと助かります。私なら、時刻、base URL、endpoint、model、HTTP status、response body の抜粋、stream の場合は [DONE] まで届いたかを残します。

逆に、API key、社内の完全な request ID、顧客データを含む prompt は残さないか、必ず伏せます。疎通確認の記事や issue に貼るなら、contentflatkey-curl-ok のような固定文字列にしておく方が安全です。再現に必要な情報と、公開すると困る情報を分けるだけでも、デバッグの心理的な負担が下がると思います。

まとめ

OpenAI 互換ルーターの疎通確認は、いきなりアプリに組み込むより、curl だけで段階を分ける方が楽でした。

  • まず /models で key と base URL を見る
  • 次に stream なしの /chat/completions で 200 と本文を見る
  • HTTP status と body は分けて保存すると調査しやすい
  • stream ありは curl -Ndata:[DONE] を見る
  • ここまで通ってから SDK の設定を見る

この順番にしておくと、作業メモもそのまま再現手順として渡しやすいですし、少しだけ安心です。

Flatkey AI は OpenAI 互換の base URL と Bearer key で呼べるので、この手順をそのまま小さい疎通確認に使えました。ただ、どのサービスでも base URL と model 名は手元の画面で確認するのが一番確実だと思います。

おわりに

SDK の設定で詰まったと思っていたものが、実は model 名や base URL の取り違えだった、ということは普通にあります。私も何度かやっています。

curl だけで 200 OK を見てから SDK に戻ると、疑う範囲がかなり狭くなります。間違いあったらコメントください。よろしくお願いします。

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?