รูปแบบ error body และลำดับการอ่าน
การตอบสนองที่ล้มเหลวทั้งหมดมีโครงสร้างเดียวกัน:
{"error":{"code":"...","message":"..."}}
ลำดับการอ่านมี 3 ขั้นตอนคงที่:
- HTTP status code กำหนดหมวดหมู่;
error.codeกำหนดสาเหตุเฉพาะ ใช้ทำ branching ในโปรแกรม;error.messageสำหรับมนุษย์ บันทึกใน log อย่าใช้ match string
ต้องแยกแยะให้ชัดเจนระหว่างสิ่งที่แก้ด้วยการ重试ได้ (429, 503 และ network timeout) กับสิ่งที่ต้องแก้ไขคำขอจึงจะผ่าน (ที่เหลือ) การสับสนระหว่างสองประเภทนี้คือสาเหตุหลักของ incidents ใน production เช่น การ重试 402 ซ้ำๆ จนเกิดคำขอ无效นับสิบต่อวินาที
แนะนำให้บันทึก 4 field คงที่ใน log: timestamp, status code, error.code และประมาณการ prompt_tokens ของคำขอนั้น เมื่อเกิดปัญหาจะเห็นได้ทันทีว่า error ประเภทใดเพิ่มขึ้นหรือล้มเหลวทั้งหมด พร้อมตั้ง alert rule: 402 แค่เกิดครั้งเดียวให้แจ้งเตือนทันทีเพราะหมายถึงบริการใช้งานไม่ได้สำหรับผู้ใช้; 429 ดูที่อัตรา หากเกิดเป็นครั้งคราวไม่ต้องจัดการ หากเกิดต่อเนื่องแสดงว่าการออกแบบ concurrency มีปัญหา
ตารางรหัสสถานะแบบด่วน
| รหัสสถานะ | error.code | ความหมาย | ทำซ้ำ? |
|---|---|---|---|
| 400 | — | คำขอไม่ถูกต้อง เช่น prompt รวม max_tokens เกิน 100k | ไม่ |
| 401 | — | key ไม่ถูกต้องหรือไม่มี | ไม่ |
| 402 | no_credit | เงินหมดหรือเครดิตทดลองใช้หมดอายุ | ไม่ |
| 403 | content_blocked | เนื้อหาถูกบล็อก | ไม่ |
| 404 | — | endpoint ไม่อยู่ | ไม่ |
| 429 | — | trigger rate limit | ใช่, backoff |
| 503 | upstream_busy | บริการกำลังยุ่งชั่วคราว | ใช่, รอไม่กี่วินาที |
ในตาราง “—” หมายถึงไม่มี code string คงที่ที่ต้องจัดการเฉพาะ ให้ทำ branching ตาม status code ได้เลย
4xx: แก้คำขอ อย่าทำซ้ำ
400 คำขอไม่ถูกต้อง
- สาเหตุที่ 1: รูปแบบ JSON ผิดพลาด มักเกิดจากการพิมพ์ string ด้วยมือโดยไม่ได้ escape quote
- สาเหตุที่ 2: prompt รวม max_tokens เกิน 100,000 การสนทนาที่ยาวมัก trigger กรณีนี้
- สาเหตุที่ 3: request body เกิน 8 MB
- วิธีแก้ไข: ใช้ไลบรารี JSON เพื่อทำ serialization; ประมาณจำนวน token ก่อนส่ง หากเกินให้ตัดประวัติหรือลด max_tokens; ดูการใช้งานบริบทยาว
401 ปัญหา key
- Header หาย หรือขาด prefix
Bearer - สร้าง key ใหม่แล้ว key เก่าหมดอายุทันที แต่เครื่องหนึ่งยังใช้ค่าเก่าอยู่
- environment variable ไม่ได้ถูก inject เข้า container หรือ cron job
402 no_credit
ยอดเงินหมด หรือทดลองใช้ฟรี 7 วันหมดอายุ ไปหน้าบัญชีเพื่อเติมเงินแบบเติมเงินล่วงหน้าจะกู้คืนได้ แนะนำให้ตรวจสอบยอดเงินด้วยบริการของคุณเอง อย่ารอให้ผู้ใช้เจอข้อผิดพลาดก่อนค่อยมาตรวจสอบ
403 content_blocked
เนื้อหาถูกบล็อก เนื้อหาผู้ใหญ่ที่ถูกต้องตามกฎหมาย เรื่องแต่ง และหัวข้อที่มีข้อโต้แย้งจะไม่ถูกปฏิเสธ แต่เนื้อหาทางเพศที่เกี่ยวข้องกับเด็กจะถูกบล็อกทั้งหมด รวมถึงนวนิยายและบทบาทสมมติ เมื่อเจอ 403 ให้ตรวจสอบอินพุตและประวัติว่ามีเนื้อหาดังกล่าวหรือไม่ แทนที่จะเปลี่ยนคำพูดแล้วลองใหม่
404 เอนด์พอยต์ไม่พบ
มีเพียงสองเอนด์พอยต์: POST /v1/chat/completions และ GET /v1/models หากเส้นทางขาด /v1 มี斜杠 เกินไป พิมพ์ผิด หรือเรียกใช้ embeddings รูปภาพ หรืออินเทอร์เฟซอื่นๆ ที่ไม่รองรับ จะเกิด 404
ใน 400 มีอีกประเภทหนึ่งที่มักเข้าใจผิด: โทเคนของบทสนทนาที่ยาวจะเพิ่มขึ้นแบบเส้นตรงตามจำนวนรอบ การทดสอบในตอนกลางวันอาจปกติดี แต่หลังจากผ่านไปหลายสิบรอบจะเกิด 400 ทันที这不是接口不稳定,而是上下文到顶了。解决办法是给历史设上限,超过阈值就丢最旧的轮次,或者把旧内容压缩成摘要。别等报错再处理,在发请求前就估算好。
เคล็ดลับในการตรวจสอบ 401: พิมพ์คีย์ API 4 หลักแรกและ 4 หลักสุดท้ายลงในบันทึก (log) ไม่ต้องพิมพ์เนื้อหาทั้งหมด แล้วเปรียบเทียบกับที่แสดงในหน้าบัญชี วิธีนี้จะยืนยันได้ว่าคีย์ที่อ่านได้จากกระบวนการทำงานคือคีย์ที่คุณคิดหรือไม่ โดยเฉพาะในสภาพแวดล้อมของคอนเทนเนอร์และงานกำหนดเวลา การขาดตัวแปรสภาพแวดล้อมหรือการอ่านค่าเก่าเป็นสาเหตุที่พบบ่อยที่สุด
429 และ 503: สองประเภทที่ควรลองใหม่
429 ขีดจำกัดอัตรา
คีย์ละ 300 คำขอต่อนาที งานแบบแบตช์ การใช้คีย์เดียวกันโดยหลายอินสแตนซ์ หรือพายุการเรียกซ้ำ จะทำให้เกิดเหตุการณ์นี้ การแก้ไขมีสองชั้น: ไคลเอนต์ต้องจำกัดอัตราก่อน (ดูโค้ดด้านล่าง) แล้วจึงทำ retry แบบ backoff สำหรับ 429
503 upstream_busy
บริการกำลังยุ่งชั่วคราว ลองใหม่ในไม่กี่วินาทีก็ได้ อย่าส่งคำขอติดกันทันที หรือลองใหม่สิบครั้งภายในหนึ่งวินาที เพราะจะทำให้สถานการณ์แย่ลง
หมดเวลาเครือข่าย
การสร้างข้อความยาวใช้เวลานาน อย่าตั้งค่า timeout สั้นเกินไป ตัวอย่างใช้ 120 วินาที เมื่อ timeout แล้วลองใหม่ต้องระวัง: คำขอครั้งก่อนอาจทำงานที่เซิร์ฟเวอร์แล้ว การเรียกซ้ำอาจทำให้เสียเครดิตซ้ำ ดังนั้นสำหรับคำขอประเภทสร้างข้อความ ควรลดจำนวนครั้งในการ retry และควรใช้สตรีมมิงเพื่อลดเวลาในการรอต่อครั้ง
import os
import random
import time
import requests
URL = "https://api.wushenchaapi.com/v1/chat/completions"
HEADERS = {
"Authorization": "Bearer " + os.environ["API_KEY"],
"Content-Type": "application/json",
}
RETRYABLE = {429, 503} # 只重试这两类
FATAL = {400, 401, 402, 403, 404}
class ApiError(Exception):
def __init__(self, status, code, message):
super().__init__(f"{status} {code}: {message}")
self.status, self.code = status, code
def call(payload, max_retries=5, base=1.0, cap=30.0):
for attempt in range(max_retries + 1):
try:
r = requests.post(URL, headers=HEADERS, json=payload, timeout=120)
except (requests.ConnectionError, requests.Timeout):
r = None # 网络层失败,按可重试处理
if r is not None and r.ok:
return r.json()
if r is not None:
try:
err = r.json().get("error", {})
except ValueError:
err = {}
if r.status_code in FATAL or r.status_code not in RETRYABLE:
raise ApiError(r.status_code, err.get("code"), err.get("message"))
if attempt == max_retries:
raise ApiError(r.status_code if r is not None else 0, "retry_exhausted", "重试次数用尽")
delay = min(cap, base * (2 ** attempt)) * random.uniform(0.5, 1.0)
time.sleep(delay)
if __name__ == "__main__":
out = call({"model": "uncensored", "max_tokens": 100,
"messages": [{"role": "user", "content": "回复一个字:好"}]})
print(out["choices"][0]["message"]["content"])ข้อควรจำ: สูตรการถอยหลังคือ min(cap, base × 2^n) × สัมประสิทธิ์สุ่ม การสุ่มช่วยหลีกเลี่ยงการที่ไคลเอนต์หลายตัวลองใหม่พร้อมกันจนชนกัน; สถานะโค้ดในชุด FATAL จะไม่ลองใหม่เลย
การจำกัดอัตราฝั่งไคลเอนต์และคำขอวินิจฉัย
ดีกว่าที่จะจำกัดอัตราไว้ล่วงหน้า แทนที่จะรอจนได้ 429 แล้วค่อยถอยหลัง เครื่องมือเล็กๆ ด้านล่างนี้จะรับประกันว่าจำนวนคำขอภายในหนึ่งนาทีจะต่ำกว่าค่าที่ตั้งไว้ ปลอดภัยสำหรับมัลติเธรด:
import threading
import time
class RateGate:
"""简单的客户端限速:保证一分钟内请求数不超过 limit。"""
def __init__(self, limit=240): # 留出余量,低于 300/分钟
self.limit, self.stamps, self.lock = limit, [], threading.Lock()
def wait(self):
while True:
with self.lock:
now = time.time()
self.stamps = [t for t in self.stamps if now - t < 60]
if len(self.stamps) < self.limit:
self.stamps.append(now)
return
sleep_for = 60 - (now - self.stamps[0])
time.sleep(max(sleep_for, 0.05))วิธีที่สะอาดที่สุดในการตรวจสอบคือแยกจากโค้ดธุรกิจ แล้วใช้ curl ส่งคำขอขั้นต่ำ -i จะเห็นทั้งสถานะและหัวตอบกลับพร้อมกัน:
curl -i https://api.wushenchaapi.com/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"uncensored","max_tokens":20,"messages":[{"role":"user","content":"ping"}]}'curl ทำงานได้แต่โค้ดธุรกิจทำงานไม่ได้ ปัญหาคือโค้ดหรือสภาพแวดล้อมของคุณ (proxy, ตัวแปรสภาพแวดล้อม, การเข้ารหัส); curl ก็ทำงานไม่ได้ ให้ดูคีย์ API ยอดเงิน และเครือข่าย
ทำไมตั้งค่าขีดจำกัดอัตราที่ 240 แทนที่จะเป็น 300: เมื่อติดตั้งหลายอินสแตนซ์ แต่ละอินสแตนซ์จะจำกัดอัตราของตัวเอง แต่รวมกันอาจเกินขีดจำกัดรวมได้ นอกจากนี้การ retry ยังใช้โควตาเพิ่ม การเผื่อพื้นที่ไว้ 20% เป็นวิธีที่ปลอดภัยกว่า หากคุณมีหลายอินสแตนซ์ ให้แบ่งโควตาของแต่ละอินสแตนซ์เท่าๆ กันจากจำนวนรวม หรือใช้ตัวนับแบบแชร์เพื่อจัดการรวม
รายการตรวจสอบการแก้ไขข้อผิดพลาด
- อ่าน
error.codeใน body อย่าดูแค่อัตราการตอบกลับ - 401: ตรวจสอบ prefix ของ key, ตัวแปรสภาพแวดล้อม หรือการสร้าง key ใหม่
- 400: JSON ถูกต้องหรือไม่, prompt + max_tokens เกิน 100,000 หรือไม่, body ของคำขอเกิน 8 MB หรือไม่
- 402: ยอดเงินและอายุการใช้งานทดลองใช้
- 403: อินพุตและประวัติมีเนื้อหาที่ห้ามหรือไม่
- 404: เส้นทางเป็น /v1/chat/completions หรือ /v1/models หรือไม่
- 429: หลายอินสแตนซ์ใช้คีย์ API เดียวกันหรือไม่, มีการจำกัดอัตราฝั่งไคลเอนต์หรือไม่
- 503: ตรวจสอบว่าได้ทำ exponential backoff retry แล้วหรือยัง และเว้นระยะอย่างน้อยไม่กี่วินาที
- หมดเวลา: timeout เพียงพอหรือไม่, สามารถเปลี่ยนเป็นสตรีมมิงได้หรือไม่
- ตัดตัวเลือกทั้งหมดข้างต้นออกแล้ว: ใช้ curl เพื่อทำซ้ำด้วยคำขอที่น้อยที่สุด
เพิ่งเชื่อมต่อ API: ดูคู่มือการเชื่อมต่อ APIก่อน พารามิเตอร์อื่นๆ อยู่ในเอกสารประกอบ
วิธีใช้รายการ: เมื่อเกิดปัญหาให้ไล่ตรวจสอบจากบนลงล่างทีละข้อ อย่าข้ามไปมา ความผิดปกติส่วนใหญ่ที่ดู "แปลกประหลาด" มักจะอยู่ที่ 4 ข้อแรก
คำแนะนำเพิ่มเติม: บันทึกสรุปผลการแก้ไขข้อผิดพลาดกลับไปยังเอกสารของทีม เพื่อที่ครั้งหน้าเมื่อเจอข้อผิดพลาดเดียวกัน จะสามารถระบุสาเหตุได้ทันที