进阶 📋 6 个步骤 第 593 / 593 篇

用 Instructor 让大模型稳定输出结构化数据(Pydantic 校验,自动重试)

大模型吐出来的 JSON 经常缺字段、类型错、甚至不合法,解析时动不动就崩。Instructor 给 OpenAI 等 SDK 打一个补丁,让你直接拿到校验过的 Pydantic 对象,并在格式不对时自动重试。本教程六步落地结构化抽取与 mini Agent。

2026.11.03· 18 分钟阅读· 约 1383 字· 🧱 Instructor / 🐍 Pydantic

让大模型帮你从一段文本里抽出「姓名、年龄、邮箱」,最朴素的写法是让它输出 JSON,再 json.loads。但现实很骨感:它可能多加解释文字、字段名写成中文、年龄写成字符串、甚至偶尔不返回合法 JSON——你的解析代码一碰就崩。Instructor 专门解决这个痛点:它给主流 SDK(OpenAI、Anthropic、Vertex 等)打一层补丁,让你声明一个 Pydantic 模型,直接拿回校验过的对象;格式不对时还会自动重试。

🧩 本教程适合:做数据抽取、表单解析、API 结果结构化、或想给 Agent 加「工具调用返回强类型结果」的 Python 开发者。需要会一点 Pydantic(没有也没关系,跟着抄)。

先搞懂:Instructor 到底帮你做了什么?

它本质上是在「调用大模型」和「你的代码」之间加了一道转换层:把你的 Pydantic 模型翻译成给模型的约束(比如用 function calling 或结构化输出),再把模型的返回反序列化成对象并做类型校验。

你写的Instructor 做的
声明 Pydantic 模型生成给模型的输出约束
response_model=User强制模型按约束输出,并解析
拿到 User 对象类型不对就重试,直到通过

它不替你写提示词工程:Instructor 负责「格式可靠」,但抽得准不准,仍取决于你的模型能力和任务描述。复杂抽取建议先把任务说清楚,再靠它保证格式。

Step 1:安装 instructor 并初始化客户端

1 装包,用 from_openai 打补丁

Instructor 以插件方式工作。下面以 OpenAI 为例,一行就能拿到增强后的客户端。

pip install instructor openai pydantic python-dotenv

import os
from dotenv import load_dotenv
import instructor
from openai import OpenAI

load_dotenv()

# 关键:用 instructor.from_openai 包一层
client = instructor.from_openai(OpenAI())
💡 它也支持 Anthropic、Vertex、Cohere 等,只要换成对应的 instructor.from_xxx 即可,用法完全一致。本文用 OpenAI 演示。

Step 2:用 Pydantic 定义你想要的结构

2 把「想要什么字段」写成模型

这是 Instructor 的核心:你描述清楚结构,它来保证模型遵守。字段类型越明确,结果越稳。

from pydantic import BaseModel, EmailStr

class User(BaseModel):
    name: str
    age: int
    email: EmailStr

关于 EmailStr:它在 pydantic 的 pydantic[email] 额外依赖里。如果不想装,把 email 改成普通 str 即可,本教程为了演示强校验才用它。pip install "pydantic[email]"。

Step 3:让模型直接返回结构化对象

3 response_model 一填,直接拿对象

把 response_model 设成你的模型,调用方式和普通 chat 接口一样,但返回已经是校验过的对象。

user = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=User,
    messages=[{
        "role": "user",
        "content": "小明 28 岁,邮箱是 [email protected]",
    }],
)

print(user.name)    # 小明
print(user.age)     # 28
print(user.email)   # [email protected]
🔑 对比原生做法:你不用再写「请只输出 JSON,字段有 name/age/email」,也不用 json.loads 再 try/except。模型被约束成只返回符合结构的内容,age 会被强制转成整数。

Step 4:处理校验失败——自动重试与兜底

4 格式不对时让它自己重来

当模型返回通不过 Pydantic 校验时,Instructor 默认会自动重试(按 max_retries)。你也可以显式控制次数,并加一段纠正式提示。

user = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=User,
    max_retries=3,   # 校验失败最多重试 3 次
    messages=[{
        "role": "user",
        "content": "用户说:我是小红,今年三十岁,邮箱 [email protected]",
    }],
)

重试也会失败:如果任务本身含糊或模型能力不够,重试到上限仍可能抛错。生产代码请用 try/except 接住 ValidationError,给一个友好的兜底(比如退回普通文本或提示用户补充信息),别让整个流程崩掉。

Step 5:抽取列表与嵌套结构

5 一次抽出多条、或带子结构

返回多条记录用 list[Model];嵌套对象直接在正常模型里再定义子模型即可。

from typing import List

class UserList(BaseModel):
    users: List[User]

result = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=UserList,
    messages=[{
        "role": "user",
        "content": (
            "名单:张三 20 岁 [email protected];"
            "李四 35 岁 [email protected]"
        ),
    }],
)

for u in result.users:
    print(u.name, u.age)
🎉 到这里,你已经能把杂乱文本稳定变成结构化数据了。把它接到爬虫、邮件、工单、合同解析,都是同一个套路:定义模型 → response_model → 拿对象。

Step 6:把结构化抽取接到一个 mini Agent

6 让 Agent 先抽取意图,再决定动作

结构化输出不只用于数据清洗。让 Agent 把用户一句话抽成「意图 + 参数」,再路由到对应函数,是很多智能体的标准做法。

class Intent(BaseModel):
    action: str        # 取值如 "查天气" / "订机票" / "闲聊"
    city: str = ""
    date: str = ""

intent = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=Intent,
    messages=[{
        "role": "user",
        "content": "帮我查一下明天北京天气",
    }],
)

if intent.action == "查天气":
    print("去查:", intent.city, intent.date)

给 action 限范围:让模型自由填字符串容易跑偏。更稳的做法是用 Literal["查天气","订机票","闲聊"] 限定枚举值,或用 Field(description=...) 写明每个字段含义,模型依从度会更高。

常见问题速查

现象原因 与 解决
没装 instructor 也能 import确认装的是 instructor(不是别的同名包),且 Python 版本足够新
返回的不是对象而是字符串忘了用 instructor.from_openai 包客户端,用了原生 OpenAI
EmailStr 报错没装 email 额外依赖,或把 email 字段改回 str
重试后还是失败任务描述不清或模型能力不足,收紧字段约束并加 try/except 兜底
← 返回教程中心