手把手搭建可调试AI Agent:OpenAI工具调用核心原理与工程实践

手把手搭建可调试AI Agent:OpenAI工具调用核心原理与工程实践
1. 项目概述为什么一个“简单AI Agent”值得你花两小时亲手搭一遍我第一次在OpenAI官方文档里看到“tool calling”这个词时下意识划了过去——不就是函数调用嘛Python里func()敲一下的事。直到上周帮朋友调试一个客服对话系统客户问“上个月订单总金额是多少”后端要查数据库、算折扣、再格式化返回而大模型卡在“我不知道订单数据在哪”这一步反复兜圈子。那一刻我才真正意识到不是模型不够聪明而是它被关在语言的玻璃房里看不见、摸不着真实世界的数据和动作。工具调用tool calling不是给API加个装饰它是给大模型装上手脚让它能真正“做事”。这篇文章讲的就是一个最精简、最透明、最可复现的AI Agent骨架——它只有两个工具乘法计算器和天气查询虽然是模拟的但它的结构、错误处理逻辑、消息流转机制和你未来接入支付网关、CRM系统、甚至控制IoT设备的Agent完全一致。它不依赖任何框架封装所有代码都在你眼皮底下运行它不回避真实问题比如模型胡乱调用工具、参数解析失败、无限循环调用它甚至把“为什么用pydantic.BaseModel而不是直接写dict”这种新手常踩的坑掰开揉碎讲清楚。如果你刚接触Agent开发它能让你30分钟内跑通第一个可交互的Agent如果你已在用LangChain或LlamaIndex它会帮你看清那些抽象层之下到底发生了什么。关键词OpenAI tool calling、AI Agent、函数调用、Pydantic Schema、消息历史管理、工具执行循环。适合所有想亲手造轮子、不想被黑盒框架牵着鼻子走的开发者。2. 整体设计思路从“模型输出JSON”到“系统完成任务”的完整闭环2.1 核心矛盾模型不执行只“建议”——这是设计起点很多初学者最大的误解是以为调用client.chat.completions.create(..., toolstools)后OpenAI服务器会自动帮你执行multiply(5, 6)。事实截然相反模型只负责“说”绝不“做”。它的输出永远是一段JSON字符串内容类似{name: multiply, arguments: {a: 5, b: 6}}。这个JSON是模型基于你的提示词system prompt和对话历史经过推理后“认为”此刻应该调用哪个工具、传什么参数。执行权100%在你手里。这个设计看似麻烦实则是安全与可控的基石。想象一下如果模型能自动调用银行转账API一个prompt注入攻击就能卷走你的钱。所以整个Agent的骨架本质是一个“决策-执行-反馈”的三步闭环模型决策输出tool call JSON→ 你执行解析JSON调用本地函数→ 你反馈把执行结果塞回消息流让模型看见→ 模型再决策……直到它决定“stop”。这个闭环的每一步都必须由你显式控制没有魔法。2.2 为什么必须用Pydantic定义输入模型手写JSON Schema的代价原文中InputMultiply(BaseModel)那段代码新手常觉得多此一举“我直接写个字典描述参数不行吗” 行但代价巨大。我们来对比两种方式手写Schema危险且易错multiply_tool { type: function, function: { name: multiply, description: Multiply two integers and returns the result integer, parameters: { type: object, properties: { a: {type: integer, description: first value}, b: {type: integer, description: second value} }, required: [a, b] # 这个字段你敢漏吗 } } }问题来了required数组必须和properties里的key严格一致漏一个模型就可能传空值type写成int还是integerOpenAI API只认integer如果参数是嵌套对象比如{user: {id: 123, profile: {age: 25}}}手写Schema的缩进、引号、逗号任何一个字符错误都会导致API报400错误而错误信息往往只说“invalid schema”你得逐行肉眼排查。Pydantic自动生成功能安全且省心class InputMultiply(BaseModel): a: int Field(descriptionfirst value) b: int Field(descriptionsecond value)model_json_schema()方法会自动生成包含required、正确type、甚至支持复杂嵌套结构的完整JSON Schema。它还做了类型校验如果你传aabcPydantic会在生成Schema前就抛出ValidationError把错误拦截在API调用之前。这就像给你的工具描述加了一道编译器检查。我试过在生产环境里因为手写Schema少了一个required字段导致模型在用户没提供必填参数时静默地传了null最终在数据库写入时报NOT NULL constraint failed而日志里根本找不到源头。用Pydantic这种问题在开发阶段就被掐死了。2.3 消息历史Messages是Agent的“记忆”与“上下文”——它必须精确管理Agent不是单次问答而是一场多轮对话。模型要理解“它刚才让我查了北京天气现在又让我把温度乘以2”就必须看到完整的对话历史。messages列表就是这个历史的载体。关键点在于它的结构和追加时机初始messages包含system角色设定和user用户第一句话。当模型返回finish_reason tool_calls时你必须将整个response.choices[0].message对象它包含了tool_calls字段追加到messages里。这一步极其重要它告诉模型“我收到了你的调用指令并已记录在案。”然后你执行工具得到结果function_response再将这个结果以role: tool、tool_call_id必须和原tool_call.id一致、name工具名的格式作为一条新消息追加进去。tool_call_id是关联键模型靠它知道“这个结果对应我刚才哪条指令”。如果漏掉第一步不追加tool_calls消息模型下次看到messages里只有原始提问和你的工具结果会一脸懵“谁让你查天气的我怎么不记得” 如果tool_call_id不匹配模型会直接忽略这条工具结果因为它无法建立关联。我踩过的最深的坑就是把tool_call.id误写成tool_call.function.name结果模型永远收不到工具返回值死循环10次后报错退出。messages不是日志它是Agent认知世界的唯一依据必须像维护数据库事务一样严谨。2.4MAX_ITERATIONS不是可有可无的保险丝——它是防止“AI发疯”的物理开关MAX_ITERATIONS 10这行代码表面看是防bug实则是防灾难。设想一个场景你的天气工具函数get_current_weather内部有个bug总是返回空字符串。模型收到空响应无法生成答案于是再次尝试调用天气工具……如此循环。没有MAX_ITERATIONS它会一直调用下去直到耗尽你的API配额或者触发OpenAI的速率限制rate limit返回429 Too Many Requests。更糟的是如果工具执行本身有副作用比如发邮件、扣款无限循环就是一场事故。MAX_ITERATIONS是硬性熔断器它强制在第10次迭代后停止并返回当前状态通常是未完成的中间结果。我在调试一个电商比价Agent时就遇到过模型在“获取商品A价格”和“获取商品B价格”之间反复横跳因为两个工具返回的格式不一致模型始终无法拼出最终结论。MAX_ITERATIONS让我能快速捕获这个现象而不是等它跑满一小时。它不是一个“优雅降级”的软开关而是一根实实在在的保险丝该烧断时必须烧断。3. 核心细节解析从工具定义到消息解析的每一处魔鬼3.1 工具定义的黄金法则函数、输入模型、描述三者必须严丝合缝一个可用的工具由三个部分构成缺一不可且必须一一对应函数本身multiply执行实际逻辑的Python函数。它必须是纯函数无副作用或副作用可控返回值最好是JSON序列化的对象如json.dumps({output: 30})方便后续解析。输入模型InputMultiply继承BaseModel的类用Field(description...)精确描述每个参数。description字段会被模型读取用于理解参数含义所以不能写“数字a”而要写“第一个参与乘法运算的整数”。工具描述multiply_tool由generate_tool_description生成的字典它把函数名、描述、输入模型的Schema三者打包。这个字典最终会通过tools参数传给OpenAI API。三者脱节的后果很直接如果函数签名是def multiply(a: int, b: int)但InputMultiply里定义了c: strgenerate_tool_description会报错因为inspect.signature拿到的参数和BaseModel的字段对不上。如果InputMultiply里a的description写成“乘数”而函数文档字符串写“被乘数”模型可能混淆参数顺序。如果函数返回30int但你的代码期望json.loads(function_response)得到一个字典就会抛JSONDecodeError。我坚持一个实践写完函数立刻写InputMultiply再立刻写函数文档字符串三者同步更新。在Git提交时这三行代码必须出现在同一个commit里。这听起来教条但能避免90%的工具调用失败。3.2available_functions字典Agent的“工具箱索引”——名字必须零误差available_functions {multiply: multiply_executor, get_current_weather: get_current_weather_executor}这行代码是Agent执行环节的“大脑”。当模型返回{name: multiply, ...}时你靠这个字典找到对应的multiply_executor函数去调用。这里的名字multiply必须和multiply_tool[function][name]里的名字完全一致包括大小写和下划线。OpenAI API返回的tool_call.function.name是字符串Python字典查找是精确匹配。我曾把工具名写成multiply_tool而字典里是multiply结果每次调用都报KeyError花了半小时才定位到这个拼写错误。更隐蔽的坑是如果你的函数名是getWeather而InputGetweather的类名是InputGetweathergenerate_tool_description会用函数名getWeather但你字典里写了get_weather那就永远对不上。我的经验是工具名统一用小写字母下划线snake_case和函数名保持绝对一致不要做任何转换。这样multiply函数 →multiply工具名 →multiply字典键形成一条清晰、无歧义的链条。3.3get_response_from_openai函数隐藏的重试与错误处理战场原文的get_response_from_openai函数过于简化只做了基础调用。在真实环境中网络抖动、API临时故障、token超限都是家常便饭。一个健壮的Agent必须内置重试逻辑。我推荐的增强版如下import time import random from openai import APIConnectionError, RateLimitError, APIStatusError def get_response_from_openai(tools, messages, clientOpenAI(), max_retries3): for attempt in range(max_retries): try: response client.chat.completions.create( modelMODEL, messagesmessages, toolstools, tool_choiceauto, timeout30 # 设置超时避免挂起 ) return response except APIConnectionError as e: print(f网络连接错误{2 ** attempt}秒后重试... ({e})) time.sleep(2 ** attempt random.uniform(0, 1)) except RateLimitError as e: print(f速率限制等待1分钟... ({e})) time.sleep(60) except APIStatusError as e: if e.status_code 400: print(f请求错误请检查tools或messages格式: {e}) raise e elif e.status_code 429: print(f配额超限等待1分钟... ({e})) time.sleep(60) else: print(fAPI服务端错误: {e}) raise e except Exception as e: print(f未知错误: {e}) raise e raise Exception(重试3次后仍失败)这个版本加入了指数退避重试Exponential Backoff第一次失败等1秒第二次等2秒第三次等4秒避免雪崩式重试。随机抖动Jitterrandom.uniform(0, 1)加一点随机时间防止大量Agent同时重试撞上同一波高峰。精准异常分类区分网络错误、限流、配额、格式错误不同错误采取不同策略。超时设置timeout30防止请求无限挂起拖垮整个Agent。没有这个重试层你的Agent在生产环境里会像纸糊的一样脆弱。我见过一个金融分析Agent因为一次偶然的网络超时整个批处理流程就中断了而它本可以安静地重试并继续。3.4chat函数中的消息追加tool_call_id是生命线绝不能错chat函数里工具执行后的消息追加是核心操作messages.append({ tool_call_id: tool_call.id, # 关键必须和原tool_call.id完全一致 role: tool, name: function_name, content: function_response, })tool_call.id是一个由OpenAI生成的唯一字符串形如call_abc123xyz。它不是你自己生成的也不是函数名它是OpenAI为这次调用分配的“工单号”。模型靠它把tool_calls指令和tool响应关联起来。如果这里写错了比如写成tool_call.function.name或者漏掉了tool_call.id模型就会“失联”它看到一条role: tool的消息但找不到对应的tool_call于是这条消息被忽略messages历史里就缺失了关键一环后续所有推理都基于错误的前提。我建议在调试时把tool_call.id和追加的tool_call_id都打印出来肉眼确认它们是否一模一样。这是一个“一错全错”的关键点值得你花10秒去验证。4. 实操过程从零开始搭建每一步都附带现场记录与参数说明4.1 环境准备与依赖安装一个干净的虚拟环境是成功的开始不要跳过这一步。我见过太多人因为全局Python环境混乱pydantic版本冲突v1和v2不兼容或者openai包版本太老不支持tool_choice参数导致卡在第一步。以下是经过我实测的、最稳妥的步骤创建独立虚拟环境强烈推荐# 创建名为ai_agent_env的虚拟环境 python -m venv ai_agent_env # 激活它Windows ai_agent_env\Scripts\activate.bat # 激活它macOS/Linux source ai_agent_env/bin/activate安装指定版本的依赖使用pip install时明确指定版本号避免自动升级引入不兼容变更。# 安装OpenAI官方SDK确保1.0.0 pip install openai1.35.11 # 安装Pydanticv2.xv1.x语法不同 pip install pydantic2.7.1 # 安装python-dotenv读取.env文件 pip install python-dotenv1.0.0 # 验证安装 pip list | grep -E (openai|pydantic|dotenv)提示openai1.35.11是我当前测试稳定的版本。如果你用更新的版本API调用方式如client.chat.completions.create基本不变但某些高级参数可能有调整。pydantic2.7.1是v2系列的稳定版其model_json_schema()方法是生成工具描述的核心。创建.env文件并配置API Key在项目根目录下创建一个名为.env的纯文本文件内容只有一行OPENAI_API_KEYsk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx注意OPENAI_API_KEY必须是你的Secret Key不是Organization ID或Project ID。它在OpenAI官网的 API Keys页面 生成。生成后立即复制并保存好页面刷新后就再也看不到了。把它放在.env里比硬编码在Python文件里安全得多也方便在不同环境开发/测试/生产间切换。4.2 编写utils.pygenerate_tool_description函数的完整实现与原理创建utils.py文件将原文的辅助函数补全。这里的关键是理解inspect.signature(func)和input_model.model_json_schema()如何协同工作。import json import inspect from pydantic import BaseModel def generate_tool_description(func, input_model: BaseModel): 生成OpenAI工具调用所需的JSON描述。 原理利用Python反射(inspect)获取函数签名获取函数文档 利用Pydantic的model_json_schema()自动推导输入参数的JSON Schema。 # 1. 获取函数签名用于提取参数名 signature inspect.signature(func) # 2. 获取函数文档字符串作为工具描述 func_doc func.__doc__ or fFunction to {func.__name__} # 3. 调用Pydantic方法生成完整的JSON Schema # model_json_schema()返回一个dict其中properties是参数定义 schema input_model.model_json_schema() # 4. 提取properties部分这是OpenAI需要的参数定义 properties schema.get(properties, {}) # 5. 构建最终的工具描述字典 # 注意OpenAI要求parameters下的type必须是object tool_description { type: function, function: { name: func.__name__, description: func_doc, parameters: { type: object, properties: properties, # 6. 关键Pydantic的schema里包含了required字段 # 我们必须把它提上来否则OpenAI不知道哪些是必填项 required: schema.get(required, []) } } } return tool_description实操心得schema.get(required, [])这一行是原文缺失的但至关重要。如果你不手动加上requiredOpenAI会认为所有参数都是可选的模型可能只传一个参数就调用函数导致你的函数因缺少参数而崩溃。Pydantic的model_json_schema()方法会自动分析Field(default...)和Field(default_factory...)来确定哪些是必填项schema.get(required)就是把它准确地提取出来。4.3 定义工具乘法与天气——从模拟到真实的平滑演进路径我们按原文定义两个工具但我会展示如何从“模拟”走向“真实”的扩展思路。乘法工具multiply# tools.py from pydantic import BaseModel, Field import json class InputMultiply(BaseModel): a: int Field(description第一个参与乘法运算的整数) b: int Field(description第二个参与乘法运算的整数) def multiply(a: int, b: int) - str: 将两个整数相乘并返回JSON格式的结果。 result a * b # 返回JSON字符串便于后续json.loads解析 return json.dumps({output: result, operation: multiply, inputs: {a: a, b: b}}) def multiply_executor(args): 执行乘法工具的包装器。 # args是json.loads后的dict直接解包 return multiply(args[a], args[b])实操心得multiply函数返回json.dumps(...)而不是原始int。这是因为chat函数里function_response会被当作字符串处理json.loads(function_response)需要一个合法的JSON字符串。如果直接返回30json.loads(30)会得到30int但json.loads({output: 30})会报错因为单引号不是JSON标准。所以所有工具函数的返回值都应该是json.dumps({...})生成的、双引号包裹的、符合JSON标准的字符串。天气工具get_current_weather# tools.py (续) import requests from typing import Optional class InputGetweather(BaseModel): location: str Field(description要查询天气的城市名称例如Beijing或Shanghai) def get_current_weather(location: str) - str: 获取指定城市的当前天气。 注意这是一个模拟函数。在生产环境中应替换为真实的API调用。 # 模拟真实API的响应结构 mock_data { Beijing: {temperature: 28 degree, type: cloudy, humidity: 65%}, Shanghai: {temperature: 32 degree, type: sunny, humidity: 78%}, Guangzhou: {temperature: 35 degree, type: rainy, humidity: 92%} } # 返回模拟数据或调用真实API weather mock_data.get(location.capitalize(), {temperature: unknown, type: unknown}) return json.dumps({location: location, weather: weather}) def get_current_weather_executor(args): 执行天气工具的包装器。 return get_current_weather(args[location]) # 可选替换为真实API例如OpenWeatherMap # def get_current_weather(location: str) - str: # API_KEY your_openweather_api_key # url fhttp://api.openweathermap.org/data/2.5/weather?q{location}appid{API_KEY}unitsmetric # try: # response requests.get(url, timeout10) # response.raise_for_status() # data response.json() # temp data[main][temp] # desc data[weather][0][description] # return json.dumps({location: location, temperature: f{temp}°C, type: desc}) # except Exception as e: # return json.dumps({error: fFailed to fetch weather: {str(e)}})实操心得模拟数据mock_data的键名用了location.capitalize()这样用户输入beijing或BEIJING都能匹配到Beijing。这是一个很小但很实用的用户体验优化。当你准备接入真实API时注释掉模拟部分取消注释真实API部分即可其他代码InputGetweather,get_current_weather_executor,available_functions完全不用改。这就是良好设计的威力接口interface稳定实现implementation可替换。4.4 主程序main.py整合所有模块启动交互式Agent现在把所有碎片组装起来。main.py是整个Agent的入口。# main.py import os import json from pydantic import BaseModel, Field from dotenv import load_dotenv from openai import OpenAI from utils import generate_tool_description from tools import multiply_executor, get_current_weather_executor, InputMultiply, InputGetweather # 1. 加载环境变量 load_dotenv() assert os.environ.get(OPENAI_API_KEY), 请在.env文件中设置OPENAI_API_KEY # 2. 初始化客户端和常量 client OpenAI() MODEL gpt-3.5-turbo SYSTEM 你是一个乐于助人的AI助手能够使用乘法计算器和天气查询工具来回答用户的问题。 MAX_ITERATIONS 10 # 3. 定义工具描述 multiply_tool generate_tool_description(multiply_executor, InputMultiply) get_weather_tool generate_tool_description(get_current_weather_executor, InputGetweather) # 4. 构建工具映射字典 available_functions { multiply: multiply_executor, get_current_weather: get_current_weather_executor } tools [multiply_tool, get_weather_tool] # 5. 核心API调用函数含重试 def get_response_from_openai(tools, messages, clientclient, max_retries3): # 此处插入上一节的增强版get_response_from_openai函数 pass # 6. 核心聊天循环 def chat(user_input): messages [ {role: system, content: SYSTEM}, {role: user, content: user_input} ] for i in range(MAX_ITERATIONS): print(f\n--- 第{i1}轮迭代 ---) try: response get_response_from_openai(toolstools, messagesmessages) except Exception as e: print(fAPI调用失败: {e}) return message response.choices[0].message finish_reason response.choices[0].finish_reason print(f模型结束原因: {finish_reason}) if finish_reason stop: print(f\n✅ 最终答案: {message.content}) break elif finish_reason tool_calls: print(f️ 模型建议调用工具:) for tool_call in message.tool_calls: print(f - 工具名: {tool_call.function.name}) print(f - 参数: {tool_call.function.arguments}) # 将模型的tool_calls消息追加到历史 messages.append(message) # 逐一执行工具调用 for tool_call in message.tool_calls: function_name tool_call.function.name function_to_call available_functions.get(function_name) if not function_to_call: print(f❌ 错误: 未找到工具 {function_name} 的执行器) continue try: # 解析参数 function_args json.loads(tool_call.function.arguments) print(f → 执行 {function_name}({function_args})) function_response function_to_call(function_args) print(f ← 工具返回: {function_response}) # 将工具执行结果追加为tool消息 messages.append({ tool_call_id: tool_call.id, role: tool, name: function_name, content: function_response, }) except json.JSONDecodeError as e: print(f❌ 参数解析错误: {e}) messages.append({ tool_call_id: tool_call.id, role: tool, name: function_name, content: json.dumps({error: Invalid JSON arguments}), }) except Exception as e: print(f❌ 工具执行错误: {e}) messages.append({ tool_call_id: tool_call.id, role: tool, name: function_name, content: json.dumps({error: str(e)}), }) else: print(f⚠️ 未知结束原因: {finish_reason}) break # 7. 用户交互主循环 def main(): print( 简易AI Agent已启动) print( 输入 stop 退出对话。) print( 尝试问5乘以6等于多少) print( 或者上海今天的天气怎么样) print(- * 50) while True: try: user_input input(\n 请输入您的问题: ).strip() if not user_input: continue if user_input.lower() stop: print( 再见) break chat(user_input) except KeyboardInterrupt: print(\n\n 强制退出。再见) break except EOFError: print(\n\n 输入结束。再见) break if __name__ __main__: main()实操心得我在chat函数里加入了详细的print语句每一行都标注了✅、️、❌等符号这些只是视觉标记不影响功能目的是让你在终端里能清晰地看到Agent的“思考”和“行动”轨迹。当你第一次运行时你会看到它如何一步步解析、调用、反馈。这种透明度是调试的基石。另外try...except块包围了json.loads和工具执行捕获了最常见的两类错误参数JSON格式错误和工具函数内部异常并将错误信息以JSON格式返回给模型让模型知道“这一步失败了”而不是让它在错误数据上继续推理。5. 常见问题与排查技巧实录那些让你抓狂的Bug我都替你踩过了5.1 典型问题速查表问题现象可能原因排查与解决方法KeyError: multiplyavailable_functions字典里没有multiply这个键或者键名拼写错误大小写、下划线。1. 检查available_functions定义确认键名和multiply_tool[function][name]完全一致。2. 在chat函数里print(available_functions.keys())确认字典里确实有这个键。JSONDecodeError: Expecting property name enclosed in double quotes工具函数返回的不是合法JSON字符串例如用了单引号或返回了原始int/dict。1. 确保所有工具函数都用json.dumps({...})返回。2. 在multiply_executor里print(repr(function_response))检查输出是否是{output: 30}字符串而不是{output: 30}字典。模型反复调用同一个工具永不stop工具返回的结果格式不符合模型预期模型无法从中提取答案或SYSTEM提示词不够强没告诉模型“得到工具结果后要总结”。1. 检查工具返回的JSON结构确保包含模型能理解的字段如temperature、output。2. 强化SYSTEM提示词例如加上“你必须在获得工具返回结果后用自己的话总结答案并给出最终回复。”400 Bad Request错误提示tools格式无效tools列表里的某个字典parameters下缺少type: object或required字段缺失/格式错误。1. 打印multiply_tool检查其结构是否和OpenAI文档要求一致。2. 确保generate_tool_description函数里包含了required: schema.get(required, [])。429 Rate Limit Error在短时间内发送了过多请求超过了OpenAI的免费额度或你的账户配额。1. 检查OpenAI平台的Usage Dashboard确认配额。2. 在get_response_from_openai里加入RateLimitError的重试逻辑见4.3节。3. 降低MAX_ITERATIONS或增加time.sleep(1)在循环内。5.2 我踩过的最深的三个坑与独家避坑技巧坑一tool_call.id的“幽灵”副本现象工具调用成功结果也返回了但模型下一轮还是说“请调用天气工具”仿佛没看到结果。 排查我打印了所有messages发现tool消息的tool_call_id和tool_calls消息里的id不一致。后来发现tool_call对象在循环中被多次引用我误用了tool_call.id的副本而那个副本在某次操作后被修改了。独家技巧永远直接使用tool_call.id不要赋值给中间变量再用。如果必须赋值用tool_call_id tool_call.id然后立刻用。或者最保险的做法是messages.append({tool_call_id: tool_call.id, ...})把tool_call.id的读取和使用放在同一行杜绝中间变量污染。坑二system消息的“隐形诅咒”现象Agent在multiply工具返回{output: 30}后回答“结果是30”但紧接着又说“我还可以帮你做别的事”。用户感觉很啰嗦。 原因SYSTEM提示词里写了“你是一个乐于助人的AI助手”模型过度解读了“乐于助人”在完成任务后还想主动提供额外帮助。独家技巧SYSTEM提示词要“冷酷无情”。改成“你是一个AI助手你的唯一任务是准确、简洁地回答用户的问题。你只能使用提供的工具。得到工具结果后立即给出最终答案不要添加任何额外解释、问候或主动提供帮助。” 这句话我反复测试过能显著提升回答的精准度和简洁度。坑三MAX_ITERATIONS的“假死”陷阱现象Agent在第10次迭代后退出