前言:为什么需要结构化输出?
在之前的教程中,我们学会了调用聊天模型得到 AIMessage,其中的 content 是一个字符串。例如:
python
model = ChatDeepSeek()
response = model.invoke("告诉我关于苹果公司的最新消息。")
print(response.content)
# "苹果公司于昨日发布了新款iPhone...其股价上涨了2%..."
这段文字对人很友好,但对程序却很不友好。如果你想提取"公司名"和"股价变化"用于后续的数据库写入或计算,就需要写正则表达式或者复杂的解析逻辑,容易出错。
结构化输出就是为了解决这个痛点:它可以让聊天模型按照你指定的 JSON Schema、Pydantic 类或 TypedDict 来生成结构化的数据,就像调用 API 返回 JSON 一样,直接得到对象或字典,无需手动解析。
LangChain 为此提供了**with_structured_output()** 方法。
1、 with_structured_output() 方法
with_structured_output() 是聊天模型的一个方法,它会返回一个新的 Runnable 实例。这个新实例的执行结果不再是文本消息,而是与给定输出结构相对应的对象。
基本使用步骤:
-
定义输出结构(如 Pydantic 类、TypedDict、JSON Schema)
-
调用
model.with_structured_output(schema)得到新的 Runnable -
用
.invoke(user_input)触发,直接得到结构化数据
with_structured_output()方法定义:
python
with_structured_output(
schema: dict | type | None = None,
*,
method: Literal['function_calling', 'json_mode', 'json_schema'] = 'json_schema',
include_raw: bool = False,
strict: bool | None = None,
**kwargs: Any,
) -> Runnable[..., dict | BaseModel]
参数解析:
schema:输出结构。可以传入:
JSON Schema 字典
TypedDict 类
Pydantic 类
OpenAI 函数/工具描述(早期方式)
method:模型生成结构化输出的方式
"json_schema"(默认):使用 OpenAI 的结构化输出 API。
"function_calling":使用工具调用(函数调用)方式。
"json_mode":使用 JSON 模式。注意:如果使用json_mode,需要在提示词中明确说明要返回的 JSON 格式。
include_raw:是否返回原始响应
False(默认):只返回解析后的结构化输出。解析失败会抛出异常。
True:返回一个字典,包含"raw"(原始BaseMessage)、"parsed"(解析后的输出)和"parsing_error"(解析错误,若有)。
strict:是否严格遵循 Schema
True:保证模型输出完全匹配 Schema,同时输入 Schema 也会被验证。
False:不验证。
None(默认):不传递strict参数,由 API 采用默认行为。
kwargs:其他参数会传递给底层的bind()方法
返回值:
一个 Runnable 实例。当执行这个实例时:
-
若
include_raw=False,返回解析后的结构化输出(Pydantic 对象、字典等)。 -
若
include_raw=True,返回包含原始消息、解析结果和错误的字典。
2、三种主流输出格式
2.1、返回 Pydantic 对象
如果输出结构定义为 Pydantic 类,那么执行后会直接得到一个 Pydantic 对象实例,LangChain 会自动将模型返回的 JSON 解析并验证成该对象。
示例:
python
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from typing import Optional
model = ChatDeepSeek(model="deepseek-chat")
class Joke(BaseModel):
"""给用户讲一个笑话"""
setup: str = Field(description="这个笑话的开头")
punchline: str = Field(description="这个笑话的妙语")
rating: Optional[int] = Field(default=None, description="从1到10分,给这个笑话评分")
structured_model = model.with_structured_output(Joke)
result = structured_model.invoke("给我讲一个关于唱歌的笑话")
print(result)
输出:

它是一个 Pydantic 对象,你可以直接访问属性 result.setup、result.rating 等。
嵌套 Pydantic 示例:
python
from typing import List
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from typing import Optional
model = ChatDeepSeek(model="deepseek-chat")
class Joke(BaseModel):
setup: str = Field(description="这个笑话的开头")
punchline: str = Field(description="这个笑话的妙语")
rating: Optional[int] = Field(default=None, description="评分1-10")
class JokeList(BaseModel):
jokes: List[Joke]
structured_model = model.with_structured_output(JokeList)
result = structured_model.invoke("分别讲一个关于唱歌和跳舞的笑话")
print(result)
输出:
jokes=[
Joke(setup='为什么唱歌的人总是很开心?', punchline='因为他们每天都活(高)音(兴)不断!', rating=8),
Joke(setup='为什么跳芭蕾舞的人总是站不稳?', punchline='因为他们总是脚尖着地,脚跟(很)容易飘起来!', rating=7)
]
2.2、 返回 TypedDict(字典)
TypedDict 是 Python 的类型提示,用于定义字典的键和值的类型。LangChain 支持将输出结构定义为 TypedDict,执行后会返回一个经过验证的字典。
TypedDict 简介:
python
from typing import TypedDict
class User(TypedDict):
name: str
age: int
email: str
is_active: bool = True
# 静态检查工具能发现这类错误:
bad_user: User = {
"name": "Dave",
"age": "forty", # 错误:应为 int
"email": "dave@example" # 拼写错误会被捕获
}
结构化输出中使用 TypedDict:
注意:当使用 TypedDict 定义字段描述时,我们需要结合 Annotated 来提供描述信息(因为 TypedDict 本身不记录描述)。
python
from langchain_deepseek import ChatDeepSeek
from typing import Optional
from typing_extensions import Annotated, TypedDict
model = ChatDeepSeek(model="deepseek-chat")
class Joke(TypedDict):
"""给用户讲一个笑话"""
setup: Annotated[str, "这个笑话的开头"]
punchline: Annotated[str, "这个笑话的结尾"]
rating: Annotated[Optional[int], None, "从1到10分,给这个笑话评分"]
structured_model = model.with_structured_output(Joke)
result = structured_model.invoke("给我讲一个关于唱歌的笑话")
print(result)
输出是一个字典:
{
'setup': '一个五音不全的人去参加合唱团面试',
'punchline': '面试官说:"你的歌声让我想起了我的前妻。" 那人兴奋地问:"真的吗?她也喜欢唱歌?" 面试官回答:"不,她一开口,我也想离婚。"',
'rating': 7
}
2.3、直接返回 JSON
如果想要更原始的 JSON 控制,可以传入一个 JSON Schema 字典。
python
from langchain_deepseek import ChatDeepSeek
model = ChatDeepSeek(model="deepseek-chat")
json_schema = {
"title": "joke",
"description": "给用户讲一个笑话。",
"type": "object",
"properties": {
"setup": {
"type": "string",
"description": "这个笑话的开头",
},
"punchline": {
"type": "string",
"description": "这个笑话的结尾",
},
"rating": {
"type": "integer",
"description": "从1到10分,给这个笑话评分",
"default": None,
},
},
}
structured_model = model.with_structured_output(json_schema)
result = structured_model.invoke("给用户讲一个笑话的开头")
print(result)
输出也是一个字典(和 TypedDict 输出类似):
{'setup': '为什么数学书总是很忧伤?', 'punchline': '', 'rating': 0}
3. 选择输出格式:联合类型 (Union)
有时候可能希望模型根据输入的不同,返回不同结构的数据。例如,有时直接讲笑话,有时是对话回应。这时可以使用 Union 类型,定义父模式包含联合类型的字段。
python
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from typing import Optional, Union
model = ChatDeepSeek(model="deepseek-chat")
class Joke(BaseModel):
setup: str = Field(description="这个笑话的开头")
punchline: str = Field(description="这个笑话的结尾")
rating: Optional[int] = Field(default=None, description="从1到10分,给这个笑话评分")
class ConversationalResponse(BaseModel):
"""对话的方式回应"""
response: str = Field(description="对用户查询的话术响应")
class FinalResponse(BaseModel):
final_output: Union[Joke, ConversationalResponse]
structured_model = model.with_structured_output(FinalResponse)
result = structured_model.invoke("给我讲一个关于唱歌的笑话")
print(result)
输出:
final_output=Joke(
setup='有一个人去参加歌唱比赛,唱完之后评委问他:',
punchline='"你唱歌的时候,是故意跑调的,还是天生的?" 那人回答:"天生的,我生下来就是这样,医生说我这是'先天性五音不全症'。" 评委说:"那你应该去当医生,因为你这嗓子能把死人唱活!"',
rating=7)
4、实用场景
从一段文本中提取结构化信息,例如从用户消息中提取人物属性。
python
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from typing import Optional
from langchain_core.messages import HumanMessage, SystemMessage
model = ChatDeepSeek(model="deepseek-chat")
class Person(BaseModel):
"""一个人的信息"""
name: Optional[str] = Field(default=None, description="这个人的名字")
hair_color: Optional[str] = Field(default=None, description="如果知道这个人头发的颜色")
skin_color: Optional[str] = Field(default=None, description="如果知道这个人的肤色")
height_in_meters: Optional[str] = Field(default=None, description="以米为单位的高度")
structured_model = model.with_structured_output(Person)
messages = [
SystemMessage(content="你是一个信息提取专家,只从文本中提取相关信息。如果不知道某个属性,请返回 null。"),
HumanMessage(content="史密斯身高6英尺,金发。")
]
result = structured_model.invoke(messages)
print(result)
输出:
name='史密斯' hair_color='金发' skin_color=None height_in_meters='1.83'
5、总结
结构化输出让我们可以从聊天模型直接获取 Pydantic 对象、TypedDict 或 JSON 字典,免去手工解析。
核心方法是 model.with_structured_output(schema),返回一个 Runnable。
schema 可以是 Pydantic 类、TypedDict 或 JSON Schema,分别对应不同的输出形式。
通过 method 参数可选择底层实现(推荐默认 json_schema)。
include_raw=True 可以同时获取原始消息和解析结果,便于调试。
结合工具调用时,应该先 bind_tools() 再 with_structured_output(),顺序不要颠倒。
掌握了结构化输出, LangChain 应用就能像调用 API 一样稳定地获取模型生成的结构化数据,为后续的存储、计算和展示打下基础。