AI · 開發者看 AI
如何呼叫 LLM API?
簡答
發送一個 HTTPS POST 請求,在標頭放入 API 金鑰,並在 JSON 主體中指定模型和訊息清單。回應是包含生成文字與 token 用量的 JSON。許多供應商接受 OpenAI 風格的 chat completions 格式,Anthropic 與 Gemini 則另有各自的格式。金鑰請放在伺服器端,並依 token 計費。
開啟JSON 格式化一次 API 呼叫發生了什麼事
LLM API 是一種網路服務。你的程式送出文字,供應商的伺服器執行模型,結果以 JSON 傳回。呼叫之間沒有記憶:每個請求都必須帶上模型該看到的完整對話。
你需要三樣東西:一個有 API 金鑰的帳號、端點的網址,以及供應商模型清單中的模型 ID。下面的範例中,MODEL_ID 只是佔位符。模型名稱經常更換,請從供應商的文件複製目前的 ID,而不是從部落格文章。
用 curl、Python 與 JavaScript 寫出最簡單的請求
chat completions 格式最早由 OpenAI 發布,是被最廣泛仿效的請求形式。許多其他供應商和本機伺服器都接受它,有時只需要換一個基礎網址。以下是最小而實用的請求:
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "MODEL_ID",
"messages": [
{"role": "system", "content": "You answer in one short paragraph."},
{"role": "user", "content": "What is a context window?"}
]
}'messages 清單存放對話內容。每則訊息都有 role 和 content。system 訊息為回覆設定規則(OpenAI 較新的模型也接受用 developer 這個名稱代表此角色)。user 訊息是使用者的請求,assistant 訊息則是先前的回覆,你要把它們一併送回去才能延續對話。縮短後的回應如下:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "MODEL_ID",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "A context window is ..."},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 27, "completion_tokens": 64, "total_tokens": 91}
}文字位於 choices[0].message.content。usage 物件記錄你被計費的 token 數。finish_reason 說明模型為什麼停下來:stop 表示正常結束,length 表示達到你設定的 token 上限,答案被截斷了。
在 Python 中,同樣的呼叫加上逾時與重試是這樣寫的。它使用常見的 requests 函式庫。各供應商的官方 SDK 做的是相同的事,還會幫你處理重試。
import os, random, time
import requests
URL = "https://api.openai.com/v1/chat/completions"
API_KEY = os.environ["OPENAI_API_KEY"] # read from the server environment
def chat(messages, model="MODEL_ID", max_attempts=5):
body = {"model": model, "messages": messages,
"max_completion_tokens": 500} # many compatible APIs: "max_tokens"
headers = {"Authorization": f"Bearer {API_KEY}"}
for attempt in range(max_attempts):
last = attempt == max_attempts - 1
try:
r = requests.post(URL, headers=headers, json=body, timeout=60)
except (requests.Timeout, requests.ConnectionError):
if last:
raise
time.sleep(2 ** attempt + random.random()) # network error: retry
continue
if r.status_code == 429 or r.status_code >= 500:
if last:
break
backoff = 2 ** attempt + random.random() # exponential + jitter
try:
wait = max(float(r.headers.get("Retry-After", 0)), backoff)
except ValueError:
wait = backoff
time.sleep(wait)
continue
r.raise_for_status() # 400, 401, 403: fix the request, do not retry
data = r.json()
return data["choices"][0]["message"]["content"], data["usage"]
raise RuntimeError("API still failing after retries")
text, usage = chat([{"role": "user", "content": "What is a context window?"}])
print(text)
print(usage["prompt_tokens"], "in /", usage["completion_tokens"], "out")各供應商不同:OpenAI 相容、Anthropic 與 Gemini
chat completions 的形式很常見,但並非通用。Anthropic 的 Messages API 和 Google 的 Gemini API 都有自己的原生格式。兩家供應商也都提供相容選項,但原生 API 會優先開放所有功能。主要差異如下:
| 項目 | OpenAI 風格 chat completions | Anthropic Messages API | Gemini API(原生) |
|---|---|---|---|
| 端點 | POST /v1/chat/completions | POST /v1/messages | POST /v1beta/models/{model}:generateContent |
| API 金鑰標頭 | Authorization: Bearer <key> | x-api-key 加上必要的 anthropic-version 日期標頭 | x-goog-api-key |
| 系統指示 | system 或 developer 訊息 | 頂層的 system 欄位 | 頂層的 systemInstruction 欄位 |
| 必要欄位 | model、messages | model、max_tokens、messages | contents(模型在網址中) |
| 回覆文字 | choices[0].message.content | content 陣列中的文字區塊 | candidates[0].content.parts[].text |
| 串流 | "stream": true,以 [DONE] 結尾的 server-sent events | "stream": true,具名的 server-sent events | 搭配 alt=sse 的 streamGenerateContent 方法 |
Gemini 提供相容 OpenAI 的基礎網址,包括 DeepSeek 以及多家中國與開源模型託管商在內的許多供應商,也接受 chat completions 格式。相容性通常只是部分的:結構化輸出、工具呼叫或圖片輸入等進階功能可能有差異。只換網址就切換供應商之前,請先閱讀各家的相容性說明頁。
API 金鑰要留在伺服器端
任何拿到你 API 金鑰的人,都能花你的錢。千萬不要把它放在瀏覽器 JavaScript、行動應用程式、公開儲存庫,或會被打包進頁面的前端環境檔案裡,這些地方使用者都讀得到。
- 把金鑰存放在伺服器上的環境變數或密鑰管理工具中。
- 讓你的網頁或行動應用程式呼叫你自己的後端端點。
- 在後端檢查使用者(登入、每位使用者的速率限制、輸入長度),再帶著金鑰呼叫 LLM API。
- 只把結果回傳給用戶端。
- 在供應商的後台設定每月支出上限,每個專案使用獨立的金鑰,金鑰一旦外洩就立即更換。
串流:邊寫邊顯示答案
長篇回答可能要花好幾秒。設定 "stream": true 後,伺服器會以 server-sent events 分成小片段傳送,因此你的介面可以在文字出現時就顯示。每一行事件以 data: 開頭,內容是含有 delta 的區塊。OpenAI 格式以 data: [DONE] 結尾。以下範例在 Node.js 伺服器上執行:
// Node.js 18+ (server side). Prints the reply as it arrives.
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "MODEL_ID",
stream: true,
messages: [{ role: "user", content: "Write a haiku about rain." }],
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of res.body) {
buffer += decoder.decode(chunk, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop(); // keep a partial line for later
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6);
if (data === "[DONE]") continue; // end of stream
const delta = JSON.parse(data).choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
}若要在瀏覽器中顯示串流,請由你的伺服器把區塊轉送給頁面。不要帶著你的金鑰直接從瀏覽器呼叫供應商。在 OpenAI 格式中,除非你在 stream_options 設定 include_usage,否則串流區塊不包含用量,所以如果需要 token 數來計費,請開啟它。
取得 JSON 而不是散文
當程式要讀取答案時,就要求輸出 JSON。有兩個層級。JSON 模式("response_format": {"type": "json_object"})保證輸出有效的 JSON,但不保證有哪些欄位。使用 JSON Schema 的結構化輸出,則讓模型遵循你指定的欄位與型別(限於支援的模型):
{
"model": "MODEL_ID",
"messages": [
{"role": "user", "content": "Extract the total and currency: 'Amount due: 1.250,00 EUR'"}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "invoice_total",
"strict": true,
"schema": {
"type": "object",
"properties": {
"total": {"type": "number"},
"currency": {"type": "string"}
},
"required": ["total", "currency"],
"additionalProperties": false
}
}
}
}即使有 schema,也要在程式中驗證解析後的結果。就算結構正確,值仍然可能出錯,例如看錯數字。Anthropic 和 Gemini 以各自的格式支援 schema 與工具定義。除錯很長的 JSON 回覆時,用 cv.cm 的 JSON 工具這類格式化工具,巢狀輸出會更好讀。
速率限制、重試與錯誤
供應商同時限制每分鐘請求數(RPM)與每分鐘 token 數(TPM),先碰到哪個就適用哪個。超過限制時,你會收到 HTTP 狀態碼 429。請依錯誤類型處理:
| 狀態碼 | 意義 | 處理方式 |
|---|---|---|
| 400 | 錯誤的請求:欄位有誤、token 太多 | 修正請求,不要重試。 |
| 401 / 403 | 金鑰遺失、錯誤或未授權 | 檢查金鑰與權限,不要重試。 |
| 429 | 速率限制,有時是額度用完 | 等待後以退避方式重試。如果訊息提到額度或帳務,就停下來修正帳戶。 |
| 500 / 503 | 伺服器錯誤或過載 | 以退避方式重試。 |
| 逾時 | 沒有及時回應 | 重試一兩次。長篇回答請使用串流。 |
以指數退避重試:先等約 1 秒,再等 2、4、8 秒,並加上一點隨機延遲(抖動,jitter),避免許多用戶端在同一時刻重試。如果回應有 Retry-After 標頭,至少要等那麼久。限制重試次數的上限。失敗的請求也可能算進你的額度,所以在緊密迴圈中立即重試只會更糟。如果你使用官方 SDK,請記得它已經會重試部分錯誤。
費用以 token 計算
你要付輸入 token(你送出的一切,包括系統提示詞和整段對話歷史)與輸出 token(模型寫出的內容)的費用。輸出 token 的單價通常高於輸入 token。公式很簡單:
cost = input_tokens / 1,000,000 * input_price_per_million
+ output_tokens / 1,000,000 * output_price_per_million
Example with made-up prices (2.00 in, 8.00 out per million tokens):
27 input + 64 output tokens
= 0.000054 + 0.000512 = 0.000566 per call
x 100,000 calls a month = about 56.60- 從每個回應的
usage讀取真實的數量並記錄下來,不要用字數估算。 - 限制每次回覆的長度:OpenAI 用
max_completion_tokens(舊的max_tokens在該處已被棄用),Anthropic 和許多相容 API 則用max_tokens。 - 對話歷史每一輪都在增長,所以第 20 則訊息比第一則花費更多。請裁剪或摘要舊的輪次。
- 簡單任務(例如分類)用較小的模型,只有在品質需要時才用較大的模型。
- 部分供應商對重複的提示詞前綴提供折扣(提示詞快取),或對可以等待的工作提供更便宜的批次處理。
常見問題
可以直接從瀏覽器呼叫 LLM API 嗎?
用你自己的金鑰並不安全,因為任何訪客都能從頁面中讀到它。請呼叫你自己的伺服器,由它加上金鑰。例外是使用者為自己帳戶輸入的金鑰,或完全在瀏覽器中執行的模型。
為什麼 API 會忘記我先前說過的話?
API 是無狀態的。每個請求都必須包含你希望模型看到的先前訊息。聊天應用程式會儲存歷史紀錄,並在每次請求時重新送出。
temperature 有什麼作用?我該設定嗎?
temperature 控制用詞選擇的隨機程度:越低越可預測,越高越多樣。部分供應商和較新的模型限制或移除了取樣參數,所以設定之前請先查看 API 參考文件。
為什麼我的回答在中途停了?
回覆達到了你設定的 token 上限,或模型的最大輸出長度。請檢查 finish_reason(在 Anthropic 是 stop_reason)。調高上限,或要求更簡短的答案。
有免費的 LLM API 嗎?
有幾家供應商提供免費方案或試用額度,但速率限制很低;開放模型也可以在你自己的電腦上,透過相容 OpenAI 的本機伺服器執行。免費方案及其條款經常變動,請查看供應商目前的頁面。