ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LangChain新手指南:从ReAct到Agent与MCP的完整实践路径

LangChain新手指南:从ReAct到Agent与MCP的完整实践路径 最近不少同学在学 LangChain 时被新老版本概念绕晕一会儿看到AgentExecutor一会儿又看到LangGraph刚搞懂ReAct又冒出MCP和Skills。尤其是新版 LangChain 生态里模型接口、回调机制、Agent 构建方式都在快速调整网上很多教程往往只贴代码不解释原理抄下来后换个版本就跑不通。这篇文章我会围绕一条可落地的学习主线展开从环境配置、模型初始化开始再到 LangChain 的 Middleware 中间件思路、ReAct 模式、AI Agent、MCP 工具接入、Skills 技能封装和 Prompt 设计最后用一个完整案例把这些概念串起来。适合正在入门 LangChain 的开发者也适合想系统梳理 AI Agent 工程化路径的后端同学。1. LangChain、ReAct、Agent、MCP 到底在解决什么问题1.1 LangChain 不是模型而是模型能力的编排框架LangChain 不是某个大语言模型它是一套用于构建大模型应用的开发框架。直白一点说模型负责“想”LangChain 负责把“想”这件事拆成可执行的流程同时补上模型缺少的能力。如果没有 LangChain你要调用大模型做一件事通常就是拼 Prompt然后直接请求模型接口再把结果返回给用户。但真实业务里往往没有这么简单比如用户先提问模型需要判断是否要查数据库。查询结果返回后模型要把结果整理为用户能看懂的话。每一步执行过程需要日志、限流、权限校验。同一个任务可能涉及多个工具模型需要反复决策。这些场景里LangChain 提供的核心价值是“编排”。它通过 Chain、Tool、Agent、LangGraph 等抽象把模型调用、外部工具、上下文记忆、中间件逻辑组装成一套完整流程。1.2 ReAct 是 Agent 的经典推理模式ReAct 的名字来自 Reasoning Acting意思是“推理与行动交替进行”。需要特别提醒的是这里的 ReAct 和前端领域的 React.js 没有任何关系面试或技术交流时要注意避免混淆。ReAct 的核心循环可以理解为模型先观察当前问题产生一个“想法”Thought接着决定执行哪个工具Action工具返回结果后模型再基于结果进行下一轮思考。整个过程不断循环直到模型认为信息足够了才给出最终回答。如果你看到 Agent 的日志像下面这样说明它正在运行 ReAct 循环Thought: 我需要查询北京的天气。 Action: get_weather Action Input: {city: 北京} Observation: 北京晴25℃ Thought: 我已经拿到天气信息可以回答用户了。 Final Answer: 北京今天天气晴朗气温25℃适合跑步。1.3 LangChain、LangGraph、Agent、MCP、Skills 的关系为了快速建立整体认知我把常见概念整理成了一张对照表概念一句话理解在 LangChain 生态中的位置LangChain大模型应用的编排框架提供 Chain、Tool、Prompt 等基础能力LangGraph有状态、可编排的 Agent 运行时用图结构表达复杂流程是 Agent 的推荐运行底座Agent能自主决策并调用工具的智能体基于 ReAct 或其他策略运行ReAct推理与行动交替的 Agent 工作模式Agent 的一种实现策略MCP模型上下文协议统一模型与外部工具交互通过适配器把 MCP Server 转成 Agent 可用的工具Skills将提示词、工具、流程封装成技能包提高 Agent 在特定场景下的复用能力后续文章内容基本围绕这张表展开。不要试图一次性把全部概念理解到位先跟着代码跑通流程再回头看概念会清晰很多。1.4 本文实战项目的主线本文的实战主线可以概括为在 LangChain 中初始化大模型 ChatModel通过 Prompt 模板组装第一轮问答再给模型接入一个用 MCP 协议封装的工具最后用 LangGraph 的 ReAct Agent 编排这个带工具的模型并在执行链路上插入 Middleware 中间件实现日志监控。这个项目覆盖了“模型接入 → 能力封装 → Agent 编排 → 工具互联 → 可观测性”的完整链路也是目前企业级 AI 应用最常见的骨架。2. 环境准备与版本选型2.1 Python 虚拟环境与项目目录LangChain 生态对 Python 3.9 以上版本支持较好实际开发中建议使用 Python 3.10 或 3.11。这里推荐使用 venv 创建独立虚拟环境避免依赖冲突。mkdir langchain-demo cd langchain-demo python -m venv .venv source .venv/bin/activateWindows 环境激活命令如下.venv\Scripts\activate2.2 安装依赖不同版本对 API 的影响很大。LangChain 是在快速迭代的项目模块拆分也越来越细比如模型接入通常不再依赖单一的langchain包而是使用langchain-openai、langchain-ollama这样的独立集成包。基础依赖可以这样安装pip install -U langchain langchain-openai langchain-community pip install -U langgraph langgraph-checkpoint pip install -U langchain-mcp-adapters mcp pip install -U python-dotenv版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你使用的是旧版 0.1.x、0.2.x或者是已经调整了包结构的 1.x 版本遇到导入错误时优先检查对应版本的官方迁移文档。2.3 模型服务的准备模型接入有两种常见方式。第一种是使用云端模型的 OpenAI 兼容接口。现在不少模型服务提供了 OpenAI 兼容的 HTTP 接口因此可以用ChatOpenAI统一接入export OPENAI_API_KEY你的密钥 export OPENAI_API_BASEhttps://api.example.com/v1 export LLM_MODEL你的模型名第二种是使用本地模型比如 Ollama。如果电脑资源允许可以先拉取一个轻量模型ollama pull qwen2.5:7b然后在环境变量中配置export OLLAMA_BASE_URLhttp://localhost:11434文章后面的模型初始化代码会同时兼容这两种方式实际运行时根据你本地的密钥和模型调整即可。注意密钥不要硬编码到代码文件里建议统一放到.env文件并且把.env加入.gitignore。3. 模型初始化与第一条 Chain3.1 初始化 ChatModelLangChain 中与模型交互最常用的类是ChatOpenAI。在较新版本中它需要从langchain_openai导入而不是旧的langchain.chat_models。# 文件model_init.py import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() llm ChatOpenAI( modelos.getenv(LLM_MODEL, gpt-4o-mini), api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_API_BASE), temperature0, )如果你的模型服务没有配置base_url也可以不传该参数。对兼容 OpenAI 协议的本地服务或国内模型平台来说base_url通常指向对应服务的/v1地址。如果使用的是 Ollama 本地模型可以换成langchain_ollama中的ChatOllama# 文件model_init_ollama.py from langchain_ollama import ChatOllama llm ChatOllama( modelqwen2.5:7b, temperature0, )temperature0通常用于工具调用、信息抽取等确定性要求较高的场景。如果希望模型更有创造力可以适当调高到 0.7 左右。3.2 使用 PromptTemplate 组装输入直接调用模型需要手动拼字符串但真实项目中我们更推荐用ChatPromptTemplate管理 Prompt。它的好处是可以把角色设定、上下文、用户输入分离开。# 文件first_chain.py from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI llm ChatOpenAI() prompt ChatPromptTemplate.from_messages([ (system, 你是一名资深后端架构师擅长用通俗的语言解释技术概念。), (human, 请解释一下什么是{concept}), ]) output_parser StrOutputParser() chain prompt | llm | output_parser result chain.invoke({concept: MCP协议}) print(result)这里我使用了 LangChain 的 LCELLangChain Expression Language语法。|符号像一个管道把数据依次传给 Prompt、模型、输出解析器。3.3 理解输出解析器的作用模型返回的原始对象通常是AIMessage里面包含 content 等字段。但在业务代码里我们往往只需要纯文本结果这时候StrOutputParser会直接从AIMessage中提取字符串内容。如果模型需要返回 JSON更推荐使用with_structured_output它能让模型严格按照你定义的 Pydantic 模型返回结构化数据。from pydantic import BaseModel, Field class ConceptExplanation(BaseModel): concept: str Field(description概念名称) summary: str Field(description通俗解释) key_points: list[str] Field(description核心要点) structured_chain prompt | llm.with_structured_output(ConceptExplanation) result structured_chain.invoke({concept: AI Agent}) print(result.model_dump())这样后续业务代码就不需要再写字符串正则解析了。4. LangChain 新版 Middleware 中间件应用思路4.1 为什么需要中间件在实际开发中我们不能只关心“模型是否回答了问题”还要关注整个 Agent 运行过程是否稳定、可观测。比如每次模型调用花了多少时间。Agent 调用了哪些工具传了什么参数。是否有某一步触发了异常。是否需要限流、缓存、权限校验。这些逻辑如果散落在每个业务节点里代码会非常冗余。更合理的做法是把它们抽成“横切逻辑”这就是中间件思想。需要说明的是LangChain 生态里没有一个叫Middleware的万能安装包。新版 LangChain 和 LangGraph 通常借助 Callback 回调机制、自定义 Runnable 或 LangGraph 图节点钩子来实现中间件。理解这一层设计能帮你避开“想找官方中间件却找不到”的困惑。4.2 通过 CallbackHandler 实现日志中间件最常用、也最接近传统中间件概念的是BaseCallbackHandler。它可以监听模型启动、模型结束、工具启动、工具结束、链启动等事件。# 文件middleware_logging.py import logging import time from langchain_core.callbacks import BaseCallbackHandler logging.basicConfig(levellogging.INFO) logger logging.getLogger(middleware) class TimeAndLoggingHandler(BaseCallbackHandler): def __init__(self): self.start_time None def on_llm_start(self, serialized, prompts, **kwargs): self.start_time time.time() logger.info([Middleware] LLM 开始推理prompt 数%d, len(prompts)) def on_llm_end(self, response, **kwargs): cost time.time() - self.start_time if self.start_time else 0 logger.info([Middleware] LLM 推理结束耗时 %.2fs, cost) def on_tool_start(self, serialized, input_str, **kwargs): logger.info([Middleware] 调用工具%s参数%s, serialized.get(name), input_str) def on_tool_end(self, output, **kwargs): logger.info([Middleware] 工具返回%s, output) def on_chain_start(self, serialized, inputs, **kwargs): logger.info([Middleware] Chain 开始输入字段%s, list(inputs.keys()))这段代码里需要注意一点Handler 中同名事件方法需要保留**kwargs因为 LangChain 回调会传入 run_id 等上下文参数。4.3 在 Chain 或 Agent 中挂载中间件Handler 写好后可以通过config传入运行时配置。# 文件use_middleware.py from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from middleware_logging import TimeAndLoggingHandler llm ChatOpenAI() prompt ChatPromptTemplate.from_messages([ (system, 你是一个简洁的技术助手。), (human, {question}), ]) chain prompt | llm | StrOutputParser() handler TimeAndLoggingHandler() result chain.invoke( {question: 什么是Agent用一句话回答。}, config{callbacks: [handler]}, ) print(最终结果, result)运行后控制台会先看到中间件打出的开始日志之后才会输出最终回答。这样在做 Agent 排障时就能比较清楚地知道是哪一步耗时较长或者哪一次工具调用传参错误。4.4 用自定义 Runnable 实现前置后置逻辑除了 Callback也可以把自定义逻辑封装成 Runnable然后放在管道前后。例如以下代码模拟了请求前校验和请求后耗时输出# 文件runnable_middleware.py import time from langchain_core.runnables import RunnableLambda def before_request(inputs: dict) - dict: print([Pipeline Middleware] 请求前执行校验输入) if not inputs.get(question): raise ValueError(question 不能为空) return inputs def after_request(output: str) - str: print([Pipeline Middleware] 请求后执行记录结果长度) print(f结果字符数{len(output)}) return output chain ( RunnableLambda(before_request) | prompt | llm | StrOutputParser() | RunnableLambda(after_request) )这种写法的好处是逻辑更显式任何接手代码的人都能一眼看出前后处理逻辑。它的缺点是如果逻辑非常复杂会污染链的可读性。因此在实际工程中简单的横切逻辑建议用 Callback业务强相关的预处理可以放在 Runnable 里。5. Prompt 设计与 Skills 技能封装5.1 Prompt 模板设计的几个关键点很多 AI 应用效果不好问题不是模型不行而是 Prompt 设计不够清晰。写 Prompt 时要尽量做到以下几点。第一角色明确。告诉模型“你是什么人”比如“你是数据库运维助手”或“你是代码审查专家”。第二边界清晰。明确哪些能做哪些不能做尤其是安全边界。比如只允许执行只读 SQL遇到删除或更新操作必须拒绝并向用户说明。第三给示例。例如希望模型按固定格式返回工具参数就给一个输入输出示例。第四把动态信息用变量传进去不要用字符串拼接污染代码。5.2 LangChain Skills 到底是什么Skills 可以理解为 Agent 的一组“可复用能力包”。传统做法是给 Agent 一段很长的 System Prompt再把所有工具一次性塞给它。问题是工具少还好一旦工具很多模型可能会混淆哪个工具在什么场景下用。Skills 的思路是把“一段技能描述 一组工具 一份操作指令”打包成一个独立单位。Agent 遇到任务时根据技能描述选择需要启用的技能包这样比把所有信息堆在一起更清晰也更容易维护。5.3 通过指令与工具实现一个“SQL 查询技能”在 LangChain 新老版本交替阶段Skills 的 API 形式也在调整。这里我用工程化方式实现一个技能包结构便于理解也方便你在稳定性更强的版本中迁移。# 文件skills/sql_assistant_skill.py from langchain_core.tools import tool tool def query_table_info(sql: str) - str: 执行只读SQL查询只允许SELECT语句。 # 实际项目中这里应连接经过授权的只读数据库账号 # 并使用参数绑定绝不能直接拼接用户输入 print(f执行SQL: {sql}) return [模拟查询结果] 返回两行数据 SQL_ASSISTANT_SKILL { name: sql_assistant, description: 当用户需要分析数据库、查询表结构或统计数据时使用, instructions: 你是数据库分析助手。当你使用SQL工具时必须遵守以下规则 1. 只允许生成SELECT只读查询。 2. 禁止生成DELETE、UPDATE、DROP、ALTER等变更语句。 3. 涉及订单、用户等敏感数据时不要打印完整明细。 4. 查询前先判断字段是否存在不确定时先查表结构。 , tools: [query_table_info], }这个技能包强调了最小权限和只读边界属于工程上非常重要的安全设计。5.4 把技能指令注入 Agent在 LangGraph 的 ReAct Agent 中可以通过state_modifier或系统 Prompt 注入技能描述。不同版本参数名可能不同这里给出常见写法# 文件agent_with_skill.py from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent from skills.sql_assistant_skill import SQL_ASSISTANT_SKILL, query_table_info llm ChatOpenAI() agent create_react_agent( llm, toolsSQL_ASSISTANT_SKILL[tools], state_modifierSQL_ASSISTANT_SKILL[instructions], ) result agent.invoke({ messages: [(human, 帮我查一下用户表中总共有多少用户)] }) for message in result[messages]: print(message.type, message.content)需要提醒的是state_modifier在不同大版本中可能存在差异。如果你安装的版本不支持该参数可以查看当前版本的create_react_agent方法签名或者改为在 messages 中手动加入 SystemMessage。6. ReAct 模式与 Agent 实战6.1 从工具调用理解 ReAct 内部流程先不引入 MCP我们用普通tool工具实现一个最小 ReAct Agent这样能更清楚看到“模型自主决定调用工具”的过程。# 文件react_agent_demo.py from langchain_core.messages import HumanMessage, SystemMessage from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from langgraph.prebuilt import create_react_agent tool def get_weather(city: str) - str: 查询指定城市的天气情况城市使用中文名称。 mock_data { 北京: 晴25℃, 上海: 小雨28℃, 广州: 多云30℃, } return mock_data.get(city, 暂未收录该城市天气数据) llm ChatOpenAI(temperature0) agent create_react_agent( llm, tools[get_weather], checkpointerMemorySaver(), ) config {configurable: {thread_id: demo-001}} messages [ SystemMessage(content你是一个天气查询助手查询天气后要给出简洁结论。), HumanMessage(content北京今天适合跑步吗), ] result agent.invoke({messages: messages}, config) for msg in result[messages]: print(f\n[{msg.type}]) print(msg.content)运行这段代码后日志中会出现多个不同类型的消息比如human、ai、tool。ai消息中会包含 thought 和 tool_call 信息tool消息则是对应工具返回结果。这就是 ReAct 模式的直观体现。6.2 观察 ReAct 中间轮次实际调试 Agent 时只看最终结果往往不够还需要观察中间轮次。可以使用stream模式for chunk in agent.stream({messages: messages}, config): for key, value in chunk.items(): if messages in value: last_msg value[messages][-1] print(f\n[{key}] {last_msg.type}: {last_msg.content})在输出中你可能会看到[agent] ai: [agent] ai: [{name: get_weather, args: {city: 北京}}] [tools] tool: 北京晴25℃ [agent] ai: 北京今天适合跑步。第二步没有直接回答用户而是给出了工具调用参数。工具返回后模型才真正拿到数据形成最终回答。这也是 Agent 和普通单轮问答模型的本质区别Agent 能基于观测结果继续推理。6.3 工具调用中的常见异常循环如果模型一直重复调用同一个工具而工具返回结果没有变化可能是因为工具描述不够清晰或者调用了不支持工具调用的模型。解决办法有三个方向在工具 description 中写清楚“什么场景下用、参数是什么、返回什么”。限制最大递归次数防止资源被耗尽。换用工具调用能力更强的模型。LangGraph 中可以通过recursion_limit限制执行步数config { configurable: {thread_id: demo-002}, recursion_limit: 10, }如果超过recursion_limitLangGraph 会抛出异常方便我们定位死循环问题。7. MCP 协议实战让 Agent 连接外部工具7.1 MCP 是什么MCPModel Context Protocol模型上下文协议是一套开放协议它定义了 AI 应用如何与外部工具、数据源进行标准化交互。过去不同工具需要不同接入方式MCP 出现后只要工具实现了 MCP ServerAI 应用就能用统一方式发现并调用能力。你可以把 MCP 理解成 AI 世界的“统一接口标准”。一个 MCP Server 可以提供查询天气、操作数据库、调用设计稿平台等能力而 Agent 不需要关心每个平台内部的 SDK 细节。7.2 编写一个轻量 MCP Server这里用 FastMCP 风格的 API 编写一个简单的数学计算服务。需要说明的是不同版本中 FastMCP 的 import 路径可能不同如果mcp.server.fastmcp导入失败说明需要安装或升级 mcp 相关依赖。# 文件mcp_math_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(math-demo) mcp.tool() def add(a: int, b: int) - int: 两个整数相加。 return a b mcp.tool() def multiply(a: int, b: int) - int: 两个整数相乘。 return a * b if __name__ __main__: mcp.run(transportstdio)这个 Server 通过 stdio 与客户端通信适合本地进程调用。它暴露了两个工具add和multiply。7.3 在 LangChain 中通过 MCP Client 获取工具LangChain 官方提供了适配包langchain-mcp-adapters它可以把 MCP Server 返回的工具转换成 LangChain 能直接使用的 Tool。# 文件load_mcp_tools.py import asyncio from pathlib import Path from langchain_mcp_adapters.client import MultiServerMCPClient async def main(): server_script str(Path(__file__).parent / mcp_math_server.py) async with MultiServerMCPClient( { math: { command: python, args: [server_script], transport: stdio, } } ) as client: tools await client.get_tools() print(加载到的工具) for tool in tools: print(tool.name, -, tool.description) asyncio.run(main())运行后控制台会输出add和multiply两个工具。这个过程中LangChain 通过 MCP 协议启动了一个本地子进程并与该子进程完成工具发现和调用准备。7.4 MCP 与普通 Function Calling 的选择如果你的工具只是当前项目内部使用直接定义tool函数就够了。但如果工具需要跨项目复用或者未来要接入多个 Agent 平台MCP 会更合适。现在很多平台都开始提供 MCP Server比如设计协作工具、数据库管理工具、代码托管平台等。作为应用开发者我们既可以直接连接已有 MCP Server也可以先把自己的内部能力封装成 MCP Server再统一开放给多个 Agent 使用。需要提醒的是MCP Server 一旦暴露到网络就相当于一个对外接口必须自己做鉴权、限流和权限控制不能默认允许一切调用。8. 综合案例带日志中间件的 MCP 查询 Agent接下来把前面的概念整合到一起做一个比较完整的案例使用MultiServerMCPClient启动一个 MCP 数学计算 Server。使用 LangGraph 的create_react_agent构建 ReAct Agent。在运行时挂载 Callback Handler作为日志中间件。输入一个问题“请计算 (15 27) * 3”观察 Agent 是如何推导和调用工具的。先复用上一节编写的mcp_math_server.py再新建主程序。# 文件main.py import asyncio import logging from pathlib import Path from langchain_core.callbacks import BaseCallbackHandler from langchain_core.messages import HumanMessage from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from langgraph.prebuilt import create_react_agent from langchain_mcp_adapters.client import MultiServerMCPClient logging.basicConfig(levellogging.INFO) logger logging.getLogger(agent-demo) class AgentLoggingMiddleware(BaseCallbackHandler): def on_llm_start(self, serialized, prompts, **kwargs): logger.info([Middleware] LLM 开始推理prompt 数%d, len(prompts)) def on_llm_end(self, response, **kwargs): logger.info([Middleware] LLM 推理结束) def on_tool_start(self, serialized, input_str, **kwargs): logger.info([Middleware] 工具调用%s参数%s, serialized.get(name), input_str) def on_tool_end(self, output, **kwargs): logger.info([Middleware] 工具返回%s, output) llm ChatOpenAI(temperature0) async def main(): server_script str(Path(__file__).parent / mcp_math_server.py) async with MultiServerMCPClient( { math: { command: python, args: [server_script], transport: stdio, } } ) as client: tools await client.get_tools() agent create_react_agent( llm, toolstools, checkpointerMemorySaver(), ) config { configurable: {thread_id: mcp-demo-001}, callbacks: [AgentLoggingMiddleware()], } result await agent.ainvoke( { messages: [ HumanMessage(content请帮我计算 (15 27) * 3 的结果) ] }, configconfig, ) print(\n最终回答) print(result[messages][-1].content) asyncio.run(main())运行前确认已经安装依赖并正确配置 OpenAI 兼容接口。如果使用 Ollama只需要把llm替换为ChatOllama其余逻辑保持不变。预期可以看到中间件
返回列表