LangChain 中 Pydantic 格式输出:结构化输出的完整指南

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 并给出默认值。
  • Fielddescription 中明确说明字段的重要性。
  • 使用 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 配合提示词注入的方式。

相关推荐
程序员大雄学编程16 分钟前
微积分43. 无穷积分入门:从概念到Python实战可视化
开发语言·python·学习·微积分
郝学胜-神的一滴25 分钟前
C++11 工程级应用 06:自己造一个类似Python的Range迭代器
开发语言·c++·windows·python·程序人生·microsoft
zh_xuan36 分钟前
c++ jthread
开发语言·c++·jthread
旧梦952737 分钟前
Java 捕获多个异常:从基础语法到最佳实践
java·开发语言
2601_9621896041 分钟前
【SpringBoot】使用IDEA创建SpringBoot项目
java·spring boot·intellij-idea
步行cgn42 分钟前
Spring Boot 启动报错:MissingServletWebServerFactoryBean 的排查与解决
java·spring boot·后端
XLYcmy42 分钟前
HTML/CSS/JS 基础与 Vue 技术栈深度解析
java·前端·css·html·vue3·vue2·api
~木雨1 小时前
String 底层原理与常量池全解析:byte []+coder、intern 陷阱、substring 内存泄漏,一篇讲透
java·jvm·内存优化·string·字符串常量池
新知图书1 小时前
12.3 智能体中Skill的约束与自进化实战
java·前端·数据库