🚀大模型落地开发实战指南!请关注微信公众号:「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 里,我只重点做了三件事:

  1. before_model:自动解析城市 + 注入强约束
  2. wrap_model_call:控制 Reasoning 的“成本”
  3. 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:结果质检 + 轻纠偏

剩下的,把旅游换成你的业务即可。

希望这篇文章能帮你从“会用模型”进一步走向“会设计一条能思考、可控的生产线”。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐