Đầu ra có cấu trúc & gọi hàm
Xin một mô hình JSON và nó vui vẻ sản xuất gần-JSON: một dấu phẩy thừa, một khóa không nháy, một câu thân mật trước ngoặc đầu. Lấy mẫu tự do luôn có thể nhả token không hợp lệ, vì sampler chẳng biết gì về schema của bạn. Đầu ra có cấu trúc khép khoảng trống đó, và gọi hàm là cùng cơ chế đội mũ khác.
Vì sao JSON tự do gãy
Mỗi token sinh ra là một lần lấy mẫu từ một phân phối. Ở mỗi bước một khối xác suất — nhỏ, nhưng không bao giờ bằng không ở nhiệt độ trên 0 — nằm trên các token sẽ phá định dạng của bạn: ngoặc đóng trước ngoặc mở, dấu phẩy sau phần tử mảng cuối, true chữ thường bị viết True (thói quen Python nướng vào dữ liệu huấn luyện). Nhân "gần như luôn hợp lệ" trên 200 token đầu ra và bạn được sự gãy ngắt quãng, không tái lập — loại lỗi tệ nhất. Ngay temperature 0 cũng chỉ làm đầu ra tất định, không phải hợp lệ schema.
Bậc thang giải pháp
| Bậc | Cơ chế | Đảm bảo | Ở đâu |
|---|---|---|---|
| Chỉ-prompt ("hãy trả lời JSON hợp lệ") | Nhờ tử tế | Không — lấy mẫu vẫn có thể sai | bất kỳ engine |
| Chế độ JSON | Có prompt cộng giới hạn vào cú pháp JSON | JSON hợp lệ cú pháp; hình dáng không đảm bảo | API tương thích OpenAI, format: "json" của Ollama |
| Ràng buộc theo schema | Schema → ngữ pháp → logits bị che | Đầu ra luôn parse được và khớp schema | llama.cpp (json_schema / GBNF), guided_json của vLLM, structured outputs của Ollama, outlines / xgrammar |
Mỗi bậc tốn tính linh hoạt và mua tính tất định. Dùng bậc cao nhất bất cứ khi nào người tiêu thụ là mã.
Decode ràng buộc bởi ngữ pháp
Biên dịch schema thành một ngữ pháp. Ở mỗi bước decode, mỗi token không thể kéo dài một tài liệu hợp lệ bị đặt logit thành −vô-cực trước khi lấy mẫu. Đầu ra không hợp lệ không phải khó xảy ra — nó bất khả thi. llama.cpp xuất xưởng ngữ pháp GBNF; ngữ pháp số-thập-phân là một dòng:
root ::= "-"? [0-9]+ ("." [0-9]+)?
Cùng ý tưởng bọc JSON Schema. Against một server llama.cpp cục bộ (endpoint tương thích OpenAI):
import json, requests
schema = {
"type": "object",
"properties": {
"title": {"type": "string"},
"component": {"type": "string",
"enum": ["auth", "upload", "ui", "api"]},
"severity": {"type": "string",
"enum": ["low", "medium", "high"]}
},
"required": ["title", "component", "severity"],
"additionalProperties": False
}
r = requests.post("http://localhost:8080/v1/chat/completions", json={
"messages": [{"role": "user",
"content": "Extract the bug: " + report}],
"response_format": {
"type": "json_schema",
"json_schema": {"name": "bug", "strict": True,
"schema": schema}
},
"temperature": 0
})
bug = json.loads(r.json()["choices"][0]["message"]["content"])
Ollama làm tương tự với một JSON Schema trong trường format; vLLM với guided_json. Cùng cơ chế, ba cách đánh vần — tên trường chính xác của engine bạn nằm ở tham số engine.
Ràng buộc không đồng nghĩa đúng
Ngữ pháp đảm bảo hình dáng, không đảm bảo sự thật. Mô hình có thể nhả {"severity": "low"} cho một báo cáo sập, hoặc nhồi một trường chuỗi bằng chữ đệm. Che cũng có thể làm đau các trường mở: khi mô hình muốn xuống đoạn mà ngữ pháp cấm xuống dòng, chất lượng tụt. Quy tắc thực dụng:
- Enum hẹp thay chuỗi tự do bất cứ khi nào không gian giá trị đã biết.
requiredcho mọi trường bạn tiêu thụ;additionalProperties: falseđể chặn sáng chế.- Giữ trường chuỗi ngắn; đường văn dài ra khỏi phần trả lời ràng buộc.
- Vẫn đặt một ví dụ làm-sẵn của đầu ra mong muốn vào prompt — nó lái nội dung, thứ ngữ pháp không làm được.
Gọi hàm và gọi công cụ
Gọi công cụ là đầu ra có cấu trúc cộng một quy ước: mô hình trả về gọi hàm nào với tham số gì; mã của bạn thực thi nó và nối kết quả. Hình dáng tools của OpenAI là phương ngữ de facto mà các engine cục bộ bắt chước:
{
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["C", "F"]}
},
"required": ["city"]
}
}
}]
}
Mô hình trả lời với tool_calls trong tin nhắn; bạn trả đầu ra như một tin nhắn role: "tool" và vòng lặp tiếp tục. Mô hình được huấn luyện cho các phương ngữ công cụ cụ thể — template dòng-dõi Hermes và Qwen được hỗ trợ tốt nhất trên engine cục bộ (llama.cpp với --jinja, Ollama, vLLM với tự động chọn công cụ). Mô hình không qua huấn luyện công cụ tạo các cuộc gọi trông tin được với tên tham số bịa ra bất kể bạn ràng buộc gì, nên chọn mô hình cho việc đó trước: chọn mô hình.
Kiểm chứng cả parse lẫn nội dung
Hai phép kiểm sau mỗi cuộc gọi. Thứ nhất, parse — json.loads thành công (ngữ pháp thường đảm bảo; chế độ JSON thì không phải luôn). Thứ hai, ngữ nghĩa: khóa bắt buộc có mặt, enum trong khoảng, con số hợp lý, chuỗi không rỗng. Khi thất bại, thử lại một lần với lỗi kiểm-chứng được đưa ngược vào prompt — các lần thử lại có ràng buộc rẻ và thường thành công ngay lần nảy đầu.
Chính xác vòng lặp này — nhả một cuộc gọi, kiểm chứng, thực thi, quan sát — là thứ agent chạy cả ngày: agent.