LangChain 智能体(Agent)
智能体 = 大模型 + 工具 + 记忆 + 决策能力。突破大模型静态回答局限,能自主思考→选工具→执行→看结果→再思考,直到完成任务。智能体不是“一次性回答”,它是一个会反复思考和行动的循环系统。LangChain 1.0 统一用 create_agent() 构建,替代旧版 create_react_agent 等,更简洁、生产就绪。普通链(Chain):固定流程,输入→处理→输出智能体(Age
一、概述
LangChain中的智能体(Agents)是具有自主决策能力的AI系统,能够根据用户输入、可用工具和环境反馈来决定执行哪些操作。一句话定义:Agent 是一个能自主思考、自主规划、自主调用工具、自主纠正错误的 AI 系统。
1.1 智能体核心定义
智能体 = 大模型 + 工具 + 记忆 + 决策能力。
- 突破大模型静态回答局限,能自主思考→选工具→执行→看结果→再思考,直到完成任务。
- 智能体不是“一次性回答”,它是一个会反复思考和行动的循环系统。
- LangChain 1.0 统一用 create_agent() 构建,替代旧版 create_react_agent 等,更简洁、生产就绪。
智能体与传统链式调用的区别:
- 普通链(Chain):固定流程,输入→处理→输出
- 智能体(Agent):动态流程,会思考、试错、循环、多步执行
1.2 智能体核心能力
- 理解用户意图
- 判断是否需要调用工具
- 自主选择工具
- 根据工具结果反思
- 多轮循环直到完成任务
- 记住上下文(记忆)
- 可中断、可恢复、可持久化(LangGraph)
1.3 智能体核心范式:ReAct(Reason + Act)
智能体的 “思考 - 行动” 标准流程(create_agent 自动实现,无需手动写循环):
- Reason(推理):LLM 分析用户问题,判断是否需要工具、用哪个工具、传什么参数。
- Act(行动):调用对应工具执行操作(如查天气、算数学、搜网页)。
- Observe(观察):接收工具返回结果,LLM 整合信息,判断是否继续或直接回答。
- 循环直到:生成最终答案 / 达到最大迭代次数(默认 15 次)。
1.4 智能体核心组件
-
模型(Model):智能体的 “大脑”
- 作用:负责推理、决策、生成回答,是智能体的核心
- 支持:OpenAI、Anthropic、Google、DeepSeek 等主流 LLM,统一接口可无缝切换LangChain
- 配置方式:
- 静态(常用):创建时固定模型(如 ChatOpenAI(model=“gpt-4o”))
- 动态:运行时根据状态选模型(成本优化 / 复杂路由)
-
工具(Tools):智能体的 “手脚”
- 作用:让智能体与外部交互(联网、计算、查库、操作文件等)
- 定义:用 @tool 装饰器快速封装函数,必须包含名称、描述、参数 schema(LLM 靠描述判断是否调用)
- 内置工具:搜索(Tavily)、计算器、数据库、API 调用、文件读写等
- 自定义工具:任何 Python 函数都能包装成工具
-
系统提示词(System Prompt):智能体的 “行为准则”
- 作用:定义智能体角色、能力边界、输出格式、约束规则
- 示例:“你是专业助手,仅用提供的工具回答,禁止编造信息,步骤清晰”
- 优化:1.0 支持动态提示词(结合中间件实时修改)
-
中间件(Middleware):智能体的 “插件系统”(1.0 核心)
- 作用:不修改核心逻辑,扩展 / 控制智能体行为(模型调用、工具执行、状态管理)
- 常用内置中间件:
- HumanInTheLoopMiddleware:敏感工具执行前需人工审批(如查隐私)
- ModelCallLimitMiddleware:限制模型调用次数,防止无限循环
- LoggingMiddleware:记录执行日志,调试 / 监控
- 自定义中间件:实现个性化逻辑(如缓存、参数校验)
-
状态(State):智能体的 “短期记忆”
- 作用:自动维护对话历史、工具调用记录、中间结果,支持多轮对话
- 1.0 特性:基于 AgentState(TypedDict),可自定义扩展(如存储用户偏好)
二、LangChain 1.0 vs LangChain 0.x
2.1 关键区别
| 维度 | LangChain 0.x | LangChain 1.0 |
|---|---|---|
| 创建方式 | 多入口(create_react_agent/create_json_agent) | 统一 create_agent 一个函数搞定 |
| 架构 | Chain 线性组合 | Agent 循环 + 中间件(洋葱模型) |
| 生产能力 | 弱,需手动实现 | 内置:状态管理、流式、重试、限流 |
| 扩展性 | 低,改源码 | 高,中间件无侵入扩展 |
2.2 0.x 时代的问题
在1.0之前,LangChain有一个严重问题:做Demo很快,上线就崩。典型痛点:
- Chain太死板:业务逻辑稍微复杂一点,框架就套不住,最后不得不手写LLM调用。
- 上下文溢出:对话一长,Token爆炸,系统直接挂掉。
- 敏感信息泄露:用户的身份证、邮箱被直接发给了OpenAI。
- 高危操作无审批:Agent一声不吭就把邮件发出去了、把数据库删了。
- 换模型等于重写:OpenAI切到国产模型,Tool Calling格式不一样,适配代码重写一遍。
示例:
# LangChain 0.x - 参数爆炸,全靠手写
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=15,
max_execution_time=300,
handle_parsing_errors=True,
return_intermediate_steps=True,
trim_intermediate_steps=10,
# 还有几十个参数...
)
2.3 1.0 的核心革新
LangChain团队分析了数百个生产级Agent,发现一个事实:绝大多数成功案例,底层都是同一个模式——ReAct(Reason + Act)。于是1.0做了一个极其大胆的决定:
- 砍掉所有Chain,只保留ReAct Agent
- 把Agent构建压缩成一个函数:create_agent
- 把生产级能力做成“插件”:Middleware(中间件)
三、智能体的核心骨架(create_agent)
3.1 最小的智能体(3个参数就够了)
三个必填参数,缺一不可:
| 参数 | 作用 | 新手理解 |
|---|---|---|
| model | 谁在思考 | 智能体的大脑 |
| tools | 能干什么 | 智能体的手脚 |
| system_prompt | 遵守什么规则 | 智能体的岗位说明书 |
from langchain.agents import create_agent
from langchain.tools import tool
# 1. 定义一个工具
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气"""
return f"{city}今天晴天,24℃"
# 2. 创建智能体——就这么简单!
agent = create_agent(
model="openai:gpt-4o", # ① 模型
tools=[get_weather], # ② 工具
system_prompt="你是一个天气助手,回答简洁友好。" # ③ 系统提示
)
# 3. 调用
result = agent.invoke({
"messages": [{"role": "user", "content": "北京天气怎么样?"}]
})
print(result["messages"][-1]["content"])
3.2 底层原理:ReAct循环(3步走)
create_agent内部是一个无限循环,直到满足结束条件:
① 思考(Reason) → ② 行动(Act) → ③ 观察(Observe) → 回到①
用天气助手的例子走一遍:
- 思考:用户问北京天气,我有get_weather工具,应该调用它
- 行动:执行get_weather(city=“北京”)
- 观察:得到返回“北京今天晴天,24℃”
- 思考:信息已获取,直接回答用户 → 终止循环
四、生产级能力插件(Middleware)
4.1 中间件是什么?
技术定义:中间件是挂载在Agent执行流程上的钩子函数,可以在不修改核心循环的前提下,插入任何自定义逻辑。
类比:快递分拣线
- 智能体是快递员(负责跑腿)
- 中间件是分拣线上的检查站
- PII检查站:把包裹上的手机号涂掉(脱敏)
- 总结检查站:包裹太多时,写个摘要(长对话压缩)
- 审批检查站:贵重包裹需要主管签字(人工审核)
4.2 中间件的5个挂载点
中间件可以在以下5个关键时刻介入:
用户输入 → [before_agent] → [before_model] → [wrap_model_call] → 模型调用 →
[after_model] → [wrap_tool_call] → 工具执行 → [after_agent] → 输出
| 钩子 | 执行时机 | 典型用途 |
|---|---|---|
| before_agent | 智能体开始工作前 | 加载用户历史、权限校验 |
| before_model | 每次调用模型前 | 修剪太长对话、注入临时提示 |
| wrap_model_call | 包裹模型调用 | 动态切换模型/工具集 |
| wrap_tool_call | 包裹工具调用 | 工具权限控制、计费 |
| after_model | 每次模型返回后 | 输出格式校验、敏感词过滤 |
| after_agent | 智能体结束后 | 清理资源、保存对话 |
4.3 内置中间件(开箱即用)
LangChain 1.0内置了4个最常用的中间件,覆盖90%的生产需求。
4.3.1 PIIMiddleware——隐私保护
作用:在发送给模型之前,自动处理身份证、邮箱、手机号等敏感信息。
from langchain.agents.middleware import PIIMiddleware
agent = create_agent(
model="openai:gpt-4o",
tools=[...],
middleware=[
# 邮箱脱敏:john@example.com → j***@e***.com
PIIMiddleware("email", strategy="redact", apply_to_input=True),
# 信用卡掩码:只显示后4位
PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
# 自定义API密钥:检测到直接阻断
PIIMiddleware(
"api_key",
detector=r"sk-[a-zA-Z0-9]{32}", # OpenAI密钥格式
strategy="block"
),
]
)
为什么要在中间件做?不能靠模型自己识别吗?
答:永远不要把隐私安全交给模型。中间件是在发送请求之前就处理掉,模型根本看不到原始数据。
4.3.2 SummarizationMiddleware——对话总结
作用:对话历史太长时,自动生成摘要,避免Token溢出。
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model="openai:gpt-4o",
tools=[...],
middleware=[
SummarizationMiddleware(
model="openai:gpt-4o-mini", # 用便宜的模型做总结
max_tokens_before_summary=4000, # 超过4000 Token触发
messages_to_keep=20 # 保留最近20条不总结
)
]
)
执行逻辑:
- 检查当前消息列表的Token总数
- 超过阈值 → 调用gpt-4o-mini生成摘要
- 将摘要插入对话,移除早期消息
4.3.3 HumanInTheLoopMiddleware——人工审批
作用:高危操作必须经过人类确认。
from langchain.agents.middleware import HumanInTheLoopMiddleware
agent = create_agent(
model="claude-sonnet-4-5-20250929",
tools=[send_email, delete_file],
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"send_email": { # 发送邮件前必须审批
"allowed_decisions": ["approve", "edit", "reject"]
},
"delete_file": { # 删除文件直接禁止
"allowed_decisions": ["reject"]
}
}
)
]
)
执行效果:当智能体试图调用send_email时,执行暂停,等待API返回审批结果。
4.3.4 ToolRetryMiddleware——工具重试
作用:工具调用失败时自动重试,支持指数退避。
from langchain.agents.middleware import ToolRetryMiddleware
agent = create_agent(
model="openai:gpt-4o",
tools=[database_tool],
middleware=[
ToolRetryMiddleware(
max_retries=3, # 最多重试3次
backoff_factor=2.0, # 延迟翻倍:1s → 2s → 4s
initial_delay=1.0, # 首次延迟1秒
max_delay=60.0, # 最大延迟60秒
jitter=True # 增加随机抖动,避免并发风暴
)
]
)
4.4 自定义中间件
内置中间件覆盖通用场景,自定义中间件才是解决业务痛点的关键。
案例1:用户分级——给高手用重武器,给新手用安全模式
场景:根据用户技术水平,动态分配不同模型和工具集。
from dataclasses import dataclass
from langchain.agents.middleware import AgentMiddleware
from langchain_openai import ChatOpenAI
# 1. 定义上下文结构(存用户信息)
@dataclass
class Context:
user_expertise: str = "beginner" # 默认新手
# 2. 自定义中间件
class ExpertiseBasedToolMiddleware(AgentMiddleware):
def wrap_model_call(self, request, handler):
# 从运行时上下文读取用户等级
user_level = request.runtime.context.user_expertise
if user_level == "expert":
# 专家用户:用最强模型 + 高级工具
model = ChatOpenAI(model="gpt-5")
tools = [advanced_search, data_analysis]
else:
# 普通用户:轻量模型 + 基础工具
model = ChatOpenAI(model="gpt-5-nano")
tools = [simple_search, basic_calculator]
# 覆盖请求中的模型和工具
return handler(request.override(model=model, tools=tools))
# 3. 使用
agent = create_agent(
model="default", # 会被中间件覆盖
tools=[simple_search, advanced_search, basic_calculator, data_analysis],
middleware=[ExpertiseBasedToolMiddleware()],
context_schema=Context
)
# 调用时传入用户等级
result = agent.invoke(
{"messages": [...]},
context={"user_expertise": "expert"} # 动态切换
)
这个模式解决了什么问题?
- 以前:写两个Agent,用if-else判断调用哪个
- 现在:一个Agent,运行时自动适配
案例2:质量门禁——自动校验输出质量
场景:研究助手必须输出至少3个核心观点、2个来源,否则让模型重试。
from langchain.agents.middleware import AgentMiddleware
class ValidationMiddleware(AgentMiddleware):
def after_model(self, state, runtime):
"""模型返回后执行"""
if "structured_response" in state:
report = state["structured_response"]
# 校验1:必须有3条以上的关键发现
if len(report.key_findings) < 3:
return {
"messages": [{
"role": "user",
"content": "请提供至少3个关键发现。"
}]
}
# 校验2:必须有2个以上的来源
if len(report.sources) < 2:
return {
"messages": [{
"role": "user",
"content": "请引用至少2个权威来源。"
}]
}
return None # 校验通过,继续执行
# 使用
agent = create_agent(
model="gemini-2.0-flash-exp",
tools=[...],
middleware=[ValidationMiddleware()],
response_format=ResearchReport
)
执行效果:如果模型偷懒只写了2个观点,中间件自动追加一条用户消息,让模型重试。用户无感知,但输出质量有保障。
4.5 中间件设计哲学总结
一句话记住中间件:Agent是固定的高速公路,中间件是沿途的服务区——想加什么功能,建个服务区就行。
| 维度 | 0.x 时代 | 1.0 中间件 |
|---|---|---|
| 上下文控制 | 散落在各个参数、回调函数 | 集中式、可组合的钩子 |
| 复用性 | 每个项目重写 | 内置+自定义,跨项目共享 |
| 测试难度 | 需要跑完整Agent | 可单独测试中间件 |
| 学习曲线 | 参数爆炸,文档混乱 | 5个钩子,一通百通 |
五、系统提示(System Prompt)
系统提示(System Prompt)是开发者预先定义的、传递给大模型 / 智能体的 “规则说明书” —— 它不参与用户的对话交互,但会全程约束 AI 的行为、角色、能力边界、输出格式,是 LangChain 智能体(Agent)的 “行为准则”。
在 LangChain 中,系统提示区别于用户输入(HumanMessage)和 AI 回复(AIMessage),属于全局、前置、优先级最高的提示信息,直接决定智能体的核心表现。与普通提示词的区别:
| 类型 | 作用范围 | 优先级 | 核心目的 | 应用场景 |
|---|---|---|---|---|
| 系统提示(System Prompt) | 全局 / 全程 | 最高 | 定义角色、规则、能力边界 | 智能体初始化时配置 |
| 用户提示(User Prompt) | 单次交互 | 次高 | 传递用户具体需求 | 每次调用智能体时传入 |
| 工具提示(Tool Prompt) | 工具调用场景 | 辅助 | 说明工具用法、参数 | 智能体绑定工具时自动生成 |
5.1 核心作用
-
定义智能体的 “角色身份”
- 让智能体明确自己的定位,避免回答偏离场景,比如:
- “你是专业的电商客服智能体,仅处理订单查询、物流跟踪、退款申请相关问题”
- “你是数据分析助手,仅使用提供的工具查询销售额数据,不回答无关问题”
- 让智能体明确自己的定位,避免回答偏离场景,比如:
-
约束智能体的 “能力边界”
- 避免智能体编造信息、越权回答,是生产级应用的核心风控手段,比如:
- “仅使用提供的工具查询数据,不编造任何未验证的信息”
- “拒绝回答与公司业务无关的问题,如时政、娱乐、敏感话题”
- 避免智能体编造信息、越权回答,是生产级应用的核心风控手段,比如:
-
规范智能体的 “输出格式”
- 让智能体返回结构化、可直接解析的结果,避免手动处理文本,比如:
- “回答必须以 JSON 格式返回,包含字段:result(结果)、source(数据来源)”
- “分析报告需分 3 部分:数据摘要、核心结论、建议,每部分不超过 200 字”
- 让智能体返回结构化、可直接解析的结果,避免手动处理文本,比如:
-
指导智能体的 “工具调用逻辑”
- 这是 LangChain Agent 最核心的作用 —— 告诉智能体 “什么时候调用工具、怎么调用、调用失败怎么办”,比如:
- “当用户询问销售额时,必须先调用 query_sales 工具,使用工具返回的结果回答”
- “工具调用失败时,最多重试 2 次,重试失败后告知用户‘数据查询失败’”
- 这是 LangChain Agent 最核心的作用 —— 告诉智能体 “什么时候调用工具、怎么调用、调用失败怎么办”,比如:
-
补充智能体的 “上下文信息”
- 传递固定的背景信息,避免用户重复说明,比如:
- “当前查询的是 2025 年 Q4 的销售数据,公司业务范围仅覆盖国内一线城市”
- “用户 ID 为 1001,默认查询的是该用户名下的订单数据”
- 传递固定的背景信息,避免用户重复说明,比如:
5.2 基础用法(单字符串)
最常用,直接传入简洁的字符串,框架会自动结合工具信息、记忆上下文生成完整提示词:
from langchain_core.agents import create_agent, AgentExecutor
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# 1. 定义工具
@tool
def query_sales(quarter: str) -> str:
"""查询指定季度销售额,quarter格式为"2025年Q4"."""
return f"{quarter}销售额:1280万元,同比增长15.3%"
# 2. 初始化模型
llm = ChatOpenAI(model="gpt-4o", api_key="你的API密钥")
# 3. 核心:定义系统提示(单字符串)
system_prompt = """
你是专业的销售数据分析智能体,严格遵守以下规则:
1. 角色:仅处理销售额查询、同比增长率分析相关问题;
2. 工具:必须调用 query_sales 工具获取数据,不编造信息;
3. 输出:回答简洁,仅返回数据+核心结论,不超过50字;
4. 边界:拒绝回答与销售额无关的问题。
"""
# 4. 创建Agent(直接传入system_prompt)
agent = create_agent(
model=llm,
tools=[query_sales],
system_prompt=system_prompt # LangChain 1.0 核心配置
)
# 5. 执行
executor = AgentExecutor(agent=agent, tools=[query_sales], verbose=True)
executor.invoke({"input": "2025年Q4销售额是多少?"})
5.3 进阶用法(动态模板)
结合变量动态生成系统提示,适配多场景复用,比如根据用户 ID 动态绑定上下文:
from langchain_core.prompts import SystemMessagePromptTemplate
# 1. 定义带变量的系统提示模板
template = """
你是销售数据分析智能体,规则如下:
1. 仅处理用户{user_id}名下的销售数据;
2. 默认查询季度为{default_quarter};
3. 必须调用 query_sales 工具,输出格式为JSON。
"""
# 2. 渲染模板(动态传入变量)
system_prompt = SystemMessagePromptTemplate.from_template(template).format(
user_id="1001",
default_quarter="2025年Q4"
).content
# 3. 后续创建Agent的逻辑和基础用法一致
agent = create_agent(model=llm, tools=[query_sales], system_prompt=system_prompt)
5.4 结合工具信息(自动增强)
LangChain 1.0 会自动将工具的 name、description、parameters 整合到系统提示中,无需手动编写工具说明,比如:
- 你定义的工具:query_sales(quarter: str) -> str: “查询指定季度销售额”
- 框架自动补充到系统提示的内容:
你可以调用以下工具:
- 工具名:query_sales
- 描述:查询指定季度销售额
- 参数:quarter(字符串,格式为"2025年Q4")
5.5 编写高质量系统提示的 6 条核心原则
- 角色明确,不模糊
- 错误:“你是一个助手”(太泛,智能体不知道该做什么)
- 正确:“你是电商订单查询助手,仅处理订单状态、物流信息查询”
- 规则可量化,不抽象
- 错误:“回答要简洁”(无量化标准)
- 正确:“回答不超过 50 字,仅包含核心数据,无多余解释”
- 工具调用逻辑清晰
- 错误:“可以调用工具查询数据”(未说明触发条件)
- 正确:“当用户询问销售额时,必须调用 query_sales 工具,仅使用工具返回结果回答”
- 边界清晰,拒绝兜底
- 错误:“尽量回答用户的问题”(易编造信息)
- 正确:“仅回答工具能查询到的信息,工具无结果时回复‘暂无相关数据’”
- 输出格式标准化
- 错误:“返回分析结果”(格式不固定)
- 正确:“返回 JSON 格式,字段:{“quarter”: “季度”, “sales”: “销售额”, “growth”: “同比增长率”}”
- 语言简洁,避免冗余
- 错误:长篇大论讲背景、无关规则(模型易忽略核心)
- 正确:只保留角色、规则、格式、工具逻辑 4 类核心信息
5.6 系统提示常见问题及解决方案
- 问题 1:智能体不调用工具,直接编造答案
- 原因:系统提示未明确 “必须调用工具”,或工具描述不清晰
- 解决方案:
system_prompt = """ 你必须调用 query_sales 工具获取销售额数据,禁止编造任何信息。 步骤: 1. 接收用户问题后,先调用工具; 2. 仅使用工具返回的结果回答; 3. 工具无结果时,回复“暂无该季度数据”。 """
- 问题 2:智能体输出格式不符合要求
- 原因:系统提示未明确格式,或格式描述不具体
- 解决方案:
system_prompt = """ 回答必须严格遵循以下JSON格式,无任何额外文本: { "quarter": "2025年Q4", "sales": "1280万元", "growth": "15.3%" } """
- 问题 3:智能体回答无关问题,越权响应
- 原因:边界规则不清晰,未明确 “拒绝场景”
- 解决方案:
system_prompt = """ 仅回答销售额相关问题,拒绝回答以下类型问题: 1. 时政、娱乐、敏感话题; 2. 公司人事、财务(非销售)数据; 3. 与2025年Q4无关的历史数据(除非用户明确指定)。 拒绝时回复:“该问题超出我的处理范围,请咨询相关专业人员。” """
- 问题 4:LangChain 1.0 系统提示不生效
- 原因:
- 系统提示传入位置错误(旧版本用 prompt,1.0 必须用 system_prompt);
- 系统提示过长,超出模型上下文窗口;
- 解决方案:
# 正确传入方式(1.0版本) agent = create_agent( model=llm, tools=[query_sales], system_prompt=short_system_prompt # 精简提示词,控制长度 )
- 原因:
六、结构化输出——让AI说“人话”变成说“数据”
结构化输出是指让 LangChain 调用的大模型 / 智能体,不再返回无规则的自然语言文本,而是返回固定格式、可直接解析的数据(如 JSON、字典、Pydantic 模型对象),是生产级 AI 应用的必备能力(避免手动解析文本的繁琐和错误)。
在 LangChain 中,结构化输出不是 “简单让模型输出 JSON 字符串”,而是通过框架层的约束 + 模型层的引导,实现:
- 输出格式 100% 符合预期(不会出现语法错误);
- 输出内容可直接映射到代码中的数据结构(如类、字典);
- 支持校验和纠错(格式错误时自动重试)。
6.1 为什么需要结构化输出?
简单来说:结构化输出是 AI 应用和业务系统对接的 “桥梁”。
| 场景 | 非结构化输出(自然语言) | 结构化输出(JSON/Pydantic) |
|---|---|---|
| 数据分析 | “2025 年 Q4 销售额 1280 万,增长 15.3%” | {“quarter”:“2025Q4”,“sales”:1280,“growth”:15.3} |
| 订单查询 | “订单 12345 已发货,物流单号 7890” | {“order_id”:“12345”,“status”:“已发货”,“logistics_id”:“7890”} |
| 代码调用 | 需要手动提取字段、处理类型转换,易出错 | 可直接作为参数传入函数,无需额外处理 |
6.2 结构化输出的实现方式
LangChain中实现结构化输出的各种方法,包括传统方式(OutputParser)以及LangChain 1.0的新方式(response_format),可能还有通过Tool calling的伪装等。可以按照以下几种实现方式分类:
- 基于输出解析器(OutputParser)的传统方法:PydanticOutputParser, JsonOutputParser, StructuredOutputParser等。
- 基于模型原生结构化输出(如OpenAI的JSON mode、函数调用)的方法:LangChain通过response_format参数或者绑定工具的方式。
- LangChain 1.0中的新方式:create_agent中的response_format参数,以及底层如何适配不同模型。
- 通过工具调用来伪装结构化输出(Tool Strategy):对于不支持原生结构化的模型,将Pydantic模型包装成工具调用。
方式 1:输出解析器(OutputParser)——传统但灵活
这是 LangChain 0.x 时代就存在的方式,通过提示工程让模型输出特定格式的文本,然后用解析器将其转为结构化数据。
-
原理:提示模型输出特定格式,然后用解析器解析
- 在提示词中加入格式说明(例如“请输出 JSON 格式”)。
- 模型返回一段文本(如 JSON 字符串)。
- 输出解析器解析这段文本,返回 Python 对象。
-
常用解析器:
- PydanticOutputParser:基于 Pydantic 模型,最强大。
- JsonOutputParser:直接解析 JSON,无需定义模型。
- CommaSeparatedListOutputParser:解析逗号分隔的列表。
- StructuredOutputParser:返回多个命名字段(值都是字符串)。
- DatetimeOutputParser:解析日期时间。
- EnumOutputParser:将输出映射到枚举值。
-
示例:PydanticOutputParser使用
from langchain.output_parsers import PydanticOutputParser from langchain.prompts import PromptTemplate from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field # 1. 定义数据结构 class Person(BaseModel): name: str = Field(description="姓名") age: int = Field(description="年龄") # 2. 创建解析器 parser = PydanticOutputParser(pydantic_object=Person) # 3. 构造提示词(包含格式说明) prompt = PromptTemplate( template="从以下文本提取信息:\n{input}\n\n{format_instructions}", input_variables=["input"], partial_variables={"format_instructions": parser.get_format_instructions()} ) # 4. 调用模型 model = ChatOpenAI() chain = prompt | model | parser result = chain.invoke({"input": "张三今年25岁"}) print(result) # Person(name='张三', age=25) -
优点:
- 模型无关:任何文本生成模型都能用。
- 高度可控:可以通过提示词精细控制格式。
-
缺点:
- 依赖提示词质量:模型可能不遵守格式,导致解析失败。
- 稳定性一般:复杂格式时模型容易出错。
方式 2:模型原生结构化输出(JSON mode / 函数调用)
现在的主流模型(OpenAI、Claude、Gemini 等)都提供了原生结构化输出能力,例如 OpenAI 的 JSON mode 和函数调用(Function Calling)。LangChain 1.0 对此做了深度集成,让开发者以统一的方式使用这些能力。
-
原理:利用模型API本身支持的结构化输出参数(如OpenAI的response_format、函数调用)
- JSON mode:通过 API 参数强制模型输出合法 JSON。
- 函数调用:让模型选择调用一个“函数”,并将函数参数作为结构化输出。
-
LangChain中的实现:
-
使用 .with_structured_output()(LCEL 风格)
- 这是最直接的 LCEL 方法,适用于任何支持结构化输出的模型。
- 底层自动判断:若模型支持函数调用,就使用函数调用;若支持 JSON mode,就使用 JSON mode。
from langchain_openai import ChatOpenAI from pydantic import BaseModel class Person(BaseModel): name: str age: int model = ChatOpenAI(model="gpt-4o") structured_model = model.with_structured_output(Person) result = structured_model.invoke("张三25岁") print(result) # Person(name='张三', age=25) -
在 create_agent 中使用 response_format 参数
- 这是 LangChain 1.0 智能体中的标准用法。
from langchain.agents import create_agent agent = create_agent( model="openai:gpt-4o", tools=[...], response_format=Person # 直接传入 Pydantic 模型 ) result = agent.invoke({"messages": [{"role": "user", "content": "张三25岁"}]}) person = result["structured_response"] # 直接得到 Person 对象 -
通过绑定工具(bind_tools)让模型返回工具调用作为结构化数据
from langchain.tools import tool from langchain_core.pydantic_v1 import BaseModel class Person(BaseModel): name: str age: int @tool(args_schema=Person) def dummy_tool(name: str, age: int): """这是一个占位工具,只用于获取参数""" pass model = ChatOpenAI().bind_tools([dummy_tool]) response = model.invoke("张三25岁") # 解析 response.tool_calls[0]["args"] 得到结构化数据
-
-
优点:
- 最稳定:模型 API 原生保证格式正确。
- 无需提示工程:格式说明由框架自动处理。
- 性能好:减少 token 浪费。
-
缺点:
- 依赖模型能力:需要模型支持 JSON mode 或函数调用。
- 可能有限制:某些模型对输出结构有大小限制。
方式 3:通过工具调用伪装(Tool Strategy)——兼容方案
当模型不支持原生结构化输出,但支持工具调用(例如大多数开源模型通过适配也支持)时,LangChain 1.0 采用了一种巧妙的方法:将 Pydantic 模型伪装成一个工具。
-
原理:对于不支持原生结构化的模型,将Pydantic模型包装成一个“工具”,让模型通过调用工具来返回结构化数据。模型会输出一个工具调用,我们解析工具调用的参数作为结构化输出。
- 将目标数据结构定义为工具的 args_schema。
- 在提示词中告诉模型“你需要调用工具才能完成任务”。
- 模型会输出一个工具调用,其参数就是我们需要的数据。
- 解析工具调用的参数,得到结构化对象。
-
LangChain实现:在 LangChain 1.0 中,如果你在 create_agent 中设置了 response_format,而模型不支持原生结构化,框架会自动采用这个策略,开发者完全无感。
-
示例:手动绑定工具并解析
from langchain.tools import tool from langchain_core.pydantic_v1 import BaseModel from langchain_openai import ChatOpenAI class Person(BaseModel): name: str age: int @tool(args_schema=Person) def return_person(name: str, age: int): """此工具用于返回人物信息""" return {"name": name, "age": age} model = ChatOpenAI(model="gpt-3.5-turbo").bind_tools([return_person]) response = model.invoke("张三25岁") tool_call = response.tool_calls[0] args = tool_call["args"] person = Person(**args) # 结构化输出 print(person) -
优点:
- 兼容性好:适用于大量支持工具调用的模型。
- 比传统解析器可靠:工具调用通常比自由文本格式稳定。
-
缺点:
- 需要模型支持工具调用(大多数现代模型都支持)。
- 可能会额外消耗一次工具调用额度(如果有计费)。
方式 4:自定义解析和验证(结合中间件)
在实际生产环境中,模型偶尔会输出格式错误的内容。这时我们可以通过输出修复解析器或中间件来增强鲁棒性。
-
原理:在中间件中对模型输出进行校验和修复,结合OutputFixingParser等,实现弹性结构化
- 使用 OutputFixingParser 或 RetryWithErrorOutputParser 包裹基础解析器。
- 当解析失败时,自动调用另一个 LLM 来修复或重试。
-
示例:使用OutputFixingParser包裹解析器
from langchain.output_parsers import PydanticOutputParser, OutputFixingParser from langchain_openai import ChatOpenAI from pydantic import BaseModel class Person(BaseModel): name: str age: int base_parser = PydanticOutputParser(pydantic_object=Person) fixing_parser = OutputFixingParser.from_llm( parser=base_parser, llm=ChatOpenAI() ) bad_json = '{"name": "张三", "age": 25' # 缺少结束括号 person = fixing_parser.parse(bad_json) # 自动修复并解析 print(person) -
结合中间件(LangChain 1.0)
可以在智能体的中间件中插入校验逻辑,当结构化输出不符合预期时,让模型重试。from langchain.agents.middleware import AgentMiddleware class ValidationMiddleware(AgentMiddleware): def after_model(self, state, runtime): if "structured_response" in state: data = state["structured_response"] if data.age < 0: # 让模型重新生成 return {"messages": [{"role": "user", "content": "年龄不能为负,请修正"}]} return None -
优点:
- 极高的鲁棒性:能处理各种边缘情况。
- 可自定义修复逻辑:不仅限于格式,还能校验业务规则。
-
缺点:
- 增加额外 LLM 调用,可能提高成本和延迟。
- 需要合理设计重试策略,避免死循环。
6.3 结构化输出的核心优化技巧
- 模型选择:优先用强工具调用能力的模型
- 推荐:GPT-4o、GPT-4 Turbo、通义千问 4、文心一言 4.0(结构化输出稳定性高);
- 不推荐:GPT-3.5 Turbo(低概率出现格式错误);
- 关键参数:temperature=0(关闭随机性,保证输出格式稳定)。
- 格式说明:越具体越好
- 错误:“输出 JSON 格式”(太泛,模型可能漏字段);
- 正确:使用 parser.get_format_instructions()(自动生成详细格式说明),包含字段名、类型、示例。
- 错误处理:添加重试机制
- 模型偶尔会输出格式错误,通过 LangChain 的 RetryOutputParser 实现自动重试:
from langchain_core.output_parsers import RetryOutputParser from langchain_core.prompts import PromptTemplate # 包装解析器,添加重试逻辑 retry_parser = RetryOutputParser.from_llm( parser=parser, llm=llm, prompt=prompt # 重试时使用的提示词 ) # 调用(格式错误时自动重试) try: result = chain.invoke({"sales_info": sales_info}) except Exception as e: result = retry_parser.parse_with_prompt(llm_response, prompt)
- 模型偶尔会输出格式错误,通过 LangChain 的 RetryOutputParser 实现自动重试:
- 字段约束:设置默认值和校验规则
- 通过 Pydantic 模型添加字段校验,避免业务异常:
from langchain_core.pydantic_v1 import BaseModel, Field, validator class SalesData(BaseModel): quarter: str = Field(description="季度") sales: float = Field(description="销售额", gt=0) # 销售额必须大于0 growth: float = Field(description="增长率", default=0.0) # 自定义校验规则 @validator("quarter") def quarter_format(cls, v): if not v.startswith("202"): raise ValueError("季度格式错误,必须以202开头(如2025Q4)") return v
- 通过 Pydantic 模型添加字段校验,避免业务异常:
- response_format 本身是模型输出格式的参数,但在 LangChain 1.0 生产级落地中,需结合厂商适配(ProviderStrategy) 和工具调用(ToolStrategy) 形成完整策略体系:
- ToolStrategy:使用人工工具调用生成结构化输出。这适用于任何支持工具调用的模型。
from pydantic import BaseModel from langchain.agents import create_agent from langchain.agents.structured_output import ToolStrategy class ContactInfo(BaseModel): name: str email: str phone: str agent = create_agent( model="openai:gpt-4o-mini", tools=[search_tool], response_format=ToolStrategy(ContactInfo) ) result = agent.invoke({ "messages": [{"role": "user", "content": "从以下内容提取联系信息:John Doe, john@example.com, (555) 123-4567"}] }) result["structured_response"] # ContactInfo(name='John Doe', email='john@example.com', phone='(555) 123-4567') - ProviderStrategy:使用模型提供商的原生结构化输出生成。这更可靠,但仅适用于支持原生结构化输出的提供商(例如 OpenAI):
from langchain.agents.structured_output import ProviderStrategy agent = create_agent( model="openai:gpt-4o", response_format=ProviderStrategy(ContactInfo) )
- ToolStrategy:使用人工工具调用生成结构化输出。这适用于任何支持工具调用的模型。
6.4 结构化输出的核心优势
- 与 LCEL 深度集成:支持 prompt | llm | parser 链式调用,代码简洁、可维护;
- 强类型校验:通过 Pydantic 模型实现字段类型、范围、格式校验,避免业务错误;
- 自动格式说明:parser.get_format_instructions() 自动生成详细格式要求,无需手动编写;
- 与智能体无缝结合:1.0 版本的 create_agent() 可直接集成结构化输出规则,适配生产级场景;
- 可扩展性强:支持自定义解析器,适配复杂业务格式(如 XML、CSV)。
七、标准内容块——跨模型兼容的终极方案
7.1 解决的问题
不同模型提供商的返回格式天差地别:
- OpenAI:response.choices[0].message.content
- Anthropic:response.content[0].text
- Gemini:response.candidates[0].content.parts[0].text
以前换个模型,解析代码全得重写。
7.2 1.0 的解决方案:content_blocks
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
response = model.invoke("What's the capital of France?")
# 统一的访问方式!
for block in response.content_blocks:
if block["type"] == "reasoning":
print(f"模型推理过程: {block['reasoning']}")
elif block["type"] == "text":
print(f"最终回答: {block['text']}")
elif block["type"] == "tool_call":
print(f"调用工具: {block['name']}({block['args']})")
优势:
- 一次编写,跑遍所有模型
- 支持推理轨迹、引用来源、内置工具等高级功能
- 完全类型提示,IDE自动补全
八、完整实战——把所有概念串起来
场景:
做一个真正的项目:自动写周报的智能体。
项目目标:
输入:“帮我写周报”,自动:
- 从Jira获取本周完成的任务
- 从Git统计代码提交
- 生成结构化的周报Markdown
- 敏感信息(任务ID)脱敏
- 发送前请求人工审批
import os
from pydantic import BaseModel, Field
from langchain.agents import create_agent
from langchain.agents.middleware import (
PIIMiddleware,
HumanInTheLoopMiddleware,
AgentMiddleware
)
from langchain.tools import tool
# ---------- 1. 定义工具 ----------
@tool
def get_jira_tasks(assignee: str) -> str:
"""获取指定负责人在本周完成的任务"""
# 真实场景会调Jira API,这里模拟数据
return """
PROJ-123: 修复登录页白屏问题
PROJ-456: 优化数据库连接池配置
PROJ-789: 更新用户文档
"""
@tool
def get_git_commits(author: str) -> str:
"""获取指定作者本周的代码提交"""
return "合并PR #12, #15, #19;修复内存泄漏"
# ---------- 2. 定义输出结构 ----------
class WeeklyReport(BaseModel):
summary: str = Field(description="本周工作整体概述")
completed_tasks: list[str] = Field(description="完成任务列表")
tech_details: str = Field(description="技术细节/代码提交")
next_plan: str = Field(description="下周计划")
# ---------- 3. 自定义中间件:自动注入任务负责人 ----------
class InjectUserMiddleware(AgentMiddleware):
def before_model(self, state, runtime):
"""模型调用前执行"""
# 从上下文获取当前用户
user = runtime.context.get("user", "zhangsan")
# 将用户信息注入到消息中
state["messages"].append({
"role": "user",
"content": f"我的Jira账号是{user},Git账号也是{user}"
})
return None
# ---------- 4. 组装智能体 ----------
agent = create_agent(
model="openai:gpt-4o",
tools=[get_jira_tasks, get_git_commits],
system_prompt="""你是一个周报助手。
步骤1:调用get_jira_tasks获取任务
步骤2:调用get_git_commits获取代码提交
步骤3:按照规定的结构化格式生成周报
""",
middleware=[
# ① 自动注入用户信息
InjectUserMiddleware(),
# ② 任务ID脱敏:PROJ-123 → PROJ-***
PIIMiddleware(
"jira_id",
detector=r"PROJ-\d+",
strategy="redact"
),
# ③ 发送邮件前必须人工审批
HumanInTheLoopMiddleware(
interrupt_on={
"send_email": {
"allowed_decisions": ["approve", "reject"]
}
}
)
],
response_format=WeeklyReport # ④ 结构化输出
)
# ---------- 5. 执行 ----------
result = agent.invoke(
{
"messages": [
{"role": "user", "content": "帮我写周报"}
]
},
context={"user": "wangwu"} # 传入当前用户
)
# 6. 获取结构化数据
report = result["structured_response"]
print(f"## 周报摘要\n{report.summary}")
print("\n## 完成任务")
for task in report.completed_tasks:
print(f"- {task}")
更多推荐


所有评论(0)