第 6 章 Agent 主循环

第 6 章 Agent 主循环

本章要解决的问题

Agent 的核心不是模型而是循环------思考、行动、观察。这个循环怎么设计才不会死循环、不浪费 token?

章节大纲

  • 6.1 Agent Loop 架构:Think → Act → Observe
  • 6.2 规划器 Planner 与执行器 Executor 的协作
  • 6.3 停止条件、最大步数与防死循环
  • 6.4 可观测性:日志、追踪(Langfuse/OpenTelemetry)
  • 🛠 解决方案:Agent 卡死/循环/乱步骤的 6 大根因与修复

6.1 Agent Loop 架构:Think → Act → Observe

6.1.1 循环是 Agent 的心脏

第 1 章说过:Agent = LLM + 工具 + 循环。前两章讲了模型和工具,本章讲循环------把两者串起来的引擎。

图 1:Agent Loop 循环

markdown 复制代码
        ┌──────────────────────────────────────────┐
        │                                          │
        ▼                                          │
   ┌─────────┐    ┌─────────┐    ┌─────────┐       │
   │ Think   │───►│ Act     │───►│ Observe │───────┘
   │ 思考     │    │ 行动     │    │ 观察     │
   │ 决定下一步│    │ 调用工具  │    │ 读取结果  │
   └─────────┘    └─────────┘    └─────────┘
        │
        ▼ (判断:任务完成了吗?)
     回答用户 / 继续循环
  • Think(思考):模型看当前上下文,决定"直接回答"还是"调用某个工具"。
  • Act(行动):执行工具调用(查询、计算、操作)。
  • Observe(观察):把工具结果放回上下文。
  • 再 Think:基于新信息继续决策,直到任务完成。

6.1.2 最小循环实现

python 复制代码
# 示例代码:演示最小 Agent 循环,省略了 API 异常重试和详细日志
import json, time
from openai import OpenAI

client = OpenAI(base_url="https://api.deepseek.com", api_key="<你的Key>")

def execute_tool(call):
    """调用第 5 章的 safe_call_tool 执行工具,返回 JSON 字符串结果。"""
    from ch05_tools import safe_call_tool  # 第 5 章定义的工具执行函数
    name = call.function.name
    arguments = call.function.arguments
    result = safe_call_tool(name, arguments)
    return json.dumps(result, ensure_ascii=False)

def agent_loop(user_query, tools, max_steps=5, timeout=120):
    messages = [{"role": "user", "content": user_query}]
    start = time.time()
    for step_no in range(max_steps):               # 步数上限
        if time.time() - start > timeout:          # 超时上限
            return "超时,返回当前结果"
        # Think:模型决策(回答 or 调工具)
        resp = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages,
            tools=tools,
            tool_choice="auto",
            temperature=0.2,
        )
        msg = resp.choices[0].message
        messages.append(msg)   # 保留模型决策(含 tool_calls)

        # 判断:模型没有继续工具调用 → 认为任务完成或进入澄清/结束
        if not msg.tool_calls:
            return msg.content

        # Act + Observe:执行每个工具调用并回填结果
        for call in msg.tool_calls:
            result = execute_tool(call)          # 第5章的安全执行
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": result,               # 观察结果
            })
        # 回到循环顶部 → 再 Think
    return "已达最大步数,返回当前结果"

这是全书最核心的一段代码------第 10-18 章的 9 大设计模式,本质都是在"怎么组织这个循环"上做文章(提示链是固定的循环、规划是带计划的循环、反思是带检查的循环......)。

6.1.3 循环的两种推进风格

风格 特点 适用
串行单步(本章) 一次一个决策,上下文累积 默认,最稳
并行多步 一次多个工具调用(parallel_tool_calls) 子任务独立时(第 12 章)

串行单步是基线,并行是优化------先跑通串行,再按需并行。

6.2 规划器 Planner 与执行器 Executor 的协作

6.2.1 两种循环形态:单循环 vs 两段式

形态 结构 特点 呼应
单循环(ReAct 式) 思考+行动交错,无独立计划 灵活、探索性强 第 15 章 ReAct
两段式(Plan-and-Execute) 先 Planner 出计划,再 Executor 执行 有计划清单、不易迷路 第 15 章 PnE

图 2:Planner-Executor 两段式

6.2.2 Planner 与 Executor 的分工

diff 复制代码
Planner(规划器)                  Executor(执行器)
- 看目标 + 上下文                  - 按计划逐项执行
- 生成任务清单(带依赖)            - 每步调用工具
- 任务完成/偏离时重规划             - 汇报每步结果
python 复制代码
def two_phase_agent(goal, max_steps=10):
    # 阶段一:Planner 出计划
    plan = planner.generate_plan(goal)         # 第15章的 JSON 计划
    results = []
    # 阶段二:Executor 执行
    for step in plan["steps"]:
        result = executor.execute_step(step)   # 单步 = 一次工具调用
        results.append(result)
        if not check_on_track(goal, plan, results):
            plan = planner.replan(goal, plan, results)  # 偏离 → 重规划
    return synthesize(goal, results)

什么时候用哪种:任务路径可预知 → 两段式(省、稳);任务需要探索 → 单循环(灵活)。混合式(计划外带探索)是生产最优(第 15 章 15.3 详述)。

6.3 停止条件、最大步数与防死循环

6.3.1 循环必须回答"何时停"

没有停止条件的循环 = 无限烧钱的定时炸弹。 三个停止条件,组合使用:

图 3:循环三停止条件

停止条件 说明 优先级
任务完成 模型判断无需再调工具(tool_calls 为空),或进入澄清/结束状态 最高(正常退出)
用户终止 用户显式取消/终止任务 高(外部干预)
最大步数 步数上限(max_steps) 兜底(防失控)
超时 总时长上限 兜底(防慢拖)
python 复制代码
import time

def agent_loop(user_query, max_steps=5, timeout=120):
    start = time.time()
    for step_no in range(max_steps):           # 步数上限
        if time.time() - start > timeout:      # 超时上限
            return "超时,返回当前结果"
        if user_cancelled():                   # 用户终止(外部信号)
            return "用户已取消任务"
        msg = think(user_query)                # 单步执行:思考/行动/观察
        if not msg.tool_calls:                 # 任务完成或进入澄清
            return msg.content
    return "达最大步数"                          # 步数兜底

6.3.2 防死循环的三类场景与对策

死循环形态 表现 对策
重复调用同一工具 同一个调用反复出现 调用历史去重(相同签名不重复执行)
无效往返 工具结果不变,模型仍继续 "无进展检测":连续 N 步结果无变化 → 终止
左右摇摆 计划 A ↔ 计划 B 反复切换 重规划次数上限 + 历史计划去重

图 4:防死循环三对策

python 复制代码
def no_progress_detector(history, threshold=3):
    """连续 N 步没有新信息 → 判定无进展"""
    if len(history) < threshold:
        return False
    recent = [h["result"] for h in history[-threshold:]]
    return len(set(recent)) == 1   # 结果完全相同 → 无进展

6.3.3 工具失败后的恢复策略

工具调用不是"调一次就成功"------网络超时、参数错误、服务不可用都可能发生。需要分层恢复:

失败次数 策略 说明
1 次 重试 指数退避(1s/2s/4s)
2~3 次 降级 换备用工具或返回"暂时无法处理"
连续 5 次 熔断 停用该工具,转人工或终止任务
python 复制代码
tool_fail_counts = {}  # 工具名 → 连续失败次数

def handle_tool_failure(tool_name, error):
    tool_fail_counts[tool_name] = tool_fail_counts.get(tool_name, 0) + 1
    if tool_fail_counts[tool_name] >= 5:
        return f"工具 {tool_name} 连续失败已熔断,请转人工处理"
    return f"工具 {tool_name} 执行失败:{error},将重试或降级"

6.3.4 成本护栏:循环的 token 预算

循环每步都调用模型,成本随步数线性增长。两层护栏:

  1. 步数硬上限(max_steps 5~20 按任务复杂度)。
  2. token 预算上限(累计超限强制终止,呼应第 24 章 G5 预算封顶)。

6.4 可观测性:日志、追踪

6.4.1 为什么循环必须可观测

Agent 循环是"多步、有状态、可能出错"的过程------没有观测,出了问题是黑盒,无法定位是哪一步出错(呼应第 21 章排错)。生产级 Agent 的标配:关键步骤都留痕。

6.4.2 每步留痕的数据结构

python 复制代码
def log_step(step_no, phase, model_input, model_output, tool_result, cost):
    return {
        "step": step_no,
        "phase": phase,          # think / act / observe
        "input_tokens": ..., "output_tokens": ...,
        "cost_usd": ...,
        "model": "deepseek-chat",
        "latency_ms": ...,
        "trace_id": current_trace_id,
    }

6.4.3 追踪工具选型

工具 特点 适用
Langfuse LLM 专用追踪/评测/成本面板,开源可自部署 推荐首选
LangSmith LangChain 生态,功能全 已用 LangChain
OpenTelemetry 通用可观测标准 与现有监控体系统一
自研日志 零依赖,但缺面板 快速起步

最小起步:先自研 JSON 日志(每步一条),跑起来后再接 Langfuse------日志先行,面板后补。

🛠 解决方案:Agent 卡死/循环/乱步骤的 6 大根因与修复

常见问题

  1. "Agent 卡死不动":工具超时未处理,循环阻塞。对策:单工具超时 + 超时回填错误信息(第 5 章)。
  2. "死循环烧钱":无停止条件/无进展检测。对策:三停止条件 + 无进展检测(6.3.2)。
  3. "乱步骤(先做后置步骤)":无计划约束,单循环自由发挥。对策:换两段式(Planner 先出计划,第 15 章)。
  4. "上下文越滚越长,最后爆窗":每步结果全量累积。对策:历史裁剪/摘要(第 3 章)+ 工具结果精简回填。
  5. "步数不够任务没做完":max_steps 设小。对策:按任务复杂度调大(第 24 章表四 3~20),但要先查根因------频繁"步数不够"说明任务拆解有问题。
  6. "线上出问题定位不到":无观测。对策:每步留痕 + 追踪(6.4),trace 到每次调用。

解决方案速查表

现象 根因 解决方案
卡死不动 工具超时 超时分层 + 错误回填
死循环 无停止条件 三条件 + 无进展检测
乱步骤 无计划 两段式 Planner
上下文爆 结果全量累积 裁剪 + 精简回填
步数不够 任务拆解差 查根因 + 调步数
定位不到 无观测 每步留痕 + 追踪

实战提示

  1. 先想清楚停止条件再写循环:任务完成判定、最大步数、超时,缺一不可。
  2. 无进展检测是省钱利器:连续几步结果没变化就停,避免无效往返。
  3. 每步留痕是标配:trace 到每次调用,出问题 5 分钟定位。
  4. 单循环先跑通,再优化:并行、两段式都是优化项,基线是串行单循环。
  5. 循环的每一步都要过第 5 章的安全执行:工具调用应始终走注册表 + 校验。
相关推荐
Csvn1 小时前
第 5 章 工具调用
人工智能·aigc·agent
clorinda1 小时前
OpenCV实战学习记录:图像拼接与答题卡识别
人工智能·opencv·学习
腾视科技-AIoT1 小时前
腾视科技AIBOX双版本重磅发布!本地安全与全球适配,解锁视频智能新可能
大数据·人工智能·科技·ai·物理ai·ainas·腾视科技
zcmodeltech1 小时前
煤化工沙盘模型控制系统设计与实现:多工段协同联动方案
网络·人工智能·stm32·嵌入式硬件·制造·多分类
CTA终结者1 小时前
2026年程序员量化开发学习:用示例、拆解和练习入门
人工智能·python
李剑一1 小时前
连名词都不会,你AI个Der!学会AI基础之:到底模型是什么玩意儿?都说大模型,有小模型嘛?训练数据多它就是大模型吗?
aigc·openai·ai编程
小酒星小杜1 小时前
如何简单地创建你的第一部漫画?从一个“可见变化”开始
人工智能·python·产品
掘金酱1 小时前
TRAE Work 实战帮征文 | 获奖名单公示
前端·人工智能·后端
sel_91 小时前
【OPD论文导读(二)】OPD(On-Policy Distillation)全景调研:十篇论文讲透“在自己生成的内容上学习“这件事
人工智能·python·深度学习·学习·算法·语言模型