Menu

Structured output: lấy JSON ổn định từ LLM

Đầu ra có cấu trúc (structured output) là yêu cầu mô hình trả lời theo một dạng cố định, như JSON, bảng hay mẫu, để chương trình hoặc con người dùng được ngay mà không phải sắp xếp lại. Đưa ra schema, nói rõ cách xử lý giá trị bị thiếu, và kiểm tra kết quả bằng code.

Mọi prompt bên dưới đều có thể chỉnh sửa: thay đổi nó, rồi mở trong ChatGPT, Claude hoặc ứng dụng AI khác.

Đầu ra có cấu trúc (structured output) là câu trả lời theo một dạng bạn đã quyết định từ trước: một đối tượng JSON với các khóa có tên, một bảng với các cột cố định, hoặc một mẫu lần nào cũng có cùng các tiêu đề. Bạn cần nó bất cứ khi nào một chương trình đọc câu trả lời, và nó cũng có ích khi người đọc là con người, vì mọi câu trả lời khi đó đều trông giống nhau, dễ lướt và dễ so sánh.

Bí quyết là mô tả dạng câu trả lời chính xác đến mức mô hình không còn gì để tự chọn. Chỉ một chữ "JSON" không phải là mô tả. Khối bên dưới trích xuất thông tin từ một báo cáo lỗi. Hãy so sánh một yêu cầu lấy "các thông tin chính" với một yêu cầu nêu rõ schema.

Lấy ra các thông tin chính từ báo cáo lỗi này: "Từ bản cập nhật hôm qua, ứng dụng bị crash khi mình nhấn Xuất ở một dự án có hơn 50 ảnh. Các dự án nhỏ hơn thì xuất bình thường. Mình dùng Android 14, phiên bản ứng dụng 3.2.0. Lỗi này đang chặn việc giao hàng cho khách của mình."
Try it
Example replyReplies vary between models and runs.

Đây là các thông tin chính từ báo cáo lỗi:

  • Vấn đề: Ứng dụng bị crash khi xuất dự án
  • Điều kiện xảy ra: Nhấn Xuất ở dự án có hơn 50 ảnh
  • Bắt đầu từ: Sau bản cập nhật gần nhất
  • Nền tảng: Android 14
  • Phiên bản ứng dụng: 3.2.0
  • Mức ảnh hưởng: Cao, vì đang chặn việc giao hàng cho khách

Các dự án nhỏ hơn có vẻ vẫn xuất được bình thường.

Câu trả lời dạng văn bản tự do chính xác và dễ đọc, nhưng không có hai lần chạy nào dùng cùng các nhãn, mức độ nghiêm trọng là một câu thay vì một giá trị, và một chương trình sẽ phải đoán mỗi trường bắt đầu ở đâu. Câu trả lời JSON có thể đưa thẳng vào hệ thống quản lý lỗi. Hãy để ý rằng câu trả lời vẫn nằm trong một khối code: các ứng dụng chat thường bọc JSON theo cách đó, và điều này quan trọng khi bạn phân tích nó bằng code.

Cách yêu cầu JSON

Một yêu cầu JSON tốt trả lời trước mọi câu hỏi mà nếu không, mô hình sẽ tự trả lời thay bạn:

  • Mọi khóa, viết chính xác. Viết tên khóa trong dấu ngoặc kép đúng như chúng phải xuất hiện. Nói "đúng các khóa sau" để mô hình không thêm khóa khác.
  • Kiểu của mọi giá trị. Chuỗi, số, boolean, mảng chuỗi, đối tượng lồng nhau.
  • Các giá trị được phép cho trường có tập giá trị cố định, như mức độ nghiêm trọng hay danh mục. Không có danh sách này, bạn sẽ nhận được "High", "high", "severe" và "P1" qua bốn lần chạy.
  • Phải làm gì khi đầu vào thiếu một giá trị. Nếu bạn không nói "null nếu không được nêu", mô hình có xu hướng lấp chỗ trống bằng một phỏng đoán nghe hợp lý, và một phiên bản ứng dụng được đoán ra trông y hệt phiên bản thật.
  • Không có gì bao quanh. "Chỉ trả về JSON, không có văn bản nào trước hay sau nó" loại bỏ câu mở đầu thân thiện.

Khi dạng dữ liệu lồng nhau hoặc khác thường, cho xem một đối tượng ví dụ hoàn chỉnh sẽ hiệu quả hơn là mô tả. Đó là few-shot prompting áp dụng cho định dạng. Hãy để giá trị trong ví dụ khác hẳn đầu vào thật để mô hình không sao chép chúng.

Một prompt trích xuất dùng lại được

Khối này chia cùng một yêu cầu thành các phần. Tắt phần định dạng đi thì mô hình vẫn trả về JSON, vì phần ràng buộc yêu cầu như vậy, nhưng nó tự chọn tên khóa, như title thay vì job_title, và code đang chờ các khóa của bạn sẽ bị lỗi. Phần ràng buộc là thứ ngăn mô hình tự điền nốt một số điện thoại viết dở hay suy ra tên công ty từ tên miền email. Dán một chữ ký thật vào ô đầu vào để thử.

Trích xuất thông tin liên hệ dưới dạng JSON
Fill in
Parts
Trích xuất thông tin liên hệ từ chữ ký email bên dưới.
Trả về một đối tượng JSON với đúng các khóa sau: { "name": "string", "job_title": "string or null", "company": "string or null", "email": "string or null", "phone": "string or null" }
Dùng null cho mọi giá trị không có trong văn bản. Không đoán và không tự điền nốt các giá trị dở dang. Chỉ trả về JSON.
Priya Nair | Head of Data, Northwind Labs | priya.nair@northwind.example
Try it
Example replyReplies vary between models and runs.
{
  "name": "Priya Nair",
  "job_title": "Head of Data",
  "company": "Northwind Labs",
  "email": "priya.nair@northwind.example",
  "phone": null
}

Bảng và mẫu cố định

Đầu ra có cấu trúc không chỉ dành cho chương trình. Khi bạn tự đọc câu trả lời, một bảng markdown hay một mẫu cố định cũng mang lại lợi ích tương tự: bạn biết mỗi thông tin nằm ở đâu trước cả khi nhìn vào.

Bảng
So sánh list, tuple và set trong Python bằng một bảng markdown với các cột sau: Kiểu, Có thứ tự, Thay đổi được, Cho phép trùng lặp, Cách dùng điển hình. Mỗi kiểu một hàng. Không có văn bản ngoài bảng.
Try it
Example replyReplies vary between models and runs.
KiểuCó thứ tựThay đổi đượcCho phép trùng lặpCách dùng điển hình
listCóCóCóMột dãy bạn thêm, xóa hoặc sắp xếp phần tử
tupleCóKhôngCóMột nhóm giá trị cố định, như tọa độ
setKhôngCóKhôngLoại bỏ phần tử trùng và kiểm tra phần tử có thuộc tập hay không thật nhanh

Một mẫu cũng hoạt động theo cách tương tự với văn bản dài hơn: đưa ra các tiêu đề theo thứ tự và nói rõ nội dung dưới mỗi tiêu đề. "Trả lời với ba nhãn in đậm: Nguyên nhân, Cách sửa, Cách kiểm tra" cho ra đúng ba nhãn đó mỗi lần, giúp một loạt câu trả lời dễ so sánh với nhau.

JSON mode trong API

Nhiều API mô hình có một cài đặt buộc đầu ra là JSON đúng cú pháp. Trong OpenAI Python SDK, đó là response_format. JSON mode yêu cầu chữ "JSON" phải xuất hiện đâu đó trong các tin nhắn của bạn, nên system prompt bên dưới nhắc tới nó và liệt kê các khóa.

import json
from openai import OpenAI

client = OpenAI()
MODEL = "your-model-id"  # e.g. from your provider's model list

report_text = "Since yesterday's update the app crashes when I tap Export..."

response = client.chat.completions.create(
    model=MODEL,
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": (
                "Extract the bug report into JSON with the keys "
                "summary (string), severity (one of low, medium, high, critical) "
                "and steps_to_reproduce (array of strings)."
            ),
        },
        {"role": "user", "content": report_text},
    ],
)

data = json.loads(response.choices[0].message.content)

JSON mode bảo đảm văn bản phân tích được (trừ khi câu trả lời bị cắt ở giới hạn token), chứ không bảo đảm nó khớp với schema của bạn. Một khóa vẫn có thể bị thiếu, hoặc severity vẫn có thể là "urgent". Một số nhà cung cấp còn nhận một JSON Schema đầy đủ, dưới dạng định dạng đầu ra hoặc schema đầu vào của một công cụ (function), và một số chế độ trong đó giới hạn câu trả lời theo đúng schema. Hãy xem tài liệu của nhà cung cấp để biết chính xác tham số, vì các tính năng này khác nhau giữa các API.

Kiểm tra kết quả bằng code

Hãy đối xử với JSON của mô hình giống như mọi đầu vào từ bên ngoài chương trình: phân tích nó, rồi kiểm tra nó. Phân tích bắt được lỗi cú pháp. Kiểm tra bắt được một đối tượng hợp lệ nhưng nội dung sai.

ALLOWED_SEVERITIES = {"low", "medium", "high", "critical"}

def problems(data):
    if not isinstance(data, dict):
        return ["the answer must be a JSON object"]
    found = []
    if not isinstance(data.get("summary"), str):
        found.append("summary must be a string")
    if data.get("severity") not in ALLOWED_SEVERITIES:
        found.append("severity must be low, medium, high or critical")
    steps = data.get("steps_to_reproduce")
    if not isinstance(steps, list) or not all(isinstance(s, str) for s in steps):
        found.append("steps_to_reproduce must be an array of strings")
    return found

Khi kiểm tra thất bại, một lần thử lại thường là đủ để sửa: gửi cho mô hình chính đầu ra của nó cùng danh sách vấn đề và yêu cầu JSON đã sửa. Hãy giới hạn số lần thử lại và ghi log các lần thất bại, vì một trường hay bị lỗi là dấu hiệu prompt chưa rõ ràng. Trong các dự án lớn hơn, một thư viện kiểm tra dữ liệu như Pydantic hoặc một trình kiểm tra JSON Schema sẽ thay cho hàm viết tay.

Còn hai thói quen nữa giúp tránh những lỗi âm thầm. Đặt giới hạn token đầu ra đủ cao cho câu trả lời dài nhất bạn dự kiến, vì một đối tượng bị cắt ngang sẽ không bao giờ phân tích được. Và khi đầu vào dài hoặc đến từ người dùng, hãy tách nó khỏi chỉ dẫn bằng dấu phân cách hoặc thẻ XML để văn bản bên trong ít bị hiểu thành chỉ dẫn. Nếu một prompt vừa phải suy luận vừa phải tạo ra JSON, hãy cân nhắc prompt chaining: để một bước suy nghĩ bằng văn bản tự do và bước thứ hai chuyển kết quả thành cấu trúc.

Câu hỏi thường gặp

Làm sao để ChatGPT trả về JSON?

Nói rõ câu trả lời phải là JSON, liệt kê mọi khóa cùng kiểu dữ liệu, và đưa ra các giá trị được phép cho trường nào có tập giá trị cố định. Thêm câu "Chỉ trả về JSON, không có văn bản nào trước hay sau nó." Trong API, hãy bật thêm JSON mode bằng response_format={"type": "json_object"}, chế độ này yêu cầu chữ JSON phải xuất hiện trong các tin nhắn của bạn.

Vì sao mô hình thêm văn bản quanh JSON?

Mô hình chat được huấn luyện để trò chuyện, nên chúng thường mở đầu bằng một câu như "Đây là JSON" hoặc bọc đối tượng trong một khối code markdown. Hãy yêu cầu chỉ trả về JSON, và trong code thì dùng JSON mode của API hoặc bỏ khối code bao quanh trước khi phân tích.

JSON mode là gì?

JSON mode là một cài đặt của API khiến mô hình tạo ra JSON đúng cú pháp, miễn là câu trả lời không bị cắt ngang vì giới hạn token đầu ra. Nó không buộc mô hình tuân theo schema của bạn: các khóa vẫn có thể bị thiếu, viết sai hoặc sai kiểu. Một số nhà cung cấp còn có chế độ chặt chẽ hơn, nhận một JSON Schema đầy đủ và giới hạn đầu ra theo đúng schema đó.

LLM có luôn trả về JSON hợp lệ không?

Nếu chỉ dựa vào prompt thì không. Ngay cả một chỉ dẫn rõ ràng thỉnh thoảng vẫn thất bại, và một câu trả lời bị cắt vì giới hạn token đầu ra thì luôn không hợp lệ. Hãy phân tích mọi phản hồi bằng một trình phân tích JSON thật, kiểm tra các trường bạn cần, rồi thử lại hoặc báo lỗi rõ ràng khi kiểm tra không đạt.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu