Instructor:让LLM 100%输出有效JSON的Python库 —— 2026指南
停止与不稳定的LLM输出作斗争。了解Instructor如何修补OpenAI客户端,使用Pydantic模型保证有效、类型安全的JSON响应。具有重试逻辑、多提供商支持和流式传输功能。
- ⭐ 11000
- MIT
- 更新于 2026-05-20
{{< resource-info >}}
最后更新:2026年5月19日
如果你曾经尝试过让大语言模型持续输出有效的JSON,你就知道那种痛苦。第一次响应完美无瑕。第二次缺少一个闭合大括号。第三次在JSON前面加了说明文字。第四次返回了有效的JSON但模式错误。这种不一致性使LLM对于需要结构化数据的生产应用程序来说不可靠——直到Instructor出现。
Instructor是一个Python库,它修补OpenAI客户端(以及其他10多个LLM提供商),使用Pydantic模型来保证结构化、类型安全、经过验证的输出。它将LLM文本生成的狂野西部转变为一个可预测的、软件工程化的流程。拥有11,000多个GitHub星标、MIT许可证和一个蓬勃发展的社区,Instructor已成为Python中结构化LLM输出的事实标准。本指南涵盖了2026年从基本设置到高级多提供商模式的所有内容。
什么是Instructor?为什么它很重要? #
Instructor由Jason Liu(jxnl)创建,是一个轻量级的Python库,位于你现有的LLM客户端之上,通过Pydantic模型验证强制执行结构化输出。与其从LLM接收原始文本并祈祷它能正确解析,不如定义一个Pydantic模式,Instructor确保每个响应都符合该模式——或者自动使用修正后的提示重试。
Instructor解决的问题是根本性的:LLM生成文本,但应用程序需要数据。每个将LLM功能投入生产环境的开发者都曾在凌晨2点收到告警,因为模型在JSON对象前添加了"Here’s your result:“而导致json.loads()崩溃。Instructor消除了这一整类错误。
# 安装Instructor
pip install instructor
# 安装你首选的LLM客户端(以OpenAI为例)
pip install openai
核心概念:修补OpenAI客户端 #
Instructor的神奇之处在于客户端修补。与其直接调用OpenAI的API,不如创建一个修补过的客户端,拦截响应,根据你的Pydantic模型验证它们,并自动处理失败。
import instructor
from openai import OpenAI
from pydantic import BaseModel
# 用Instructor修补OpenAI客户端
client = instructor.from_openai(OpenAI())
# 将你的输出模式定义为Pydantic模型
class UserProfile(BaseModel):
name: str
age: int
email: str
interests: list[str]
# 从自然语言中提取结构化数据
def extract_profile(user_description: str) -> UserProfile:
return client.chat.completions.create(
model="gpt-4o",
response_model=UserProfile,
messages=[
{
"role": "user",
"content": f"从这段描述中提取用户画像: {user_description}"
}
]
)
# 使用
profile = extract_profile(
"Sarah是一名28岁的软件工程师,来自西雅图。"
"她喜欢徒步旅行、摄影和阅读科幻小说。"
"她的邮箱是sarah.chen@example.com"
)
print(profile)
# UserProfile(name='Sarah', age=28, email='sarah.chen@example.com',
# interests=['hiking', 'photography', 'reading sci-fi novels'])
# 直接访问类型化字段
print(f"姓名: {profile.name}, 年龄: {profile.age}")
print(f"邮箱有效: {'@' in profile.email}")
注意response_model=UserProfile如何告诉Instructor根据我们的模式验证LLM的输出。结果是一个完全类型化的Pydantic对象——不是原始字符串或未类型化的字典。
通过自动重试处理验证失败 #
当LLM产生无效输出时会发生什么?Instructor的默认行为是重新询问模型,并提供关于出错原因的反馈,创建一个自我修正的循环。
from pydantic import BaseModel, Field, field_validator
class ValidatedProduct(BaseModel):
name: str = Field(description="产品名称,最多50个字符")
price: float = Field(description="美元价格,必须为正数")
category: str = Field(description="以下之一: electronics, clothing, food, books")
@field_validator('category')
@classmethod
def validate_category(cls, v):
allowed = {'electronics', 'clothing', 'food', 'books'}
if v.lower() not in allowed:
raise ValueError(f"类别必须是以下之一: {allowed}")
return v.lower()
@field_validator('price')
@classmethod
def validate_price(cls, v):
if v <= 0:
raise ValueError("价格必须为正数")
return round(v, 2)
# 验证失败时Instructor自动重试
def parse_product(description: str) -> ValidatedProduct:
return client.chat.completions.create(
model="gpt-4o",
response_model=ValidatedProduct,
max_retries=3, # 最多重试3次并附带反馈
messages=[
{"role": "user", "content": f"解析这个产品: {description}"}
]
)
# 即使第一次尝试有问题,也能正常工作
product = parse_product(
"无线蓝牙降噪耳机,售价$79.99。"
"电子产品类别。"
)
print(product)
# ValidatedProduct(name='无线蓝牙降噪耳机',
# price=79.99, category='electronics')
嵌套模型和复杂模式 #
现实世界的应用程序需要的不仅仅是扁平结构。Instructor可以轻松处理任意嵌套的Pydantic模型。
from typing import Optional, List
from pydantic import BaseModel, Field
class Address(BaseModel):
street: str
city: str
state: str = Field(description="2字母州代码")
zip_code: str
country: str = "US"
class OrderItem(BaseModel):
product_name: str
quantity: int = Field(ge=1, description="必须至少为1")
unit_price: float = Field(gt=0)
@property
def total(self) -> float:
return self.quantity * self.unit_price
class CustomerOrder(BaseModel):
customer_name: str
customer_email: str
shipping_address: Address
billing_address: Optional[Address] = None
items: List[OrderItem]
order_notes: Optional[str] = None
@property
def grand_total(self) -> float:
return sum(item.total for item in self.items)
def extract_order(email_text: str) -> CustomerOrder:
return client.chat.completions.create(
model="gpt-4o",
response_model=CustomerOrder,
messages=[
{"role": "user", "content": f"从邮件中提取订单:\n\n{email_text}"}
]
)
order = extract_order("""
您好,我想下一个订单。
客户:John Smith (john.smith@email.com)
发货地址:123 Oak Street, San Francisco, CA 94102
商品:
- MacBook Pro M3,数量1,$1999
- USB-C集线器,数量2,每个$49
请为笔记本电脑提供礼品包装。
""")
print(f"客户: {order.customer_name}")
print(f"发货城市: {order.shipping_address.city}")
print(f"订单总额: ${order.grand_total:.2f}")
多提供商支持:超越OpenAI #
Instructor不会将你锁定在OpenAI中。它以相同的API支持10多个LLM提供商,使供应商切换毫不费力。
# --- Anthropic Claude ---
import anthropic
import instructor
anthropic_client = instructor.from_anthropic(anthropic.Anthropic())
result = anthropic_client.messages.create(
model="claude-sonnet-4-20250514",
response_model=UserProfile,
max_tokens=1024,
messages=[{"role": "user", "content": "提取:Mike 35岁,喜欢网球"}]
)
print(result)
# --- Google Gemini ---
import google.generativeai as genai
import instructor
gemini_client = instructor.from_gemini(
genai.GenerativeModel("gemini-2.5-pro")
)
result = gemini_client.generate_content(
response_model=UserProfile,
contents=["提取:Lisa 29岁,喜欢烹饪和瑜伽"]
)
print(result)
# --- Cohere ---
import cohere
import instructor
cohere_client = instructor.from_cohere(cohere.Client())
result = cohere_client.chat(
response_model=UserProfile,
message="提取:David 42岁,喜欢高尔夫和钓鱼"
)
print(result)
高容量应用的批处理 #
处理数千个项目时,单个API调用太慢。Instructor支持使用asyncio进行并发执行的批处理。
import asyncio
import instructor
from openai import AsyncOpenAI
from pydantic import BaseModel
# 使用异步客户端进行批处理
async_client = instructor.from_openai(AsyncOpenAI())
class SentimentResult(BaseModel):
text: str
sentiment: str # "positive", "negative", "neutral"
confidence: float
key_phrases: list[str]
async def analyze_single(text: str) -> SentimentResult:
return await async_client.chat.completions.create(
model="gpt-4o-mini",
response_model=SentimentResult,
messages=[
{"role": "user", "content": f"分析情感: {text}"}
]
)
async def analyze_batch(texts: list[str]) -> list[SentimentResult]:
"""并发处理多个文本。"""
tasks = [analyze_single(text) for text in texts]
results = await asyncio.gather(*tasks)
return results
# 并发处理100条评论
texts = [
"这个产品超出了我的预期!",
"质量太差,一天就坏了。",
"还可以,没什么特别的但能用。",
# ... 再添加97个
]
results = asyncio.run(analyze_batch(texts))
positive = sum(1 for r in results if r.sentiment == "positive")
print(f"正面: {positive}/{len(results)}")
流式结构化输出 #
对于实时应用程序,Instructor支持在LLM生成结果时流式传输部分结果。
from typing import Iterable
from pydantic import BaseModel
class PartialArticle(BaseModel):
title: str
sections: list[str]
key_points: list[str]
# 在生成过程中流式传输结构化数据
def stream_article(topic: str) -> Iterable[PartialArticle]:
return client.chat.completions.create_partial(
model="gpt-4o",
response_model=PartialArticle,
stream=True,
messages=[
{"role": "user", "content": f"撰写关于以下主题的文章大纲: {topic}"}
]
)
# 在部分结果到达时消费它们
for partial in stream_article("2026年可再生能源趋势"):
print(f"标题: {partial.title}")
print(f"已有章节数: {len(partial.sections)}")
print("---")
内置重试与重新询问 #
Instructor的重试系统不仅仅是重复请求——它向LLM提供关于验证失败的具体反馈,使其能够自我修正。
from pydantic import BaseModel, field_validator
class StrictDateRange(BaseModel):
start_date: str = Field(description="YYYY-MM-DD格式")
end_date: str = Field(description="YYYY-MM-DD格式,必须在开始日期之后")
@field_validator('start_date', 'end_date')
@classmethod
def validate_date_format(cls, v):
from datetime import datetime
datetime.strptime(v, "%Y-%m-%d")
return v
@field_validator('end_date')
@classmethod
def validate_order(cls, end, info):
start = info.data.get('start_date')
if start and end <= start:
raise ValueError("end_date必须在start_date之后")
return end
# Instructor将使用特定验证错误反馈进行重试
def extract_date_range(text: str) -> StrictDateRange:
return client.chat.completions.create(
model="gpt-4o",
response_model=StrictDateRange,
max_retries=3,
messages=[
{"role": "user", "content": f"提取日期范围: {text}"}
]
)
# 即使模型最初交换日期或使用错误格式,
# Instructor也会使用具体的错误消息重新询问
try:
result = extract_date_range(
"项目从2026年3月15日持续到2026年1月10日"
)
print(result)
except Exception as e:
print(f"达到最大重试次数后失败: {e}")
使用Literal进行受限分类 #
对于分类任务,使用Python的Literal类型将输出限制为特定值。
from typing import Literal
class SupportTicket(BaseModel):
customer_query: str
category: Literal[
"billing",
"technical_support",
"account_access",
"feature_request",
"refund",
"general_inquiry"
]
priority: Literal["low", "medium", "high", "urgent"]
suggested_response: str
def classify_ticket(ticket_text: str) -> SupportTicket:
return client.chat.completions.create(
model="gpt-4o-mini",
response_model=SupportTicket,
messages=[
{
"role": "system",
"content": "你是一名客户支持分类专家。"
},
{"role": "user", "content": f"分类这张工单:\n\n{ticket_text}"}
]
)
# 分类结果保证是允许值之一
ticket = classify_ticket(
"这个月我的订阅被重复扣费了。"
"请立即退还重复扣款。"
)
print(f"类别: {ticket.category}") # 永远是"billing"
print(f"优先级: {ticket.priority}") # 永远是4个值之一
与FastAPI集成构建生产API #
Instructor在API开发中表现出色。以下是一个完整的FastAPI端点,带有结构化LLM输出:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import instructor
from openai import OpenAI
app = FastAPI(title="结构化LLM API")
client = instructor.from_openai(OpenAI())
# 请求模式
class ExtractionRequest(BaseModel):
text: str
extract_fields: list[str]
# 响应模式
class ExtractedData(BaseModel):
entities: list[dict]
relationships: list[dict]
summary: str
@app.post("/extract", response_model=ExtractedData)
async def extract_entities(request: ExtractionRequest):
"""从非结构化文本中提取结构化实体。"""
try:
result = client.chat.completions.create(
model="gpt-4o",
response_model=ExtractedData,
messages=[
{
"role": "user",
"content": (
f"从这段文本中提取实体。"
f"关注: {', '.join(request.extract_fields)}\n\n"
f"文本: {request.text}"
)
}
]
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 使用 uvicorn main:app --reload 运行
高级:函数调用替代方案 #
Instructor可以用更强大的基于Pydantic的模式替代OpenAI的函数调用。
from typing import Type
class SearchQuery(BaseModel):
"""带有参数的生成搜索查询"""
keywords: list[str]
filters: dict[str, str]
sort_by: Literal["relevance", "date", "price_asc", "price_desc"]
def generate_search(user_request: str) -> SearchQuery:
return client.chat.completions.create(
model="gpt-4o",
response_model=SearchQuery,
messages=[
{
"role": "user",
"content": f"为以下请求生成优化搜索查询: {user_request}"
}
]
)
query = generate_search(
"找100美元以下的无线耳机,电池续航好,最新的优先"
)
print(query.keywords) # ['wireless earbuds', 'bluetooth']
print(query.filters) # {'max_price': '100'}
print(query.sort_by) # 'date'
错误处理和日志记录 #
生产系统需要了解Instructor的重试行为。配置日志以进行调试。
import logging
import instructor
# 启用详细日志记录
instructor.enable_logging()
logging.basicConfig(level=logging.DEBUG)
# 或配置特定的记录器
logger = logging.getLogger("instructor")
logger.setLevel(logging.INFO)
# 为生产环境添加文件处理程序
handler = logging.FileHandler("instructor.log")
handler.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
logger.addHandler(handler)
# 现在所有重试尝试和验证错误都会被记录
result = client.chat.completions.create(
model="gpt-4o",
response_model=UserProfile,
max_retries=3,
messages=[{"role": "user", "content": "提取:Jane,25岁,喜欢艺术"}]
)
常见问题解答 #
Instructor支持哪些LLM提供商? #
Instructor支持OpenAI(GPT-4、GPT-4o、GPT-3.5)、Anthropic(Claude 3/3.5/4 Sonnet、Opus、Haiku)、Google(Gemini 1.5/2.0/2.5 Pro、Flash)、Cohere、Mistral、Groq、Ollama(本地模型)、Azure OpenAI、AWS Bedrock、Fireworks AI和Together AI。相同的response_modelAPI在所有提供商上都能以相同方式工作。
Instructor与OpenAI的JSON模式有何不同? #
OpenAI的JSON模式保证有效的JSON语法,但提供无模式验证。模型仍然可以返回字段名错误、类型不正确、缺少必填字段或超出预期范围的值的JSON。Instructor添加了一个Pydantic验证层,捕获所有这些 issues 并触发带有纠正性反馈的自动重试。JSON模式是语法保证;Instructor是语义保证。
Instructor可以与本地/开源模型一起使用吗? #
可以。Instructor与任何可通过支持的客户端库访问的模型一起工作。对于本地模型,使用Ollama或llama-cpp-python集成。对于托管在vLLM或TGI上的模型,使用OpenAI兼容API。关键要求是模型具有足够的指令遵循能力,能够生成映射到JSON的结构化文本。
Instructor的性能开销是多少? #
Instructor的开销极小——每次调用通常10-50毫秒用于Pydantic验证。重试机制仅在验证失败时增加延迟(对于能力足够的模型,这应该小于5%的调用)。对于高吞吐量应用程序,使用gpt-4o-mini或带有异步批处理的本地模型。与LLM API延迟本身(通常为500毫秒-5秒)相比,开销可以忽略不计。
重试/重新询问机制如何工作? #
当验证失败时,Instructor捕获Pydantic ValidationError,提取特定的错误消息(例如"age must be a positive integer”),并向LLM发送一个新请求,该请求包含:原始提示、不正确的响应和验证错误详细信息。这创建了一个自我修正循环,大多数问题在1-2次重试内解决。你通过max_retries参数控制最大重试次数。
我可以将Instructor与async/await模式一起使用吗? #
可以。Instructor通过AsyncOpenAI、AsyncAnthropic和其他异步客户端完全支持异步。对单个调用使用await client.chat.completions.create(),或使用asyncio.gather()进行并发批处理。流式传输在异步模式下也支持,通过create_partial()。
Instructor是否适合企业级生产部署? #
绝对适合。Instructor的11,000+ GitHub星标、MIT许可证、活跃的维护和基于Pydantic的架构使其具备企业就绪能力。它与FastAPI、监控系统(Datadog、Prometheus)和结构化日志记录干净地集成。原始LLM API无法匹配的验证层增加了可靠性。许多财富500强公司在生产数据管道中使用Instructor。
推荐部署与基础设施 #
上述工具想要落地生产,靠谱的基础设施是前提。dibi8 自己也在用的两个选择:
- DigitalOcean — 新用户 60 天 $200 免费额度。
- HTStack — 香港 VPS,国内访问低延迟,dibi8.com 自己也跑在它上面。
Aff 链接 — 不增加你的成本,但能帮 dibi8 持续运营。
结论 #
Instructor将LLM从不可预测的文本生成器转变为可靠的结构化数据源。通过将Pydantic验证的强大功能与智能重试逻辑相结合,它解决了生产LLM部署面临的#1问题:输出一致性。无论你是从文档中提取实体、分类支持工单,还是构建复杂的多步骤代理系统,Instructor都提供了专业应用程序所需的类型安全性和可靠性。
该库的多提供商支持意味着你永远不会被锁定在单一LLM供应商。它与FastAPI、异步模式和流式传输的无缝集成使其适用于从后台批处理作业到实时API的所有场景。凭借11,000+星标和活跃的社区,Instructor已赢得作为现代AI开发者工具包中基本工具的地位。
如果你仍然在用json.loads()解析原始LLM输出并祈祷它能正常工作,那么是时候升级了。今天安装Instructor,体验100%有效的JSON,100%的时间意味着什么。
💬 留言讨论