Đến cuối hướng dẫn này, bạn sẽ biết cách gọi OpenAI Structured Outputs từ code của mình: truyền JSON Schema, bật strict: true, nhận JSON khớp đúng hình dạng đã khai báo, xử lý lỗi/từ chối, và tạo bộ kiểm thử API trong Apidog để xác minh payload thực tế luôn tuân thủ hợp đồng.
Chuẩn bị trước khi bắt đầu
Structured Outputs giới hạn đầu ra của mô hình theo JSON Schema bạn cung cấp. Khi đặt strict: true, mô hình phải trả về JSON hợp lệ và khớp schema: đủ trường bắt buộc, đúng kiểu dữ liệu, enum chỉ nhận giá trị đã khai báo.
So với prompt kiểu “chỉ trả về JSON”, cách này đáng tin cậy hơn nhiều. Prompt tự do vẫn có thể sinh thêm văn bản, thiếu field, sai kiểu hoặc trả về format không mong muốn. Structured Outputs chuyển “mong muốn” thành ràng buộc ở thời điểm sinh token.
Bạn cần:
- API key OpenAI trong biến môi trường
OPENAI_API_KEY. - Một model hỗ trợ strict schema.
- Một JSON Schema mô tả output mong muốn.
- Một cách kiểm thử response thực tế, ví dụ bằng Apidog.
Chọn model hỗ trợ Structured Outputs
Structured Outputs có trên các model OpenAI gần đây, bắt đầu từ dòng GPT-4o và tiếp tục qua dòng GPT-5. Tài liệu OpenAI hiện khuyến nghị bắt đầu dự án mới với flagship mới nhất của họ, ví dụ gpt-5.5 tại thời điểm bài viết gốc.
Các model cũ hơn, bao gồm dòng gpt-3.5, có thể hỗ trợ JSON mode nhưng không hỗ trợ schema strict. Nếu code của bạn phụ thuộc vào strict: true, hãy kiểm tra model ID cụ thể trước khi deploy.
Có hai tính năng dễ nhầm:
JSON mode
{
"response_format": {
"type": "json_object"
}
}
JSON mode chỉ đảm bảo output là JSON hợp lệ về cú pháp. Nó không đảm bảo đủ field, đúng kiểu, đúng enum hoặc đúng schema nghiệp vụ.
Structured Outputs
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "your_schema",
"strict": true,
"schema": {}
}
}
}
Structured Outputs đảm bảo output là JSON hợp lệ và khớp JSON Schema bạn truyền vào.
| JSON mode | Structured Outputs strict | |
|---|---|---|
| Tham số | response_format: {"type":"json_object"} |
response_format với type: "json_schema", strict: true
|
| JSON hợp lệ | Có | Có |
| Khớp schema | Không | Có |
| Field bắt buộc được thực thi | Không | Có |
| Kiểu dữ liệu và enum được thực thi | Không | Có |
| Cần validate sau đó | Luôn cần | Vẫn nên làm ở tầng test/CI |
Lưu ý API: Chat Completions dùng response_format. API Responses mới hơn biểu diễn cùng ý tưởng dưới text.format với type: "json_schema". Quy tắc schema giống nhau, chỉ khác wrapper.
Gửi request Structured Outputs đầu tiên
Ví dụ: bạn muốn trích xuất một ticket hỗ trợ từ nội dung người dùng.
Request dưới đây yêu cầu model trả về đúng schema support_ticket.
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [
{
"role": "system",
"content": "Trích xuất phiếu hỗ trợ vào schema."
},
{
"role": "user",
"content": "Hệ thống thanh toán của tôi báo lỗi 500 mỗi khi tôi sử dụng thẻ đã lưu. Bắt đầu từ hôm nay. Tài khoản: acct_8842."
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"summary": {
"type": "string"
},
"category": {
"type": "string",
"enum": ["billing", "bug", "account", "other"]
},
"severity": {
"type": "integer"
},
"account_id": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
}
},
"required": ["summary", "category", "severity", "account_id"],
"additionalProperties": false
}
}
}
}'
Đọc response
Model trả về message.content là một chuỗi JSON khớp schema, ví dụ:
{
"summary": "Hệ thống thanh toán trả về HTTP 500 khi thanh toán bằng thẻ đã lưu",
"category": "bug",
"severity": 3,
"account_id": "acct_8842"
}
Trong code, bạn vẫn parse chuỗi JSON này như bình thường:
import json
msg = response.choices[0].message
ticket = json.loads(msg.content)
print(ticket["category"])
Điểm quan trọng: account_id không bị bỏ qua khi thiếu dữ liệu. Vì mọi field đều phải nằm trong required, field “tùy chọn” nên được mô hình hóa bằng anyOf với null.
Viết schema đúng tập con được hỗ trợ
Structured Outputs chấp nhận một tập con của JSON Schema. Khi thiết kế schema, hãy tuân thủ các quy tắc sau.
1. Root phải là object
Không đặt schema cấp cao nhất là array hoặc string.
Không nên:
{
"type": "array",
"items": {
"type": "string"
}
}
Nên bọc array trong object:
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["items"],
"additionalProperties": false
}
2. Mọi property phải nằm trong required
Structured Outputs không có “optional field” theo kiểu truyền thống. Nếu một field có thể không có giá trị, hãy cho phép null.
{
"account_id": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
}
}
Sau đó vẫn đưa field này vào required.
{
"required": ["account_id"]
}
3. Luôn đặt additionalProperties: false
Mỗi object nên chặn key ngoài schema:
{
"type": "object",
"properties": {
"summary": { "type": "string" }
},
"required": ["summary"],
"additionalProperties": false
}
Điều này giúp model không sinh thêm field ngoài hợp đồng.
4. Giữ schema nhỏ và nông
Schema có giới hạn kích thước, khoảng 100 property object và tối đa 5 cấp lồng nhau. Nếu schema quá sâu hoặc quá lớn, hãy tách nhỏ hoặc làm phẳng.
5. Đừng xem mọi keyword JSON Schema là ràng buộc được model đảm bảo
Một số keyword dùng cho validation như:
patternformatminLengthminimum
không được đảm bảo bởi model. Nếu bạn cần kiểm tra regex, định dạng email, ngày tháng, range số hoặc rule nghiệp vụ, hãy validate sau khi nhận response.
Ví dụ:
if ticket["severity"] < 1 or ticket["severity"] > 5:
raise ValueError("severity phải nằm trong khoảng 1-5")
Nếu bạn từng làm việc với các trường tùy chọn hoặc oneOf/anyOf/allOf trong JSON Schema, cách tư duy tương tự vẫn áp dụng: schema khóa hình dạng, còn rule nghiệp vụ nên được kiểm tra riêng.
Xử lý refusal, truncation và tool calls
Có trường hợp output không khớp schema một cách có chủ đích: model từ chối yêu cầu không an toàn.
Khi đó response có thể chứa trường refusal thay vì JSON theo schema. Code của bạn nên kiểm tra nhánh này trước khi parse.
import json
msg = response.choices[0].message
if msg.refusal:
handle_refusal(msg.refusal)
else:
ticket = json.loads(msg.content)
process_ticket(ticket)
Ngoài refusal, còn hai tình huống cần xử lý:
Response bị cắt do
max_tokens
JSON có thể bị cắt giữa chừng. Hãy kiểm tra trạng thái kết thúc response và tăng token limit nếu cần.Parallel tool calls
Structured Outputs không hỗ trợ parallel tool calls. Khi kết hợp tool calling, đặt:
{
"parallel_tool_calls": false
}
Kiểm thử schema trong Apidog
Strict mode giúp model sinh JSON theo schema, nhưng bạn vẫn nên kiểm thử response thực tế. Lý do:
- Model có thể được thay thế.
- Prompt có thể bị chỉnh sửa.
- Schema có thể thay đổi.
- Một field trong
requiredcó thể bị xóa nhầm. - Luồng refusal hoặc lỗi có thể thay đổi.
Đây là nơi Apidog hữu ích: bạn lưu request, chạy lại nhiều lần, validate response theo schema và đưa vào CI.
Phân chia trách nhiệm như sau:
- OpenAI strict mode: sinh JSON theo schema.
- Apidog: kiểm tra response nhận được có khớp schema/hợp đồng mong đợi hay không.
Quy trình kiểm thử đề xuất
Bước 1: Tạo request trong Apidog
Tạo request gọi Chat Completions và đặt phần body chứa response_format.
{
"model": "gpt-5.5",
"messages": [
{
"role": "system",
"content": "Trích xuất phiếu hỗ trợ vào schema."
},
{
"role": "user",
"content": "Hệ thống thanh toán của tôi báo lỗi 500 mỗi khi tôi sử dụng thẻ đã lưu. Bắt đầu từ hôm nay. Tài khoản: acct_8842."
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"summary": { "type": "string" },
"category": {
"type": "string",
"enum": ["billing", "bug", "account", "other"]
},
"severity": { "type": "integer" },
"account_id": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
}
},
"required": ["summary", "category", "severity", "account_id"],
"additionalProperties": false
}
}
}
}
Lưu request này vào collection để chạy lại được.
Bước 2: Thêm assertion cho response
Bạn nên kiểm tra tối thiểu:
-
summarylà string. -
categorythuộc enumbilling | bug | account | other. -
severitylà integer. -
account_idlà string hoặcnull. - Không có field ngoài schema.
Apidog có thể xác thực response dựa trên JSON Schema, giúp collection fail khi payload lệch hợp đồng.
Bước 3: Chạy trong CI
Đưa collection vào pipeline. Khi prompt, schema hoặc model thay đổi, test sẽ chạy lại. Nếu output không còn khớp schema, build fail sớm thay vì lỗi ở production.
Bước 4: Mock API cho consumer
Trước khi tích hợp OpenAI thật, hoặc khi muốn test frontend/backend consumer mà không tốn token, tạo mock API trả về response mẫu hợp lệ.
Ví dụ mock response:
{
"summary": "Hệ thống thanh toán trả về HTTP 500 khi thanh toán bằng thẻ đã lưu",
"category": "bug",
"severity": 3,
"account_id": "acct_8842"
}
Consumer có thể phát triển dựa trên hợp đồng ổn định. Khi tích hợp OpenAI thật sẵn sàng, bạn thay mock bằng request thật. Bạn có thể tải xuống Apidog để quản lý request, assertion và mock trong cùng một nơi.
FAQ
JSON mode có còn cần thiết không?
Có, JSON mode vẫn đảm bảo output là JSON hợp lệ. Nhưng nó không thực thi schema.
Với code mới, hãy dùng Structured Outputs với strict: true nếu bạn có hình dạng dữ liệu cố định. Chỉ dùng JSON mode khi model không hỗ trợ strict schema hoặc khi bạn thật sự không cần schema cố định.
Root schema có thể là array không?
Không. Root phải là object.
Thay vì:
{
"type": "array"
}
hãy dùng:
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["items"],
"additionalProperties": false
}
Làm sao tạo field tùy chọn?
Không bỏ field khỏi required. Hãy cho phép giá trị null.
{
"type": "object",
"properties": {
"account_id": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
}
},
"required": ["account_id"],
"additionalProperties": false
}
Key luôn tồn tại, nhưng value có thể là null.
strict: true có nghĩa là bỏ hết validation không?
Không. Strict mode đảm bảo cấu trúc JSON khớp schema, nhưng các rule cấp giá trị như pattern, format, range số hoặc logic nghiệp vụ vẫn nên được validate trong code hoặc test.
Nếu bạn mới làm quen với schema, xem thêm hướng dẫn JSON Schema.
Nên dùng model nào?
Structured Outputs hoạt động trên GPT-4o trở lên, bao gồm dòng GPT-5. Hãy xác nhận model ID cụ thể hỗ trợ strict schema trước khi phụ thuộc vào tính năng này trong production.
Tổng kết
Quy trình triển khai thực tế:
- Chọn model hỗ trợ Structured Outputs.
- Gửi request với
response_format.type = "json_schema". - Đặt
strict: true. - Viết schema với root object, đầy đủ
required, vàadditionalProperties: false. - Dùng
nullđể mô hình hóa field có thể thiếu giá trị. - Kiểm tra
refusaltrước khi parse JSON. - Validate thêm rule nghiệp vụ trong code/test.
- Dùng Apidog để lưu request, assertion, mock và chạy kiểm thử trong CI.
Model cam kết hình dạng dữ liệu. Test của bạn xác minh cam kết đó vẫn đúng sau mỗi thay đổi.

Top comments (0)