【手搓 AI Agent 从 0 到 1】第六课:智能体循环——让 AI 不止回答一次
前置知识已完成第一课至第五课本课目标把决策和工具调用放进循环让 AI 反复思考、反复行动核心概念Agent Loop / 状态追踪 / 终止条件 / 步骤累积前言前五课我们一步步给 AI 加了能力——第二课给了它角色第三课给了它结构化输出第四课给了它决策能力第五课给了它工具调用。但不管是哪一课AI 的行为模式都一样用户说话 → AI 回复一次 → 结束一回。就这一回。ChatGPT 是这么工作的吗不是。当你让 ChatGPT “帮我查一下明天北京的天气然后推荐适合的穿搭”它不会一步到位——它先查天气看到晴25℃然后根据这个结果推荐穿搭。中间发生了一次观察→决策→执行的循环。它把上一步的结果当作下一步的输入一步步推进直到任务完成。这就是 Agent 和 Chatbot 的核心区别Agent 不止回答一次它会循环。一、从问答到循环先看一下我们前五课构建的系统是什么样子# 第四课decisionagent.decide(帮我总结这篇文章,choices[answer,summarize,translate])# AI 选了 summarize然后没了。# 第五课tool_callagent.request_tool(What is 42 * 7?)resultagent.execute_tool_call(tool_call)# AI 用了计算器得到了 294然后也没了。每次 AI 只做一件事做完就结束。它的世界是单步的——看不到前一步的结果也想不到下一步该做什么。但真实的任务往往是多步的用户帮我分析这篇文章的情感倾向如果是负面的总结主要原因 正确的处理流程 步骤1分析情感 → 负面 步骤2判断 → 是负面的需要继续 步骤3总结原因 → 主要原因有3点…… 步骤4任务完成 而前五课的系统能做到吗不能。因为它没有上一步的结果这个概念。要支持多步任务我们需要两样东西循环——让 AI 能反复思考、反复行动状态——让 AI 知道当前做到哪一步了上一步做了什么第六课就做这两件事。二、智能体循环观察、决策、执行、重复2.1 什么是 Agent LoopAgent Loop 就是把观察→决策→执行这个过程放在一个循环里让 AI 重复执行直到任务完成。┌──────────────────────────────────┐ │ Agent Loop │ │ │ │ ┌─────────────────────┐ │ │ │ 1. 观察当前状态 │ │ │ │ 步骤计数、 │ │ │ │ 前几步的结果 │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ 2. 决策下一步做什么 │ │ │ │ 调用 LLM │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ 3. 执行动作 │ │ │ │ 更新状态 │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ 是否完成 │ │ ↓ ↓ │ │ 否 → 回到 1 是 → 结束 │ │ │ └──────────────────────────────────┘就这么简单。没有魔法没有复杂框架。就是一个while循环里面放三步逻辑。你可能觉得这也太简单了吧——确实简单。但简单不代表没用。这个循环模式几乎是所有 Agent 框架的底层骨架。LangChain 的 AgentExecutor、AutoGen 的对话循环、CrewAI 的任务编排底层都是这个东西。2.2 和前几课的对比把五课放在一起对比你能看到一条清晰的演进线第二课用户 → AI 回复 → 结束 第三课用户 → AI 返回 JSON → 结束 第四课用户 → AI 选择动作 → 执行一次 → 结束 第五课用户 → AI 选择工具参数 → 执行一次 → 结束 第六课用户 → AI 选择动作 → 执行 → 观察结果 → 再选择 → 再执行 → … → 完成前五课是单步的第六课是多步的。区别就在循环两个字。三、状态让循环有记忆3.1 为什么需要状态没有状态的循环是瞎循环。想象一下AI 在步骤 2 说我要分析情感在步骤 3 又说我要分析情感在步骤 4 还是分析情感——每一步都在做同一件事因为它不记得上一步做过什么。状态解决了这个问题。每次循环迭代AI 都能看到当前的状态信息已经执行了几步前面几步做了什么任务是否完成有了这些信息AI 才能做出合理的下一步决策。3.2 状态包含什么最基本的状态只需要两个字段classAgentState:def__init__(self):self.steps0# 已执行步数self.doneFalse# 是否完成self.results[]# 每步的结果累积steps防止无限循环。不管 AI 怎么想步数到了就必须停。doneAI 自己判断任务完成了主动退出。results保存每步的产出让后续步骤能看到前面的结果。这比很多人想象的简单得多。不需要复杂的记忆系统那是第七课的事只需要一个计数器、一个标志位、一个列表。3.3 状态放在哪放在 Prompt 里。每次调用 LLM 之前把当前状态拼进 prompt让模型知道你现在在第几步前面做了什么。promptf...角色设定 当前状态步骤{state.steps}已完成{state.done}历史动作{state.results}请决定下一步动作...就是这么直白。没有什么花哨的状态编码没有什么向量数据库。直接把状态文字拼进 prompt模型就能理解。四、终止条件没有刹车的车不能开循环最怕什么停不下来。没有终止条件的循环会无限运行把你的 API 配额烧光把你的服务器跑死。所以终止条件是 Agent Loop 里最重要的安全机制。本课实现三个终止条件构成双重保险4.1 AI 主动终止AI 在某一步决定任务完成了输出done动作。ifaction.get(action)done:state.mark_done()# 标记完成循环退出这是理想情况——AI 认为任务做完了主动退出。4.2 最大步数限制whilenotstate.doneandstate.stepsmax_steps:...不管 AI 怎么想步数到了就强制停。这是你的安全网。max_steps一般设 35 步。太少了任务完不成太大了浪费资源。3 步是一个不错的起点。4.3 解析失败兜底ifactionisNone:break# LLM 返回了无法解析的内容退出三次重试都失败说明 LLM 卡住了。这时候不应该继续循环而是直接退出。三个终止条件的关系循环继续条件 ✅ AI 没说 done ✅ 步数 max_steps ✅ 本步解析成功 全部满足 → 继续 任一不满足 → 退出五、代码实现5.1 状态类AgentState打开agent/agent.py首先找到新增的AgentState类classAgentState: 智能体状态追踪第六课引入 跟踪智能体在循环中的进度 - 执行了多少步 - 是否已完成 - 每步的结果是什么 def__init__(self):self.steps:int0self.done:boolFalseself.results:list[dict][]defto_dict(self)-dict:将状态转换为字典用于拼入 Promptreturn{steps:self.steps,done:self.done,results:self.results[-3:],# 只传最近3步防止 prompt 过长}defadd_result(self,result:dict)-None:记录一步的结果self.results.append(result)defincrement_step(self)-None:步数 1self.steps1defmark_done(self)-None:标记任务完成self.doneTruedefreset(self)-None:重置状态开始新一轮循环前调用self.steps0self.doneFalseself.results[]注意一个细节to_dict()方法只返回最近 3 步的结果self.results[-3:]。这是为了控制 prompt 长度——如果 Agent 跑了 10 步把所有结果都塞进 prompt 会很长。只保留最近的 3 步既能让 AI 看到上下文又不会让 prompt 膨胀。5.2 单步执行agent_step()defagent_step(self,user_input:str)-dict|None: 执行智能体循环的一步观察→决策→执行。 第六课版本。 Args: user_input: 用户输入或系统观察 Returns: 动作决策如果步骤失败则返回 None state_dictself.state.to_dict()# 构建历史动作摘要history_summaryifstate_dict.get(results):history_lines[]fori,rinenumerate(state_dict[results],1):actionr.get(action,?)reasonr.get(reason,)history_lines.append(f 步骤{i}: 动作{action}, 原因{reason})history_summary\n之前的动作\n\n.join(history_lines)user_promptf你是一个智能体助手。你需要根据当前状态决定下一步该做什么。 当前状态步骤{state_dict.get(steps,0)}, 已完成{state_dict.get(done,False)}{history_summary}可用动作 - analyze分析用户输入或已有结果 - research深入研究某个方面 - summarize总结已获得的信息 - answer给出最终答案 - done任务已完成可以结束 规则 1. 只返回有效的 JSON 2. 不要任何解释不要 Markdown 3. 直接以 {{ 开头以 }} 结尾 4. JSON 格式{{action: 动作名称, reason: 选择该动作的原因}} 用户输入{user_input}请返回 JSONforattemptinrange(3):responseself.client.chat.completions.create(modelself.model,messages[{role:system,content:self.system_prompt},{role:user,content:user_prompt},],temperature0.0,)textresponse.choices[0].message.content parsedextract_json_from_text(text)ifparsedandactioninparsed:ifreasonnotinparsed:parsed[reason]f执行动作:{parsed[action]}self.state.increment_step()self.state.add_result(parsed)returnparsedreturnNone这段代码你不会陌生——第三课的 JSON 提取、第四课的验证、第五课的 prompt 风格全部复用JSON 输出 extract_json_from_text()—— 第三课以来的标准操作。重试 3 次—— 老规矩了LLM 有随机性最多试三次。验证关键字段—— 检查action字段是否存在。始终验证模型输出。temperature0.0—— 决策需要确定性每一步该做什么不应该随机。User Prompt 放动态内容—— 可用动作列表和历史状态都是动态的放 User Prompt。唯一的新东西是状态注入在 prompt 里告诉模型当前执行到第几步、之前做了什么。这是让循环有记忆的关键。5.3 循环执行run_loop()defrun_loop(self,user_input:str,max_steps:int5)-list[dict]: 运行多步智能体循环。 Args: user_input: 初始用户输入 max_steps: 最大执行步数安全限制 Returns: 每步的动作结果列表 self.state.reset()results[]print(f 启动智能体循环最多{max_steps}步)print(f 用户输入{user_input}\n)whilenotself.state.doneandself.state.stepsmax_steps:step_numself.state.steps1print(f--- 步骤{step_num}---)actionself.agent_step(user_input)ifaction:action_nameaction.get(action,?)reasonaction.get(reason,)print(f 动作{action_name})print(f 原因{reason})results.append(action)ifaction_namedone:self.state.mark_done()print(f ✅ 智能体主动结束)breakelse:print(f ❌ 步骤失败退出循环)breakprint()ifnotself.state.doneandself.state.stepsmax_steps:print(f⚠️ 达到最大步数{max_steps}强制结束)print(f\n 循环结束共执行{len(results)}步)returnresults仔细看这个循环的结构whilenotself.state.doneandself.state.stepsmax_steps:actionself.agent_step(user_input)ifaction:results.append(action)ifaction.get(action)done:self.state.mark_done()else:break三个终止条件一个都不能少终止条件代码位置作用AI 主动完成if action.get(action) done正常退出最大步数while self.state.steps max_steps安全限制解析失败if action: ... else: break兜底结果累积每步的 action 都被 append 到results列表里最终整个循环返回一个完整的动作历史。六、运行示例6.1 基础运行fromagent.agentimportAgent agentAgent(modelqwen2.5:7b)resultsagent.run_loop(帮我分析一下 Python 的优缺点,max_steps3)fori,resultinenumerate(results,1):actionresult.get(action,unknown)reasonresult.get(reason,无原因)print(f 步骤{i}: [{action}]{reason})预期输出类似 启动智能体循环最多 3 步 用户输入帮我分析一下 Python 的优缺点 --- 步骤 1 --- 动作analyze 原因首先分析用户请求识别需要讨论Python的优缺点 --- 步骤 2 --- 动作research 原因深入研究Python的具体优缺点细节 --- 步骤 3 --- 动作answer 原因已经收集了足够的信息可以给出完整回答 循环结束共执行 3 步注意一个现象早期步骤中可能出现重复或相似的分析。这不是 Bug而是 LLM 的正常行为——它在逐步完善自己的理解可能在收敛到最终答案之前会重复探索。这也正是max_steps的价值所在即使 AI 在原地打转步数到了也会被强制拉停。6.2 更大的 max_steps# 给 AI 更多步骤来完成复杂任务resultsagent.run_loop(帮我研究一下深度学习和传统机器学习的区别,max_steps5)你可能会观察到步骤 12分析用户需求确定研究方向步骤 34深入某个方面步骤 5总结并给出答案或者达到 max_steps 被强制结束6.3 交互模式cdlesson06 python complete_example.py会进入交互模式你可以输入任意问题观察 Agent 如何一步步处理。七、与第五课的本质区别把两课的流程放在一起对比区别一目了然第五课工具调用——单步用户输入 → AI 选择工具 → AI 提取参数 → 执行一次 → 返回结果一次性。AI 选一个工具、带一组参数、执行一次、结束。每一步都是孤立的。第六课Agent Loop——多步用户输入 ↓ 步骤1观察状态 → AI 决策 → 执行 → 更新状态 ↓ 步骤2观察状态含步骤1结果→ AI 决策 → 执行 → 更新状态 ↓ 步骤3观察状态含步骤1-2结果→ AI 决策 → 执行 → 更新状态 ↓ …… ↓ 达到终止条件 → 结束每一步都能看到之前所有步骤的结果。步骤之间有上下文、有累积、有推进。第五课第六课执行次数一次多次循环步骤间关系无关系前一步的结果影响下一步状态追踪无有steps、done、results终止条件不需要必须有max_steps doneAI 的角色“帮我选一个工具”“帮我规划并执行多步任务”第五课的 AI 是执行者——你说一句它做一件事。第六课的 AI 是规划者——它自己决定每一步该做什么什么时候该停。八、关键洞察8.1 智能体不是聪明的 Prompt这是本课最重要的一个洞察智能体不是聪明的提示词。智能体是带状态的循环。你可以在一个单次调用的 prompt 里写请分步骤思考但那不叫 Agent。那只是让模型在输出里模拟多步推理。真正的 Agent 是你的代码在循环每次循环调用一次 LLM把结果存起来再调用下一次。魔力不在 prompt 里而在循环里。8.2 状态实现连续性没有状态每一步都是失忆的——AI 不知道自己做了什么、做到哪一步了。有了状态步骤之间才能衔接。这就是为什么AgentState虽然简单就三个字段但不可或缺。8.3 终止条件是安全带开车必须系安全带写循环必须设终止条件。max_steps是你的安全带。不管 AI 的判断出了什么问题步数到了就停。不要信任 AI 会主动说 “done”你必须自己加限制。8.4 简单先行复杂后加本课的循环刻意保持了简单——没有规划、没有记忆、没有复杂的推理链。先建立循环 状态的基础模式后面的课程会在此基础上叠加更复杂的能力。如果一上来就搞复杂的规划系统反而容易迷失。先跑起来再跑得更好。九、常见问题QAgent 在循环里反复做同一件事怎么办A先检查 prompt 是否包含了历史状态信息。如果 AI 看不到之前做了什么它当然会重复。确保to_dict()的结果被正确拼入了 prompt。如果状态信息已经有了但还是重复可能是任务太模糊——试试给用户输入加更多约束或者缩短max_steps减少浪费。Q循环永远不退出怎么办A三重检查①max_steps条件是否在 while 里正确判断了②self.state.increment_step()是否在每次循环里被调用了③ AI 输出的 done 动作是否被正确解析了绝大多数死循环都是忘了调increment_step()。Q每一步的结果该传多少给下一步A代码里用的是最近 3 步self.results[-3:]。这是一个经验值——太少了 AI 看不到足够上下文太多了 prompt 会很长。对于本地模型7B35 步的历史信息是比较合适的。如果你的模型上下文窗口更大可以适当增加。Qmax_steps 设多少合适A取决于任务的复杂度。简单任务分类、计算3 步就够了。中等任务分析、总结5 步比较合适。复杂任务研究、规划可以到 10 步但要注意 token 消耗。经验法则从 3 开始不够再加。Q状态只有 steps/done/results 够用吗A对于本课的需求完全够。但如果你想支持更复杂的场景可以扩展状态。比如加一个current_goal字段记录当前子目标或者加errors记录失败次数。第七课会加更强大的记忆系统。十、下期预告第七课记忆——让 Agent 跨对话记住信息本课的状态只在一次循环内有效。循环结束状态清空。下一次对话AI 又是失忆状态。但真实的智能体需要跨对话记忆——它应该记住昨天你告诉它的偏好、上周它帮你做的分析、上个月你设定的目标。下一课我们给 Agent 加上持久化的记忆系统。这是从能用的工具到有个性的助手的关键一步。敬请期待完整代码获取本课涉及的完整代码包括AgentState类——轻量级状态追踪agent_step()方法——单步执行逻辑run_loop()方法——多步循环引擎complete_example.py——演示模式 交互模式完整代码 参照 第一篇文章 最后标签#Python#AI Agent#LLM#智能体循环#Ollama#Qwen#大模型#手搓Agent本文为《手搓 AI Agent 从 0 到 1》系列教程第 6 课