☰
从零搭建你的第一个AI Agent:大模型从问答到自主执行
2026/10/20 6:29:18 网站建设 项目流程

你大概已经发现了,和聊天机器人打交道时,它回复得再天花乱坠,到头来还是得自己动手查资料、改文件、发消息。所谓“AI Agent”就是奔着改变这件事来的:它把大模型从“问答机”升级成“能自己干活的助手”——拆解目标、调用工具、读文件、查接口,在无人盯守的情况下一路把任务跑完。

这个标题很直白,就是“从0搭建你的第一个AI Agent”。这篇内容适合三类人:刚入门想做点正经AI项目的开发者、整天琢磨自动化但又不太会写后端的产品经理,以及所有被重复性电脑操作折磨到崩溃的打工人。我会把整体设计拆开,讲清楚Agent的运行原理,再带着你从零手写一个最小可用的Agent。整个过程中你会真实触碰到“工具调用”“记忆管理”“意图解析”这些关键词,也能看到它们到底是怎么协作的。

1. 先别急着写代码:Agent到底是个什么东西

很多人的第一个误区,是把Agent当成“加强版聊天机器人”。两者看起来差不多,但思考逻辑完全不一样。

1.1 对话机器人是“说话”,Agent是“办事”

传统聊天机器人的工作流是:用户输入一句话,模型生成一段回答,然后结束。它没有状态,不关心你要干嘛,更不会主动去调数据库、开浏览器。

Agent的工作流更像一个员工在领任务:老板说“帮我整理一下这周的销售数据”,员工会先拆解——数据在哪?需要哪些字段?要不要做图表?然后查系统、拉数据、做统计、生成报告。每一步都有一个明确输出,最终目标是把“活”干完,而不是把“话”说完。

从工程落地角度,这个区别带来两个关键变化:

  • 输出从“文本”变成了“动作”。模型不仅产生回答,还产出一份“操作指令”。
  • 执行从“单次”变成了“循环”。Agent需要不断观察执行结果,再决定下一步做什么。

理解了这一点,“Agent能做什么”的问题就迎刃而解:能帮你把多步骤的重复性工作自动化,能跨工具协同办公,能像助手一样理解模糊指令并拆解成可执行任务。

1.2 Agent的四个核心组件:规划、工具、记忆、行动

如果只看源码,一个Agent本质上就是一段循环代码。但围绕这个循环,有四个组件缺一不可:

规划(Planning):把大目标拆成小步骤。比如“订一张周五从北京到上海的高铁票”要拆成:确认日期时间、查车次、选座位、提交订单。大模型最擅长干这个。

工具(Tools):Agent的“手脚”。工具是真实的API、函数或外部系统,比如天气查询接口、日历写入接口、文件读写函数。模型判断“该做什么”之后,由工具真正“执行”。

记忆(Memory):Agent的“工作记忆”。你不可能每次调用都把所有历史重发一遍,需要一个机制存住关键信息。记忆分为短期(当前任务上下文)和长期(跨会话的偏好,比如“用户习惯用表格看数据”)。

行动(Action):基于规划结果执行工具调用。它是连接模型、工具、外部世界的桥梁,通常以“function call”的形式体现。

可以这样理解:规划是大脑,工具是手脚,记忆是便签纸,行动是肌肉。缺了哪个,Agent都会变成残疾人。现在来看真实系统里它们是如何协同的。

2. 搭建前的环境准备:选择最小可靠工具链

动手前,先把地基打好。

2.1 开发环境与核心依赖

不建议上来就用那些重框架。单元测试跑不通、概念复杂、配置项一大堆,新手很容易迷失。我推荐一条“最小可行路径”:

  • Python 3.10以上
  • 一个大模型API的访问权限(我下面代码以通用接口为例)
  • requests库用于HTTP请求
  • 一个支持JSON解析的文本编辑器或IDE

Windows、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的核心骨架,通常称为ReAct(Reason+Act)模式。

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": f"Bearer {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, headers=headers, json=payload, timeout=60) 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": "北京"}}。

这里有两个关键点值得说透:

工具的“描述”比“实现”更重要。模型全靠工具的名字、描述、参数定义来判断何时调用它。描述写得太模糊,它该调时不调;参数定义太复杂,它容易调错。给工具起名字就像给函数起名,要一看就懂。

temperature=0.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_ascii=False) }) # 让模型基于工具结果生成最终回答 final_answer = call_model(messages) print("最终回答:", final_answer["content"])

运行逻辑是这样的:

  1. 用户问“北京天气怎么样”
  2. 模型判断“需要查天气”,返回tool_calls请求
  3. 你的代码捕获这个请求,调用真正的get_weather("北京")函数
  4. 天气结果作为role=tool的消息回传给模型
  5. 模型看到工具返回的数据,生成最终自然语言回答

这个循环,就是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_ascii=False) messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": json.dumps(output, ensure_ascii=False) })

这个循环就是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_KEY,IDE里跑脚本却说没有定义。解决办法是统一环境变量来源,或者直接在代码里用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_LEVEL=DEBUG的环境变量控制日志详略程度。

我个人的习惯是:把每一步的网络请求和响应都落盘到一个日志文件里。这个文件还在,Problem就能定位;这个文件没了,排错就像盲人摸象。实现上不复杂,在call_model函数里加一行print(json.dumps(payload, ensure_ascii=False))就够了。

等你把这三点都打磨过一遍,再考虑接入工作流编排框架、多Agent协作、异步任务队列这些高级话题时,你的地基就是扎实的。因为你会发现,那些框架本质上只是在帮你管理“规划、工具、记忆、行动”这四个组件的更复杂形态而已。

最后的几句实在话

搭这个Agent,技术难度不大,真正难的是思维方式的转变:你不再告诉机器“每一步怎么做”,而是告诉它“目标是什么、有哪些工具”,然后信任它能自己规划路径。这种思维转变对产品设计和日常工作的启发,比代码本身更有价值。

我在实际搭建中体会最深的一点是:别追求“全知全能”的Agent。给它3-5个精心封装好的工具,比一口气连20个API要稳定得多。工具越少,模型的决策空间越窄,每一步的可靠性越高。等用户真的产生了更多需求,再逐步打开新的工具权限,这样迭代既安全又高效。

现在你可以打开终端,把上面第三版的代码粘进去,换好模型配置,然后对它说一句:“帮我查查北京和上海的天气,如果你觉得上海更适合散步,就告诉我为什么。”看看你的第一个AI Agent是怎么一步一步完成任务的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询