Sửa lỗi Pydantic ValidationError: Invalid JSON for OpenAI Function Call

intermediate🧠 AI Tools2026-05-17| Python 3.9+, pydantic>=2.0, openai>=1.0.0 (cũng ảnh hưởng pydantic v1 với openai<1.0)

Error Message

ValidationError: Invalid JSON for OpenAI function call
#pydantic#openai#function-calling#json-schema

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 / $ref trong các model lồng nhau — OpenAI không giải quyết các tham chiếu nội bộ
  • anyOf từ các trường Optional — 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ư title trê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$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.

Related Error Notes