如何基于 Pydantic 模型自动生成 OpenAI 兼容的 function 描述?
解读
在国内大模型落地场景中,OpenAI 兼容接口已成为事实标准,无论是私有化部署的百亿参数模型还是公有云上的千亿参数服务,function calling 都是让大模型与业务系统对接的核心能力。
面试官问这道题,想验证三件事:
- 你是否真正理解 OpenAI function schema 的字段语义(name / description / parameters / required 等)。
- 你是否能把 Pydantic 的字段元数据 无损映射到该 schema,并处理中文业务描述。
- 你是否能在 LLMOps 流水线 里把“模型-函数”契约自动化,而不是让算法同事手写 JSON。
知识点
-
OpenAI function 描述规范
必须返回纯 JSON Schema 2020-12 子集,字段层级:
{name, description, parameters: {type: "object", properties: {...}, required: [...]}}。
任何不合规字段(如额外的 default、title)都会导致国内多数推理网关直接 400 Bad Request。 -
Pydantic 元数据捕获
Field(description=...)会落入schema()的'description'键。- 中文描述必须保证 utf-8 无 BOM,否则部分国产网关会截断。
- 嵌套 BaseModel 需要递归展开,遇到
Union[...]时要降级为anyOf,否则与 JSON Schema 不兼容。
-
自动生成策略
- 优先使用 Pydantic v2 的
.model_json_schema(),再对title做裁剪,只保留properties / required / type。 - 对 List[str]、Dict[str, int] 等泛型,要把 Python 类型映射成
array与object,并补全items / additionalProperties。 - 如果业务侧需要 示例值,可在
Field(json_schema_extra={"example": ...})里写入,再额外弹出到x-example供前端调试,但正式 schema 需剔除,防止网关校验失败。
- 优先使用 Pydantic v2 的
-
LLMOps 集成
把生成函数封装成 CLI 插件:pdm run gen-func-schemas --module=src.domain.tools --out=dist/functions.json,在 CI 阶段校验 schema 是否可解析,再注入 Kubernetes ConfigMap,供推理服务热加载。这样任何字段变更都能 版本化追踪,避免上线后大模型调用失败。
答案
给出一个可直接落地的 Python 实现,兼容 Pydantic v2 与国内主流推理网关:
from typing import Any, Dict, List, Type
from pydantic import BaseModel, Field
import json
def to_openai_function(model_cls: Type[BaseModel], name: str, description: str) -> Dict[str, Any]:
"""
将 Pydantic 模型转成 OpenAI 函数描述
适用于 国内百亿/千亿参数模型推理网关
"""
# 1. 生成 JSON Schema
raw_schema = model_cls.model_json_schema()
# 2. 剔除无关字段,保留严格子集
clean_properties = {}
for k, v in raw_schema.get("properties", {}).items():
clean_prop = {k2: v2 for k2, v2 in v.items() if k2 in {"type", "description", "enum", "items", "properties", "additionalProperties", "anyOf"}}
if "description" not in clean_prop and model_cls.model_fields[k].description:
clean_prop["description"] = model_cls.model_fields[k].description
clean_properties[k] = clean_prop
parameters = {
"type": "object",
"properties": clean_properties,
"required": raw_schema.get("required", [])
}
# 3. 组装 OpenAI 格式
function_desc = {
"name": name,
"description": description,
"parameters": parameters
}
# 4. 国内网关兼容性二次校验
try:
json.dumps(function_desc, ensure_ascii=False) # 中文必须非 ascii
except (TypeError, ValueError) as e:
raise RuntimeError("生成的 schema 无法通过 JSON 序列化,请检查嵌套类型") from e
return function_desc
# ----------------- 业务示例 -----------------
class DeliveryAddress(BaseModel):
province: str = Field(description="收件人所在省,如 浙江省")
city: str = Field(description="收件人所在市,如 杭州市")
detail: str = Field(description="详细地址,不包含省市")
class CreateOrderParams(BaseModel):
user_id: int = Field(description="用户主键")
sku_list: List[str] = Field(description="商品 SKU 列表")
address: DeliveryAddress = Field(description="收货地址")
func = to_openai_function(
CreateOrderParams,
name="create_order",
description="国内电商场景下,为用户创建订单"
)
print(json.dumps(func, ensure_ascii=False, indent=2))
运行后输出即为 可直接投喂给国产大模型推理服务的 function 描述,无需人工二次编辑。
拓展思考
-
动态多函数注册
在 真实 LLMOps 平台 中,函数会随着业务迭代频繁增删。可以把所有工具函数放到一个__init__.py里,通过pkgutil.iter_modules自动扫描,再批量生成functions.json,最后调用 推理服务的热更新接口(如/v1/functions/reload)完成 零停机发布。 -
安全与合规
国内监管要求 生成内容可追溯,因此要在 schema 里额外注入x-business-id字段,用于链路追踪;同时利用 字段级脱敏注解(Field(json_schema_extra={"sensitive": True})),在日志落盘前自动打码,防止用户地址、手机号外泄。 -
性能优化
当函数参数非常庞大(如上百字段)时,可把parameters先转成 Avro Schema 再转回 JSON Schema,利用 Avro 的 字段编号 实现差分传输,降低每次请求 30% 以上的网络开销,这在 私有化集群跨机房调用 场景下尤为关键。
掌握以上要点,即可在面试中向考官展示你对 大模型真实业务落地 的完整闭环思考,而不仅仅停留在“写一段代码”层面。