从零构建AI Agent:LLM驱动本地工具调用的核心原理与实战
1. 项目概述为什么现在要自己动手实现一个AI Agent最近几个月AI Agent这个概念火得不行感觉身边搞技术的朋友都在聊。但说实话很多讨论都停留在概念层面或者直接甩给你一个LangChain、AutoGen这样的重型框架看完了文档还是一头雾水不知道核心的轮子是怎么转起来的。这就像学开车直接给你一辆全自动的智能汽车你虽然能开但永远不知道引擎盖下面发生了什么。所以我决定抛开那些复杂的框架回归本质带大家从最基础的模块开始亲手搭建一个能“思考”并“动手”的AI Agent。我们这个项目的目标非常明确实现一个能够理解用户指令、自主调用本地工具Tool来完成任务的智能体。比如你告诉它“帮我查一下当前文件夹里最大的三个文件是什么”它应该能理解你的意图然后调用我们预先写好的“查找大文件”工具执行后把结果用自然语言反馈给你。这听起来是不是有点像给ChatGPT装上了“手”和“脚”没错其核心思想就是大语言模型LLM作为“大脑”负责理解和规划本地工具作为“肢体”负责执行具体操作。大脑和肢体之间需要一套清晰的“沟通协议”和“调度机制”这就是我们要实现的关键。为什么选择“LLM对话 本地Tool调用”这个组合作为起点呢首先这是所有智能体最基础、最核心的能力闭环。理解、决策、执行、反馈构成了智能行为的基本单元。其次本地工具调用意味着完全可控、无网络依赖、安全性高非常适合处理企业内部数据、自动化个人工作流等场景。最后从零实现这个过程能让你彻底吃透Agent的工作原理未来无论使用什么框架你都能心中有数快速定位问题。2. 核心架构设计大脑、工具库与调度中枢一个能自主调用工具的AI Agent其架构可以类比为一个高效的项目团队。LLM是团队中的“项目经理”或“策略分析师”它不直接干活但负责理解客户用户的需求并制定执行方案。而各种各样的本地工具就是团队里的“开发工程师”、“测试工程师”、“运维工程师”他们各有专长负责具体的实施。2.1 三大核心组件拆解我们的系统主要由三个部分组成它们之间的协作关系决定了Agent的智能程度和稳定性。1. 大脑大语言模型LLM这是Agent的智能核心。它的核心职责不是直接生成最终答案而是进行意图识别和任务规划。意图识别分析用户的自然语言指令判断用户到底想干什么。例如“今天的天气怎么样”是查询天气“把‘报告.pdf’发给我”是查找并发送文件。任务规划如果任务需要多个步骤或工具LLM需要将其分解成一个可执行的序列。比如“总结一下上周的销售数据并生成图表”就需要先“读取数据文件”再“分析数据”最后“调用图表生成工具”。注意这里有一个关键点我们通常使用LLM的对话接口Chat Completion而不是补全接口。因为我们需要的是结构化的决策输出例如JSON格式的调用指令而不是一段自由的文本。同时为了控制成本和提高响应速度项目初期强烈建议使用本地部署的开源模型如Qwen、ChatGLM、Llama等或性价比高的API服务。2. 工具库本地可执行函数工具Tool的本质就是一个有明确定义的函数。一个合格的工具有三个要素功能描述用自然语言清晰说明这个工具是干什么的。这是给LLM“看”的决定了LLM是否能正确选择它。参数规范定义函数需要哪些输入参数以及每个参数的类型和含义。LLM需要从用户指令中提取或推断出这些参数。执行函数具体的代码实现。它执行真正的操作比如读写文件、调用系统命令、计算数据、发送邮件等。3. 调度中枢Agent执行引擎这是连接大脑和工具库的“神经系统”和“调度中心”。它需要完成以下工作工具注册与管理维护一个工具目录并能向LLM提供这些工具的详细描述。对话与决策循环将用户输入和工具描述一起提交给LLM获取LLM的决策通常是“直接回答”或“调用某个工具”。参数提取与验证解析LLM返回的决策提取调用工具所需的参数并检查其有效性如文件路径是否存在。安全沙箱执行在受控的环境中调用工具函数防止恶意工具对系统造成破坏。结果处理与反馈将工具执行的结果成功或失败重新组织成自然语言反馈给LLM或用户并决定下一步动作继续调用工具还是结束对话。2.2 技术选型背后的思考为什么不用现成的框架在从零开始的过程中每一个技术选型都值得深思。LLM接口选择我们直接使用OpenAI格式的Chat Completion API。这几乎成了行业标准无论是调用云端API如OpenAI、DeepSeek还是本地模型通过Ollama、LM Studio等部署接口都是一致的极大提高了项目的可移植性。通信格式LLM和调度中枢之间需要一种无歧义的通信方式。JSON是最佳选择。我们会要求LLM始终返回结构化的JSON数据例如{action: call_tool, tool_name: search_files, arguments: {pattern: *.pdf}}。这种格式便于程序解析也减少了LLM“胡言乱语”带来的解析失败风险。开发语言选择Python。原因很简单生态丰富。无论是操作文件、处理数据、连接网络还是机器学习相关的库Python都有最成熟的支持。而且将函数作为工具暴露出来在Python中非常自然。3. 从零开始构建你的第一个工具与Agent骨架理论讲得再多不如动手写一行代码。让我们从一个最简单的“Hello World”级Agent开始。3.1 第一步定义一个清晰的工具我们先创建一个名为toolkit.py的文件在这里定义我们的工具库。# toolkit.py import json import subprocess from datetime import datetime from typing import Dict, Any def get_current_time() - str: 获取当前的系统时间。 无需任何参数。 返回一个格式化的时间字符串。 now datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S) def search_files_by_keyword(directory: str, keyword: str) - list: 在指定目录及其子目录中搜索文件名包含特定关键词的文件。 参数: directory (str): 要搜索的目录路径。 keyword (str): 需要匹配的关键词。 返回: list: 匹配到的文件路径列表。 # 这里使用简单的os.walk实现实际应用中可以考虑更高效的方法 import os matched_files [] for root, dirs, files in os.walk(directory): for file in files: if keyword.lower() in file.lower(): matched_files.append(os.path.join(root, file)) return matched_files # 工具元信息字典用于向LLM描述工具 TOOLS_META [ { name: get_current_time, description: 获取当前的日期和时间。, parameters: { type: object, properties: {}, # 此工具无参数 required: [] } }, { name: search_files_by_keyword, description: 根据文件名中的关键词搜索文件。, parameters: { type: object, properties: { directory: {type: string, description: 要搜索的根目录路径。}, keyword: {type: string, description: 文件名中需要包含的关键词。} }, required: [directory, keyword] } } ]关键点解析每个工具函数都必须有清晰、完整的文档字符串Docstring。LLM正是依靠这些描述来理解工具功能的。描述要具体避免歧义例如“处理文件”就太模糊“搜索包含关键词的文件”就明确得多。TOOLS_META列表是工具的“说明书”。它严格按照类似OpenAI Function Calling的格式定义包含了工具名、描述和参数模式。这个列表会被拼接到给LLM的提示词Prompt中。3.2 第二步实现核心调度引擎接下来我们创建agent_core.py实现最核心的调度逻辑。# agent_core.py import json import logging from typing import Dict, Any, Optional # 假设我们有一个调用LLM的函数这里先用一个模拟函数代替 def call_llm(messages: list, tools: list) - Dict[str, Any]: 模拟LLM调用。在实际项目中这里应替换为真实的API调用如OpenAI, Claude或本地模型。 我们假设LLM返回一个有效的JSON对象。 # 这是一个极其简化的模拟仅用于演示逻辑。 # 真实情况需要处理网络请求、错误、不同的响应格式等。 user_input messages[-1][content] if 时间 in user_input: return {action: call_tool, tool_name: get_current_time, arguments: {}} elif 搜索 in user_input or 文件 in user_input: # 这里本应由LLM解析出参数我们手动模拟一下 return {action: call_tool, tool_name: search_files_by_keyword, arguments: {directory: ., keyword: test}} else: return {action: final_answer, content: f我收到了你的消息{user_input}但我目前还不知道如何处理这个请求。} class SimpleAgent: def __init__(self, tools_meta: list, toolkit_module): 初始化Agent。 :param tools_meta: 工具元信息列表即TOOLS_META。 :param toolkit_module: 包含实际工具函数的模块如import toolkit。 self.tools_meta tools_meta self.toolkit toolkit_module self.conversation_history [] # 维护对话历史 logging.basicConfig(levellogging.INFO) self.logger logging.getLogger(__name__) def _build_system_prompt(self) - str: 构建系统提示词定义Agent的角色和能力。 prompt 你是一个有帮助的AI助手可以调用工具来帮助用户解决问题。你可以使用的工具如下 for tool in self.tools_meta: prompt f- {tool[name]}: {tool[description]}\n if tool[parameters].get(properties): prompt 参数:\n for param_name, param_info in tool[parameters][properties].items(): prompt f * {param_name}: {param_info.get(description, )}\n prompt 请根据用户的问题决定是直接回答还是调用上述工具。 如果你决定调用工具你必须严格按照以下JSON格式回复 {action: call_tool, tool_name: 工具名, arguments: {参数1: 值1, 参数2: 值2}} 如果你决定直接回答请使用格式 {action: final_answer, content: 你的回答内容} 请确保你的回复是且仅是一个合法的JSON对象。 return prompt def run(self, user_input: str) - str: 处理一轮用户输入。 self.logger.info(f用户输入: {user_input}) # 1. 更新对话历史 self.conversation_history.append({role: user, content: user_input}) # 2. 构建本次请求的消息列表 messages [{role: system, content: self._build_system_prompt()}] messages.extend(self.conversation_history[-5:]) # 只保留最近5轮对话防止上下文过长 # 3. 调用LLM进行决策 self.logger.info(正在请求LLM决策...) try: llm_response call_llm(messages, self.tools_meta) self.logger.info(fLLM原始响应: {llm_response}) except Exception as e: return f调用LLM时发生错误{e} # 4. 解析并执行LLM的决策 action llm_response.get(action) if action call_tool: tool_name llm_response.get(tool_name) arguments llm_response.get(arguments, {}) # 4.1 查找工具 tool_func getattr(self.toolkit, tool_name, None) if not tool_func: response f错误找不到名为 {tool_name} 的工具。 else: # 4.2 执行工具 self.logger.info(f正在执行工具 {tool_name}参数: {arguments}) try: result tool_func(**arguments) response f工具 {tool_name} 执行成功。结果{result} except Exception as e: response f工具 {tool_name} 执行失败错误{e} # 将工具执行结果也加入历史以便LLM在下一轮知晓上下文 self.conversation_history.append({role: assistant, content: response}) return response elif action final_answer: answer llm_response.get(content, LLM返回了空回答。) self.conversation_history.append({role: assistant, content: answer}) return answer else: error_msg f无法解析LLM的响应{llm_response} self.conversation_history.append({role: assistant, content: error_msg}) return error_msg3.3 第三步运行你的第一个Agent创建一个main.py来把一切串联起来。# main.py import toolkit from agent_core import SimpleAgent def main(): # 1. 初始化Agent传入工具描述和工具模块 agent SimpleAgent(tools_metatoolkit.TOOLS_META, toolkit_moduletoolkit) print(简易AI Agent已启动。输入 退出 或 quit 结束对话。) print(- * 40) # 2. 简单的对话循环 while True: try: user_input input(\n你: ).strip() if user_input.lower() in [退出, quit, exit]: print(Agent: 再见) break if not user_input: continue # 3. 运行Agent response agent.run(user_input) print(fAgent: {response}) except KeyboardInterrupt: print(\n\n程序被中断。) break except Exception as e: print(f发生未知错误: {e}) if __name__ __main__: main()现在运行python main.py你就可以体验了。虽然我们用的LLM是模拟的但整个数据流和决策逻辑已经完整跑通。你可以尝试输入“现在几点了”会模拟调用时间工具或“帮我找文件”会模拟调用搜索工具。实操心得在这个最简单的版本中我们故意将LLM调用模拟化了。这带来了一个巨大好处解耦。你可以独立测试和优化Agent的调度逻辑而无需担心LLM API的稳定性、费用和延迟。等核心流程稳定后替换掉call_llm函数里的模拟代码接入真实的模型整个系统就能立刻工作。这是一种非常高效的开发模式。4. 核心进阶接入真实LLM与实现函数调用骨架有了现在我们要注入真正的“智能”——接入一个真实的大语言模型并实现标准的函数调用Function Calling流程。4.1 接入OpenAI格式的API我们将改造agent_core.py中的call_llm函数使其能够与真实的API对话。这里以兼容OpenAI API的本地模型通过Ollama部署为例。首先安装必要的库pip install openai。然后修改call_llm函数# agent_core.py (部分修改) import openai import os def call_llm_real(messages: list, tools_meta: list) - Dict[str, Any]: 调用真实的LLM API以Ollama为例它兼容OpenAI API。 # 配置客户端指向本地Ollama服务 client openai.OpenAI( base_urlhttp://localhost:11434/v1, # Ollama默认地址 api_keyollama, # Ollama不需要真key但需提供非空值 ) try: response client.chat.completions.create( modelqwen2.5:7b, # 替换为你本地部署的模型名称 messagesmessages, tools[{type: function, function: tool} for tool in tools_meta], # 关键将工具描述传给API tool_choiceauto, # 让模型自行决定是否调用工具 temperature0.1, # 降低随机性使输出更稳定 max_tokens1024, ) message response.choices[0].message self.logger.info(fLLM响应消息: {message}) # 处理工具调用 if message.tool_calls: # 目前只处理第一个工具调用对于简单Agent足够了 tool_call message.tool_calls[0] tool_name tool_call.function.name try: arguments json.loads(tool_call.function.arguments) except json.JSONDecodeError: arguments {} self.logger.error(f解析工具参数失败: {tool_call.function.arguments}) return {action: call_tool, tool_name: tool_name, arguments: arguments} else: # 模型选择直接回答 return {action: final_answer, content: message.content} except openai.APIError as e: # 处理API错误如网络问题、模型未加载等 self.logger.error(fOpenAI API错误: {e}) return {action: final_answer, content: f抱歉思考过程遇到了一点问题{e}} except Exception as e: self.logger.error(f调用LLM时发生未知错误: {e}) return {action: final_answer, content: 系统内部错误请稍后再试。}关键升级点tools参数我们将TOOLS_META直接通过API的tools参数传递给LLM。这利用了模型原生的函数调用能力比我们自己用提示词Prompt来约束格式更可靠、更强大。tool_choice设置为auto让模型自己判断是否需要调用工具。你也可以强制它调用required或指定某个工具。响应解析模型的响应中如果message.tool_calls不为空就说明它决定调用工具。我们需要解析出工具名和参数已经是JSON格式。4.2 强化工具执行与错误处理接入真实模型后工具执行的可靠性变得至关重要。我们需要增强SimpleAgent.run方法中的工具调用部分。# 在SimpleAgent.run方法中替换工具调用部分 if action call_tool: tool_name llm_response.get(tool_name) arguments llm_response.get(arguments, {}) # 1. 工具存在性检查 tool_func getattr(self.toolkit, tool_name, None) if not tool_func or not callable(tool_func): response f错误系统未找到名为 {tool_name} 的可执行工具。 self.logger.error(response) self.conversation_history.append({role: assistant, content: response}) return response # 2. 参数验证示例检查目录是否存在 if tool_name search_files_by_keyword: dir_to_search arguments.get(directory) if dir_to_search and not os.path.exists(dir_to_search): response f错误指定的目录 {dir_to_search} 不存在。 self.logger.warning(response) self.conversation_history.append({role: assistant, content: response}) return response # 3. 安全执行可在此处加入超时、资源限制等 self.logger.info(f执行工具: {tool_name}, 参数: {arguments}) try: # 这里可以添加超时控制例如使用 signal 或 multiprocessing result tool_func(**arguments) # 对结果进行格式化避免过长或包含复杂对象 if isinstance(result, (list, tuple)) and len(result) 5: display_result f{result[:5]}... (共{len(result)}项) elif isinstance(result, dict): display_result json.dumps(result, indent2, ensure_asciiFalse) else: display_result str(result) response f【工具调用成功】\n工具{tool_name}\n结果{display_result} self.logger.info(f工具执行成功结果长度{len(str(result))}) except TypeError as e: # 参数不匹配错误 response f工具调用失败参数错误。{e} self.logger.error(f工具参数错误 {tool_name}: {e}) except PermissionError as e: response f工具调用失败权限不足无法访问资源。 self.logger.error(f权限错误 {tool_name}: {e}) except Exception as e: # 捕获所有其他异常 response f工具 {tool_name} 在执行过程中发生了意外错误{type(e).__name__} self.logger.exception(f工具执行异常 {tool_name}:) # 记录完整异常堆栈 # 4. 将结果加入历史无论成功与否 self.conversation_history.append({role: assistant, content: response}) return response注意事项错误处理是生产级Agent和玩具项目的分水岭。LLM可能会生成不合法的参数如不存在的路径工具本身也可能有Bug。必须对每一种可能的失败情况设计友好的用户反馈并将错误信息记录到日志中方便调试。同时要小心避免将详细的系统错误信息如堆栈跟踪直接返回给用户这可能暴露系统细节。5. 打造更智能的Agent记忆、规划与复杂任务处理基础的单轮工具调用已经实现但一个真正有用的Agent需要具备记忆能力和复杂任务分解能力。5.1 实现对话记忆与上下文管理我们的conversation_history已经实现了简单的短期记忆。但我们需要更智能的管理策略因为LLM的上下文长度是有限的。# 在SimpleAgent类中增加上下文管理方法 class SimpleAgent: def __init__(self, tools_meta: list, toolkit_module, max_history_turns10): # ... 其他初始化 ... self.max_history_turns max_history_turns # 控制历史轮数 self.conversation_history [] self.system_prompt self._build_system_prompt() # 持久化系统提示 def _format_messages_for_llm(self, user_input: str) - list: 格式化消息管理上下文长度。 # 1. 添加最新用户输入 self.conversation_history.append({role: user, content: user_input}) # 2. 构建消息列表以系统提示开始 messages [{role: system, content: self.system_prompt}] # 3. 截取最近N轮对话防止上下文爆炸 recent_history self.conversation_history[-(self.max_history_turns * 2):] # 乘以2因为包含user和assistant轮次 # 4. 可选关键信息总结如果历史太长可以尝试用另一个LLM调用总结之前的关键信息然后替换旧历史。 # 这是一个高级优化初期可以跳过。 messages.extend(recent_history) return messages def run(self, user_input: str) - str: # 使用新的消息格式化方法 messages self._format_messages_for_llm(user_input) # ... 后续LLM调用和工具执行逻辑不变 ...上下文管理策略固定窗口如上所示只保留最近N轮对话。简单有效但可能遗忘很早的重要信息。关键信息提取对于长对话可以定期例如每10轮让LLM自己总结一下对话的“核心事实”和“用户偏好”然后将这个总结作为系统提示的一部分清空旧历史。这能极大地扩展Agent的“记忆深度”。5.2 实现多步骤任务规划与执行真正的挑战来了用户说“帮我清理桌面上的临时文件然后告诉我还剩多少空间”。这涉及多个工具“查找临时文件”、“删除文件”、“检查磁盘空间”和步骤。我们需要让LLM进行任务规划Planning。修改我们的系统提示词和决策逻辑# 修改_build_system_prompt方法 def _build_system_prompt(self) - str: prompt 你是一个高级AI助手可以调用工具解决复杂问题。你具备任务规划和分解能力。 可用工具 ... (工具列表保持不变) ... 工作流程 1. **理解与分析**仔细分析用户请求判断是否需要多个步骤完成。 2. **规划与分解**如果需要多步在心中或在回复中制定一个步骤计划。例如“首先调用A工具获取X然后用X作为参数调用B工具...”。 3. **逐步执行**一次只执行一个步骤。调用一个工具等待结果然后根据结果决定下一步。 4. **总结报告**所有步骤完成后向用户清晰汇报最终结果。 请严格按以下JSON格式回复 - 若需要调用工具{action: call_tool, tool_name: xxx, arguments: {...}} - 若步骤完成需继续下一步或最终回答{action: final_answer, content: 当前步骤结果或最终总结} - 若在规划中可先输出规划但最终仍需通过工具调用执行{action: final_answer, content: 计划1. ... 2. ... 现在开始执行第一步。} 记住你一次只能做一个动作调用一个工具或输出一段话。 return prompt同时我们需要修改Agent的主循环使其能够处理多轮交互以完成一个复杂任务。这需要引入“状态”的概念。一个简单的方法是让Agent在等待工具结果后自动将结果作为新的上下文再次请求LLM决定下一步直到LLM给出明确的最终答案。# 这是一个简化的多步执行逻辑可以整合到run方法中或作为一个新的run_complex_task方法。 def execute_plan(self, initial_input: str): 执行一个可能包含多步的任务。 pending_task initial_input max_steps 10 # 防止无限循环 step_count 0 while step_count max_steps: step_count 1 self.logger.info(f步骤 {step_count}: 处理输入 {pending_task[:50]}...) # 1. 向LLM提交当前状态可能是用户输入也可能是上一步的工具结果 messages self._format_messages_for_llm(pending_task) llm_decision self.call_llm_real(messages, self.tools_meta) # 2. 处理决策 if llm_decision[action] call_tool: # 执行工具 tool_result self._execute_tool(llm_decision[tool_name], llm_decision[arguments]) # 将工具执行结果设置为下一轮的“pending_task” pending_task tool_result # 注意这里不直接返回给用户而是继续循环 continue elif llm_decision[action] final_answer: # 如果LLM认为任务完成返回最终答案 final_content llm_decision[content] # 检查内容是否包含“完成”、“最终”、“结果是”等标识词或者是否是一个明确的结论。 # 这里逻辑可以很复杂简单起见我们假设LLM输出的final_answer就是最终答案。 self.conversation_history.append({role: assistant, content: final_content}) return final_content # 如果超过最大步数强制结束 return f任务执行超时超过{max_steps}步可能陷入循环。最后的状态是{pending_task}实操心得实现多步规划是Agent开发中的一个难点。LLM的规划能力并不稳定可能会“迷失”或陷入循环。因此设置最大步数、让用户有机会中途干预、以及让LLM在每一步都清晰地输出其“思考过程”可以通过提示词要求是至关重要的调试和维稳手段。初期建议从简单的、步骤明确的两步任务开始测试。6. 避坑指南与性能优化实战在开发过程中我踩过不少坑也总结了一些让Agent更稳定、更高效的技巧。6.1 常见问题与排查技巧问题现象可能原因排查与解决思路LLM不调用工具总是直接回答。1. 工具描述不够清晰。2. 系统提示词未强调工具调用。3. 模型本身函数调用能力弱。1. 优化工具描述确保准确、无歧义并举例说明使用场景。2. 在系统提示词中明确指令如“你必须优先考虑使用工具来解决问题”。3. 换用函数调用能力更强的模型如GPT-4系列、Claude 3、DeepSeek最新版本。LLM调用了错误的工具或参数。1. 工具间功能描述相似导致混淆。2. 用户指令模糊。3. 参数描述不清。1. 区分工具描述突出其独特性和使用边界。2. 在提示词中要求LLM在不确定时向用户澄清。3. 在工具参数描述中指定格式示例如{directory: /home/user/docs, keyword: report}。工具执行成功但LLM无法理解结果并继续。1. 工具返回的结果太复杂或非结构化。2. 结果未有效反馈到对话历史中。1. 让工具返回简洁、结构化的文本。对于复杂数据如列表、字典可以格式化成清晰的段落或Markdown列表。2. 确保将【工具调用成功】\n结果...这样的格式完整地添加到conversation_history中作为模型的下一轮输入。Agent陷入死循环不断调用同一个工具。1. 任务规划逻辑有缺陷。2. 工具结果未能改变LLM的决策状态。3. 缺少循环终止判断。1. 在提示词中要求LLM“评估当前状态如果目标已达成则停止”。2. 引入“步骤计数器”和最大步数限制。3. 让LLM在每次行动前输出其“当前目标”和“下一步理由”便于人工分析和调试。处理速度很慢。1. LLM API调用延迟高。2. 某些本地工具执行耗时如大文件搜索。3. 上下文历史过长。1. 考虑使用更快的模型或配置。2. 为耗时工具设置超时或采用异步调用。3. 实施上文提到的上下文窗口限制和总结策略。6.2 安全与可靠性加固工具权限隔离不要用高权限如root运行Agent进程。为Agent创建一个专用系统用户并严格控制其可访问的文件和系统命令范围。输入验证与净化对所有从LLM解析出来的、以及用户直接输入的参数进行严格验证。特别是文件路径、系统命令参数要防止目录遍历../../../、命令注入; rm -rf /等攻击。def safe_path(base_dir, user_path): 将用户输入路径限制在base_dir目录下 full_path os.path.abspath(os.path.join(base_dir, user_path)) if not full_path.startswith(os.path.abspath(base_dir)): raise PermissionError(访问路径超出允许范围。) return full_path设置执行超时对于任何工具调用特别是可能挂起的操作使用signal或multiprocessing设置超时。import signal class TimeoutException(Exception): pass def timeout_handler(signum, frame): raise TimeoutException(工具执行超时) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(30) # 设置30秒超时 try: result tool_func(**arguments) signal.alarm(0) # 取消闹钟 except TimeoutException: return 工具执行时间过长已中断。6.3 提示工程优化技巧提示词Prompt是Agent的“灵魂指令”。几个立竿见影的优化点角色扮演让LLM扮演一个“严谨的工程师”或“细心的助理”这比单纯给指令效果更好。例如“你是一个谨慎的自动化助手在采取任何行动前都会仔细核对信息...”少样本学习Few-shot在系统提示词中提供1-2个完美的输入输出示例。这能极大地校准LLM的输出格式和行为。示例对话 用户我想知道当前文件夹里所有图片文件。 助手{action: call_tool, tool_name: search_files_by_keyword, arguments: {directory: ., keyword: .jpg}} 注意实际中需要更复杂的示例来处理多工具调用输出格式强制除了要求返回JSON还可以在提示词末尾加上“请确保你的回复是且仅是一个合法的JSON对象不要有任何其他前缀或后缀。”这能减少模型“说废话”的概率。分步思考Chain-of-Thought对于复杂任务可以要求LLM在输出JSON前先输出一段“思考”的内容。虽然我们最终只解析JSON但这段思考过程能显著提高模型规划的正确性。我们可以通过API参数如OpenAI的response_format或seed来获得更稳定的JSON输出。走到这一步你已经拥有了一个功能完整、可扩展的AI Agent雏形。它能够理解你的意图调用你赋予它的工具并通过规划尝试解决复杂问题。这个从0到1的过程最重要的不是代码本身而是理解了Agent各个组件之间如何协同工作数据如何流动以及如何通过提示词、错误处理和状态管理来让这个系统变得可靠。你可以以此为基石开始添加更多强大的工具网络搜索、数据库查询、发送邮件或者尝试更高级的架构如ReAct模式、智能体工作流打造真正属于你自己的智能助手。