Skip to content

Agent 的结构分解

Agent 不是一个神秘概念

"AI Agent"这个词承载了太多想象。自主决策、目标驱动、环境感知、持续学习——这些描述让 Agent 听起来离通用人工智能只有一步之遥。但在当前的工程实践中,一个 LLM Agent 的结构可以被精确分解为三个组件的组合:

LLM + 工具调用 + 循环控制。

LLM 负责理解任务和决定下一步行动。工具调用将 LLM 的决策转化为对外部系统的操作。循环控制决定何时继续、何时停止。去掉任何一个组件,就不再是通常意义上的 Agent:没有工具调用的 LLM 只是一个聊天机器人;没有循环控制的 LLM + 工具只是一次性的函数调用;没有 LLM 的工具 + 循环是传统的脚本自动化。

python
def minimal_agent(task: str, tools: dict, max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": task}]

    for step in range(max_steps):
        # LLM 决策:下一步做什么
        response = call_llm(messages, available_tools=tools)

        if response.is_final_answer:
            return response.content

        # 工具调用:执行 LLM 的决策
        tool_result = tools[response.tool_name](**response.tool_args)

        # 循环控制:将结果反馈给 LLM,进入下一轮
        messages.append({"role": "assistant", "content": response.raw})
        messages.append({"role": "tool", "content": str(tool_result)})

    return "达到最大步数限制"

这个最小 Agent 实现不到 20 行。理解这个最小结构的价值在于:当你需要构建 Agent 时,从这个结构出发逐步添加复杂性,而不是从一个重量级框架出发试图理解它做了什么。

工具绑定的设计原则

工具是 Agent 与外部世界的接口。工具设计的质量直接决定了 Agent 的能力边界和可靠性。

工具应该是原子性的。 一个工具做一件事。"搜索并总结"不是一个好工具——应该拆成"搜索"和"总结"两个工具,由 Agent 决定是否以及如何组合。原子性工具给 Agent 更大的组合灵活性,同时让每个工具的行为更可预测。

工具的输入输出应该有明确的类型定义。 工具的参数不应该是 **kwargs 或自由格式的字符串。Pydantic 模型同样适用于工具参数的定义——它为 LLM 提供了结构化的参数说明,同时在运行时验证 LLM 生成的参数值。

python
from pydantic import BaseModel, Field

class SearchParams(BaseModel):
    query: str = Field(description="搜索关键词")
    max_results: int = Field(description="最大返回结果数", ge=1, le=20, default=5)
    date_range: str | None = Field(
        description="日期范围过滤,格式为 YYYY-MM-DD:YYYY-MM-DD",
        default=None
    )

工具应该是幂等的或至少是安全的。 Agent 会循环执行,同一个工具可能被多次调用。如果工具有副作用(发送邮件、创建订单、删除数据),重复调用可能造成问题。幂等设计(重复调用产生相同结果)是首选;如果无法幂等,至少应该在工具层面实现去重或确认机制。

终止条件:最被忽视的设计决策

Agent 的循环何时停止?这个问题看似简单,实际上是 Agent 设计中最容易出错的部分。

LLM 自行决定终止。 最常见的方案:LLM 判断任务已完成,返回最终答案而非工具调用。问题是 LLM 可能过早终止(任务未完成就给出答案)或永不终止(陷入工具调用的循环)。

最大步数限制。 硬性上限,防止无限循环。但固定的步数限制是粗糙的——简单任务可能 2 步就够,复杂任务可能需要 20 步。过低的限制截断复杂任务,过高的限制浪费资源。

成本预算。 累计 token 消耗达到预设阈值时强制终止。比步数限制更精确,因为不同步骤的 token 消耗差异很大。

超时机制。 墙钟时间超过阈值时终止。对面向用户的应用特别重要——用户不会等待一个 Agent 运行 5 分钟。

实践中,这些终止条件通常需要组合使用。一个健壮的 Agent 应该有至少三层终止保护:LLM 的自主判断(正常路径)、步数/成本限制(安全网)、超时机制(最后防线)。

状态机视角

Agent 的行为可以用状态机形式化。每个状态代表 Agent 的一个决策点,每个转移代表一次工具调用或 LLM 推理。

[初始化] → [LLM推理] → [选择工具] → [执行工具] → [LLM推理] → ... → [输出结果]
                ↑                              |
                └──────────────────────────────┘

用状态机视角看 Agent,好处是:

可观测性。 每次状态转移都是一个可记录的事件。通过记录状态序列,可以回溯 Agent 的完整决策过程——它调用了哪些工具、按什么顺序、每步的输入输出是什么。

可测试性。 可以为特定的状态转移编写测试:给定某个中间状态,Agent 是否选择了合理的下一步?这比测试端到端行为更精确、更可控。

错误恢复。 如果某次工具调用失败,状态机可以回退到上一个决策点,让 LLM 选择替代方案。这比简单的重试更智能——重试假设同样的操作会成功,回退允许 LLM 换一条路径。

何时不需要 Agent

Agent 的循环控制会放大不确定性——LLM 的每次决策都是概率性的,多次决策的组合让系统行为更加难以预测。以下场景用更简单的架构就行:

任务步骤固定。 如果处理流程是预先确定的(先检索、再分析、最后总结),不需要 Agent——一个线性的 pipeline 更可靠、更快速、更易调试。Agent 的价值在于动态决策,固定步骤不需要动态决策。

没有外部工具交互。 如果任务只涉及文本处理(总结、翻译、分类),不需要 Agent——单次 LLM 调用或简单的 chain 就够了。

可靠性要求极高。 Agent 的多步决策意味着每一步都可能出错,错误会累积。如果业务要求 99.9% 的成功率,Agent 架构可能无法满足——每步 99% 的成功率,经过 5 步后只剩 95%。

选择 Agent 架构还是简单 pipeline,是架构层面的战略决策,应该在写代码之前定下来。