OpenAI Chat Completions API 是一种广泛实现的、基于 JSON over HTTP 的对话式语言模型接口。客户端发送按顺序排列的消息列表和生成选项;服务器返回一条或多条包含元数据和令牌使用量的 assistant 消息。
其基本结构如下:
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 用于标识应运行的模型以及模型应看到的对话上下文。其余字段控制生成行为。一些网关通过配置、部署名称、URL 或请求头选择模型,而不是要求请求体中包含 model。
消息 #
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 | 核采样阈值 |
max_tokens | 在旧版或兼容 API 中限制生成的令牌数 |
max_completion_tokens | 某些模型使用的新版 completion 令牌数上限 |
n | 候选选项的数量 |
frequency_penalty | 抑制重复使用频率较高的令牌 |
presence_penalty | 鼓励引入尚未使用的令牌 |
stop | 在配置的序列处停止生成 |
stream | 选择返回一个完整响应还是流式传输多个块 |
response_format | 在支持时请求 JSON 或受 schema 约束的输出 |
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 | 提供商生成的请求或 completion 标识符 |
object | 响应类型,通常为 chat.completion |
created | 提供商分配的 Unix 时间戳 |
model | 生成结果的模型 |
choices | assistant 输出候选项 |
usage | 输入、输出和总令牌用量 |
system_fingerprint | 可选的提供商侧模型配置标识符 |
每个 choice 通常包含 index、assistant message、finish_reason 和可选的对数概率。常见的结束原因包括:
stop:正常完成;length:达到输出或上下文限制;tool_calls:模型请求了一个或多个工具;以及content_filter:安全过滤器停止了生成。
HTTP 响应成功并不意味着答案正确。它只表示提供商接受了请求,并生成了语法有效的模型结果。
提供商扩展 #
OpenAI 兼容并不意味着字节级完全相同。例如:
- Azure 兼容响应可能包含
prompt_filter_results和content_filter_results。 - 网关可能增加延迟检查点、路由详情、缓存统计或服务层级元数据。
- 令牌详情可能区分推理、音频、缓存、已接受预测或已拒绝预测令牌。
- 不同提供商的错误响应和速率限制请求头可能不同。
在可能的情况下,应用程序应忽略未知字段,并避免让领域模型绑定到每一个提供商扩展。当某个扩展是必需的时,应在提供商专用配置或元数据中明确表示它。
Schema 中的工具调用 #
请求可以包含工具定义:
{
"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"
]
}
}
}
]
}模型不会执行该函数。它可以返回包含结构化请求的 assistant 消息:
{
"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工具调用通常至少需要两次模型往返。授权、验证、幂等性、超时和副作用仍由应用程序负责,而不是由模型负责。
流式响应 #
当 "stream": true 时,服务器通常返回 Server-Sent Events,其数据包含增量的 chat.completion.chunk 对象:
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 | 表示一条会话记录的通用接口 |
SystemMessage | system 角色指令 |
UserMessage | user 角色输入 |
AssistantMessage | assistant 文本和可选工具调用 |
ToolResponseMessage | 与 assistant 工具调用关联的结果 |
ChatOptions | 可移植的请求选项 |
OpenAiChatOptions | 使用 OpenAI 适配器时的 OpenAI 专用选项 |
ChatResponse | 包含生成结果和元数据的提供商无关响应 |
Generation | 一个 assistant 候选项 |
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/completions对于 Spring AI OpenAI 集成,传输类型位于 OpenAiApi 下,包括请求、消息、choice 和 completion 结构。应用程序通常应使用可移植类型,而不是直接构造这些传输 DTO。
响应沿相反路径处理:
HTTP response JSON
-> OpenAI response DTOs
-> AssistantMessage
-> Generation
-> ChatResponseMetadata and Usage
-> ChatResponse这一边界解释了为什么记录 ChatClientRequest 时可能显示 Java record 或 Prompt 表示,而 HTTP 线级日志显示具体的 OpenAI 兼容 JSON。前者是转换前的提供商无关请求;后者是转换后序列化的传输请求体。
Advisor 与会话记忆 #
ChatMemory 不属于 OpenAI 线协议 schema,而是应用侧抽象。记忆 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 只能看到名称、描述和 schema。它不能反射 JVM,也不能调用任意方法。
记录两种表示 #
Spring AI Advisor 日志和 HTTP 线级日志回答的是不同问题。
SimpleLoggerAdvisor 等 Advisor 可以显示:
- 之前的 Advisor 修改请求后的 Spring AI 消息;
ChatOptions;- 会话 ID 等 Advisor 上下文;以及
- 提供商无关的
ChatResponse。
请求看起来像 Java toString() 输出,是因为日志记录的是 ChatClientRequest,而不是最终的 HTTP 请求体。
HTTP 客户端日志可以显示:
- URL 和请求头;
- 最终序列化的 JSON 请求;
- 提供商特有的字段和默认值;
- 原始 JSON 响应;以及
- 流式块。
线级日志发生在提供商适配器创建传输 DTO 之后,因此适合诊断序列化问题和提供商兼容性。但它也存在风险:消息、检索到的文档、工具结果、授权请求头、Cookie 和个人数据都可能出现在日志中。只有在受控的开发环境中才应启用完整载荷日志,并应用脱敏和保留期限限制。
兼容性边界 #
OpenAI 兼容端点应被视为提供商契约,而不是行为完全相同的证明。请验证:
- 端点路径和身份验证;
- 支持的消息角色和内容类型;
- 模型命名和部署选择;
- 请求选项名称和限制;
- 工具调用和结构化输出支持;
- 流式事件格式;
- 令牌使用量统计;
- 安全和拒绝字段;
- 错误正文和 HTTP 状态码;以及
- 提供商特有的扩展。
Spring AI 通过将这些细节保留在模型适配器中来减少应用程序耦合,但它无法使不兼容的提供商行为变得一致。请将每个受支持的网关和模型作为独立的执行路径进行测试。
请参阅 Spring AI,了解更广泛的应用模型,包括 Advisors、RAG、向量存储、记忆仓库、可观测性和生产架构。
评论
在 GitHub 上阅读或评论(发表评论需要登录)