LangChain:结构化输出

前言:为什么需要结构化输出?

在之前的教程中,我们学会了调用聊天模型得到 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 实例。这个新实例的执行结果不再是文本消息,而是与给定输出结构相对应的对象

基本使用步骤:

  1. 定义输出结构(如 Pydantic 类、TypedDict、JSON Schema)

  2. 调用 model.with_structured_output(schema) 得到新的 Runnable

  3. .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.setupresult.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 一样稳定地获取模型生成的结构化数据,为后续的存储、计算和展示打下基础。

相关推荐
yanghuashuiyue3 小时前
Deep Agents 框架-CLI
python·langchain·langgraph·deepagents
swipe3 小时前
别再把 AI 聊天做成纯文本:从 agui 这个前后端项目,拆解“可感知工具调用”的流式 AI UI
后端·langchain·llm
H_unique6 小时前
LangChain:调用工具Ⅲ
python·langchain
醉舞经阁半卷书16 小时前
深入掌握LangChain
python·langchain
BU摆烂会噶8 小时前
【LangGraph】运行时上下文(Runtime Context)
人工智能·python·langchain
Aision_8 小时前
LangGraph 中 State、Node、Edge 是怎么协作的?
langchain·prompt·aigc·embedding·ai编程·ai写作·agi
MATLAB代码顾问9 小时前
AI Agent智能体开发实战:LangChain自动化工作流
人工智能·langchain·自动化
青木96012 小时前
智能体(Agent)开发与部署项目
langchain·benchmark·智能体·gaia
人道领域13 小时前
从零构建高可用Agent:后端架构实战与避坑指南
架构·langchain·agent