Instructor实战:让LLM老老实实返回JSON

Instructor实战:让LLM老老实实返回JSON

你让GPT返回个用户信息JSON,它给你包了三层markdown:

json 复制代码
{
  "name": "张三"
}

你要个评分数字,它给你返回"高"。你要个列表,它少了俩字段。

这不是模型不行,是你没用对工具。Instructor就是专门治这个病的------3行代码,保证LLM输出跟你的Pydantic模型一模一样。

为什么需要Instructor

Raw JSON mode有三个硬伤:

1. 格式不可控

python 复制代码
# 你期望的
{"score": 0.85}

# GPT给你的
```json
{"score": "很高", "confidence": "非常确定"}
\```

2. 字段缺失/多余 模型经常自己发挥,加字段或者漏字段,下游代码直接炸。

3. 没有重试机制 验证失败了就失败了,你得手写重试逻辑。

Instructor的解法很直接:用Pydantic定义schema,自动验证,失败了自动重试,直到拿到合法数据或者超过重试次数。

5分钟上手

python 复制代码
pip install instructor openai pydantic

最简单的例子------提取人物信息:

python 复制代码
import instructor
from openai import OpenAI
from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int
    occupation: str

client = instructor.from_openai(OpenAI())

person = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "Extract: John is 30, works as an engineer"}
    ],
    response_model=Person,
    max_retries=3
)

print(person.name)  # John
print(person.age)   # 30

工作流程:

  1. 定义Pydantic模型
  2. from_openai()包装客户端
  3. response_model参数
  4. 拿到的直接是验证过的Python对象

验证失败?自动重试。字段类型错了?自动重试。直到成功或者达到max_retries

复杂场景:嵌套结构+自定义验证

真实业务不会这么简单。来个工单分类系统:

python 复制代码
from pydantic import BaseModel, Field, field_validator
from typing import List, Optional
from enum import Enum

class Priority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"

class Tag(BaseModel):
    name: str = Field(..., min_length=2, max_length=20)
    confidence: float = Field(..., ge=0.0, le=1.0)

class Ticket(BaseModel):
    title: str = Field(..., min_length=10)
    priority: Priority
    tags: List[Tag] = Field(..., min_items=1, max_items=5)
    estimated_hours: Optional[float] = Field(None, gt=0, le=100)
    
    @field_validator('estimated_hours')
    @classmethod
    def validate_hours(cls, v):
        if v is not None and v % 0.5 != 0:
            raise ValueError('工时必须是0.5的倍数')
        return v

ticket = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": "分类工单:生产环境Redis崩了,影响所有用户登录"
    }],
    response_model=Ticket,
    max_retries=3
)

print(ticket.priority)  # Priority.CRITICAL
print(ticket.tags[0].name)  # redis
print(ticket.estimated_hours)  # 2.0 (符合0.5倍数规则)

这里用了:

  • 枚举类型(Priority)
  • 嵌套对象(List[Tag])
  • 字段约束(min_length、ge/le范围)
  • 自定义验证器(工时必须0.5倍数)

任何一个不符合,Instructor会把验证错误信息塞回prompt,让模型重新生成。

流式输出:边生成边处理

大列表或者复杂对象,等模型全生成完太慢。Instructor支持Partial streaming:

python 复制代码
from instructor import Partial

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

stream = client.chat.completions.create_partial(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": "提取:张三30岁工程师,李四25岁设计师,王五35岁产品"
    }],
    response_model=Partial[UserList],
    stream=True
)

for partial_result in stream:
    if partial_result.users:
        print(f"已提取 {len(partial_result.users)} 个用户")
        for user in partial_result.users:
            if user.name:  # Partial对象字段可能None
                print(f"  - {user.name}")

输出:

markdown 复制代码
已提取 1 个用户
  - 张三
已提取 2 个用户
  - 张三
  - 李四
已提取 3 个用户
  - 张三
  - 李四
  - 王五

Partial[T]会把模型里的所有字段标记为Optional,你可以实时处理部分结果,不用等全部生成完。

注意 :streaming模式下不能用@field_validator,因为中间状态可能不满足验证规则。

多provider支持:一套代码跑遍所有模型

Instructor 2026版支持15+家provider,API完全一样:

python 复制代码
# OpenAI
client = instructor.from_openai(OpenAI())

# Anthropic Claude
from anthropic import Anthropic
client = instructor.from_anthropic(Anthropic())

# Google Gemini
import google.generativeai as genai
client = instructor.from_gemini(genai.GenerativeModel("gemini-2.0-flash-001"))

# 本地模型 (Ollama)
from openai import OpenAI
client = instructor.from_openai(
    OpenAI(base_url="http://localhost:11434/v1", api_key="ollama"),
    mode=instructor.Mode.JSON
)

切provider只需要改初始化代码,response_model和业务逻辑完全不变。

本地模型建议用Mode.JSON而不是Mode.TOOLS,因为开源模型的function calling能力参差不齐。

三个踩坑记录

1. 重试次数别设太高

python 复制代码
# ❌ 这样写token消耗爆炸
response = client.create(
    response_model=ComplexModel,
    max_retries=10  # 验证失败会重试10次,每次都扣token
)

# ✅ 3次够了,验证失败说明prompt有问题
max_retries=3

每次重试都会把错误信息加到prompt里再调一次API。复杂模型验证失败10次,token消耗是正常的3-5倍。

2. 嵌套太深会超context

python 复制代码
class DeepNested(BaseModel):
    level1: List['DeepNested']  # 递归嵌套

# 模型生成超深结构时,验证错误信息会非常长
# 重试时prompt可能超出context window

解决:限制嵌套深度,或者用max_items约束列表长度。

3. streaming不支持同步validators

python 复制代码
class User(BaseModel):
    email: str
    
    @field_validator('email')
    @classmethod
    def validate_email(cls, v):
        # ❌ 在 create_partial(stream=True) 里不会执行
        if '@' not in v:
            raise ValueError('Invalid email')
        return v

streaming模式返回的是Partial对象,验证器会被跳过。需要在最终结果上再验证一次。

Instructor vs PydanticAI:什么时候该换

Instructor适合单次提取任务

  • 从文本提取结构化数据
  • 分类、打标签
  • 格式转换

PydanticAI适合多轮agent交互

  • 需要调用工具(搜索、数据库查询)
  • 多步骤推理
  • 需要记录执行历史和可观测性

两者都是Pydantic团队官方产品,Instructor专注schema-first提取,PydanticAI是完整的agent runtime。

简单记:只要JSON就用Instructor,要干活就上PydanticAI

一张清单

生产环境用Instructor之前,确认这5点:

  • max_retries设成2-3,不要超过5
  • 嵌套层级 ≤ 3层
  • List字段都加了max_items限制
  • streaming场景没用field_validator
  • 算过token成本(重试会double消耗)

完整代码:python.useinstructor.com/

相关推荐
明天有专业课3 小时前
RAG-检索优化策略
面试·aigc
imbackneverdie3 小时前
AI PPT工具实测分享
人工智能·ai作画·aigc·ppt·ai工具·aippt
手写码匠3 小时前
手写 AI 智能路由系统:从零构建多模型调度与负载均衡
人工智能·深度学习·算法·aigc
摄影图4 小时前
量子计算科技图片素材 高清商用适配多场景
科技·aigc·量子计算·贴图·插画
哥只是传说中的小白14 小时前
GrsaiApi官方正版字字动画插件!支持nano banana pro和gpt-image-2模型
人工智能·gpt·ai作画·开源·aigc·api
向量引擎16 小时前
向量引擎、deepseek v4、GPT Image 2、api key:Agent 热潮下,AI 应用真正卷的是“调度能力
人工智能·gpt·aigc·ai编程·ai写作·agi·api调用
Awu122718 小时前
⚡精通Claude第9课-高级功能:Planning Mode、Auto Mode 与自动化工作流
aigc·ai编程·claude
Aision_21 小时前
OpenClaw和Hermes的记忆有什么区别
人工智能·gpt·langchain·prompt·aigc·agi
爱吃的小肥羊1 天前
Claude code额度限时提高50%,但Claude 又改计费模式了
aigc·ai编程