Skip to content

LangChain 结构化输出

概要

with_structured_output() 让 LLM 按预定义 Schema 返回结构化对象——不再是自由文本,而是可直接被下游代码消费的 Pydantic 实例或 dict。一个方法调用替代了传统的"Prompt 指令 + OutputParser + 异常重试"长链路,把 Schema 直接传给模型底层 API,由模型侧原生保证输出格式。适用场景包括 结构化提取数据分类表单填充

LangChain 支持四种 Schema 定义方式:Pydantic BaseModel(推荐首选)、TypedDict(轻量 dict)、JSON Schema(跨语言)、@dataclass(dataclass 风格)。

一、四种 Schema 方式总览

四种方式的差异只在 schema 参数,模型调用代码无需变化:

Schema 方式输出类型自带校验适用场景
Pydantic BaseModel实例(result.field生产环境,类型安全、校验完备
TypedDictdict(result["key"]简单键值结构,快速原型
JSON Schema dictdict(result["key"]已有 JSON Schema、跨语言复用
@dataclass实例(result.field✅*偏好 dataclass 风格

@dataclass 需用 pydantic.dataclasses.dataclass,标准库 dataclasses.dataclass 不兼容。

选择建议

生产环境首选 Pydantic BaseModel;简单提取用 TypedDict;已有 JSON Schema 用 JSON Schema dict;偏好 dataclass 风格用 pydantic.dataclasses

二、Pydantic 模式详解

Pydantic 是官方推荐的结构化输出载体。定义 BaseModel 子类作为 Schema,模型按字段定义返回经过校验的实例。

1. 基础使用

python
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

class Joke(BaseModel):
    """笑话结构"""
    setup: str = Field(description="笑话的铺垫部分")
    punchline: str = Field(description="笑话的包袱/笑点")

model = ChatOpenAI(model="gpt-4o-mini")
structured_model = model.with_structured_output(Joke)

result = structured_model.invoke("给我讲一个关于程序员的冷笑话")
# result.setup / result.punchline —— Pydantic 实例,属性访问
python
from pydantic import BaseModel, Field

class PersonInfo(BaseModel):
    """人物信息提取"""
    name: str = Field(description="人物姓名")
    age: int = Field(description="年龄,未知则填 -1")
    occupation: str = Field(description="职业")
    skills: list[str] = Field(description="技能列表")

model = ChatOpenAI(model="gpt-4o-mini")
structured_model = model.with_structured_output(PersonInfo)

result = structured_model.invoke("张三,28岁,全栈工程师,精通 Vue 和 Python。")
# result.name, result.skills, ...
python
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "从用户输入中提取人物信息。"),
    ("user", "{input}"),
])

chain = prompt | structured_model
result = chain.invoke({"input": "李四,35岁,架构师"})

WARNING

Field(description=...) 决定模型输出质量——description 随 Schema 一起发送给模型。每个字段都应该写 description。

2. 可选字段与默认值

模型并非总能获取所有信息。用 Optional + default=None 让字段在无信息时返回 None——不设则模型会强行猜测。用 default 预设合理缺省值,减少模型推断压力:

python
from typing import Optional
from pydantic import BaseModel, Field

class Ticket(BaseModel):
    """客服工单"""
    summary: str = Field(description="问题摘要")
    priority: Optional[str] = Field(default=None, description="紧急程度,未提及时为 None")
    language: str = Field(default="zh-CN", description="用户语言")
    channel: str = Field(default="chat", description="提交渠道:chat / email / phone")

3. 枚举与约束

Literal 将字段约束到有限取值集合,模型输出非枚举值会触发 Pydantic 校验失败,LangChain 自动重试。Field 的约束参数(ge/le/pattern/min_length 等)提供更细粒度的边界控制:

python
from typing import Literal
from pydantic import BaseModel, Field

class Ticket(BaseModel):
    """客服工单"""
    summary: str = Field(description="问题摘要", min_length=5, max_length=200)
    category: Literal["技术故障", "账号问题", "投诉建议", "退款申请", "其他"] = Field(
        description="工单分类"
    )
    severity: Literal["低", "中", "高", "紧急"] = Field(default="中", description="严重程度")
    priority_score: int = Field(description="优先级评分", ge=1, le=10, default=5)
    customer_phone: str = Field(description="联系电话", pattern=r"^1[3-9]\d{9}$", default="")
约束类型说明
ge / leint, float最小值 / 最大值
gt / ltint, float严格大于 / 小于
min_length / max_lengthstr, list长度范围
patternstr正则匹配
min_items / max_itemslist列表项数量范围

约束在校验阶段生效——不满足时 LangChain 自动重试直至合规或达上限。

4. 列表与嵌套

从文本中提取多个同类实体用 list[Item];复杂场景下用 BaseModel 嵌套形成层次化结构:

python
from typing import Optional
from pydantic import BaseModel, Field

class CustomerInfo(BaseModel):
    name: str = Field(description="客户姓名")
    phone: Optional[str] = Field(default=None, description="联系电话")

class OrderInfo(BaseModel):
    order_id: Optional[str] = Field(default=None, description="订单号")
    product: str = Field(description="涉及产品")

class Ticket(BaseModel):
    """客服工单(嵌套结构)"""
    customer: CustomerInfo = Field(description="客户信息")
    order: Optional[OrderInfo] = Field(default=None, description="关联订单")
    summary: str = Field(description="问题摘要")
    action_items: list[str] = Field(default_factory=list, description="待办事项列表")

嵌套结构让 Schema 与业务模型对齐——客户、订单、工单各司其职,下游代码按层级消费。default_factory=list 优于 default=[],避免 Pydantic 共享可变默认值陷阱。

三、其他三种 Schema 方式

1. TypedDict — 轻量 dict 输出

定义最简洁,输出为普通 dict。可选字段用 Annotated[类型, None, "描述"] 标记:

python
from typing import TypedDict
from typing_extensions import Annotated
from langchain_openai import ChatOpenAI

class MovieDict(TypedDict):
    """电影信息"""
    title: Annotated[str, "电影名称"]
    year: Annotated[int, "上映年份"]
    rating: Annotated[str | None, None, "评分,未知则为 None"]

model = ChatOpenAI(model="gpt-4o-mini")
structured = model.with_structured_output(MovieDict)
result = structured.invoke("《肖申克的救赎》是哪一年的?")
# {'title': '肖申克的救赎', 'year': 1994} —— dict 访问

WARNING

TypedDict 输出为 dict,无校验能力——多余字段或类型不匹配不会报错。对可靠性有要求的场景优先用 Pydantic。

2. JSON Schema — 原生字典定义

直接传入 JSON Schema dict,适合已有 Schema 定义或跨语言复用:

python
from langchain_openai import ChatOpenAI

json_schema = {
    "title": "WeatherInfo",
    "description": "天气信息",
    "type": "object",
    "properties": {
        "city": {"type": "string", "description": "城市名称"},
        "temperature": {"type": "number", "description": "摄氏温度"},
        "condition": {"type": "string", "enum": ["晴", "多云", "雨", "雪"]},
    },
    "required": ["city", "temperature"],
}

model = ChatOpenAI(model="gpt-4o-mini")
structured = model.with_structured_output(json_schema)
result = structured.invoke("北京今天天气怎么样?")

3. @dataclass — Pydantic dataclass 风格

pydantic.dataclasses.dataclass 替代 BaseModel,语法更轻,同时保留校验能力:

python
from pydantic.dataclasses import dataclass
from pydantic import Field
from langchain_openai import ChatOpenAI

@dataclass
class Person:
    """人物信息"""
    name: str = Field(description="姓名")
    age: int = Field(description="年龄")
    occupation: str = Field(description="职业")

model = ChatOpenAI(model="gpt-4o-mini")
structured = model.with_structured_output(Person)
result = structured.invoke("小王,25岁,前端工程师")

WARNING

标准库 from dataclasses import dataclass 不兼容,必须用 from pydantic.dataclasses import dataclass

小结

with_structured_output() 把 Schema 传给模型底层 API,让输出从自由文本变为 结构化对象。四种 Schema 方式中,Pydantic BaseModel 是生产首选——类型安全、校验完备、约束丰富。TypedDict 轻量但无校验,JSON Schema 适合跨语言复用,@dataclass 是语法替代。

结构化输出与 Tools 工具 是 Agent 的两大核心能力:with_structured_output() 约束输出格式,bind_tools() 赋予外部行动力。两者通过 LCEL 组合——prompt | model.with_structured_output(...)prompt | model.bind_tools(...)——覆盖了 LLM 应用的绝大多数场景。

📖 相关资源

CC BY-NC-SA 4.0 协议