ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI Agent开发实战:从工具调用到系统架构的完整指南

AI Agent开发实战:从工具调用到系统架构的完整指南 1. 从零到一一个AI Agent项目的诞生契机最近我把自己从零开始折腾了大半年的一个项目——“智语”给发布了。这感觉有点像看着自己亲手搭的积木房子终于能邀请朋友来参观了。项目名字听起来挺唬人其实核心很简单一个能帮你处理日常琐碎任务的AI助手。不是那种只会聊天的聊天机器人而是能真正“动手”帮你干点事的智能体。为什么想起来做这个源头其实挺生活化的。我发现自己每天要花大量时间在重复、琐碎但又不得不做的“数字家务”上。比如看到一篇好文章想存下来整理到笔记里得复制、打开笔记App、粘贴、分类一套流程下来阅读的兴致都打断了。再比如想追踪某个产品的价格变化得每天手动去电商平台搜一下记下来对比。这些事单个看都不难但加起来就特别消耗精力。我当时就想如果有个“数字管家”能理解我的意图自动把这些事给办了该多好。市面上当然有各种自动化工具像IFTTT、Zapier功能强大但它们要么需要我手动配置复杂的逻辑链条对于非技术背景的朋友不太友好要么在处理非结构化信息比如从一段聊天记录里提取关键信息并执行时显得力不从心。而大语言模型的出现让我看到了新的可能性一个能真正“理解”自然语言指令并据此规划、调用工具去执行的AI Agent。“智语”这个项目就是在这个想法驱动下开始的。它的目标不是做一个无所不能的超级AI而是做一个足够聪明、足够听话的“执行者”。你告诉它“把这篇公众号文章的核心观点总结一下存到我的Notion数据库‘灵感收集’页面里”它就应该能自己去打开链接、阅读内容、提炼要点然后找到你的Notion创建一条新记录。整个过程你只需要发一句话指令。听起来是不是有点像给大模型装上了“手”和“脚”没错这就是AI Agent的核心概念之一工具调用。但真正做起来你会发现从“知道这个概念”到“做出一个能稳定运行的产品”中间隔着十万八千里。接下来我就把这大半年来踩过的坑、获得的经验毫无保留地分享出来。如果你也对AI Agent开发感兴趣或者单纯好奇一个这样的项目是怎么从零搭建起来的那么这篇内容应该能给你一些实实在在的参考。2. 核心架构设计让AI学会“使用工具”决定动手之后第一个拦路虎就是架构设计。一个AI Agent系统尤其是面向复杂任务的绝对不是简单地把用户问题扔给大模型然后返回答案就完事了。它需要一套精密的“思考-行动”循环机制。我最终为“智语”设计的核心架构可以概括为“一个大脑两只手一个记忆库”。一个大脑指的就是大语言模型。它是整个系统的决策中心负责理解用户的意图、拆解任务、规划步骤并在每一步决定调用哪个工具。这里的选择至关重要。早期我尝试过一些开源模型虽然在简单的对话上表现不错但在需要复杂逻辑推理和精准工具调用的场景下稳定性欠佳。最终我选择了性能与成本相对平衡的GPT-4系列模型作为核心“大脑”。它的强项在于遵循指令和逻辑链推理这对于Agent的可靠性至关重要。注意模型选型没有绝对的好坏关键看场景。如果你的任务极其简单或对成本极度敏感一些优秀的开源模型如Claude 3 Haiku或经过微调的Mistral、Llama系列也是不错的选择。但“智语”定位是处理稍复杂的多步任务对推理能力要求高所以选择了能力更强的模型。两只手指的是工具执行层。这是Agent能从“思考”走向“行动”的关键。我把它分成了两层基础工具层这是一系列原子操作比如“发送HTTP请求”、“读写本地文件”、“执行一段Python代码”、“操作浏览器”。这些工具由代码直接实现功能单一且确定。复合技能层这是建立在基础工具之上的、面向具体场景的“技能”。比如“搜索天气”这个技能内部可能封装了“构造搜索URL”、“发送HTTP请求”、“解析返回的HTML页面”等多个基础工具。对于“大脑”来说它只需要知道有“搜索天气”这个技能可用而不必关心其内部如何实现。这种分层设计的好处是灵活性和可维护性。当需要增加一个新功能时比如“订咖啡”我只需要用现有的基础工具组合出一个“订咖啡”技能然后告诉“大脑”这个新技能的存在即可无需改动核心的决策逻辑。一个记忆库则是Agent的“工作经验”。它需要记住和用户的对话历史、之前执行任务的结果、以及用户的个人偏好比如默认把笔记存到哪个文件夹。没有记忆的Agent每次对话都是“初次见面”无法处理涉及上下文的复杂任务。我实现了一个分层的记忆系统短期会话记忆保存在内存中记录当前对话轮次的内容用于理解上下文。长期向量记忆将重要的对话历史、任务结果转换成向量存入向量数据库我用了Chroma。当用户提出一个模糊的需求时比如“把昨天我们讨论的那个方案找出来”Agent可以在这里进行语义搜索找到相关的历史信息。技能记忆记录每个技能被成功调用的参数、场景和结果用于后续优化技能的成功率。这个“大脑-手-记忆”的架构构成了“智语”的基石。但光有架构图还不够如何让它们协同工作才是真正的挑战。3. 任务规划与执行循环Agent的“思考”过程架构搭好了接下来要让Agent“动”起来。用户输入一句“帮我查一下北京明天下午的天气如果下雨就提醒我带伞”Agent内部是如何处理的呢这个过程就是任务规划与执行循环它是Agent智能的核心体现。我实现的循环大致分为四个阶段可以把它想象成一个经验丰富的助理接到老板指令后的工作流程第一阶段意图解析与任务拆解用户指令进来后首先由“大脑”LLM进行深度解析。这一步不仅仅是理解字面意思还要识别出隐含的意图和约束条件。对于上面的指令LLM需要解析出核心任务查询天气。关键参数地点北京时间明天下午。条件分支如果天气状况包含“雨”那么执行提醒动作。隐含需求“提醒”可能需要通过某个通知渠道如邮件、钉钉、短信发送给我。解析完成后LLM会将这个复杂的自然语言指令翻译成一个结构化的任务计划。这个计划通常是一个任务列表或一个有向无环图。例如调用技能get_weather(location北京, date明天, time_period下午)获取天气结果。判断结果中是否包含“雨”、“雷阵雨”等关键词。如果条件成立调用技能send_reminder(message明天下午北京有雨记得带伞。, channel钉钉)。第二阶段工具匹配与调用有了任务计划Agent就开始为每一步寻找合适的“手”工具/技能。我维护了一个工具描述注册表。每个可用的工具都有一份详细的自然语言描述比如工具名get_weather 描述查询指定城市、指定日期的天气情况。 参数 - location (string): 城市名例如“北京”、“上海”。 - date (string): 日期支持“今天”、“明天”、“2024-10-27”等格式。 - time_period (string可选): 时间段如“上午”、“下午”、“晚上”。 返回一个包含天气状况、温度、湿度等信息的结构化数据。在每一步LLM会根据当前子任务的目标从这个注册表中检索出最匹配的工具并生成符合该工具要求的调用参数。这个过程需要LLM有很好的函数调用能力。生成参数后系统就会安全地执行这个工具调用。第三阶段观察结果与状态更新工具执行完毕后会返回一个结果。这个结果可能是成功的数据如{weather: 小雨, temp: 18℃}也可能是失败的错误信息如{error: 城市不存在}。这个结果被称为“观察”。 Agent的“大脑”需要消化这个“观察”并更新整个任务的状态。如果结果是成功的并且是任务计划中的一步那么它就标记这一步完成并准备执行下一步。如果结果失败了“大脑”需要分析原因是参数错了还是工具本身有问题然后它可能会尝试调整参数重试或者选择另一个工具甚至向用户请求澄清。第四阶段循环判断与最终输出Agent会判断当前任务计划是否全部完成。如果没有就带着最新的“观察”和任务状态回到第一阶段或第二阶段继续规划或调用下一个工具形成一个“思考-行动-观察”的循环。 直到所有步骤完成或者达到最大循环次数防止死循环Agent才会将最终的结果整合成一段友好的自然语言回复给用户。例如“已为您查询。北京明天下午天气为小雨气温18℃。已通过钉钉向您发送了带伞提醒。”这个循环的稳定性直接决定了Agent的可用性。其中最大的挑战在于错误处理和规划幻觉。LLM有时会“幻想”出一些不存在的工具或参数或者对工具返回的结果做出错误解读。为了解决这个问题我引入了严格的工具调用验证层和规划复盘机制这部分我们后面会详细讲。4. 关键实现细节工具调用、记忆与安全纸上谈兵容易真正写代码实现时到处都是细节。这里我挑三个最核心、也最让人头疼的部分展开说说如何让工具调用既灵活又可靠如何设计一个实用的记忆系统以及如何守住安全底线。4.1 工具调用的标准化与验证让LLM去调用代码工具最大的风险是“乱调用”。LLM可能生成不合法的参数或者调用一个不存在的工具。我的解决方案是“强类型描述 运行时验证”。首先我为每一个工具都定义了一个严格的JSON Schema。这不仅仅是给LLM看的自然语言描述更是一份机器可读的“合同”。例如对于send_email工具{ name: send_email, description: 发送电子邮件到指定地址。, parameters: { type: object, properties: { to: { type: array, items: {type: string, format: email}, description: 收件人邮箱地址列表。 }, subject: {type: string, description: 邮件主题。}, body: {type: string, description: 邮件正文支持HTML。} }, required: [to, subject, body] } }当LLM决定调用send_email时它必须生成一个符合这个Schema的JSON对象。系统在收到调用请求后会先用JSON Schema验证器检查参数格式是否正确比如to字段是不是邮箱数组。这第一道关卡就拦住了大部分格式错误。其次对于通过格式验证的参数在真正执行工具函数前还有一道业务逻辑验证。比如to列表里的邮箱地址是否在我们的白名单内防止Agent乱发邮件body长度是否超过限制这些校验由工具函数本身或一个前置的拦截器来完成。最后工具执行本身必须放在一个安全的沙盒环境中尤其是那些执行系统命令或代码的工具。我使用了一个受限的子进程并设置了超时和资源限制CPU、内存确保即使工具调用出现死循环或恶意代码也不会拖垮主系统。4.2 分层记忆系统的实践记忆系统听起来高大上但实现的原则是“按需记忆高效检索”。我的记忆系统分为三层每一层解决不同的问题1. 会话缓存短期记忆最简单就是用一个大数组或队列保存当前对话的(用户输入, Agent思考过程, Agent输出)三元组。每次新的用户输入进来都会把最近N轮的历史比如最近10轮作为上下文一起送给LLM。这保证了对话的连贯性。实现的关键是控制上下文长度避免因历史太长导致API调用成本剧增或模型性能下降。我采用了简单的“先进先出”队列并会估算token数在接近模型上下文窗口上限时优先丢弃最早的、非关键的历史。2. 向量记忆库长期经验这是记忆系统的核心。所有我认为重要的交互信息如任务最终结果、用户确认过的偏好、执行失败的重要教训都会被转换成一个文本摘要然后通过嵌入模型我用了text-embedding-3-small转换成向量存入Chroma数据库。 当用户提出一个可能需要历史信息的问题时比如“我上周让你保存的那个关于神经网络的文章在哪”系统会做以下操作将当前问题也转换成向量。在向量数据库中搜索最相似的K条历史记忆。将这些搜索到的记忆片段作为额外的上下文插入到本次对话的提示词中。 这样LLM就能“想起”相关往事给出准确的回答“您指的是《Attention Is All You Need》这篇论文的解读吧我已将它保存在您的Notion‘AI论文’页面中。”3. 技能知识库领域记忆这部分更像一个静态的“使用说明书”。它不是来自对话而是我预先为每个工具/技能编写的详细文档、最佳实践示例、常见的失败案例及解决方法。当Agent在规划任务或遇到工具调用错误时除了看工具描述还可以检索这个知识库寻找类似问题的处理方案。这相当于给LLM配备了一本随时可查的“故障手册”大大提升了它自主解决问题的能力。4.3 权限与安全边界设计让一个AI能自动执行操作安全是头等大事。我的原则是最小权限原则和关键操作人工确认。权限分级我为每个工具设置了权限等级。例如信息查询类如查天气、搜新闻默认开放无需授权。个人数据操作类如读邮件、管理日历需要用户首次使用时明确授权OAuth连接且仅能访问用户授权范围的数据。对外操作类如发邮件、发钉钉消息、创建在线文档每次调用前对于非预设白名单的操作比如给陌生人发邮件都需要用户二次确认。系统级操作类如执行任意Shell命令、读写服务器文件在“智语”的当前版本中被彻底禁止。这是绝对不能逾越的红线。操作确认与审计所有成功执行的工具调用都会生成一条不可篡改的日志记录“谁哪个用户/会话、在什么时候、通过哪个Agent、调用了什么工具、参数是什么、结果是什么”。这个日志方便回溯和审计。对于高风险操作系统会暂停执行并向用户发送一个确认请求例如在聊天界面弹出一个确认按钮只有用户点击确认后操作才会继续。输入输出过滤与监控所有用户输入和工具返回的内容都会经过一层简单的敏感词过滤和异常检测比如是否包含大量乱码、是否试图进行SQL注入等模式。虽然不能100%防住但能挡住大部分明显的恶意行为。同时系统会监控工具调用的频率如果发现短时间内某个工具被异常频繁地调用会自动触发限流或告警。安全设计没有终点它是在易用性和安全性之间不断寻找平衡的过程。我的经验是宁可让Agent“笨”一点、慢一点多问用户一句也要把风险控制在摇篮里。5. 开发中的典型挑战与解决方案在开发“智语”的过程中我遇到了无数个坑。有些是技术上的有些是逻辑设计上的。这里分享几个最具代表性的挑战和我的解决思路希望能帮你绕过这些弯路。5.1 规划幻觉与逻辑死循环这是LLM-based Agent最常见的问题之一。规划幻觉指的是LLM在拆解任务时可能会凭空创造出不存在的步骤或工具。例如用户让“订一张从北京到上海的机票”LLM规划的步骤里可能包含“调用check_user_vip_status工具”而这个工具我根本没提供。我的解决方案是“两步验证法”规划时工具过滤在LLM进行任务规划时我提供给它的“工具列表”提示词中会明确强调“你只能使用以下工具 [工具A描述], [工具B描述]...”。同时在规划的输出格式上强制要求它必须从给定的工具列表中选择。执行前工具存在性检查当解析出LLM规划中要调用的工具名后系统会立刻在注册表中查找。如果找不到不会直接报错导致任务失败而是将“工具XXX不存在”作为一个“观察”反馈给LLM并要求它重新规划。这相当于给了LLM一次自我纠正的机会。通常经过一两次纠正它就能回到正确的工具路径上。逻辑死循环更棘手。比如一个任务需要满足条件A才能执行步骤B但步骤B又是达成条件A的前提。LLM可能会在这两步之间来回尝试陷入无限循环。我采用了循环检测与强制跳出机制系统会维护一个本次任务中已执行步骤的哈希记录。如果发现某个相同的“状态”如相同的工具调用和近似参数在短时间如3个循环内重复出现系统就判定可能陷入死循环。此时系统会中断当前循环向LLM发送一个强提示“检测到可能陷入循环。当前状态为XXX。请重新评估任务或直接向用户请求更多信息。” 同时在界面上给用户一个提示告知任务卡住了可能需要更明确的指令。5.2 工具执行的稳定性与错误处理工具执行可能失败原因千奇百怪网络超时、第三方API返回了意料之外的数据格式、权限不足、资源不存在等等。一个健壮的Agent必须能妥善处理这些错误而不是直接崩溃。我建立了一个分级错误处理策略可重试错误如网络超时、第三方服务临时不可用返回5xx错误。对于这类错误系统会自动进行最多3次重试每次重试间隔指数级增加。需调整错误如参数错误返回4xx错误如“城市不存在”、权限错误。系统会将具体的错误信息如“错误代码404 消息城市‘北亰’未找到”作为“观察”反馈给LLM。LLM需要尝试理解错误修正参数比如将“北亰”改为“北京”然后重新调用。致命错误如工具内部代码异常、资源耗尽。系统会捕获异常记录详细日志并终止当前任务分支向用户返回一个友好的错误提示如“处理您的请求时遇到了一个内部问题已记录。请稍后再试或尝试其他指令。”。关键在于要把丰富的错误信息结构化地反馈给LLM而不是简单地抛出一个异常文本。LLM需要从错误信息中学习才能做出正确的下一步决策。5.3 上下文管理与成本控制使用商业LLM API成本是按Token消耗计算的。Agent的思考过程规划、反思和长记忆检索都会消耗大量Token。如果不加控制一个复杂任务可能轻松消耗数万Token成本很高。我的优化措施包括压缩历史上下文不是把完整的对话历史都塞进去。对于较早的对话我会让LLM自己生成一个简短的摘要来替代原始长文本。例如将10轮关于“规划旅行”的详细讨论总结成一句话“用户正在规划一次为期三天的上海旅行已确定日期和预算正在筛选景点。”选择性记忆检索不是每次用户提问都去搜索整个向量数据库。我会先让LLM判断当前问题是否需要长期记忆。如果需要再让LLM根据问题提炼出2-3个关键词用这些关键词去搜索而不是把整个问题作为查询向量。这减少了不必要的搜索和上下文注入。设定思考深度限制对于单个任务我会限制“思考-行动”循环的最大次数比如10次。防止Agent在一个无解的问题上无限思考下去白白消耗Token。缓存常用结果对于一些相对静态的查询结果如城市列表、工具描述在本地进行缓存避免重复向LLM提问。这些优化措施让“智语”在处理典型任务时的平均Token消耗下降了约40%在保证体验的同时有效控制了运行成本。6. 从Demo到产品工程化与部署考量当一个Agent在本地开发环境能跑起来后下一步就是把它变成一个可供他人使用的、稳定的服务。这个过程涉及到大量的工程化工作。首先是一个清晰的API设计。“智语”的核心是一个后台服务我为其设计了RESTful API。最关键的端点有两个POST /chat处理用户的消息。这是一个流式接口因为Agent的“思考-行动”过程可能需要几秒甚至十几秒流式响应可以让用户看到“正在思考...正在执行...”的中间状态体验更好。GET /skills获取当前可用的技能列表及其描述。这可以用于前端动态展示用户能做什么。其次是状态管理与异步处理。一个复杂的任务可能执行很长时间。不能让用户的HTTP请求一直挂起。我的做法是当收到一个任务请求后立即返回一个唯一的task_id并启动一个后台的异步任务我用的是Celery Redis来实际执行Agent循环。用户可以通过另一个端点GET /task/{task_id}/status来轮询任务状态和获取最终结果。前端则可以用WebSocket或长轮询来实时更新进度。然后是配置与密钥管理。Agent需要接入各种第三方服务的API密钥如OpenAI、邮件服务、Notion等。这些敏感信息绝不能硬编码在代码里。我使用了环境变量和配置文件相结合的方式。在部署时通过Docker的env-file或Kubernetes的Secret来注入这些密钥。同时为不同环境开发、测试、生产准备了不同的配置文件。最后是监控与日志。这是保证线上服务稳定的眼睛。我集成了Prometheus和Grafana来监控关键指标API指标请求量、延迟、错误率。Agent核心指标任务成功率、平均执行步骤数、工具调用频率及错误分布。成本指标各模型API的Token消耗趋势。 所有日志都结构化输出到ELKElasticsearch, Logstash, Kibana栈方便根据task_id、user_id或错误类型进行追踪和排查。当任务失败时日志里必须包含完整的“思考链”这样才能复现问题。部署上我选择了Docker容器化。将核心服务、记忆数据库Chroma、任务队列Redis、监控组件等都打包成独立的容器使用Docker Compose或Kubernetes进行编排。这保证了环境的一致性也便于横向扩展。例如当任务队列积压时可以快速启动更多的Celery Worker容器来处理。7. 未来迭代方向与个人反思“智语”的第一个版本发布了但它远非完美更像一个坚实可用的起点。回顾整个开发过程有几个方向是我接下来想重点深化的也有一些深刻的教训。在迭代方向上多模态能力目前的“智语”主要处理文本信息。但现实世界的信息是多元的。我希望它能“看懂”图片和文档。例如用户发一张冰箱内部照片它能识别出有哪些食材并据此推荐菜谱。这需要集成视觉模型如GPT-4V或开源的VLM并在工具层增加图像处理和分析的能力。技能市场的构想我一个人能开发的技能是有限的。我希望能设计一套安全的技能开发框架和发布标准让其他开发者也能为“智语”贡献技能。用户可以像安装手机App一样按需安装自己需要的技能形成一个生态。这里最大的挑战是技能的安全审核和沙盒隔离。更强大的自主规划与学习目前的规划能力还比较依赖我预设的提示词模板。我希望Agent能从成功和失败的历史任务中自我学习优化自己的规划策略。比如如果它发现“先查天气再规划行程”的成功率比“先规划行程再查天气”高它以后就应该优先采用前一种策略。这涉及到更复杂的强化学习或经验回放机制。个性化与用户习惯学习现在的记忆系统还比较被动。我希望Agent能更主动地学习用户的偏好和习惯。比如如果用户总是让Agent把技术文章保存到Notion的“技术仓库”那么当用户再次说“保存这篇文章”时Agent应该能主动询问“是否还是保存到‘技术仓库’”甚至直接默认这么做。在个人反思上我最大的几点体会是不要过早追求完美架构初期我花了太多时间在设计一个“终极”架构上后来发现很多设计都是过度工程。Agent领域变化很快最好的方法是先做出一个能跑通的、最简版本MVP快速验证核心想法然后再根据实际遇到的问题去迭代重构。先让轮子转起来再考虑怎么让它转得更快更稳。提示词工程是核心但不是全部良好的提示词设计如思维链、Few-shot示例能极大提升Agent的表现但它无法解决工具本身的可靠性问题也无法弥补模型能力的根本不足。当效果遇到瓶颈时与其死磕提示词不如想想是否应该增加一个新工具或者用更确定的代码逻辑去替代LLM的模糊推理。测试极其困难但极其重要测试一个AI Agent比测试传统软件难得多因为它的输出具有不确定性。我建立了一套混合测试策略单元测试测试每个工具函数、集成测试测试固定的任务流、以及基于“评分模型”的模糊测试用另一个LLM给Agent的回复打分。即使这样依然会有意想不到的边界情况出现。线上完善的监控和快速回滚机制是必须的。用户体验大于技术炫技开发者很容易沉迷于让Agent完成更复杂的任务但用户最关心的是稳定、准确和快速。一个能100%成功执行“查天气并提醒”的Agent远比一个能执行10步复杂任务但成功率只有70%的Agent更有价值。稳定性和响应速度是赢得用户信任的基础。从一行代码开始到一个可以对外服务的项目“智语”的开发过程是一次充满挑战但也收获巨大的旅程。它让我更深刻地理解了AI不仅仅是生成文本更是连接数字世界与真实需求的桥梁。这个项目还在继续前面的路还很长但看到它开始能真正帮人省去一些麻烦时那种成就感是无可替代的。如果你也在构建自己的AI应用希望这些粗浅的经验能给你带来一点启发。
返回列表