1. 引言
在使用 LangChain 构建大语言模型应用时,我们经常需要模型输出结构化的数据,而不仅仅是纯文本。例如,从一段文本中提取实体、生成 JSON 格式的配置、或者解析用户的意图。LangChain 提供了基于 Pydantic 的结构化输出能力,让我们可以定义输出数据的模型,并让模型严格按照该模型生成结果。
本文将详细介绍如何在 LangChain 中使用 Pydantic 实现结构化输出,涵盖核心概念、基础用法、进阶技巧以及常见问题。
2. 为什么需要结构化输出
大语言模型的默认输出是自由文本,这在很多场景下难以直接用于程序处理。结构化输出的价值体现在:
- 可靠性:通过定义输出模型,约束模型生成符合预期的字段和类型。
- 可解析性:输出可以直接反序列化为 Python 对象,无需复杂的字符串解析。
- 类型安全:借助 Pydantic 的校验能力,在运行时即可发现输出异常。
- 下游集成:结构化数据可以方便地接入数据库、API 或前端展示。
3. 核心概念
3.1 Pydantic 模型
Pydantic 是一个数据校验库,通过定义类来声明数据的结构和类型。在 LangChain 中,我们使用 Pydantic 模型来描述期望的输出格式:
python
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(description="人物的姓名")
age: int = Field(description="人物的年龄")
occupation: str = Field(description="人物的职业")
3.2 输出解析器
LangChain 提供了多种输出解析器,用于将模型的文本输出转换为 Pydantic 对象:
PydanticOutputParser:通用的 Pydantic 输出解析器。PydanticToolsParser:配合工具调用(Tool Calling)使用。JsonOutputParser:输出 JSON 格式。
3.3 提示词注入
使用 PydanticOutputParser 时,需要将格式说明注入到提示词中,让模型了解输出要求。解析器提供了 get_format_instructions() 方法,可以自动生成格式说明。
4. 基础用法
4.1 使用 PydanticOutputParser
这是最经典的方式,适用于大多数模型。核心思路是:定义 Pydantic 模型 → 创建解析器 → 将格式说明注入提示词 → 调用模型 → 解析输出。
python
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
# 1. 定义输出模型
class Person(BaseModel):
name: str = Field(description="人物的姓名")
age: int = Field(description="人物的年龄")
occupation: str = Field(description="人物的职业")
# 2. 创建解析器
parser = PydanticOutputParser(pydantic_object=Person)
# 3. 构建提示词模板
prompt = PromptTemplate(
template="请从用户的输入中提取人物信息。\n{format_instructions}\n用户输入:{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 4. 创建模型
model = ChatOpenAI(model="gpt-4o", temperature=0)
# 5. 构建链并调用
chain = prompt | model | parser
result = chain.invoke({"query": "张三今年28岁,是一名软件工程师。"})
print(result)
print(type(result)) # <class '__main__.Person'>
4.2 使用 with_structured_output
LangChain 提供了更简洁的 with_structured_output 方法,直接绑定 Pydantic 模型,无需手动构建解析器:
python
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(description="人物的姓名")
age: int = Field(description="人物的年龄")
occupation: str = Field(description="人物的职业")
model = ChatOpenAI(model="gpt-4o", temperature=0)
structured_model = model.with_structured_output(Person)
result = structured_model.invoke("李四今年35岁,是一名产品经理。")
print(result)
print(result.name) # 李四
这种方式底层会自动处理提示词注入和输出解析,代码更加简洁。
4.3 使用 PydanticToolsParser
当模型支持工具调用(Tool Calling)时,可以使用 PydanticToolsParser,将结构化输出封装为工具调用的形式:
python
from langchain.output_parsers import PydanticToolsParser
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(description="人物的姓名")
age: int = Field(description="人物的年龄")
occupation: str = Field(description="人物的职业")
# 将 Pydantic 模型转换为工具定义
tool = convert_to_openai_tool(Person)
model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools([tool])
parser = PydanticToolsParser(tools=[Person])
chain = model | parser
result = chain.invoke("王五今年42岁,是一名律师。")
print(result)
5. 进阶技巧
5.1 嵌套模型
Pydantic 支持嵌套模型,适合描述复杂的数据结构:
python
from pydantic import BaseModel, Field
from typing import List
class Address(BaseModel):
city: str = Field(description="城市")
street: str = Field(description="街道")
class Person(BaseModel):
name: str = Field(description="姓名")
age: int = Field(description="年龄")
address: Address = Field(description="地址")
hobbies: List[str] = Field(description="爱好列表")
5.2 可选字段与默认值
使用 Optional 和默认值可以让模型在信息不足时也能正常输出:
python
from typing import Optional
class Person(BaseModel):
name: str = Field(description="姓名")
age: Optional[int] = Field(default=None, description="年龄,未知时为空")
email: Optional[str] = Field(default=None, description="邮箱,未知时为空")
5.3 枚举约束
使用枚举可以限制字段的取值范围:
python
from enum import Enum
class Sentiment(str, Enum):
positive = "正面"
negative = "负面"
neutral = "中性"
class Review(BaseModel):
content: str = Field(description="评论内容")
sentiment: Sentiment = Field(description="情感倾向")
5.4 自定义校验
Pydantic 的 field_validator 可以在解析后对输出进行二次校验:
python
from pydantic import BaseModel, Field, field_validator
class Person(BaseModel):
name: str = Field(description="姓名")
age: int = Field(description="年龄")
@field_validator("age")
@classmethod
def validate_age(cls, v):
if v < 0 or v > 150:
raise ValueError("年龄必须在 0 到 150 之间")
return v
6. 常见问题与解决方案
6.1 模型输出格式错误
当模型输出的 JSON 格式不合法时,解析器会抛出异常。解决方案:
- 使用
temperature=0降低随机性。 - 在提示词中提供更明确的格式示例(Few-shot)。
- 使用支持工具调用的模型和
with_structured_output。
6.2 字段缺失
当模型遗漏某些必填字段时,Pydantic 会报校验错误。解决方案:
- 将字段设置为
Optional并给出默认值。 - 在
Field的description中明确说明字段的重要性。 - 使用
model_validate配合partial模式进行容错。
6.3 中文输出编码问题
确保在提示词中明确要求输出 JSON 格式,并注意模型返回的字符串编码。使用 ensure_ascii=False 可以保留中文:
python
import json
# 模型输出解析后,如需序列化
json_str = result.model_dump_json()
print(json_str) # 默认 ensure_ascii=True
7. 总结
LangChain 结合 Pydantic 提供了强大而灵活的结构化输出能力。通过定义 Pydantic 模型,我们可以约束模型的输出格式,获得类型安全、易于解析的结构化数据。无论是简单的信息提取,还是复杂的嵌套结构,LangChain 都提供了对应的解决方案。
建议在实际项目中优先使用 with_structured_output 方法,它更加简洁且对工具调用模型有更好的支持。对于不支持工具调用的模型,则使用 PydanticOutputParser 配合提示词注入的方式。