
你大概已经发现了和聊天机器人打交道时它回复得再天花乱坠到头来还是得自己动手查资料、改文件、发消息。所谓“AI Agent”就是奔着改变这件事来的它把大模型从“问答机”升级成“能自己干活的助手”——拆解目标、调用工具、读文件、查接口在无人盯守的情况下一路把任务跑完。这个标题很直白就是“从0搭建你的第一个AI Agent”。这篇内容适合三类人刚入门想做点正经AI项目的开发者、整天琢磨自动化但又不太会写后端的产品经理以及所有被重复性电脑操作折磨到崩溃的打工人。我会把整体设计拆开讲清楚Agent的运行原理再带着你从零手写一个最小可用的Agent。整个过程中你会真实触碰到“工具调用”“记忆管理”“意图解析”这些关键词也能看到它们到底是怎么协作的。1. 先别急着写代码Agent到底是个什么东西很多人的第一个误区是把Agent当成“加强版聊天机器人”。两者看起来差不多但思考逻辑完全不一样。1.1 对话机器人是“说话”Agent是“办事”传统聊天机器人的工作流是用户输入一句话模型生成一段回答然后结束。它没有状态不关心你要干嘛更不会主动去调数据库、开浏览器。Agent的工作流更像一个员工在领任务老板说“帮我整理一下这周的销售数据”员工会先拆解——数据在哪需要哪些字段要不要做图表然后查系统、拉数据、做统计、生成报告。每一步都有一个明确输出最终目标是把“活”干完而不是把“话”说完。从工程落地角度这个区别带来两个关键变化输出从“文本”变成了“动作”。模型不仅产生回答还产出一份“操作指令”。执行从“单次”变成了“循环”。Agent需要不断观察执行结果再决定下一步做什么。理解了这一点“Agent能做什么”的问题就迎刃而解能帮你把多步骤的重复性工作自动化能跨工具协同办公能像助手一样理解模糊指令并拆解成可执行任务。1.2 Agent的四个核心组件规划、工具、记忆、行动如果只看源码一个Agent本质上就是一段循环代码。但围绕这个循环有四个组件缺一不可规划Planning把大目标拆成小步骤。比如“订一张周五从北京到上海的高铁票”要拆成确认日期时间、查车次、选座位、提交订单。大模型最擅长干这个。工具ToolsAgent的“手脚”。工具是真实的API、函数或外部系统比如天气查询接口、日历写入接口、文件读写函数。模型判断“该做什么”之后由工具真正“执行”。记忆MemoryAgent的“工作记忆”。你不可能每次调用都把所有历史重发一遍需要一个机制存住关键信息。记忆分为短期当前任务上下文和长期跨会话的偏好比如“用户习惯用表格看数据”。行动Action基于规划结果执行工具调用。它是连接模型、工具、外部世界的桥梁通常以“function call”的形式体现。可以这样理解规划是大脑工具是手脚记忆是便签纸行动是肌肉。缺了哪个Agent都会变成残疾人。现在来看真实系统里它们是如何协同的。2. 搭建前的环境准备选择最小可靠工具链动手前先把地基打好。2.1 开发环境与核心依赖不建议上来就用那些重框架。单元测试跑不通、概念复杂、配置项一大堆新手很容易迷失。我推荐一条“最小可行路径”Python 3.10以上一个大模型API的访问权限我下面代码以通用接口为例requests库用于HTTP请求一个支持JSON解析的文本编辑器或IDEWindows、macOS还是Linux都无所谓纯Python脚本没有平台限制。唯一要注意的是Python环境别弄乱建议建一个新的虚拟环境比如python -m venv agent_env然后pip install requests就够了。2.2 模型接口选择能力边界决定Agent上限Agent的“智能”来自模型。选模型时重点关注三个方面推理能力、工具调用能力、上下文长度。推理能力决定它能不能把复杂任务拆清楚工具调用能力决定它能不能按约定格式输出操作指令上下文长度决定它一次性还记得多少信息。第一次练手建议选一个有工具调用支持的模型因为手写一个解析函数调用协议的解析器虽然也不难但初始化阶段简化一层是最好的。关于模型API现在各家平台都推出类兼容的接口你只需要把代码里的api_key、base_url、model这三个变量替换成自己实际在用的配置即可。安全性提醒一句密钥千万别硬编码上传到公开仓库建议用环境变量读取。我把依赖固定在这三层是为了让你不被繁琐配置带偏。等你自己真的搭完一遍再去看LangChain这种框架的源码体会会完全不一样。3. 动手实现从“能聊”到“会干”的三步演进现在进入正题。我会分三个版本迭代第一版只有对话第二版给Agent装上“手”第三版让它连续思考、多次调用工具直到任务完成。每一步都有完整代码和运行逻辑讲解。3.1 第一版最小ReAct循环跑起来先写一个最简单的“对话循环”让模型能思考并输出下一步动作。这是Agent的核心骨架通常称为ReActReasonAct模式。import json import requests API_KEY 你的-api-key BASE_URL https://你的模型接口地址/v1 MODEL_NAME 你的模型名称 def call_model(messages): url f{BASE_URL}/chat/completions headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: MODEL_NAME, messages: messages, temperature: 0.2, tools: [ { type: function, function: { name: get_weather, description: 查询某个城市的当前天气, parameters: { type: object, properties: { city: {type: string, description: 城市名} }, required: [city] } } } ] } response requests.post(url, headersheaders, jsonpayload, timeout60) return response.json()[choices][0][message] def run_agent(user_input): messages [ {role: system, content: 你是一个有帮助的AI助手擅长使用工具来回答用户的问题。}, {role: user, content: user_input} ] # 第一轮让模型决定要不要调用工具 result call_model(messages) print(模型回复:, result)这段代码里我定义了一个工具get_weather它描述了“能查什么城市的天气”并且定义了参数。模型收到用户输入后会尝试输出一个结构化调用请求{name: get_weather, arguments: {city: 北京}}。这里有两个关键点值得说透工具的“描述”比“实现”更重要。模型全靠工具的名字、描述、参数定义来判断何时调用它。描述写得太模糊它该调时不调参数定义太复杂它容易调错。给工具起名字就像给函数起名要一看就懂。temperature0.2是刻意设置的。Agent不是创意写作它需要稳定、可预期的规划输出温度高了容易乱来。实际跑一轮你会看到模型返回里带一个tool_calls字段。这就说明它已经决定“我需要调用工具”。但我们还没写“执行”这部分所以它还不会真正查天气——这是下一节的事。3.2 第二版给Agent装上“手”学会调用工具光有规划不行还要实际行动。这一步就是解析模型给出的指令调用真实的函数再把结果喂回给模型让它继续回答。def get_weather(city): 模拟的天气查询函数真实场景替换为实际API mock_data { 北京: 晴22°C, 上海: 多云25°C, 广州: 小雨28°C } return mock_data.get(city, 暂时没有该城市的天气数据) def run_agent_with_tools(user_input): messages [ {role: system, content: 你是一个有帮助的AI助手擅长使用工具来回答用户的问题。}, {role: user, content: user_input} ] # 模型判断需要调用工具 result call_model(messages) messages.append(result) # 提取工具调用请求 if result.get(tool_calls): for tool_call in result[tool_calls]: func_name tool_call[function][name] args json.loads(tool_call[function][arguments]) print(f[工具调用] {func_name}({args})) # 执行真实函数 if func_name get_weather: func_result get_weather(args[city]) # 把工具结果附加回对话 messages.append({ role: tool, tool_call_id: tool_call[id], content: json.dumps(func_result, ensure_asciiFalse) }) # 让模型基于工具结果生成最终回答 final_answer call_model(messages) print(最终回答:, final_answer[content])运行逻辑是这样的用户问“北京天气怎么样”模型判断“需要查天气”返回tool_calls请求你的代码捕获这个请求调用真正的get_weather(北京)函数天气结果作为roletool的消息回传给模型模型看到工具返回的数据生成最终自然语言回答这个循环就是Agent最核心的本质模型负责决策代码负责执行。写到这里很多人会问工具调用是不是只能做“查天气”这种简单的事完全不是。你完全可以定义send_email、read_file、query_database、create_calendar_event把真实业务能力暴露给模型。有两层工具接入时的适配心得我用自己的经历说一下工具函数返回的结果必须是JSON且保持健壮。如果工具报错不要直接抛异常而是返回一个描述错误的JSON串让模型知道“刚才那一步没成功可以考虑换方案”。这会让Agent更稳定。一次对话可以有多个函数调用模型是按需并行返回的。代码里的for tool_call in result[tool_calls]就是处理这种情况。3.3 第三版让它“一路跑到底”——连续多轮工具调用的规划第二版解决了一次性的工具调用。但现实问题往往是“多步连锁”的。举个例子用户问“比北京和上海的天气如果上海更好帮我写一封推荐去上海出差的邮件摘要”。这会涉及先后查北京和上海的天气、比较、生成邮件摘要。每一步都是独立的工具调用但目标只有一个。要让Agent具备这种连续规划能力只需要把上面的处理逻辑包进一个while循环只要模型还在请求调用工具就继续执行没有工具调用时说明它准备给出最终答案了。def run_agent_full(task): messages [ {role: system, content: 你是一个任务执行助手请一步步完成任务。每次需要调用工具时按指定格式输出。}, {role: user, content: task} ] max_iterations 8 # 防止死循环的保险 for _ in range(max_iterations): result call_model(messages) messages.append(result) if not result.get(tool_calls): print(任务完成最终结果:) print(result[content]) return # 执行所有工具调用 for tool_call in result[tool_calls]: func_name tool_call[function][name] args json.loads(tool_call[function][arguments]) print(f[第{_ 1}轮工具调用] {func_name}({args})) if func_name get_weather: output get_weather(args[city]) elif func_name send_email: output send_email(args[to], args[subject]) else: output json.dumps({error: f未知工具:{func_name}}, ensure_asciiFalse) messages.append({ role: tool, tool_call_id: tool_call[id], content: json.dumps(output, ensure_asciiFalse) })这个循环就是Agent的“引擎”。关键变量是max_iterations——它是一个安全阀门防止模型陷入“无限调用工具”的恶性循环。实测下来大部分正常任务3-5轮就能完成。如果超过8轮还没结束大概率是定义的任务有歧义或者工具返回结果不符合预期。设置上限并返回“无法完成”是工程上必须做的兜底。在这个阶段我认为有三个操作细节需要特别注意每一步的messages.append(result)不能省。它把模型的“思考过程”也放入了上下文后续模型才知道自己之前做过什么、打算做什么。这就是Agent的短期记忆。工具执行结果也要用tool_call_id精确关联。一个模型响应里可能并行发起了多个工具调用系统需要知道哪个结果对应哪个请求。最后模型会停止输出tool_calls这是自然终止信号。不要试图用正则去匹配内容里的“完成”之类的词那不可靠。到这一步你其实就是拥有一个真正意义上的最小Agent了模型能规划、能调用外部函数、能迭代多次直到达成目标。很多生产系统的Agent剥离掉复杂框架和UI之后核心也只是这个循环。3.4 再加一个任务实例让Agent去“查资料做总结”的完整运行记录为了让整个过程更有代入感我用一个稍微复杂的任务来说明假设用户给的指令是“帮我查北京和上海的天气然后告诉我哪个更适合户外跑步”。第1轮调用模型返回tool_calls [get_weather(北京), get_weather(上海)]程序并行执行两个函数拿到“晴22°C”和“多云25°C”结果回传第2轮调用模型已经看到两地的天气不再发出工具调用直接输出“北京晴22°C非常适合户外跑步上海多云但温度25°C也能跑不过北京更干燥凉爽整体建议北京更适合。” 这里漂亮的一点是模型能够自己完成比较和推理这是工具与推理结合的产物。如果温度差异很大模型还可能主动进一步查询空气质量——只是当前这个任务不需要那么多信息。任务到此结束。整个执行过程就是一次微型“项目调度”。这个是Agent在真实场景下的工作方式它不只是回答“天气怎么样”而是完成一个包含查询、比较、建议的完整决策链路。4. 常见问题与排查技巧实录从零开始最容易踩的坑其实不在模型而在工程细节。我把实操中遇到的典型问题整理成一个速查表。4.1 API报错与配置问题现象可能原因解决办法鉴权失败401API Key错误检查环境变量是否导出确认密钥没有多余空格超时504模型请求过大或网络不稳定减少上下文长度设置合理的timeout建议60s返回“模型不存在”model_name与服务平台不匹配去平台文档核对精确的模型标识符上下文长度溢出历史消息过多截断早期对话消息只保留最近N轮我在练手时发现一个非常隐蔽的坑本地终端环境变量和IDE环境变量不一致。明明在命令行里已经export了API_KEYIDE里跑脚本却说没有定义。解决办法是统一环境变量来源或者直接在代码里用os.environ.get(API_KEY)检查一下。4.2 工具调用的“反常”情况与调优策略情况一模型执意不调用工具。超出来的问题通常是工具的description与用户指令语义匹配不上。给get_weather加入“输入城市名返回该城市实时天气状况包括温度和天气现象”这类细节几乎立竿见影。情况二模型调用了不存在的工具。大概率是工具描述里有歧义或者多个工具的description大同小异。把所有工具名列出来自查一遍命名尽量动词开头、语义唯一。情况三工具返回结果奇怪地影响了回答。比如查天气返回了JSON模型却回答“天气信息不存在”。这种时候请用调试模式打印实际传给模型的内容。我发现一个规律很多模型对content字段的字符串处理与预期不太一样。解决方案是让工具返回结果自带说明前缀比如天气查询结果晴22°C模型的理解成功率会大幅提升。情况四Agent陷入重复调用同一个工具得到同样的结果。这是典型的规划失智。两个方向缓解在system提示里明确“不要重复调用已经获得结果的工具请基于已有结果继续推理”或者程序侧做去重如果连续N轮检测到完全相同的工具调用直接强制终止。4.3 提示词设计的独家心得提示词在Agent里扮演的角色被严重低估。我踩过几次坑之后总结出一个能延展到各类Agent的模板结构角色描述你是谁你的工作方式。操作边界你必须用什么工具不许用什么工具。任务步骤请按步骤做每步给出确定性输出。失败策略如果工具调用失败应该怎么重新规划。终止条件什么情况下输出最终答案。举个例子你是任务执行助手。你的职责是利用可用工具完成任务。 可用工具get_weather, send_email。 如果用户要求发邮件必须使用send_email工具执行执行完成后告知发送结果。 若工具返回异常请说明异常原因并提出替代方案不要编造成功结果。 当所有子任务完成后用自然语言汇总最终结论。这个模板能让Agent的决策稳定性有一个可见的提升。尤其是“若工具返回异常不要编造成功结果”这条是防止AI“一本正经地撒谎”的有效手段。5. 从Demo到可用下一步进阶与工程化建议搭建完第一个最小Agent很多人会兴奋地想把它部署上线。我的建议是先在本地加三个扩展再考虑上线不迟。5.1 扩展一长期记忆当前框架里的记忆全靠messages对话一结束就全忘了。现实需求是跨会话记忆比如电商客服要记住用户的收货地址。最简单的方案是引入向量数据库把历史关键信息向量化存储每次启动时检索相关记忆注入system提示。这一块网上现成方案很多但如果只是想跑通MVP用SQLite存“用户-偏好”键值对就已经够用。关键是先理解“记忆”在Agent架构中的位置再选存储策略。5.2 扩展二更多工具与权限控制工具越多Agent能力越强风险也越大。给Agent一个delete_file工具它就可能真的删掉你的项目文件。新手最容易忽略的是工具侧的安全校验。请在工具函数内部加好鉴权、限流、数据校验。不要指望模型帮你判断“这个操作是否安全”模型没有安全意识它只有任务意识。正确的姿势外部输入默认不可信工具函数内部严格校验参数。比如把之前的get_weather改成真实API调用def get_weather(city): if city not in [北京, 上海, 广州]: return json.dumps({error: 仅支持查询北京、上海、广州的天气}) # 调用远程API...这样即便模型发神经传了个乱码城市你的工具也不会出大问题。5.3 扩展三可观测性与调试Agent程序第一次跑失败你几乎没法直接看出问题出在哪一环。是模型理解错了还是工具返回值不对还是上下文拼错了这需要给每一轮循环加日志记录。建议在循环里打印三层信息本轮模型回复的原始JSON、本轮要执行的工具与参数、工具返回结果。用一个LOG_LEVELDEBUG的环境变量控制日志详略程度。我个人的习惯是把每一步的网络请求和响应都落盘到一个日志文件里。这个文件还在Problem就能定位这个文件没了排错就像盲人摸象。实现上不复杂在call_model函数里加一行print(json.dumps(payload, ensure_asciiFalse))就够了。等你把这三点都打磨过一遍再考虑接入工作流编排框架、多Agent协作、异步任务队列这些高级话题时你的地基就是扎实的。因为你会发现那些框架本质上只是在帮你管理“规划、工具、记忆、行动”这四个组件的更复杂形态而已。最后的几句实在话搭这个Agent技术难度不大真正难的是思维方式的转变你不再告诉机器“每一步怎么做”而是告诉它“目标是什么、有哪些工具”然后信任它能自己规划路径。这种思维转变对产品设计和日常工作的启发比代码本身更有价值。我在实际搭建中体会最深的一点是别追求“全知全能”的Agent。给它3-5个精心封装好的工具比一口气连20个API要稳定得多。工具越少模型的决策空间越窄每一步的可靠性越高。等用户真的产生了更多需求再逐步打开新的工具权限这样迭代既安全又高效。现在你可以打开终端把上面第三版的代码粘进去换好模型配置然后对它说一句“帮我查查北京和上海的天气如果你觉得上海更适合散步就告诉我为什么。”看看你的第一个AI Agent是怎么一步一步完成任务的。