打造專屬 ChatGPT(三):回傳物件、除錯與實務設定 (20260601更新)
K. 可觀測性與資料管理
前面幾章介紹的參數,大多會直接影響模型怎麼回答。
例如:
- messages 影響模型看到的上下文。
- temperature 影響生成的隨機性。
- response_format 影響輸出格式。
- tools 影響模型是否能呼叫工具。
- stream 影響回覆是否分段傳回。
接下來這一章要看的參數,則偏向產品實務中的可觀測性、資料管理與服務層級控制。
常見相關參數包括:
- store
- metadata
- user
- service_tier
這些參數不一定會改變模型回答的內容,但會影響你如何追蹤請求、管理資料、分析用量、排查問題,以及控制處理延遲或成本。
如果你只是寫一個測試腳本,可能暫時不需要特別設定它們。
但如果你要把 Chat Completions API 放進正式產品,這些參數就很值得理解。
K.1 為什麼需要可觀測性?
當你只是在本機測試 API,通常只需要看:
print(completion.choices[0].message.content)
就足夠了。
但正式產品會遇到更多問題,例如:
- 哪個功能最常呼叫模型?
- 哪個使用者消耗最多 token?
- 哪些請求延遲特別高?
- 哪些模型回覆品質不穩定?
- 哪些 prompt 成本太高?
- 哪些請求使用了 tool calling?
- 哪些請求產生了 length、tool_calls 或 content_filter?
- 發生錯誤時,要怎麼追蹤是哪一筆請求?
這些問題不只是「模型回答得好不好」,而是產品營運、成本控管與除錯能力的問題。
因此,正式專案中通常會記錄:
- request ID
- user ID 或內部使用者識別碼
- session ID
- feature name
- model
- prompt tokens
- completion tokens
- total tokens
- latency
- finish reason
- error type
- metadata
- 是否 stream
- 是否 tool calling
- 是否 structured output
store、metadata、user、service_tier 這類參數,就是在這個脈絡下變得重要。
K.2 store:是否儲存 Chat Completion
store 用來控制這次 Chat Completion 是否要被儲存。
概念上可以這樣理解:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
store=True,
)
如果設定 store=True,這筆 completion 會被儲存,之後可以透過 Chat Completions 的 retrieve、update、delete、list 等端點管理或查詢。Chat Completions API reference 目前仍列出建立、取得、更新、刪除與列出 Chat Completions 的端點。
如果設定 store=False,或不使用儲存功能,則比較像一般即時請求:你拿到結果後,由自己的系統決定要不要保存相關資料。
實務上,是否設定 store=True 取決於你的用途。
適合考慮 store=True 的情境:
- 你想在平台端保留 completion,方便後續查詢。
- 你想搭配 metadata 管理特定請求。
- 你正在做測試、評估或除錯。
- 你需要追蹤特定 completion ID。
- 你想使用相關的 stored completion 管理端點。
不一定適合 store=True 的情境:
- 使用者輸入包含敏感資料。
- 你自己的系統已經有完整紀錄。
- 你不希望第三方平台端保留該次 completion。
- 該請求只是一次性、低價值、無需追蹤。
- 你有嚴格的資料保存政策或合規需求。
例如,一個客服分類器可能會這樣:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
response_format={
"type": "json_schema",
"json_schema": schema
},
store=True,
metadata={
"feature": "support_ticket_classifier",
"environment": "production"
},
)
這樣之後你可以更容易追蹤哪些請求屬於客服分類功能。
不過,store=True 不應該取代你自己的產品資料庫或 audit log。
正式產品中,仍建議你在自己的系統裡記錄必要的請求摘要、使用者操作、權限判斷、成本資訊與錯誤資訊。
K.3 metadata:為請求加上可查詢的標籤
metadata 可以用來替請求加上自訂標籤。
它通常是一組 key-value object,例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
metadata={
"feature": "trip_summary",
"environment": "production",
"app": "tallytrip",
"version": "2026-06-01"
},
)
metadata 的價值是讓你在之後分析或查詢時,可以知道這筆請求是從哪個功能、哪個環境或哪個流程發出的。
常見 metadata 欄位包括:
| metadata key | 說明 |
|---|---|
| feature | 功能名稱,例如 chatbot、receipt_ocr、trip_summary |
| environment | 環境,例如 development、staging、production |
| app | 應用名稱,例如 tallytrip |
| version | prompt 或功能版本 |
| session_id | 內部 session ID |
| request_type | 請求類型,例如 chat、extract、classify |
| experiment | A/B test 或實驗名稱 |
| customer_tier | 使用者方案,例如 free、pro、enterprise |
例如:
metadata={
"feature": "receipt_extraction",
"environment": "production",
"schema_version": "receipt_v2",
"experiment": "ocr_prompt_ab_test"
}
這樣你之後就能比較:
- receipt_v1 和 receipt_v2 哪個抽取品質比較好。
- 哪個功能的 token 消耗最高。
- staging 和 production 的錯誤比例是否不同。
- 某個 A/B test prompt 是否降低成本或提升成功率。
K.4 metadata 不要放敏感資料
metadata 很方便,但不要把它當成任意資料儲存區。
不建議放進 metadata 的內容包括:
- 使用者真實姓名
- 電話
- 地址
- 身分證字號
- 信用卡資訊
- API key
- access token
- 完整聊天內容
- 醫療、法律、財務等敏感細節
- 任何不必要的個人資料
比較好的做法是放內部識別碼或不可逆雜湊值。
例如,不建議:
metadata={
"email": "[email protected]",
"phone": "+886912345678"
}
比較建議:
metadata={
"internal_user_id": "usr_8f3a91",
"feature": "trip_summary"
}
如果真的需要追蹤到某位使用者,應該在你自己的資料庫中用內部 ID 對應,而不是把個資直接放進 API metadata。
實務原則是:
metadata 應該用來分類與追蹤請求,不應該用來儲存敏感內容。
K.5 user:標記終端使用者
user 參數通常用來標記這次請求來自哪個終端使用者。
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
user="usr_8f3a91",
)
這裡的 user 不一定要是使用者真實姓名或 email。
實務上比較建議使用你系統內部的匿名 ID、使用者 ID 或 hash 後的識別碼。
例如:
user="user_12345"
或:
user="hash_9f86d081"
user 的用途通常包括:
- 協助追蹤不同終端使用者的請求。
- 協助偵測濫用或異常行為。
- 協助成本與用量分析。
- 協助 debug 特定使用者回報的問題。
不過,和 metadata 一樣,不建議把個資直接塞進 user。
不建議:
user="[email protected]"
建議:
user="usr_8f3a91"
如果你的產品有登入系統,可以把自己的 internal user id 傳入 user。
如果是匿名使用者,可以使用 session id 或匿名 visitor id。
K.6 metadata 和 user 的差異
metadata 和 user 都可以幫助你追蹤請求,但用途不同。
可以這樣理解:
| 參數 | 主要用途 | 範例 |
|---|---|---|
| user | 標記終端使用者 | usr_8f3a91 |
| metadata | 標記請求脈絡、功能、環境、版本 | feature=receipt_extraction |
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
user="usr_8f3a91",
metadata={
"feature": "receipt_extraction",
"environment": "production",
"schema_version": "receipt_v2"
},
)
這裡:
- user 告訴你「是哪個終端使用者」。
- metadata 告訴你「這次請求屬於哪個功能與情境」。
在正式產品中,通常兩者都會搭配使用。
K.7 service_tier:控制處理服務層級
service_tier 用來指定這次請求的處理服務層級。
官方文件目前列出的可用值包括:
auto
default
flex
scale
priority
可以先用這張表理解:
| service_tier | 說明 |
|---|---|
| auto | 依 Project 設定自動選擇;未特別設定時,通常會使用 default |
| default | 使用所選模型的標準價格與效能 |
| flex | 使用 flex processing,通常偏向較低成本但較高延遲或較不穩定可用性 |
| scale | 依方案或專案設定使用 scale 相關處理層級 |
| priority | 使用 priority processing,通常偏向較低延遲但成本較高或需特定資格 |
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier="default",
)
如果你不設定 service_tier,預設行為通常是 auto,也就是依照 Project 設定決定實際處理方式。
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
)
這時候你沒有明確指定服務層級,平台會使用預設策略。
如果你希望非即時任務降低成本,可以考慮:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier="flex",
)
如果你希望正式產品中的即時聊天降低延遲,且你的帳號與模型支援,可以考慮:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier="priority",
)
不過要注意:不是所有模型、帳號、專案或方案都一定支援所有 service tier。正式使用前應以最新 pricing、project 設定與模型支援狀態為準。
K.8 response 裡的 service_tier 不一定等於 request 裡的 service_tier
使用 service_tier 時,有一個很重要的細節:
response body 裡的 service_tier 代表實際用來處理這次請求的服務層級,可能和你在 request 裡指定的值不同。
例如你送出:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier="auto",
)
回傳結果可能顯示:
print(completion.service_tier)
輸出:
default
這不一定代表錯誤,而是因為 auto 會依照 Project 設定或可用性,選擇實際處理層級。
同樣地,你請求某個 tier,但最後 response 顯示的 tier 可能反映實際執行結果。
因此如果你要做成本分析或效能分析,應該記錄 response 裡實際回傳的 service_tier,而不只是記錄 request 裡送出的值。
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier="auto",
)
log_record = {
"model": completion.model,
"service_tier": completion.service_tier,
"usage": completion.usage,
}
這樣你的 log 會比較接近真實執行情況。
K.9 service_tier 的實務選擇
可以用任務類型來思考 service_tier。
| 使用情境 | 建議思路 |
|---|---|
| 即時聊天 UI | 優先考慮延遲,可能使用 default 或 priority |
| 客服即時回答 | 延遲重要,但也要控制成本 |
| 批次摘要 | 可接受較高延遲,可考慮 flex |
| 夜間背景任務 | 可考慮 flex |
| 資料抽取 pipeline | 依是否即時決定 |
| 使用者等待中的操作 | 避免使用延遲過高的 tier |
| 開發測試 | 可以考慮較低成本設定 |
| 高價值付費功能 | 可考慮較高服務層級 |
例如,聊天 UI 可能這樣:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
stream=True,
service_tier="priority",
)
批次摘要可能這樣:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier="flex",
)
如果你的產品有不同方案,也可以根據 plan 動態選擇:
def choose_service_tier(user_plan: str) -> str:
if user_plan == "enterprise":
return "priority"
if user_plan == "free":
return "flex"
return "default"
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
service_tier=choose_service_tier(user_plan),
)
不過,這只是概念範例。實際使用時仍要確認你的帳號、模型與專案是否支援對應 tier。
K.10 usage:成本與用量觀察的核心欄位
雖然 usage 是 response 欄位,不是 request 參數,但它和可觀測性高度相關。
非串流模式下,completion 通常會包含:
completion.usage
它會提供這次請求的 token 使用量,例如:
{
"prompt_tokens": 120,
"completion_tokens": 80,
"total_tokens": 200
}
這些資訊可以用來做:
- 成本估算
- 功能用量分析
- prompt 優化
- 模型比較
- 異常請求偵測
- 使用者額度管理
例如你可以記錄:
usage = completion.usage
log_record = {
"model": completion.model,
"prompt_tokens": usage.prompt_tokens,
"completion_tokens": usage.completion_tokens,
"total_tokens": usage.total_tokens,
}
如果是串流模式,則需要搭配:
stream_options={
"include_usage": True
}
並在最後的 usage chunk 中取得 token 使用量。
這部分已經在前面的串流章節介紹過。
實務上,我會建議正式產品一定要記錄 usage,至少記錄:
- model
- prompt_tokens
- completion_tokens
- total_tokens
- feature
- user id
- request time
- latency
- finish_reason
這樣才能知道 AI 功能的真實成本。
K.11 建立自己的 AI request log
OpenAI API 的 metadata、user、store 很有用,但正式產品仍應該建立自己的 request log。
一個簡化版資料表可能長這樣:
ai_request_logs
- id
- user_id
- feature
- model
- service_tier_requested
- service_tier_used
- prompt_tokens
- completion_tokens
- total_tokens
- latency_ms
- finish_reason
- status
- error_code
- created_at
如果你的產品有多種 AI 功能,也可以多記錄:
- session_id
- conversation_id
- prompt_version
- schema_version
- tool_names
- response_format_type
- is_streaming
- request_metadata
例如,在 Django 裡可能會有類似 model:
from django.db import models
class AIRequestLog(models.Model):
user_id = models.CharField(max_length=128, null=True, blank=True)
feature = models.CharField(max_length=128)
model = models.CharField(max_length=128)
service_tier_requested = models.CharField(max_length=32, null=True, blank=True)
service_tier_used = models.CharField(max_length=32, null=True, blank=True)
prompt_tokens = models.IntegerField(null=True, blank=True)
completion_tokens = models.IntegerField(null=True, blank=True)
total_tokens = models.IntegerField(null=True, blank=True)
latency_ms = models.IntegerField(null=True, blank=True)
finish_reason = models.CharField(max_length=64, null=True, blank=True)
status = models.CharField(max_length=32, default="success")
error_code = models.CharField(max_length=128, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
這樣你就可以在後台分析:
- 哪個功能最貴。
- 哪個模型延遲最高。
- 哪個 prompt version 比較省 token。
- 哪些請求常因 length 截斷。
- 哪些使用者觸發大量請求。
K.12 實務範例:可追蹤的 Chat Completion 請求
以下是一個比較接近正式產品的範例。
import time
from openai import OpenAI
client = OpenAI()
def create_chat_completion(
*,
user_id: str,
feature: str,
messages: list,
model: str = "gpt-5.5",
):
started_at = time.perf_counter()
completion = client.chat.completions.create(
model=model,
messages=messages,
user=user_id,
metadata={
"feature": feature,
"environment": "production",
"prompt_version": "v1"
},
store=True,
service_tier="auto",
)
latency_ms = int((time.perf_counter() - started_at) * 1000)
choice = completion.choices[0]
usage = completion.usage
log_record = {
"user_id": user_id,
"feature": feature,
"model": completion.model,
"service_tier_used": getattr(completion, "service_tier", None),
"prompt_tokens": usage.prompt_tokens if usage else None,
"completion_tokens": usage.completion_tokens if usage else None,
"total_tokens": usage.total_tokens if usage else None,
"finish_reason": choice.finish_reason,
"latency_ms": latency_ms,
"completion_id": completion.id,
}
# 這裡可以寫入你的資料庫或 log system
print(log_record)
return choice.message.content
這個範例做了幾件事:
- 用 user 標記終端使用者。
- 用 metadata 標記功能與 prompt 版本。
- 用 store=True 讓 completion 可被儲存與管理。
- 用 service_tier=”auto” 交由 Project 設定決定處理層級。
- 記錄 latency。
- 記錄 token usage。
- 記錄 finish reason。
- 記錄 completion id。
這比只呼叫 API 然後印出內容更適合正式產品。
K.13 錯誤處理也應該納入可觀測性
可觀測性不只包含成功請求,也包含失敗請求。
例如:
- API timeout
- rate limit
- invalid request
- tool arguments parse error
- JSON schema validation failed
- content filter
- upstream service error
- network error
一個簡化的錯誤處理範例:
import time
from openai import OpenAI
client = OpenAI()
def safe_chat_completion(messages: list, user_id: str):
started_at = time.perf_counter()
try:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
user=user_id,
metadata={
"feature": "support_chat",
"environment": "production"
},
service_tier="auto",
)
latency_ms = int((time.perf_counter() - started_at) * 1000)
return {
"ok": True,
"content": completion.choices[0].message.content,
"usage": completion.usage,
"latency_ms": latency_ms,
"finish_reason": completion.choices[0].finish_reason,
}
except Exception as e:
latency_ms = int((time.perf_counter() - started_at) * 1000)
error_log = {
"ok": False,
"user_id": user_id,
"feature": "support_chat",
"latency_ms": latency_ms,
"error_type": type(e).__name__,
"error_message": str(e),
}
# 正式產品應寫入 log system,例如資料庫、Sentry、Datadog、Cloud Logging 等
print(error_log)
raise
正式產品中,錯誤 log 不應該只寫 print。
你可以接到:
- Sentry
- Datadog
- Grafana / Loki
- Google Cloud Logging
- AWS CloudWatch
- 自己的資料庫
- OpenTelemetry
重點是:當使用者回報「AI 剛剛回答怪怪的」或「剛剛一直轉圈」,你要能回頭找到是哪一次請求、用了哪個模型、token 用量多少、延遲多久、finish reason 是什麼、是否有錯誤。
K.14 資料保留與隱私設計
使用 AI API 時,資料管理不只是技術問題,也包含隱私與合規設計。
設計產品時,建議先決定:
- 哪些請求內容會存進自己的資料庫?
- 哪些內容只記錄摘要,不保存全文?
- 哪些資料會傳給模型?
- 哪些資料需要遮罩或去識別化?
- 哪些功能可以設定 store=True?
- 哪些功能應該避免儲存?
- log 保留多久?
- 使用者是否可以要求刪除資料?
- 內部人員誰可以查看 AI request log?
例如,如果你在做收據 OCR 或旅遊分帳工具,可能會處理:
- 商店名稱
- 消費金額
- 消費日期
- 使用者旅程資料
- 同行旅伴名稱
- 上傳圖片
- 分帳紀錄
這些資料可能不一定都是高度敏感,但仍然是使用者資料。
因此正式產品中應該避免把完整內容隨意寫入 log,尤其不要把圖片 base64、完整收據文字、個資或付款資訊塞進 metadata。
比較好的做法是:
- metadata 只放功能與內部識別碼。
- request log 只保存必要摘要。
- 敏感內容做遮罩或不保存。
- 高風險資料寫入前先做資料分類。
- 設定資料保留期限。
- 建立刪除與匯出流程。
K.15 可觀測性與資料管理小結
整理一下:
| 參數或欄位 | 用途 |
|---|---|
| store | 控制是否儲存 Chat Completion,方便後續 retrieve、update、delete、list |
| metadata | 替請求加上功能、環境、版本、實驗等標籤 |
| user | 標記終端使用者,建議使用匿名或內部 ID |
| service_tier | 指定處理服務層級,例如 auto、default、flex、scale、priority |
| usage | 回傳 token 使用量,是成本分析核心 |
| finish_reason | 判斷模型停止原因 |
| completion.id | 追蹤特定 completion 的識別碼 |
實務上我會建議:
- 正式產品要記錄 usage、latency、model、finish_reason。
- 使用 metadata 標記功能、環境、prompt version。
- 使用 user 標記匿名化的終端使用者 ID。
- 不要把敏感資料放進 metadata 或 user。
- 是否使用 store=True 要依資料保存策略決定。
- 若設定 service_tier,要記錄 response 實際回傳的 service_tier。
- 建立自己的 AI request log,而不是只依賴 API 回傳結果。
- 高風險或敏感資料應該做遮罩、驗證與資料保留控管。
這一章的重點是:AI 功能上線後,不能只看「模型有沒有回覆」。
你還需要知道它花了多少 token、延遲多久、屬於哪個功能、是哪個使用者觸發、是否成功、是否被截斷、是否呼叫工具,以及是否需要保存。
下一章會介紹機率與除錯相關參數,例如 logprobs、top_logprobs、seed 與 system_fingerprint。這些參數比較進階,但在分析模型輸出、除錯與理解可重現性時很有幫助。
L. 機率與除錯相關參數
前面幾章介紹的參數,主要集中在模型如何生成、如何輸出、如何呼叫工具,以及如何追蹤請求。
這一章要看的參數比較偏進階,通常不會是初學者第一時間會用到的設定,但在分析模型輸出、做分類任務、觀察信心程度、除錯或測試可重現性時很有幫助。
相關參數與欄位包括:
- logprobs
- top_logprobs
- seed
- system_fingerprint
可以先用一句話理解:
logprobs 和 top_logprobs 幫你觀察模型在每個 token 上的機率分布;seed 和 system_fingerprint 則幫你理解同樣請求是否有機會得到接近一致的結果。
這些參數不一定適合每個產品功能,但如果你正在做分類器、評分器、測試集比較、模型輸出分析或 prompt 調校,就值得理解它們。
L.1 logprobs:回傳模型輸出 token 的對數機率
logprobs 用來要求 API 回傳模型輸出 token 的 log probabilities,也就是對數機率。
型別:
boolean or null
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請回答 yes 或 no:台北是台灣的首都嗎?"
}
],
logprobs=True,
)
print(completion.choices[0].message.content)
print(completion.choices[0].logprobs)
如果設定:
logprobs=True
API 會在回傳結果中包含模型生成 token 的 log probability 資訊。
簡化後概念上可能像這樣:
{
"choices": [
{
"message": {
"role": "assistant",
"content": "yes"
},
"logprobs": {
"content": [
{
"token": "yes",
"logprob": -0.02,
"bytes": [121, 101, 115]
}
]
},
"finish_reason": "stop"
}
]
}
這裡的 logprob 是對數機率。
越接近 0,代表模型對這個 token 的機率越高;越小的負數,代表機率越低。
例如:
| logprob | 粗略理解 |
|---|---|
| -0.01 | 非常高機率 |
| -0.5 | 還算高 |
| -2 | 較低 |
| -5 | 很低 |
| -10 | 非常低 |
要注意,log probability 不是百分比。
它是自然對數形式的機率。如果你想轉回一般機率,可以用指數函數:
import math
probability = math.exp(logprob)
例如:
import math
print(math.exp(-0.01)) # 約 0.99
print(math.exp(-2)) # 約 0.135
print(math.exp(-5)) # 約 0.0067
不過在大多數實務情境中,你不一定需要把它轉成百分比。
只要知道「越接近 0 代表模型越有信心」,通常就足夠了。
L.2 logprobs 適合什麼情境?
logprobs 適合用在你想觀察模型輸出信心程度的情境。
常見用途包括:
- 分類任務
- yes / no 判斷
- 多選一答案
- autocomplete 評估
- prompt 調校
- 模型輸出分析
- 檢查模型是否在某些 token 上很不確定
- 對低信心回答加上人工審核流程
例如你在做客服分類器,讓模型輸出:
billing
如果模型對 billing 這個 token 的 logprob 很高,代表它比較有信心。
如果 logprob 很低,可能代表這筆分類結果不穩定,可以交給人工確認或二次模型判斷。
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "請只輸出 billing、account、technical、general 其中之一。"
},
{
"role": "user",
"content": "我付款了,但帳號還是沒有升級。"
}
],
temperature=0,
logprobs=True,
)
choice = completion.choices[0]
print("分類結果:", choice.message.content)
print("logprobs:", choice.logprobs)
這種任務中,logprobs 可以輔助你判斷模型對分類結果的信心。
不過要注意:
logprobs 不是完整的可靠性保證。它只能告訴你模型在生成 token 時的機率資訊,不等於輸出內容一定正確。
例如模型可能非常有信心地產生錯誤答案。
所以在高風險任務中,不應該只依賴 logprobs 判斷是否可信。
L.3 top_logprobs:查看每個位置的候選 token
top_logprobs 用來指定在每個 token 位置,要回傳幾個最可能的候選 token 及其 log probabilities。
型別:
integer or null
範圍:
0 到 20
使用 top_logprobs 時,必須同時設定:
logprobs=True
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "請只輸出 billing、account、technical、general 其中之一。"
},
{
"role": "user",
"content": "我付款了,但是帳號還沒有升級。"
}
],
temperature=0,
logprobs=True,
top_logprobs=5,
)
print(completion.choices[0].message.content)
print(completion.choices[0].logprobs)
這樣回傳結果中,除了實際選中的 token,也會包含該位置其他高機率候選 token。
簡化後可能像這樣:
{
"token": "billing",
"logprob": -0.05,
"top_logprobs": [
{
"token": "billing",
"logprob": -0.05
},
{
"token": "account",
"logprob": -3.2
},
{
"token": "technical",
"logprob": -5.1
},
{
"token": "general",
"logprob": -6.8
}
]
}
這可以幫助你看到模型的第二選擇、第三選擇是什麼。
例如模型輸出 billing,但 account 的 logprob 也很接近,代表這筆分類可能有模糊性。
如果 billing 遙遙領先其他候選,則代表模型對這個 token 的選擇比較明確。
L.4 top_logprobs 的實務用途
top_logprobs 比 logprobs 更適合做分析與比較。
常見用途包括:
- 比較分類候選的相對信心。
- 分析模型為什麼選了某個詞。
- 找出容易混淆的類別。
- 評估 prompt 是否讓模型穩定輸出某些答案。
- 做 autocomplete 或 ranking 分析。
- 觀察模型在特定位置有哪些替代輸出。
例如你在做分類任務,類別有:
billing
account
technical
general
若模型輸出 billing,但 account 也很接近,代表這個 case 可能同時涉及付款與帳號狀態。
你可以根據這個資訊設計後續流程,例如:
- 若第一名和第二名差距太小,標記為 uncertain。
- 若最高 logprob 低於某個閾值,交給人工審核。
- 若分類結果和關鍵字規則衝突,再做第二次判斷。
範例:
def should_review(logprob: float, threshold: float = -1.5) -> bool:
return logprob < threshold
這只是概念範例。實務上閾值不能隨便設定,應該根據你的測試資料、錯誤成本與任務類型調整。
L.5 logprobs 不適合什麼情境?
雖然 logprobs 很有用,但不是所有情境都適合。
不太適合的情境包括:
- 一般聊天機器人
- 長篇文章生成
- 創意寫作
- 多段落摘要
- tool calling 流程
- 高度自然語言的開放式回答
原因是長回答會包含很多 token,每個 token 都有 logprob。
這些資訊資料量很大,也不一定容易解讀。
例如一篇 800 字文章的每個 token 都有 logprob,但你未必能從中直接判斷整篇文章品質好不好。
對長文本而言,logprobs 可以做研究或分析,但在一般產品功能中通常不是第一優先。
比較適合使用 logprobs 的任務通常有幾個特徵:
- 輸出很短。
- 候選答案有限。
- 需要信心評估。
- 需要比較候選 token。
- 可以把低信心結果交給後續流程處理。
例如:
| 任務 | 是否適合 logprobs |
|---|---|
| yes / no 判斷 | 適合 |
| 單一分類標籤 | 適合 |
| 多選一答案 | 適合 |
| 長文章生成 | 通常不適合 |
| 自然聊天 | 通常不適合 |
| tool calling arguments | 通常不優先使用 |
| JSON 資料抽取 | 可分析,但不一定必要 |
L.6 seed:嘗試讓輸出更可重現
seed 是用來讓模型盡力提供 deterministic sampling 的參數。
型別:
integer or null
概念上,如果你使用相同的:
- seed
- model
- messages
- 其他請求參數
就比較有機會得到相同或接近相同的輸出。
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請幫我產生一句產品標語。"
}
],
temperature=0.7,
seed=12345,
)
print(completion.choices[0].message.content)
你可以在測試時固定 seed,讓結果比較容易比較。
例如你想比較兩個 prompt 版本:
PROMPT_V1 = "請幫我寫一句產品標語。"
PROMPT_V2 = "請幫我寫一句簡短、有記憶點的產品標語。"
for prompt in [PROMPT_V1, PROMPT_V2]:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": prompt
}
],
temperature=0.7,
seed=12345,
)
print(completion.choices[0].message.content)
固定 seed 可以降低隨機性帶來的干擾,讓你更容易觀察 prompt 改動造成的差異。
L.7 seed 不保證完全一樣
使用 seed 時要特別注意:
seed 只能讓 API 盡力產生可重現結果,不保證每次一定完全相同。
原因包括:
- 後端模型設定可能更新。
- 系統內部推論環境可能變動。
- 模型版本或路由可能調整。
- 串流、工具呼叫、多模態輸入等情境可能增加變數。
- 即使參數相同,也可能仍有非完全 deterministic 的因素。
因此,不應該把 seed 當成「保證輸出完全一致」的機制。
它比較適合用在測試、除錯、prompt 比較與模型行為觀察。
例如:
適合:
- prompt A/B test
- 測試資料集比較
- regression test
- 範例輸出穩定化
- 重現某次可疑輸出
不適合:
- 當成安全控制
- 當成業務邏輯保證
- 當成資料一致性依據
- 要求每次生產完全相同法律或財務文字
如果你真的需要完全固定的輸出,應該改用規則、模板、程式邏輯或資料庫內容,而不是依賴模型生成。
L.8 system_fingerprint:觀察後端設定是否變動
system_fingerprint 是 response 裡可能出現的欄位,用來表示模型執行時的後端設定指紋。
它不是 request 參數,而是 response 欄位。
你可以這樣讀取:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請用一句話介紹 Chat Completions API。"
}
],
seed=12345,
)
print(completion.system_fingerprint)
system_fingerprint 的用途是搭配 seed 使用,幫助你判斷為什麼同樣請求可能得到不同結果。
例如,你在兩次測試中使用完全相同的:
- model
- messages
- temperature
- seed
- 其他參數
但輸出結果不同。
這時候可以比較:
system_fingerprint
如果兩次的 system_fingerprint 不同,代表後端設定可能已經變動,因此輸出不同是合理的。
概念上:
| seed | system_fingerprint | 輸出差異解讀 |
|---|---|---|
| 相同 | 相同 | 較有機會得到相近結果 |
| 相同 | 不同 | 後端設定可能變動,結果可能不同 |
| 不同 | 相同 | 取樣隨機性不同,結果可能不同 |
| 不同 | 不同 | 結果不同很正常 |
L.9 用 seed 和 system_fingerprint 做測試紀錄
如果你正在做 prompt regression test 或模型輸出評估,建議記錄:
- model
- messages 或 prompt version
- temperature
- top_p
- seed
- system_fingerprint
- output
- usage
- finish_reason
- created time
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0.3,
seed=12345,
)
record = {
"model": completion.model,
"seed": 12345,
"system_fingerprint": completion.system_fingerprint,
"finish_reason": completion.choices[0].finish_reason,
"output": completion.choices[0].message.content,
"usage": completion.usage,
}
這樣之後如果輸出突然變得不一樣,你可以回頭檢查:
- prompt 是否變了?
- model 是否變了?
- temperature 是否變了?
- seed 是否變了?
- system_fingerprint 是否變了?
- max_completion_tokens 是否導致截斷?
- response_format 是否調整?
- tools 是否變動?
這比只保存輸出文字更容易除錯。
L.10 實務範例:分類任務搭配 logprobs
假設我們要做一個客服訊息分類器,類別固定為:
- billing
- account
- technical
- general
可以這樣寫:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是客服分類器。請只輸出 billing、account、technical、general 其中之一,不要加任何解釋。"
},
{
"role": "user",
"content": "我昨天付款了,但是帳號還沒有升級。"
}
],
temperature=0,
logprobs=True,
top_logprobs=5,
max_completion_tokens=10,
)
choice = completion.choices[0]
print("分類結果:", choice.message.content)
print("logprobs:", choice.logprobs)
這種設定有幾個重點:
- temperature=0:讓分類結果更穩定。
- max_completion_tokens=10:分類標籤很短,不需要長輸出。
- logprobs=True:取得實際輸出 token 的機率資訊。
- top_logprobs=5:觀察其他候選 token。
如果你發現模型常常輸出:
billing
但 account 的候選 logprob 也接近,代表這類訊息可能同時涉及付款與帳號狀態。
這時候你可以調整分類 schema,或允許多標籤分類。
L.11 實務範例:固定 seed 比較 prompt 版本
假設你想比較兩個 prompt 對文章摘要品質的影響,可以固定 seed。
from openai import OpenAI
client = OpenAI()
article = """
Chat Completions API 讓開發者可以傳入 messages,
並透過 temperature、response_format、tools、stream 等參數控制模型行為。
"""
prompts = {
"v1": "請摘要以下文章。",
"v2": "請用三個條列重點摘要以下文章,並保留技術名詞。"
}
for version, instruction in prompts.items():
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": instruction
},
{
"role": "user",
"content": article
}
],
temperature=0.3,
seed=12345,
)
print("prompt version:", version)
print("system_fingerprint:", completion.system_fingerprint)
print("output:")
print(completion.choices[0].message.content)
print()
這樣做可以讓你更清楚比較 prompt version 的差異,而不是每次都被隨機性干擾。
但仍要記得:即使固定 seed,也不是絕對可重現。
所以測試結果應該看整體趨勢,不要只依賴單次輸出。
L.12 logprobs、seed 與 temperature 的關係
這幾個參數經常會一起出現在測試與除錯情境中。
可以這樣理解:
| 參數 | 控制或觀察什麼 |
|---|---|
| temperature | 控制生成隨機性 |
| top_p | 控制候選 token 範圍 |
| logprobs | 觀察實際輸出 token 的 log probability |
| top_logprobs | 觀察每個位置的候選 token |
| seed | 嘗試讓取樣更可重現 |
| system_fingerprint | 觀察後端設定是否可能變動 |
如果你要做穩定測試,可以設定:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0,
seed=12345,
)
如果你要分析分類信心,可以設定:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0,
logprobs=True,
top_logprobs=5,
)
如果你要做 prompt 比較,可以設定:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0.3,
seed=12345,
)
但不建議一開始就把所有進階參數都加上。
先明確知道你要解決的是哪個問題,再決定用哪個參數。
L.13 常見錯誤與注意事項
錯誤一:把 logprobs 當成正確率
logprobs 只能表示模型在生成某個 token 時的機率資訊,不等於答案正確率。
模型可能高信心錯誤,也可能低信心但答對。
錯誤二:用 logprobs 評估長篇文章品質
長篇文章有很多 token,每個 token 都有 logprob。
單純看平均 logprob 不一定能代表文章是否好讀、是否正確、是否有邏輯。
錯誤三:設定 top_logprobs 但忘記 logprobs=True
使用 top_logprobs 時,應該同時設定:
logprobs=True
否則請求可能無法如預期回傳候選 token 機率。
錯誤四:以為 seed 保證完全 deterministic
seed 是 best effort,不是絕對保證。
如果你需要完全固定輸出,應該使用程式邏輯或模板。
錯誤五:沒有記錄 system_fingerprint
如果你正在做可重現性測試,卻沒有記錄 system_fingerprint,之後很難判斷輸出變化是不是因為後端設定改變。
錯誤六:所有功能都開 logprobs
logprobs 會增加回傳資料量,也會讓 response 更複雜。
一般聊天或長文生成不一定需要開啟。
L.14 機率與除錯參數小結
整理一下:
| 參數或欄位 | 用途 | 適合情境 |
|---|---|---|
| logprobs | 回傳輸出 token 的 log probability | 分類、短答案、信心分析 |
| top_logprobs | 回傳每個位置的候選 token 與 log probability | 比較候選答案、分析分類模糊性 |
| seed | 嘗試讓輸出更可重現 | 測試、除錯、prompt 比較 |
| system_fingerprint | 表示後端設定指紋 | 搭配 seed 觀察輸出差異原因 |
實務建議:
- 一般聊天:通常不用 logprobs。
- 分類任務:可以使用 logprobs=True 和 top_logprobs。
- prompt 測試:可以固定 seed。
- 可重現性分析:記錄 system_fingerprint。
- 高風險判斷:不要只依賴 logprobs,要搭配驗證或人工審核。
- 長篇生成:通常不需要逐 token 分析。
- 正式產品:把 seed、system_fingerprint、usage、finish_reason 一起記錄,會比較好除錯。
這一章的重點是:
logprobs 和 top_logprobs 幫你看模型在 token 層級的信心分布;seed 和 system_fingerprint 幫你理解輸出是否有機會重現,以及當結果改變時可能是哪裡變了。
下一章會開始解析非串流模式下的 Chat Completion 回傳物件,也就是 chat.completion response object。這會幫助你更完整理解 id、object、created、model、choices、message、finish_reason、usage 等欄位。
M. Chat Completion 回傳物件解析
前面的章節主要在看 request 端,也就是我們如何透過 model、messages、temperature、response_format、tools、stream 等參數控制模型行為。
接下來要看 response 端。
當你沒有啟用串流,也就是沒有設定 stream=True 時,Chat Completions API 會在模型產生完整回答後,一次回傳一個 Chat Completion object。
最常見的讀取方式是:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請用一句話介紹 Chat Completions API。"
}
],
)
print(completion.choices[0].message.content)
這裡的 completion 就是一個 Chat Completion object。
如果把它簡化成 JSON 來看,大致會像這樣:
{
"id": "chatcmpl_xxx",
"object": "chat.completion",
"created": 1710000000,
"model": "gpt-5.5",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Chat Completions API 是一個讓開發者傳入對話訊息,並取得模型回覆的介面。"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 30,
"total_tokens": 50
},
"service_tier": "default",
"system_fingerprint": "fp_xxx"
}
這個物件不只包含模型回答的文字,也包含這次請求的識別碼、模型名稱、候選回答、停止原因、token 使用量、服務層級與系統指紋等資訊。
M.1 Chat Completion object 總覽
Chat Completion object 常見欄位如下:
| 欄位 | 說明 |
|---|---|
| id | 這次 Chat Completion 的唯一識別碼 |
| object | 物件類型,通常是 chat.completion |
| created | 建立時間,Unix timestamp,單位是秒 |
| model | 這次實際使用的模型 |
| choices | 模型產生的候選回答列表 |
| usage | token 使用量 |
| service_tier | 實際使用的服務層級,可能依請求與專案設定而異 |
| system_fingerprint | 後端系統設定指紋,可用於可重現性分析 |
其中,日常開發最常用的是:
completion.choices[0].message.content
成本與追蹤最常用的是:
completion.usage
除錯與可觀測性常用的是:
completion.id
completion.model
completion.choices[0].finish_reason
completion.system_fingerprint
completion.service_tier
M.2 id:這次 completion 的唯一識別碼
id 是這次 Chat Completion 的唯一識別碼。
例如:
{
"id": "chatcmpl_xxx"
}
在 Python 中可以讀取:
print(completion.id)
id 很適合用於 log、除錯與追蹤。
例如你可以把它存進自己的資料庫:
log_record = {
"completion_id": completion.id,
"model": completion.model,
"finish_reason": completion.choices[0].finish_reason,
"total_tokens": completion.usage.total_tokens if completion.usage else None,
}
這樣之後如果某次請求出問題,你就可以根據 completion id 回頭查詢或比對紀錄。
如果這次請求有設定 store=True,這個 id 也會和 stored completion 管理流程有關,之後可以用來 retrieve、update、delete 或 list stored completions。
實務建議:
- 正式產品中建議記錄 completion.id。
- Debug 使用者問題時,completion id 很有用。
- 如果使用 store=True,completion id 更重要。
- 不要把 completion id 當成使用者可操作的公開授權依據;權限仍應由你的後端判斷。
M.3 object:物件類型
object 代表這個 response object 的類型。
在非串流 Chat Completion 回應中,通常是:
{
"object": "chat.completion"
}
在 Python 中可以讀取:
print(completion.object)
這個欄位的主要用途是讓你分辨目前拿到的是哪一種物件。
非串流模式:
chat.completion
串流模式:
chat.completion.chunk
因此,如果你的程式同時處理串流與非串流,就可以透過 object 判斷資料類型。
不過在一般 Python SDK 使用中,你通常不太需要手動判斷 object,因為 SDK 回傳的物件型別已經足夠明確。
M.4 created:建立時間
created 是這次 completion 建立的時間,格式是 Unix timestamp,單位是秒。
例如:
{
"created": 1710000000
}
Python 讀取:
print(completion.created)
如果你想轉成人類可讀的時間,可以這樣:
from datetime import datetime, timezone
created_at = datetime.fromtimestamp(completion.created, tz=timezone.utc)
print(created_at.isoformat())
created 可以用來:
- 記錄請求時間。
- 比對 request log。
- 分析用量趨勢。
- 排查某段時間的錯誤或延遲問題。
不過正式產品中,通常也會在自己的系統裡記錄 created_at,因為 API 的 created 是模型端建立時間,而你的系統可能還需要記錄:
- 使用者送出請求的時間。
- 後端收到請求的時間。
- API request 開始時間。
- API response 結束時間。
- 實際 latency。
例如:
import time
started_at = time.perf_counter()
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
)
latency_ms = int((time.perf_counter() - started_at) * 1000)
created 可以搭配自己的 latency log 一起使用。
M.5 model:實際使用的模型
model 代表這次 response 實際使用的模型。
例如:
{
"model": "gpt-5.5"
}
Python 讀取:
print(completion.model)
這個欄位很重要,因為正式產品中你可能會:
- 根據環境切換模型。
- 根據使用者方案切換模型。
- 做 A/B testing。
- 使用 fallback model。
- 在不同功能使用不同模型。
例如:
completion = client.chat.completions.create(
model=CHAT_MODEL,
messages=messages,
)
log_record = {
"requested_model": CHAT_MODEL,
"response_model": completion.model,
}
實務上建議記錄 response 裡的 model,而不只是記錄你 request 中送出的模型名稱。
原因是:
- 某些系統可能會使用模型別名。
- 模型名稱可能映射到具體版本。
- 未來若有 fallback 或路由機制,response model 更能代表實際執行結果。
- 成本分析也應該盡量以實際模型為準。
M.6 choices:候選回答列表
choices 是模型產生的候選回答列表。
例如:
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Chat Completions API 是一個讓開發者傳入對話訊息,並取得模型回覆的介面。"
},
"logprobs": null,
"finish_reason": "stop"
}
]
}
一般情況下,choices 只有一個元素,所以我們通常讀:
completion.choices[0]
如果你設定了 n 大於 1,API 可能會回傳多個候選回答:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請幫我想一句產品標語。"
}
],
n=3,
)
這時候可以逐一讀取:
for choice in completion.choices:
print(choice.index)
print(choice.message.content)
要注意的是,n 會增加生成 token 數與成本。
如果只是希望模型在同一則回答中列出多個選項,很多時候不需要用 n,直接在 prompt 裡要求「請列出 5 個選項」即可。
M.7 choices[].index:候選回答的索引
每個 choice 都有 index,代表它在 choices 陣列中的位置。
例如:
{
"index": 0
}
如果 n=1,通常只會有:
index = 0
如果 n=3,可能會有:
index = 0
index = 1
index = 2
Python 讀取:
for choice in completion.choices:
print(choice.index)
index 的主要用途是讓你知道目前處理的是第幾個候選回答。
在一般聊天應用中,通常不太需要特別使用它。
但如果你開啟 n,或需要保存多個候選結果,index 就會有用。
M.8 choices[].message:模型產生的 assistant message
message 是 Chat Completion response 裡最重要的部分,因為它就是模型這次產生的 assistant message。
例如:
{
"message": {
"role": "assistant",
"content": "Chat Completions API 是一個讓開發者傳入對話訊息,並取得模型回覆的介面。"
}
}
Python 讀取:
message = completion.choices[0].message
print(message.role)
print(message.content)
一般文字回答時,最常用的是:
completion.choices[0].message.content
如果你要把這次 assistant 回覆加入多輪對話,也可以把它存回 messages:
assistant_message = completion.choices[0].message
messages.append({
"role": "assistant",
"content": assistant_message.content
})
這樣下一次呼叫 API 時,模型就能看到前一輪回答。
不過,如果這次 message 包含 tool_calls,就不能只存 content。
你需要保留完整 assistant message,因為 tool call id、function name 和 arguments 都在 message 裡。
M.9 message.role:模型回覆的角色
在 Chat Completion response 中,模型產生的 message 通常是:
{
"role": "assistant"
}
也就是 assistant message。
Python 讀取:
print(completion.choices[0].message.role)
一般情況下,這個值會是:
assistant
這和 request 裡的 messages 結構一致。
你傳入:
{
"role": "user",
"content": "請解釋 Chat Completions API。"
}
模型回傳:
{
"role": "assistant",
"content": "Chat Completions API 是一個..."
}
這也是多輪對話的基礎:每次模型回覆後,你可以把 assistant message 加回 messages,下一輪再接新的 user message。
M.10 message.content:模型回覆文字
message.content 是最常使用的欄位。
例如:
answer = completion.choices[0].message.content
print(answer)
這通常是模型產生的文字回答。
例如:
Chat Completions API 是一個讓開發者把對話訊息傳給模型,並取得 AI 回覆的介面。
如果你使用一般聊天、摘要、翻譯、改寫、文章生成,通常主要就是讀這個欄位。
但有幾個情境要注意。
第一,如果模型產生 tool calls,message.content 可能是 null 或空值,因為模型這一輪不是要直接回答,而是要呼叫工具。
例如:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Taipei\"}"
}
}
]
}
第二,如果你使用 audio output,message 可能還會包含 audio 相關資料。
第三,如果你使用 refusal 或安全相關情境,message 也可能包含 refusal 類欄位。不同 SDK 版本與模型可能呈現略有差異,因此正式產品中不應該假設 message.content 永遠有值。
比較安全的寫法是:
message = completion.choices[0].message
if message.content:
print(message.content)
elif getattr(message, "tool_calls", None):
print("模型要求呼叫工具")
else:
print("這次沒有一般文字內容")
M.11 message.tool_calls:模型要求呼叫工具
如果你在 request 中提供了 tools,模型可能會回傳 tool_calls。
例如:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Taipei\"}"
}
}
]
}
Python 讀取:
message = completion.choices[0].message
if message.tool_calls:
for tool_call in message.tool_calls:
print(tool_call.id)
print(tool_call.function.name)
print(tool_call.function.arguments)
這裡要記住:
- tool_calls 代表模型要求你的程式呼叫工具。
- 模型不會真的執行工具。
- function.arguments 是 JSON 字串,需要你自己 parse。
- 執行工具前仍要做 schema validation、權限檢查與商業邏輯驗證。
- 執行完工具後,要用 tool message 把結果回填給模型。
例如:
import json
tool_call = message.tool_calls[0]
arguments = json.loads(tool_call.function.arguments)
result = get_weather(**arguments)
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
tool_call.id 很重要,因為回填 tool result 時必須用它來對應是哪一次工具呼叫。
M.12 choices[].logprobs:token 機率資訊
如果 request 中設定:
logprobs=True
response 的 choice 裡可能會包含 logprobs。
例如:
{
"logprobs": {
"content": [
{
"token": "yes",
"logprob": -0.02,
"bytes": [121, 101, 115]
}
]
}
}
Python 讀取:
print(completion.choices[0].logprobs)
如果你沒有設定 logprobs=True,這個欄位通常會是 null。
logprobs 適合用在:
- 短答案分類
- yes / no 判斷
- 候選答案比較
- 模型信心分析
- prompt 調校
不適合用在:
- 一般聊天
- 長篇文章
- 開放式自然語言回答
因為長篇回答會有很多 token,逐 token 分析不一定容易轉換成產品價值。
這部分已經在前一章介紹過,這裡只需要知道:logprobs 是掛在每個 choice 底下的欄位,不是掛在最外層 completion object 上。
M.13 choices[].finish_reason:模型停止原因
finish_reason 代表模型為什麼停止生成。
例如:
{
"finish_reason": "stop"
}
Python 讀取:
finish_reason = completion.choices[0].finish_reason
print(finish_reason)
常見值包括:
| finish_reason | 說明 |
|---|---|
| stop | 模型自然停止,或遇到 stop sequence |
| length | 達到 token 上限而停止 |
| tool_calls | 模型產生工具呼叫,需要你執行工具流程 |
| content_filter | 因內容過濾而停止 |
| function_call | 舊版 function calling,相容性用法 |
finish_reason 對除錯非常重要。
例如:
choice = completion.choices[0]
if choice.finish_reason == "stop":
print("正常結束")
elif choice.finish_reason == "length":
print("回答可能被 max_completion_tokens 截斷")
elif choice.finish_reason == "tool_calls":
print("模型要求呼叫工具")
elif choice.finish_reason == "content_filter":
print("內容可能被安全系統過濾")
如果你發現使用者常回報「回答到一半就停了」,可以先檢查 finish_reason 是否是:
length
如果是,可能需要提高 max_completion_tokens,或縮短 prompt。
如果 finish_reason 是:
tool_calls
代表這一輪不是最終回答,而是模型在要求工具呼叫。你需要進入 Tool Calling 流程,而不是直接把 message.content 顯示給使用者。
M.14 usage:token 使用量
usage 是成本分析與用量追蹤中最重要的欄位之一。
例如:
{
"usage": {
"prompt_tokens": 20,
"completion_tokens": 30,
"total_tokens": 50
}
}
Python 讀取:
usage = completion.usage
print(usage.prompt_tokens)
print(usage.completion_tokens)
print(usage.total_tokens)
常見欄位包括:
| 欄位 | 說明 |
|---|---|
| prompt_tokens | 輸入 messages 與相關上下文消耗的 token |
| completion_tokens | 模型輸出消耗的 token |
| total_tokens | 總 token 數,通常是 prompt + completion |
| prompt_tokens_details | 輸入 token 的細部分類,若模型或回應支援 |
| completion_tokens_details | 輸出 token 的細部分類,若模型或回應支援 |
usage 的用途包括:
- 成本估算
- 使用者額度管理
- 功能成本分析
- prompt 優化
- 模型比較
- 異常用量偵測
例如:
usage = completion.usage
log_record = {
"prompt_tokens": usage.prompt_tokens if usage else None,
"completion_tokens": usage.completion_tokens if usage else None,
"total_tokens": usage.total_tokens if usage else None,
}
正式產品中,建議至少記錄:
- model
- prompt_tokens
- completion_tokens
- total_tokens
- feature
- user
- created_at
- latency_ms
這樣你才能知道每個 AI 功能實際花了多少成本。
M.15 prompt_tokens_details 與 completion_tokens_details
在部分模型或回應中,usage 可能還會包含更細的 token 使用資訊。
例如:
{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 80,
"total_tokens": 200,
"prompt_tokens_details": {
"cached_tokens": 40
},
"completion_tokens_details": {
"reasoning_tokens": 20,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
}
}
這些細項可能包括:
| 欄位 | 說明 |
|---|---|
| cached_tokens | 可能代表使用 prompt caching 的 token 數 |
| reasoning_tokens | reasoning models 內部推理消耗的 token |
| accepted_prediction_tokens | prediction 相關接受 token,若支援 |
| rejected_prediction_tokens | prediction 相關拒絕 token,若支援 |
這些欄位是否出現,會依模型、API 功能與請求參數而不同。
因此寫程式時不要假設它們一定存在。
比較安全的寫法是:
usage = completion.usage
if usage:
print("total:", usage.total_tokens)
prompt_details = getattr(usage, "prompt_tokens_details", None)
completion_details = getattr(usage, "completion_tokens_details", None)
if prompt_details:
print("prompt details:", prompt_details)
if completion_details:
print("completion details:", completion_details)
實務上,如果你只是做基本成本統計,先記錄 prompt_tokens、completion_tokens、total_tokens 就夠了。
如果你正在使用 reasoning models、prompt caching 或進階效能分析,再進一步記錄 details。
M.16 system_fingerprint:後端系統指紋
system_fingerprint 代表後端系統設定的指紋。
例如:
{
"system_fingerprint": "fp_xxx"
}
Python 讀取:
print(completion.system_fingerprint)
這個欄位通常會和 seed 搭配,用於分析可重現性。
例如,你使用相同的:
- model
- messages
- temperature
- seed
- 其他參數
但兩次輸出不同,就可以比較兩次的 system_fingerprint。
如果兩次 system_fingerprint 不同,代表後端設定可能變動,因此輸出差異比較合理。
概念上:
| seed | system_fingerprint | 輸出差異解讀 |
|---|---|---|
| 相同 | 相同 | 較有機會得到接近結果 |
| 相同 | 不同 | 後端設定可能變動 |
| 不同 | 相同 | 取樣條件不同 |
| 不同 | 不同 | 結果不同很正常 |
正式測試或 prompt regression test 中,建議記錄 system_fingerprint。
M.17 service_tier:實際使用的服務層級
service_tier 代表這次請求實際使用的服務層級。
例如:
{
"service_tier": "default"
}
Python 讀取:
print(completion.service_tier)
可用值可能包括:
auto
default
flex
scale
priority
不過要注意:response 裡的 service_tier 代表實際處理這次請求的服務層級,可能和 request 裡指定的值不完全相同。
例如你 request 設定:
service_tier="auto"
response 可能顯示:
default
這代表系統依照 Project 設定或可用性,實際使用了 default tier。
如果你要做成本或效能分析,應該記錄 response 回傳的 service_tier,而不是只記錄 request 裡送出的設定。
例如:
log_record = {
"service_tier_used": completion.service_tier,
"model": completion.model,
"usage": completion.usage,
}
M.18 refusal 與 annotations:特殊回覆資訊
在部分回應中,assistant message 可能還會包含 refusal、annotations 等欄位。
例如,某些情況下 message 可能長這樣:
{
"role": "assistant",
"content": "...",
"refusal": null,
"annotations": []
}
這些欄位會依模型、輸出類型、安全情境或 SDK 版本而有所不同。
可以先這樣理解:
| 欄位 | 說明 |
|---|---|
| refusal | 模型拒絕回答時可能出現的資訊 |
| annotations | 回覆中的附加標註資訊,依功能與模型支援而定 |
在一般聊天範例中,你通常不需要特別處理它們。
但正式產品中,如果你有處理安全回覆、引用、註解或特殊輸出,就應該保留對這些欄位的彈性。
比較安全的原則是:
不要假設 message 只會有 role 和 content。
實務程式應該能處理 tool_calls、audio、refusal、annotations 等可能欄位。
M.19 實務範例:把 Chat Completion response 轉成 log
以下是一個把 response 轉成 request log 的簡化範例:
import time
from openai import OpenAI
client = OpenAI()
def ask(messages: list, user_id: str, feature: str):
started_at = time.perf_counter()
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
user=user_id,
metadata={
"feature": feature,
"environment": "production"
},
service_tier="auto",
)
latency_ms = int((time.perf_counter() - started_at) * 1000)
choice = completion.choices[0]
usage = completion.usage
log_record = {
"completion_id": completion.id,
"object": completion.object,
"created": completion.created,
"model": completion.model,
"service_tier": getattr(completion, "service_tier", None),
"system_fingerprint": getattr(completion, "system_fingerprint", None),
"finish_reason": choice.finish_reason,
"prompt_tokens": usage.prompt_tokens if usage else None,
"completion_tokens": usage.completion_tokens if usage else None,
"total_tokens": usage.total_tokens if usage else None,
"latency_ms": latency_ms,
"feature": feature,
"user_id": user_id,
}
print(log_record)
return choice.message.content
這個範例會記錄:
- completion id
- object type
- created timestamp
- model
- service tier
- system fingerprint
- finish reason
- token usage
- latency
- feature
- user id
這些資訊對正式產品的除錯與成本分析都很有用。
M.20 實務範例:安全讀取模型回答或工具呼叫
正式產品中,不建議直接假設:
python id=”ds2n7j” completion.choices[0].message.content
一定有值。
比較穩妥的處理方式是:
import json
choice = completion.choices[0]
message = choice.message
if choice.finish_reason == "tool_calls" and message.tool_calls:
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print("需要呼叫工具:", function_name)
print("工具參數:", arguments)
elif message.content:
print("模型回答:", message.content)
elif getattr(message, "refusal", None):
print("模型拒絕回答:", message.refusal)
else:
print("沒有可直接顯示的文字內容")
這樣可以同時處理:
- 一般文字回答
- 工具呼叫
- refusal
- 沒有 content 的特殊情境
這在 Tool Calling、結構化輸出、多模態與安全情境中都比較穩健。
M.21 Chat Completion 回傳物件小結
整理一下,非串流模式下,API 回傳的是 chat.completion object。
最常見的讀取方式是:
completion.choices[0].message.content
但正式產品中,還應該理解其他欄位:
| 欄位 | 作用 |
|---|---|
| id | 追蹤這次 completion |
| object | 物件類型,通常是 chat.completion |
| created | 建立時間 |
| model | 實際使用的模型 |
| choices | 候選回答列表 |
| choices[].message | 模型產生的 assistant message |
| choices[].message.content | 模型回覆文字 |
| choices[].message.tool_calls | 模型要求呼叫的工具 |
| choices[].logprobs | token 機率資訊,需設定 logprobs=True |
| choices[].finish_reason | 停止原因 |
| usage | token 使用量 |
| system_fingerprint | 後端系統指紋 |
| service_tier | 實際使用的服務層級 |
實務建議:
- 一般文字回答:讀 completion.choices[0].message.content。
- Tool Calling:檢查 message.tool_calls 和 finish_reason。
- 成本分析:記錄 usage。
- 除錯:記錄 id、model、finish_reason、system_fingerprint。
- 效能分析:記錄 service_tier 與 latency。
- 多候選回答:遍歷 choices。
- 正式產品:不要假設 message.content 永遠有值。
理解 chat.completion object 後,下一章會看串流模式下的回傳物件,也就是 chat.completion.chunk。串流模式和非串流模式最大的差異是:非串流一次回傳完整 message,串流則會分多次回傳 delta。
N. Streaming Chunk 回傳物件解析
前一章介紹的是非串流模式下的 chat.completion object。
當你沒有設定 stream=True 時,API 會等模型產生完整回答後,一次回傳完整物件。你通常會讀:
completion.choices[0].message.content
但如果啟用串流:
stream=True
API 就不會一次回傳完整回答,而是分多次回傳 chat.completion.chunk。
這時候你讀的就不是:
message.content
而是每個 chunk 裡的:
delta.content
最基本的 streaming 範例:
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請用三句話介紹 Chat Completions API。"
}
],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
這段程式會一邊收到模型輸出的文字片段,一邊印出來。
N.1 chat.completion.chunk 總覽
啟用 streaming 後,每次收到的 chunk 大致會像這樣:
{
"id": "chatcmpl_xxx",
"object": "chat.completion.chunk",
"created": 1710000000,
"model": "gpt-5.5",
"choices": [
{
"index": 0,
"delta": {
"content": "Chat"
},
"finish_reason": null
}
],
"usage": null,
"service_tier": "default",
"system_fingerprint": "fp_xxx"
}
Streaming chunk 常見欄位包括:
| 欄位 | 說明 |
|---|---|
id | 這次 Chat Completion 的識別碼,通常同一個 stream 中相同 |
object | 物件類型,通常是 chat.completion.chunk |
created | 建立時間,Unix timestamp |
model | 使用的模型 |
choices | 這次 chunk 的候選片段 |
choices[].delta | 這次 chunk 新增的內容 |
choices[].finish_reason | 停止原因,通常最後才出現 |
usage | token 使用量,通常只有啟用 stream_options.include_usage 後的 final chunk 會有 |
service_tier | 實際使用的服務層級,若回應提供 |
system_fingerprint | 後端系統設定指紋,若回應提供 |
其中最常用的是:
chunk.choices[0].delta.content
但正式產品中不能假設每個 chunk 都一定有 choices[0].delta.content。
有些 chunk 可能只包含 role,有些可能包含 tool call 片段,有些可能包含 finish reason,有些 final usage chunk 甚至可能沒有 choices。
N.2 id:同一串流中的 completion 識別碼
每個 chunk 都會帶有 id。
例如:
{
"id": "chatcmpl_xxx"
}
同一次 streaming response 中,多個 chunk 通常會共用同一個 id,代表它們都屬於同一次 Chat Completion。
Python 讀取:
for chunk in stream:
print(chunk.id)
id 可以用來:
- 追蹤同一次 streaming response。
- 在後端 log 中關聯多個 chunk。
- 對照使用者回報的特定請求。
- 和非串流 completion id 一樣,用於除錯與紀錄。
實務上,你通常不會把每個 chunk 都寫入資料庫,但可以在收到第一個 chunk 時記錄 id:
completion_id = None
for chunk in stream:
if completion_id is None:
completion_id = chunk.id
print("completion id:", completion_id)
N.3 object:物件類型
Streaming chunk 的 object 通常是:
chat.completion.chunk
例如:
{
"object": "chat.completion.chunk"
}
這和非串流模式的:
chat.completion
不同。
可以用這張表理解:
| 模式 | object |
|---|---|
| 非串流 | chat.completion |
| 串流 | chat.completion.chunk |
如果你的程式同時支援 streaming 與 non-streaming,可以用 object 幫助判斷目前處理的是哪種 response。
N.4 created:建立時間
created 是這次 chunk 的建立時間,格式是 Unix timestamp,單位是秒。
例如:
{
"created": 1710000000
}
Python 讀取:
print(chunk.created)
同一次 streaming response 中,不同 chunk 的 created 通常會相同或接近。
在大多數應用中,你不會逐一使用每個 chunk 的 created,而是會在自己的系統中記錄:
- streaming 開始時間
- 第一個 token 到達時間
- streaming 結束時間
- 總 latency
- time to first token
對聊天 UI 來說,time to first token 很有價值,因為它更接近使用者感受到的等待時間。
例如:
import time
started_at = time.perf_counter()
first_token_at = None
parts = []
for chunk in stream:
if not chunk.choices:
continue
content = chunk.choices[0].delta.content
if content:
if first_token_at is None:
first_token_at = time.perf_counter()
parts.append(content)
ended_at = time.perf_counter()
metrics = {
"time_to_first_token_ms": int((first_token_at - started_at) * 1000) if first_token_at else None,
"total_latency_ms": int((ended_at - started_at) * 1000),
}
這比只記錄整體請求時間更能反映 streaming 體驗。
N.5 model:使用的模型
每個 chunk 通常也會帶有 model。
例如:
{
"model": "gpt-5.5"
}
Python 讀取:
print(chunk.model)
同一次 streaming response 中,model 通常相同。
實務上,你可以在第一個 chunk 記錄使用模型:
model_used = None
for chunk in stream:
if model_used is None:
model_used = chunk.model
正式產品建議記錄 response 裡實際回傳的模型名稱,而不是只記錄 request 中指定的模型。這樣在模型別名、fallback 或路由情境中比較準確。
N.6 choices:chunk 的候選片段
Streaming chunk 裡的 choices 是這次 chunk 的候選片段列表。
例如:
{
"choices": [
{
"index": 0,
"delta": {
"content": "Chat"
},
"finish_reason": null
}
]
}
一般情況下,如果沒有設定 n,通常只會有一個 choice,也就是:
chunk.choices[0]
但處理時仍建議先檢查 choices 是否存在:
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
為什麼要這樣寫?
因為如果你設定了:
stream_options={
"include_usage": True
}
streaming 結束時可能會收到一個 final usage chunk。這個 chunk 可能包含 usage,但 choices 是空陣列。
如果你的程式直接讀:
chunk.choices[0]
就可能出錯。
因此 streaming 的安全處理原則是:
if not chunk.choices:
# 可能是 final usage chunk,或其他沒有 choices 的 chunk
continue
N.7 choices[].index:候選片段索引
每個 choice 裡的 index 代表它屬於第幾個候選回答。
例如:
{
"index": 0
}
如果你沒有設定 n,通常只會有:
index = 0
如果你設定 n=2 並且使用 streaming,可能會收到不同 index 的片段。
這時候你需要分別累積不同 choice 的內容。
概念上:
parts_by_index = {}
for chunk in stream:
if not chunk.choices:
continue
for choice in chunk.choices:
index = choice.index
delta = choice.delta
if index not in parts_by_index:
parts_by_index[index] = []
if delta.content:
parts_by_index[index].append(delta.content)
一般聊天 UI 通常不會搭配 n > 1 使用 streaming,因為同時串流多個候選回答會讓前端與後端處理變複雜,也會增加成本。
實務建議:
- 一般 streaming UI:維持
n=1。 - 需要多候選回答:考慮非串流模式,或讓模型在單一回答中列出多個候選。
- 若真的使用
n > 1,要依choice.index分開累積文字。
N.8 choices[].delta:chunk 新增的內容
delta 是 streaming chunk 中最核心的欄位。
非串流模式下,模型回傳的是完整 message:
completion.choices[0].message.content
串流模式下,模型每次回傳的是增量內容,也就是:
chunk.choices[0].delta
delta 可能包含:
rolecontenttool_calls- 其他依模型與輸出型態而定的欄位
例如第一個 chunk 可能包含 role:
{
"delta": {
"role": "assistant",
"content": ""
}
}
後續 chunk 可能包含 content:
{
"delta": {
"content": "Chat"
}
}
再下一個 chunk:
{
"delta": {
"content": " Completions"
}
}
最後可能是空 delta 搭配 finish reason:
{
"delta": {},
"finish_reason": "stop"
}
所以處理 streaming 時,要記住:
delta 是這次新增的片段,不是完整 message。
你需要把多個 delta.content 串起來,才會得到完整回答。
N.9 delta.role:assistant 角色片段
有些 stream 的第一個 chunk 可能會包含:
{
"delta": {
"role": "assistant"
}
}
這表示這個 streaming response 正在產生 assistant message。
Python 讀取:
role = chunk.choices[0].delta.role
在一般文字串流中,你通常不需要特別處理 delta.role。
但如果你想在後端重建完整 assistant message,可以保存它:
assistant_role = None
parts = []
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if delta.role:
assistant_role = delta.role
if delta.content:
parts.append(delta.content)
message = {
"role": assistant_role or "assistant",
"content": "".join(parts)
}
這樣你就能把 streaming 片段組成一個完整的 assistant message。
N.10 delta.content:文字片段
delta.content 是 streaming 模式下最常用的欄位。
例如:
{
"delta": {
"content": "Chat"
}
}
Python 讀取:
content = chunk.choices[0].delta.content
每次收到 delta.content,就把它追加到目前回答中:
parts = []
for chunk in stream:
if not chunk.choices:
continue
content = chunk.choices[0].delta.content
if content:
parts.append(content)
print(content, end="")
full_text = "".join(parts)
在前端 UI 中也是同樣概念:
assistantMessage += content;
render(assistantMessage);
要注意:
delta.content可能是空字串。delta.content可能不存在。- 有些 chunk 可能是 role、tool call、finish reason 或 usage,不含文字。
- 因此不要假設每個 chunk 都有可顯示文字。
比較安全的 Python 寫法:
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
content = getattr(delta, "content", None)
if content:
print(content, end="")
N.11 choices[].finish_reason:停止原因
在 streaming 模式下,finish_reason 通常一開始是 null,直到最後才會出現。
例如中間 chunk:
{
"finish_reason": null
}
最後 chunk:
{
"finish_reason": "stop"
}
Python 讀取:
finish_reason = None
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
if choice.finish_reason:
finish_reason = choice.finish_reason
print("finish_reason:", finish_reason)
常見值包括:
| finish_reason | 說明 |
|---|---|
stop | 模型自然停止,或遇到 stop sequence |
length | 達到 token 上限而停止 |
tool_calls | 模型產生工具呼叫 |
content_filter | 因內容過濾而停止 |
function_call | 舊版 function calling,相容性用法 |
finish_reason 對 streaming 特別重要,因為你可以用它判斷後續流程。
例如:
if finish_reason == "stop":
print("串流正常結束")
elif finish_reason == "length":
print("回答可能被 token 上限截斷")
elif finish_reason == "tool_calls":
print("需要執行工具呼叫流程")
elif finish_reason == "content_filter":
print("內容可能被安全系統過濾")
如果 finish_reason="length",你可能需要調整 max_completion_tokens。
如果 finish_reason="tool_calls",代表這一輪 stream 不是最終文字回答,而是工具呼叫。
N.12 usage:串流模式下的 token 使用量
非串流模式下,usage 通常會直接出現在完整 response object 中。
串流模式下,預設情況通常不會在每個 chunk 都提供完整 usage。
如果你想在 streaming 結束時取得 token 使用量,需要設定:
stream_options={
"include_usage": True
}
範例:
stream = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請用三句話介紹 Chat Completions API。"
}
],
stream=True,
stream_options={
"include_usage": True
},
)
parts = []
usage = None
for chunk in stream:
if chunk.usage:
usage = chunk.usage
continue
if not chunk.choices:
continue
content = chunk.choices[0].delta.content
if content:
parts.append(content)
print(content, end="")
print("\nusage:", usage)
啟用 include_usage 後,通常會在串流結束前或結束時收到一個額外的 usage chunk。
這個 chunk 的特徵通常是:
usage有值。choices可能是空陣列。- 它不包含新的
delta.content。 - 如果 stream 被中斷,可能收不到這個 usage chunk。
因此處理時應該先檢查:
if chunk.usage:
usage = chunk.usage
continue
再檢查:
if not chunk.choices:
continue
N.13 final usage chunk:最後的用量片段
final usage chunk 可以簡化理解成:
{
"id": "chatcmpl_xxx",
"object": "chat.completion.chunk",
"created": 1710000000,
"model": "gpt-5.5",
"choices": [],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 30,
"total_tokens": 50
}
}
這和一般內容 chunk 不同:
一般內容 chunk:
{
"choices": [
{
"delta": {
"content": "Chat"
},
"finish_reason": null
}
],
"usage": null
}
final usage chunk:
{
"choices": [],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 30,
"total_tokens": 50
}
}
所以如果你的程式這樣寫:
for chunk in stream:
print(chunk.choices[0].delta.content)
就可能在 final usage chunk 出錯,因為 choices 是空的。
比較安全:
for chunk in stream:
if chunk.usage:
usage = chunk.usage
continue
if not chunk.choices:
continue
content = chunk.choices[0].delta.content
if content:
print(content, end="")
如果你的產品很依賴 token usage,要注意串流中斷時可能拿不到 final usage chunk。
例如使用者關閉頁面、網路中斷或前端取消請求時,final usage chunk 可能不會抵達。
N.14 system_fingerprint 與 service_tier
Streaming chunk 也可能包含:
system_fingerprintservice_tier
例如:
{
"system_fingerprint": "fp_xxx",
"service_tier": "default"
}
這些欄位和非串流模式的用途類似。
system_fingerprint 可以用於可重現性分析,尤其搭配 seed 時:
system_fingerprint = None
for chunk in stream:
if system_fingerprint is None and chunk.system_fingerprint:
system_fingerprint = chunk.system_fingerprint
service_tier 則代表實際使用的服務層級:
service_tier_used = None
for chunk in stream:
if service_tier_used is None and chunk.service_tier:
service_tier_used = chunk.service_tier
正式產品中,你可以把這些值和 usage、latency 一起寫入 log。
N.15 把 streaming chunks 組成完整 assistant message
串流模式下,如果你要保存完整對話歷史,通常需要把 chunks 組合成一則 assistant message。
簡化範例:
stream = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
stream=True,
)
assistant_role = "assistant"
parts = []
finish_reason = None
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
delta = choice.delta
if delta.role:
assistant_role = delta.role
if delta.content:
parts.append(delta.content)
if choice.finish_reason:
finish_reason = choice.finish_reason
assistant_message = {
"role": assistant_role,
"content": "".join(parts)
}
messages.append(assistant_message)
這樣你就能把 streaming 回覆存回 messages,支援多輪對話。
如果你只是在 UI 上顯示文字,可以不一定要保存完整 message。
但如果你的對話需要持續上下文,建議還是要把最終 assistant message 存起來。
N.16 streaming tool calls:工具呼叫片段
如果同時使用:
stream=True
以及:
tools=tools
模型產生的工具呼叫也可能透過 chunk 分段回傳。
非串流模式下,tool call 可能一次完整出現在:
completion.choices[0].message.tool_calls
但串流模式下,tool call 的資訊可能分散在多個 chunk 的:
chunk.choices[0].delta.tool_calls
例如第一個 chunk 可能包含 tool call id 和 function name:
{
"delta": {
"tool_calls": [
{
"index": 0,
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": ""
}
}
]
}
}
後續 chunk 可能逐步傳回 arguments:
{
"delta": {
"tool_calls": [
{
"index": 0,
"function": {
"arguments": "{\"city\""
}
}
]
}
}
下一個 chunk:
{
"delta": {
"tool_calls": [
{
"index": 0,
"function": {
"arguments": ":\"Taipei\"}"
}
}
]
}
}
最後 finish_reason 可能是:
{
"finish_reason": "tool_calls"
}
也就是說,streaming tool calls 需要你自己累積工具呼叫資料,等完成後再解析 arguments。
N.17 累積 streaming tool calls 的概念範例
以下是一個簡化概念,用來說明如何累積 streaming tool calls。
import json
tool_calls_by_index = {}
finish_reason = None
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
delta = choice.delta
if delta.tool_calls:
for tool_call_delta in delta.tool_calls:
index = tool_call_delta.index
if index not in tool_calls_by_index:
tool_calls_by_index[index] = {
"id": None,
"type": None,
"function": {
"name": None,
"arguments": ""
}
}
current = tool_calls_by_index[index]
if tool_call_delta.id:
current["id"] = tool_call_delta.id
if tool_call_delta.type:
current["type"] = tool_call_delta.type
if tool_call_delta.function:
if tool_call_delta.function.name:
current["function"]["name"] = tool_call_delta.function.name
if tool_call_delta.function.arguments:
current["function"]["arguments"] += tool_call_delta.function.arguments
if choice.finish_reason:
finish_reason = choice.finish_reason
if finish_reason == "tool_calls":
for index, tool_call in tool_calls_by_index.items():
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
print(function_name, arguments)
這只是概念範例,正式產品中還需要處理:
- 多個 choices
- 多個 tool calls
- arguments JSON parse error
- function name 不存在
- tool call id 缺失
- 權限檢查
- schema validation
- tool 執行錯誤
- parallel tool calls
如果你剛開始做 Tool Calling,建議先用非串流模式把流程做穩。
等你熟悉 tool calls 的完整流程後,再加入 streaming tool call 支援。
N.18 streaming response 的安全處理模板
一個比較穩健的文字 streaming 處理模板可以長這樣:
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
stream=True,
stream_options={
"include_usage": True
},
)
parts = []
usage = None
finish_reason = None
completion_id = None
model_used = None
system_fingerprint = None
service_tier_used = None
for chunk in stream:
if completion_id is None:
completion_id = chunk.id
if model_used is None:
model_used = chunk.model
if system_fingerprint is None and getattr(chunk, "system_fingerprint", None):
system_fingerprint = chunk.system_fingerprint
if service_tier_used is None and getattr(chunk, "service_tier", None):
service_tier_used = chunk.service_tier
if chunk.usage:
usage = chunk.usage
continue
if not chunk.choices:
continue
choice = chunk.choices[0]
delta = choice.delta
if delta.content:
parts.append(delta.content)
print(delta.content, end="")
if choice.finish_reason:
finish_reason = choice.finish_reason
full_text = "".join(parts)
log_record = {
"completion_id": completion_id,
"model": model_used,
"finish_reason": finish_reason,
"usage": usage,
"system_fingerprint": system_fingerprint,
"service_tier": service_tier_used,
}
這個模板會處理:
- 文字片段累積
- final usage chunk
- empty choices
- finish reason
- completion id
- model
- system fingerprint
- service tier
比起只寫:
for chunk in stream:
print(chunk.choices[0].delta.content)
更適合正式產品。
N.19 前端處理 streaming chunk 的概念
後端通常會把 OpenAI streaming response 轉成自己的 streaming response,再傳給前端。
常見方式包括:
- Server-Sent Events
- WebSocket
- HTTP chunked response
- 框架內建 streaming response
前端收到文字片段後,通常會做:
let assistantMessage = "";
function onToken(content) {
assistantMessage += content;
renderAssistantMessage(assistantMessage);
}
如果後端會傳 metadata,例如 completion id、usage 或 finish reason,前端也可以在串流結束後更新訊息狀態。
概念上:
const message = {
role: "assistant",
content: "",
status: "streaming",
usage: null,
};
for await (const event of stream) {
if (event.type === "content") {
message.content += event.content;
render(message);
}
if (event.type === "usage") {
message.usage = event.usage;
}
if (event.type === "done") {
message.status = "done";
message.finishReason = event.finish_reason;
}
}
正式聊天 UI 通常還要處理:
- 使用者取消生成
- 連線中斷
- 錯誤訊息
- partial message
- 重新生成
- 滾動到底部
- markdown 增量渲染
- code block 尚未閉合
- 工具呼叫中的 loading 狀態
Streaming 看起來只是「一段一段顯示文字」,但真正做成產品時,UI 狀態管理會比非串流複雜許多。
N.20 streaming chunk 常見錯誤
錯誤一:假設每個 chunk 都有 choices[0]
final usage chunk 可能沒有 choices。
所以應該先檢查:
if not chunk.choices:
continue
錯誤二:假設每個 chunk 都有 delta.content
有些 chunk 可能只有 role、finish reason、tool calls 或 usage。
所以應該檢查:
if delta.content:
...
錯誤三:沒有處理 finish_reason
如果不記錄 finish_reason,你可能不知道回答是正常停止、被截斷,還是產生了工具呼叫。
錯誤四:串流中斷後仍假設有完整回答
使用者取消、網路中斷或伺服器錯誤時,可能只收到部分內容。
應該標記 partial message 或 incomplete 狀態。
錯誤五:以為 streaming tool calls 會一次完整回傳
Tool call arguments 可能分多個 chunk 回傳,必須累積後再 parse JSON。
錯誤六:沒有處理 final usage chunk
如果啟用 stream_options.include_usage,要特別處理 final usage chunk,否則可能在 choices[0] 取值時出錯。
N.21 Streaming Chunk 回傳物件小結
整理一下,串流模式下,API 回傳的是一連串 chat.completion.chunk。
最常見的讀取方式是:
chunk.choices[0].delta.content
但正式產品中,應該理解更多欄位:
| 欄位 | 作用 |
|---|---|
id | 同一次 streaming completion 的識別碼 |
object | 物件類型,通常是 chat.completion.chunk |
created | 建立時間 |
model | 實際使用的模型 |
choices | 本次 chunk 的候選片段 |
choices[].index | 候選片段索引 |
choices[].delta | 本次新增內容 |
delta.role | assistant 角色片段 |
delta.content | 本次新增文字 |
delta.tool_calls | 工具呼叫片段 |
choices[].finish_reason | 停止原因,通常最後出現 |
usage | token 使用量,通常在 final usage chunk 出現 |
system_fingerprint | 後端系統指紋 |
service_tier | 實際使用的服務層級 |
非串流與串流最大的差異是:
| 模式 | 讀取方式 | 說明 |
|---|---|---|
| 非串流 | completion.choices[0].message.content | 一次取得完整回答 |
| 串流 | chunk.choices[0].delta.content | 分段取得新增內容 |
實務建議:
- 一般聊天 UI 適合使用 streaming。
- 後端資料抽取或分類通常不需要 streaming。
- 開啟
stream_options.include_usage時,要處理 final usage chunk。 - 不要假設每個 chunk 都有
choices[0]。 - 不要假設每個 chunk 都有
delta.content。 - 要記錄
finish_reason,方便判斷是否正常結束。 - 若使用 streaming tool calls,要累積
delta.tool_calls後再 parse。 - 串流中斷時,應該標記 partial 或 incomplete 狀態。
理解 chat.completion.chunk 後,下一章會整理已棄用或相容性參數,例如 max_tokens、functions、function_call 與 functionrole。這些內容在舊文章或舊專案中仍然常見,但新專案應該優先使用較新的參數與訊息格式。
O. 已棄用或相容性參數整理
如果你查過早期的 Chat Completions API 教學,或維護過 2023、2024 年左右寫的 OpenAI API 程式碼,很可能會看到一些現在已經不建議新專案優先使用的參數或寫法。
常見例子包括:
max_tokensfunctionsfunction_callfunctionrole- 舊版
systemmessage 寫法 - 舊版
function_callresponse 欄位
這些內容不一定完全不能用,但在新的文章或新專案中,應該要把它們放在相容性或 legacy 段落,而不是當成主要推薦用法。
這一章的目的不是說舊寫法都錯,而是幫你建立一張新舊對照表:
舊專案看得懂,新專案不要再優先照抄。
O.1 為什麼需要整理 deprecated 與 legacy 用法?
OpenAI API 演進很快,尤其 Chat Completions API 經歷過幾次重要變化。
例如:
- 早期常用
systemmessage 放高層級指令。 - 早期工具呼叫使用
functions和function_call。 - 後來工具呼叫改成
tools和tool_choice。 - 舊版輸出長度控制常用
max_tokens。 - 新版更建議使用
max_completion_tokens。 - 新模型中,
developermessage 取代了部分過去systemmessage 的定位。
這些變化會造成一個問題:網路上的範例很多,但不一定都符合目前 API 文件的建議寫法。
你可能會看到舊範例這樣寫:
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "What's the weather today?"
}
],
functions=[
{
"name": "get_weather",
"description": "Get weather information.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"]
}
}
],
function_call="auto",
max_tokens=300,
)
這段程式在舊文章中很常見,但如果要寫新文章,就應該改成:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是一位有幫助的助理,請使用繁體中文回答。"
},
{
"role": "user",
"content": "台北今天的天氣如何?"
}
],
tools=[
{
"type": "function",
"function": {
"name": "get_weather",
"description": "取得指定城市的天氣資訊。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"],
"additionalProperties": False
}
}
}
],
tool_choice="auto",
max_completion_tokens=300,
)
這兩段程式的概念相近,但使用的是不同世代的 API 參數。
O.2 新舊對照總表
可以先用這張表快速理解:
| 舊用法 | 新建議用法 | 說明 |
|---|---|---|
max_tokens | max_completion_tokens | 控制模型最多生成多少 completion tokens |
functions | tools | 定義模型可使用的工具 |
function_call | tool_choice | 控制模型是否使用工具或指定工具 |
function role | tool role | 回填工具執行結果 |
message.function_call | message.tool_calls | 模型要求呼叫工具時的新欄位 |
system message | developer message | 新模型中開發者層級指令建議優先使用 developer |
max_tokens with o-series | 不建議 / 不相容 | o-series models 不相容 max_tokens,應使用 max_completion_tokens |
這張表可以放在文章中讓讀者快速對照舊程式與新寫法。
O.3 max_tokens:舊版輸出長度控制
max_tokens 是早期常見的輸出長度控制參數。
舊寫法:
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
max_tokens=300,
)
現在新專案應優先改用:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
max_completion_tokens=300,
)
max_completion_tokens 的用途是設定模型最多可以產生多少 completion tokens。
它和成本控制、延遲控制、避免輸出過長都有關。
兩者差異可以這樣理解:
| 參數 | 狀態 | 說明 |
|---|---|---|
max_tokens | deprecated | 舊版輸出 token 上限參數 |
max_completion_tokens | 新建議用法 | completion tokens 上限,包含部分模型的 visible output tokens 與 reasoning tokens |
要特別注意:max_tokens 不相容部分較新的 reasoning / o-series models。
如果你使用新模型卻仍傳入 max_tokens,可能會遇到錯誤或不支援情況。
因此新文章中,建議統一使用:
max_completion_tokens=...
不要再把 max_tokens 當成主要範例。
O.4 max_tokens 遷移範例
舊版:
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": "請用 300 字介紹 Chat Completions API。"
}
],
max_tokens=500,
)
新版:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請用 300 字介紹 Chat Completions API。"
}
],
max_completion_tokens=500,
)
如果你的專案中有很多地方使用 max_tokens,可以先做全域搜尋:
grep -R "max_tokens" .
然後逐步替換成:
max_completion_tokens
但不要只做字串替換。
建議同時檢查使用的模型是否有 reasoning tokens,因為對部分模型而言,max_completion_tokens 可能也會涵蓋模型內部推理 token,而不只是最後顯示給使用者的文字 token。
O.5 functions:舊版工具定義方式
早期 Chat Completions API 使用 functions 來定義模型可以呼叫的函式。
舊寫法:
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
functions=[
{
"name": "get_weather",
"description": "取得指定城市的天氣資訊。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"]
}
}
],
function_call="auto",
)
現在新專案應改用 tools:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=[
{
"type": "function",
"function": {
"name": "get_weather",
"description": "取得指定城市的天氣資訊。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"],
"additionalProperties": False
}
}
}
],
tool_choice="auto",
)
差異是:
舊版 functions | 新版 tools |
|---|---|
| 直接放 function schema | 每個 tool 都有 type,function schema 放在 function 底下 |
搭配 function_call | 搭配 tool_choice |
| 較早期 Function Calling 用法 | 較新的 Tool Calling 架構 |
| 工具類型較窄 | tools 架構可容納更廣泛工具類型 |
新文章中建議使用 tools,只在相容性段落提到 functions。
O.6 function_call:舊版工具選擇方式
function_call 是舊版控制模型是否呼叫函式的參數。
舊寫法:
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
functions=functions,
function_call="auto",
)
新版應改用:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
tool_choice="auto",
)
舊版強制呼叫某個 function:
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
functions=functions,
function_call={
"name": "get_weather"
},
)
新版強制呼叫某個 tool:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
tool_choice={
"type": "function",
"function": {
"name": "get_weather"
}
},
)
可以這樣對照:
| 需求 | 舊版 | 新版 |
|---|---|---|
| 由模型自行判斷 | function_call="auto" | tool_choice="auto" |
| 禁止呼叫工具 | function_call="none" | tool_choice="none" |
| 強制呼叫工具 | function_call={"name": "..."} | tool_choice={"type": "function", "function": {"name": "..."}} |
| 要求必須呼叫工具 | 舊版支援較有限 | tool_choice="required" |
新專案建議直接使用 tool_choice。
O.7 message.function_call:舊版工具呼叫回傳欄位
在舊版 Function Calling 中,模型可能會在 assistant message 裡回傳:
{
"role": "assistant",
"content": null,
"function_call": {
"name": "get_weather",
"arguments": "{\"city\":\"Taipei\"}"
}
}
新版 Tool Calling 則會回傳:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Taipei\"}"
}
}
]
}
差異如下:
舊版 function_call | 新版 tool_calls |
|---|---|
| 一次通常是一個 function call | 可支援多個 tool calls |
| 沒有 tool call id 回填流程 | 使用 tool_call_id 對應工具結果 |
function result 使用 function role | tool result 使用 tool role |
| 不適合平行工具呼叫 | 可搭配 parallel_tool_calls |
新版工具呼叫流程更清楚,尤其當模型一次需要呼叫多個工具時,tool_calls 比舊版 function_call 更好處理。
O.8 function role:舊版工具結果回填
舊版 Function Calling 中,工具執行結果會用 function role 回填。
舊寫法:
{
"role": "function",
"name": "get_weather",
"content": "{\"city\":\"Taipei\",\"forecast\":\"今天下午有短暫陣雨。\"}"
}
新版應改用 tool role:
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"city\":\"Taipei\",\"forecast\":\"今天下午有短暫陣雨。\"}"
}
差異:
舊版 function role | 新版 tool role |
|---|---|
使用 name 對應 function | 使用 tool_call_id 對應工具呼叫 |
| 較不適合多工具呼叫 | 可明確對應每個 tool call |
| legacy 用法 | 新專案建議使用 |
新版流程中,tool_call_id 很重要。
它必須對應到前一輪 assistant message 裡的 tool call id。
例如:
tool_call = message.tool_calls[0]
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result, ensure_ascii=False)
})
如果沒有正確帶上 tool_call_id,模型就無法知道這個 tool message 是哪一次工具呼叫的結果。
O.9 system message:舊範例常見,但新模型建議優先 developer
很多舊範例會使用 system message:
messages=[
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "請介紹 Chat Completions API。"
}
]
在早期 Chat Completions 教學中,這是很常見的寫法。
不過對較新的模型,官方文件已說明 developer messages 取代先前的 system messages。
因此新文章中,如果要放應用程式層級的規則,建議優先示範:
messages=[
{
"role": "developer",
"content": "你是一位技術寫作者,請使用繁體中文回答,並提供可執行範例。"
},
{
"role": "user",
"content": "請介紹 Chat Completions API。"
}
]
可以這樣理解:
| role | 建議定位 |
|---|---|
developer | 新模型中,放開發者或應用程式層級指令 |
system | 舊範例、舊模型或相容性情境中仍可能看到 |
user | 使用者輸入 |
assistant | 模型回覆 |
tool | 工具結果回填 |
這不代表你看到 system 就一定錯。
但如果你正在寫新的教學文章,建議用 developer 當主要示範,並在相容性段落說明 system 是舊範例常見寫法。
O.10 舊版 Function Calling 完整遷移範例
假設舊程式是這樣:
import json
from openai import OpenAI
client = OpenAI()
functions = [
{
"name": "get_weather",
"description": "取得指定城市的天氣資訊。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"]
}
}
]
messages = [
{
"role": "system",
"content": "你是天氣助理。"
},
{
"role": "user",
"content": "台北今天會下雨嗎?"
}
]
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
functions=functions,
function_call="auto",
max_tokens=300,
)
message = completion.choices[0].message
if message.function_call:
function_name = message.function_call.name
arguments = json.loads(message.function_call.arguments)
result = get_weather(**arguments)
messages.append(message)
messages.append({
"role": "function",
"name": function_name,
"content": json.dumps(result, ensure_ascii=False)
})
final_completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
)
print(final_completion.choices[0].message.content)
新版可以改成:
import json
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "取得指定城市的天氣資訊。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名稱,例如 Taipei、Tokyo、Seoul。"
}
},
"required": ["city"],
"additionalProperties": False
}
}
}
]
messages = [
{
"role": "developer",
"content": "你是天氣助理。需要即時天氣資訊時,請使用工具。"
},
{
"role": "user",
"content": "台北今天會下雨嗎?"
}
]
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
tool_choice="auto",
max_completion_tokens=300,
)
message = completion.choices[0].message
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_weather":
result = get_weather(**arguments)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
final_completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
)
print(final_completion.choices[0].message.content)
這個遷移包含幾個重點:
| 舊版 | 新版 |
|---|---|
system | developer |
functions | tools |
function_call | tool_choice |
message.function_call | message.tool_calls |
role=function | role=tool |
name=function_name | tool_call_id=tool_call.id |
max_tokens | max_completion_tokens |
O.11 舊版程式碼遷移檢查清單
如果你要把舊專案升級到較新的 Chat Completions API 寫法,可以用這份 checklist:
[ ] 搜尋 max_tokens,改成 max_completion_tokens
[ ] 搜尋 functions,改成 tools
[ ] 搜尋 function_call,改成 tool_choice
[ ] 搜尋 message.function_call,改成 message.tool_calls
[ ] 搜尋 role=function,改成 role=tool
[ ] 工具結果回填時加入 tool_call_id
[ ] 檢查是否需要支援多個 tool_calls
[ ] 檢查是否要設定 parallel_tool_calls
[ ] 將新範例中的 system message 改成 developer message
[ ] 檢查模型是否支援你使用的參數
[ ] 檢查 max_completion_tokens 是否足夠容納 visible output 與 reasoning tokens
[ ] 更新測試案例,確認 tool calling 流程正常
尤其是工具呼叫遷移,不要只把 functions 換成 tools。
你還需要同步修改 response 處理、工具結果回填與多工具呼叫處理邏輯。
O.12 OpenAI-compatible API 的相容性差異
很多第三方服務會提供 OpenAI-compatible API,也就是看起來使用 /chat/completions,參數也和 OpenAI 類似。
例如:
- Azure OpenAI
- 各種 LLM gateway
- self-hosted model server
- vLLM
- Ollama compatible server
- OpenRouter 或其他代理服務
- 雲端模型平台
這些服務可能支援部分 OpenAI Chat Completions 參數,但不一定完整支援所有新參數。
常見差異包括:
- 支不支援
developerrole。 - 支不支援
tools。 - 支不支援
parallel_tool_calls。 - 支不支援
response_format.json_schema。 - 支不支援
stream_options.include_usage。 - 支不支援
service_tier。 - 支不支援
modalities或audio。 - 是否仍要求使用
systemmessage。 - 是否仍使用
max_tokens而不是max_completion_tokens。 - 回傳物件欄位是否和 OpenAI 完全一致。
因此,如果你不是直接使用 OpenAI 官方 API,而是使用 OpenAI-compatible 服務,應該以該服務自己的文件為準。
這篇文章主要以 OpenAI 官方 Chat Completions API 為主。
如果你使用的是相容 API,請把本文當成概念參考,不要假設所有參數都可直接使用。
O.13 不要只靠 AI 產生的舊範例
現在很多人會直接請 LLM 幫忙產生 OpenAI API 程式碼。
但要注意,LLM 可能會產生舊版 API 寫法。
例如它可能輸出:
completion = openai.ChatCompletion.create(...)
或:
functions=[...],
function_call="auto",
max_tokens=500
這些寫法在舊文章、舊 SDK 或舊範例中很常見,但不一定適合新專案。
如果你看到 AI 產生的程式碼中包含:
openai.ChatCompletion.createfunctionsfunction_callfunctionrolemax_tokens- 只使用
systemmessage - 沒有
tool_call_id的工具回填
就應該停下來檢查是否是舊版寫法。
比較新的 Python SDK 常見寫法是:
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
)
工具呼叫則使用:
tools=tools,
tool_choice="auto"
輸出長度控制使用:
max_completion_tokens=500
O.14 什麼情況下仍需要理解舊用法?
雖然新專案應該使用新寫法,但舊用法仍然值得理解。
原因包括:
- 你可能要維護舊專案。
- 你可能會讀到舊教學文章。
- 很多 Stack Overflow 或 GitHub issue 還是舊寫法。
- 某些 OpenAI-compatible API 可能只支援舊參數。
- 團隊內部可能還有舊封裝。
- 舊程式碼可能需要逐步遷移,而不是一次改完。
因此這篇文章不需要完全忽略舊參數。
比較好的做法是:
- 前面主要章節用新寫法。
- 在本章集中整理舊寫法。
- 提供新舊對照表。
- 提醒讀者新專案不要優先照抄。
- 如果維護舊專案,知道要如何遷移。
這樣讀者不會在文章前半段被新舊 API 混在一起搞混,也能在需要時查到舊寫法代表什麼意思。
O.15 已棄用或相容性參數小結
整理一下:
| 舊參數或寫法 | 建議替代 |
|---|---|
max_tokens | max_completion_tokens |
functions | tools |
function_call | tool_choice |
message.function_call | message.tool_calls |
role: "function" | role: "tool" |
function result name | tool_call_id |
system message | 新模型範例優先使用 developer message |
openai.ChatCompletion.create(...) | client.chat.completions.create(...) |
實務建議:
- 新文章範例統一使用
client.chat.completions.create(...)。 - 新專案統一使用
max_completion_tokens。 - 新工具呼叫流程統一使用
tools、tool_choice、tool_calls、toolmessage。 - 舊版
functions/function_call放在 legacy 段落即可。 systemmessage 不一定完全不能用,但新模型與新文章建議優先示範developermessage。- 遷移舊專案時,不要只改參數名稱,也要更新 response 處理與 tool result 回填邏輯。
- 使用 OpenAI-compatible API 時,要確認該服務實際支援哪些參數。
這一章的核心結論是:
舊寫法要看得懂,但新專案不要優先照抄。
文章主線應使用目前建議寫法,再把 deprecated 與 legacy 用法集中整理到相容性章節。
下一章會進入實務設定範例,把前面所有參數組合成幾種常見場景,例如一般聊天機器人、結構化資料抽取、Tool Calling Agent、串流聊天室 UI 與可追蹤的正式產品請求。
P. 實務設定範例
前面幾章已經把 Chat Completions API 的主要參數拆開說明:
model決定使用哪個模型。messages決定模型看到什麼上下文。temperature、top_p、max_completion_tokens控制生成結果。response_format控制輸出格式。tools、tool_choice、parallel_tool_calls控制工具呼叫。stream、stream_options控制串流回應。metadata、user、store、usage幫助產品追蹤與成本分析。logprobs、seed、system_fingerprint則偏向除錯與分析。
這一章會把這些參數組合成幾個常見場景,讓你更容易知道實務上該怎麼設定。
要先強調一點:
參數不是越多越好。
好的 API 設定,是根據任務需求選擇必要參數,而不是把所有參數都塞進去。
以下範例都以 Python SDK 為主。
P.1 一般聊天機器人
如果你只是要做一個基本聊天機器人,通常不需要太複雜的參數。
重點是:
- 設定
model - 組好
messages - 設定適中的
temperature - 設定合理的
max_completion_tokens - 多輪對話時自行保存歷史 messages
範例:
from openai import OpenAI
client = OpenAI()
messages = [
{
"role": "developer",
"content": "你是一位友善且精準的 AI 助理,請使用繁體中文回答。"
},
{
"role": "user",
"content": "請解釋什麼是 Chat Completions API。"
}
]
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0.7,
max_completion_tokens=600,
)
print(completion.choices[0].message.content)
這種設定適合:
- 一般聊天
- 技術問答
- 產品客服
- 知識型助理
- 內容解釋
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
temperature | 0.7 | 保持自然度,不會太死板 |
max_completion_tokens | 600 | 避免回答過長,也保留足夠空間 |
developer message | 有 | 控制語言與回答風格 |
stream | 未啟用 | 後端測試或簡單應用先不用 streaming |
如果要做成真正的聊天 UI,可以再加入 streaming。
P.2 技術問答助理
技術問答通常需要更穩定、清楚、可驗證的回答,因此可以把 temperature 調低一點。
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是一位熟悉 Python、Django 與 API 設計的技術顧問。回答時請使用繁體中文,並優先提供可執行範例。若不確定,請明確說明。"
},
{
"role": "user",
"content": "請示範如何在 Django 中建立一個簡單的 API endpoint。"
}
],
temperature=0.3,
max_completion_tokens=1200,
)
這種設定適合:
- 技術文件問答
- 程式碼說明
- API 使用教學
- 開發者助理
- Debug 輔助
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
temperature | 0.3 | 技術回答需要穩定,不宜太發散 |
max_completion_tokens | 1200 | 技術回答通常需要範例與說明 |
developer message | 明確指定角色 | 讓模型以技術顧問角度回答 |
實務建議:
如果是高風險技術操作,例如部署、資安、資料庫 migration,建議在 developer message 中要求模型說明假設條件與風險。
P.3 結構化資料抽取
如果你要把模型輸出交給程式處理,建議使用 response_format 搭配 json_schema。
例如,從一段文字中抽取產品資訊:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是資料抽取器。請根據使用者輸入抽取產品資訊,並依照指定 schema 輸出。"
},
{
"role": "user",
"content": "TallyTrip 是一個給自由行小團體使用的旅程協作工具,支援收據 OCR、多人分帳與多幣別結算。"
}
],
temperature=0,
max_completion_tokens=800,
response_format={
"type": "json_schema",
"json_schema": {
"name": "product_info",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"target_users": {
"type": "string"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
},
"summary": {
"type": "string"
}
},
"required": [
"name",
"target_users",
"features",
"summary"
],
"additionalProperties": False
}
}
},
)
print(completion.choices[0].message.content)
這種設定適合:
- 資料抽取
- 表單自動填寫
- 文件欄位整理
- 分類任務
- 後端流程輸入
- 前端 UI 結構資料
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
temperature | 0 | 抽取任務需要穩定 |
response_format | json_schema | 確保輸出符合結構 |
strict | True | 提高 schema 遵循度 |
additionalProperties | False | 避免模型輸出多餘欄位 |
實務建議:
即使使用 json_schema,後端仍應該做 validation,不要在沒有檢查的情況下直接寫入資料庫。
P.4 收據 OCR 資料整理
如果你正在做旅遊分帳、記帳或收據整理,可以把 OCR 文字交給模型整理成結構化資料。
範例:
receipt_text = """
店名:Tokyo Coffee
日期:2026-05-20
拿鐵 600 JPY
蛋糕 800 JPY
總計 1400 JPY
"""
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是收據資料整理助理。請根據 OCR 文字抽取消費資訊。若欄位無法判斷,請填 null。"
},
{
"role": "user",
"content": receipt_text
}
],
temperature=0,
max_completion_tokens=1000,
response_format={
"type": "json_schema",
"json_schema": {
"name": "receipt_expense",
"strict": True,
"schema": {
"type": "object",
"properties": {
"merchant": {
"type": ["string", "null"]
},
"date": {
"type": ["string", "null"],
"description": "日期,格式為 YYYY-MM-DD"
},
"currency": {
"type": ["string", "null"],
"enum": ["TWD", "JPY", "USD", "KRW", "EUR", None]
},
"total": {
"type": ["number", "null"]
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"amount": {
"type": "number"
}
},
"required": ["name", "amount"],
"additionalProperties": False
}
}
},
"required": [
"merchant",
"date",
"currency",
"total",
"items"
],
"additionalProperties": False
}
}
},
)
print(completion.choices[0].message.content)
這種設定適合:
- 收據 OCR 後處理
- 消費品項整理
- 多幣別花費記錄
- 記帳草稿建立
- 旅行分帳工具
實務流程可以設計成:
- 使用者上傳收據。
- OCR 取得文字。
- 模型整理成 JSON。
- 後端驗證欄位。
- 建立「待確認」花費草稿。
- 使用者確認後才寫入正式帳務。
不要讓模型抽取結果直接進入正式帳務。
金額、幣別與日期都應該讓使用者確認。
P.5 圖片收據分析
如果模型支援圖片輸入,也可以直接把收據圖片放進 content parts。
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是收據資料整理助理。請根據圖片內容抽取消費資訊。看不清楚的欄位請填 null。"
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "請讀取這張收據,整理出 merchant、date、currency、total。"
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/receipt.jpg",
"detail": "high"
}
}
]
}
],
temperature=0,
max_completion_tokens=1000,
response_format={
"type": "json_schema",
"json_schema": {
"name": "receipt_summary",
"strict": True,
"schema": {
"type": "object",
"properties": {
"merchant": {
"type": ["string", "null"]
},
"date": {
"type": ["string", "null"]
},
"currency": {
"type": ["string", "null"]
},
"total": {
"type": ["number", "null"]
}
},
"required": [
"merchant",
"date",
"currency",
"total"
],
"additionalProperties": False
}
}
},
)
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
image_url.detail | high | 收據通常有小字,需要較高細節 |
temperature | 0 | 資料抽取不需要創意 |
response_format | json_schema | 結果要給程式處理 |
null 欄位 | 允許 | 看不清楚時不要硬猜 |
實務建議:
如果收據辨識精度很重要,建議使用專門 OCR 系統先抽文字,再交給模型整理。圖片理解可以輔助,但不應該是唯一驗證來源。
P.6 Tool Calling Agent
如果你的 AI 助理需要查資料庫或呼叫後端 API,就可以使用 tools。
例如,查詢旅程花費:
import json
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_trip_expense_summary",
"description": "查詢指定旅程的花費總結,包含總金額、幣別與每位成員的分攤資訊。",
"parameters": {
"type": "object",
"properties": {
"trip_id": {
"type": "string",
"description": "旅程 ID。"
},
"currency": {
"type": "string",
"enum": ["TWD", "JPY", "USD", "KRW", "EUR"]
}
},
"required": ["trip_id", "currency"],
"additionalProperties": False
}
}
}
]
messages = [
{
"role": "developer",
"content": "你是 TallyTrip 的旅程助理。需要查詢旅程花費時,請使用工具。"
},
{
"role": "user",
"content": "這趟東京旅行目前總共花了多少日圓?trip_id=tokyo_2026"
}
]
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
tool_choice="auto",
)
message = completion.choices[0].message
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_trip_expense_summary":
# 實務上這裡應呼叫你的後端服務或資料庫
tool_result = {
"trip_id": arguments["trip_id"],
"currency": arguments["currency"],
"total": 128000,
"members": [
{
"name": "Alfred",
"paid": 60000,
"share": 42667
},
{
"name": "Ben",
"paid": 38000,
"share": 42667
},
{
"name": "Cindy",
"paid": 30000,
"share": 42666
}
]
}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result, ensure_ascii=False)
})
final_completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
)
print(final_completion.choices[0].message.content)
else:
print(message.content)
這種設定適合:
- 查詢資料庫
- 查詢訂單狀態
- 查詢旅程資訊
- 執行後端邏輯
- 讓 AI 助理接上內部系統
實務建議:
- 工具參數一定要驗證。
- 工具執行前要檢查使用者權限。
- 高風險操作要加入使用者確認。
- 不要讓模型直接決定刪除、付款、發信等操作。
- 查詢型工具可以使用
tool_choice="auto"。 - 寫入型工具建議使用更嚴格流程。
P.7 串流聊天室 UI
如果你要做像 ChatGPT 一樣的聊天 UI,通常會使用 stream=True。
範例:
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是一位友善的 AI 助理,請使用繁體中文回答。"
},
{
"role": "user",
"content": "請用三句話介紹 Chat Completions API。"
}
],
temperature=0.7,
max_completion_tokens=800,
stream=True,
stream_options={
"include_usage": True
},
)
parts = []
usage = None
finish_reason = None
for chunk in stream:
if chunk.usage:
usage = chunk.usage
continue
if not chunk.choices:
continue
choice = chunk.choices[0]
delta = choice.delta
if delta.content:
parts.append(delta.content)
print(delta.content, end="")
if choice.finish_reason:
finish_reason = choice.finish_reason
full_text = "".join(parts)
print("\n\nfinish_reason:", finish_reason)
print("usage:", usage)
這種設定適合:
- 聊天 UI
- 長文生成
- 即時客服
- 使用者正在等待的互動流程
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
stream | True | 讓使用者即時看到文字 |
stream_options.include_usage | True | 串流結束時取得 token usage |
temperature | 0.7 | 一般聊天自然度 |
max_completion_tokens | 800 | 控制最長回答 |
實務建議:
前端或後端要處理:
- 串流中斷
- 使用者取消生成
- final usage chunk
- partial message
finish_reason="length"- markdown 尚未閉合
- tool calls 的 streaming delta
P.8 客服分類器
分類任務通常需要穩定、短輸出,可以使用 response_format 或簡單文字標籤。
如果想要可解析的結果,建議用 json_schema。
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是客服分類器。請根據使用者訊息判斷問題類型與優先級。"
},
{
"role": "user",
"content": "我昨天付款了,但是帳號還沒有升級。"
}
],
temperature=0,
max_completion_tokens=300,
response_format={
"type": "json_schema",
"json_schema": {
"name": "support_ticket_category",
"strict": True,
"schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": [
"billing",
"account",
"technical",
"general"
]
},
"priority": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"reason": {
"type": "string"
}
},
"required": [
"category",
"priority",
"reason"
],
"additionalProperties": False
}
}
},
)
print(completion.choices[0].message.content)
這種設定適合:
- 客服 ticket 分流
- 使用者意圖分類
- 問題優先級判斷
- 內容審核前處理
- 後台自動標籤
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
temperature | 0 | 分類任務需要穩定 |
response_format | json_schema | 結果要給程式處理 |
enum | 有 | 限制分類範圍 |
max_completion_tokens | 300 | 分類結果很短,不需長輸出 |
如果你想分析分類信心,也可以搭配 logprobs=True,但若已使用 JSON Schema,實務上通常會先依任務需求評估是否真的需要 logprobs。
P.9 文章摘要工具
文章摘要通常需要保留重點,但不需要太高創意。
範例:
article = """
Chat Completions API 是 OpenAI 提供的對話生成介面。
開發者可以傳入 messages,並透過 temperature、response_format、tools、stream 等參數控制模型行為。
"""
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是文章摘要工具。請使用繁體中文,整理重點,避免加入原文沒有的資訊。"
},
{
"role": "user",
"content": article
}
],
temperature=0.3,
max_completion_tokens=800,
)
print(completion.choices[0].message.content)
如果摘要要給程式處理,可以改成結構化輸出:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是文章摘要工具。請根據文章內容輸出標題、摘要與關鍵字。"
},
{
"role": "user",
"content": article
}
],
temperature=0.2,
max_completion_tokens=1000,
response_format={
"type": "json_schema",
"json_schema": {
"name": "article_summary",
"strict": True,
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"summary": {
"type": "string"
},
"keywords": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"title",
"summary",
"keywords"
],
"additionalProperties": False
}
}
},
)
這種設定適合:
- 部落格摘要
- 文件摘要
- 會議記錄整理
- 知識庫整理
- SEO description 生成
實務建議:
摘要任務的品質很依賴輸入內容品質與上下文長度。
如果文章太長,可能需要先分段摘要,再做總摘要。
P.10 可追蹤的正式產品請求
如果你要把 Chat Completions API 放進正式產品,建議至少加入:
usermetadatastore視需求決定service_tier- usage log
- latency log
- finish reason log
範例:
import time
from openai import OpenAI
client = OpenAI()
def create_product_completion(
*,
user_id: str,
feature: str,
messages: list,
model: str = "gpt-5.5",
):
started_at = time.perf_counter()
completion = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.5,
max_completion_tokens=800,
user=user_id,
metadata={
"feature": feature,
"environment": "production",
"prompt_version": "v1"
},
store=True,
service_tier="auto",
)
latency_ms = int((time.perf_counter() - started_at) * 1000)
choice = completion.choices[0]
usage = completion.usage
log_record = {
"completion_id": completion.id,
"user_id": user_id,
"feature": feature,
"model": completion.model,
"service_tier": getattr(completion, "service_tier", None),
"finish_reason": choice.finish_reason,
"prompt_tokens": usage.prompt_tokens if usage else None,
"completion_tokens": usage.completion_tokens if usage else None,
"total_tokens": usage.total_tokens if usage else None,
"latency_ms": latency_ms,
"system_fingerprint": getattr(completion, "system_fingerprint", None),
}
# 實務上這裡應寫入資料庫或 log system
print(log_record)
return choice.message.content
這種設定適合正式產品中的:
- AI 客服
- AI 摘要
- AI 資料抽取
- AI 聊天助理
- AI 旅程助理
- AI 後台工具
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
user | internal user id | 標記終端使用者 |
metadata | feature、environment、prompt version | 方便分析與除錯 |
store | 視需求 | 是否儲存 completion |
service_tier | auto | 交由專案設定決定 |
usage | response 欄位 | 成本分析核心 |
finish_reason | response 欄位 | 判斷是否正常結束 |
實務建議:
不要把 email、電話、地址、API key、access token 或完整敏感內容放進 metadata 或 user。
P.11 Prompt 版本控管範例
正式產品中,prompt 會持續調整。建議在 metadata 中記錄 prompt version。
例如:
PROMPT_VERSION = "receipt_extraction_v3"
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是收據資料抽取器。請根據 OCR 文字抽取消費資訊。"
},
{
"role": "user",
"content": receipt_text
}
],
temperature=0,
response_format={
"type": "json_schema",
"json_schema": receipt_schema
},
metadata={
"feature": "receipt_extraction",
"prompt_version": PROMPT_VERSION,
"environment": "production"
},
)
這樣你之後可以分析:
- v2 和 v3 哪個成功率比較高。
- 哪個 prompt 版本 token 消耗比較低。
- 哪個版本比較常輸出 invalid result。
- 哪個版本使用者修正率比較低。
如果你沒有記錄 prompt version,後續很難知道品質變化是因為模型、資料、prompt 還是程式邏輯改變。
P.12 批次背景任務
如果是背景批次任務,例如夜間摘要、資料整理、批次分類,通常不需要 streaming,也可以考慮較低延遲優先級。
範例:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是資料整理工具。請根據輸入內容產生簡潔摘要。"
},
{
"role": "user",
"content": long_text
}
],
temperature=0.2,
max_completion_tokens=1000,
service_tier="flex",
metadata={
"feature": "nightly_summary_job",
"environment": "production"
},
)
這種設定適合:
- 夜間批次摘要
- 大量文件整理
- 批次分類
- 後台資料清理
- 不需要使用者即時等待的任務
參數選擇理由:
| 參數 | 設定 | 理由 |
|---|---|---|
stream | 不啟用 | 後台任務不需要即時顯示 |
temperature | 偏低 | 結果要穩定 |
service_tier | flex | 可接受較高延遲時考慮成本 |
metadata | 有 | 標記批次任務來源 |
實務建議:
使用 service_tier="flex" 前,應確認你的帳號、模型與專案支援該 tier,並評估延遲是否符合任務需求。
P.13 參數選擇速查表
可以用這張表快速判斷不同任務要用哪些參數:
| 任務 | 建議參數 |
|---|---|
| 一般聊天 | model、messages、temperature、max_completion_tokens |
| 技術問答 | 較低 temperature、明確 developer message |
| 結構化資料抽取 | temperature=0、response_format.json_schema |
| 收據 OCR 整理 | response_format.json_schema、後端 validation |
| 圖片分析 | content parts、image_url、必要時 detail="high" |
| Tool Calling Agent | tools、tool_choice、tool message |
| 串流聊天 UI | stream=True、stream_options.include_usage |
| 客服分類 | temperature=0、json_schema、必要時 logprobs |
| 文章摘要 | temperature=0.2~0.4、適當 max_completion_tokens |
| 正式產品追蹤 | user、metadata、usage、finish_reason |
| Prompt 測試 | seed、system_fingerprint、prompt version |
| 批次任務 | 不使用 streaming,視情況使用 service_tier |
P.14 實務設定小結
整理一下,實務上不要從「有哪些參數」開始,而要從「任務需要什麼」開始。
如果是給人看的聊天回答:
temperature=0.7
stream=True
如果是給程式處理的資料:
temperature=0
response_format={"type": "json_schema", ...}
如果需要查資料庫:
tools=tools
tool_choice="auto"
如果需要正式產品追蹤:
user=user_id
metadata={...}
如果需要成本分析:
completion.usage
如果需要除錯與可重現性分析:
seed=12345
completion.system_fingerprint
可以用這個原則收斂:
聊天要自然,抽取要穩定,工具要驗證,串流要處理 chunk,正式產品要記錄 usage 與 metadata。
下一章會進入總結,整理 Chat Completions API 參數背後的幾個控制層級:模型選擇、對話上下文、生成控制、輸出格式、工具呼叫、串流體驗、多模態、可觀測性與除錯。
Q. 總結:參數不是越多越好,而是要知道每個參數控制哪一層
Chat Completions API 的參數很多,剛開始看很容易覺得混亂。
但如果把它們拆開來看,其實可以分成幾個層級:
| 層級 | 代表參數 | 控制什麼 |
|---|---|---|
| 模型選擇 | model | 使用哪個模型、能力、速度、成本 |
| 對話上下文 | messages | 模型看見什麼內容與指令 |
| 生成控制 | temperature、top_p、max_completion_tokens | 模型怎麼生成回答 |
| 輸出格式 | response_format | 回傳自然語言、JSON 或 JSON Schema |
| 工具呼叫 | tools、tool_choice、parallel_tool_calls | 模型是否能呼叫外部工具 |
| 串流體驗 | stream、stream_options | 回覆是否分段傳回 |
| 多模態 | content parts、modalities、audio | 圖片、音訊、檔案輸入輸出 |
| 可觀測性 | store、metadata、user、usage | 請求追蹤、用量分析、成本管理 |
| 除錯分析 | logprobs、top_logprobs、seed、system_fingerprint | 機率觀察與可重現性分析 |
| 相容性 | max_tokens、functions、function_call | 舊版程式碼與 legacy 用法 |
理解這些層級,比背誦所有參數更重要。
因為實務上,好的 API 設定通常不是「把所有參數都填滿」,而是根據產品需求選擇必要參數。
Q.1 最小可用設定
如果你只是要發出第一個 Chat Completion 請求,最小設定只需要:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "user",
"content": "請用一句話解釋 Chat Completions API。"
}
],
)
這裡最重要的是:
model:要使用哪個模型。messages:模型要根據什麼內容回答。
這是所有 Chat Completions API 應用的基礎。
Q.2 一般產品建議設定
如果要做一般聊天、客服或 AI 助理,可以從這個設定開始:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=[
{
"role": "developer",
"content": "你是一位友善且精準的 AI 助理,請使用繁體中文回答。"
},
{
"role": "user",
"content": user_input
}
],
temperature=0.7,
max_completion_tokens=800,
)
這裡加入了:
developermessage:設定應用程式層級規則。temperature:控制自然度與變化。max_completion_tokens:控制輸出長度與成本。
這已經足以支撐很多基本應用。
Q.3 需要穩定輸出時
如果任務需要穩定、可預期,例如分類、資料抽取、欄位整理,建議降低隨機性:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0,
max_completion_tokens=800,
)
如果輸出要給程式解析,應該再加入 response_format:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
temperature=0,
response_format={
"type": "json_schema",
"json_schema": {
"name": "structured_result",
"strict": True,
"schema": schema
}
},
)
這種設定適合:
- 結構化資料抽取
- 客服分類
- 收據 OCR 整理
- 文件欄位整理
- 表單自動填寫
但要記得,結構化輸出只代表格式比較穩定,不代表內容一定正確。正式產品仍應做 validation。
Q.4 需要外部資料時
如果模型需要查詢資料庫、內部 API 或外部系統,就應該使用 Tool Calling。
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
tool_choice="auto",
)
如果模型回傳 tool_calls,你的程式要:
- 解析工具名稱與參數。
- 驗證參數。
- 檢查使用者權限。
- 執行工具。
- 用
toolmessage 回填結果。 - 再呼叫一次模型產生最終回答。
Tool Calling 的重點不是讓模型直接控制後端,而是讓模型提出工具呼叫意圖,再由你的程式負責執行與控管。
高風險操作,例如刪除資料、付款、發信、下單,應該加入使用者確認流程,不要讓模型直接執行。
Q.5 需要即時聊天體驗時
如果你要做聊天 UI,通常會啟用 streaming:
stream = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
stream=True,
stream_options={
"include_usage": True
},
)
串流模式下,最常讀:
chunk.choices[0].delta.content
而不是:
completion.choices[0].message.content
實作 streaming 時要記得:
- 不是每個 chunk 都有
choices。 - 不是每個 chunk 都有
delta.content。 - final usage chunk 的
choices可能是空陣列。 - 串流中斷時,可能拿不到完整回答或 usage。
- Tool Calling 搭配 streaming 時,需要累積
delta.tool_calls。
Streaming 很適合使用者正在等待的互動場景,但對後端批次任務、分類、資料抽取不一定必要。
Q.6 需要圖片、音訊或檔案時
如果你的輸入不只是文字,可以使用 content parts。
例如圖片輸入:
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "請描述這張圖片。"
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/image.jpg",
"detail": "high"
}
}
]
}
]
多模態適合:
- 收據圖片分析
- 票券截圖整理
- 圖片描述
- 文件理解
- 語音摘要
- 語音助理
但多模態能力高度依賴模型支援。不是每個模型都支援圖片、音訊、檔案或音訊輸出。實作前應確認模型能力與 API 文件。
Q.7 正式產品一定要做可觀測性
如果只是寫 demo,可以只印出模型回答。
但如果是正式產品,建議至少記錄:
completion.idmodelusagefinish_reasonlatencyusermetadataservice_tiersystem_fingerprint
例如:
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
user=user_id,
metadata={
"feature": "support_chat",
"environment": "production",
"prompt_version": "v1"
},
)
正式產品不是只問「模型有沒有回答」,還要知道:
- 它花了多少 token?
- 回答是否被截斷?
- 是哪個功能觸發?
- 是哪個使用者觸發?
- 用了哪個模型?
- 延遲多久?
- 哪個 prompt version?
- 是否呼叫工具?
- 是否發生錯誤?
這些資訊會影響成本控管、品質分析與除錯效率。
Q.8 舊參數要看得懂,但新專案不要優先照抄
如果你看到舊文章或舊程式碼,可能會看到:
max_tokens=500
functions=[...]
function_call="auto"
新專案應優先使用:
max_completion_tokens=500
tools=[...]
tool_choice="auto"
新舊對照如下:
| 舊用法 | 新建議用法 |
|---|---|
max_tokens | max_completion_tokens |
functions | tools |
function_call | tool_choice |
message.function_call | message.tool_calls |
role: "function" | role: "tool" |
system message | 新模型範例優先使用 developer message |
舊寫法仍值得理解,因為你可能會維護舊專案、看舊教學或使用 OpenAI-compatible API。
但如果你正在寫新的程式或新的教學文章,建議主線使用目前較新的寫法。
Q.9 我會怎麼選參數?
如果是我在做一個正式產品,我通常會這樣選。
一般聊天:
temperature=0.7
max_completion_tokens=800
技術問答:
temperature=0.3
max_completion_tokens=1200
結構化資料抽取:
temperature=0
response_format={"type": "json_schema", ...}
Tool Calling:
tools=tools
tool_choice="auto"
串流聊天 UI:
stream=True
stream_options={"include_usage": True}
正式產品追蹤:
user=user_id
metadata={
"feature": feature,
"environment": "production",
"prompt_version": "v1"
}
Prompt 測試與除錯:
seed=12345
logprobs=True
但我不會在每個請求中都放上所有參數。
每個功能只放它真正需要的參數,會更容易維護,也更容易除錯。
Q.10 最後的實務建議
最後整理幾個我認為最重要的實務原則。
第一,先設計 messages,再調參數。
很多輸出問題不是 temperature 沒調好,而是 developer message 不清楚、上下文太亂,或使用者輸入沒有被正確整理。
第二,給人看的輸出和給程式看的輸出要分開設計。
給人看的回答可以自然一點;給程式處理的結果應該使用 response_format 和 JSON Schema。
第三,Tool Calling 一定要驗證。
模型產生的 arguments 不應該被直接信任。後端仍要做權限檢查、schema validation、商業邏輯驗證與錯誤處理。
第四,Streaming 是使用者體驗功能,不是所有任務都需要。
聊天 UI 很適合 streaming;批次分類、摘要、資料抽取通常不一定需要。
第五,正式產品要記錄 usage。
沒有 usage log,就很難做成本分析。至少記錄 prompt_tokens、completion_tokens、total_tokens、model、feature、user 與 latency。
第六,舊範例要小心。
很多網路範例仍然使用 max_tokens、functions、function_call 或舊 SDK 寫法。新專案應以目前官方文件為準。
第七,不要把 AI 輸出直接當成事實或高風險操作依據。
無論是收據金額、法律內容、醫療資訊、付款操作、資料刪除,都應該有驗證、確認與 fallback 流程。
Q.11 結語
Chat Completions API 看起來是一個「傳入 messages、取得 assistant 回覆」的介面,但真正做成產品時,它其實是一組可以控制模型行為的參數系統。
你可以用:
messages設計上下文temperature控制生成風格max_completion_tokens控制長度與成本response_format取得結構化資料tools接上外部系統stream改善聊天體驗metadata與usage做產品追蹤logprobs、seed、system_fingerprint做除錯與分析
但真正重要的不是知道所有參數,而是知道:
這個功能需要模型控制哪一層?
如果是聊天,先把 messages 和 streaming 做好。
如果是資料抽取,先把 response_format 和 validation 做好。
如果是 AI agent,先把 tools、權限與錯誤處理做好。
如果是正式產品,先把 usage、metadata、log 和成本追蹤做好。
參數不是越多越好。
真正好的 Chat Completions API 設計,是讓每個參數都有明確目的,並且能被產品需求、工程維護與使用者體驗共同支持。