第24课:LangChain|内置Agent使用【ReAct、OpenAI Function Calling实战】
文章目录
- 课程导读 & 学习目标
- 前置知识与环境准备
- 1.1 环境沿用
- 1.2 依赖包安装
- 1.3 模型选择
- 1.4 上节课回顾与本课定位
- 核心概念深度拆解
- 2.1 ReAct Agent:提示词驱动的通用模式
- 2.2 OpenAI Function Calling:原生结构化调用
- 2.3 两种模式的对比总结
- 2.4 LangGraph中的create_react_agent函数
- 底层运行原理剖析
- 3.1 ReAct Agent的内部实现
- 3.2 Function Calling Agent的内部实现
- 3.3 思维链(Chain-of-Thought)与ReAct的关系
- 核心API/组件源码解读
- 4.1 create_react_agent 快速上手
- 4.2 自定义系统提示词(state_modifier)
- 4.3 手动实现ReAct Agent(不借助create_react_agent)
- 手把手项目实战教学
- 实战一:使用create_react_agent构建本地ReAct Agent(Ollama)
- 实战二:Function Calling Agent(智谱GLM-4)
- 实战三:对比ReAct和Function Calling的输出差异
- 完整可运行Python代码
- 环境依赖安装命令
- 常见报错坑点与避坑方案
- 坑1:`create_react_agent` 报错 `Model does not support bind_tools`
- 坑2:ReAct Agent 无法正确解析 Action
- 坑3:Function Calling Agent 调用工具时参数类型错误
- 坑4:本地Ollama模型无响应或超时
- 本节核心知识点总结
- 课后练习题
- 选择题
- 简答题
- 实践题
- 🔗《30节课 LangChain 从入门到精通》系列课程导航
课程导读 & 学习目标
在上一节课中,我们深入剖析了Agent的核心原理,并使用LangGraph从零构建了一个支持工具调用的ReAct Agent。你理解了“LLM + 工具 + 循环”的工作机制。但是,在实际开发中,我们往往不需要从头编写所有节点和边——LangChain和LangGraph提供了更高级的抽象和预置模式,可以大幅简化Agent的开发工作。
本节课将聚焦于两种最主流的Agent模式:ReAct和OpenAI Function Calling。ReAct(Reasoning + Acting)是一种基于提示词模板的通用模式,通过引导LLM输出“Thought/Action/Action Input/Observation”结构来实现工具调用,兼容任何支持文本生成的LLM。OpenAI Function Calling则是OpenAI、智谱GLM-4等模型原生支持的结构化工具调用方式,模型直接返回JSON格式的工具参数,更加可靠和高效。
需要注意的是,LangChain旧版中的initialize_agent、AgentExecutor等API已被标记为废弃。现代开发中,我们推荐使用LangGraph的统一框架来实现这两种模式。本节课将教你如何在LangGraph中优雅地实现ReAct Agent和Function Calling Agent,并对比它们的适用场景。
学完本节课,你将达到以下目标:
- 深刻理解ReAct Agent的工作机制:掌握如何通过提示词模板引导模型输出结构化的“思考-行动-观察”序列。
- 掌握OpenAI Function Calling Agent的实现:学会使用
llm.bind_tools()构建原生工具调用的Agent,体验更可靠的工具使用。 - 对比两种模式的优缺点:了解何时选择ReAct(兼容任何模型),何时选择Function Calling(模型原生支持,精度更高)。
- 能够快速构建具备多工具调用能力的Agent:通过LangGraph预置组件简化代码。
- 完成两个完整的实战项目:基于本地Ollama模型的ReAct Agent(无需function calling支持)和基于智谱GLM-4的Function Calling Agent。
前置知识与环境准备
1.1 环境沿用
继续使用前几课的langchain_course虚拟环境,Python 3.10+。
# 激活虚拟环境sourcevenv/bin/activate# Mac/Linux# venv\Scripts\activate # Windows# 升级pippipinstall--upgradepip1.2 依赖包安装
# 核心依赖pipinstalllangchain==0.3.7 langchain-core==0.3.21 langchain-community==0.3.7 langchain-openai==0.2.8 python-dotenv==1.0.1# LangGraph 基础pipinstalllanggraph# 本地模型支持(ReAct Agent演示用)pipinstalllangchain-ollama# 验证安装python-c"from langgraph.prebuilt import create_react_agent; print('✓ LangGraph预置ReAct Agent可用')"python-c"from langchain_core.messages import HumanMessage; from langchain_openai import ChatOpenAI; llm=ChatOpenAI(model='gpt-3.5-turbo'); llm.bind_tools([]); print('✓ Function calling支持')"1.3 模型选择
本节课将演示两种Agent:
- ReAct Agent:使用Ollama本地模型(如
qwen2.5:7b),不需要function calling能力,通用性最强。 - OpenAI Function Calling Agent:使用智谱GLM-4(兼容OpenAI接口)或真正的OpenAI GPT-4。
确保你的.env文件中配置了对应的API密钥(如果是智谱或OpenAI)。Ollama用户请确保已下载模型并启动服务。
1.4 上节课回顾与本课定位
第23课我们手写了LangGraph Agent的所有组件(agent_node、tools_node、条件边)。本节课我们将使用LangGraph提供的create_react_agent预置函数快速构建ReAct Agent,并演示如何利用bind_tools实现更简洁的Function Calling Agent。两者的核心思想相同,但实现方式和适用场景不同。学完本课,你将对Agent的工程实践有更全面的掌握。
核心概念深度拆解
2.1 ReAct Agent:提示词驱动的通用模式
ReAct(Reasoning and Acting)是2022年由Google Research提出的Agent范式。它通过提示词引导LLM输出一个特定的格式,包含:
- Thought:模型的推理过程。
- Action:要调用的工具名称。
- Action Input:传递给工具的参数。
- Observation:工具返回的结果(由系统填充,不是模型生成)。
LangGraph的create_react_agent函数封装了这一模式。你只需要提供工具列表和LLM,它会自动构建状态图、解析器、工具执行器等。核心逻辑是:将工具的描述和调用规范注入到系统提示词中,然后解析模型输出中的Action和Action Input。
这种模式的优势是兼容任何文本生成模型,包括不支持原生function calling的开源模型(如Llama 2、Qwen 1.5等)。缺点是依赖模型严格遵循输出格式,对提示词工程要求较高,且解析可能失败。
2.2 OpenAI Function Calling:原生结构化调用
OpenAI、智谱GLM-4、DeepSeek-V4等模型在API层面支持functions或tools参数。通过bind_tools将工具schema传给模型后,模型直接返回一个结构化的tool_calls字段,包含工具名称和参数JSON。这种方式无需手动解析文本,极其可靠。
在LangGraph中,我们可以使用ToolExecutor和自定义节点来处理tool_calls,或者在create_react_agent中也支持传入state_modifier来指定使用function calling。
优势:高可靠性、支持复杂参数类型、并行工具调用。劣势:要求模型API支持function calling,不适用于普通本地模型(除非通过Ollama的兼容模式,但部分模型仍不支持)。
2.3 两种模式的对比总结
| 特性 | ReAct Agent | Function Calling Agent |
|---|---|---|
| 实现原理 | 提示词模板 + 文本解析 | 原生API结构化调用 |
| 模型要求 | 任何文本生成模型 | 模型需支持function calling |
| 可靠性 | 中等(依赖格式遵循) | 高(机器可读JSON) |
| 参数复杂度 | 仅支持简单字符串参数 | 支持嵌套对象、数组、枚举等 |
| 并行调用 | 通常单步单工具 | 原生支持并行多个工具 |
| 调试难度 | 较难(需检查模型输出文本) | 简单(直接查看tool_calls) |
| 适用场景 | 本地模型、旧模型、教学演示 | 生产环境、API模型 |
2.4 LangGraph中的create_react_agent函数
create_react_agent是LangGraph提供的预置Agent构建器。其签名如下:
fromlanggraph.prebuiltimportcreate_react_agent agent=create_react_agent(model:BaseChatModel,tools:List[BaseTool],state_modifier:Optional[str]=None,messages_modifier:Optional[Callable]=None,checkpointer:Optional[BaseCheckpointSaver]=None,interrupt_before:Optional[List[str]]=None,interrupt_after:Optional[List[str]]=None,)model:支持bind_tools的聊天模型(如果模型不支持,可以传一个自定义的model,内部会使用ReAct提示词)。tools:工具列表。state_modifier:系统提示词,用来指导Agent行为。checkpointer:可选的状态持久化。
该函数返回一个编译后的StateGraph实例,可以像普通LangGraph应用一样调用.invoke或.stream。
对于不支持function calling的模型,create_react_agent会自动降级为ReAct模式,提示词中包含工具描述和输出格式指令。对于支持function calling的模型,它会使用原生工具调用方式。这意味着create_react_agent是一个统一的接口,你无需关心底层实现,它会自动选择最优策略。
底层运行原理剖析
3.1 ReAct Agent的内部实现
当create_react_agent检测到模型不支持bind_tools时,它会构建一个ReAct提示词模板。该模板包括:
你是一个非常有用的AI助手,能够使用以下工具来解决问题: {tools} 使用格式如下: Thought: 你当前的想法。 Action: 工具名称,必须是[{tool_names}]之一 Action Input: 工具的输入,应该是JSON字符串。 ……(重复Thought/Action/Action Input/Observation直到获得最终答案) Final Answer: 对用户的最终回复LLM的输出会被正则表达式或json.loads解析,提取Action和Action Input。然后ToolExecutor执行工具,生成Observation,再将Observation附加到消息历史中,重复循环。
3.2 Function Calling Agent的内部实现
如果模型支持bind_tools,create_react_agent会:
- 调用
model.bind_tools(tools)获得一个绑定好工具的模型实例。 - 构建一个代理节点,调用该模型,期望模型返回
AIMessage对象,其tool_calls属性非空。 - 如果
tool_calls存在,路由到tools_node执行工具调用,将结果以ToolMessage形式追加。 - 如果没有
tool_calls,则直接输出content作为最终答案。
这种模式避免了文本解析,直接使用API原生功能。
3.3 思维链(Chain-of-Thought)与ReAct的关系
ReAct是思维链的一种扩展。思维链只关注推理步骤,而ReAct在此基础上增加了“行动”和“观察”,使模型不仅能思考,还能与外部环境交互。
核心API/组件源码解读
4.1 create_react_agent 快速上手
fromlanggraph.prebuiltimportcreate_react_agentfromlangchain_community.toolsimporttoolfromlangchain_openaiimportChatOpenAI@tooldefadd(a:int,b:int)->int:"""两数相加"""returna+b llm=ChatOpenAI(model="gpt-3.5-turbo")agent=create_react_agent(llm,[add])result=agent.invoke({"messages":[("human","3加5等于多少?")]})print(result["messages"][-1].content)4.2 自定义系统提示词(state_modifier)
agent=create_react_agent(llm,tools,state_modifier="你是一个数学专家。请一步步思考,使用计算器工具辅助计算。")state_modifier会作为系统消息插入到对话开头,指导Agent的行为风格。
4.3 手动实现ReAct Agent(不借助create_react_agent)
如果需要完全控制提示词格式,可以手动实现ReAct Agent,但通常没有必要。create_react_agent已经足够灵活。
手把手项目实战教学
实战一:使用create_react_agent构建本地ReAct Agent(Ollama)
目标:使用本地Ollama模型(qwen2.5:7b)和create_react_agent构建一个ReAct Agent,配备计算器和时间查询工具。由于qwen2.5:7b支持function calling?需要确认。实际上qwen2.5:7b在Ollama中可能不支持原生function calling,但create_react_agent会自动使用ReAct模式。我们将演示跨平台兼容性。
importdatetimefromlangchain_ollamaimportChatOllamafromlangchain_core.toolsimporttoolfromlanggraph.prebuiltimportcreate_react_agent@tooldefcalculate(expression:str)->str:"""计算数学表达式。例如'2+2'"""try:returnstr(eval(expression))except:return"计算错误"@tooldefget_current_time()->str:"""获取当前时间"""returndatetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")llm=ChatOllama(model="qwen2.5:7b",temperature=0)tools=[calculate,get_current_time]agent=create_react_agent(llm,tools)result=agent.invoke({"messages":[("human","现在几点了?然后计算1234*5678")]})print(result["messages"][-1].content)注意:第一次运行时,模型可能会尝试输出Action和Action Input,create_react_agent会自动解析并调用工具。
实战二:Function Calling Agent(智谱GLM-4)
目标:使用智谱GLM-4(支持OpenAI function calling)构建一个更为可靠的Agent。
importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttoolfromlanggraph.prebuiltimportcreate_react_agent load_dotenv()@tooldefcalculate(expression:str)->str:"""计算数学表达式"""returnstr(eval(expression))llm=ChatOpenAI(model="glm-4",openai_api_key=os.getenv("ZHIPU_API_KEY"),openai_api_base=os.getenv("BASE_URL"),temperature=0)agent=create_react_agent(llm,[calculate])result=agent.invoke({"messages":[("human","计算(2+3)*4的结果")]})print(result["messages"][-1].content)由于glm-4支持function calling,create_react_agent会自动切换到原生模式,无需手动解析文本。
实战三:对比ReAct和Function Calling的输出差异
通过流式输出查看内部步骤:
# ReAct模式(使用Ollama)forstepinagent.stream({"messages":[("human","2的10次方")]}):print(step)你会看到agent节点输出包含thought和action字段的文本,而在Function Calling模式下,你会看到tool_calls字段。
完整可运行Python代码
整合上述实战,形成完整的演示脚本。
#!/usr/bin/env python# -*- coding: utf-8 -*-""" LangChain 第24课:内置Agent实战 演示两种模式: 1. 基于Ollama的ReAct Agent(通用) 2. 基于智谱GLM-4的Function Calling Agent """importdatetimeimportosfromdotenvimportload_dotenvfromlangchain_core.toolsimporttoolfromlanggraph.prebuiltimportcreate_react_agentfromlangchain_ollamaimportChatOllamafromlangchain_openaiimportChatOpenAI load_dotenv()# ========== 定义公共工具 ==========@tooldefcalculate(expression:str)->str:"""计算数学表达式的值。输入应为字符串格式,如 '2+2' 或 'sqrt(16)'。"""try:# 注意:生产环境请用安全计算库,这里仅演示returnstr(eval(expression))exceptExceptionase:returnf"计算错误:{e}"@tooldefget_current_time()->str:"""获取当前日期和时间"""returndatetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")defrun_react_agent():"""使用本地Ollama模型 + ReAct模式(自动降级)"""print("\n=== ReAct Agent (Ollama) ===")llm=ChatOllama(model="qwen2.5:7b",temperature=0)agent=create_react_agent(llm,[calculate,get_current_time])query="现在几点了?然后帮我计算 1234 * 5678 的结果。"print(f"用户:{query}")result=agent.invoke({"messages":[("human",query)]})final=result["messages"][-1].contentprint(f"Agent:{final}")defrun_function_calling_agent():"""使用智谱GLM-4 + Function Calling原生模式"""print("\n=== Function Calling Agent (智谱GLM-4) ===")llm=ChatOpenAI(model="glm-4",openai_api_key=os.getenv("ZHIPU_API_KEY"),openai_api_base=os.getenv("BASE_URL"),temperature=0)agent=create_react_agent(llm,[calculate])query="计算 (2+3)*4 的结果"print(f"用户:{query}")result=agent.invoke({"messages":[("human",query)]})final=result["messages"][-1].contentprint(f"Agent:{final}")if__name__=="__main__":run_react_agent()run_function_calling_agent()环境依赖安装命令
# 激活虚拟环境sourcevenv/bin/activate# venv\Scripts\activate# 安装核心依赖pipinstalllangchain==0.3.7 langchain-core==0.3.21 langchain-community==0.3.7 langchain-openai==0.2.8 python-dotenv==1.0.1 langgraph langchain-ollama# 拉取本地模型(如未下载)ollama pull qwen2.5:7b# 验证安装python-c"from langgraph.prebuilt import create_react_agent; print('✓ create_react_agent可用')"常见报错坑点与避坑方案
坑1:create_react_agent报错Model does not support bind_tools
原因:你使用的模型(如某些Ollama模型)确实不支持function calling,但create_react_agent仍会以ReAct模式运行,不会报错。如果报错,请升级langchain-core和langgraph到最新版。
解决方案:确保langgraph>=0.1.0。如果模型确实不支持,程序会自动降级,无需干预。
坑2:ReAct Agent 无法正确解析 Action
现象:模型输出不符合预期格式,Agent卡住或循环。
原因:模型能力较弱,不能很好地遵循输出格式指令。
解决方案:尝试更换更强的模型(如qwen2.5:14b、llama3.2);或使用更精确的提示词模板,在state_modifier中明确要求“必须严格按格式输出”。
坑3:Function Calling Agent 调用工具时参数类型错误
现象:模型生成tool_calls但参数是字符串而非数字。
原因:工具函数的参数类型定义不清晰(如使用了int但docstring未说明)。
解决方案:使用Pydantic模型定义参数,并在工具注解中明确类型。@tool会自动从类型注解生成schema。
@tooldefadd(a:int,b:int)->int:"""相加两个整数"""returna+b坑4:本地Ollama模型无响应或超时
原因:模型加载慢或硬件不足。
解决方案:减少max_tokens,或使用更小的量化模型(如qwen2.5:7b-q4_0)。
本节核心知识点总结
📌两种内置Agent模式:
- ReAct Agent:基于提示词模板,兼容任何模型,通过解析
Action/Action Input文本调用工具。 - Function Calling Agent:利用模型原生的结构化输出,可靠、高效,但要求模型支持function calling。
📌create_react_agent是LangGraph提供的预置Agent构建器,它会根据模型能力自动选择ReAct或Function Calling模式。你只需提供LLM和工具列表,即可快速得到生产级的Agent。
📌核心参数:
model:聊天模型实例。tools:工具列表。state_modifier:系统提示词,用于指导Agent行为。checkpointer:可选的状态持久化,支持断点续传。
📌适用场景:
- 本地开发、模型受限时使用ReAct模式。
- 生产环境、API模型推荐使用Function Calling模式。
📌与旧版AgentExecutor的对比:新版LangGraph的create_react_agent功能更强大,支持流式、状态持久化、人机交互,是官方推荐的统一接口。
课后练习题
选择题
1. 使用create_react_agent构建Agent时,如果本地模型不支持function calling,会发生什么?
A. 程序报错退出。
B. 自动降级为ReAct模式,通过文本解析调用工具。
C. 忽略工具列表,直接回答问题。
D. 自动下载支持function calling的模型。
答案及解析:B。create_react_agent会检测模型能力,自动选择合适模式。
2. 以下哪项是ReAct Agent相比Function Calling Agent的劣势?
A. 需要更多token。
B. 解析依赖模型输出格式,可靠性较低。
C. 不支持并行工具调用。
D. 以上都是。
答案及解析:D。ReAct模式通常需要更多token(因为要输出Thought),解析不可靠,且不支持并行调用。
3.state_modifier参数的作用是什么?
A. 修改工具列表。
B. 设置系统提示词,指导Agent行为。
C. 定义模型温度。
D. 开启记忆功能。
答案及解析:B。state_modifier作为系统消息注入,影响Agent的决策风格。
简答题
4. 请简述在LangGraph中,create_react_agent比手工编写Agent节点(如第23课)的优势。
参考答案:
- 代码量少:无需手动定义
agent_node、tools_node、条件边,减少样板代码。 - 自动模式切换:根据模型能力自动选择ReAct或Function Calling,兼容性强。
- 内置解析器:处理ReAct的文本解析和Function Calling的
tool_calls,避免自己写正则。 - 支持检查点:可选
checkpointer参数轻松集成状态持久化。 - 流式支持:内置
.stream方法,方便调试。
5. 解释为什么在生产环境中更推荐使用Function Calling Agent而非ReAct Agent。
参考答案:Function Calling Agent利用模型原生的结构化输出,避免了文本解析的不确定性和格式错误,可靠性大大提升。此外,它还支持复杂参数类型(嵌套对象、数组)和并行多工具调用,而ReAct通常只能处理简单字符串参数且单步单工具。Function Calling Agent的调试也更容易,因为tool_calls字段是机器可读的JSON。
实践题
6. 扩展本课的Function Calling Agent,添加一个“发送邮件”工具(模拟),要求工具的参数为收件人、主题、正文。然后让Agent发送一封测试邮件。
参考答案:
frompydanticimportBaseModel,FieldclassEmailInput(BaseModel):to:str=Field(description="收件人邮箱")subject:str=Field(description="邮件主题")body:str=Field(description="邮件正文")@tool(args_schema=EmailInput)defsend_email(to:str,subject:str,body:str)->str:"""发送邮件(模拟)"""print(f"[模拟] 发送邮件给{to}:{subject}-{body[:50]}...")returnf"邮件已发送至{to}"tools.append(send_email)# 然后让Agent执行:请发一封主题为 '测试' 的邮件给 admin@example.com,正文为 'Hello Agent'。通过这个练习,你将体会到Function Calling对复杂参数类型的天然支持。
🔗《30节课 LangChain 从入门到精通》系列课程导航
去订阅
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~
