资讯动态

用Python从零搭建AI Agent:环境、工具调用与记忆增强实战

发布时间:2026/10/18 20:33:43 来源:尧图企业网站定制
这两年“AI Agent”这个词被反复提起但问十个朋友九个都说“知道概念”真要动手写第一个能自己选工具、自己完成任务的智能体多半卡在第一步。其实Agent没那么玄乎核心就是“大模型负责做决策外围代码负责做执行”。这篇文章我就用Python给你完整梳理一遍从环境准备、核心循环、工具接入到记忆增强和常见坑位排查带你从零把一个能跑起来的智能体搭出来。整篇都会用具体的场景和代码说话适合已经会Python基础语法、但还没系统性写过Agent的开发者也适合想搞明白Agent内部循环原理的人。1. 内容设计与思路拆解1.1 什么是真正的“智能体”很多人把“能聊天的机器人”当成Agent这是个很大的误解。普通的聊天机器人只能根据你输入的问题生成一段文本它没有任何后续动作。而Agent不一样它具备“感知-决策-执行-复盘”的闭环能力。举个生活化的例子如果你让普通聊天机器人“帮我在周五三点定个会议室”它只会回复你一段“好的建议您联系行政部”之类的文字但一个真正的Agent会自己查日历、看会议室是否空闲、调用预订接口完成预订然后告诉你“已订好到时候提醒你”。从这个案例能看出来Agent的核心是“模型做决策代码做执行”。模型是大脑负责把复杂任务拆解成步骤并决定调用哪个工具工具闸门、数据库、搜索等代码模块是手脚负责实际改变环境或获取信息。这个组合让Agent不再停留在“说话”层面而变成可以完成任务的工作单元。做第一个Agent前心里最好装一个四步循环模型观察从用户输入和上下文提取信息→ 决策由大模型判断需要什么工具→ 执行调用相应函数并拿到结果→ 循环把结果返回给模型继续下一步或给出最终答案。这个循环是所有Agent的骨架不管底层用的是多么复杂的框架最终都逃不开这个模型。1.2 为什么第一版用Python选Python不是因为它最“高级”而是因为它的开发效率和生态太适合Agent原型了。首先是库生态无论是调用对话模型、做向量检索、解析文档还是处理异步任务Python都有非常成熟的库随便拿几个拼起来就能跑通一个原型。其次是调试体验Agent开发最大的痛点就是“模型输出不可控”Python的脚本式执行和丰富日志工具能让你快速定位是模型没选对工具、参数格式错了还是工具内部报错。当然Python也有短板比如性能不算顶级。但对于Agent第一版来说性能通常不是瓶颈真正瓶颈是你的逻辑是否顺滑、工具是否稳定。等你把思路验证清楚、业务边界摸透再考虑用更高效的语言重写核心模块也不迟。我见过太多人第一版就上了高并发框架结果业务没跑顺光是环境配置就折腾一周完全没有意义。1.3 手写循环“现成框架”哪个好这里把路分成两条一条是手写Agent循环另一条是直接用开源的Agent编排框架。我的建议非常直接——第一个Agent一定要手写一遍循环。手写循环能让你真正理解“模型返回的结果是怎么映射到代码执行的”这是所有上层框架的根基。等你理解了再换成框架哪里有问题你能立刻想到底层发生了什么如果你一开始就用框架遇到诡异问题会非常被动因为你在黑盒上猜。手写循环的核心难度在于“消息协议”的设计。你需要定义清楚模型返回的内容里什么字段代表“继续调用工具”什么字段代表“最终回答”。现在主流方案是让模型支持函数调用Function Calling模型会在必要时返回一个结构化的工具调用指令你只需要解析这个指令并执行。实现完这套协议后你会发现框架本质就是在这套协议上加了状态管理、记忆机制和一堆封装好的工具并没有魔法。2. 环境准备与基础概念2.1 Python开发环境与依赖清单开始写代码前先把基础环境整干净。建议使用Python 3.10及以上版本Python 3.8以前版本在处理类型注解和一些异步语法时会有麻烦没必要给自己挖坑。强烈推荐用python -m venv .venv创建虚拟环境再激活它避免把一堆依赖装到系统Python里。不同项目依赖冲突的痛你应该多少体验过。依赖方面第一版尽量精简避免安装一堆不知道干嘛的包。基础清单如下一个大模型SDK用来调用对话接口不同服务商有自己的SDK功能上大同小异负责把消息列表发给模型并返回结果有的还支持函数调用。HTTP请求库如果有些工具需要调外部接口比如查天气、查文档通常要用requests或aiohttp。日志库直接用Python标准库logging就行不需要额外引入。Agent执行过程中的每一步日志都值得记录后面排查问题全靠这些日志。可选依赖如果要做长期记忆需要引入一个向量数据库客户端和Embedding接口如果做日程工具可能需要datetime等标准库。所有依赖尽量锁定版本可以在虚拟环境里pip freeze requirements.txt。Agent项目经常遇到“昨天还能跑今天坏了”的情况锁定版本能少掉一半头发。2.2 理解模型接口与函数调用机制大模型本质上是个“文本到文本”的接口。你给它一串消息它返回一段文本。那它怎么知道该调用工具呢关键在于系统提示词和函数协议。你在系统提示词里告诉模型你是一个个人助理你可以使用以下工具每个工具的名称、描述、参数格式都写在提示词里。模型收到用户问题后会根据这些信息“决定”自己是否需要调用工具。如果你用的模型服务支持函数调用那就不需要把工具描述硬塞进提示词而是通过结构化的参数传给API。模型判断需要某个工具时返回的不再是普通文本而是一个类似于tool_calls的结构化字段里面包含工具名和参数JSON。你只需要把这个指令拿过来找准对应函数执行再把执行结果作为一条新消息发回给模型。模型看到结果后会继续决定是调用下一个工具还是给出最终答案。这里有个很重要的认知模型调用工具并不保证工具一定存在或参数完全正确所以你的代码要做参数校验和容错。模型是“意图识别器”而执行结果的兜底逻辑必须由代码负责。把希望完全寄托在模型“足够聪明”上是Agent不稳定最大的来源。2.3 上下文消息的结构设计Agent和模型之间交换的信息都以“消息列表”形式存在。每一条消息需要一个role字段通常有system系统设定、user用户输入、assistant模型回复、tool工具执行结果这几种。你要维护一个有顺序的消息列表每次请求时把它完整发给模型模型才能基于前因后果做出决策。我在开发中常犯的一个错误是只记录“用户说了什么”和“模型回复了什么”忘记记录“工具返回了什么”。这会导致模型第二次请求时根本不知道刚才工具查到什么答案。正确做法是把每一步都追加进消息列表无论是用户提问、模型思考、工具执行还是最终回复都要留下完整记录。等这一步做好之后你再去看那些AI Agent框架的历史管理模块会发现核心逻辑相差不大。3. 从零搭建第一个智能体3.1 定一个具体场景文档问答加日程处理学习Agent最好别用一个泛泛的“客服机器人”一定要有明确的工具边界。我这里定一个非常典型的场景一个能读取本地笔记、查询天气、创建日程的个人助理。它需要三个工具search_note搜索本地笔记内容、get_weather查询天气信息、create_schedule创建日程提醒。这三个工具有足够的区分度又不会太复杂非常适合当训练项目。工具边界的划定是有讲究的。每个工具应该只做一件明确的事不要写一个万能的process_command函数。工具描述越明确模型越容易判断该不该调用、什么时候调用。比如search_note的描述可以写成“在本地笔记库中搜索关键词返回最相关的三条笔记内容”而不是“搜索任何东西”。这样模型就不会在用户问天气时错误调用它。3.2 手写Agent核心循环代码会分成三块模型客户端、工具集、循环控制器。第一版不引入异步、不引入复杂状态机就是把最基本的思路展示出来。先定义模型客户端这里用伪接口表示实际使用时替换成你选用的模型服务SDK# llm.py class LLMClient: def __init__(self, api_key: str): self.api_key api_key def chat(self, messages: list, tools: list None) - dict: 调用大模型Chat接口。 返回结构建议统一为 { content: str, # 模型返回的文本内容 tool_calls: [ # 模型要求调用的工具列表 { id: call_xxx, name: search_note, arguments: {keyword: 旅行} } ] } # 实际实现请根据你使用的模型SDK进行调整 pass接着定义工具集。每个工具本质上就是一个普通Python函数外加一份“描述信息”描述信息里包含了名称、功能、参数说明# tools.py def search_note(keyword: str) - str: 在本地笔记库中搜索关键词返回最相关的三条笔记内容。 # 实际实现可以读取本地markdown文件 return f关于{keyword}的笔记共有2条: ... def get_weather(city: str) - str: 查询指定城市当天天气情况。 # 实际实现可调用天气接口或读取静态数据 return f{city}今天多云气温18~25度风力3级。 def create_schedule(time: str, event: str) - str: 创建日程提醒time格式为YYYY-MM-DD HH:MMevent为日程内容。 return f已创建日程{time} {event}工具描述信息放在一个字典里传给模型TOOL_SCHEMAS [ { name: search_note, description: 在本地笔记库中搜索关键词返回最相关的三条笔记内容。, parameters: { type: object, properties: { keyword: {type: string, description: 搜索关键词} }, required: [keyword] } }, { name: get_weather, description: 查询指定城市当天天气情况。, parameters: { type: object, properties: { city: {type: string, description: 城市名称} }, required: [city] } }, { name: create_schedule, description: 创建日程提醒time格式为YYYY-MM-DD HH:MMevent为日程内容。, parameters: { type: object, properties: { time: {type: string, description: 日程时间格式YYYY-MM-DD HH:MM}, event: {type: string, description: 日程内容} }, required: [time, event] } } ]然后是核心循环控制器。循环逻辑的通用伪代码可以这样写def run_agent(user_input: str, max_iterations: int 5): messages [ {role: system, content: 你是一个贴心的个人助理。当需要查询或操作时调用工具最后用自然语言回答用户。}, {role: user, content: user_input} ] for step in range(max_iterations): response llm.chat(messages, toolsTOOL_SCHEMAS) messages.append({ role: assistant, content: response[content], tool_calls: response.get(tool_calls) }) # 如果没有工具调用就说明模型要给出最终答案了 if not response.get(tool_calls): return response[content] # 遍历所有工具调用并执行 for call in response[tool_calls]: tool_name call[name] tool_args call[arguments] if tool_name in TOOL_MAP: result TOOL_MAP[tool_name](**tool_args) messages.append({ role: tool, tool_call_id: call.get(id, ), content: result }) else: # 处理工具不存在的情况 messages.append({ role: tool, tool_call_id: call.get(id, ), content: f错误工具 {tool_name} 不存在 }) return 达到最大迭代次数未能完成任务请尝试更明确的描述。这段代码就是Agent的雏形。每次循环里它把消息列表发给模型模型返回两种可能之一要么直接回答要么要求调用工具。如果要求调用工具程序就会执行对应的Python函数把结果追加到消息列表里再次发送给模型直到模型认为信息足够、给出最终答案。里面有两个容易踩的细节一是一定要把tool_call_id原样回传很多模型服务靠它关联工具请求和结果二是执行工具参数时不要用裸的eval直接用**tool_args解包即可工具内部再做好参数校验。3.3 参数设置与提示词设计的关键点同样是上面这个循环参数设置不同跑出来的效果会差很多。首先是temperature它控制模型的随机性。Agent开发里我建议调低到0到0.3之间因为工具选择是一个“判断题”不是“创作题”模型不应该有太多创造发挥。我第一次做Agent时图新鲜把温度调到0.8结果模型经常脑补工具名和参数惨不忍睹。其次是max_tokens要设置合理值。有些工具调用返回结果很长如果返回值超过了max_tokens模型回复会被截断你解析tool_calls就会失败。建议把单次输出max_tokens设置得相对大一些毕竟Agent场景下模型可能要先输出一段分析再调用工具。提示词设计也直接影响成功率。系统提示词里应当写清楚三件事机器人的角色、可用的工具、回应的风格。但不要试图在提示词里塞过长的工具说明工具描述属于工具协议层的字段不需要你手工复制。如果模型服务不支持结构化工具协议你可以把所有工具描述统一拼进系统提示词但那样会消耗更多token而且模型解析的稳定度会下降能上原生函数调用就优先用原生的。3.4 用现成编排框架快速实现如果你已经被手写循环折磨够了或者你的场景确实需要多智能体协作、复杂记忆、条件分支这时就可以上现成框架。以某个开源Agent编排框架为例它的核心概念包括Agent执行单元、Tool工具封装、Memory记忆、Planner规划器。用框架搭上面这个个人助理通常只需要几步封装工具把search_note、get_weather、create_schedule按框架要求包装成Tool对象声明名称、描述和参数Schema。创建Agent实例化一个Agent指定使用哪个模型、装载哪些工具、使用什么记忆策略。启动会话调用agent.run(帮我查一下北京明天天气并添加日程)框架内部会自动完成规划、调用、总结的循环。用框架的好处是省事坏处是出了问题不好排查。我用框架重新实现上述功能后发现Agent在处理多轮对话时确实更稳因为框架帮你处理了很多上下文细节。但我依然建议你把第3.2节的代码亲自跑一遍因为只有手写过一次你才能真正看懂框架的日志输出知道它每一条日志对应底层哪个环节。否则一旦框架升级或者默认行为改变你会完全摸不着头脑。4. 让Agent真正“好用”的进阶设计4.1 短期记忆滑动窗口与上下文管理所有对话模型都有自己的上下文长度上限。当你维护的消息列表越来越长就会撞到token上限。所以Agent必须要有短期记忆管理策略。最常用的方法是“滑动窗口”只保留最近N轮对话更早的对话可以压缩成“历史摘要”放进系统提示词。这里要特别注意一个细节不能把所有历史都一刀切丢掉尤其是“工具执行结果”往往携带关键信息。比如用户让你查了三天的天气最后问“这三天哪天适合跑步”如果你只保留最后一轮的天气结果模型是没有足够信息回答的。我的做法是把每一轮工具结果精简后存储在历史摘要里而不是简单地把旧消息从列表物理删除。比如把大段天气数据压缩成“三天天气分别为晴、雨、多云”。这样既保留语义又极大减少token占用。实现滑动窗口时你可以在循环外围加一个MessageBuffer类负责维护消息列表、截断旧消息、生成摘要。核心逻辑也不复杂当消息列表的预估token数超过阈值时调用模型对前面的消息做一次总结然后将总结作为一条系统消息放在列表头部。这个方法第一次跑通时你会非常有成就感因为它解决了Agent“聊多了就失忆”的痛点。4.2 长期记忆向量库加持的个人知识仓库短期记忆解决“这次会话内”的遗忘问题长期记忆则是让Agent跨会话记住偏好和知识。比如用户两周前问过“我对花生过敏”新会话里用户说“帮我推荐晚餐”你肯定不希望Agent推荐花生酱。长期记忆的常见方案是向量库加Embedding把所有需要记忆的文本切成片段做成向量存进数据库每次用户输入时把输入也转成向量检索最相关的记忆片段拼进当前消息上下文。下面是一段贴合第一版项目的伪代码# memory.py from vector_store import VectorStore memory VectorStore() def save_memory(text: str): vector embed(text) memory.add(vector, text) def load_relevant_memory(query: str, top_k: int 3) - list[str]: query_vector embed(query) results memory.search(query_vector, top_ktop_k) return [item[text] for item in results]记忆模块接入Agent循环的方式很优雅在每次调用模型前先拿用户当前问题到长期记忆里检索把检索到的相关内容作为“背景消息”放在用户问题之前。不要把检索到的所有内容都塞进去取最相关的2到3条就够了。长期记忆不是越多越好塞进去大量无关内容反而会干扰模型判断。在中文场景下Embedding模型的选择要测试不同模型对中文语义的捕捉差别挺大。建议用一组真实语句做评测比如把“我对花生过敏”和“我不能吃坚果”当成两段记忆看检索系统能不能在问“晚餐推荐”时准确召回它们。召回不准确后面的推理再强也没用。4.3 多工具协作与优先级决策当工具数量超过五个以后模型选错工具的概率会明显上升。首先应该控制每个工具描述的长度不要写废话要写清楚“什么场景下用、大多数情况下不要用于什么场景”。比如你有一个get_stock_price工具描述里可以写“查询个股实时价格适用于股票行情相关的问题”不要写“获取金融数据”因为金融数据过于宽泛。其次在系统提示词中明确工具优先级。比如个人助理场景优先用本地笔记库回答私密问题不要用搜索结果回答。你可以这样写“当问题涉及用户个人笔记时优先使用search_note当需要外部实时信息时使用get_weather。”这个看似很小的提示能让模型的行为稳定很多。还有一类情况是多个工具都可以完成同一个任务比如有一个create_schedule和check_schedule模型可能会把“查看日程”误调成“创建日程”。这种情况下你不仅要靠工具描述区分还要在代码层面做“操作前确认”如果工具是修改类操作先让模型向用户确认。在Agent的第一版里修改操作直接执行是很危险的建议在create_schedule执行前加一个requires_confirmationTrue的标记被标记的工具在真正执行前需要用户确认这是一道很好的安全网。4.4 错误处理与重试策略Agent开发中有一句经验错误不是例外而是日常。模型返回的JSON参数可能损坏工具可能超时外部接口可能限流。你必须在第一个版本就把错误处理写进去。针对“模型返回的工具调用格式不合法”可以在循环里加一个try-except捕获JSON解析异常把错误信息回传给模型让模型重新生成。很多模型对“你刚才的工具调用解析失败缺少参数city请重新调用”这类反馈非常敏感会自动修正。这个过程本身就是Agent自我纠错能力的来源。针对“工具执行超时”建议每个工具内部自己处理异常并返回可读的错误信息而不是把堆栈抛给Agent循环。你的工具返回字符串应该是一个让模型能理解的文本比如“查询天气接口超时请稍后重试或询问用户”。这样模型就能把错误信息整合进回复而不是直接崩溃。如果某个外部接口连续超时你可以在Agent层增加重试计数器最多重试两次避免无限卡死。5. 常见问题与排查实录5.1 高频问题速查表这里把我开发Agent过程中遇到的典型问题整理成一张表方便你按图索骥。问题现象常见原因排查思路与解决方案模型不调用工具直接瞎回答工具描述不清晰系统提示词没说明要使用工具模型服务不支持函数调用检查工具Schema明确“什么时候使用这个工具”把系统提示词改成“当需要时可调用工具”改用支持函数调用的模型接口模型调用了不存在的工具名工具描述和实际函数名不一致模型幻觉生成工具名核对所有工具名是否一致工具名建议简短有意义代码层面增加兜底分支向模型返回“工具不存在”工具参数缺字段参数Schema没有声明required模型生成了错误参数在每个工具Schema中把必填字段设置为required解析参数时做校验缺参数时把缺失列表反馈给模型Agent陷入无限循环工具结果没有正确改变状态模型反复调用同一工具最大迭代次数设置过大检查消息列表是否正确追加tool结果增加工具调用的去重机制把max_iterations设小一点比如5模型答非所问忘记上下文上下文被截断工具结果被丢弃消息role错乱确认每一轮消息都按正确角色追加检查滑动窗口策略是否把关键工具结果截掉了把工具结果精简后保留代码报错tool_call_id不存在部分模型服务不返回该字段在回传工具结果前检查工具调用对象是否有id没有就自己生成一个唯一值并保证一致并发请求时Agent状态互相污染用了全局消息列表每个会话独立创建消息列表和Agent实例不要共用全局变量5.2 四个值得记下的踩坑经验先讲第一个坑一开始我把工具名称取得特别文艺比如get_sky_status代表天气查询。结果模型经常认为这个工具和“心情”有关用户问“今天心情怎么样”时居然去调它。后来我把工具名改成直白的get_weather并在描述里加了一句“只能用于天气查询不能用于分析情绪”问题立刻缓解。工具命名绝对不要追求花哨直白是稳定性的第一保障。第二个坑是上下文消息角色使用错误。最初的代码里我习惯把工具结果以user角色回传某个API上也能跑通但换了一个模型后模型完全无视工具结果一直重复“让我检查一下”。后来才意识到很多模型服务要求工具结果必须使用专用的tool角色且必须携带对应的tool_call_id。改了之后模型终于能“看到”工具返回的内容了。这个经验说明不同服务商的协议细节有差异读文档比硬猜更节省时间。第三个坑是安全边界。我的实验Agent里有一个“删除文件”工具测试时模型确实按预期调用了但它在用户只问“帮我看看有哪些垃圾文件”却要求“全部删除”时没有做二次确认直接把对应文件删了。那以后我定了铁律所有具有破坏性或不可逆操作的工具在真正执行前必须经过一个requires_confirmation检查点。这个机制甚至可以用于日程创建这类非破坏操作防止误操作。第四个坑是外部接口的穷举。早期天气工具是直接调外部接口接口一超时Agent循环就直接抛异常。改进后的方案是把所有外部依赖包一层“包裹器”在包裹器里处理超时、重试、限流并把错误信息转为模型能理解的字符串。这样即使外部接口挂了Agent还能给用户一句“天气服务暂时不可用建议稍后再试”而不是整个进程崩溃。真实项目中工具的稳定性比模型的聪明度更能决定用户体验。5.3 如何评估你的Agent是不是真的变好了很多人在跑通第一个Agent后只会说“看起来不错”但这是不够的。要系统性地评估一个Agent至少要看四个指标任务完成率、平均调用步数、工具选择准确率、失败原因分布。每次改动提示词或工具Schema前准备一组测试用例比如二十个典型用户问题覆盖单工具、多工具、边界模糊、无工具可调用等情况。跑完一轮记录模型是否正确选择了工具、是否在合理的步数内完成、是否给出了用户可理解的答案。我一般会把测试结果导出成结构化表格这样能直观看到改动前后成功率变化。有一段时间我调整了系统提示词把“简洁回答”改成了“详细回答”结果任务完成率没变但平均步数暴增因为模型总想多调用工具来“展示能力”。这正是量化指标的价值它能阻止你凭感觉优化。Agent开发里没有评测指标就谈不上迭代。6. 写在最后的个人体会如果你照上面的步骤把第一个Agent跑通我相信你已经对“模型加工具加循环”这个内核有了很深的体感。我个人在实际项目里最大的体会是Agent真正难的地方从来不是用什么框架、调哪个大模型而是把工具边界定义清楚、把上下文管理好、把失败路径想全。很多看起来“模型不够聪明”的问题排查到最后往往是工具描述有歧义或者消息角色用错了。所以每当你对Agent效果不满意先别急着换模型回过头检查工具和无脑的细节往往会有意外收获。我也建议你从今天这个最小原型出发先增加一个长期记忆模块再把某个工具换成真实的外部服务接口你会发现整个系统的复杂度上了一个台阶但也有了真正落地的姿态。祝你玩得开心。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑