Phân biệt hai lỗi vì cách xử lý khác nhau.
Kèm chữ thừa (Here is the JSON: {...}, hoặc bọc JSON trong một khối code fence): xảy ra khi chỉ dặn bằng prompt. Cách chắc chắn là dùng structured output theo schema của provider (response_format với JSON Schema, responseSchema của Gemini, hoặc dùng tool call như một schema). Khi đó decoder bị ràng buộc ở tầng token, không sinh ra ký tự nằm ngoài schema. Nếu buộc phải dùng prompt-only thì bóc JSON bằng cách tìm cặp ngoặc cân đối, đừng dùng strip() theo cảm tính.
Bị cắt giữa chừng: gần như luôn là chạm max_tokens. Cách phát hiện chuẩn là đọc lý do kết thúc (finish_reason == "length" / stop_reason == "max_tokens"), chứ không phải bắt exception của json.loads.
if resp.choices[0].finish_reason == "length":
raise OutputTruncated # retry with a larger cap or a smaller task
data = json.loads(resp.choices[0].message.content)Cách phòng: đặt max_tokens đủ rộng so với schema; tránh trường tự do dài trong cùng response với dữ liệu cấu trúc; nếu cần danh sách lớn thì chia nhỏ nhiều lần gọi thay vì bắt model xuất một mảng 200 phần tử.
Cuối cùng, luôn validate lại bằng Pydantic/Zod sau khi parse — đúng cú pháp JSON không có nghĩa là đúng ràng buộc nghiệp vụ.