打造專屬 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
versionprompt 或功能版本
session_id內部 session ID
request_type請求類型,例如 chat、extract、classify
experimentA/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 的內容包括:

  • 使用者真實姓名
  • Email
  • 電話
  • 地址
  • 身分證字號
  • 信用卡資訊
  • 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 不同,代表後端設定可能已經變動,因此輸出不同是合理的。

概念上:

seedsystem_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模型產生的候選回答列表
usagetoken 使用量
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_tokensreasoning models 內部推理消耗的 token
accepted_prediction_tokensprediction 相關接受 token,若支援
rejected_prediction_tokensprediction 相關拒絕 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 不同,代表後端設定可能變動,因此輸出差異比較合理。

概念上:

seedsystem_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[].logprobstoken 機率資訊,需設定 logprobs=True
choices[].finish_reason停止原因
usagetoken 使用量
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停止原因,通常最後才出現
usagetoken 使用量,通常只有啟用 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 可能包含:

  • role
  • content
  • tool_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_fingerprint
  • service_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.roleassistant 角色片段
delta.content本次新增文字
delta.tool_calls工具呼叫片段
choices[].finish_reason停止原因,通常最後出現
usagetoken 使用量,通常在 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_tokensfunctionsfunction_call 與 functionrole。這些內容在舊文章或舊專案中仍然常見,但新專案應該優先使用較新的參數與訊息格式。

O. 已棄用或相容性參數整理

如果你查過早期的 Chat Completions API 教學,或維護過 2023、2024 年左右寫的 OpenAI API 程式碼,很可能會看到一些現在已經不建議新專案優先使用的參數或寫法。

常見例子包括:

  • max_tokens
  • functions
  • function_call
  • function role
  • 舊版 system message 寫法
  • 舊版 function_call response 欄位

這些內容不一定完全不能用,但在新的文章或新專案中,應該要把它們放在相容性或 legacy 段落,而不是當成主要推薦用法。

這一章的目的不是說舊寫法都錯,而是幫你建立一張新舊對照表:

舊專案看得懂,新專案不要再優先照抄。

O.1 為什麼需要整理 deprecated 與 legacy 用法?

OpenAI API 演進很快,尤其 Chat Completions API 經歷過幾次重要變化。

例如:

  • 早期常用 system message 放高層級指令。
  • 早期工具呼叫使用 functions 和 function_call
  • 後來工具呼叫改成 tools 和 tool_choice
  • 舊版輸出長度控制常用 max_tokens
  • 新版更建議使用 max_completion_tokens
  • 新模型中,developer message 取代了部分過去 system message 的定位。

這些變化會造成一個問題:網路上的範例很多,但不一定都符合目前 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_tokensmax_completion_tokens控制模型最多生成多少 completion tokens
functionstools定義模型可使用的工具
function_calltool_choice控制模型是否使用工具或指定工具
function roletool role回填工具執行結果
message.function_callmessage.tool_calls模型要求呼叫工具時的新欄位
system messagedeveloper 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_tokensdeprecated舊版輸出 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 roletool 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)

這個遷移包含幾個重點:

舊版新版
systemdeveloper
functionstools
function_calltool_choice
message.function_callmessage.tool_calls
role=functionrole=tool
name=function_nametool_call_id=tool_call.id
max_tokensmax_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 參數,但不一定完整支援所有新參數。

常見差異包括:

  • 支不支援 developer role。
  • 支不支援 tools
  • 支不支援 parallel_tool_calls
  • 支不支援 response_format.json_schema
  • 支不支援 stream_options.include_usage
  • 支不支援 service_tier
  • 支不支援 modalities 或 audio
  • 是否仍要求使用 system message。
  • 是否仍使用 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.create
  • functions
  • function_call
  • function role
  • max_tokens
  • 只使用 system message
  • 沒有 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 可能只支援舊參數。
  • 團隊內部可能還有舊封裝。
  • 舊程式碼可能需要逐步遷移,而不是一次改完。

因此這篇文章不需要完全忽略舊參數。
比較好的做法是:

  1. 前面主要章節用新寫法。
  2. 在本章集中整理舊寫法。
  3. 提供新舊對照表。
  4. 提醒讀者新專案不要優先照抄。
  5. 如果維護舊專案,知道要如何遷移。

這樣讀者不會在文章前半段被新舊 API 混在一起搞混,也能在需要時查到舊寫法代表什麼意思。

O.15 已棄用或相容性參數小結

整理一下:

舊參數或寫法建議替代
max_tokensmax_completion_tokens
functionstools
function_calltool_choice
message.function_callmessage.tool_calls
role: "function"role: "tool"
function result nametool_call_id
system message新模型範例優先使用 developer message
openai.ChatCompletion.create(...)client.chat.completions.create(...)

實務建議:

  • 新文章範例統一使用 client.chat.completions.create(...)
  • 新專案統一使用 max_completion_tokens
  • 新工具呼叫流程統一使用 toolstool_choicetool_callstool message。
  • 舊版 functions / function_call 放在 legacy 段落即可。
  • system message 不一定完全不能用,但新模型與新文章建議優先示範 developer message。
  • 遷移舊專案時,不要只改參數名稱,也要更新 response 處理與 tool result 回填邏輯。
  • 使用 OpenAI-compatible API 時,要確認該服務實際支援哪些參數。

這一章的核心結論是:

舊寫法要看得懂,但新專案不要優先照抄。
文章主線應使用目前建議寫法,再把 deprecated 與 legacy 用法集中整理到相容性章節。

下一章會進入實務設定範例,把前面所有參數組合成幾種常見場景,例如一般聊天機器人、結構化資料抽取、Tool Calling Agent、串流聊天室 UI 與可追蹤的正式產品請求。

P. 實務設定範例

前面幾章已經把 Chat Completions API 的主要參數拆開說明:

  • model 決定使用哪個模型。
  • messages 決定模型看到什麼上下文。
  • temperaturetop_pmax_completion_tokens 控制生成結果。
  • response_format 控制輸出格式。
  • toolstool_choiceparallel_tool_calls 控制工具呼叫。
  • streamstream_options 控制串流回應。
  • metadatauserstoreusage 幫助產品追蹤與成本分析。
  • logprobsseedsystem_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)

這種設定適合:

  • 一般聊天
  • 技術問答
  • 產品客服
  • 知識型助理
  • 內容解釋

參數選擇理由:

參數設定理由
temperature0.7保持自然度,不會太死板
max_completion_tokens600避免回答過長,也保留足夠空間
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 輔助

參數選擇理由:

參數設定理由
temperature0.3技術回答需要穩定,不宜太發散
max_completion_tokens1200技術回答通常需要範例與說明
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 結構資料

參數選擇理由:

參數設定理由
temperature0抽取任務需要穩定
response_formatjson_schema確保輸出符合結構
strictTrue提高 schema 遵循度
additionalPropertiesFalse避免模型輸出多餘欄位

實務建議:

即使使用 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 後處理
  • 消費品項整理
  • 多幣別花費記錄
  • 記帳草稿建立
  • 旅行分帳工具

實務流程可以設計成:

  1. 使用者上傳收據。
  2. OCR 取得文字。
  3. 模型整理成 JSON。
  4. 後端驗證欄位。
  5. 建立「待確認」花費草稿。
  6. 使用者確認後才寫入正式帳務。

不要讓模型抽取結果直接進入正式帳務。
金額、幣別與日期都應該讓使用者確認。

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.detailhigh收據通常有小字,需要較高細節
temperature0資料抽取不需要創意
response_formatjson_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
  • 長文生成
  • 即時客服
  • 使用者正在等待的互動流程

參數選擇理由:

參數設定理由
streamTrue讓使用者即時看到文字
stream_options.include_usageTrue串流結束時取得 token usage
temperature0.7一般聊天自然度
max_completion_tokens800控制最長回答

實務建議:

前端或後端要處理:

  • 串流中斷
  • 使用者取消生成
  • 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 分流
  • 使用者意圖分類
  • 問題優先級判斷
  • 內容審核前處理
  • 後台自動標籤

參數選擇理由:

參數設定理由
temperature0分類任務需要穩定
response_formatjson_schema結果要給程式處理
enum限制分類範圍
max_completion_tokens300分類結果很短,不需長輸出

如果你想分析分類信心,也可以搭配 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 放進正式產品,建議至少加入:

  • user
  • metadata
  • store 視需求決定
  • 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 後台工具

參數選擇理由:

參數設定理由
userinternal user id標記終端使用者
metadatafeature、environment、prompt version方便分析與除錯
store視需求是否儲存 completion
service_tierauto交由專案設定決定
usageresponse 欄位成本分析核心
finish_reasonresponse 欄位判斷是否正常結束

實務建議:

不要把 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_tierflex可接受較高延遲時考慮成本
metadata標記批次任務來源

實務建議:

使用 service_tier="flex" 前,應確認你的帳號、模型與專案支援該 tier,並評估延遲是否符合任務需求。

P.13 參數選擇速查表

可以用這張表快速判斷不同任務要用哪些參數:

任務建議參數
一般聊天modelmessagestemperaturemax_completion_tokens
技術問答較低 temperature、明確 developer message
結構化資料抽取temperature=0response_format.json_schema
收據 OCR 整理response_format.json_schema、後端 validation
圖片分析content parts、image_url、必要時 detail="high"
Tool Calling Agenttoolstool_choicetool message
串流聊天 UIstream=Truestream_options.include_usage
客服分類temperature=0json_schema、必要時 logprobs
文章摘要temperature=0.2~0.4、適當 max_completion_tokens
正式產品追蹤usermetadatausagefinish_reason
Prompt 測試seedsystem_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模型看見什麼內容與指令
生成控制temperaturetop_pmax_completion_tokens模型怎麼生成回答
輸出格式response_format回傳自然語言、JSON 或 JSON Schema
工具呼叫toolstool_choiceparallel_tool_calls模型是否能呼叫外部工具
串流體驗streamstream_options回覆是否分段傳回
多模態content parts、modalitiesaudio圖片、音訊、檔案輸入輸出
可觀測性storemetadatauserusage請求追蹤、用量分析、成本管理
除錯分析logprobstop_logprobsseedsystem_fingerprint機率觀察與可重現性分析
相容性max_tokensfunctionsfunction_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,
)

這裡加入了:

  • developer message:設定應用程式層級規則。
  • 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,你的程式要:

  1. 解析工具名稱與參數。
  2. 驗證參數。
  3. 檢查使用者權限。
  4. 執行工具。
  5. 用 tool message 回填結果。
  6. 再呼叫一次模型產生最終回答。

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.id
  • model
  • usage
  • finish_reason
  • latency
  • user
  • metadata
  • service_tier
  • system_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_tokensmax_completion_tokens
functionstools
function_calltool_choice
message.function_callmessage.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_tokenscompletion_tokenstotal_tokensmodelfeatureuser 與 latency

第六,舊範例要小心。

很多網路範例仍然使用 max_tokensfunctionsfunction_call 或舊 SDK 寫法。新專案應以目前官方文件為準。

第七,不要把 AI 輸出直接當成事實或高風險操作依據。

無論是收據金額、法律內容、醫療資訊、付款操作、資料刪除,都應該有驗證、確認與 fallback 流程。

Q.11 結語

Chat Completions API 看起來是一個「傳入 messages、取得 assistant 回覆」的介面,但真正做成產品時,它其實是一組可以控制模型行為的參數系統。

你可以用:

  • messages 設計上下文
  • temperature 控制生成風格
  • max_completion_tokens 控制長度與成本
  • response_format 取得結構化資料
  • tools 接上外部系統
  • stream 改善聊天體驗
  • metadata 與 usage 做產品追蹤
  • logprobsseedsystem_fingerprint 做除錯與分析

但真正重要的不是知道所有參數,而是知道:

這個功能需要模型控制哪一層?

如果是聊天,先把 messages 和 streaming 做好。
如果是資料抽取,先把 response_format 和 validation 做好。
如果是 AI agent,先把 tools、權限與錯誤處理做好。
如果是正式產品,先把 usage、metadata、log 和成本追蹤做好。

參數不是越多越好。
真正好的 Chat Completions API 設計,是讓每個參數都有明確目的,並且能被產品需求、工程維護與使用者體驗共同支持。