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 clientOpenAI()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:# 问题在这里没有循环上限responseclient.chat.completions.create(modelgpt-4o-mini,messagesmessages,toolstools,)messageresponse.choices[0].messageifnotmessage.tool_calls:returnmessage.content messages.append(message)fortcinmessage.tool_calls:argsjson.loads(tc.function.arguments)resultget_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 ...无限循环直到你按 CtrlC问题就在这一行whileTrue:# 没有退出条件三、原因分析三个初学者常犯的错误错误一用 while True 而不是固定次数循环whileTrue:# 危险依赖模型自己停下来初学者容易觉得“模型应该知道自己什么时候完成”。实际上模型可能觉得“信息还不够”继续调用工具模型可能重复调用同一个工具模型可能陷入某种推理循环。永远不要用while True写 Agent 循环。错误二工具描述写得太模糊description:查天气,# 太模糊模型不知道需要传什么参数参数是什么格式参数是必填还是选填模糊的描述会让模型反复试错比如先试{city: 北京}再试{location: 北京}再试{address: 北京}——每次试错都是一次循环。错误三没有把模型的最终回答和工具调用区分清楚ifnotmessage.tool_calls:returnmessage.content看起来没问题但如果模型返回的content是空字符串比如它只是想调用工具这个判断会误判。四、修复方案三个必须加的保护保护一固定循环次数defrun_agent(user_input:str,max_steps:int5):# 加默认参数messages[{role:user,content:user_input}]forstepinrange(max_steps):# 固定次数循环responseclient.chat.completions.create(modelgpt-4o-mini,messagesmessages,toolstools,)messageresponse.choices[0].messageifnotmessage.tool_calls:returnmessage.content messages.append(message)fortcinmessage.tool_calls:argsjson.loads(tc.function.arguments)resultget_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[第{step1}步])# ... 调用大模型 ...ifnotmessage.tool_calls:print(f[完成]{message.content})returnmessage.contentfortcinmessage.tool_calls:print(f[工具]{tc.function.name}({tc.function.arguments}))# ... 执行工具 ...print(f[结果]{result})关键点初学者一定要能看到每一步发生了什么才能发现问题。完整代码importjsonfromopenaiimportOpenAI clientOpenAI()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:int5):messages[{role:user,content:user_input}]forstepinrange(max_steps):print(f\n[第{step1}步])responseclient.chat.completions.create(modelgpt-4o-mini,messagesmessages,toolstools,)messageresponse.choices[0].messageifnotmessage.tool_calls:print(f[完成]{message.content})returnmessage.content messages.append(message)fortcinmessage.tool_calls:argsjson.loads(tc.function.arguments)print(f[工具]{tc.function.name}({args}))resultget_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的顺序不能乱正确的顺序是用户消息大模型的响应包含工具调用工具结果。如果顺序乱了大模型会困惑。细节三参数 JSON 解析可能失败argsjson.loads(tc.function.arguments)模型偶尔会返回不合法的 JSON。初学者可以先打印看print(tc.function.arguments)# 看看实际返回什么argsjson.loads(tc.function.arguments)六、排查技巧怎么知道 Agent 哪里出了问题方法一打印每一步的消息列表fori,msginenumerate(messages):print(f[{i}]{msg})看清楚大模型每一轮收到了什么、返回了什么。方法二设置一个很小的 max_steps调试时把max_steps设为 2 或 3快速看到问题不用等 200 次循环。方法三统计工具调用次数fromcollectionsimportCounter tool_counterCounter()# 在工具调用处tool_counter[tc.function.name]1# 循环结束时打印print(f工具调用统计:{dict(tool_counter)})如果某个工具被调用了很多次说明模型在反复试错。七、总结这次踩坑让我记住了一句话Agent 的循环必须有人为设的上限不能指望模型自己停下来。核心要点回顾不要用while True写 Agent 循环用for step in range(max_steps)max_steps设 5-10 就够了超限要返回明确的提示工具描述要写清楚做什么、传什么、格式是什么每一步都打印出来看清楚在发生什么tool_call_id必须和工具调用的 id 对应。给初学者的三句话先把这 30 行代码跑通比看十篇高级文章都有用出问题先打印每一步的消息列表90% 的问题都能看出来不要急着用 LangChain先用最基础的openai库理解原理。参考资料OpenAI Function Calling 官方指南OpenAI Python SDK 快速开始