深入LangChain源码:Agent执行器与工具调用机制剖析
深入LangChain源码Agent执行器与工具调用机制剖析引言从黑盒到白盒的认知跃迁如果你曾使用LangChain构建过Agent应用大概率写过这样的代码fromlangchain.agentsimportAgentExecutor,create_openai_tools_agent agent_executorAgentExecutor(agentagent,toolstools)resultagent_executor.invoke({input:北京今天天气怎么样})几行代码一个能自主调用工具的AI Agent就诞生了。但你是否思考过当LLM决定调用工具时代码层面究竟发生了什么LLM返回的是文本还是结构化数据函数是如何被找到并执行的这篇文章将深入LangChain源码从create_agent到tool.invoke拆解Agent执行器与工具调用的完整链路。读完你将彻底理解Agent的“思考-行动”循环是如何实现的以及LangChain 1.0版本带来的架构革命。全文基于LangChain 1.x和LangGraph源码与你可能见过的AgentExecutor旧实现有本质区别。一、从AgentExecutor到LangGraph架构演进的底层逻辑在LangChain v0.1时代Agent由两个核心组件构成Factory Function如create_openai_tools_agent将LLM、Prompt和Tools组装成Agent定义Runtime EngineAgentExecutor负责执行“思考-行动”循环# 旧范式v0.3已废弃fromlangchain.agentsimportAgentExecutor,create_tool_calling_agent agent_executorAgentExecutor(agentagent,toolstools)resultagent_executor.invoke({input:...})致命缺陷AgentExecutor是一个黑盒化的while循环。你无法在工具执行前插入人工审批无法精细控制重试策略流式输出的粒度也极其粗糙。LangChain 1.0的答案将Agent底层全部替换为LangGraph状态图。create_agent本质上是LangGraph的上层封装# 新范式v1.xfromlangchain.agentsimportcreate_agentfromlangchain_openaiimportChatOpenAI agentcreate_agent(modelllm,tools[get_weather])resultagent.invoke({messages:[{role:user,content:北京天气}]})注意输入格式的变化{input: ...}→{messages: [...]}。二、源码拆解从模型节点到工具节点2.1create_agent构建有向状态图# 源码位置langchain/agents/factory.pydefcreate_agent(model,tools,...):# 创建状态图状态里只有一个核心字段messages所有对话历史graphStateGraph(AgentState)# 添加 model 节点调用 LLMgraph.add_node(model,model_node)# 添加 tools 节点用 LangGraph 内置的 ToolNode 封装所有工具tool_nodeToolNode(tools)graph.add_node(tools,tool_node)# 入口先调用 modelgraph.add_edge(START,model)# 条件边model → tools有 tool_calls 时或 model → END无 tool_calls 时graph.add_conditional_edges(model,_make_model_to_tools_edge(...),{tools:tools,END:END})# tools → model工具执行完回到 LLMgraph.add_edge(tools,model)returngraph.compile()核心逻辑图只有两个节点在model和tools之间来回跳转model节点调用LLM传入工具描述JSON schemaLLM根据对话历史决定是直接回答还是调用工具tools节点拿到LLM返回的tool_calls找到对应函数并执行将结果包装成ToolMessage退出条件当LLM的输出中没有tool_calls时图终止。2.2 model节点tool_calls从何而来这是最关键的认知突破。当调用llm.invoke(messages, tools[...])时tools参数直接传给OpenAI API。API在响应中直接返回结构化的tool_calls字段而非从文本中解析。# LLM 返回的结构AIMessage(content,tool_calls[{id:call_abc123,name:get_weather,args:{city:北京}# 已经是 dict不需要 JSON 解析}])关键结论tool_calls是API原生返回的结构化数据LangChain只做包装。因此不存在“从文本解析JSON”的性能开销和幻觉风险。2.3 tools节点函数分发与执行当图路由到tools节点时ToolNode执行这个逻辑classToolNode:def_execute_tool_sync(self,request,config,...):callrequest.tool_call# 1. 根据函数名查找工具字典查找非反射toolself.tools_by_name[call[name]]# 2. 调用 tool.invoke 执行responsetool.invoke(call_args,config)# 3. 包装成 ToolMessagereturnToolMessage(contentstr(response),tool_call_idcall[id],namecall[name],statussuccess,)关键设计tools_by_name是{函数名: 工具实例}字典。tool装饰器在定义时就把函数包装成StructuredTool对象引用直接存在self._run里。执行时是字典查找 普通方法调用没有反射操作。2.4tool.invoke从ToolCall到真实函数# 源码位置langchain_core/tools/base.pydefinvoke(input,configNone,**kwargs):# 把 ToolCall 拆解成参数tool_input,run_kwargs_prep_run_args(input,config)# if input[type] tool_call:# tool_input input[args].copy() # {city: 北京}returnself.run(tool_input,**run_kwargs)# → self._run(city北京) # 最终调用原函数调用链路invoke→run→_to_args_and_kwargs把dict拆成参数→_run执行原函数。2.5 AgentState图的状态管理classAgentState(TypedDict):messages:Annotated[list[BaseMessage],add_messages]messages是唯一核心字段用add_messages这个reducer函数将新消息合并到已有列表中。每轮对话累积图引擎自动将完整历史传给下一轮LLM。三、执行流程全景图无有用户输入model节点LLM输出是否有tool_calls?返回最终答案tools节点ToolNode查字典执行tool.invoke包装ToolMessage每一轮messages累积 [HumanMessage, AIMessage(tool_calls), ToolMessage] → LLM看到工具已执行 → 输出最终回答 → 无tool_calls → 终止。四、常见误区澄清误区1LLM返回的是文本LangChain从中解析JSON正解tool_calls是OpenAI API直接返回的结构化字段。tools参数传给APIAPI响应里自带结构化数据不是从文本解析的。误区2函数调用靠反射正解tool装饰器在定义时就把函数包装成StructuredTool对象存在字典里。执行是字典查找直接调用没有getattr等反射操作。误区3Agent会无限循环正解LangGraph内置recursion_limit默认约1000次迭代。正常情况下LLM一两次就能完成远不到上限。五、Agent架构演进的工程启示LangChain Agent架构的演进本质上是从“黑盒循环”向“显式状态图”的迁移。特性旧范式AgentExecutor新范式LangGraph控制流隐式while循环显式StateGraphPrompt结构依赖agent_scratchpad魔术变量标准消息列表流式能力弱仅Step级别强Token级 消息级状态控制不可见显式State字典完全可控工具调用依赖OutputParser解析文本原生bind_toolsAPI迁移建议简单工具调用直接用llm.bind_tools() LCEL无需Agent复杂Agent立即迁移到LangGraphAgentExecutor已是Legacy六、总结源码阅读的三个核心认知读完本文你应该建立三个核心认知tool_calls是API原生结构——不是解析出来的是OpenAI API直接返回的函数分发靠字典查找——不是反射是tools_by_name[name]直接调用LangGraph取代了AgentExecutor——Agent现在是一张有向状态图不是while循环当你下次调用agent.invoke()时你将看到的不再是魔法而是一个清晰的图引擎在model和tools节点之间优雅地流转数据。参考文献LangChain Agent源码深度解析与实战LangChain源码解析Function Call是如何被执行的LangChain Agent 架构演进深度解析从AgentExecutor到LangGraphLangChain官方文档 — Tool calling深入探索LangChain Agent源码机制