Cấu trúc lỗi và thứ tự đọc
Mọi phản hồi thất bại đều có cấu trúc giống nhau:
{"error":{"code":"...","message":"..."}}
Thứ tự đọc cố định gồm ba bước:
- Mã trạng thái HTTP, xác định nhóm lỗi;
error.code, xác định nguyên nhân cụ thể để lập nhánh;error.message, dùng cho người đọc và ghi log, không dùng để khớp chuỗi.
Trước tiên, hãy xác định rõ ranh giới: những lỗi có thể khắc phục bằng cách thử lại (429, 503 và thời gian chờ mạng) và những lỗi phải thay đổi mới khắc phục được (các lỗi còn lại). Việc trộn lẫn hai loại này là nguyên nhân phổ biến nhất gây ra sự cố khi vận hành, chẳng hạn như liên tục thử lại với mã 402, dẫn đến hàng chục yêu cầu không hợp lệ mỗi giây.
Trong nhật ký, bạn nên cố định ghi lại bốn trường: thời gian, mã trạng thái, error.code và số token prompt ước tính của yêu cầu hiện tại. Khi sự cố xảy ra, bạn có thể ngay lập tức nhận ra liệu một loại lỗi cụ thể nào đó có tăng đột biến hay tổng thể các yêu cầu đều thất bại. Hãy cấu hình một quy tắc cảnh báo: chỉ cần xuất hiện một lần mã 402 thì hãy thông báo ngay lập tức vì điều này có nghĩa là dịch vụ đã không khả dụng cho người dùng; đối với mã 429, hãy xem xét tỷ lệ, nếu chỉ xuất hiện ngẫu nhiên thì không cần xử lý, nhưng nếu xuất hiện liên tục thì cho thấy thiết kế độ đồng thời có vấn đề.
Tra cứu nhanh mã trạng thái
| Mã trạng thái | error.code | Ý nghĩa | Thử lại? |
|---|---|---|---|
| 400 | — | Yêu cầu không hợp lệ, ví dụ prompt cộng max_tokens vượt quá 100k | Không |
| 401 | — | Khóa API không hợp lệ hoặc thiếu | Không |
| 402 | no_credit | Hết dư lượng hoặc hết hạn dùng thử miễn phí | Không |
| 403 | content_blocked | Nội dung bị chặn | Không |
| 404 | — | Endpoint không tồn tại | Không |
| 429 | — | Đã đạt giới hạn tốc độ | Có, dùng backoff |
| 503 | upstream_busy | Dịch vụ đang bận tạm thời | Có, sau vài giây |
Dấu “—” trong bảng nghĩa là không có code cố định nào cần xử lý đặc biệt; bạn chỉ cần lập nhánh theo mã trạng thái.
4xx: Sửa yêu cầu, đừng thử lại
400 Yêu cầu không hợp lệ
- Nguyên nhân 1: Lỗi định dạng JSON, thường do gõ tay chuỗi mà chưa escape dấu ngoặc kép.
- Nguyên nhân 2: prompt cộng max_tokens vượt quá 100,000. Hội thoại dài dễ kích hoạt lỗi này.
- Nguyên nhân 3: Kích thước yêu cầu vượt quá 8 MB.
- Khắc phục: serialize bằng JSON; ước lượng token trước khi gửi, cắt lịch sử hoặc giảm max_tokens nếu vượt; xemlong context.
401 Lỗi khóa API
- Thiếu Header hoặc thiếu tiền tố
Bearer. - Đã tạo lại key, key cũ mất hiệu lực ngay lập tức nhưng một số máy vẫn đang dùng giá trị cũ.
- Biến môi trường chưa được đưa vào container hoặc tác vụ định kỳ.
402 no_credit
Số dư đã hết hoặc thời gian dùng thử 7 ngày đã hết hạn. Bạn có thể khôi phục bằng cách nạp tiền vào số dư trả trước tại trang tài khoản. Bạn nên tự giám sát số dư của dịch vụ mình, đừng đợi người dùng báo lỗi mới phát hiện.
403 content_blocked
Nội dung bị chặn. Các nội dung dành cho người trưởng thành hợp pháp, các chủ đề hư cấu và gây tranh cãi sẽ không bị từ chối, nhưng mọi nội dung liên quan đến tình dục của trẻ vị thành niên đều bị chặn, bao gồm cả tiểu thuyết và nhập vai. Khi gặp mã 403, bạn nên kiểm tra xem đầu vào và lịch sử có chứa các nội dung này hay không, thay vì thay đổi cách diễn đạt và thử lại.
404 endpoint không tồn tại
Chỉ có hai endpoint: POST /v1/chat/completions và GET /v1/models. Nếu bạn bỏ sót /v1 trong đường dẫn, thêm dấu gạch chéo thừa, viết sai chính tả hoặc gọi đến các endpoint không hỗ trợ như embeddings, hình ảnh, v.v., hệ thống sẽ trả về lỗi 404.
Một lỗi dễ bị hiểu nhầm trong nhóm 400 là: số token của hội thoại dài tăng tuyến tính theo số lượt. Bạn có thể thấy mọi thứ hoạt động bình thường khi thử nghiệm vào ban ngày, nhưng sau vài chục lượt, lỗi 400 đột nhiên xuất hiện. Đây không phải do API không ổn định, mà là do cửa sổ ngữ cảnh đã đạt giới hạn. Giải pháp là đặt giới hạn cho lịch sử hội thoại: khi vượt quá ngưỡng, bạn hãy loại bỏ các lượt cũ nhất hoặc nén nội dung cũ thành tóm tắt. Đừng đợi đến khi có lỗi mới xử lý; bạn hãy ước lượng trước khi gửi yêu cầu.
Một mẹo để kiểm tra lỗi 401 là in ra bốn ký tự đầu và bốn ký tự cuối của key vào log, không in toàn bộ nội dung, sau đó so sánh với thông tin hiển thị trên trang tài khoản. Cách này giúp bạn xác nhận xem biến môi trường trong quá trình của bạn có đọc đúng key bạn nghĩ hay không, đặc biệt quan trọng trong môi trường container hoặc tác vụ định kỳ, nơi nguyên nhân phổ biến nhất là thiếu biến môi trường hoặc đọc phải giá trị cũ.
429 và 503: Hai loại cần thử lại
429 giới hạn tốc độ
Mỗi key được giới hạn 300 yêu cầu mỗi phút. Các tác vụ batch, nhiều instance dùng chung một key hoặc bão thử lại đều có thể kích hoạt giới hạn này. Cách khắc phục gồm hai lớp: phía client cần tự giới hạn tốc độ trước (xem mã nguồn bên dưới), sau đó thực hiện thử lại với cơ chế backoff khi nhận được 429.
503 upstream_busy
Dịch vụ đang bận tạm thời. Bạn chỉ cần đợi vài giây rồi thử lại. Đừng gửi liên tục ngay lập tức, cũng đừng thử lại mười lần trong một giây vì điều đó sẽ làm tình trạng tồi tệ hơn.
Hết thời gian chờ mạng
Sinh văn bản dài tốn nhiều thời gian hơn, bạn đừng đặt timeout quá ngắn, ví dụ dùng 120 giây. Khi thử lại sau khi hết thời gian chờ, bạn cần lưu ý: yêu cầu trước đó có thể đã được thực thi trên máy chủ, việc gọi lại có thể làm tiêu hao thêm tín dụng. Vì vậy, với các yêu cầu sinh nội dung, bạn nên giảm số lần thử lại và tốt nhất là kết hợp với truyền phát (streaming) để rút ngắn thời gian chờ cho mỗi lần gọi.
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"])Điểm mấu chốt: công thức backoff là min(cap, base × 2^n) × hệ số ngẫu nhiên. Độ rung ngẫu nhiên (jitter) giúp tránh việc nhiều client cùng thử lại một lúc; các mã trạng thái trong tập FATAL sẽ không bao giờ được thử lại.
Giới hạn tốc độ phía client và yêu cầu chẩn đoán
Thay vì đợi 429 rồi mới thực hiện backoff, bạn nên giới hạn tốc độ ngay từ đầu. Công cụ nhỏ dưới đây đảm bảo số lượng yêu cầu trong một phút luôn thấp hơn giá trị đã đặt, và an toàn cho đa luồng:
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))Khi kiểm tra sự cố, cách sạch nhất là tách khỏi mã nguồn nghiệp vụ, dùng curl để gửi một yêu cầu tối thiểu. Cờ -i cho phép bạn xem đồng thời dòng trạng thái và các header phản hồ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"}]}'Nếu curl hoạt động nhưng mã nguồn nghiệp vụ của bạn không hoạt động, vấn đề nằm ở mã nguồn hoặc môi trường của bạn (proxy, biến môi trường, mã hóa); nếu curl cũng không hoạt động, hãy kiểm tra key, số dư và mạng.
Tại sao giới hạn tốc độ được đặt ở mức 240 thay vì 300: Khi triển khai nhiều phiên bản, mỗi phiên bản có giới hạn tốc độ riêng, nhưng tổng cộng vẫn có thể vượt quá tổng mức; cộng thêm việc thử lại sẽ chiếm thêm quota. Để lại 20% dư lượng là cách an toàn hơn. Nếu bạn có nhiều phiên bản, hãy chia đều tổng mức cho mỗi phiên bản hoặc sử dụng bộ đếm chia sẻ để điều phối tập trung.
Danh sách kiểm tra khắc phục sự cố
- Đọc
error.codetrong body, đừng chỉ xem mã trạng thái. - 401: tiền tố khóa API, biến môi trường, xem bạn đã tạo lại khóa chưa.
- 400: JSON có hợp lệ không, prompt + max_tokens có vượt quá 100,000 không, body của yêu cầu có vượt quá 8 MB không.
- 402: Số dư và thời hạn dùng thử.
- 403: Dữ liệu đầu vào và lịch sử có chứa nội dung bị cấm không.
- 404: Đường dẫn có phải là /v1/chat/completions hoặc /v1/models không.
- 429: Có nhiều instance dùng chung một key không, bạn đã áp dụng giới hạn tốc độ phía client chưa.
- 503: Bạn đã thực hiện thử lại với cơ chế backoff chưa, khoảng thời gian thử lại có ít nhất vài giây không.
- Hết thời gian chờ: timeout có đủ lớn không, bạn có thể chuyển sang chế độ stream không.
- Nếu đã loại trừ tất cả các trường hợp trên: hãy dùng curl với yêu cầu tối thiểu để tái hiện lỗi.
Nếu bạn vừa mới tích hợp, hãy xem hướng dẫn tích hợp. Các tham số khác có trong tài liệu.
Cách sử dụng danh sách: Khi gặp sự cố, bạn hãy loại trừ từng mục từ trên xuống dưới, đừng nhảy cóc. Hầu hết các sự cố "kỳ lạ" cuối cùng đều nằm trong bốn mục đầu tiên.
Bổ sung một kinh nghiệm: Hãy ghi lại kết luận của mỗi lần khắc phục sự cố vào tài liệu nhóm. Lần sau gặp cùng lỗi, bạn có thể đối chiếu ngay.