当前位置: 首页 > news >正文

第二阶段(核心攻坚):死磕 LangGraph + ReAct 架构 + 工具调用,手撕一个带记忆的多工具 Agent

1. 为什么「带记忆的多工具 Agent」是面试硬通货

大模型应用开发已经过了“会调 API 就能写简历”的阶段。现在面试官真正想考察的是:你能否让模型在真实任务中自主决策、调用外部工具、并且记住上下文持续工作。这三点分别对应三个核心技术:

  • ReAct 架构:Reasoning + Acting 的循环,让模型在“思考”和“行动”之间交替,而不是一次性输出答案。
  • LangGraph:把 Agent 的决策流程建模成状态图,比传统的 AgentExecutor 更灵活、更可控。
  • 工具调用(Tool Calling):让模型能查天气、搜网页、执行代码、读写数据库,突破纯文本生成的边界。

再加一层记忆——多轮对话里模型要记住用户之前说过什么、执行过什么,这就是“带记忆的多工具 Agent”。这套组合拳在面试里几乎必考,而且通常要求现场手撕代码

本文会用一段可以复制运行的 Python 代码,完整实现一个带记忆的多工具 Agent。读完你就能直接拿去面试。

核心概念

在正式进入代码之前,先建立三个关键概念的共识,它们是理解后文所有示例的基础:

  1. Agent(智能体):以大模型为决策中枢,能够根据目标自主规划步骤、调用外部工具并持续迭代执行的应用形态。它不是简单的“问答机器人”,而是一个会“干活”的执行体。
  2. ReAct 架构:Reasoning + Acting 的缩写,让模型在“推理”与“行动”之间交替循环。模型先思考下一步,再决定是调用工具还是输出最终答案,直到任务完成。
  3. 工具调用(Tool Calling):模型输出结构化指令、由程序实际执行外部工具并返回结果的能力。它让 Agent 从“只能生成文本”扩展为“能查数据、能计算、能操作外部系统”。

这三个概念叠加起来,再加上贯穿多轮对话的记忆,就构成了本文要手撕的完整 Agent。

2. ReAct 架构:模型为什么要「边想边做」

2.1 传统方式的问题

如果只用一个简单的提示词让模型回答问题,模型遇到“北京明天天气怎么样”这种需要实时数据的问题时,只能凭训练数据编一个答案。这不是能力问题,而是它没有获得外部信息的手段

2.2 ReAct 的核心循环

ReAct 的核心是一个循环:

需要

不需要

接收用户问题

模型思考:需要工具吗?

输出工具调用指令

执行工具,拿到结果

把结果喂回模型

输出最终答案

每一轮循环包含两个动作:

  1. Reasoning(推理):模型分析当前状态,决定下一步是调用工具还是直接回答。
  2. Acting(行动):模型生成一个结构化的工具调用,由程序真正执行工具,再把结果拼回上下文。

这个循环会一直持续,直到模型认为信息足够、输出最终答案为止。

2.3 一个最小的 ReAct 提示词

REACT_PROMPT="""你是一个智能助手,可以使用以下工具: {tools} 请按照以下格式回答: Thought: 你的思考过程 Action: 要调用的工具名 Action Input: 工具的输入参数 Observation: 工具执行结果 ...(Thought/Action/Action Input/Observation 可以重复多次) Thought: 我现在知道最终答案了 Final Answer: 给用户的最终回答 用户问题:{question} """

这个提示词就是 ReAct 的“灵魂”。它强制模型按照固定的节奏输出,程序再解析ActionAction Input,真正调用工具。现在很多框架已经把这个过程封装好了,但面试官问你 ReAct 原理时,你要能讲清楚这个循环

3. LangGraph 核心概念:把 Agent 画成一张图

3.1 为什么不用 LangChain 的 AgentExecutor

早期 LangChain 的AgentExecutor是一个黑盒循环,流程写死在框架里。想加记忆、加人工确认、加条件分支都很别扭。LangGraph 的思路完全不同:把 Agent 的每个环节都拆成一个节点(Node),用边(Edge)连接起来,形成一个显式的状态图

好处有三个:

  • 流程可视化:Agent 的每一步都看得见,出问题好排查。
  • 高度可控:可以随时在图里插入人工审批、重试、超时等节点。
  • 状态可持久化:图的状态就是记忆的载体,天然支持多轮对话。

3.2 三个核心概念

概念作用类比
State在节点之间传递的数据结构流水线上的工件
Node一个处理函数,接收 State 并返回更新流水线上的工位
Edge决定下一个节点是谁,可以带条件流水线上的传送带

一个最简单的图长这样:

fromtypingimportTypedDict,Annotatedfromlanggraph.graphimportStateGraph,START,ENDclassAgentState(TypedDict):messages:Annotated[list,"messages"]defnode_a(state:AgentState):# 处理逻辑,返回要更新的字段return{"messages":state["messages"]+["A 处理完成"]}graph=StateGraph(AgentState)graph.add_node("a",node_a)graph.add_edge(START,"a")graph.add_edge("a",END)app=graph.compile()

3.3 条件边:ReAct 循环的图表达

ReAct 里“要不要继续调工具”是一个判断,这在 LangGraph 里用条件边表达:

defshould_continue(state:AgentState)->str:last_message=state["messages"][-1]# 如果最后一条消息包含工具调用,就回到工具节点ifgetattr(last_message,"tool_calls",None):return"tools"# 否则结束returnEND graph.add_conditional_edges("agent",should_continue,{"tools":"tools",END:END})

这就是 LangGraph 表达 ReAct 循环的完整方式:agent节点负责推理,tools节点负责执行工具,条件边决定“继续做”还是“收工”。整个循环在图的状态里自动运行,直到走出END

4. 实战:手撕一个带记忆的多工具 Agent

下面是一段完整可运行的代码。我们用langchain-openai作为模型层,工具是两个常用场景:查询天气执行简单计算。整个 Agent 有完整的四轮对话记忆,并且每轮都能调用不同工具。

4.1 安装依赖

pipinstalllanggraph langchain-openai langchain-core

4.2 定义工具

fromlangchain_core.toolsimporttool@tooldefget_weather(city:str)->str:"""查询指定城市的天气。 参数: city: 城市名称,例如"北京" """weather_data={"北京":"晴,28°C,空气质量优","上海":"多云,26°C","广州":"雷阵雨,32°C","深圳":"晴,30°C",}returnweather_data.get(city,f"暂无{city}的天气数据")@tooldefcalculate(expression:str)->str:"""执行一个数学表达式计算。 参数: expression: 数学表达式,例如 "123 * 45 + 67" """try:result=eval(expression,{"__builtins__":{}},{})returnf"计算结果为:{result}"exceptExceptionase:returnf"计算失败:{e}"

说明:示例中eval使用了受限的命名空间,仅用于教学演示。生产环境请替换为ast解析或专用计算库,避免任意代码执行风险。

4.3 定义带记忆的 State

记忆的本质是把历史消息保存在 State 里,并且使用add_messages归并器来追加而不是覆盖

fromtypingimportTypedDict,Annotatedfromlangchain_core.messagesimportBaseMessagefromlanggraph.graph.messageimportadd_messagesclassAgentState(TypedDict):# add_messages 保证了每轮消息会追加到列表末尾,而不是覆盖messages:Annotated[list[BaseMessage],add_messages]

4.4 绑定工具并构建图

importosfromlangchain_openaiimportChatOpenAIfromlanggraph.graphimportStateGraph,START,ENDfromlanggraph.prebuiltimportToolNode# 初始化模型model=ChatOpenAI(model="gpt-4o-mini",temperature=0,api_key=os.getenv("OPENAI_API_KEY"),)# 把工具列表绑定到模型tools=[get_weather,calculate]model_with_tools=model.bind_tools(tools)defagent_node(state:AgentState):"""agent 节点:模型根据当前消息决定调用工具还是直接回答。"""response=model_with_tools.invoke(state["messages"])return{"messages":[response]}# 构建图defbuild_agent():graph=StateGraph(AgentState)graph.add_node("agent",agent_node)# ToolNode 会自动解析最后一条消息里的 tool_calls 并执行工具graph.add_node("tools",ToolNode(tools))defshould_continue(state:AgentState)->str:last_message=state["messages"][-1]ifgetattr(last_message,"tool_calls",None):return"tools"returnEND graph.add_edge(START,"agent")graph.add_conditional_edges("agent",should_continue,{"tools":"tools",END:END})graph.add_edge("tools","agent")returngraph.compile()app=build_agent()

4.5 四轮对话验证记忆与工具切换

fromlangchain_core.messagesimportHumanMessage,AIMessage# 记忆列表会贯穿所有轮次memory:list[BaseMessage]=[]# 第一轮:查天气memory.append(HumanMessage(content="北京今天天气怎么样?"))result=app.invoke({"messages":memory})memory=result["messages"]print("第一轮:",memory[-1].content)# 第二轮:计算题(验证工具切换)memory.append(HumanMessage(content="请帮我算一下 1024 * 768 等于多少?"))result=app.invoke({"messages":memory})memory=result["messages"]print("第二轮:",memory[-1].content)# 第三轮:追问天气(验证多轮记忆)memory.append(HumanMessage(content="那上海呢?"))result=app.invoke({"messages":memory})memory=result["messages"]print("第三轮:",memory[-1].content)# 第四轮:验证模型记得上下文memory.append(HumanMessage(content="我刚才问了哪几个城市?"))result=app.invoke({"messages":memory})memory=result["messages"]print("第四轮:",memory[-1].content)

第三轮的“那上海呢?”最能体现记忆的价值:模型必须回顾前文才知道用户是在问天气,而不是问别的。第四轮则直接考验模型对整段对话的回忆能力。

5. 面试高频追问:这样回答才加分

5.1 “ReAct 和 Function Calling 是什么关系?”

参考答案:Function Calling 是模型层的能力,指模型可以输出结构化的工具调用请求;ReAct 是应用层的循环架构,它利用 Function Calling 来驱动“思考—行动—观察”的循环。两者不在一个层面:Function Calling 是“手”,ReAct 是“怎么用手”。

5.2 “为什么用 LangGraph 而不是直接写 while 循环?”

参考答案:while 循环也能实现 ReAct,但 LangGraph 提供了三样东西:一是状态管理,图的 State 天然支持持久化、断点恢复和回溯;二是可视化与可观测性,每个节点的输入输出都能追踪;三是可扩展性,加人工审批、并行分支、超时重试都只需要改图结构,不需要改动核心循环代码。

5.3 “记忆存在哪?怎么持久化?”

参考答案:短期记忆存在 State 的messages列表里,进程内就能跑;持久化需要给graph.compile()传入checkpointer,比如MemorySaver(内存)或SqliteSaver(磁盘),这样每次invoke都传入thread_id,就能恢复对应会话的完整状态。跨会话的长期记忆还可以单独挂一个向量库,把历史对话存成 embedding,检索后拼进提示词。

5.4 “工具调用失败了怎么办?”

参考答案:分两层处理。第一层是模型层面,在工具描述里写清楚异常返回格式,模型拿到错误信息后会自己调整策略;第二层是图层面,用ToolNode的异常捕获或者自定义 try/except 包裹节点,把工具异常转成一条普通消息返回给模型,让它继续决策,避免整个 Agent 崩溃。

5.5 “怎么控制 Agent 不无限循环?”

参考答案:三个手段:一是设定max_iterations,超过就强制结束并返回中间结果;二是在should_continue里加判断,连续 N 次调用同一个工具且输入没变化就终止;三是给模型绑定max_tokens和时间预算。这些控制逻辑在图的条件边里实现非常自然。

6. 总结

这一阶段的目标不是“会用 LangGraph”,而是能从零手写出一个带记忆的多工具 Agent,并讲清楚每一层为什么这样设计。梳理一下核心链路:

ReAct 循环

LangGraph 状态图

Tool Calling

记忆 State

可运行的多工具 Agent

面试时如果被要求手撕,就按本文的节奏来:先定义工具,再定义带add_messages的 State,然后构建agent+tools两个节点,最后用条件边串成循环。逻辑清晰、结构完整,就是硬通货该有的样子。

http://www.jsqmd.com/news/1402591/

相关文章:

  • AI智能代理操作系统实践指南:从环境搭建到自动化工作流设计
  • Prometheus架构解析
  • 2026年8月湖北金属结构焊接加工/金属结构加工厂家实力推荐_武汉茂更茂科技有限公司 - 品牌宣传支持者
  • 财政涉农支出数据分析:从概念到应用,解锁三农政策研究
  • 商用音效素材平台测评|不同创作者如何选对音效网站
  • 2026年8月咖啡机配件/美式咖啡机配件行业优选推荐_宁波市维为电器有限公司 - 行业平台推荐
  • 2026年8月超薄智能开关/全屋智能开关公司推荐测评_深圳市新贵峰科技有限公司 - 品牌宣传支持者
  • Maven 3.9.1 安装配置与实战指南:从零搭建Java项目构建环境
  • OMV 6网络配置指南:从Netplan原理到实战排错
  • 基于SpringBoot的社区助老志愿服务系统设计与实现(源码+lw+部署文档+讲解等)
  • 2026年8月浙江轮毂两轮电动车电机/永磁同步两轮电动车电机厂家推荐测评_浙江大泰新能源有限公司 - 行业平台推荐
  • 调理脾胃的产品对儿童瘦小会有副作用吗 权威科普解答
  • 基于SpringBoot的助农扶贫平台系统(源码+lw+部署文档+讲解等)
  • 时间紧迫自救!亲测这6款AI写作辅助软件,从大纲到终稿全程开挂
  • C++引用参数与返回值的核心原理、陷阱与最佳实践
  • 【C语言】一维数组+字符串
  • 2026年8月高频同步换向电源/微弧氧化电源行业优选推荐_宁波帆洲电源科技有限公司 - 品牌宣传支持者
  • 2026 年新消息:临桂诚信的建材推广品牌电话,别再被坑了!家装选对它,省出一台高端家电钱! - 行业鉴选官
  • 计算机必学---SQL
  • 2026年8月宁波水泵石墨轴承/高转速石墨轴承厂家信誉推荐_宁波市鄞州区格耐法机械密封件有限公司 - 品牌宣传支持者
  • 零基础部署YOLO改进源码:云服务器环境配置与深度学习实践指南
  • 个人微信API接口使用价值分析:让微信能力融入开发项目的5个关键
  • 2026年8月末端快换/零点快换行业精选厂家_山东威寻机器人科技有限公司 - 品牌宣传支持者
  • 2026年小学生参加信奥赛C++相关地方赛事拿奖攻略!(附2025年CSP-X复赛真题及题解(山东、河南、江西、辽宁、广西)
  • 智能汽车E/E架构演进:从分布式到中央计算的14项关键技术解析
  • 基于SpringBoot的足球俱乐部管理系统的设计与实现(源码+lw+部署文档+讲解等)
  • 上海急招岗位,哪个招聘平台的收费更合理?推荐几家收费套餐更划算的招聘渠道
  • 2026年最新一体化管理系统/全链路供应链数字化/智能仓配一体化科技企业核心竞争力解构-任
  • 2026年8月宁波浸锑石墨密封件/高温高压耐磨石墨密封件厂家推荐_宁波市鄞州区格耐法机械密封件有限公司 - 品牌宣传支持者
  • 基于SpringBoot的游戏赛事平台设计与实现(源码+讲解视频+LW)