Nguyên Nhân Gây Ra Lỗi Này
Bạn định nghĩa một Pydantic model, chuyển đổi thành JSON schema, truyền vào API function calling của OpenAI — và nhận lại:
ValidationError: Invalid JSON for OpenAI function call
Có hai vấn đề có thể xảy ra. API của OpenAI có thể từ chối schema ngay lập tức. Hoặc nó chấp nhận lời gọi nhưng việc parse phản hồi trở lại thành đối tượng Pydantic lại thất bại vì cấu trúc không khớp với kỳ vọng của bạn. Dù theo cách nào, thủ phạm hầu như luôn là sự không khớp giữa những gì Pydantic tạo ra và những gì OpenAI thực sự mong đợi.
Quy Trình Debug
Bước 1 — In ra schema thực tế bạn đang gửi
Đừng đoán mò — hãy dump schema ra và đọc nó.
import json
from pydantic import BaseModel
from typing import Optional, List
class SearchParams(BaseModel):
query: str
max_results: int = 10
category: Optional[str] = None
# Pydantic v2
schema = SearchParams.model_json_schema()
print(json.dumps(schema, indent=2))
Dán kết quả vào JSON Formatter & Validator — nó làm nổi bật các vấn đề cấu trúc ngay lập tức và hoàn toàn chạy trên trình duyệt, không có gì rời khỏi máy của bạn.
Bước 2 — Kiểm tra những gì OpenAI thực sự nhận được
Bật debug logging để ghi lại request API thô:
import openai
import logging
logging.basicConfig(level=logging.DEBUG)
openai.log = "debug"
Tìm payload functions hoặc tools trong debug output. Đó chính là schema mà OpenAI đang xác thực.
Bước 3 — Xác định nguyên nhân gốc rễ
Các thủ phạm phổ biến nhất:
- Dùng
.schema()thay vì.model_json_schema()(thay đổi không tương thích của Pydantic v2) $defs/$reftrong các model lồng nhau — OpenAI không giải quyết các tham chiếu nội bộanyOftừ các trườngOptional— một số model OpenAI từ chối pattern này- Thiếu
additionalProperties: false— bắt buộc trong strict mode - Các trường metadata thừa của Pydantic như
titletrên các thuộc tính lồng nhau
Giải Pháp
Cách fix 1 — Dùng đúng phương thức schema cho phiên bản Pydantic của bạn
Pydantic v2 đã đổi tên .schema() thành .model_json_schema(). Điều này gây ra nhiều nhầm lẫn khi migration — phương thức cũ vẫn hoạt động trong v2 nhưng trả về cảnh báo deprecation và đôi khi trả về cấu trúc không tương thích.
# Pydantic v1
schema = MyModel.schema()
# Pydantic v2 — dùng cái này
schema = MyModel.model_json_schema()
Cách fix 2 — Làm phẳng các tham chiếu model lồng nhau
Lồng một Pydantic model vào trong model khác, và schema được tạo ra sẽ xuất hiện các con trỏ $defs và $ref. API function calling của OpenAI không giải quyết những tham chiếu này nội bộ — nó gặp tham chiếu chưa được giải quyết và thất bại.
from pydantic import BaseModel
from typing import List
class Item(BaseModel):
name: str
quantity: int
class Order(BaseModel):
items: List[Item]
shipping_address: str
# Cái này sẽ chứa $defs — gây vấn đề với OpenAI
raw_schema = Order.model_json_schema()
# Làm phẳng $defs bằng cách inline chúng
def inline_refs(schema: dict, defs: dict) -> dict:
if '$ref' in schema:
ref_key = schema['$ref'].split('/')[-1]
return inline_refs(defs[ref_key], defs)
result = {}
for key, value in schema.items():
if key == '$defs':
continue
elif isinstance(value, dict):
result[key] = inline_refs(value, defs)
elif isinstance(value, list):
result[key] = [inline_refs(i, defs) if isinstance(i, dict) else i for i in value]
else:
result[key] = value
return result
defs = raw_schema.get('$defs', {})
flat_schema = inline_refs(raw_schema, defs)
print(json.dumps(flat_schema, indent=2))
Cách fix 3 — Xử lý các trường Optional tạo ra anyOf
Pydantic v2 render Optional[str] thành {"anyOf": [{"type": "string"}, {"type": "null"}]}. OpenAI strict mode từ chối kiểu null bên trong anyOf. Có hai cách tiếp cận — ghi đè từng trường, hoặc loại bỏ null khỏi toàn bộ schema trong một lần xử lý.
from pydantic import BaseModel, Field
from typing import Optional
class SearchParams(BaseModel):
query: str
category: Optional[str] = Field(
default=None,
json_schema_extra={"type": "string"} # Ghi đè anyOf bằng kiểu đơn giản
)
Hoặc xử lý hậu kỳ schema để loại bỏ null khỏi anyOf:
def strip_null_anyof(schema: dict) -> dict:
"""Thay thế {anyOf: [T, null]} bằng chỉ T"""
if 'anyOf' in schema:
non_null = [s for s in schema['anyOf'] if s.get('type') != 'null']
if len(non_null) == 1:
return {**non_null[0], **{k: v for k, v in schema.items() if k != 'anyOf'}}
return {k: strip_null_anyof(v) if isinstance(v, dict) else v for k, v in schema.items()}
Cách fix 4 — Thêm additionalProperties: false cho strict mode
Strict function calling có một quy tắc cứng: mọi object trong schema phải rõ ràng không cho phép các thuộc tính thêm vào. Pydantic không thêm điều này theo mặc định.
def add_strict_flags(schema: dict) -> dict:
if schema.get('type') == 'object':
schema['additionalProperties'] = False
for value in schema.get('properties', {}).values():
add_strict_flags(value)
return schema
strict_schema = add_strict_flags(MyModel.model_json_schema())
Cách fix 5 — Dùng instructor để bỏ qua công việc thủ công
Thư viện instructor được xây dựng đặc biệt cho vấn đề này. Nó xử lý việc làm phẳng $ref, dọn dẹp anyOf, và các cờ strict mode mà không cần bạn viết một hàm tiện ích schema nào:
pip install instructor
import instructor
from openai import OpenAI
from pydantic import BaseModel
client = instructor.from_openai(OpenAI())
class UserInfo(BaseModel):
name: str
age: int
user = client.chat.completions.create(
model="gpt-4o",
response_model=UserInfo,
messages=[{"role": "user", "content": "Extract: John is 30 years old"}]
)
print(user) # UserInfo(name='John', age=30)
Không cần chỉnh sửa schema. Không cần các hàm xử lý hậu kỳ. Đáng áp dụng nếu bạn gặp vấn đề này nhiều hơn một lần.
Xác Minh Bản Fix
Trước khi gọi API thật, hãy kiểm tra schema cục bộ với jsonschema:
import jsonschema
import json
schema = flat_schema # schema đã xử lý của bạn
# Kiểm tra schema có hợp lệ JSON Schema không
try:
jsonschema.Draft7Validator.check_schema(schema)
print("Schema is valid")
except jsonschema.SchemaError as e:
print(f"Schema error: {e.message}")
# Kiểm tra với một payload mẫu
sample = {"query": "test", "max_results": 5}
try:
jsonschema.validate(sample, schema)
print("Sample payload validates correctly")
except jsonschema.ValidationError as e:
print(f"Payload error: {e.message}")
Cả hai kiểm tra đều qua? Hãy thực hiện lời gọi API. Không còn ValidationError nghĩa là bản fix đã hoạt động.
Bài Học Rút Ra
- In schema ra trước. Dán vào một trình xác thực và đọc nó. Hầu hết các lỗi trở nên rõ ràng ngay lập tức — không cần chạm vào API.
- Migration Pydantic v1 → v2 là bẫy phổ biến. Nếu lỗi này xuất hiện ngay sau khi nâng cấp,
.schema()→.model_json_schema()có lẽ là bản fix duy nhất bạn cần. - OpenAI strict mode nghĩa là nghiêm ngặt thực sự. Mọi trường phải được khai báo, mọi object phải có
additionalProperties: false, không có$refở bất kỳ đâu trong cây schema. - Cho môi trường production, hãy dùng
instructor. Nó được duy trì đặc biệt cho tích hợp Pydantic-to-OpenAI và theo dõi các thay đổi API để bạn không phải làm vậy. - Để kiểm tra schema nhanh, JSON Formatter & Validator rất tiện dụng — dán schema vào và phát hiện các cấu trúc lỗi ngay lập tức, không cần cài đặt.

