OpenAI Chat Completions API は、会話型言語モデル向けに広く実装されている JSON over HTTP インターフェースです。クライアントはメッセージと生成オプションの順序付きリストを送信し、サーバーはメタデータとトークン使用量を含む 1 つ以上のアシスタントメッセージを返します。
基本的な構造は次のとおりです。
HTTP client
-> POST /v1/chat/completions
-> request JSON containing messages and options
-> model inference
<- response JSON containing choices and usage多くのゲートウェイやモデルサーバーは、基盤モデルが OpenAI でホストされていない場合でも、OpenAI 互換エンドポイントを実装しています。そのため、この形式は事実上の相互運用 API として有用ですが、HTTP、JSON-RPC、MCP のような独立した正式標準ではありません。互換プロバイダーによって、サポートするフィールド、モデル、制限、エラー形式、拡張機能が異なる場合があります。
Spring AI は、このワイヤー形式の上に、Java アプリケーション向けのプロバイダー中立なモデルを提供します。アプリケーションコードは Prompt、Message、ChatOptions、ChatResponse を扱い、OpenAI または OpenAI 互換アダプターがこれらのオブジェクトとプロバイダーの JSON の相互変換を行います。
HTTP リクエスト #
簡略化したリクエストは次のとおりです。
POST /v1/chat/completions HTTP/1.1
Host: api.example.com
Authorization: Bearer ...
Content-Type: application/json
{
"model": "gpt-4.1",
"messages": [
{
"role": "system",
"content": "Answer concisely."
},
{
"role": "user",
"content": "Explain optimistic locking."
}
],
"temperature": 0.3,
"max_tokens": 1000,
"stream": false
}model と messages は、実行対象と、そのモデルが参照する会話コンテキストを識別します。残りのフィールドは生成を制御します。一部のゲートウェイでは、リクエスト本文で model を要求する代わりに、設定、デプロイメント名、URL、またはヘッダーによってモデルを選択します。
メッセージ #
messages は順序付きです。その順序自体がプロンプトの一部になります。
[
{
"role": "system",
"content": "You are a support assistant."
},
{
"role": "user",
"content": "Where is order 42?"
},
{
"role": "assistant",
"content": "I need to look up that order."
},
{
"role": "user",
"content": "It belongs to account 7."
}
]一般的なロールは次のとおりです。
| Role | Meaning |
|---|---|
system | アプリケーションの指示と動作 |
developer | 新しいモデル API がサポートする開発者向け指示 |
user | ユーザーが提供した入力 |
assistant | 以前に生成された、または新たに生成されたモデル出力 |
tool | アプリケーションによるツール呼び出しの結果 |
テキストは通常、文字列として表現されます。マルチモーダルをサポートするモデルでは、テキスト、画像、音声、その他のメディアを含む型付きコンテンツパートの配列を受け付ける場合があります。
エンドポイントは通常ステートレスです。呼び出し元が会話 ID を再利用しただけでは、モデルは以前の HTTP リクエストを記憶しません。会話を継続するには、クライアントが選択した過去のメッセージを再度送信する必要があります。
load conversation history
-> append current user message
-> send all selected messages
-> receive assistant message
-> save new user and assistant messagesモデルのコンテキスト制限内に収めるため、履歴は切り詰めたり、要約したり、外部ストレージから取得したりできます。
生成オプション #
頻繁に実装されるリクエストフィールドには次のものがあります。
| Field | Purpose |
|---|---|
model | モデルまたはデプロイメント識別子 |
temperature | サンプリングのランダム性を制御 |
top_p | nucleus sampling のしきい値 |
max_tokens | 旧式または互換 API で生成トークン数を制限 |
max_completion_tokens | 一部のモデルで使用される新しい完了トークン数の上限 |
n | 候補選択肢の数 |
frequency_penalty | 頻繁に使用されたトークンの繰り返しを抑制 |
presence_penalty | まだ使用されていないトークンの導入を促進 |
stop | 設定されたシーケンスで生成を停止 |
stream | 完全なレスポンスを 1 つ返すか、チャンクをストリームするかを選択 |
response_format | サポートされる場合に JSON またはスキーマ制約付き出力を要求 |
tools | モデルが要求できる関数を記述 |
tool_choice | 選択可能なツール、またはツールを選択するかどうかを制約 |
プロバイダーのサポートと意味は異なります。ある互換エンドポイントが受け付けるフィールドが、別のエンドポイントでは拒否、無視、改名されたり、異なる意味で解釈されたりする場合があります。同じエンドポイントの背後にあるモデルのバージョンによっても異なる可能性があります。
サンプリングオプションも相互作用します。アプリケーションでは、評価なしに temperature と top_p の両方を変更するのではなく、通常はいずれか一方を調整します。
HTTP レスポンス #
ストリーミングしないレスポンスを簡略化すると、次のようになります。
{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1784778339,
"model": "gpt-4.1",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Optimistic locking detects concurrent updates..."
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 18,
"total_tokens": 43
}
}主なフィールドは次のとおりです。
| Field | Meaning |
|---|---|
id | プロバイダーが生成したリクエストまたは完了の識別子 |
object | レスポンスタイプ。通常は chat.completion |
created | プロバイダーが割り当てた Unix タイムスタンプ |
model | 結果を生成したモデル |
choices | アシスタント出力の候補 |
usage | 入力、出力、合計トークンの使用量 |
system_fingerprint | プロバイダー側のモデル設定を示すオプションの識別子 |
各 choice には通常、index、アシスタントの message、finish_reason、およびオプションの対数確率が含まれます。一般的な終了理由は次のとおりです。
stop: 通常の完了length: 出力またはコンテキストの上限に到達tool_calls: モデルが 1 つ以上のツールを要求content_filter: 安全性フィルターによって生成が停止
HTTP レスポンスが成功したからといって、回答が正しいとは限りません。成功は、プロバイダーがリクエストを受け付け、構文的に有効なモデル結果を生成したことを意味します。
プロバイダー拡張 #
OpenAI 互換であることは、バイト単位で同一であることを意味しません。たとえば次のような違いがあります。
- Azure 互換レスポンスには
prompt_filter_resultsやcontent_filter_resultsが含まれる場合があります。 - ゲートウェイがレイテンシーチェックポイント、ルーティング情報、キャッシュ統計、サービス階層のメタデータを追加する場合があります。
- トークンの詳細で、推論、音声、キャッシュ、受理された予測、拒否された予測のトークンを区別する場合があります。
- エラーレスポンスとレート制限ヘッダーはプロバイダーごとに異なります。
可能な場合、アプリケーションは未知のフィールドを無視し、すべてのプロバイダー拡張にドメインモデルを結合しないようにします。拡張が必要な場合は、プロバイダー固有の設定またはメタデータとして明示的に表現します。
スキーマ内のツール呼び出し #
リクエストにはツール定義を含めることができます。
{
"model": "gpt-4.1",
"messages": [
{
"role": "user",
"content": "What is the status of order 42?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "find_order",
"description": "Look up an order by ID",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Canonical order ID"
}
},
"required": [
"order_id"
]
}
}
}
]
}モデルは関数を実行しません。代わりに、構造化されたリクエストを含むアシスタントメッセージを返せます。
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "find_order",
"arguments": "{\"order_id\":\"42\"}"
}
}
]
}ホストアプリケーションは引数を検証し、ローカルまたはリモートの処理を呼び出し、後続のリクエストで結果を送信します。
{
"role": "tool",
"tool_call_id": "call_123",
"content": "{\"status\":\"SHIPPED\"}"
}したがって、通常の通信は次のようになります。
model request with tool schemas
<- assistant tool-call request
application executes tool
-> model request with tool result
<- final assistant answerツール呼び出しには、少なくとも 2 回のモデルとの往復が必要になることがよくあります。認可、検証、冪等性、タイムアウト、副作用について責任を持つのはモデルではなくアプリケーションです。
ストリーミングレスポンス #
"stream": true の場合、サーバーは通常、増分的な chat.completion.chunk オブジェクトをデータとして含む Server-Sent Events を返します。
data: {"object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"}}]}
data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"Optimistic"}}]}
data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" locking"}}]}
data: [DONE]チャンクには完全なメッセージではなく差分が含まれます。クライアントは、提供される場合、コンテンツ、ツール呼び出しの断片、終了理由、使用量を集約する必要があります。ストリーミングは最初に表示可能な出力が得られるまでの時間を短縮しますが、合計レイテンシーやトークンコストを必ずしも削減するわけではありません。
Spring AI のオブジェクトモデル #
Spring AI は、アプリケーション向けのチャット概念とプロバイダーのワイヤー DTO を分離します。主な型は次のとおりです。
| Spring AI type | Responsibility |
|---|---|
ChatClient | プロンプト、Advisor、ツール、出力変換向けの流暢なアプリケーション API |
ChatModel | チャットモデルを呼び出すプロバイダー中立インターフェース |
Prompt | 順序付きメッセージとリクエストオプション |
Message | 会話内の 1 エントリを表す共通インターフェース |
SystemMessage | system ロールの指示 |
UserMessage | user ロールの入力 |
AssistantMessage | アシスタントのテキストとオプションのツール呼び出し |
ToolResponseMessage | アシスタントのツール呼び出しに対応する結果 |
ChatOptions | 移植可能なリクエストオプション |
OpenAiChatOptions | OpenAI アダプター使用時の OpenAI 固有オプション |
ChatResponse | 生成結果とメタデータを含むプロバイダー中立レスポンス |
Generation | アシスタント候補の 1 つ |
ChatResponseMetadata | モデル、使用量、レート制限、プロバイダーのメタデータ |
Usage | 入力、出力、合計トークン数 |
低レベルのアプリケーションフローは次のとおりです。
ChatOptions options = OpenAiChatOptions.builder()
.model("gpt-4.1")
.temperature(0.3)
.maxTokens(1000)
.build();
Prompt prompt = new Prompt(
List.of(
new SystemMessage("Answer concisely."),
new UserMessage("Explain optimistic locking.")
),
options
);
ChatResponse response = chatModel.call(prompt);
AssistantMessage answer = response.getResult().getOutput();ChatClient は、より高レベルな流暢な同等 API を提供します。
ChatResponse response = chatClient.prompt()
.system("Answer concisely.")
.user("Explain optimistic locking.")
.options(options)
.call()
.chatResponse();call() は同期的な終端操作です。プロンプト構築メソッドはリクエストを設定するだけであり、call() が Advisor チェーンとモデルアダプターを呼び出して完了を待機します。代わりに stream() はリアクティブストリームを返します。
Spring AI によるリクエストのシリアライズ #
Spring AI は通常、Prompt.toString() を HTTP 本文としてシリアライズしません。プロバイダーアダプターがマッピングを実行します。
Spring AI Prompt
messages:
SystemMessage
UserMessage
AssistantMessage
ToolResponseMessage
options:
ChatOptions
|
v
OpenAI adapter
OpenAI request DTOs
role/content conversion
option and tool-schema conversion
|
v
JSON serializer
|
v
POST /v1/chat/completionsSpring AI OpenAI 統合では、トランスポート型は OpenAiApi 配下に表現され、リクエスト、メッセージ、choice、completion の構造を含みます。アプリケーションでは通常、これらのトランスポート DTO を直接構築するのではなく、移植可能な型を使用すべきです。
レスポンスは逆方向に進みます。
HTTP response JSON
-> OpenAI response DTOs
-> AssistantMessage
-> Generation
-> ChatResponseMetadata and Usage
-> ChatResponseこの境界があるため、ChatClientRequest をログに記録すると Java のレコードまたは Prompt 表現が表示される一方、HTTP ワイヤーログには具体的な OpenAI 互換 JSON が表示されます。前者は変換前のプロバイダー中立リクエストであり、後者は変換後にシリアライズされたトランスポート本文です。
Advisor と会話メモリ #
ChatMemory は OpenAI のワイヤースキーマの一部ではありません。これはアプリケーション側の抽象化です。メモリ Advisor はモデル呼び出し前にメッセージを読み込み、呼び出し後に新しいメッセージを保存します。
ChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
ChatClient client = builder
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(memory).build()
)
.build();各リクエストでは会話キーを指定します。
ChatResponse response = client.prompt()
.user(question)
.advisors(spec -> spec.param(
ChatMemory.CONVERSATION_ID,
conversationId
))
.call()
.chatResponse();概念的には、Advisor は次の処理を実行します。
ChatMemory.get(conversationId)
-> add historical messages to Prompt
-> ChatModel.call(Prompt)
-> ChatMemory.add(current user and assistant messages)その結果、シリアライズされた messages 配列には、選択された会話ウィンドウ全体が含まれる場合があります。会話 ID、ストレージ、保持期間、アクセス制御はホストアプリケーションに属し、Chat Completions API はそれらを定義しません。
Spring AI のツールシリアライズ #
Spring AI は実行可能な機能を ToolCallback として表現します。アノテーション付きの Java メソッドを検出してコールバックに変換できます。
public class OrderTools {
@Tool(description = "Look up an order by ID")
public OrderStatus findOrder(
@ToolParam(description = "Canonical order ID") String orderId) {
return orderService.find(orderId);
}
}オブジェクトを登録します。
String answer = chatClient.prompt()
.user("Where is order 42?")
.tools(orderTools)
.call()
.content();これにより、Spring AI は次の処理を行います。
- アノテーション付きメソッドを検査する。
ToolCallbackを作成する。- 引数の JSON Schema を導出する。
- コールバック定義をプロバイダーの
tools配列にマッピングする。 - 返された
tool_callsをAssistantMessage.ToolCallの値にマッピングする。 - 自動ツール実行が有効な場合、ホストアプリケーション内でコールバックを呼び出す。
- 次のモデルリクエスト向けに、結果をツールメッセージとしてシリアライズする。
LLM が認識するのは、名前、説明、スキーマだけです。JVM をリフレクションしたり、任意のメソッドを呼び出したりすることはできません。
2 つの表現のロギング #
Spring AI の Advisor ロギングと HTTP ワイヤーロギングは、異なる問いに答えます。
SimpleLoggerAdvisor などの Advisor は、次の内容を表示できます。
- それ以前の Advisor がリクエストを変更した後の Spring AI メッセージ
ChatOptions- 会話 ID などの Advisor コンテキスト
- プロバイダー中立の
ChatResponse
リクエストが Java の toString() 出力のように見えるのは、最終的な HTTP 本文ではなく ChatClientRequest を記録しているためです。
HTTP クライアントロギングでは、次の内容を表示できます。
- URL とヘッダー
- 最終的にシリアライズされた JSON リクエスト
- プロバイダー固有のフィールドとデフォルト値
- 生の JSON レスポンス
- ストリーミングチャンク
ワイヤーロギングは、プロバイダーアダプターがトランスポート DTO を作成した後に発生します。したがって、シリアライズやプロバイダー互換性を診断する適切な層です。ただし危険性もあります。メッセージ、取得したドキュメント、ツール結果、認証ヘッダー、Cookie、個人データがログに現れる可能性があります。完全なペイロードロギングは、管理された開発環境でのみ有効化し、秘匿化と保持期間の制限を適用してください。
互換性の境界 #
OpenAI 互換エンドポイントは、同一の動作を保証するものではなく、プロバイダー契約として扱うべきです。次の項目を検証してください。
- エンドポイントパスと認証
- サポートされるメッセージロールとコンテンツ型
- モデル名とデプロイメント選択
- リクエストオプションの名前と制限
- ツール呼び出しと構造化出力のサポート
- ストリーミングイベント形式
- トークン使用量の計算
- 安全性および拒否フィールド
- エラー本文と HTTP ステータスコード
- プロバイダー固有の拡張
Spring AI は、これらの詳細をモデルアダプター内に保持することで、アプリケーションの結合を減らします。ただし、互換性のないプロバイダー動作を同一にすることはできません。サポートする各ゲートウェイとモデルを、個別の実行パスとしてテストしてください。
Advisor、RAG、ベクトルストア、メモリリポジトリ、可観測性、本番アーキテクチャを含む、より広範なアプリケーションモデルについては、Spring AI を参照してください。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)