Agent 不只是套个循环,而是模型、工具与环境之间的动态博弈

项目目标

在完成模型训练、推理优化和 RAG 之后,LLM 仍然是一个相对孤立的系统。它具备强大的文本处理能力,但无法直接与外部世界交互。项目 30 的目标是打破这个边界。

我们将构建一个 Agent 循环(Agent Loop),使模型能够根据用户的需求,自主决定调用哪些工具,观察执行结果,并在失败时尝试修正。这个过程将涵盖从工具定义、Prompt 工程到状态管理的全栈细节。

任务 1:工具定义与 Schema 抽象

模型无法直接运行 Python 函数或访问 API,它只能生成表示调用意图的文本。因此,Agent 的第一步是把本地能力翻译成模型能理解的结构化描述(Schema)。

一个健壮的工具定义应包含:

  1. 唯一标识符(Name):简洁且具描述性。
  2. 详尽的功能描述(Description):这是模型判断“何时调用”的关键,类似于函数的文档字符串。
  3. 参数定义(Parameters):通常采用 JSON Schema 格式,明确类型、默认值和约束条件。
def calculate_real_estate_tax(price: float, city: str):
    """计算特定城市的房产税。"""
    # 模拟业务逻辑
    rates = {"shanghai": 0.01, "beijing": 0.012}
    rate = rates.get(city.lower(), 0.008)
    return price * rate

# 转换为模型可消费的格式
tools_schema = [
    {
        "type": "function",
        "function": {
            "name": "calculate_real_estate_tax",
            "description": "根据房价和城市名计算房产税额度",
            "parameters": {
                "type": "object",
                "properties": {
                    "price": {"type": "number", "description": "房屋总价"},
                    "city": {"type": "string", "description": "城市名称,如上海、北京"}
                },
                "required": ["price", "city"]
            }
        }
    }
]

任务 2:实现 ReAct 模式(Thought-Action-Observation)

目前主流的 Agent 模式是 ReAct(Reasoning and Acting)。它要求模型在采取行动前先进行“思考”(Thought),然后生成“行动”(Action),执行后获取“观察”(Observation)。

我们需要设计一个 System Prompt 来约束模型输出特定的格式。这种格式必须足够稳健,便于正则表达式或 JSON 解析器提取。

System Prompt 示例:

你是一个具备工具调用能力的助手。
对于每个用户请求,你必须遵循以下严格的循环格式:

Thought: 思考当前需要做什么,分析还缺少哪些信息。
Action: 调用工具,格式必须为 {"name": "tool_name", "args": {"key": "value"}}。
Observation: 这里会填入系统反馈的工具执行结果。
... (重复上述过程,直到获得最终答案)
Final Answer: 输出给用户的最终结论。

禁止在 Final Answer 之前直接给出结论。

任务 3:构建 Agent 状态机循环

这是项目的核心代码实现。一个完整的 Agent 循环需要处理三个核心逻辑:上下文拼接、异常反馈与终止条件。

class MinimalAgent:
    def __init__(self, llm_client, tools_map, max_steps=5):
        self.llm = llm_client
        self.tools = tools_map  # dict: {name: func}
        self.max_steps = max_steps

    def run(self, query):
        messages = [{"role": "system", "content": SYSTEM_PROMPT}]
        messages.append({"role": "user", "content": query})
        
        for step in range(self.max_steps):
            # 1. 产生 Thought + Action
            response = self.llm.generate(messages)
            messages.append({"role": "assistant", "content": response})
            
            # 2. 解析并执行 Action
            action = self.extract_action(response)
            if not action:
                # 检查是否包含 Final Answer
                if "Final Answer:" in response:
                    return response.split("Final Answer:")[-1]
                continue
                
            # 3. 执行本地工具并捕获异常
            try:
                result = self.tools[action['name']](**action['args'])
                obs_content = f"Observation: {result}"
            except Exception as e:
                # 将错误反馈给模型,让其修正参数
                obs_content = f"Observation: Error executing tool: {str(e)}"
            
            messages.append({"role": "user", "content": obs_content})
            
        return "Failed to complete task within max steps."

任务 4:观测与分析(Plotting)

Agent 系统具有高度的不确定性,因此必须进行定量观测。我们需要记录以下指标并绘制分析图表:

  1. 任务成功率与步数分布:记录不同复杂度的任务平均需要多少步完成。如果大部分任务都在第 5 步强制终止,说明模型陷入了死循环。
  2. 工具调用准确率:统计模型生成的 JSON 格式错误率。
  3. 推理开销分析:统计每一轮循环中 Thought 占用的 token 比例。过长的思考可能意味着模型在徘徊。
  4. Token 消耗增长曲线:由于每一步都要携带历史上下文,Agent 的成本是随步数平方级(或至少是累进地)增长的。

故意破坏实验

实验是理解 Agent 脆弱性的最好方式:

  • 实验 A:描述冲突。定义两个工具,一个是 get_weather,另一个是 get_forecast,且描述非常接近。观察模型是否会在选择工具时产生震荡。
  • 实验 B:强制错误反馈。在 Observation 中手动输入 Permission DeniedInvalid Argument。观察模型是会选择放弃,还是尝试变换参数重新调用,或是直接向用户撒谎说“执行成功”。
  • 实验 C:长序列压力测试。设计一个需要调用 10 次工具的任务。观察模型是否在第 7 或第 8 步开始遗忘最初的 User Prompt 目标。

为什么 Agent 会失败(Explain)

通过实验,我们需要总结出 Agent 失败的三大核心模式:

  1. 推理环路(Reasoning Loop):模型认为需要调用 A 才能解决问题,但 A 返回的结果不符合预期,模型在下一轮 Thought 中仍然认为需要调用 A,从而形成死循环。
  2. 幻觉调用(Hallucinated Tools):模型尝试调用一个在 Schema 中不存在,但其预训练知识中存在的函数(如猜测系统中应该有 search_google)。
  3. 格式坍塌(Format Collapse):在长对话中,由于 Observation 的干扰,助手角色开始模仿 User 的语气,或者不再输出 Thought: 前缀,导致解析器失效。

本项目交付物

  • 一个支持多步 ReAct 循环、具备基本错误恢复能力的 Python Agent 脚本。
  • 一组针对特定任务(如:查询三座城市的税率并计算总额)的完整 Trace 日志。
  • 针对不同底座模型(如 Qwen2.5-7B 与 Llama-3.1-8B)在工具调用准确率上的对比图表。
  • 一份关于“如何通过 Prompt 抑制 Agent 幻觉”的实验报告。

延伸阅读

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:项目 30:构建 Tool Use 与 Agent Loop

本文链接:https://www.sshipanoo.com/blog/ai/llm-roadmap/lab-30-agent-loop/

本文最后一次更新为 天前,文章中的某些内容可能已过时!