2026-08-08
AI
0

目录

1. 四种使用模式
1.1 Pydantic模式
1.1.1 基本使用
1.1.2 高级特性
1.1.2.1 可选字段
1.1.2.2 默认值
1.1.2.3 枚举类型
1.1.2.4 列表提取
1.1.2.5 嵌套结构
1.1.2.6 限制条件
1.2 TypedDict模式
1.2.1 基本使用
1.2.1.1 Annotated的使用
1.2.1.2 ...的使用
1.3 JSON Schema模式
1.4@dataclass模式
2. 类型校验
3.获取结构化结果方式
3.1 使用withstructuredoutput
3.2 使用输出解析器(不推荐)

在上一篇中我们讲述了工具的使用,这一篇我们说一下结构化输出。什么是结构化输出呢?LangChain的结构化输出(Structured Output) 指的是:要求模型最终返回一个符合预定义结构的数据对象,例如固定字段的JSONPydantic 模型、TypedDict,而不再是无格式的自然语言文本。

目前LangChain 1.x 支持多种Schema与结构化输出方式:

  • Pydantic(字段校验、描述、嵌套结构,功能最丰富)
  • TypedDict(轻量类型约束)
  • JSON Schema(与前后端/跨语言接口最通用)
  • dataclass

模型对象可以调用 with_structured_output() 绑定输出模式(schema)。

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

1. 四种使用模式

1.1 Pydantic模式

它通过在运行时强制执行类型提示,确保数据的正确性和一致性,是 生产场景首选 。

1.1.1 基本使用

需要满足的几个要素:

  • 所有结构化输出的数据模型都必须继承 BaseModel
  • 使用 类型提示 。Pydantic 支持丰富的字段类型:strintfloatList[xxx]Optional[xxx]
  • 使用 Field() 添加字段默认值和描述,帮助 LLM 理解字段含义

示例:

python
import os from dotenv import load_dotenv from langchain.chat_models import init_chat_model from pydantic import BaseModel, Field load_dotenv() model = init_chat_model( model="qwen3.7-plus", model_provider="openai", # 关键:指定使用openai兼容协议 api_key=os.getenv("DASHSCOPE_API_KEY"), base_url=os.getenv("DASHSCOPE_API_BASE_URL") ) class Person(BaseModel): """人物信息""" name: str = Field(description="姓名") age: int = Field(description="年龄") occupation: str = Field(description="职业") # class MovieModel(BaseModel): # """ # 电影的详细信息 # """ # title: str = Field(description="电影标题") # year: int = Field(description="电影上映年份") # director: str = Field(description="导演") # rating: float = Field(description="电影评分,满分十分") # 创建结构化输出的 LLM structured_llm = model.with_structured_output(Person) # 调用 result = structured_llm.invoke("张三是一名 30 岁的软件工程师") print(result) print(type(result)) # result 是 Person 实例 print(result.name) # "张三" print(result.age) # 30 print(result.occupation) # "软件工程师"

1.1.2 高级特性

1.1.2.1 可选字段

问题:LLM 未填充某些字段怎么办? 使用 Optional 指定字段为可选的。

python
from pydantic import BaseModel, Field from typing import Optional class Person(BaseModel): """人物信息""" name: str = Field(description="姓名") age: int = Field(description="年龄") occupation: str = Field(description="职业") structured_llm = model_with_closeai.with_structured_output(Person) structured_llm.invoke("张三是一名医生") # Person(name='张三', age=0, occupation='医生')

上面案例可见,如果模型分析不出age,输出的信息中age是0,如果我们的需求是如果缺少该字段,该字段的值是None。这时候可以将age设置为可选的。

python
from typing import Optional from pydantic import BaseModel, Field class Person(BaseModel): """人物信息""" name: str = Field(description="姓名") age: Optional[int] = Field(description="年龄") occupation: str = Field(description="职业") structured_llm = model_with_closeai.with_structured_output(Person) structured_llm.invoke("张三是一名医生") # Person(name='张三', age=None, occupation='医生')

1.1.2.2 默认值

LLM 未提供的信息会使用默认值。格式如下:

Field(default="默认值", description="描述")

注意:不同模型提供商对default字段的支持是不同的。

python
class Person(BaseModel): """人物信息""" name: str = Field(description="姓名") age: int = Field(1,description="年龄") # age 默认值为 1 occupation: str = Field(description="职业")

1.1.2.3 枚举类型

问题:如何限制字段的可选值?

回答:使用枚举。

python
from enum import Enum class Priority(str, Enum): LOW = "低" MEDIUM = "中" HIGH = "高" class Task(BaseModel): title: str priority: Priority # 只能是 LOW/MEDIUM/HIGH

如果嫌单独定义一个 Enum 类太麻烦,也可以直接导入 typing 中的 Literal ,直接在字段里把允许的值写死。

python
from typing import Optional, Literal from pydantic import BaseModel, Field class CustomerInfo(BaseModel): """客户信息""" name: str = Field(description="客户姓名") phone: str = Field(description="电话号码") email: Optional[str] = Field("未提供", description="邮箱") issue: str = Field(description="问题描述") # 使用 Literal 直接限定字面量值 urgency: Literal["低", "中", "高"] = Field(description="紧急程度") # 测试 structured_llm = model.with_structured_output(CustomerInfo) conversation = """ 客服: 您好,请问有什么可以帮助您? 客户: 我是王小明,电话 138-1234-5678,我的订单一直没发货,很着急! 客服: 好的,我帮您查一下 """ result = structured_llm.invoke(f"从以下客服对话中提取客户信息:\n{conversation}") print(result) # name='王小明' phone='138-1234-5678' email=None issue='订单未发货' urgency='高'

1.1.2.4 列表提取

python
from typing import List class Person(BaseModel): """人物信息""" name: str age: int class PersonList(BaseModel): """人物列表信息""" people: List[Person] # 多个 Person 对象 structured_llm = model.with_structured_output(PersonList) result = structured_llm.invoke("张三 30岁,李四 25岁") print(result) # people=[Person(name='张三', age=30), Person(name='李四', age=25)]

案例2:

python
class Review(BaseModel): """产品评论""" product: str rating: int = Field(description="评分 1-5") pros: List[str] = Field(description="优点列表") cons: List[str] = Field(description="缺点列表") structured_llm = model.with_structured_output(Review) review = structured_llm.invoke(""" iPhone 17 很棒!摄像头强大,手感好。但是价格贵,没有充电器。4分。 """) print(review) # product='iPhone 17' rating=4 pros=['摄像头强大', '手感好'] cons=['价格贵', '没有充电器']

应用场景:

  • 批量处理用户评论
  • 自动生成分析报告
  • 发现产品改进点

1.1.2.5 嵌套结构

python
from pydantic import BaseModel class Address(BaseModel): """地点描述""" city: str district: str class Company(BaseModel): """公司信息""" name: str address: Address # 嵌套模型 structured_llm = model.with_structured_output(Company)

说明:LLM 能力有限,复杂嵌套结构可能会出错。所以建议:

  • 嵌套层级 ≤ 3 层
  • 使用清晰的 description
  • 必要时拆分成多个调用

1.1.2.6 限制条件

python
from pydantic import ValidationError class User(BaseModel): name: str = Field(min_length=2, max_length=20) age: int = Field(ge=0, le=150) email: str print("\n有效数据:") try: user = User(name="张三", age=30, email="zhang@example.com") print(f"[OK] {user.name}, {user.age}, {user.email}") except ValidationError as e: print(f"[FAIL] {e}") print("\n无效数据(年龄超出范围):") try: user = User(name="李四", age=200, email="li@example.com") print(f"[OK] {user}") except ValidationError as e: print(f"[FAIL] 验证失败(符合预期): {e.errors()[0]['msg']}") #有效数据: # [OK] 张三, 30, zhang@example.com # 无效数据(年龄超出范围): # [FAIL] 验证失败(符合预期): Input should be less than or equal to 150

1.2 TypedDict模式

TypedDict 是 Python 3.8+ 引入的一种类型提示工具,即带有类型声明的字典结构。适合需要快速定义字典结构且无需 Pydantic 重量级功能的场景。

TypedDict 可以进一步说明:

  • 这个字典应该有哪些字段
  • 每个字段的类型是什么
  • TypedDict 主要是类型声明,不是运行时强校验器。
python
from typing_extensions import TypedDict class MovieDict(TypedDict): title: str year: int director: str rating: float movie: MovieDict = { "title1": "盗梦空间", "year": 2010, "director": "克里斯托弗·诺兰", "rating": 8.8, } print(movie)

1.2.1 基本使用

1.2.1.1 Annotated的使用

Annotated 用来在“类型”之外,再附加一些额外信息,即元数据。类似于 PydanticField

基本形式: Annotated[类型, 附加信息1, 附加信息2, ...]

示例:

python
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("给我介绍下电影《星际穿越》") print(type(response)) print(response) # <class 'dict'> # {'title': '星际穿越', 'year': 2014, 'director': '克里斯托弗·诺兰', 'rating': 9.2}

TypedDict也支持类型嵌套

python
from typing import TypedDict, List, Annotated # 使用TypedDict定义嵌套结构 class Actor(TypedDict): """演员情况""" name: Annotated[str, "演员姓名"] role: Annotated[str, "饰演的角色"] class Movie(TypedDict): """电影情况""" title: Annotated[str, "电影标题"] year: Annotated[int, "上映年份"] director: Annotated[str, "导演"] cast: Annotated[List[Actor], "演员列表"] # 嵌套列表定义 rating: Annotated[float, "评分"]

1.2.1.2 ...的使用

不同的模型提供商效果不一样

python
from typing_extensions import TypedDict, Annotated class MovieDict(TypedDict): """ 电影的详细信息 """ title: Annotated[str, ..., "电影标题"] year: Annotated[int, ..., "电影上映年份"] director: Annotated[str, ..., "导演"] rating: Annotated[float, ..., "电影评分,满分十分"] model_with_structure = model.with_structured_output(MovieDict) response = model_with_structure.invoke("根据这段话抽取盗梦空间的信息,不包含的信息可以留空:盗梦空间在2010年上映,导演是克里斯托弗·诺兰。") print(response) print(type(response)) # {'title': '盗梦空间', 'year': 2010, 'director': '克里斯托弗·诺兰', 'rating': 0} # <class 'dict'>

说明:上述代码的 ... 是Python的字面量,等价于 Ellipsis ,可以理解为占位符。下游框架(如LangChain)可以对 ... 作定制化处理,如LangChain中Annotated的 ... 表示当前字段是必须存在的,不可省略,用来指示模型的输出。

1.3 JSON Schema模式

这种方式需要按照JSON Schema规范拼接JSON字符串,比较繁琐,并且缺少校验机制。不推荐。

python
json_schema = { "title": "Movie", "description": "A movie with details", "type": "object", "properties": { "title": { "type": "string", "description": "The title of the movie" }, "year": { "type": "integer", "description": "The year the movie was released" }, "director": { "type": "string", "description": "The director of the movie" }, "rating": { "type": "number", "description": "The movie's rating out of 10" } }, "required": ["title", "year", "director", "rating"] } structured_model = model.with_structured_output( json_schema, method="json_schema" ) response = structured_model.invoke("给出盗梦空间的信息") print(response) # {'title': '盗梦空间', 'year': 2010, 'director': '克里斯托弗·诺兰', 'rating': 9.3} # <class 'dict'>

说明:

1、method:结构化输出的方式,但是否可用,依赖于模型供应商及Langchain适配器的具体实现。比 如,DeepSeek模型服务不支持json_shema模式。

  • json_schema:使用模型供应商提供的专用结构化输出功能。

2、以上代码中定义json_schema的时候指定的title、description、type、properties、required是遵循JSON Schema 规范的标准关键字,是固定写法。几个关键字的解释如下:

  • title :为整个 Schema 或特定属性提供一个人类可读的标题,不能是中文,用于提高可读性。
  • description :提供更详细的文字描述,说明 Schema 或属性的用途等,和 title一样,旨在帮助理解。
  • type :定义当前数据节点必须是什么数据类型。常见类型有 string, number, integer, boolean,object, array, null。object即是json对象。
  • properties :用于定义JSON 对象(Object)中可以包含哪些属性(键),以及每个属性对应的值类型和说明。
  • required :当 type为 "object"时使用,是一个数组,列出了对象中必须存在的属性名。

1.4@dataclass模式

@dataclass是 Python 标准库 dataclasses 提供的类装饰器,用于简化“以字段为核心”的数据类定义。 给类加上 @dataclass 后,Python 会根据字段声明自动生成常用方法,例如:

  • __init__
  • __repr__
  • __eq__
python
from pydantic import Field from dataclasses import dataclass @dataclass class Movie(): """ 电影的详细信息 """ title: str = Field(description="电影标题") year: int = Field(description="电影上映年份") director: str = Field(description="导演") rating: float = Field(description="电影评分,满分十分") structured_model = model.with_structured_output(Movie) response = structured_model.invoke("给出盗梦空间的信息") print(response) print(type(response))

2. 类型校验

这里以pydantic模式为例

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="电影评分,满分十分") model_with_structure = model.with_structured_output(MovieModel) response = model_with_structure.invoke("给出盗梦空间的信息") print(response) print(type(response))

模拟大模型的响应信息和pydantic定义的数据格式不一致 image.png

只有pydantic模式,如果格式不一致会报错 image.png

3.获取结构化结果方式

以上定义输出结构的四种模式中,我们都是通过调用“with_structured_output”来获取结构化输出结果,除了这种方式外,还可以通过使用输出解释器来获取结构化输出结果。下面介绍这两种获取结构化结果的方式。

3.1 使用with_structured_output

这种方式是 最新 、 最简洁 的API,直接让模型“理解”你需要的数据结构,并返回解析好的对象。 此外,我们可以在with_structured_output方法中传入 include_raw=True 参数,表示返回解析前的 原始AIMessage ,从而访问令牌用量等元数据。

python
class Movie(BaseModel): """电影信息""" title: str = Field(description="电影标题") year: int = Field(description="上映年份") director: str = Field(description="导演") rating: float = Field(description="评分(10分制)") # 设置模型结构化输出 model_with_structure = model.with_structured_output(Movie, include_raw=True) # 调用模型并获取结构化输出 resp = model_with_structure.invoke("给我介绍下电影《星际穿越》")

输出包含了完整的输出响应,包含三个字段

  • raw:返回的原始AIMessage。
  • parsed:解析后的输出
  • parsing_error:解析错误,当前用的是Pydantic,校验,格式不符合schema会导致报错。其它 三种方式不符合schema不会导致报错。

3.2 使用输出解析器(不推荐)

这种方法更传统,依赖于在提示词中明确指示模型输出特定格式的文本,然后使用解析器进行转换。 其流程是: 提示词指导 (引导生成指定类型) → 模型生成文本 → 解析器转换

python
from langchain_core.output_parsers import JsonOutputParser from langchain_core.prompts import ChatPromptTemplate # 1. 创建提示词模板 prompt_template = ChatPromptTemplate.from_messages([ ("system", "回答用户问题,必须始终输出一个包含title(电影标题)和year(上映年份)的JSON 对象"), ("human", "问题:{question}") ]) # 3. 定义结构 class Movie(BaseModel): """电影信息""" title: str = Field(description="电影标题") year: int = Field(description="上映年份") # 4. 创建输出解析器 parser = JsonOutputParser(pydantic_object=Movie) # 5. 创建链 chain = prompt_template | model | parser # 6. 调用(返回字典) response = chain.invoke({"question": "介绍电影《盗梦空间》"}) print(response)
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:繁星

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!