05 · 实战:第一个多步骤任务
用一个真实的多步骤任务演示升级后的 agent 如何工作:创建计划、按步骤执行、展示 ReAct 循环的完整运转过程。
从问答到执行
第 1 章的 agent 只能回答问题——搜一下、读一下、给出答案。现在它有了 Agent Loop、ReAct 模式、Todo 机制和退出条件,可以处理更复杂的任务了。
这一节用一个真实任务走一遍完整流程,看看升级后的 agent 怎么工作。
Todo 机制的运转原理
在进入实战之前,先理解 Todo 机制在代码层面是怎么运转的。三个关键组件协作:
TodoManager(src/todo.ts)——计划管理器。每次 runAgent 创建一个新实例,维护一个 items 数组。步骤通过 createItems() 创建(自增 ID,初始 pending),通过 updateStatus() 更新状态。
create_todos 工具(src/tools/create-todos.ts)——工厂函数创建,通过闭包捕获 TodoManager。模型调用时传入步骤描述列表,工具调用 todoManager.createItems() 批量创建。
update_todo 工具(src/tools/update-todo.ts)——同样通过闭包捕获 TodoManager。模型每开始一步标记 running,完成标记 completed,失败标记 failed。注意状态不可回退——pending 不在允许列表中。
create_todos({ items: ["步骤A", "步骤B"] })
→ TodoManager.createItems()
→ TodoItem { id: "1", description: "步骤A", status: "pending" }
→ TodoItem { id: "2", description: "步骤B", status: "pending" }
→ 返回: "已创建 2 个步骤: ..."
update_todo({ id: "1", status: "running" })
→ TodoManager.updateStatus("1", "running")
→ 返回: "步骤 #1 → running\n执行计划: ..."
agent.ts 中的提前退出:
→ todoManager.allCompleted()
→ items.length > 0 && every(item => item.status === "completed")
→ 满足且模型不再请求工具 → 直接返回,省 token实战任务:分析项目工具的参数定义
给 agent 一个需要多步分析的任务:
> 帮我找出这个项目中所有工具的参数定义,对比它们是否一致,然后给出一份工具清单和改进建议第一轮:模型分析任务,创建计划
模型判断这是复杂任务,调用 create_todos:
工具调用: create_todos({
items: [
"搜索项目中所有工具定义的位置",
"读取各个工具的参数定义",
"对比分析工具参数的一致性",
"生成工具清单和改进建议报告"
]
})
工具返回:
已创建 4 个步骤:
执行计划:
[ ] #1 搜索项目中所有工具定义的位置
[ ] #2 读取各个工具的参数定义
[ ] #3 对比分析工具参数的一致性
[ ] #4 生成工具清单和改进建议报告模型先规划再动手。每个步骤有明确的 ID 和状态,方便后续跟踪。
第二轮:搜索工具位置,标记步骤 1 为 running
工具调用: update_todo({ id: "1", status: "running" })
工具调用: search({ query: "Tool" })
工具返回 (search): 找到 src/tools/ 下多个文件中的 Tool 类型引用模型标记步骤 1 开始执行,同时搜索关键词定位工具文件。这就是 ReAct 模式——Reason(分析需要搜索)→ Act(调用 search)→ Observe(看到搜索结果)。
第三轮:逐个读取工具文件
工具调用: update_todo({ id: "1", status: "completed" })
工具调用: update_todo({ id: "2", status: "running" })
工具调用: read_file({ path: "src/tools/search.ts" })
工具调用: read_file({ path: "src/tools/read-file.ts" })
工具调用: read_file({ path: "src/tools/create-todos.ts" })
工具调用: read_file({ path: "src/tools/update-todo.ts" })模型一口气读了四个工具文件。每次工具调用都是一轮 ReAct 循环:模型看到上一次的结果,决定下一步做什么。
第四轮:完成分析,给出结果
工具调用: update_todo({ id: "2", status: "completed" })
工具调用: update_todo({ id: "3", status: "completed" })
工具调用: update_todo({ id: "4", status: "running" })模型对比了四个工具的参数定义后,发现几个不一致的地方,然后给出清单和建议。最终标记步骤 4 完成。
终端中的输出
> 帮我找出这个项目中所有工具的参数定义,对比一致性,给出改进建议
--- 执行计划 ---
[x] #1 搜索项目中所有工具定义的位置
[x] #2 读取各个工具的参数定义
[x] #3 对比分析工具参数的一致性
[x] #4 生成工具清单和改进建议报告
----------------
[模型的完整分析和建议]
[模型调用: 16次 | 工具调用: 24次]模型有时会"过度规划"
上面的实际运行中,模型创建了 24 个步骤(远超最初的 4 个),包括很多修改代码的具体步骤。这导致了迭代上限被触发(15 次循环不够用)。
这是一个重要的观察:Todo 机制是引导模型行为的工具,但模型自己决定创建多少步骤。 如果模型过度拆分任务,可能在迭代上限内完不成。
目前没有自动机制来约束步骤数量。这在后续章节可以通过系统提示词的优化来改善——比如告诉模型"步骤不超过 5 个"。
简单问题不走计划
对比一下:如果问"入口文件在哪",模型会判断这是简单问题,直接用搜索和读文件工具回答,不创建计划。
系统提示词中的这段引导让模型能区分两种情况:
对于复杂任务,先用 create_todos 工具创建执行计划,然后按步骤执行。
...
对于简单问题,可以直接使用搜索和读文件工具回答,不需要创建计划。这一章的代码结构
完成第 2 章后,项目结构变为:
mini-coding-agent/
├── src/
│ ├── types.ts # 类型定义(新增 TodoItem, AgentStats, AgentResult)
│ ├── model.ts # 模型层(不变)
│ ├── todo.ts # Todo 管理器(新增)
│ ├── agent.ts # Agent Loop(升级:ReAct + 计划 + 统计)
│ ├── main.ts # CLI 入口(升级:显示计划和统计)
│ └── tools/
│ ├── search.ts # 搜索工具(不变)
│ ├── read-file.ts # 读文件工具(不变)
│ ├── create-todos.ts # 创建计划工具(新增)
│ └── update-todo.ts # 更新步骤状态工具(新增)
├── test/
│ ├── agent.test.ts # Agent 测试(升级)
│ ├── model.test.ts # 模型测试(不变)
│ ├── todo.test.ts # Todo 管理器测试(新增)
│ └── tools/
│ ├── search.test.ts # 搜索工具测试(不变)
│ ├── read-file.test.ts # 读文件工具测试(不变)
│ ├── create-todos.test.ts # 创建计划测试(新增)
│ └── update-todo.test.ts # 更新步骤测试(新增)
├── package.json
└── tsconfig.json这一章做了什么
回顾第 2 章的成果:
- Agent Loop 升级:从简单的 5 轮上限进化为结构化的执行循环,支持可配置的迭代上限
- ReAct 模式:模型在循环中持续推理(Reason)、执行(Act)、观察(Observe)
- Todo 机制:模型通过
create_todos创建计划、通过update_todo跟踪步骤状态,完成时自动检测并提前退出 - 退出条件:正常完成(模型不再调工具)、计划完成(
allCompleted())、达到上限三种停止条件 - 执行统计:
AgentStats跟踪模型调用次数、工具调用次数和是否触及上限 - Repair Loop:工具失败时错误信息回传给模型,模型自行调整策略
从第 1 章的"能回答问题",到第 2 章的"能规划并执行多步骤任务",agent 的能力上了一个台阶。下一章开始建设工具系统——让 agent 有更多"手脚"可以使用。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。