2026/8/11 13:15:56

手写AI Agent工具调用循环:从状态机设计到工程实践

手写AI Agent工具调用循环:从状态机设计到工程实践 1. 项目概述从“调用”到“循环”的认知跃迁最近和几个做AI应用的朋友聊天发现一个挺有意思的现象大家一提到“AI Agent”脑子里蹦出来的第一个画面往往是LangChain、AutoGPT这类框架或者是一个能自动上网查资料、写邮件的“智能体”。但当我们真正动手想从零构建一个属于自己的、能稳定完成特定任务的Agent时却常常卡在了一个看似基础实则至关重要的环节上——如何让这个Agent能持续、正确地调用外部工具并基于结果做出下一步决策这个持续运转的机制就是所谓的“Tool Loop”工具调用循环。你可能会想这不就是让大模型LLM输出一个JSON然后我去解析、执行对应的函数吗一开始我也这么认为但实际做下来才发现一个健壮的Tool Loop远不止于此。它本质上是一个状态机需要处理LLM输出的不确定性、工具执行可能失败、上下文如何有效管理、循环何时终止等一系列问题。网上很多教程和框架帮你封装好了这一切但如果你不亲手拆开这个“黑盒”写一遍就很难真正理解Agent决策的脉络遇到诡异Bug时也无从下手调试。今天我就以“手写一个AI Agent工具调用循环”为目标抛开那些重型框架只用最基础的Python和OpenAI API或其他你熟悉的LLM接口带你走一遍从设计到实现的完整过程。我们会从最核心的循环状态机开始逐步加入错误处理、上下文管理、动态工具选择等高级特性。无论你是想深入理解Agent原理还是需要为一个轻量级、定制化场景构建专属的自动化流程这篇内容都能给你提供可直接复用的代码骨架和避坑指南。2. 核心循环机制的设计与状态拆解一个完整的Tool Loop其核心是一个循环处理流程。但这个循环不是简单的while True它必须清晰地定义几个关键状态并在状态间有序转换。这是整个系统稳定性的基石。2.1 定义循环的四大核心状态经过多次实践我通常将一次工具调用循环的生命周期划分为四个状态PLANNING规划Agent根据当前任务和上下文决定下一步要做什么。这是LLM发挥核心推理能力的地方输出应该是一个结构化的“行动计划”通常包含要调用的工具名称和参数。EXECUTION执行根据PLANNING阶段输出的计划定位到具体的工具函数并传入参数执行。这里需要和外部系统、API或数据库交互。OBSERVATION观察捕获工具执行的结果。这个结果可能是成功的数据也可能是各种类型的错误如网络超时、参数错误、权限不足等。REASONING推理评估上一步的观察结果。结合历史决定下一步动作是任务已完成终止循环还是需要根据工具返回的结果继续调用下一个工具回到PLANNING亦或是工具执行失败需要调整参数重试或选择备用方案这个状态机模型的美妙之处在于它将LLM的“思考”和外部世界的“执行”清晰解耦。LLM专注于在PLANNING和REASONING状态进行推理和决策而EXECUTION和OBSERVATION则是对确定性的外部环境的操作与反馈。2.2 为何要手写状态机框架的“甜蜜负担”你可能会问LangChain的AgentExecutor或者AutoGPT的架构不是已经实现了这些吗没错但它们带来便利的同时也引入了复杂性。当你使用一个高级框架时调试困难框架内部状态流转复杂当循环出现意外中断或工具调用不符合预期时定位问题如同大海捞针。定制成本高如果你想实现一个非常规的逻辑比如在特定工具失败后不是重试而是直接切换到一套降级流程可能需要深入框架源码理解其抽象层反而比从头写更耗时。理解断层直接使用框架容易让你成为一个“调包侠”对Agent如何思考、如何决策缺乏第一性的理解。手写这个循环就像亲手组装一台收音机。你能清楚地知道每一个电阻、电容的作用当收音机不响时你也有能力一步步检查电路。这对于构建需要高可靠性的生产级Agent或者进行深入的学术研究是至关重要的。3. 基础实现构建一个最小可用的Tool Loop理论说再多不如一行代码。让我们从最简单的场景开始一个能调用预设工具并持续运行直到LLM说“任务完成”的循环。3.1 环境准备与工具定义首先确保你有Python环境并安装了openai库这里以OpenAI API为例你也可以替换为任何提供类似聊天补全功能的LLM服务。pip install openai接下来我们定义几个简单的工具函数。这是你的Agent能够操作外部世界的“手”和“脚”。# tool_functions.py def search_web(query: str) - str: 模拟一个网络搜索工具。在实际应用中这里会调用SerpAPI、Google Search API等。 print(f[工具调用] 搜索网络关键词: {query}) # 模拟返回结果 return f关于{query}的搜索结果...模拟数据 def calculate(expression: str) - str: 模拟一个计算器工具。 print(f[工具调用] 计算表达式: {expression}) try: result eval(expression) # 注意生产环境请使用更安全的评估方法如ast.literal_eval return f计算结果: {result} except Exception as e: return f计算错误: {e} def get_weather(city: str) - str: 模拟一个天气查询工具。 print(f[工具调用] 查询{city}的天气) # 模拟返回 weather_data { 北京: 晴15-25°C, 上海: 多云18-28°C, 深圳: 阵雨22-30°C } return weather_data.get(city, f未找到{city}的天气信息)我们需要一个方式来告诉LLM这些工具的存在及其用法。通常我们会构造一个“工具描述”列表作为系统提示词的一部分。def get_tool_descriptions(): 生成供LLM识别的工具描述列表。 tools [ { name: search_web, description: 当需要获取最新的、未知的或实时信息时使用此工具。输入是一个搜索查询字符串。, parameters: { type: object, properties: { query: {type: string, description: 搜索关键词} }, required: [query] } }, { name: calculate, description: 用于执行数学计算。输入是一个数学表达式字符串。, parameters: { type: object, properties: { expression: {type: string, description: 数学表达式如(35)*2} }, required: [expression] } }, { name: get_weather, description: 查询指定城市的当前天气情况。, parameters: { type: object, properties: { city: {type: string, description: 城市名称例如‘北京’、‘上海’} }, required: [city] } } ] return tools注意这里的工具描述格式参考了OpenAI的Function Calling格式这是一种被广泛支持的标准。它明确了工具的名称、描述和参数schema能极大地提升LLM调用工具的准确性。3.2 实现核心循环状态机现在我们来编写最核心的循环逻辑。我们将用一个字典来维护当前的状态并实现状态间的转换。# tool_loop_core.py import json import openai # 请先设置你的OPENAI_API_KEY环境变量 class SimpleToolLoop: def __init__(self, llm_client, tools_dict, max_iterations10): 初始化简单工具循环。 :param llm_client: LLM客户端实例如openai.OpenAI :param tools_dict: 工具名称到函数对象的映射如 {search_web: search_web_func} :param max_iterations: 最大循环迭代次数防止无限循环 self.llm llm_client self.tools tools_dict self.max_iterations max_iterations # 初始化对话历史用于给LLM提供上下文 self.conversation_history [ {role: system, content: 你是一个有帮助的AI助手可以调用工具来解决问题。请根据用户需求逐步思考并调用合适的工具。当你拥有足够信息可以给出最终答案时请直接回复用户不要调用工具。} ] def _call_llm_for_plan(self, user_query): PLANNING状态调用LLM让其决定下一步行动。 # 构建包含工具描述的提示词 tool_descriptions get_tool_descriptions() prompt f当前可用工具{json.dumps(tool_descriptions, ensure_asciiFalse)}\n\n用户问题{user_query}\n\n请分析是否需要调用工具。如果需要请严格按照以下JSON格式回复只输出这个JSON对象\n{{\action\: \call_tool\, \tool_name\: \工具名\, \tool_input\: {{...工具参数...}} }}\n如果不需要调用工具可以直接给出最终答案请回复\n{{\action\: \final_answer\, \content\: \你的答案\}} # 将历史对话和当前规划提示合并 messages self.conversation_history [{role: user, content: prompt}] try: response self.llm.chat.completions.create( modelgpt-3.5-turbo, # 或 gpt-4 messagesmessages, temperature0.1, # 低温度保证输出格式稳定 max_tokens500 ) llm_output response.choices[0].message.content.strip() # 尝试解析LLM的输出为JSON return json.loads(llm_output) except json.JSONDecodeError: # 如果LLM没有返回合法JSON可能它想直接说话 print(fLLM返回无法解析为JSON: {llm_output}) return {action: final_answer, content: llm_output} except Exception as e: print(f调用LLM失败: {e}) return {action: error, content: fLLM调用异常: {e}} def _execute_tool(self, tool_name, tool_input): EXECUTION状态执行指定的工具。 if tool_name not in self.tools: return f错误未知工具 {tool_name} try: tool_func self.tools[tool_name] # 根据工具函数签名传入参数 if isinstance(tool_input, dict): result tool_func(**tool_input) else: # 如果工具输入不是字典尝试作为单一参数传入 result tool_func(tool_input) return result except Exception as e: return f工具执行错误: {e} def run(self, user_query): 运行主循环。 print(f用户问题: {user_query}) self.conversation_history.append({role: user, content: user_query}) for i in range(self.max_iterations): print(f\n--- 第 {i1} 次迭代 ---) # 1. PLANNING print([状态] PLANNING...) plan self._call_llm_for_plan(user_query) print(fLLM计划: {plan}) action plan.get(action) if action call_tool: # 2. EXECUTION tool_name plan.get(tool_name) tool_input plan.get(tool_input, {}) print(f[状态] EXECUTION - 调用工具: {tool_name}, 参数: {tool_input}) observation self._execute_tool(tool_name, tool_input) print(f[状态] OBSERVATION - 工具结果: {observation[:100]}...) # 打印前100字符 # 将工具调用和结果加入历史为后续REASONING提供上下文 self.conversation_history.append({ role: assistant, content: f我调用了工具{tool_name}输入是{tool_input}。 }) # 通常我们会以一个特殊的“系统”或“工具”角色来添加结果这里简化处理 self.conversation_history.append({ role: user, # 用user角色模拟工具返回简化逻辑 content: f工具{tool_name}返回的结果是{observation} }) # 循环继续下一次迭代的PLANNING会基于更新后的历史 elif action final_answer: # 3. REASONING 得出结论任务完成 final_content plan.get(content, 任务完成。) print(f[状态] REASONING - 得出最终答案循环终止。) print(f最终答案: {final_content}) self.conversation_history.append({role: assistant, content: final_content}) return final_content else: # 处理错误或未知action error_msg plan.get(content, 未知行动指令) print(f[状态] ERROR - {error_msg}) return f处理过程中出错: {error_msg} print(f达到最大迭代次数 {self.max_iterations}强制终止。) return 任务未能在限制步骤内完成。3.3 运行你的第一个Agent现在让我们把它跑起来。# main.py from openai import OpenAI from tool_functions import search_web, calculate, get_weather from tool_loop_core import SimpleToolLoop # 1. 初始化LLM客户端请替换为你的API Key client OpenAI(api_keyyour-api-key-here) # 更佳实践是从环境变量读取 # 2. 准备工具映射 available_tools { search_web: search_web, calculate: calculate, get_weather: get_weather } # 3. 创建循环实例 agent SimpleToolLoop(client, available_tools, max_iterations5) # 4. 提问 result agent.run(先查询一下北京今天的天气然后计算如果气温下降5度那么温度会是多少) print(\n 运行结束 )运行这段代码你会看到控制台打印出类似下面的状态流转信息用户问题: 先查询一下北京今天的天气然后计算如果气温下降5度那么温度会是多少 --- 第 1 次迭代 --- [状态] PLANNING... LLM计划: {action: call_tool, tool_name: get_weather, tool_input: {city: 北京}} [状态] EXECUTION - 调用工具: get_weather, 参数: {city: 北京} [工具调用] 查询北京的天气 [状态] OBSERVATION - 工具结果: 晴15-25°C... --- 第 2 次迭代 --- [状态] PLANNING... LLM计划: {action: call_tool, tool_name: calculate, tool_input: {expression: 25-5}} # 注意这里LLM需要从天气结果中提取数字 [状态] EXECUTION - 调用工具: calculate, 参数: {expression: 25-5} [工具调用] 计算表达式: 25-5 [状态] OBSERVATION - 工具结果: 计算结果: 20... --- 第 3 次迭代 --- [状态] PLANNING... LLM计划: {action: final_answer, content: 北京今天天气是晴15-25°C。如果气温下降5度那么最高温度将会是20°C。} [状态] REASONING - 得出最终答案循环终止。 最终答案: 北京今天天气是晴15-25°C。如果气温下降5度那么最高温度将会是20°C。 运行结束 恭喜你已经实现了一个最基础的、能进行多步推理和工具调用的AI Agent循环。它虽然简陋但完整地体现了PLAN - ACT - OBSERVE - REASON的核心思想。4. 进阶优化让循环更健壮、更智能基础循环能跑通但离“健壮”和“好用”还差得远。在实际项目中我们至少需要处理以下几个棘手的问题。4.1 强化LLM输出的解析与稳定性上面的代码中我们粗暴地要求LLM输出一个JSON字符串并用json.loads()解析。这在实践中非常脆弱LLM可能在JSON外添加额外解释。JSON格式可能轻微错误如尾随逗号、单引号。LLM可能“忘记”格式要求直接输出自然语言。解决方案使用Function Calling或结构化输出。OpenAI、Anthropic等主流API都原生支持Function Calling。它不再是让LLM“生成”一个JSON字符串而是通过API参数声明你希望调用的函数LLM会在响应中返回一个结构化的、指明它“想要调用哪个函数以及参数是什么”的消息。这极大地提高了可靠性和准确性。def _call_llm_with_function_calling(self, user_query): 使用OpenAI的Function Calling进行规划。 tool_descriptions get_tool_descriptions() messages self.conversation_history [{role: user, content: user_query}] try: response self.llm.chat.completions.create( modelgpt-3.5-turbo, messagesmessages, tools[{type: function, function: tool} for tool in tool_descriptions], # 关键将工具描述作为“tools”参数传入 tool_choiceauto, # 让模型自行决定是否调用工具 ) response_message response.choices[0].message # 检查LLM是否想要调用工具 if response_message.tool_calls: # 通常一次只调用一个工具我们取第一个 tool_call response_message.tool_calls[0] tool_name tool_call.function.name try: tool_args json.loads(tool_call.function.arguments) except json.JSONDecodeError: tool_args {} return { action: call_tool, tool_name: tool_name, tool_input: tool_args, tool_call_id: tool_call.id # 保留ID用于后续关联结果 } else: # LLM直接给出了最终回答 return { action: final_answer, content: response_message.content } except Exception as e: return {action: error, content: fLLM调用异常: {e}}使用Function Calling后LLM输出错误格式的概率大大降低。同时API响应中的tool_call_id可以用于在后续对话中精确地将工具执行结果关联回这次调用保持上下文的连贯性。实操心得如果你的LLM提供商不支持Function Calling退而求其次的方法是使用“结构化输出”提示工程。例如在提示词中要求LLM输出严格遵循JSON Schema的文本并在解析失败时设计重试或降级逻辑。但无论如何对LLM输出的解析都必须有异常处理绝不能假设它总是正确的。4.2 设计复杂的错误处理与重试机制工具执行可能失败原因千奇百怪网络波动、API限流、参数无效、资源不存在等等。一个健壮的循环必须能妥善处理这些错误。错误分类与处理策略错误类型可能原因建议处理策略可重试错误网络超时、临时性服务不可用5xx错误延迟后自动重试如最多3次指数退避输入错误参数格式错误、缺少必要参数、参数值无效不重试。将错误信息反馈给LLM让其重新规划调整参数或选择其他工具逻辑/权限错误资源不存在、权限不足、余额不够不重试。将明确的错误信息反馈给LLM让其调整任务目标或终止任务。工具本身错误工具函数内部Bug捕获异常记录日志反馈通用错误信息并考虑将工具标记为“不可用”。我们需要在_execute_tool方法中加入更细致的错误捕获并在主循环的OBSERVATION和REASONING阶段加入对错误结果的判断。def _execute_tool_with_retry(self, tool_name, tool_input, max_retries3): 执行工具包含重试机制。 for attempt in range(max_retries): try: result self._execute_tool(tool_name, tool_input) # 调用基础的执行方法 # 这里可以加入对结果内容的检查判断是否为业务逻辑错误 if 错误 in result or 失败 in result: # 简单示例实际应根据工具返回约定判断 return result # 返回业务错误不重试 return result except requests.exceptions.Timeout: if attempt max_retries - 1: wait_time (2 ** attempt) random.random() # 指数退避 print(f工具{tool_name}调用超时{wait_time:.1f}秒后重试...) time.sleep(wait_time) continue else: return f错误工具{tool_name}调用超时已达最大重试次数。 except requests.exceptions.ConnectionError: return f错误网络连接失败请检查网络。 except Exception as e: # 其他未预见的异常 return f工具{tool_name}执行时发生未预期错误: {repr(e)} return f错误工具{tool_name}执行失败。 def run_robust(self, user_query): 增强版运行循环包含错误处理。 self.conversation_history.append({role: user, content: user_query}) for i in range(self.max_iterations): print(f\n--- 第 {i1} 次迭代 ---) # PLANNING plan self._call_llm_with_function_calling(user_query) if plan[action] call_tool: tool_name, tool_input plan[tool_name], plan[tool_input] # EXECUTION (with retry) observation self._execute_tool_with_retry(tool_name, tool_input) # OBSERVATION REASONING # 关键将执行结果无论成功失败以结构化方式加入历史 self._append_tool_result_to_history(plan.get(tool_call_id), tool_name, observation) # 判断是否因错误需要终止循环这里我们可以加入规则。 # 例如连续多次工具调用失败则可能任务无法继续。 if self._is_critical_error(observation): return f任务因关键错误中断{observation} # 否则循环继续 elif plan[action] final_answer: return plan[content] else: # 处理LLM自身的错误 return fLLM规划阶段出错{plan.get(content)} return 任务超时未完成。 def _append_tool_result_to_history(self, tool_call_id, tool_name, result): 将工具调用结果以标准格式加入对话历史。 # 这是遵循OpenAI Function Calling约定的格式 self.conversation_history.append({ role: tool, tool_call_id: tool_call_id, content: result }) def _is_critical_error(self, observation): 判断观察结果是否为需要终止循环的关键错误。 critical_errors [权限不足, 资源不存在, 无法访问] return any(err in observation for err in critical_errors)4.3 上下文管理与历史压缩随着循环进行对话历史会越来越长。这带来两个问题1) 可能超出LLM的上下文窗口限制2) 无关历史可能干扰LLM的当前决策。解决方案智能的历史摘要与窗口滑动。我们不能简单粗暴地只保留最近N条消息因为早期的关键指令如用户需求可能被丢掉。一个常见的策略是系统指令永驻始终保持最初的系统提示词。用户查询永驻保持最初的任务描述。工具调用与结果摘要将过去多次的工具调用和结果压缩成一条总结性的消息。保留近期完整交互保留最近2-3轮完整的LLM输出-工具结果交互。def _compress_conversation_history(self): 压缩对话历史以节省Token并保持关键信息。 if len(self.conversation_history) 8: # 设定一个阈值 return # 假设历史结构[系统消息 用户查询 后续多轮交互...] compressed_history [] compressed_history.append(self.conversation_history[0]) # 系统消息 compressed_history.append(self.conversation_history[1]) # 初始用户查询 # 将中间大量的工具调用和结果进行摘要 middle_messages self.conversation_history[2:-4] # 保留最后两轮完整交互 if middle_messages: summary 在此之前我已经进行了一系列操作 # 这里可以调用另一个LLM来生成摘要简单起见我们手动提取关键信息 tool_calls [] for msg in middle_messages: if msg.get(role) assistant and tool_call_id in msg: tool_calls.append(f调用了{msg.get(tool_name)}工具) elif msg.get(role) tool: tool_calls[-1] f结果概要{msg[content][:50]}... # 关联结果 summary .join(tool_calls) compressed_history.append({role: system, content: summary}) # 保留最近几轮完整交互 compressed_history.extend(self.conversation_history[-4:]) self.conversation_history compressed_history然后在主循环的每次迭代后可以判断历史长度决定是否触发压缩。注意事项历史压缩是一把双刃剑。过度压缩可能导致LLM丢失重要细节。对于需要精确记忆的任务如多轮对话中的精确数据慎用压缩或者采用更高级的向量检索记忆库如RAG来管理长期记忆。5. 高级特性与扩展思路当你掌握了基础循环和健壮性优化后可以尝试为你的Agent注入更多“智能”。5.1 动态工具发现与加载上面的例子中工具列表是启动时就固定的。但在一个插件化系统中工具可能是动态注册的。我们可以实现一个工具注册表允许在运行时添加或移除工具并在每次规划时将最新的工具描述列表提供给LLM。class DynamicToolLoop(SimpleToolLoop): def __init__(self, llm_client, max_iterations10): super().__init__(llm_client, {}, max_iterations) self.tool_registry {} # 名称 - 函数对象 self.tool_descriptions_cache None self._update_tool_descriptions() def register_tool(self, name: str, func: callable, description: str, parameters: dict): 动态注册一个工具。 self.tool_registry[name] func self._update_tool_descriptions() print(f工具已注册: {name}) def _update_tool_descriptions(self): 根据注册表更新工具描述JSON。 descriptions [] for name, func in self.tool_registry.items(): # 这里需要一个机制将函数和其描述、参数schema关联起来。 # 一个简单做法是用装饰器或在一个中央配置中维护这些元数据。 # 此处为示例假设我们从某个配置中获取。 desc get_tool_metadata(name) # 假设的函数 if desc: descriptions.append(desc) self.tool_descriptions_cache descriptions def _call_llm_for_plan(self, user_query): # 使用最新的工具描述缓存 # ... 其余逻辑与父类相同但使用 self.tool_descriptions_cache5.2 多Agent协作与子任务分解复杂的任务可能需要多个各司其职的Agent协作完成。你的主循环可以演变成一个协调器Orchestrator它负责解析顶级任务将其分解为子任务然后创建或调用专门的子Agent每个子Agent本身就是一个Tool Loop去执行最后汇总结果。这引入了新的状态如DECOMPOSE任务分解、DISPATCH派发给子Agent、COLLECT收集结果和AGGREGATE汇总。实现这一模式你的手写循环就升级成了一个微型的Agent调度框架。5.3 集成长期记忆与知识库要让Agent真正“有记性”需要将循环过程中的关键信息如执行结果、学到的知识存储到向量数据库或图数据库中。在后续的PLANNING阶段除了当前对话历史还可以先进行一个RETRIEVE步骤从记忆库中检索相关的历史经验或知识片段作为上下文提供给LLM从而实现持续学习和更优的决策。6. 常见问题与调试技巧实录在实际手写和调试Tool Loop的过程中我踩过不少坑这里分享几个最典型的。6.1 LLM不按格式输出导致解析失败问题明明在提示词里要求输出JSONLLM却回复“好的我将调用xxx工具参数是...”。排查检查提示词提示词是否足够清晰、强硬尝试使用“你必须只输出JSON不要有任何其他文字”这类指令。检查Temperature过高的temperature如0.7会增加输出的随机性导致格式错误。在规划阶段建议设置为0.1或0。使用Function Calling这是最根本的解决方案能几乎杜绝格式问题。加入后处理如果必须用文本解析写一个健壮的解析器尝试用正则表达式提取JSON块或者使用json.loads()的strictFalse参数并准备一个fallback逻辑。6.2 循环陷入死胡同或无限循环问题Agent反复调用同一个工具或者在不该调用工具的时候继续调用无法输出最终答案。排查设置硬性上限就像我们代码里的max_iterations这是最后的安全网。检查工具结果工具返回的结果是否清晰、无歧义一个模糊的结果如“操作成功”可能让LLM无法判断任务是否完成。确保工具结果包含足够的信息量。强化系统提示在系统提示词中明确告诉LLM“当你拥有回答问题所需的全部信息时请直接给出最终答案停止调用工具。”可以举例说明。引入超时或重复检测在循环状态中记录每个工具被调用的次数和参数。如果检测到在短时间内用相同/相似参数重复调用同一工具可以强制中断并反馈错误。6.3 工具执行结果未被正确纳入上下文问题Agent“忘记”了上一步工具执行的结果在下一步规划中又要求调用同一个工具。排查检查历史记录格式确保工具执行结果被正确地以LLM能理解的格式添加到了对话历史中。对于OpenAI Function Calling必须使用role: “tool”的消息。验证Token长度可能历史太长早期的工具结果被截断了。实现上文提到的历史压缩机制。在提示词中强调可以在每次规划时在用户消息中手动拼接上一步的结果作为显式提醒。6.4 性能瓶颈问题每个循环都要等待LLM响应和工具执行串行操作导致总耗时很长。优化并行工具调用如果多个工具调用之间没有依赖关系可以在PLANNING阶段让LLM一次性规划多个并行操作然后同时执行。流式响应对于需要长时间运行的工具考虑使用异步调用并在等待时先给用户一个中间状态反馈。缓存对频繁且结果不变的查询如“北京的天气”实际上半小时内变化不大可以引入缓存机制避免重复调用外部API。手写一个AI Agent的工具调用循环是一个从“知其然”到“知其所以然”的绝佳实践。它强迫你去思考Agent每一个决策背后的逻辑去处理现实世界中的各种不确定性。当你亲手实现了状态管理、错误处理和上下文流转后再去使用那些高级框架你会更加得心应手也更能理解它们的设计哲学和潜在局限。这个循环就是智能体与世界交互的“心跳”掌握它你就握住了构建自主智能应用的核心脉搏。