OpenAI Chat Completions 與 Spring AI

最後更新: 2025-04-20 05:43:09 +0800 +0800
tags: Ai | Llm | Openai | Spring-Ai | Api | Json
原文以英文撰寫。您正在閱讀AI翻譯版本。

OpenAI Chat Completions API 是一種廣泛實作、以 JSON over HTTP 為基礎的對話式語言模型介面。用戶端會傳送依序排列的訊息清單與生成選項;伺服器則回傳一則或多則包含中繼資料與權杖用量的 assistant 訊息。

其基本結構如下:

text
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 請求 #

簡化後的請求如下:

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 是有順序的。其順序是提示的一部分:

json
[
  {
    "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."
  }
]

常見角色如下:

RoleMeaning
system應用程式指示與行為
developer較新模型 API 支援的開發者指示
user使用者提供的輸入
assistant先前的或新生成的模型輸出
tool應用程式工具呼叫的結果

文字通常以字串表示。支援多模態的模型可能接受由具類型內容部分組成的陣列,其中包含文字、影像、音訊或其他媒體。

端點通常是無狀態的。呼叫端僅僅重複使用對話 ID,並不會讓模型記住先前的 HTTP 請求。若要繼續對話,用戶端必須再次傳送所選取的先前訊息:

text
load conversation history
  -> append current user message
  -> send all selected messages
  -> receive assistant message
  -> save new user and assistant messages

為了保持在模型的上下文限制內,歷史記錄可以被截斷、摘要化,或從外部儲存中擷取。

生成選項 #

常見的請求欄位包括:

FieldPurpose
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 回應 #

簡化後的非串流回應如下:

json
{
  "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
  }
}

主要欄位如下:

FieldMeaning
id提供者生成的請求或 completion 識別碼
object回應類型,通常為 chat.completion
created提供者指派的 Unix 時間戳
model產生結果的模型
choicesassistant 輸出候選項
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 中的工具呼叫 #

請求可以包含工具定義:

json
{
  "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 訊息:

json
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_123",
      "type": "function",
      "function": {
        "name": "find_order",
        "arguments": "{\"order_id\":\"42\"}"
      }
    }
  ]
}

主機應用程式會驗證引數、呼叫本地或遠端操作,並在後續請求中傳送結果:

json
{
  "role": "tool",
  "tool_call_id": "call_123",
  "content": "{\"status\":\"SHIPPED\"}"
}

因此,通常的流量如下:

text
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 物件:

text
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 typeResponsibility
ChatClient用於提示、Advisor、工具和輸出轉換的流暢應用程式 API
ChatModel呼叫聊天模型的提供者無關介面
Prompt有序訊息與請求選項
Message表示單一對話項目的通用介面
SystemMessagesystem 角色指示
UserMessageuser 角色輸入
AssistantMessageassistant 文字與可選的工具呼叫
ToolResponseMessage與 assistant 工具呼叫關聯的結果
ChatOptions可移植的請求選項
OpenAiChatOptions使用 OpenAI 介面卡時的 OpenAI 專用選項
ChatResponse包含生成結果與中繼資料的提供者無關回應
Generation一個 assistant 候選項
ChatResponseMetadata模型、用量、速率限制與提供者中繼資料
Usage輸入、輸出與總權杖數

低階應用程式流程如下:

java
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:

java
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 本文。提供者介面卡會執行對映:

text
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。

回應會沿著相反路徑處理:

text
HTTP response JSON
  -> OpenAI response DTOs
  -> AssistantMessage
  -> Generation
  -> ChatResponseMetadata and Usage
  -> ChatResponse

這個邊界解釋了為何記錄 ChatClientRequest 時可能顯示 Java record 或 Prompt 表示,而 HTTP 線上記錄則顯示具體的 OpenAI 相容 JSON。前者是轉換前的提供者無關請求;後者則是轉換後序列化的傳輸本文。

Advisor 與對話記憶 #

ChatMemory 不屬於 OpenAI 線上 schema,而是應用程式端的抽象。記憶 Advisor 會在模型呼叫前載入訊息,並在呼叫後儲存新訊息:

java
ChatMemory memory = MessageWindowChatMemory.builder()
    .maxMessages(20)
    .build();

ChatClient client = builder
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(memory).build()
    )
    .build();

每個請求都會提供對話鍵:

java
ChatResponse response = client.prompt()
    .user(question)
    .advisors(spec -> spec.param(
        ChatMemory.CONVERSATION_ID,
        conversationId
    ))
    .call()
    .chatResponse();

從概念上看,Advisor 會執行:

text
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 方法可以被探索並轉換為回呼:

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);
    }
}

註冊該物件:

java
String answer = chatClient.prompt()
    .user("Where is order 42?")
    .tools(orderTools)
    .call()
    .content();

會使 Spring AI:

  1. 檢查帶有註解的方法;
  2. 建立 ToolCallback;
  3. 為其引數推導 JSON Schema;
  4. 將回呼定義對映到提供者的 tools 陣列;
  5. 將回傳的 tool_calls 對映為 AssistantMessage.ToolCall 值;
  6. 啟用自動工具執行時,在主機應用程式中呼叫回呼;以及
  7. 將結果序列化為下一次模型請求的工具訊息。

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 上閱讀或評論(發表評論需要登入)