从零构建最小Agent:手把手教你写AI循环调用代码

用一段Python代码演示Agent工具调用循环,并修复finish后未跳出的bug。
文章以实战方式讲解如何用Python构建一个最小可用的Agent:先重申Agent由目标、工具和验收结果三要素构成,通过新增finish工具作为任务完成的显式信号;接着介绍system与user prompt的设计要点,强调模型可能不按要求返回;然后逐段拆解主循环,包括最大步数限制、tool_choice默认为auto、工具调用解析以及三类分支处理逻辑。运行后暴露了finish调用后循环未跳出的bug,作者用标志位修复并验证流程正确,最后指出Agent骨架不复杂,难点在于每轮返回值的判断与边界处理。
这是「手把手教你写AI第一行代码」系列中关于Agent的实战课。UP主用一段完整的Python代码演示了如何让大模型自主调用工具、循环执行任务,直到产出可验收的结果。整个过程暴露了真实开发中常见的逻辑漏洞,也给出了修复思路。
什么是Agent:目标、工具与验收循环
UP主在开篇重申了Agent的核心定义:给定一个目标,提供可调用的工具,再设定一个验收结果,模型便能在每一轮循环中自主决定调用哪个工具,直到返回我们认可的结果。
这与上一节课的tool_choice形成对比——上节课强制指定函数调用,而Agent不指定具体工具,让模型自己思考该调用什么。作者为此新增了一个finish工具,作为任务完成的显式信号。

代码结构上,tools的schema与上节课基本一致,但增加了finish方法。它的参数只有一个必选的summary,用于描述本轮做了什么,避免模型自由发挥。
tool_choice 参数是 OpenAI 兼容接口中控制函数调用行为的字段,常见取值有三类:"none" 表示禁止调用任何工具,"auto" 表示由模型自行判断是否需要调用以及调用哪个函数,{type: "function", function: {name: "xxx"}} 则强制模型调用指定函数。上一节课用固定指定某个函数的方式演示单次工具调用,属于「单步」用法;Agent 场景下把它留空或显式设为 "auto",模型才会在每一轮自主选择工具,从而形成多步决策。需要注意的是,auto 只是「允许模型选择」,并不保证模型一定调用工具,因此工程上仍要处理模型直接返回文本的情况。
组装多轮对话:system与user prompt的设计
Agent需要自己定义system prompt和user prompt。系统提示词负责设定人设与可用工具,并明确要求“任务完成后必须用finish结束,不要用普通文字结束”。用户提示词则反复强调提交成功后必须调用finish。
UP主坦言,即使反复提醒,模型也可能不按要求返回——这是调用接口时的常见痛点。
消息组装方面,每一轮的assistant消息都要塞回对话历史,延续了此前长对话课程的做法。作者新建了一个函数来构造assistant结构以便复用,并给取值加了防护,因为上线项目中拿不到值时必须做防御性判断。

主循环实现:步数限制与工具调用解析
主循环用for从1遍历到最大步数加1。UP主特意设置了最大6轮,而不是用while True——模型能力不足时可能卡住或空跑多轮,不设上限会在不注意时把账户余额榨干。
tool_choice不写,默认即为auto。代码会打印response,然后从message中取choices[0],正常项目中还需判断索引是否越界。
拿到message后,逻辑分三支:
- 没有调用工具:把模型的返回追加进消息,并提醒它调用工具,若未提交则提示调用
submit_path,若已提交则要求用finish结束,然后进入下一轮。 - 正常调用了工具:通过
toAssistant函数把message放进去,用json库解析工具名称。UP主提醒,上工程时最好单独写函数校验返回结构是否符合预期。 - 调用了错误工具:告诉模型“还没提交正确识别结果不能结束”或“掉了一个错误的工具”,并附上工具名称。

作者在代码中多次用打印日志的方式观察每一轮调用了哪个工具、内容是什么,这也是调试Agent最直接的手段。
工具调用的返回结构在 OpenAI 风格接口中形如 choices[0].message.tool_calls,它是一个数组,每项包含 id、type 和 function 三个字段,其中 function.name 是工具名,function.arguments 是一段 JSON 字符串而非对象,必须先经 json.loads 解析才能取到参数。这也解释了为何代码中要用 json 库解析工具名称——模型输出的参数本质上是文本,可能出现多余字符、字段缺失甚至非法 JSON,工程上需要用 try/except 包裹并校验必填字段。解析完成后,还要以 role: "tool"、tool_call_id 对应原调用 id、content 为字符串结果的形式追加回消息列表,模型才能把工具输出与自己的请求对应起来,否则下一轮会报错或丢失上下文。
实战踩坑:finish后循环没有跳出
实际运行后立刻暴露了一个bug:第一轮调用了submit_path返回两个动物,第二轮调用了finish,按理任务已完成,但循环仍在继续。
原因是代码逻辑漏掉了一个点——当模型正常使用finish时,应该结束循环。修复方式是加一个标志位,在finish分支中置位,跳出内层后判断该标志再break。

修复后重新运行,第一轮调用工具、第二轮调用finish并拿到结果,流程符合预期。UP主借此说明:Agent的骨架并不复杂,难点在于对每一轮返回值的判断和边界处理。
循环跳出控制是 Agent 主循环的常见陷阱。Python 的 break 只能跳出当前所在的那一层循环,若 finish 分支位于内层 if 或 for 中,直接写 break 可能只跳出内层而外层继续执行。惯用做法有两种:一是设置布尔标志位(如 finished = True),在内层处理后于外层循环体开头判断并 break;二是把主循环抽成函数,检测到 finish 时直接 return 结果。相比之下,标志位写法更贴近初学者的思维,但要注意标志位必须在每轮开始时重置或全局只置位一次,避免状态残留。此外,除了 finish,最大步数耗尽、模型连续多轮无工具调用、工具返回致命错误,也都应作为循环终止条件一并设计。
总结与延伸
这节课的代码量比前几节明显更大,但每一步都对应着Agent的基本构件:schema定义、提示词约束、循环控制、工具解析与错误反馈。作者原本还计划讲两个Agent协作构建workflow,但认为课程到此已足够,留作有兴趣者的延伸方向。
对于想动手实践的开发者,建议先跑通这个最小闭环,再逐步加入结构校验、日志与异常处理,向生产可用迈进。
相关推荐

亲手构建Agent:理解AI Agent的最佳路径
想真正理解AI Agent?本文分享如何从零构建一个最小Agent,拆解大模型与Harness的分工,推荐PiAgent、DeepSeek Harness等学习型框架,并给出八个递进阶段和RAG与Agent的关系分析。

Kimi K3.1 Pro实测:开源模型能否挑战Opus 5.5?
Kimi K3.1 Pro实测对比Opus 5.5和DeepSeek V4.1 Flash:基准测试亮眼,但MOBA游戏开发任务暴露短板。速度接近Opus,成本接近DeepSeek,成为开源模型中的性价比之选。

多Agent交接的边界:该给什么,该藏什么?
多Agent工作流中,交接该给多少上下文?本文拆解Reddit开发者的一线经验:给Coder原子spec加目标文件是否足够、给Reviewer为何必须带spec、切片太窄时如何用命名依赖请求有界摘录,并提炼一条可操作的交接判断规则。