如何基于 Pydantic 模型自动生成 OpenAI 兼容的 function 描述?

解读

在国内大模型落地场景中,OpenAI 兼容接口已成为事实标准,无论是私有化部署的百亿参数模型还是公有云上的千亿参数服务,function calling 都是让大模型与业务系统对接的核心能力。
面试官问这道题,想验证三件事:

  1. 你是否真正理解 OpenAI function schema 的字段语义(name / description / parameters / required 等)。
  2. 你是否能把 Pydantic 的字段元数据 无损映射到该 schema,并处理中文业务描述。
  3. 你是否能在 LLMOps 流水线 里把“模型-函数”契约自动化,而不是让算法同事手写 JSON。

知识点

  1. OpenAI function 描述规范
    必须返回纯 JSON Schema 2020-12 子集,字段层级:
    {name, description, parameters: {type: "object", properties: {...}, required: [...]}}
    任何不合规字段(如额外的 default、title)都会导致国内多数推理网关直接 400 Bad Request

  2. Pydantic 元数据捕获

    • Field(description=...) 会落入 schema()'description' 键。
    • 中文描述必须保证 utf-8 无 BOM,否则部分国产网关会截断。
    • 嵌套 BaseModel 需要递归展开,遇到 Union[...] 时要降级为 anyOf,否则与 JSON Schema 不兼容。
  3. 自动生成策略

    • 优先使用 Pydantic v2 的 .model_json_schema(),再对 title 做裁剪,只保留 properties / required / type
    • List[str]、Dict[str, int] 等泛型,要把 Python 类型映射成 arrayobject,并补全 items / additionalProperties
    • 如果业务侧需要 示例值,可在 Field(json_schema_extra={"example": ...}) 里写入,再额外弹出到 x-example 供前端调试,但正式 schema 需剔除,防止网关校验失败。
  4. 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 描述,无需人工二次编辑。

拓展思考

  1. 动态多函数注册
    真实 LLMOps 平台 中,函数会随着业务迭代频繁增删。可以把所有工具函数放到一个 __init__.py 里,通过 pkgutil.iter_modules 自动扫描,再批量生成 functions.json,最后调用 推理服务的热更新接口(如 /v1/functions/reload)完成 零停机发布

  2. 安全与合规
    国内监管要求 生成内容可追溯,因此要在 schema 里额外注入 x-business-id 字段,用于链路追踪;同时利用 字段级脱敏注解Field(json_schema_extra={"sensitive": True})),在日志落盘前自动打码,防止用户地址、手机号外泄。

  3. 性能优化
    当函数参数非常庞大(如上百字段)时,可把 parameters 先转成 Avro Schema 再转回 JSON Schema,利用 Avro 的 字段编号 实现差分传输,降低每次请求 30% 以上的网络开销,这在 私有化集群跨机房调用 场景下尤为关键。

掌握以上要点,即可在面试中向考官展示你对 大模型真实业务落地 的完整闭环思考,而不仅仅停留在“写一段代码”层面。