跳到主要内容

结构化输出解析

大型语言模型能够生成任意文本,但这也带来了问题:

  • 输出格式不统一,难以解析
  • 难以存储到关系数据库
  • 下游处理困难

结构化输出可以将模型输出限制为特定格式(通常是 JSON),便于后续处理。


方式一:with_structured_output()

使用 Pydantic 模型定义输出结构,返回一个字典或 Pydantic 对象。

基本用法

from typing import List
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

# 1. 定义数据结构
class Person(BaseModel):
"""人物信息"""
name: str = Field(description="姓名")
age: int = Field(description="年龄")
hobbies: List[str] = Field(description="爱好列表")

# 2. 初始化模型并绑定结构化输出
llm = ChatOpenAI(model="gpt-4o", temperature=0)
structured_llm = llm.with_structured_output(Person)

# 3. 调用
result = structured_llm.invoke("提取信息:他叫张三,30岁,喜欢阅读和音乐。")

print(result.name) # 张三
print(result.age) # 30
print(result.hobbies) # ['阅读', '音乐']
print(type(result)) # <class '__main__.Person'>

组合提示词模板

# 提示词模板可以随意组合
from langchain_core.prompts import PromptTemplate

prompts = (
PromptTemplate.from_template("帮我生成一个关于{topic}的笑话。")
+ '要求:1、内容搞笑一点;'
+ '要求:2、输出的内容采用{language}'
)

chain = prompts | structured_llm

resp = chain.invoke({"topic": "猫", "language": "中文"})
print(resp)
print(resp.__dict__)

包含原始输出

# 返回原始响应 + 解析结果
structured_llm = llm.with_structured_output(Person, include_raw=True)
result = structured_llm.invoke("提取信息:他叫李四,25岁。")

# result 是字典
print(result["raw"]) # 原始 AIMessage
print(result["parsed"]) # 解析后的 Person 对象
print(result["parsing_error"]) # 解析错误(如果有)

工作原理

.with_structured_output() 内部会:

  1. 导入合适的输出解析器
  2. 将 Pydantic 模式格式化为模型所需的正确格式
  3. 返回一个 Runnable 对象,可用于 LCEL 链

方式二:JsonOutputParser

使用解析器将输出转为 JSON:

from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

model = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 1. 定义数据结构
class WeatherInfo(BaseModel):
"""天气信息"""
city: str = Field(description="城市名称")
temperature: int = Field(description="温度(摄氏度)")
condition: str = Field(description="天气状况")

# 2. 创建解析器
json_parser = JsonOutputParser(pydantic_object=WeatherInfo)

# 3. 创建提示词(关键:必须包含 "json" 关键词)
prompt = ChatPromptTemplate.from_template(
"""请从以下信息提取天气数据,以 JSON 格式返回。
信息:{weather_info}

必须返回以下格式:{{"city": "城市", "temperature": 数字, "condition": "状况"}}
"""
)

# 4. 构建链
chain = prompt | model | json_parser

# 5. 调用
result = chain.invoke({"weather_info": "北京今天晴,温度25度"})
print(result) # {'city': '北京', 'temperature': 25, 'condition': '晴'}

方式三:SimpleJsonOutputParser

一些模型(如 Mistral、OpenAI、Together AI、Ollama)支持 JSON 模式功能。启用后,模型输出始终为有效 JSON。

基本用法

from langchain_core.output_parsers import SimpleJsonOutputParser
from langchain_core.prompts import ChatPromptTemplate

# 创建聊天提示词模板,要求模型以特定格式回答问题
prompt = ChatPromptTemplate.from_template(
'尽你可能回答用户的问题。' # 基本指令
'你必须始终输出一个包含"answer"和"followup_question"键的JSON对象。' # 输出格式要求
'"answer"代表:对用户问题的回答;' # answer 字段说明
'"followup_question"代表:用户可能提出的后续问题。' # followup_question 字段说明
'{question}' # 用户问题占位符
)

# 组装链:提示词 -> LLM -> JSON 解析器
chain = prompt | llm | SimpleJsonOutputParser()

resp = chain.invoke({"question": "细胞的动力源是什么?"})

print(resp)
# 输出: {'answer': '细胞线粒体是细胞的动力源...', 'followup_question': '线粒体是如何产生能量的?'}

要点

  • SimpleJsonOutputParser 将模型输出解析为 Python 字典
  • 需要在提示词中明确指定 JSON 格式要求
  • 模型需要支持 JSON 模式

方式四:工具调用(bind_tools)

工具调用是一种更规范的方法来生成结构化输出。它的工作原理是:

  1. 将所需的模式绑定到聊天模型(使用 .bind_tools()
  2. 模型生成包含匹配参数的 tool_calls 字段的 AIMessage

基本用法

from pydantic import BaseModel, Field

# 定义响应格式化类
class ResponseFormatter(BaseModel):
"""始终使用此工具来格式化你的用户响应"""

answer: str = Field(description="对用户问题的回答") # 回答内容字段
followup_question: str = Field(description="用户可能提出的后续问题") # 后续问题字段


# 绑定工具到 LLM
runnable = llm.bind_tools([ResponseFormatter])

# 调用
resp = runnable.invoke("细胞的动力源是什么?")

print(resp)
# 输出: AIMessage(content='...', tool_calls=[...])

# 获取结构化输出
print(resp.tool_calls[-1]['args'])
# 输出: {'answer': '...', 'followup_question': '...'}

# 美化打印
resp.pretty_print()

与 with_structured_output() 的关系

  • .with_structured_output() 内部会使用工具调用方法(当模型支持时)
  • 工具调用是一种更通用的技术
  • 两者都能实现结构化输出,.with_structured_output() 更简洁

其他解析器

解析器用途
StrOutputParser输出转字符串
JsonOutputParser输出转 JSON
SimpleJsonOutputParser简单 JSON 解析
PydanticOutputParserPydantic v1 模型
CommaSeparatedListOutputParser逗号分隔转列表
BooleanOutputParseryes/no 转布尔值
IntOutputParser输出转整数
FloatOutputParser输出转浮点数

三种方法对比

方法适用场景优点缺点
.with_structured_output()通用场景简洁、自动处理需要模型支持
SimpleJsonOutputParser / JsonOutputParserJSON 输出兼容性好需要提示词约束
.bind_tools()复杂场景灵活、可控代码稍复杂

Agent 中结构化输出

from pydantic import BaseModel, Field
from langchain.agents import create_agent

# 定义输出结构
class WeatherResult(BaseModel):
city: str = Field(description="城市")
temperature: int = Field(description="温度")
condition: str = Field(description="天气状况")

# 创建 Agent(支持结构化输出)
agent = create_agent(
model=llm,
tools=[get_weather], # 需要先定义天气工具
system_prompt="你是一个天气预报助手。"
)

# 调用
result = agent.invoke({
"messages": [{"role": "user", "content": "北京天气怎么样?"}]
})

完整代码汇总

# ========== 方法一:.with_structured_output() ==========
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
from typing import Optional

class Joke(BaseModel):
setup: str = Field(description="笑话的开头部分")
punchline: str = Field(description="笑话的包袱/笑点")
rating: Optional[int] = Field(description="笑话的有趣程度评分")

runnable = llm.with_structured_output(Joke)
chain = PromptTemplate.from_template("帮我生成一个关于{topic}的笑话。") | runnable
resp = chain.invoke({"topic": "猫"})

# ========== 方法二:JsonOutputParser / SimpleJsonOutputParser ==========
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template(
'回答用户问题,输出JSON格式,包含"answer"和"followup_question"字段。{question}'
)
chain = prompt | llm | JsonOutputParser()
resp = chain.invoke({"question": "细胞的动力源是什么?"})

# ========== 方法三:bind_tools ==========
class ResponseFormatter(BaseModel):
answer: str = Field(description="对用户问题的回答")
followup_question: str = Field(description="用户可能提出的后续问题")

runnable = llm.bind_tools([ResponseFormatter])
resp = runnable.invoke("细胞的动力源是什么?")
result = resp.tool_calls[-1]['args']

关键要点

  1. 提示词必须包含 "json"

    • DeepSeek 等模型要求提示词中包含 "json" 关键词
  2. 推荐配置

    • 设置 temperature=0 获得更稳定输出
    • 提供清晰的 JSON 格式示例
    • 明确每个字段的描述
  3. 方案选择

    • JsonOutputParser / SimpleJsonOutputParser:最简洁,推荐
    • with_structured_output():更类型安全
    • 手动 JSON 解析:最稳定,适合关键应用
  4. 常见错误

    • 提示词没有 "json" 关键词
    • 没有设置低温度
    • 没有提供 JSON 格式示例

要点速记

概念关键点
with_structured_output()最常用,传入 Pydantic 类
JsonOutputParser / SimpleJsonOutputParser需要提示词指定 JSON 格式
.bind_tools()工具调用方式,更灵活
Pydantic定义输出结构,Field 描述字段含义
tool_calls工具调用结果在 AIMessagetool_calls 字段中

总结

结构化输出核心流程:

1. 定义 Pydantic 模型

2. 选择解析方式(with_structured_output / JsonOutputParser / bind_tools)

3. 构造提示词(包含 "json" 关键词和示例)

4. 构建链并调用

5. 获取结构化结果