news 2026/10/8 7:25:00

Python Agent 踩坑实录:第一次写 Agent,我忘了加一行代码,循环跑了 200 次

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python Agent 踩坑实录:第一次写 Agent,我忘了加一行代码,循环跑了 200 次

Python Agent 踩坑实录:第一次写 Agent,我忘了加一行代码,循环跑了 200 次

摘要:本文从一次"初学者写 Agent 忘了加最大步数限制、循环跑了 200 次才停"的经历出发,分析 Agent 循环失控的三个常见原因:没有max_steps、工具描述模糊导致模型反复试错、不检查模型是否已经给出答案。文章给出一段 30 行的完整可运行代码和三个必须加的保护措施,适合刚接触 Agent 的 Python 学习者。

文章目录

  • Python Agent 踩坑实录:第一次写 Agent,我忘了加一行代码,循环跑了 200 次
    • 一、问题背景:我的 Agent 跑了 200 次才停
    • 二、最小复现代码:五步循环失控
    • 三、原因分析:三个初学者常犯的错误
      • 错误一:用 while True 而不是固定次数循环
      • 错误二:工具描述写得太模糊
      • 错误三:没有把模型的最终回答和工具调用区分清楚
    • 四、修复方案:三个必须加的保护
      • 保护一:固定循环次数
      • 保护二:把工具描述写清楚
      • 保护三:打印每一步,看清楚在发生什么
      • 完整代码
    • 五、初学者容易忽略的其他细节
      • 细节一:`tool_call_id` 必须对应
      • 细节二:`messages` 的顺序不能乱
      • 细节三:参数 JSON 解析可能失败
    • 六、排查技巧:怎么知道 Agent 哪里出了问题
      • 方法一:打印每一步的消息列表
      • 方法二:设置一个很小的 max_steps
      • 方法三:统计工具调用次数
    • 七、总结

一、问题背景:我的 Agent 跑了 200 次才停

刚学 Agent 的时候,我跟着教程写了一个能查天气的小助手。

代码很简单:

  • 调用大模型;
  • 如果大模型要调工具,就执行工具;
  • 把工具结果返回给大模型。

跑起来后,我输入“北京天气怎么样”,然后就去倒水了。

回来一看,终端还在刷屏:

调用工具: get_weather({'city': '北京'}) 工具结果: 晴,25°C 调用工具: get_weather({'city': '北京'}) 工具结果: 晴,25°C 调用工具: get_weather({'city': '北京'}) 工具结果: 晴,25°C ...(重复 200 次)

Agent 一直在调用同一个工具,传同一个参数,拿同一个结果,永远不结束。

我以为是模型的问题,换了几个模型都一样。后来才发现,问题出在我自己的代码里——我忘了一行最重要的代码。

这就是本文要讲的坑——Agent 的循环必须有人为设的上限,不能指望模型自己停下来。

二、最小复现代码:五步循环失控

先看错误版本:

importjsonfromopenaiimportOpenAI client=OpenAI()defget_weather(city:str)->str:data={"北京":"晴,25°C","上海":"多云,28°C"}returndata.get(city,f"查不到{city}")tools=[{"type":"function","function":{"name":"get_weather","description":"查天气",# 描述太模糊"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"],},},}]defrun_agent(user_input:str):messages=[{"role":"user","content":user_input}]whileTrue:# 问题在这里!没有循环上限response=client.chat.completions.create(model="gpt-4o-mini",messages=messages,tools=tools,)message=response.choices[0].messageifnotmessage.tool_calls:returnmessage.content messages.append(message)fortcinmessage.tool_calls:args=json.loads(tc.function.arguments)result=get_weather(args["city"])messages.append({"role":"tool","tool_call_id":tc.id,"content":result,})print(run_agent("北京天气怎么样?"))

输出:

调用工具: get_weather({'city': '北京'}) 工具结果: 晴,25°C 调用工具: get_weather({'city': '北京'}) 工具结果: 晴,25°C ...(无限循环,直到你按 Ctrl+C)

问题就在这一行:

whileTrue:# 没有退出条件

三、原因分析:三个初学者常犯的错误

错误一:用 while True 而不是固定次数循环

whileTrue:# 危险:依赖模型自己停下来

初学者容易觉得“模型应该知道自己什么时候完成”。实际上:

  • 模型可能觉得“信息还不够”,继续调用工具;
  • 模型可能重复调用同一个工具;
  • 模型可能陷入某种推理循环。

永远不要用while True写 Agent 循环。

错误二:工具描述写得太模糊

"description":"查天气",# 太模糊

模型不知道:

  • 需要传什么参数?
  • 参数是什么格式?
  • 参数是必填还是选填?

模糊的描述会让模型反复试错,比如先试{"city": "北京"},再试{"location": "北京"},再试{"address": "北京"}——每次试错都是一次循环。

错误三:没有把模型的最终回答和工具调用区分清楚

ifnotmessage.tool_calls:returnmessage.content

看起来没问题,但如果模型返回的content是空字符串(比如它只是想调用工具),这个判断会误判。

四、修复方案:三个必须加的保护

保护一:固定循环次数

defrun_agent(user_input:str,max_steps:int=5):# 加默认参数messages=[{"role":"user","content":user_input}]forstepinrange(max_steps):# 固定次数循环response=client.chat.completions.create(model="gpt-4o-mini",messages=messages,tools=tools,)message=response.choices[0].messageifnotmessage.tool_calls:returnmessage.content messages.append(message)fortcinmessage.tool_calls:args=json.loads(tc.function.arguments)result=get_weather(args["city"])messages.append({"role":"tool","tool_call_id":tc.id,"content":result,})returnf"超过最大步数{max_steps},任务未完成"# 超限返回提示

关键点:for step in range(max_steps)保证最多循环 5 次,到点自动退出。

保护二:把工具描述写清楚

tools=[{"type":"function","function":{"name":"get_weather","description":"查询指定城市的天气。输入城市名称,返回该城市的天气信息。","parameters":{"type":"object","properties":{"city":{"type":"string","description":"城市名称,例如:北京、上海、深圳",}},"required":["city"],},},}]

关键点:描述要包含三要素——做什么、传什么、格式是什么。

保护三:打印每一步,看清楚在发生什么

forstepinrange(max_steps):print(f"\n[第{step+1}步]")# ... 调用大模型 ...ifnotmessage.tool_calls:print(f"[完成]{message.content}")returnmessage.contentfortcinmessage.tool_calls:print(f"[工具]{tc.function.name}({tc.function.arguments})")# ... 执行工具 ...print(f"[结果]{result}")

关键点:初学者一定要能看到每一步发生了什么,才能发现问题。

完整代码

importjsonfromopenaiimportOpenAI client=OpenAI()defget_weather(city:str)->str:data={"北京":"晴,25°C","上海":"多云,28°C"}returndata.get(city,f"查不到{city}")tools=[{"type":"function","function":{"name":"get_weather","description":"查询指定城市的天气。输入城市名称,返回该城市的天气信息。","parameters":{"type":"object","properties":{"city":{"type":"string","description":"城市名称,例如:北京、上海、深圳",}},"required":["city"],},},}]defrun_agent(user_input:str,max_steps:int=5):messages=[{"role":"user","content":user_input}]forstepinrange(max_steps):print(f"\n[第{step+1}步]")response=client.chat.completions.create(model="gpt-4o-mini",messages=messages,tools=tools,)message=response.choices[0].messageifnotmessage.tool_calls:print(f"[完成]{message.content}")returnmessage.content messages.append(message)fortcinmessage.tool_calls:args=json.loads(tc.function.arguments)print(f"[工具]{tc.function.name}({args})")result=get_weather(args["city"])print(f"[结果]{result}")messages.append({"role":"tool","tool_call_id":tc.id,"content":result,})print(f"[超限] 达到最大步数{max_steps}")returnf"超过最大步数{max_steps}"print(run_agent("北京天气怎么样?"))

输出:

[第 1 步] [工具] get_weather({'city': '北京'}) [结果] 晴,25°C [第 2 步] [完成] 北京今天晴天,气温 25°C。

五、初学者容易忽略的其他细节

细节一:tool_call_id必须对应

messages.append({"role":"tool","tool_call_id":tc.id,# 必须和上面工具的 id 一致"content":result,})

如果tool_call_id写错了,大模型会报错或者不知道这是哪个工具的结果。

细节二:messages的顺序不能乱

正确的顺序是:

  1. 用户消息;
  2. 大模型的响应(包含工具调用);
  3. 工具结果。

如果顺序乱了,大模型会困惑。

细节三:参数 JSON 解析可能失败

args=json.loads(tc.function.arguments)

模型偶尔会返回不合法的 JSON。初学者可以先打印看:

print(tc.function.arguments)# 看看实际返回什么args=json.loads(tc.function.arguments)

六、排查技巧:怎么知道 Agent 哪里出了问题

方法一:打印每一步的消息列表

fori,msginenumerate(messages):print(f"[{i}]{msg}")

看清楚大模型每一轮收到了什么、返回了什么。

方法二:设置一个很小的 max_steps

调试时把max_steps设为 2 或 3,快速看到问题,不用等 200 次循环。

方法三:统计工具调用次数

fromcollectionsimportCounter tool_counter=Counter()# 在工具调用处tool_counter[tc.function.name]+=1# 循环结束时打印print(f"工具调用统计:{dict(tool_counter)}")

如果某个工具被调用了很多次,说明模型在反复试错。

七、总结

这次踩坑让我记住了一句话:

Agent 的循环必须有人为设的上限,不能指望模型自己停下来。

核心要点回顾:

  1. 不要用while True写 Agent 循环,用for step in range(max_steps);
  2. max_steps设 5-10 就够了,超限要返回明确的提示;
  3. 工具描述要写清楚:做什么、传什么、格式是什么;
  4. 每一步都打印出来,看清楚在发生什么;
  5. tool_call_id必须和工具调用的 id 对应。

给初学者的三句话:

  • 先把这 30 行代码跑通,比看十篇高级文章都有用;
  • 出问题先打印每一步的消息列表,90% 的问题都能看出来;
  • 不要急着用 LangChain,先用最基础的openai库理解原理。

参考资料:

  • OpenAI Function Calling 官方指南
  • OpenAI Python SDK 快速开始
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 7:23:50

Postman 9.10.1 macOS x64离线安装与接口测试实战

简介:Postman v9.10.1 for macOS (x64) 安装包,面向macOS Intel芯片设备的用户,是一款常用接口测试工具,能发送几乎所有类型的HTTP请求。该版本可满足开发、测试及运维人员在API调试、接口联调中的实际需要,同时也适用…

作者头像 李华
网站建设 2026/10/8 7:22:28

上下文模式:大模型应用中的上下文管理与工程实践

1. 为什么“上下文模式”会成为一个绕不开的话题做AI应用开发这两年,我踩过最大的坑不是模型选型,也不是提示词写得不够花哨,而是上下文怎么给、给多少、什么时候不给。你精心设计的Prompt放进空对话里效果惊艳,一旦放进真实业务场…

作者头像 李华
网站建设 2026/10/8 7:21:56

不关SIP也能秒切桌面:InstantSpaceSwitcher安全与隐私完全指南

不关SIP也能秒切桌面:InstantSpaceSwitcher安全与隐私完全指南 【免费下载链接】InstantSpaceSwitcher Native space switching on macOS with no animation 项目地址: https://gitcode.com/gh_mirrors/in/InstantSpaceSwitcher InstantSpaceSwitcher 是一款…

作者头像 李华
网站建设 2026/10/8 7:21:10

261008-report

261008-report 🧑🏻‍💻Author: Zenos 📝Overview: 本文档主要记录26年10月第一周学习内容以及后续学习计划。 文章目录261008-report[toc]一、研究背景1.1 微小目标检测1.1.1 微小目标检测面临的挑战1.1.…

作者头像 李华
网站建设 2026/10/8 7:20:46

显卡驱动16.1656升级16.1692:性能优化与兼容性修复指南

1. 从一条版本号说起:16.1656 到 16.1692 到底动了什么驱动版本号这种东西,平时没人会盯着看,只有当机器开始闹脾气——花屏、掉帧、外接屏不亮、休眠唤醒后黑屏——才会有人翻出设备管理器,盯着那一串数字发呆。16.1656 和 16.16…

作者头像 李华
网站建设 2026/10/8 7:20:42

ssm307自习室预订座位管理分析与实现+vue(文档+源码)_kaic

5 系统实现5.1 座位分类管理管理员可以管理座位分类信息,可以添加,修改,删除座位分类信息。下图就是房间信息管理页面。图5.1 座位分类信息管理页面管理可以在这个页面中的文本框中添加分类信息,添加完成后点击提交,管…

作者头像 李华