Streaming
Đặt stream: true là gateway chuyển tiếp server-sent events theo nhịp model sinh chữ. Trang này nói về những phần SDK không tự lo cho bạn: chunk usage cuối, delta reasoning, và lỗi xảy ra sau khi HTTP 200 đã cam kết.
Bật streaming
curl -N https://api.clfaigateway.dev/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k2.6",
"messages": [{"role": "user", "content": "Explain SSE in one sentence."}],
"stream": true,
"stream_options": {"include_usage": true}
}'Chunk đến dưới dạng sự kiện data: và stream kết thúc bằng data: [DONE]. Chunk cuối luôn mang số token chính thức trong usage — mọi stream đều có, bất kể bạn có xin hay không. stream_options.include_usage chỉ đổi hình dạng các chunk ở giữa: bật thì chúng mang "usage": null (đúng schema OpenAI); tắt thì vắng hẳn trường này.
JSON mode không stream được: response_format kiểu JSON đi cùng stream: true trả 400 json_mode_no_stream.
Model reasoning
Model reasoning (cả chín model đang mở) stream phần suy nghĩ qua delta.reasoning_content trước khi câu trả lời bắt đầu trong delta.content. Gateway chuyển tiếp nguyên văn để bạn hiển thị trực tiếp. Mỗi delta reasoning tăng bộ đếm reasoning_tokens, trả trong completion_tokens_details của chunk usage cuối.
Phần suy nghĩ tiêu từ chính max_tokens — hãy chừa ngân sách cho nó
Toàn bộ ngân sách completion phải gánh cả phần suy luận lẫn câu trả lời. Ngân sách cạn giữa lúc đang nghĩ thì bạn nhận finish_reason "length" với rất ít hoặc không có chữ nào — và token vẫn bị tính vì model đã thật sự tiêu chúng. Mức "ăn" khác nhau theo model: chúng tôi đo kimi-k2.6 tiêu 1.500-3.000 token suy nghĩ trên prompt phân tích và tới 8.000 trên một câu đố logic, ở mọi mức reasoning_effort trừ none — nên loại việc đó hãy cho nó max_tokens từ 8.000 trở lên, hoặc gửi none khi không cần phần nghĩ. GLM 5.3 còn ăn dữ hơn trên context khổng lồ — với prompt ~240K token, chúng tôi thấy nó tiêu hơn 7.000 token chỉ để nghĩ trước khi có chữ nào hiện ra, nên việc agentic cỡ đó hãy chừa từ 8.000 trở lên; nhiều tool agent mặc định thấp hơn và bóp chết câu trả lời. Thấy câu trả lời rỗng thì xem finish_reason trước khi trách model: "length" nghĩa là ngân sách chết, không phải model hỏng.
Chưa thấy gì trên màn hình? Prefill và phần nghĩ đi trước
Prompt rất lớn thì token đầu đến muộn là chủ đích: prefill chạy cỡ 72-77 ms mỗi 1.000 token input trên các model cửa sổ 1M, nên prompt 250K token mất ~20 giây trước khi có gì để stream — và model reasoning có thể nghĩ thêm vài phút nữa trước khi delta.content bắt đầu. Hãy hiển thị delta.reasoning_content ngay khi nó về để người dùng thấy tiến độ, và để read-timeout phía client từ 60 giây trở lên với prompt lớn. Trong log của chúng tôi, ca "treo" phổ biến nhất thật ra là client bỏ cuộc giữa lúc model đang nghĩ: dòng đó nằm trong usage log dạng success gắn cờ client-disconnected, chỉ tính token đã phát.
reasoning_tokens chỉ để nhìn
Token reasoning là tập con của completion_tokens và không bao giờ bị tính tiền như một chiều giá riêng — con số này tồn tại để bạn thấy vì sao câu trả lời dài hơn phần chữ nhìn thấy. Request không stream không tách được phần này nên reasoning_tokens ở đó bằng 0.
reasoning_effort theo từng model
Mỗi model nhận một bộ mức reasoning_effort riêng, và cùng một tên mức không có nghĩa là cùng độ sâu ở mọi model. Chúng tôi chạy thử mọi mức hợp lệ ngày 25/09/2026 — một câu đố logic ngắn, 3-8 lượt mỗi mức — và ghi trung vị completion tokens (phần nghĩ cộng câu trả lời). Độ dài phần nghĩ dao động mạnh giữa các lượt, nên hãy đọc đây là xu hướng, không phải cam kết.
| Model | Mức nhận | Mức thật sự thay đổi gì |
|---|---|---|
deepseek-v4-flash | low, medium, high, xhigh, max | Gần như không điều khiển được — low còn nghĩ lâu hơn (~2.600 token) high (~1.100). Không tắt được phần nghĩ. |
deepseek-v4-pro | low, medium, high, xhigh, max | Có tác dụng nhưng không đều: low ~880 → high ~1.300 → xhigh ~4.100 token; max nằm giữa (~1.600). Không tắt được phần nghĩ. |
glm-4.7-flash | low, medium, high | Không khác biệt nhất quán — mức nào nó cũng nghĩ dài (4.000-7.500 token). |
glm-5.2 | low, medium, high, xhigh, max | Hai nấc: low, medium và high như nhau (~1.800-2.900 token); xhigh và max nghĩ lâu hơn (~3.100-4.500). Không tắt được phần nghĩ. |
glm-5.3 | low, medium, high, max | Hai nấc: low và high nghĩ ngắn (~460 token); medium và max nghĩ đầy đủ (~1.300). Không tắt được phần nghĩ. |
glm-5.3-flash | low, medium, high, xhigh, max | Ba nấc: low gần như không nghĩ (~95 token — nhanh nhất, nhưng trả lời sai câu đố 2 trên 3 lượt); high nghĩ ngắn (~415); medium, xhigh và max nghĩ đầy đủ (~630-1.200). |
kimi-k2.6 | none, low, medium, high | none tắt hẳn phần nghĩ (~880 token, kém chính xác hơn với bài nhiều bước); low, medium và high đều nghĩ hết cỡ (~6.600-7.100). |
kimi-k2.7-code | low, medium, high | Luôn nghĩ hết cỡ — đổi mức không tạo khác biệt đo được. |
qwen3.8-27b | low, medium, xhigh | Có bậc thật, tăng nhẹ: low ~650 → medium ~740 → xhigh ~1.000 token. |
Không gửi reasoning_effort thì gateway gửi low — với GLM 5.3 Flash nghĩa là gần như không nghĩ, nên việc nhiều bước trên model đó hãy gửi high hoặc max. Gửi mức model không nhận thì nhận 400 reasoning_effort_not_supported kèm danh sách mức hợp lệ. Kể cả none trên các model không tắt được phần nghĩ: upstream sẽ lặng lẽ chạy nó như một mức nghĩ đầy đủ và vẫn tính tiền phần nghĩ, nên gateway từ chối thay vì để bạn trả tiền cho thứ mình không xin.
Lỗi sau khi stream đã bắt đầu
Phát byte đầu tiên xong là HTTP status đã chốt 200, không đổi được nữa. Nếu upstream hỏng giữa chừng, gateway phát đúng một sự kiện cuối mang finish_reason: "error" và object error, rồi [DONE]:
data: {"id":"req_01j9zxg0aabbccddeeff00112233","object":"chat.completion.chunk","created":1774694600,"model":"kimi-k2.6","choices":[{"index":0,"delta":{},"finish_reason":"error"}],"error":{"message":"Upstream provider error while streaming. You were charged only for tokens already delivered.","type":"api_error","code":"upstream_error","param":null}}
data: [DONE]SDK sẽ không tự báo lỗi này
SDK chỉ nhìn HTTP status sẽ thấy một stream thành công nhưng kết thúc sớm. Hãy kiểm finish_reason trên từng chunk — mẫu Python và JavaScript phía trên đã làm — và coi "error" là request hỏng. Bạn chỉ bị tính phần token đã nhận trước lúc hỏng.
Nếu bạn ngắt kết nối trước
Ngắt kết nối không hủy phần đã sinh: bạn bị tính số token đã phát tới lúc ngắt, đếm chính xác từ usage per-chunk — không bao giờ ước lượng. Chặn trần xấu nhất bằng max_tokens. Toàn bộ chính sách phía tiền, kể cả bảo hiểm zero-completion, nằm ở Billing.
Stream chốt sổ ra sao: trường status
Mỗi request xuất hiện trong GET /v1/generation?id={x-request-id} sau khi chốt sổ, kèm status:
| status | Nghĩa là | Bạn trả |
|---|---|---|
success | Stream đã xong — kể cả trường hợp bạn chủ động ngắt sớm. | Toàn bộ token đã phát. |
partial | Upstream hỏng giữa stream; bạn đã nhận finish_reason: "error". | Chỉ phần token đã phát trước lúc hỏng. |
error | Request hỏng trước khi có bất kỳ output nào. | $0 khi bảo hiểm zero-completion áp dụng — xem Billing. |
Chi phí stream đọc sau khi xong
Stream không mang header x-gw-cost-nano — header đã gửi đi trước khi biết chi phí. Đọc chi phí đã chốt từ GET /v1/generation bằng header x-request-id (cũng chính là trường id của mọi chunk).
Một quy tắc riêng nữa của stream: Idempotency-Key không hỗ trợ cho request stream — Idempotency giải thích vì sao.
Nếu stream của bạn dồn về một cục
Gateway flush từng chunk ngay lập tức và không bao giờ nén text/event-stream. Nếu chunk vẫn dồn về một cục ở cuối, có thứ gì đó GIỮA chúng tôi và code của bạn đang buffer — gần như luôn là proxy công ty, hoặc reverse proxy của chính bạn (nginx mặc định buffer response: đặt proxy_buffering off; cho route SSE, hoặc tôn trọng quy ước X-Accel-Buffering: no). Nền tảng serverless buffer response cũng gây hệt vậy.
Cách phân định nhanh: chạy đúng request đó bằng curl -N từ chính mạng bị nghi. curl chảy mượt thì chỗ buffer nằm trong stack của bạn, không phải trên đường truyền.
Sau proxy công ty, cấu hình SDK tường minh (httpx ≥ 0.28 đổi tên tùy chọn thành số ít proxy): Python OpenAI(http_client=httpx.Client(proxy="http://proxy:8080")), Node qua fetchOptions. Gặp APIConnectionError nghĩa là request chưa tới được chúng tôi — soi proxy/DNS/TLS phía bạn; APIStatusError mới là đã tới.
Timeout phía client và model reasoning
Mặc định của SDK (timeout tổng 10 phút, Python áp per-read khi stream) an toàn cho model reasoning. Sai lầm phổ biến là hạ nó toàn cục — timeout=30 — trong khi model reasoning có thể nghĩ 30–70 giây trước token nhìn-thấy-được đầu tiên với prompt dài (chúng tôi stream reasoning_content sớm chính là để kết nối không bao giờ im lặng lâu). Timeout quá thấp ném APITimeoutError giữa chừng, rồi SDK tự gửi lại cả request — với call không stream mà thiếu Idempotency-Key, thế là tính tiền hai lần.
# Python — connect ngắn, read rộng (thay vì một timeout nhỏ toàn cục)
import httpx
client = OpenAI(
base_url="https://api.clfaigateway.dev/v1",
api_key=os.environ["CLF_API_KEY"],
timeout=httpx.Timeout(600.0, connect=5.0),
)