Dùng Structured Outputs với API tương thích OpenAI: hướng dẫn thực tế về JSON Schema
Bài viết này được dịch và biên tập từ bài gốc tiếng Anh trên blog của Modelflare.
Hướng dẫn chi tiết đến từng field về output JSON Schema strict trên Responses API và Chat Completions, bao gồm các lớp validate, cách xử lý lỗi và bài test tương thích route.
Structured Outputs cho phép ứng dụng yêu cầu JSON tuân theo một schema đã định nghĩa, thay vì chỉ dặn model "hãy trả về JSON". Với một API tương thích OpenAI, cùng một schema có thể dùng qua Responses API hoặc Chat Completions, nhưng các field trong request khác nhau — và khả năng tương thích phải được kiểm chứng cho đúng model và route đã chọn.
Pattern an toàn cho production gồm ba lớp: dùng output với schema strict khi model hỗ trợ, parse và validate JSON trả về trong ứng dụng, rồi mới áp dụng business rule lên các giá trị. Tuân thủ schema loại bỏ được nhiều lỗi định dạng, nhưng không chứng minh câu trả lời của model là đúng về mặt dữ kiện hay ngữ nghĩa.
Structured Outputs không giống JSON mode
Có ba cách phổ biến để yêu cầu model trả về output dạng JSON.
| Phương pháp | Sinh JSON hợp lệ | Bắt buộc theo schema của bạn | Trường hợp sử dụng điển hình |
|---|---|---|---|
| Chỉ dùng prompt | Không đảm bảo | Không | Prototype chấp nhận được lỗi parse |
| JSON mode | Có, khi được hỗ trợ và hoàn tất | Không | JSON linh hoạt, ứng dụng tự validate cấu trúc |
Structured Outputs với strict: true |
Có, tùy vào việc hoàn tất và cách xử lý refusal | Có, trong phạm vi tập con JSON Schema được hỗ trợ | Trích xuất dữ liệu có kiểu và workflow của ứng dụng |
Hướng dẫn Structured Outputs của OpenAI khuyến nghị dùng Structured Outputs thay cho JSON mode khi model và endpoint được chọn có hỗ trợ. Hướng dẫn này cũng phân biệt hai nhiệm vụ khác nhau:
- Dùng response format có cấu trúc khi model cần trả lời người dùng bằng một object dữ liệu có thể dự đoán được.
- Dùng function calling khi model cần yêu cầu ứng dụng của bạn thực hiện một hành động.
Bài viết này tập trung vào nhiệm vụ thứ nhất. Một tool loop sau đó có thể tái sử dụng schema argument strict, nhưng Call ID và message kết quả của nó tuân theo một contract khác.
Định nghĩa schema trước khi chọn endpoint
Xét một workflow phân loại ticket hỗ trợ (support triage). Ứng dụng cần bốn field:
category: một trongbilling,technicalhoặcaccount;priority: số nguyên từ 1 đến 3;requires_human: kiểu boolean;summary: mô tả ngắn cho hàng đợi hỗ trợ.
JSON Schema như sau:
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": [
"billing",
"technical",
"account"
]
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 3
},
"requires_human": {
"type": "boolean"
},
"summary": {
"type": "string"
}
},
"required": [
"category",
"priority",
"requires_human",
"summary"
],
"additionalProperties": false
}
Đặt mọi property là bắt buộc và đặt additionalProperties thành false sẽ tạo ra một object ổn định cho code downstream. Điều đó không có nghĩa là mọi keyword của JSON Schema đều dùng được ở mọi nơi. OpenAI hỗ trợ một tập con đã được tài liệu hóa, còn một provider tương thích OpenAI khác có thể hỗ trợ một tập con khác, hoặc hoàn toàn không có chế độ schema strict.
Gửi schema qua Responses API
Responses API đặt schema của response trong text.format. Ví dụ dưới đây đặt API key của Modelflare làm biến môi trường; hãy chọn model đang khả dụng với API key của bạn và đã được kiểm chứng hỗ trợ Structured Outputs.
export MODELFLARE_API_KEY="<YOUR_API_KEY>"
export MODEL_ID="<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>"
curl -sS https://modelflare.dev/v1/responses \
-H "Authorization: Bearer $MODELFLARE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"input\": \"The customer was charged twice and wants a refund.\",
\"text\": {
\"format\": {
\"type\": \"json_schema\",
\"name\": \"support_ticket\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"category\": {
\"type\": \"string\",
\"enum\": [\"billing\", \"technical\", \"account\"]
},
\"priority\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 3
},
\"requires_human\": { \"type\": \"boolean\" },
\"summary\": { \"type\": \"string\" }
},
\"required\": [\"category\", \"priority\", \"requires_human\", \"summary\"],
\"additionalProperties\": false
},
\"strict\": true
}
}
}"
Trong response của Responses API, nội dung được sinh ra nằm trong các output item có kiểu. SDK có thể cung cấp helper tiện lợi như output text đã gộp hoặc output đã parse sẵn, nhưng một HTTP client không nên giả định object có cấu trúc là một field ở cấp cao nhất. Hãy đọc object response đã hoàn tất, tìm phần text output, rồi parse text đó thành JSON.
Gửi cùng schema qua Chat Completions
Chat Completions đặt schema của response trong response_format.json_schema. Phần thân schema giữ nguyên; chỉ lớp bọc là khác.
curl -sS https://modelflare.dev/v1/chat/completions \
-H "Authorization: Bearer $MODELFLARE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"messages\": [
{
\"role\": \"user\",
\"content\": \"The customer was charged twice and wants a refund.\"
}
],
\"response_format\": {
\"type\": \"json_schema\",
\"json_schema\": {
\"name\": \"support_ticket\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"category\": {
\"type\": \"string\",
\"enum\": [\"billing\", \"technical\", \"account\"]
},
\"priority\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 3
},
\"requires_human\": { \"type\": \"boolean\" },
\"summary\": { \"type\": \"string\" }
},
\"required\": [\"category\", \"priority\", \"requires_human\", \"summary\"],
\"additionalProperties\": false
},
\"strict\": true
}
}
}"
Với Chat Completions, text JSON thường được trả về trong choices[0].message.content. Hãy parse nội dung đó thay vì coi chuỗi này là một object đã có kiểu.
Có thể tóm tắt hai lớp bọc như sau:
| Mục đích | Responses API | Chat Completions |
|---|---|---|
| Nơi chứa schema | text.format |
response_format.json_schema |
| Kiểu format | text.format.type |
response_format.type |
| Tên schema | text.format.name |
response_format.json_schema.name |
| Thân schema | text.format.schema |
response_format.json_schema.schema |
| Cờ strict | text.format.strict |
response_format.json_schema.strict |
| Vị trí kết quả | Output item có kiểu / helper output text | choices[0].message.content |
Lớp tương thích OpenAI của Modelflare có logic chuyển đổi tường minh giữa hai lớp bọc này trên các route OpenAI/Codex được hỗ trợ. Tuy nhiên, việc chuyển đổi không làm cho một model upstream vốn không hỗ trợ Structured Outputs bỗng nhiên hỗ trợ. Với các họ model khác được cung cấp qua pass-through Chat Completions nguyên bản, contract request chính xác của provider upstream vẫn là chuẩn tham chiếu.
Validate cấu trúc và ý nghĩa một cách riêng biệt
Giả sử model trả về:
{
"category": "billing",
"priority": 2,
"requires_human": true,
"summary": "Customer reports a duplicate charge and requests a refund."
}
Object này thỏa mãn schema, nhưng ứng dụng vẫn cần thực hiện hai bước kiểm tra riêng biệt.
Validate cấu trúc
Dùng một JSON Schema validator hoặc helper có kiểu của SDK để xác nhận:
- object chứa đầy đủ mọi property bắt buộc;
- không có property nào ngoài dự kiến;
- mỗi giá trị có đúng kiểu yêu cầu;
- category nằm trong enum cho phép;
- priority nằm trong khoảng cho phép.
Kể cả khi provider cam kết tuân thủ strict, validate phía ứng dụng vẫn bảo vệ phần còn lại của hệ thống khỏi các route không hỗ trợ, lỗi tích hợp, nội dung bị cắt cụt và những thay đổi contract trong tương lai.
Validate ngữ nghĩa và nghiệp vụ
Schema không thể xác định khách hàng có thật sự bị trừ tiền hai lần hay không, priority 2 có đúng không, hay có cần người duyệt hoàn tiền không. Những quyết định đó cần dữ liệu gốc và chính sách của ứng dụng.
Với các workflow có tác động lớn, hãy coi object của model là một đề xuất phân loại. Đối chiếu nó với dữ liệu gốc đáng tin cậy, áp dụng quyền hạn và giới hạn tài chính bằng code tất định, và lưu lại một tham chiếu audit an toàn. Đừng bao giờ để một object hợp lệ về cấu trúc do model sinh ra vượt qua ranh giới phân quyền, billing hay bảo mật.
Xử lý output không hoàn chỉnh và các trường hợp ngoại lệ
Parser cho production cần nhiều hơn một lời gọi JSON.parse cho trường hợp suôn sẻ.
Refusal
Model có thể từ chối một request vì lý do an toàn. Contract structured output của OpenAI giúp phân biệt refusal với output schema thông thường. Hãy kiểm tra status của response và cách biểu diễn refusal trước khi tìm object JSON; đừng báo refusal là lỗi parse schema.
Bị cắt cụt và giới hạn output
Nếu quá trình sinh dừng lại trước khi object hoàn chỉnh, không schema nào có thể khôi phục phần đuôi bị thiếu. Hãy kiểm tra trạng thái hoàn tất và lý do dừng (stop reason) của endpoint. Đặt giới hạn output đủ cho object hợp lệ lớn nhất, đồng thời giữ cho schema và nội dung được yêu cầu ở mức có giới hạn.
Keyword schema không được hỗ trợ
Các cài đặt strict output thường chỉ hỗ trợ một tập con của JSON Schema. Hãy bắt đầu với object, array, kiểu nguyên thủy, enum, field bắt buộc và hành vi tường minh cho additional property. Kiểm tra tài liệu hiện hành của provider trước khi dùng reference, cấu trúc đệ quy, union phức tạp hoặc các keyword validate nâng cao.
Latency khi dùng schema lần đầu
Một số provider tiền xử lý và cache schema mới. Vì vậy, request đầu tiên với một schema mới có thể mất nhiều thời gian hơn các request sau. Hãy tái sử dụng các schema ổn định, có version thay vì sinh một schema riêng cho mỗi request, và tách số đo lần đầu khỏi các so sánh latency ở trạng thái ổn định.
Không tương thích model hoặc route
Một endpoint tương thích OpenAI có thể chấp nhận Chat Completions thông thường nhưng lại từ chối json_schema, bỏ qua strict, hoặc chuyển tiếp các field này tới một model không hỗ trợ chúng. Một response 200 có chứa JSON vẫn chưa chứng minh được việc tuân thủ strict. Hãy test với input không hợp lệ và các edge case, rồi validate object nhận được.
Chạy bài test tương thích trước khi lên production
Dùng một ma trận nhỏ cho mỗi model và route bạn định bật.
| Bài test | Bằng chứng cần ghi lại |
|---|---|
| Object bắt buộc tối thiểu | Status, model, route, object đã parse, kết quả validate |
| Từng giá trị enum | Mỗi giá trị cho phép có được sinh ra và parse được hay không |
| Thiếu thông tin | Model dùng giá trị fallback có giới hạn hay hỏi lại để làm rõ |
| Input kích hoạt cơ chế an toàn | Cách biểu diễn refusal và cách ứng dụng xử lý |
| Giới hạn output thấp | Trạng thái hoàn tất và cách xử lý khi bị cắt cụt |
| Tính năng schema không được hỗ trợ | Báo lỗi tường minh hay âm thầm bỏ qua ràng buộc |
| Lớp bọc Responses và Chat | Cả hai có tạo ra object tương đương cho ứng dụng hay không |
| Lặp lại schema ổn định | Thời gian lần đầu so với ở trạng thái ổn định |
Đừng so sánh các model với schema, prompt, region, chế độ streaming hoặc giới hạn output khác nhau rồi quy kết quả chỉ cho chất lượng model. Hãy ghi lại ngày test chính xác, vì hành vi của provider và tình trạng khả dụng của model có thể thay đổi.
Chọn endpoint sau khi đã test workflow
Dùng Responses API khi ứng dụng đã làm việc với output item có kiểu, streaming event của Responses API hoặc một workflow tool rộng hơn trên Responses API. Dùng Chat Completions khi ứng dụng có tích hợp ổn định dựa trên message và route được chọn hỗ trợ contract response_format. Quyết định ở góc nhìn rộng hơn được trình bày trong Responses API vs Chat Completions.
Dù chọn endpoint nào:
- giữ một nguồn JSON Schema duy nhất, có version;
- chuyển schema đó sang lớp bọc của endpoint mà không thay đổi ý nghĩa;
- validate response đã hoàn tất trước khi sử dụng;
- áp dụng business rule tất định sau bước validate cấu trúc;
- theo dõi riêng các lỗi parse, refusal, bị cắt cụt và không tương thích.
Khi mới migrate client, hãy bắt đầu với OpenAI-Compatible API Guide. Về parse event và stream bị gián đoạn, hãy xem AI API Streaming Guide. Sau đó, kiểm chứng Structured Outputs trên đúng route mà API key của bạn sẽ sử dụng.
Structured Outputs hữu ích nhất khi chúng thu hẹp response của model thành một interface đáng tin cậy cho ứng dụng. Chúng không thay thế việc kiểm chứng dữ kiện, phân quyền hay logic billing — và "tương thích OpenAI" vẫn là một tuyên bố cần được test theo từng tính năng, chứ không phải một sự bảo đảm có thể suy ra từ Base URL.
Tác giả là đội ngũ phát triển Modelflare, một AI API gateway; nếu bạn muốn dùng nhiều provider qua một API tương thích OpenAI, có thể tham khảo thêm tại https://modelflare.dev.
All rights reserved