# AgentExecutor 源码解析与 Agent 组件缺陷

# 01. AgentExecutor 源码解析

在 LangChain 中,无论是什么类型的 Agent(内置封装),都必须通过 AgentExecutor 来创建执行者才可以运行具有 循环 + 工具执行 的智能体,在 智能体执行者 的底层,实际操作是调用 Agent 智能体,执行它选择的操作/工具,将操作输出传递回 Agent,然后重复,伪代码如下:

next_action = agent.get_action(...)
while next_action != AgentFinish:
    observation = run(next_action)
    next_action = agent.get_action(..., next_action, observation)
return next_action
1
2
3
4
5

简易后的运行流程如下:

图片描述

其核心代码是 AgentExecutor 中的 _call() 函数,核心代码如下:

def _call(
    self,
    inputs: Dict[str, str],
    run_manager: Optional[CallbackManagerForChainRun] = None,
) -> Dict[str, Any]:
    
    name_to_tool_map = {tool.name: tool for tool in self.tools}
    color_mapping = get_color_mapping(
        [tool.name for tool in self.tools], excluded_colors=["green", "red"]
    )
    
    intermediate_steps: List[Tuple[AgentAction, str]] = []
    
    iterations = 0
    time_elapsed = 0.0
    start_time = time.time()
    
    while self._should_continue(iterations, time_elapsed):
        
        next_step_output = self._take_next_step(
            name_to_tool_map,
            color_mapping,
            inputs,
            intermediate_steps,
            run_manager=run_manager,
        )
        
        if isinstance(next_step_output, AgentFinish):
            return self._return(
                next_step_output, intermediate_steps, run_manager=run_manager
            )
		
        
        intermediate_steps.extend(next_step_output)
        
        if len(next_step_output) == 1:
            next_step_action = next_step_output[0]
            
            tool_return = self._get_tool_return(next_step_action)
            if tool_return is not None:
                return self._return(
                    tool_return, intermediate_steps, run_manager=run_manager
                )
        
        iterations += 1
        time_elapsed = time.time() - start_time
    
    output = self.agent.return_stopped_response(
        self.early_stopping_method, intermediate_steps, **inputs
    )
    return self._return(output, intermediate_steps, run_manager=run_manager)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 02. 传统 Agent 组件的缺陷

LangChain 封装的 Agent 和 Agent执行者 虽然解决了 LCEL 表达式创建的单链应用没法执行 循环步骤 的问题,对于一些简单类型的 Agent 智能体创建已经足够使用,但是仍然存在不少缺陷。

假设我们要创建一个 Agent 应用,该 Agent 可以处理数学和物理问题,并由两个不同的 LLM 负责不同的模块,根据用户的提问使用不同的 LLM + 工具列表 + 线路来回答用户的问题,传统的 Agent 就无能为力了。

其实除了上述的问题,传统 Agent 还存在不少缺陷,如下:

  • 只有 循环步骤 并没有 条件步骤 ,一个 Agent 应用只能一条路走到黑,不能执行不同的路由;

  • 没法亦或者很难将多个 Agent 融合起来相互协作;

  • 因对 Prompt 与输出解析器的过度封装,导致要修改 Agent 内部的方案变得异常困难;

  • 无论是 Agent 还是 AgentExecutor,因其 黑盒机制 ,无法在执行的过程中进行额外的干预;

  • 想对 Agent 进行扩展或者动态切换 LLM 难度非常大,例如 添加记忆 、 切换LLM 等;

这也是 LangChain 旧版本被人诟病的原因,包括 2023 年底在 LLM 领域很火的一篇文章《 为什么我放弃了 LangChain? 》,被传播了一轮又一轮,链接: https://www.jiqizhixin.com/articles/2024-06-24-10,大家一直在放弃和使用中挣扎。

这是因为在 LangChain 设计的早期(v0.1版本之前),早期的传统链(非LCEL表达式)、传统 Agent 智能体的过度封装,并且层层抽象,让代码变得非常复杂(就像一个组件同时拥有 N 种调用方法一样)。

不过这个弊端随着 LCEL 表达式和今年年初的 LangGraph 发布,普遍都被解决了,更易用与统一的接口(LCEL 表达式统一 invoke、stream、batch 接口),控制性更高、支持循环/逻辑的 LangGraph 图结构程序、实时监控 LLM 的 LangSmith 平台,让 LLM/Agent 应用的开发变得超级简单,甚至串联数百个节点、上百种工具与逻辑路线的 Agent 工作流也成为了现实。

但是对于 传统Agent组件 来讲,我们还是需要去掌握,因为在复杂度较小的情况下,该思路还是非常值得借鉴的,但对于一些复杂应用就无能为力了,下一章我们会来掌握 LangChain 的最后一块拼图—— LangGraph ,掌握利用 LangGraph 构建复杂应用的技巧及底层运行原理,并且 在 LLMOps 项目中,将 LangGraph 与知识库、工具、审核、多用户/多应用、多模态输入输出等功能结合起来 ,在实战中感受 LangGraph + LECL 表达式带来的酣畅淋漓的体验。

# 最新版 LangChain 用法提示

  • 新版 LangChain 更推荐用 LCEL、Runnable、ChatModel.bind_tools()、结构化输出和 LangGraph 来组织复杂链路;老式 Chain、部分 AgentExecutor 写法可以读懂,但新项目应优先选择更清晰的图或 Runnable 编排。

  • 工具调用相关代码要区分两层:模型是否原生支持 tool/function calling,以及业务侧如何定义工具 schema、参数校验、错误兜底和观测日志。

  • 如果示例中的导入路径和你当前安装版本不同,优先查当前版本包内导出位置;常见迁移方向是从 langchain 拆到 langchain-core、langchain-community、langchain-openai、langgraph 等包。

# 拓展

  • 工具或插件不要只看能不能调通,更要看是否可观测、可限流、可重试、可审计。联网类工具还要处理超时、空结果、搜索噪声和结果时效性。

  • Agent 场景里,Prompt 只是调度策略的一部分;工具描述、参数 schema、历史状态、错误反馈、停止条件和人工介入点同样会影响最终稳定性。

# 常见问题

  • 为什么模型没有调用工具?常见原因是工具描述不清晰、参数 schema 过宽或过窄、用户问题不需要工具、模型本身不支持工具调用,或者工具绑定位置不对。

  • 为什么工具调用后回答仍然不准?先看工具返回是否正确,再看工具结果是否被放回模型上下文,最后检查输出解析、历史消息和异常兜底是否覆盖了真实错误。

# 面试题

  • 解释函数调用、工具调用和 Agent 的区别。

  • LangChain 中 tool schema 的作用是什么?为什么参数校验对生产环境很重要?

  • ReACT Agent 和 tool-calling Agent 的核心差异是什么?分别适合什么场景?

  • LangGraph 相比 LCEL 更适合解决哪些复杂编排问题?

# 生产问题排查

问题 常见原因 处理方式
工具没有被调用 工具描述弱、绑定失败、模型不支持 打印绑定后的模型配置,补充工具描述,换用支持工具调用的模型
参数格式错误 schema 设计不清晰,模型生成字段不稳定 使用 Pydantic/JSON Schema 校验,失败后把错误反馈给模型重试
联网结果不可用 搜索为空、接口超时、命中低质量页面 增加超时、重试、结果过滤、来源白名单和降级回答
Agent 循环不停止 缺少终止条件或工具返回被误判 设置最大迭代次数,记录每轮 thought/action/observation,增加停止规则
线上难以复现 缺少输入、工具请求和模型响应日志 给每次调用加 trace id,记录工具入参、出参、耗时和异常