让大模型帮你从一段文本里抽出「姓名、年龄、邮箱」,最朴素的写法是让它输出 JSON,再 json.loads。但现实很骨感:它可能多加解释文字、字段名写成中文、年龄写成字符串、甚至偶尔不返回合法 JSON——你的解析代码一碰就崩。Instructor 专门解决这个痛点:它给主流 SDK(OpenAI、Anthropic、Vertex 等)打一层补丁,让你声明一个 Pydantic 模型,直接拿回校验过的对象;格式不对时还会自动重试。
先搞懂:Instructor 到底帮你做了什么?
它本质上是在「调用大模型」和「你的代码」之间加了一道转换层:把你的 Pydantic 模型翻译成给模型的约束(比如用 function calling 或结构化输出),再把模型的返回反序列化成对象并做类型校验。
| 你写的 | Instructor 做的 |
|---|---|
| 声明 Pydantic 模型 | 生成给模型的输出约束 |
| response_model=User | 强制模型按约束输出,并解析 |
| 拿到 User 对象 | 类型不对就重试,直到通过 |
它不替你写提示词工程:Instructor 负责「格式可靠」,但抽得准不准,仍取决于你的模型能力和任务描述。复杂抽取建议先把任务说清楚,再靠它保证格式。
Step 1:安装 instructor 并初始化客户端
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())
instructor.from_xxx 即可,用法完全一致。本文用 OpenAI 演示。Step 2:用 Pydantic 定义你想要的结构
这是 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:让模型直接返回结构化对象
把 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]
Step 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:抽取列表与嵌套结构
返回多条记录用 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)
Step 6:把结构化抽取接到一个 mini 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 兜底 |