别再堆 Prompt 了:用 LangChain 1.0 搭建“深度思考 Agent”
从“会聊天”到“会思考”:我用 LangChain 1.0 + Qwen 做了一个旅游 Agent,一个真正能“思考”的旅游规划Agent。在这个过程中,我顺手把“深度思考(reasoning)”这一套,从 **模型 → API → LangChain 1.0 → 中间件**,走了一遍闭环,也踩了不少坑。这篇文章就借这个项目,聊聊 Reasoning 到底怎么开、怎么控、怎么落地。
🚀大模型落地开发实战指南!请关注微信公众号:「AGI启程号」 深入浅出,助你轻松入门!
📚 数据分析、深度学习、大模型与算法的综合进阶,尽在CSDN博客主页
从“会聊天”到“会思考”:我用 LangChain 1.0 + Qwen 做了一个旅游 Agent,一个真正能“思考”的旅游规划Agent。
在这个过程中,我顺手把“深度思考(reasoning)”这一套,从 模型 → API → LangChain 1.0 → 中间件,走了一遍闭环,也踩了不少坑。
这篇文章就借这个项目,聊聊 Reasoning 到底怎么开、怎么控、怎么落地。

目录
关注同名公众号及时获取更多技术资讯
本期代码运行实例详见 GitHub:
👉 https://github.com/SWUSTcyt/langchain-travel-agent.git
01 为什么要关心 LangChain 1.0?
在 0.x 时代,我们做 Agent 差不多是这么个流程:
- 选一个大模型;
- 绑几个工具(搜索、数据库、地图 API……);
- 用 Agent 框架帮你决定“什么时候该用工具”。
能跑,但有几个老问题:
- Prompt、裁剪、Guardrails 分散:逻辑散落在各种链和回调里,难维护;
- 想要“先自评再重写”“先检索再回答”,得自己手搓一堆链;
- 工程化之后,逻辑越来越多,全是 if-else 拼命长。
LangChain 1.0 做了一件很关键的小事:
把这些“前后处理、反思、限流、审计”的能力,系统化收拢到了 Middleware(中间件)里。
简单一句话:
Agent = 一条生产线,Middleware = 插在生产线各个路口的“小工位”。
有了它,我们可以更优雅地回答这三个问题:
- 这个模型什么时候开始深度思考?
- 它的思考预算谁来控制?
- 出来的结果谁来质检和纠偏?
02 模型层:先把“深度思考”开起来
在这个 Demo 里我用的是 Qwen-Turbo,它本身支持“深度思考 + 流式输出”。模型初始化放在 agent.py 中,核心配置大概长这样:
from langchain_community.chat_models.tongyi import ChatTongyi
from tools import tools # MCP(高德)工具在 tools.py 中加载
qwen = ChatTongyi(
dashscope_api_key=DASHSCOPE_API_KEY,
model="qwen-turbo-latest",
streaming=True, # 流式输出
model_kwargs={
"enable_thinking": True, # 开启深度思考
"incremental_output": True, # 思考 + 流式 必须一起开
"temperature": 0.7,
"top_p": 0.8,
"max_output_tokens": 1500, # 模型级成本护栏
},
)
# 交给 LangChain 处理工具调用
qwen = qwen.bind_tools(tools)
这一层你可以理解为:
“告诉模型:你可以多想一会儿,但最长只能说这么多。”
剩下的就交给 LangChain 1.0 的 Middleware 去“管住它”。
03 把 Agent 想成一条生产线:Middleware 就是闸口
官方文档会画一张循环图:
模型调用 →(可能)工具调用 → 再次模型调用 → …… → 结束
LangChain 1.0 提供了几个关键钩子(hook):
- before_model:模型调用之前,适合改 Prompt、抽取结构化信息、打系统提示;
- wrap_model_call:像给 model.invoke() 加一层代理,适合控参数、做“自评→重写”等多段推理;
- wrap_tool_call:在工具真正被调用前拦一下,适合修参数、加权限、做预算限制;
- after_model:模型调用之后,适合做结果质检、串城检查、命中关键词再触发纠偏;
- after_agent:整轮 Agent 结束后的收尾工作。
在我的旅游 Agent 里,我只重点做了三件事:
- before_model:自动解析城市 + 注入强约束
- wrap_model_call:控制 Reasoning 的“成本”
- wrap_tool_call + after_model:强制工具对齐城市 & 串城质检
下面就用几个关键中间件快速过一遍。
04 三段关键中间件:一张“深思链路骨架”
4.1 城市解析 & 系统提示:before_model
用户说的每一句话里,其实都隐藏着一些重要变量,比如“城市”。在旅游场景下,城市就是一切的“坐标系”。
所以我在 middleware.py 里写了一个 pin_cities_and_adcodes:
- 从最后一条用户消息里自动抽取城市;
- 把城市写进
runtime.state["target_cities"]; - 重写 System Prompt(工具优先 + 强约束 + 禁串城)。
核心逻辑如下(保留关键部分):
from langchain.agents.middleware import before_model
from langchain_core.messages import SystemMessage, HumanMessage
from .utils import extract_cities, msg_text
@before_model
def pin_cities_and_adcodes(state, runtime):
messages = state.get("messages", [])
human_msgs = [m for m in messages if isinstance(m, HumanMessage)]
query = msg_text(human_msgs[-1]) if human_msgs else ""
# 1) 抽城市
cities = extract_cities(query)
runtime.state["target_cities"] = set(cities)
runtime.state["target_cities_source_text"] = query
# 2) 拼系统提示
if cities:
city_tips = (
f"\n【强制约束】本轮用户指定城市:{', '.join(cities)}。"
"仅在这些城市范围内规划;所有 MCP 工具调用必须携带匹配的 city/cityd。"
)
else:
city_tips = "\n【强制约束】请从用户问题中判断目标城市;禁止跨城或引入未出现的城市。"
sys_msg = SystemMessage(
content=(
"你是旅行规划助手。需要本地信息时优先使用 MCP(amap-maps) 的工具:"
"searchPOI / getRoute / getWeather。风格:\"轻松不赶\"。" + city_tips
)
)
# 3) system 置顶
new_messages = [sys_msg] + [
m for m in messages if not isinstance(m, SystemMessage)
]
return {"messages": new_messages}
这一段做的是语义层预处理:
“先帮模型把城市这个‘关键条件’想清楚,再让它进入 reasoning 模式。”
4.2 成本护栏:wrap_model_call
第二点是控制“模型思考的成本”。
我统一用一个 cost_guard 来兜底:
- 强制
max_output_tokens - 强制 thinking 开关
- 强制流式输出
- 再做一次 SystemMessage 置顶
示例:
from langchain.agents.middleware import wrap_model_call
@wrap_model_call
def cost_guard(req, handler):
# system 置顶(略)
guarded = req.override(
model_settings={
"max_output_tokens": 1200,
"incremental_output": True,
"enable_thinking": True,
}
)
return handler(guarded)
相当于:Reasoning 的“API 预算”统一放在一个地方管控。
4.3 工具护栏:预算 + 城市对齐 + 串城质检
4.3.1 工具预算计数
@wrap_tool_call
def track_tool_budget(req, handler):
MAX_TOOL_ROUNDS = 2
...
if current_round > MAX_TOOL_ROUNDS:
return ToolMessage(
tool_call_id=call_id or "call_budget_exceeded",
content='{"error":"预算已耗尽","message":"工具调用已达到上限,请直接给出最终答案。"}',
name="budget_guard",
)
return handler(req)
4.3.2 城市对齐
@wrap_tool_call
def enforce_city_on_tools(req, handler):
...
params["city"] = pinned_city
params["cityd"] = pinned_city
if "adcode" in params:
params["adcode"] = ""
return handler(req.override(tool_input=params))
4.3.3 串城质检(after_model)
- 抽取最终回答的城市
- 如果发现串城 → 在文末自动加一段提示说明
避免触发二次模型调用(防递归)。
05 实际体验:一个“会思考”的旅游助手
普通用户模式
输入:
“规划成都 2 天美食 + 人文轻松路线”
Agent 会:
- 并行调用高德工具(POI + 路线 + 天气)
- 整合成可验证的路线 + 时段 + 天气提醒

开发者模式
会看到:
🛰 解析城市:成都
🔧 工具 amap-searchPOI | city=成都 …
🤖 模型生成中…
以及完整 JSON(便于 Debug)。
06 Reasoning 的三个层次
6.1 模型层 Reasoning
- 支持深度思考的模型
- 启用 enable_thinking
- 给出 token 预算
6.2 API / Middleware 层 Reasoning
- cost_guard 控成本
- before_model 抽变量
- wrap_tool_call 管工具参数
- after_model 做质检
6.3 结构层 Reasoning(工程级)
- 修复工具调用结构
- 日志 & 开发者模式
- 可观察、可调优
07 结语:你可以直接复用哪些东西?
整个项目串起来的思想是:
- 用中间件统一管理 Reasoning
- 抽取变量(城市)从 Prompt 中解放出来
- 用日志让 Reasoning 变得“看得见”
你可以直接抄下面这套:
- 一个 before_model:抽关键变量 + 重写系统提示
- 一个 wrap_model_call:成本护栏
- 一两个 wrap_tool_call:工具预算 + 参数修正
- 一个 after_model:结果质检 + 轻纠偏
剩下的,把旅游换成你的业务即可。
希望这篇文章能帮你从“会用模型”进一步走向“会设计一条能思考、可控的生产线”。
更多推荐



所有评论(0)