0

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 trong billing, technical hoặc account;
  • 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:

  1. giữ một nguồn JSON Schema duy nhất, có version;
  2. chuyển schema đó sang lớp bọc của endpoint mà không thay đổi ý nghĩa;
  3. validate response đã hoàn tất trước khi sử dụng;
  4. áp dụng business rule tất định sau bước validate cấu trúc;
  5. 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí