LangChain 结构化输出(Structured Output)技术文档

在 LangChain 的 Model I/O 架构中,结构化输出是连接大语言模型(LLM)与下游业务系统的关键桥梁。它负责将模型生成的非结构化自然语言,转化为程序可直接消费、校验和处理的强类型数据对象。本章将深入剖析结构化输出的四种 Schema 定义模式、底层校验机制以及生产环境中的最佳实践。


一、 核心概念与架构定位

1.1 什么是结构化输出

结构化输出的核心目标是将"自然语言回答"转化为"程序可稳定消费的数据"。模型最终返回的必须是一个符合预定义结构的数据对象(如 Pydantic 模型实例、TypedDict、标准字典),而不再是无格式的自然语言文本。

工程价值

  1. 易于代码处理:下游系统可直接通过属性或键名读取数据,无需编写脆弱的正则表达式或字符串分割逻辑。
  2. 结果高度稳定:消除"模型说法变了但意思差不多"导致的解析失败。
  3. 适配复杂工作流:完美支持表单抽取、分类路由、工具参数生成、Agent 状态传递等场景。

1.2 传统方式 vs 结构化输出

#mermaid-svg-N4ioKMqXvaxwbNqU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-N4ioKMqXvaxwbNqU .error-icon{fill:#552222;}#mermaid-svg-N4ioKMqXvaxwbNqU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-N4ioKMqXvaxwbNqU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-N4ioKMqXvaxwbNqU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-N4ioKMqXvaxwbNqU .marker.cross{stroke:#333333;}#mermaid-svg-N4ioKMqXvaxwbNqU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-N4ioKMqXvaxwbNqU p{margin:0;}#mermaid-svg-N4ioKMqXvaxwbNqU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU .cluster-label text{fill:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU .cluster-label span{color:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU .cluster-label span p{background-color:transparent;}#mermaid-svg-N4ioKMqXvaxwbNqU .label text,#mermaid-svg-N4ioKMqXvaxwbNqU span{fill:#333;color:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU .node rect,#mermaid-svg-N4ioKMqXvaxwbNqU .node circle,#mermaid-svg-N4ioKMqXvaxwbNqU .node ellipse,#mermaid-svg-N4ioKMqXvaxwbNqU .node polygon,#mermaid-svg-N4ioKMqXvaxwbNqU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-N4ioKMqXvaxwbNqU .rough-node .label text,#mermaid-svg-N4ioKMqXvaxwbNqU .node .label text,#mermaid-svg-N4ioKMqXvaxwbNqU .image-shape .label,#mermaid-svg-N4ioKMqXvaxwbNqU .icon-shape .label{text-anchor:middle;}#mermaid-svg-N4ioKMqXvaxwbNqU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-N4ioKMqXvaxwbNqU .rough-node .label,#mermaid-svg-N4ioKMqXvaxwbNqU .node .label,#mermaid-svg-N4ioKMqXvaxwbNqU .image-shape .label,#mermaid-svg-N4ioKMqXvaxwbNqU .icon-shape .label{text-align:center;}#mermaid-svg-N4ioKMqXvaxwbNqU .node.clickable{cursor:pointer;}#mermaid-svg-N4ioKMqXvaxwbNqU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-N4ioKMqXvaxwbNqU .arrowheadPath{fill:#333333;}#mermaid-svg-N4ioKMqXvaxwbNqU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-N4ioKMqXvaxwbNqU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-N4ioKMqXvaxwbNqU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N4ioKMqXvaxwbNqU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-N4ioKMqXvaxwbNqU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N4ioKMqXvaxwbNqU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-N4ioKMqXvaxwbNqU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-N4ioKMqXvaxwbNqU .cluster text{fill:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU .cluster span{color:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-N4ioKMqXvaxwbNqU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-N4ioKMqXvaxwbNqU rect.text{fill:none;stroke-width:0;}#mermaid-svg-N4ioKMqXvaxwbNqU .icon-shape,#mermaid-svg-N4ioKMqXvaxwbNqU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N4ioKMqXvaxwbNqU .icon-shape p,#mermaid-svg-N4ioKMqXvaxwbNqU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-N4ioKMqXvaxwbNqU .icon-shape .label rect,#mermaid-svg-N4ioKMqXvaxwbNqU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N4ioKMqXvaxwbNqU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-N4ioKMqXvaxwbNqU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-N4ioKMqXvaxwbNqU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 结构化输出:简洁且强类型
定义 Pydantic/TypedDict Schema
with_structured_output 绑定
模型基于 Schema 约束生成
LangChain 自动解析与类型校验
直接获取强类型 Python 对象
传统方式:繁琐且脆弱
编写 Prompt 苦求返回 JSON
模型生成文本
手动 json.loads 解析
手动 try-except 捕获异常
手动校验字段类型与业务逻辑


二、 底层工作流与核心机制

2.1 with_structured_output 的核心作用

在 LangChain 1.x 中,模型对象通过调用 with_structured_output(schema) 方法绑定输出模式。该方法返回一个新的 Runnable 对象,其内部封装了 Schema 转换、Prompt 注入、结果解析与校验的完整逻辑。

2.2 结构化输出的底层工作流

大语言模型 LangChain 开发者 大语言模型 LangChain 开发者 #mermaid-svg-0UG07OMZikjw7el2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0UG07OMZikjw7el2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0UG07OMZikjw7el2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0UG07OMZikjw7el2 .error-icon{fill:#552222;}#mermaid-svg-0UG07OMZikjw7el2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0UG07OMZikjw7el2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0UG07OMZikjw7el2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0UG07OMZikjw7el2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0UG07OMZikjw7el2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0UG07OMZikjw7el2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0UG07OMZikjw7el2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0UG07OMZikjw7el2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0UG07OMZikjw7el2 .marker.cross{stroke:#333333;}#mermaid-svg-0UG07OMZikjw7el2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0UG07OMZikjw7el2 p{margin:0;}#mermaid-svg-0UG07OMZikjw7el2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0UG07OMZikjw7el2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0UG07OMZikjw7el2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0UG07OMZikjw7el2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0UG07OMZikjw7el2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0UG07OMZikjw7el2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0UG07OMZikjw7el2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0UG07OMZikjw7el2 .sequenceNumber{fill:white;}#mermaid-svg-0UG07OMZikjw7el2 #sequencenumber{fill:#333;}#mermaid-svg-0UG07OMZikjw7el2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0UG07OMZikjw7el2 .messageText{fill:#333;stroke:none;}#mermaid-svg-0UG07OMZikjw7el2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0UG07OMZikjw7el2 .labelText,#mermaid-svg-0UG07OMZikjw7el2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0UG07OMZikjw7el2 .loopText,#mermaid-svg-0UG07OMZikjw7el2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0UG07OMZikjw7el2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0UG07OMZikjw7el2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0UG07OMZikjw7el2 .noteText,#mermaid-svg-0UG07OMZikjw7el2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0UG07OMZikjw7el2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0UG07OMZikjw7el2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0UG07OMZikjw7el2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0UG07OMZikjw7el2 .actorPopupMenu{position:absolute;}#mermaid-svg-0UG07OMZikjw7el2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-0UG07OMZikjw7el2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0UG07OMZikjw7el2 .actor-man circle,#mermaid-svg-0UG07OMZikjw7el2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0UG07OMZikjw7el2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 模型底层启动语法采样约束(Grammar-based sampling) 1. 定义 Schema (如 Pydantic Model)2. 调用 with_structured_output(Schema)3. 协议转换:将 Schema 转换为标准 JSON Schema4. 模型交互:将 JSON Schema 作为 Tool/Function 注入请求5. 返回符合 JSON 规范的字符串/Tool Call6. 自动解析:JSON 字符串 ->> Python 字典7. 自动校验:字典 ->> Pydantic 实例 (类型/规则校验)8. 返回强类型 Python 对象

核心约束机制:现代大模型(如 OpenAI、Anthropic)普遍支持"函数调用"或"JSON Mode"。LangChain 会将 JSON Schema 作为 Tools 传入,模型在解码生成 Token 时,会严格按照 JSON Schema 的语法树进行选择,从而在模型底层保证了输出格式绝不走样。


三、 四种 Schema 定义模式深度解析

LangChain 支持四种 Schema 定义方式。其中,只有 Pydantic 返回的是 Schema 类实例,其余三种方式返回的都是字典 ;也只有 Pydantic 在类型不匹配时会抛出异常

3.1 模式一:Pydantic(生产环境首选)

Pydantic 提供字段校验、描述、嵌套结构,功能最丰富,是生产场景的绝对首选。

3.1.1 基本使用要素
  1. 必须继承 BaseModel
  2. 使用 Python 类型提示(str, int, List 等)。
  3. 使用 Field() 添加字段描述(Description),这直接充当了 Prompt 的一部分,帮助 LLM 理解字段含义。
python 复制代码
from pydantic import BaseModel, Field

class MovieModel(BaseModel):
    """电影的详细信息"""
    title: str = Field(description="电影标题")
    year: int = Field(description="电影上映年份")
    director: str = Field(description="导演")
    rating: float = Field(description="电影评分,满分十分")

# 绑定并调用
structured_llm = model.with_structured_output(MovieModel)
response = structured_llm.invoke("给出盗梦空间的信息")

# response 是 MovieModel 实例,支持 IDE 自动补全
print(response.title)  # 输出: 盗梦空间
3.1.2 高级特性与平台差异避坑

在实际工程中,不同模型平台(如 CloseAI/OpenAI 与 OpenRouter)对 Pydantic 高级特性的支持存在差异,需特别注意。

特性 1:可选字段(Optional)

当 LLM 无法提取某字段时,使用 Optional 可防止校验失败。

python 复制代码
from typing import Optional
class Person(BaseModel):
    name: str = Field(description="姓名")
    age: Optional[int] = Field(description="年龄") # 缺失时返回 None,而非报错

特性 2:默认值(Default)

python 复制代码
class Config(BaseModel):
    timeout: Optional[int] = Field(30, description="超时时间(秒)")
    retry: bool = Field(False, description="是否支持重试")

⚠️ 平台差异:CloseAI (OpenAI) 能正确应用默认值;但部分中转平台(如早期的 OpenRouter 某些模型)可能会忽略默认值,导致字段缺失或返回 0/False。

特性 3:枚举类型限制(Enum / Literal)

限制字段的可选值,防止模型"幻觉"生成非法状态。

python 复制代码
from typing import Literal
from enum import Enum

class Priority(str, Enum):
    LOW = "低"
    MEDIUM = "中"
    HIGH = "高"

class CustomerInfo(BaseModel):
    issue: str = Field(description="问题描述")
    # 方式 A:使用 Enum 类
    urgency_enum: Priority = Field(description="紧急程度")
    # 方式 B:使用 Literal 直接限定字面量(更简洁)
    urgency_literal: Literal["低", "中", "高"] = Field(description="紧急程度")

特性 4:限制条件(Validation Rules)

使用 Pydantic 的约束参数(如 min_length, gt, ge)。

python 复制代码
class Product(BaseModel):
    name: str = Field(min_length=2)
    price: float = Field(gt=0)  # 必须大于 0
    stock: int = Field(ge=0)    # 必须大于等于 0

⚠️ 核心避坑(模型纠错能力差异)

当 Prompt 传入非法数据(如 price=-7999)时:

  • CloseAI (OpenAI) :模型具备极强的自我纠错能力,会忽略负号,返回 price=7999.0
  • OpenRouter (部分模型) :纠错能力较弱,可能会返回 price=1.0(强行满足 gt=0 的最小值)或直接报错。

3.2 模式二:TypedDict(轻量级类型约束)

TypedDict 是 Python 标准库提供的带有类型声明的字典结构。适合需要快速定义结构且无需 Pydantic 重量级校验的场景。

3.2.1 Annotated 的使用

由于 TypedDict 不支持 Field(),需使用 typing.Annotated 附加元数据(如字段描述)。

python 复制代码
from typing_extensions import TypedDict, Annotated

class MovieTypedDict(TypedDict):
    """电影的详细信息"""
    title: Annotated[str, "电影的正式名称"]
    year: Annotated[int, "电影的公映年份,四位数字"]
    director: Annotated[str, "导演全名"]
    rating: Annotated[float, "10分制评分"]

structured_llm = model.with_structured_output(MovieTypedDict)
response = structured_llm.invoke("介绍下电影《星际穿越》")

# response 是标准字典 (dict)
print(response['title']) 
3.2.2 特殊占位符 ... (Ellipsis) 与平台差异

Annotated 中使用 ... 表示该字段为必填项,不可省略。

python 复制代码
class MovieDict(TypedDict):
    title: Annotated[str, ..., "电影标题"]   # 必填
    year: Annotated[int, ..., "上映年份"]    # 必填
    director: Annotated[str, ..., "导演"]    # 必填
    rating: Annotated[float, "电影评分"]     # 非必填(无 ...)

⚠️ 平台差异

当输入文本缺失 rating 信息时:

  • CloseAI (OpenAI) :严格遵守 ... 约束,输出的字典中不包含 rating 键。
  • OpenRouter :可能无法正确识别 ... 的必填语义,强行补全一个默认值(如 rating: 0)。

3.3 模式三:JSON Schema(跨语言通用)

直接按照 JSON Schema 规范拼接 JSON 字符串。这种方式比较繁琐,且缺少 Python 层面的类型校验,不推荐作为首选,但在需要与前后端跨语言接口对齐时非常有用。

python 复制代码
json_schema = {
    "title": "Movie",
    "type": "object",
    "properties": {
        "title": {"type": "string", "description": "The title of the movie"},
        "year": {"type": "integer", "description": "Release year"},
        "rating": {"type": "number", "description": "Rating out of 10"}
    },
    "required": ["title", "year", "rating"] # 必填字段
}

# 必须显式指定 method="json_schema"
structured_model = model.with_structured_output(json_schema, method="json_schema")
response = structured_model.invoke("给出盗梦空间的信息")
# response 是标准字典 (dict)

3.4 模式四:@dataclass(Python 标准库数据类)

使用 Python 标准库 dataclasses 提供的 @dataclass 装饰器。代码简洁,但不具备运行时强校验能力

python 复制代码
from dataclasses import dataclass
from pydantic import Field # 注意:dataclass 中也可借用 Field 提供描述,但校验由 LangChain 底层处理

@dataclass
class Movie:
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")
    rating: float = Field(description="评分")

structured_model = model.with_structured_output(Movie)
response = structured_model.invoke("给出盗梦空间的信息")
# response 是标准字典 (dict),而非 dataclass 实例

四、 运行时类型校验机制(核心避坑)

为了验证不同 Schema 在数据格式不匹配时的处理方式,我们构造一个 Fake Server,强制返回字段名不匹配的 JSON(如返回 title1 而非 title)。

4.1 四种模式的校验行为对比

Schema 模式 接收到非法响应后的行为 返回值类型 工程影响
Pydantic 抛出 ValidationError 异常,程序中断。 Schema 类实例 最安全。能在第一时间暴露模型输出错误或网络劫持,防止脏数据流入业务逻辑。
TypedDict 静默失败,直接返回原始字典。 dict 存在隐患。业务代码通过 dict['title'] 取值时会抛出 KeyError
JSON Schema 静默失败,直接返回原始字典。 dict 同上。
@dataclass 静默失败,直接返回原始字典。 dict 同上。

结论 :在核心业务链路中,必须使用 Pydantic 以利用其运行时强校验能力。


五、 获取结构化结果的两种范式

5.1 范式一:with_structured_output(现代标准,强烈推荐)

这是 LangChain 1.x 最简洁、最稳定的 API。

进阶用法:include_raw=True

当需要访问底层元数据(如 Token 消耗、原始响应)或处理解析错误时,可传入 include_raw=True

python 复制代码
model_with_structure = model.with_structured_output(Movie, include_raw=True)
resp = model_with_structure.invoke("介绍下电影《星际穿越》")

# resp 是一个包含三个键的字典:
# 1. 'raw': 原始的 AIMessage 对象(包含 usage_metadata 等性能指标)
# 2. 'parsed': 解析后的 Pydantic 实例(Movie 对象)
# 3. 'parsing_error': 解析错误信息(如果模型输出不符合 Schema,此处会记录异常,而非直接崩溃)

print(resp['parsed'].title)
print(resp['raw'].usage_metadata['total_tokens'])

5.2 范式二:输出解析器 JsonOutputParser(传统方式,不推荐)

这种方法依赖于在 Prompt 中明确指示模型输出 JSON 文本,然后使用解析器进行转换。
#mermaid-svg-bR30ZwM998KavY2w{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bR30ZwM998KavY2w .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bR30ZwM998KavY2w .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bR30ZwM998KavY2w .error-icon{fill:#552222;}#mermaid-svg-bR30ZwM998KavY2w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bR30ZwM998KavY2w .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bR30ZwM998KavY2w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bR30ZwM998KavY2w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bR30ZwM998KavY2w .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bR30ZwM998KavY2w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bR30ZwM998KavY2w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bR30ZwM998KavY2w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bR30ZwM998KavY2w .marker.cross{stroke:#333333;}#mermaid-svg-bR30ZwM998KavY2w svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bR30ZwM998KavY2w p{margin:0;}#mermaid-svg-bR30ZwM998KavY2w .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bR30ZwM998KavY2w .cluster-label text{fill:#333;}#mermaid-svg-bR30ZwM998KavY2w .cluster-label span{color:#333;}#mermaid-svg-bR30ZwM998KavY2w .cluster-label span p{background-color:transparent;}#mermaid-svg-bR30ZwM998KavY2w .label text,#mermaid-svg-bR30ZwM998KavY2w span{fill:#333;color:#333;}#mermaid-svg-bR30ZwM998KavY2w .node rect,#mermaid-svg-bR30ZwM998KavY2w .node circle,#mermaid-svg-bR30ZwM998KavY2w .node ellipse,#mermaid-svg-bR30ZwM998KavY2w .node polygon,#mermaid-svg-bR30ZwM998KavY2w .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bR30ZwM998KavY2w .rough-node .label text,#mermaid-svg-bR30ZwM998KavY2w .node .label text,#mermaid-svg-bR30ZwM998KavY2w .image-shape .label,#mermaid-svg-bR30ZwM998KavY2w .icon-shape .label{text-anchor:middle;}#mermaid-svg-bR30ZwM998KavY2w .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bR30ZwM998KavY2w .rough-node .label,#mermaid-svg-bR30ZwM998KavY2w .node .label,#mermaid-svg-bR30ZwM998KavY2w .image-shape .label,#mermaid-svg-bR30ZwM998KavY2w .icon-shape .label{text-align:center;}#mermaid-svg-bR30ZwM998KavY2w .node.clickable{cursor:pointer;}#mermaid-svg-bR30ZwM998KavY2w .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bR30ZwM998KavY2w .arrowheadPath{fill:#333333;}#mermaid-svg-bR30ZwM998KavY2w .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bR30ZwM998KavY2w .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bR30ZwM998KavY2w .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bR30ZwM998KavY2w .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bR30ZwM998KavY2w .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bR30ZwM998KavY2w .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bR30ZwM998KavY2w .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bR30ZwM998KavY2w .cluster text{fill:#333;}#mermaid-svg-bR30ZwM998KavY2w .cluster span{color:#333;}#mermaid-svg-bR30ZwM998KavY2w div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bR30ZwM998KavY2w .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bR30ZwM998KavY2w rect.text{fill:none;stroke-width:0;}#mermaid-svg-bR30ZwM998KavY2w .icon-shape,#mermaid-svg-bR30ZwM998KavY2w .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bR30ZwM998KavY2w .icon-shape p,#mermaid-svg-bR30ZwM998KavY2w .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bR30ZwM998KavY2w .icon-shape .label rect,#mermaid-svg-bR30ZwM998KavY2w .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bR30ZwM998KavY2w .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bR30ZwM998KavY2w .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bR30ZwM998KavY2w :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Prompt 指导

要求输出 JSON
模型生成

纯文本 JSON
JsonOutputParser

正则/AST 解析
Python 字典

代码范式(结合 LCEL)

python 复制代码
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate

# 1. 定义结构
class Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")

# 2. 创建解析器
parser = JsonOutputParser(pydantic_object=Movie)

# 3. 创建 Prompt(必须注入格式指令)
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "回答用户问题,必须始终输出一个包含 title 和 year 的 JSON 对象。{format_instructions}"),
    ("human", "问题:{question}")
])

# 注意:需要将 parser 的格式指令注入 Prompt
prompt = prompt_template.partial(format_instructions=parser.get_format_instructions())

# 4. 构建 LCEL 链
chain = prompt | model | parser

# 5. 调用(返回字典)
response = chain.invoke({"question": "介绍电影《盗梦空间》"})

为什么不推荐?

  1. 极度依赖 Prompt :模型可能会在 JSON 前后添加 Markdown 标记(如 json ... ),导致解析失败。
  2. 缺乏底层约束:模型是"尽力"生成 JSON,而不是像 Function Calling 那样在底层被"强制"生成 JSON,错误率较高。
  3. 冗余代码:需要手动处理格式指令的注入。

六、 总结与工程决策清单

6.1 Schema 模式选型矩阵

场景需求 推荐模式 理由
生产环境核心业务 Pydantic 强类型校验、丰富的约束规则、IDE 完美支持。
轻量级脚本/快速原型 TypedDict 语法简洁,无需引入 Pydantic 依赖,适合简单字典结构。
跨语言/前后端接口对齐 JSON Schema 标准协议,可直接导出给前端或其他语言使用。
纯 Python 数据类偏好 @dataclass 符合 Python 原生习惯,但需注意其无强校验的缺陷。

6.2 生产环境最佳实践清单

  1. 默认使用 Pydantic :利用 Field(description="...") 将业务逻辑说明直接转化为 Prompt,减少 Prompt 编写工作量。
  2. 谨慎处理缺失值 :对于模型可能无法提取的字段,务必使用 Optional[T] 或设置合理的 default 值,防止 ValidationError 阻断主流程。
  3. 利用 include_raw=True 做监控 :在需要统计 Token 成本或记录 LangSmith 追踪时,开启此参数以获取底层 AIMessage
  4. 控制嵌套层级 :LLM 处理深层嵌套 JSON 的能力有限,建议 Pydantic 模型的嵌套层级 ≤ 3 层,必要时拆分为多次调用。
  5. 废弃 JsonOutputParser :除非对接极老旧的不支持 Function Calling 的模型,否则全面拥抱 with_structured_output
相关推荐
吴声子夜歌18 小时前
MongoDB 4.x——数据模型
数据库·mongodb
lhxcc_fly19 小时前
LangGraph 项目部署知识点总结
ai·langchain·项目部署·langgraph
七夜zippoe19 小时前
DolphinDB实时排名计算:Top-N实时排行
数据库·dolphindb·计算·实时排名·top-n
cmes_love19 小时前
CME、LME、CBOT、NYMEX等交易所外盘期货tick和分钟历史行情数据下载和分析
数据库·区块链
早点睡啊Y19 小时前
深入学LangChain 官方文档(九)Memory 记忆系统首讲
langchain
猫猫不是喵喵.19 小时前
双亲委派机制与类加载过程
java·开发语言
大模型码小白19 小时前
向量化引擎与 AI 排障:当 SIMD 遇到异常检测,存储诊断的范式转移
java·大数据·数据库·人工智能·python
程序员清风19 小时前
公司要裁员之前,都会有哪些信号?
java·后端·面试
Flittly19 小时前
【AgentScope Java新手村系列】(20)文字转语音TTS
java·spring boot·spring