RSS

第 5 章 / 共 21 章

计划可见性:让长任务不跑偏

约 7 分钟 更新于

上一章留下的问题:现在这个 Agent 能用工具、有护栏、可扩展。给它一个十步的重构任务试试——前三步干得漂亮,第四步遇到一个报错,它开始排查,排查中又发现另一个问题,顺手改了,然后……然后它忘了原来要干什么。最后给你一份”我修好了那个报错”的总结,而十步里还有六步没动。

原因不神秘:随着工具输出不断堆进 messages,最初那句任务描述在整个上下文里的占比越来越小。目标被自己的执行过程淹没了。

5.1 一个什么也不做的工具

解法有点反直觉:加一个不产生任何实际效果的工具,让模型把计划写下来。

CURRENT_TODOS = []

def run_todo_write(todos: list) -> str:
    global CURRENT_TODOS
    CURRENT_TODOS = todos
    lines = ["\n## 当前任务清单"]
    for t in CURRENT_TODOS:
        icon = {"pending": " ", "in_progress": "▸", "completed": "✓"}[t["status"]]
        lines.append(f"  [{icon}] {t['content']}")
    print("\n".join(lines))
    return f"已更新 {len(CURRENT_TODOS)} 条任务"

工具定义里给 status 加上枚举约束,防止模型自创状态:

{
    "name": "todo_write",
    "description": "写下或更新当前的任务清单。开始一个复杂任务前先调用它列出所有步骤,"
                   "每完成一步就更新状态。这是保持工作可见、不遗漏步骤的方式。",
    "input_schema": {
        "type": "object",
        "properties": {
            "todos": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "content": {"type": "string"},
                        "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
                    },
                    "required": ["content", "status"],
                },
            }
        },
        "required": ["todos"],
    },
}

它不读文件、不跑命令、不改任何东西。它唯一的作用是:把计划写进对话历史,让计划和执行过程一起被模型看见。

这就是 Agent 工程里一个反复出现的模式:增加”可见性”,往往比增加”能力”更能提升成功率。

第5章:写计划—执行—更新的闭环
图 5.1:计划驱动的执行闭环。要看的是中心那个”清单”——它每一轮都被重新写进上下文,所以永远不会被工具输出稀释掉;这才是它能对抗跑偏的原因。

5.2 提醒机制:把纪律做成默认值

模型不一定每次都自觉更新清单。加一个简单的计数器来推它一把:

rounds_since_todo = 0

# 循环里,模型返回后:
rounds_since_todo += 1
if any(b.type == "tool_use" and b.name == "todo_write" for b in response.content):
    rounds_since_todo = 0

# 准备下一轮之前:
if rounds_since_todo >= 3:
    results.append({"type": "text",
                    "text": "<reminder>请更新你的任务清单。</reminder>"})
    rounds_since_todo = 0

连着三轮没碰清单,就插一条提醒。这是教学版的做法——直白、可观察。生产实现里同样的意图会做得更细致(比如只在长任务、多步任务上触发),但核心一致:把好习惯做成系统默认,而不是指望模型自律。

5.3 走一遍:加了清单前后的十步任务

任务:“把 utils/ 下所有函数补上类型注解,并保证测试通过。”

没有清单时的典型轨迹:读文件 → 改第一个文件 → 跑测试 → 报错 → 排查报错 → 发现一个无关的 bug → 修它 → 再跑测试 → 通过 → 输出”完成”。剩下六个文件根本没被碰过,而模型是真心以为自己做完了。

有清单时的典型轨迹:

轮次动作清单状态
1glob: utils/**/*.py—
2todo_write:列出 7 个文件 + 1 条”跑测试”8 条 pending
3把第 1 条置为 in_progress,读+改 utils/text.py1 ▸ / 7 待办
4第 1 条 completed,第 2 条 in_progress1 ✓ / 1 ▸ / 6 待办
…
12跑测试报错 → 排查 → 修复清单仍在,明确显示还剩 2 条
15全部 completed8 ✓

差别不在于第 12 轮那次报错处理得更好——两版处理得一样好。差别在于报错处理完之后,有清单的那一版知道自己该回到哪里。

5.4 工程现实:清单不是任务系统

有一件事现在就要说清楚,免得第12章你会混淆:**这份清单活在进程内存里,会话一结束就没了,也没有依赖关系、没有归属人。**它解决的是”当前这一次对话里,别跑偏”。

跨会话的持久化、任务之间的依赖顺序、多个 Agent 认领同一批工作——那是第12章”任务系统”的活儿。两者在成熟系统里是并存的,不是替代关系:清单是 Agent 自己的便签,任务图是团队共享的看板。

5.5 常见坑

5.6 让清单在中断后还能接上

5.7 为什么计划要写进对话而不是提示词