首页 > 编程语言 >别再手动解析LLM输出!LangChain四种结构化输出方案对比

别再手动解析LLM输出!LangChain四种结构化输出方案对比

来源:互联网 2026-07-24 08:18:14

LangChain四种结构化输出方案:Pydantic强校验(生产首选)、TypedDict轻量无校验、JSONSchema灵活动态但无类型安全、@dataclass简洁少依赖。推荐正式项目用Pydantic并开启include_raw=True,同时获取解析结果与原始消息。

相信不少人在开发LLM应用时,都遇到过这么个糟心事儿:模型好不容易吐出一段自然语言,可你这边还得费劲巴拉地写正则、json.loads(),甚至还得用上eval()去抓取信息。这代码不仅脆弱得像纸糊的,后续维护起来更是让人头大。

别再手动解析LLM输出!LangChain四种结构化输出方案对比

长期稳定更新的攒劲资源: >>>点此立即查看<<<

其实,LangChain早就提供了更优雅的解法——结构化输出(Structured Output)。你只需要定义一个Schema,模型就会乖乖地按你的要求,返回类型安全的数据。今天,咱们就来全方位对比一下四种主流方式:PydanticTypedDictJSON Schema@dataclass。除此之外,我还会补充两个获取结构化结果的进阶技巧,让你不仅能拿到干净的数据对象,还能顺手捕获原始消息和Token用量,顺便把那些隐藏的“坑”也一并指出来。


LangChain结构化输出环境准备

下面所有的示例,都基于LangChain + OpenRouter的DeepSeek模型。当然,你也可以换成OpenAI、Anthropic这些支持函数调用的模型,套路都一样。

 复制代码from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import osload_dotenv(override=True)
model = init_chat_model(
    model="deepseek-v4-flash",
    model_provider="openai",
    api_key=os.getenv("OPENROUTER_API_KEY"),
    base_url=os.getenv("OPENROUTER_BASE_URL")
)

方案一:Pydantic(生产环境首选)

Pydantic在Python数据验证领域,地位基本是“事实标准”。LangChain对它的支持也是最完善的。你只需要继承BaseModel,加上类型注解和Field描述,再调用with_structured_output,就能拿到一个类型安全的实例。

基础示例

 复制代码from pydantic import BaseModel, Fieldclass Person(BaseModel):
    name: str = Field(description="姓名")
    age: int = Field(description="年龄")
    occupation: str = Field(description="职业")structured = model.with_structured_output(Person)
res = structured.invoke("张三是一名30岁的软件工程师")
print(res)          # name='张三' age=30 occupation='软件工程师'
print(type(res))    # 

高级特性

  • 可选字段 & 默认值
    Optional或者设置default,模型会尽力去推理,如果缺失就用默认值顶上。不过得注意,有些厂商的模型可能不支持默认值,这个得留个心。
  • 枚举限制
    EnumLiteral把可选值锁死,比如紧急程度只能选“高/中/低”,这样能有效避免脏数据。
  • 嵌套与列表
    支持多层嵌套(建议不超过3层)和list[Model],用来提取复杂信息非常顺手。
  • 字段约束
    Field(..., ge=0, le=150)这类约束,会在实例化时进行校验。如果LLM输出的数据不合法,会直接抛出ValidationError,相当于多了一道安全防线。

注意:约束条件只在Pydantic实例化时生效,部分模型可能不遵守,所以服务端返回的JSON仍然可能“越界”。面对这种情况,务必捕获异常。

Pydantic 核心工作流程

很多人只管调用,但不清楚LangChain是怎么把Pydantic类变成LLM能理解的东西的。了解这个流程,以后排查Bug会快很多。整个过程可以分为6步

步骤做了什么关键动作
1定义模型你写class Person(BaseModel),加上类型和描述
2生成 SchemaLangChain调用model.model_json_schema(),自动转成JSON Schema字典
3包装成工具把这个JSON Schema作为LLM的Tool(工具/函数),传入请求参数中
4LLM 推理模型看懂Schema,按规则生成严格符合格式的JSON字符串
5Pydantic 校验LangChain拿到JSON,调用Pydantic进行类型、约束(如le=150)的校验
6实例化返回校验通过后,JSON被转换为Python对象Person实例),交付给你

为什么要懂这个?
如果第5步校验失败(比如模型抽风输出了age=200),程序会直接抛出ValidationError。如果不清楚这个流程,你可能还会误以为是模型没返回数据,而实际上是数据“不干净”被拦截了。


方案二:TypedDict(轻量级类型提示)

如果你不想引入Pydantic这个“重量级”选手,但又希望有类型提示,TypedDict是最佳选择。它只是类型声明,不执行运行时校验,非常适合快速原型验证。

 复制代码
from typing_extensions import TypedDict, Annotatedclass Movie(TypedDict):
    title: Annotated[str, "电影名称"]
    year: Annotated[int, "上映年份"]
    director: Annotated[str, "导演"]
    rating: Annotated[float, "评分"]structured = model.with_structured_output(Movie)
res = structured.invoke("星际穿越")
print(res)          # 字典类型
print(type(res))    # 

亮点Annotated里可以加上描述,LangChain会将其转为Schema的description。你还可以用...占位符来表示必填字段。

缺点:没有校验,字段类型出了问题也不会报错,全凭LLM的“自觉性”。


方案三:JSON Schema(原始字典)

当你需要动态生成Schema,或者根本不想定义任何类时,直接写JSON Schema字典就行了。LangChain通过method="json_schema"来支持。

 复制代码schema = {
    "type": "object",
    "properties": {
        "title": {"type": "string", "description": "电影名称"},
        "year": {"type": "integer", "description": "上映年份"}
    },
    "required": ["title", "year"]
}
structured = model.with_structured_output(schema, method="json_schema")
res = structured.invoke("盗梦空间")
print(res)  # {'title': '盗梦空间', 'year': 2010}

这种方法最灵活,但代价是彻底失去了类型安全和IDE提示。它更适合临时脚本或者配置驱动的场景,用起来快,但维护起来也容易出问题。


方案四:@dataclass(标准库简洁方案)

Python内置的@dataclass也能“冒充”一把Schema,LangChain同样支持。可以配合Pydantic的Field添加描述,但没有运行时验证

 复制代码from dataclasses import dataclass
from pydantic import Field@dataclass
class Movie:
    title: str = Field(description="标题")
    year: int = Field(description="年份")structured = model.with_structured_output(Movie)
res = structured.invoke("流浪地球")
print(res)  # Movie(title='流浪地球', year=2019)

优点是没有额外依赖,缺点和TypedDict类似——不校验类型,而且Field的支持可能不如Pydantic那么全面。


进阶技巧:获取结构化结果及原始响应

除了直接拿到解析后的对象,有时我们还需要原始的AIMessage(用于调试、获取tool_calls或审计),或者想统计Token消耗。LangChain提供了两种便捷的方式。

使用with_structured_output(..., include_raw=True)

在调用with_structured_output时,传入include_raw=True,返回的将是一个字典,包含三个字段:

  • raw:原始的AIMessage对象(包含完整的响应元数据)
  • parsed:解析后的结构化对象(如果解析成功)
  • parsing_error:解析过程中的异常(如果有)
 复制代码from pydantic import BaseModel, Fieldclass Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")
    director: str = Field(description="导演")
    rating: float = Field(description="评分(10分制)")# 开启 include_raw
structured = model.with_structured_output(Movie, include_raw=True)
resp = structured.invoke("给我介绍下电影《星际穿越》")print(type(resp))  # 
print(resp.keys()) # dict_keys(['raw', 'parsed', 'parsing_error'])# 查看解析后的对象
print(resp['parsed'])  # Movie(title='星际穿越', year=2014, ...)# 查看原始消息的令牌用量
print(resp['raw'].usage_metadata)  # 含 input_tokens, output_tokens 等

适用场景:你既需要结构化数据,又需要监控成本或调试原始输出。这个方案一步到位,非常推荐。

使用JsonOutputParser(传统管道方式)

LangChain还提供了JsonOutputParser,配合ChatPromptTemplate和管道|操作符使用。这种方式更加“显式”,适合需要自定义Prompt的场景。

 复制代码from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Fieldclass Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")parser = JsonOutputParser(pydantic_object=Movie)prompt = ChatPromptTemplate.from_messages([
    ("system", "回答用户问题,必须始终输出一个包含 title 和 year 的 JSON 对象"),
    ("human", "问题:{question}")
])chain = prompt | model | parser
response = chain.invoke({"question": "介绍电影《盗梦空间》"})
print(response)  # {'title': '盗梦空间', 'year': 2010}

注意JsonOutputParser返回的是字典,而不是Pydantic实例。如果想获得Pydantic对象,可以改用PydanticOutputParser。而且,这种方式不能直接获取原始AIMessage,需要额外处理。

对比

  • with_structured_output(include_raw=True)更简洁,一步到位,在大多数场景下都推荐使用。
  • JsonOutputParser更灵活,适合需要精细控制Prompt和解析流程的老项目。

LangChain结构化输出方案对比表

方案运行时校验自动类型转换IDE 友好可获取原始消息典型场景
Pydantic 强校验(include_raw)生产级 API,数据质量严苛
TypedDict(include_raw)快速原型,字典操作
JSON Schema(include_raw)动态 Schema,临时调用
@dataclass(include_raw)轻量数据类,无校验需求
JsonOutputParser(只解析)(需额外处理)自定义 Prompt 管道,传统方式

建议:正式项目别犹豫,直接上Pydantic,并开启include_raw=True。这样既能保证数据质量,又能监控Token成本。如果追求极致性能,而且对数据质量有足够信心,TypedDict也是个不错的选择。


避坑指南

  1. 默认值「失灵」:不同模型供应商对默认值的支持不一致。实测OpenRouter表现良好,但某些闭源模型可能会忽略默认值。所以,最好在业务层做个兜底。
  2. 嵌套不宜太深:LLM对深层嵌套(超过3层)的理解有限,容易漏字段或乱填,尽量让结构扁平化。
  3. 描述是王道:字段的description直接影响提取的准确性,务必写清楚示例或范围,别偷懒。
  4. 校验异常处理:使用Pydantic时,务必捕获ValidationError,否则程序一旦遇到脏数据就可能直接崩溃。
  5. include_raw=True的副作用:返回的是字典而非对象,记得取parsed字段,那才是你的结构化数据。
  6. JsonOutputParser 与 with_structured_output 的区别:前者更“底层”,后者更“智能”,优先推荐后者。

总结

LangChain的with_structured_output,把“结构化输出”这件事变得异常简单。我们只需要根据场景,选择合适的Schema定义方式就行。无论是追求严谨的Pydantic,还是灵活的JSON Schema,都能显著提升开发效率,让你彻底告别那些烦人的字符串解析。而include_raw=TrueJsonOutputParser,则提供了更细粒度的控制,让成本监控和调试变得轻而易举。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。