从零构建能写代码的AI智能体:Agent核心原理与实战教程
1. 项目概述1.1 这个项目到底在做什么先别急着看代码我们花几分钟把这件事聊透。AI Agent这个词最近被炒得火热但真正能把它讲清楚、并且提供一套可以落地的执行路径的人并不多。我这次写的是一个保姆级教程核心目标就一句话让你从零开始亲手构建一个能写代码的AI智能体。我先用一个生活化的类比帮你建立认知框架。假设你是一位项目经理手下有一个程序员小助手他的能力上限取决于你给他多少工作指南。你只告诉他“写一个登录接口”他可能写出跑不起来的碎片代码但如果你告诉他“先分析需求、再挑数据库、然后写接口、最后自测”他就能独立完成一套完整功能。AI Agent做的就是这个“小助手”的定位它不是聊天框里你一句它一句的对话玩具而是一个能接收任务、拆解步骤、调用环境、生成代码、自测修正、最终交付结果的自动化执行引擎。开发这样一个智能体涉及的核心技术点包括大模型接口封装、提示词工程设计、任务规划与工作流编排、代码执行沙箱集成、错误反馈闭环、以及记忆管理。把这些点串起来你就拥有了一个“AI程序员”。1.2 这个项目解决什么痛点市面上关于AI编程的教程太多但我观察到一个普遍现象大多数入门者卡在两次门槛前。第一次卡在“幻觉”层面。很多人把AI当成人觉得给它一句话它就应该自己跑完全流程。现实不是这样。大模型在生成代码时本质上是在做概率预测它不确定你的代码环境装了什么依赖、你的变量名想用什么样的前缀、你的项目结构是分层的还是平铺的。所以你会看到它输出一堆“看起来很有道理但其实跑不通”的代码。如果没有一个Agent框架帮它做环境感知、约束约束再约束这些代码基本等于废品。第二次卡在“操作断层”层面。从理清原理到写出一行能运行的框架代码中间隔着大量工程细节。API的参数按照什么规则传、工具的Schema怎么定义、Agent的主循环该怎么写、被执行代码的环境如何隔离每一个小坑都会让人劝退。这套教程的价值就在这里我帮你把这个断层补齐。你不需要自己从文档堆里摸索只需要跟着我的路径一步步把框架搭起来跑通第一个Demo然后再往里面填充自己的逻辑。提示这个项目适合有一定Python基础、了解HTTP请求原理、但从未独立搭建过Agent框架的开发者。如果你连Python的基本语法还不熟悉建议先花一到两周补基础再回来看这篇教程。2. 核心技术逻辑拆解2.1 Agent的核心结构一个带工具的思考引擎如果你拆开任何一个成熟的AI Agent会发现它的本质是一层“外挂逻辑”加上一个“大脑”。“大脑”就是大模型的对话接口负责理解意图、生成行动指令“外挂逻辑”就是工具和环境的集合负责把“思考结果”变成“实际动作”。这个结构用一句话概括模型负责决策代码负责执行。为什么非得这样拆因为大模型本身是一个离线训练出来的参数系统它没有手和脚连“往磁盘里写一个文件”这种基本动作都做不了——它只能输出文字。Agent框架就是给它配上“手”的过程我们定义好一批工具函数比如“执行Python代码”、“搜索本地文件”、“调用另一个API”然后把它们是做什么的、参数是什么格式翻译成模型能读懂的Schema。当用户说“帮我写一个FizzBuzz程序”时Agent的大致工作流程是模型读入任务结合系统提示词判断完成这件事需要执行一次代码生成。模型输出一次工具调用请求内容是“调用执行代码工具参数为Python代码字符串”。Agent框架拦截这个请求解析出工具名和参数在本地沙箱执行这段代码。执行结果以文本形式返回给模型模型再根据结果判断是继续修改还是直接输出最终答案。这四步构成了Agent的最小闭环。别看它简单它是你后面所有花哨功能的地基。2.2 提示词设计和模型选择的底层逻辑提示词工程是Agent开发里最容易被低估的部分。多数人的刻板印象是“提示词就是几句咒语”其实在很多模型能力已经很强的情况下提示词真正起到的作用是“降低任务的模糊度”。举个例子我曾经做个实验同一个代码生成任务用两套系统提示词去跑第一套是“你是一个AI助手。用户让你写代码你要写代码。”第二套是“你是一个代码生成Agent。收到任务后你必须先输出计划分析需求、选择技术方案、列出实现步骤。然后你必须调用代码执行工具生成代码并运行根据运行结果决定是否需要修复。代码运行成功或你确认无法解决后才能结束对话回复用户。所有结论要基于实际执行结果禁止在没有证据的情况下声称代码可用。”同样的复现任务第一套提示词下模型成功调用工具的概率不到40%;第二套模式下成功率直接提升到90%以上。原因很简答第二套提示词把“Action Space”约束了告诉模型“你能做的事是什么样子的、什么情况下算结束”模型就不会东一句西一句乱来了。模型选型方面如果你只是想跑通流程首选支持工具调用Function Calling的模型这块的生态成熟度会直接影响到你后面踩坑的数量。计费也要注意调工具场景下token消耗比纯聊天高出很多因为工具返回的结果会被反复塞进上下文每次循环都会翻倍一个简单任务跑掉5万token一点也不奇怪。所以别选太贵的模型做调试先用性价比高的模型把流程跑通了再切换。2.3 工具这个词到底指什么“工具”Tool这个概念容易让人误解。你以为工具是“某个软件功能”但在Agent体系里工具是“一个可以编程调用的函数单位”。具体来说一个工具就是一段函数的描述包含三个要素函数名、函数说明、参数Schema。Schema声明每个参数的类型、含义、是否必填。例如“执行Python代码”这个工具参数就只有一个code字符串代表要执行的代码函数说明则告诉模型“这个工具会在一个隔离的Python环境里运行你给的代码并返回标准输出或错误消息”。工具是Agent被“赋予能力”的唯一方式。你想让Agent会算数给它一个计算器工具想让Agent会发邮件给它一个邮件调用工具。这背后的设计哲学是不该把动作的复杂性塞进模型理解层而是让模型只负责“选择”和“规划”让函数负责“执行”和“确定性”。我有次在做金融报告时添加“写临时文件”工具顺手就把逐字稿扫描成了PDF存档特别方便。至于Agent是“真的理解自己在做什么”还是“在机械地干活”哲学上你可以慢慢争论工程上最后能完成任务的Agent就是好Agent。3. 环境准备与工具链选型3.1 基础环境配置列表动手之前先把环境装好否则后面每跑一步都在报错。以下是推荐的基础环境项照着配就行。依赖项推荐方案备注Python版本3.10及以上事件循环、异步特性对Agent这类IO密集型应用很重要大模型SDK某主流大模型厂商的Python包支持工具调用文档清晰代码执行沙箱子进程(subprocess)调用本地Python解释器干净、可控、实现快依赖管理venv pip隔离环境避免污染全局开发调试IDEVS Code Jupyter NotebookNotebook适合边改边测逻辑片段用subprocess做沙箱本质是在系统里起一个独立的子进程去执行代码主进程和子进程之间有标准输入输出管道来交互。这样做的好处是Agent要执行的代码和Agent框架本身不在同一个进程空间主框架不会因为子进程的崩溃而死掉。坏处是子进程对系统资源的访问很难完全限制所以更严格的生产环境通常要用容器隔离比如Docker。这里教学场景用subprocess就够了。3.2 大模型API接入配置全解接入大模型的API是Agent开发中最常规但最容易被忽视细节的一环。我见过不止一个人在这里踩坑拿到了Key按照官方文档抄了代码结果发现模型老是不按预期输出工具调用。先说基本配置步骤申请API访问凭证控制台会有不同模型的调用权限务必打开工具调用功能的权限开关。安装官方Python包创建客户端实例设置环境变量存储API Key。定义消息历史的数据结构。注意Agent对话是一个多轮沟通的过程你不能每次只发一条新消息必须把历史消息全部带上否则模型没有上下文判断。设置超时重试策略。生产环境推荐用指数退避初次失败后等1秒再试再失败等2秒然后是4秒封顶30秒。我在联调时发现一个高频坑有人会忽略会话中工具消息tool role的格式要求把工具执行结果放在user消息里再发回去。这样做会导致模型在某些情况下产生混乱尤其是在coarse对话历史比较长的时候。所以标准做法是模型发出工具调用请求后生成结构化的“工具消息”准确附加返回内容和对应调用ID不能省略、不能改Have子段。提醒API Key本质上等同资金账户的钥匙不要在代码里硬编码也不要提交进版本库里。放到环境变量文件里配合IDE的dotenv方案读取这是底线。4. 核心实现从框架代码到第一个Demo4.1 构建Agent主循环消息路由中枢主循环是Agent的心脏所有消息都在这里中转。在动手之前我们先明确一下主循环要处理的两种输出形态文本型响应模型认为任务已完成没有工具调用请求直接给用户最终回答。工具调用请求模型认为需要外部动作辅助输出结构化的工具名参数。框架需要拦截它、执行工具、把结果回传给模型。基于这个认知主循环的伪代码逻辑可以这样写def agent_run(user_message, max_iterations10): history [] history.append({role: user, content: user_message}) for _ in range(max_iterations): response llm_client.chat(history, toolsTOOL_SCHEMAS) if response.is_tool_call: # 解析工具名和参数 tool_name response.tool_name tool_args response.tool_args # 调用本地工具 tool_result execute_tool(tool_name, tool_args) # 把工具调用请求和结果都加入历史 history.append(response.message) history.append({ role: tool, tool_call_id: response.call_id, content: tool_result }) else: # 模型没有请求工具视为最终答复 return response.content.strip() return 已达到最大迭代次数未完成这段代码里有个关键阈值max_iterations。为什么要设上限因为模型在遇到执行报错时可能会陷入“反复改代码-反复报错”的死循环。如果不限迭代次数等于你的账号余额一直在被你AI自己消耗。在实际项目中10次是一个合理的上限值如果到了10次还没跑成功说明这个任务超出了当前模型的能力范围应该报错让上级干预。4.2 搞定工具调用协议模型怎么知道有哪些工具可用大模型API的工具调用机制虽然各家协议写法稍有不同核心逻辑大同小异。你需要做的是定义一个工具Schema列表比如这样TOOL_SCHEMAS [ { type: function, function: { name: run_python_code, description: 在Python 3.10环境中运行一段代码返回标准输出或错误信息, parameters: { type: object, properties: { code: { type: string, description: 要执行的Python代码 } }, required: [code] } } } ]这个Schema本身是给人看的可读格式但在协议层模型看到的具体编码格式取决于你用的SDK。有些SDK要求工具参数用JSON Schema有些则接受字典。把这些定义成常量后在每次chat调用的参数里传入toolsTOOL_SCHEMAS即可。值得注意的是即使你声明了工具列表模型也不是每次都会调用工具。它在自己觉得自己能回答时会直接输出文本这没问题。关键区别在于大模型可能错误地“声称”自己执行了代码而事实上它并没有。怎么解决看下面一节。4.3 工具执行器让生成的代码真正跑起来执行器负责收取模型的“意图”把它转成真实操作。执行代码这块我用的是subprocess.run方案直接调用Python解释器import subprocess, json def run_python_code(code: str) - str: try: result subprocess.run( [python, -c, code], capture_outputTrue, textTrue, timeout10, cwd/tmp/agent_workspace ) if result.returncode 0: return json.dumps({status: success, stdout: result.stdout}) else: # 如果代码非预期终止需要把错误信息进行可视化方便Agent判断修复 return json.dumps({status: error, stderr: result.stderr}) except subprocess.TimeoutExpired: return json.dumps({status: error, stderr: 执行超时(10s)})用这个执行器能避开一个隐蔽的坑通过-c参数执行代码时多行代码中的顶层print函数输出与执行环境无关的调试信息会在标准输出流中累积。因此建议不要把整个项目的代码都放进去执行只放一段最小可复现的代码片段。这里还有一个安全考量。框架执行的是AI生成的代码没人能保证它完全安全。比如它可能生成删除文件的恶意代码。放在教学演示环境还能接受但如果部署到公开服务必须使用Docker等容器隔离技术否则代码一旦失控代价非常大。4.4 跑通第一个能够闭环的Demo自动写斐波那契代码接下来我们来跑一个极简但能完整展示Agent闭环的Demo。用户输入“写一个函数计算斐波那契数列的第n项并测试前10项输出正确”整个对话流模拟如下第一轮用户输入指令。模型没有直接给答案而是解读为需要调用代码工具输出一个工具调用请求。框架拦截请求把参数含生成函数的代码交给执行器跑起来。执行器返回输出包含代码的运行结果或报错信息。第二轮模型看到输出发现有缺陷它会再次调用工具执行改进后的代码。执行器再次执行返回正确的运行结果。模型选择结束循环输出“已成功生成并验证斐波那契函数”附上一段最终代码。这个闭环约用时3到15秒取决于模型推理速度。紧接着框架需要写一个界面来承载这个交互最简单的做法是命令行接口if __name__ __main__: while True: user_input input(你: ) if user_input.lower() exit: break print(Agent:, agent_run(user_input))输入任意任务文本它就能自主完成“生成代码-运行-修复-交付”的闭环。很多没接触过Agent的人第一次看到模型“自己改自己的代码”会非常震撼——这正是Agent的魅力所在。5. 任务规划机制升级5.1 为什么平铺式执行不够用当你用四步闭环跑通Demo后可能觉得已经“入门了”。但一旦任务复杂度上升你会发现单纯的一轮轮“生成-执行-修复”很快撑不住。举个例子用户说“帮我写一个爬虫抓取某网站的新闻标题存到CSV文件里并统计标题词频”。这个任务拆解出来至少有这几步配置环境、写爬虫代码、测试网络请求、处理编码问题、写存储逻辑、写词频统计逻辑。如果让模型在单轮主循环里自由发挥它极大概率会陷入“改一个地方、弄坏另一个地方”的泥潭。为什么因为模型上下文窗口是有限的它思考的宽度有限。当代码量超过一定规模模型每次只盯着上一步的报错去修就像程序里打地鼠甲处补丁乙处冒泡、乙处补丁丙处崩塌。5.2 引入任务分解的Planning模块解决方案是给Agent增加一个Planning规划模块。不再是模型直接从“任务”跳到“代码执行”而是先经历一次“任务拆解”阶段plan_prompt 请将以下任务拆分为最多5个可独立执行的步骤。每个步骤必须满足 - 输出一个明确的可交付物 - 步骤之间按依赖关系排序 - 后续步骤只能依赖前序步骤的输出结果 任务{task} 请只输出JSON格式的步骤列表不要输出其他内容。 这个阶段的核心技巧是用独立的对话请求单独完成规划而不是放在同一个历史上下文里让模型自由漫游。因为如果将规划指令和代码执行指令混在一起模型很容易受到之前对话的干扰丢失对整体的把握。独立请求让模型进入一个“规划模式”输出的JSON结构更容易被后面一步步执行。拿到步骤列表后主循环就改写为一个“计划驱动”的循环plan llm_client.chat(plan_prompt, parse_jsonTrue) for step in plan.steps: result agent_run(请执行步骤: step.description) step_artifacts.append(result)每步执行结果会累计到上下文中让模型在后续步骤能引用前面的输出。你甚至可以给步骤间传递“共享变量”比如“工作目录路径”把整个流程串成一个流水线。5.3 可选的记忆组件长效状态管理现在你的Agent还是一个“鱼一样的记忆”——每次任务结束后对话一清空它就什么都不记得了。如果你想让它长期记住用户的偏好比如“我平时喜欢用pandas库处理数据”可以引入记忆组件。思路是用一个向量数据库来存储对话关键信息的向量。每轮对话结束后调一个嵌入模型把这次对话的核心内容向量化存储。当新任务到来时先做一次相似度检索把最相关的历史记忆片段注入到提示词上下文里。这个方案适合进阶场景不急着第一步做。这里先提一句是为了避免各位把“静态的Agent”当成全部追求后面有辨识需求时知道自己往哪个方向改。6. 实战记录让AI开发一个完整的“文本文件管理工具”6.1 设定任务目标按照保姆级的定位我不停在理论层讲空话直接拉一个接近现实的完整案例出来。任务设定得非常具体你可以照着抄“创建一个Python命令行工具名为txtmanager.py它能实现以下功能扫描指定目录下的所有.txt文件按文件大小从大到小排序输出每个文件名、大小和最后修改时间支持统计所有文件的平均大小支持递归子目录。”这个任务包含多个子功能足够检验Agent的规划、编码、修复、交付能力同时任务太重也不会让教学演示变得不可控。6.2 Agent的完整行为过程记录我带着你模拟一下Agent的执行过程如下步骤一生成任务清单模型输出的计划大致为扫描并筛选.txt文件读取文件的元信息实现排序逻辑实现统计逻辑编写命令行入口和帮助信息步骤二生成第一版代码代码写出后直接在沙箱里跑。但第一版通常有细节问题它很可能默认os.walk返回的路径拼法有问题或者处理文件修改时间格式化时漏掉了时区问题或者是文件大小排序忘了处理单位统一。步骤三Agent自动分析报错信息此时执行器返回stderr内容Agent看到报错后的反应模式非常关键有经验的开发者期望它去“拆解报错点”而不是把整个代码推翻重写。在提示词里我会特别强调一句“你有且只能修改代码中导致问题的那部分不要改变整体结构”这样能有效防止模型过度重构。步骤四运行成功后的最终交付在下一次迭代时代码运行返回正确输出。模型就会在结尾生成一段完整的最终代码并附带声明“测试通过”。这个结果拿到了Agent这次任务才算真正完成。6.3 对Agent产出结果的验证思路有几个朋友问我“Agent说测试通过了我该怎么验证它是真的还是演的” 这里就要引入关键策略——“强制副作用验证”。所谓强制副作用就是要求Agent在代码里输出你能肉眼验证、或者能程序断言的外部痕迹。比如要求它“运行代码后把输出的摘要保存到result.txt文件中”然后框架在执行完代码后额外读取result.txt并将文件内容作为工具响应的一部分返回给模型。这样模型要“谎报”就非常困难了。针对txtmanager这个案例我会在任务描述中追加一句“运行完成后必须在控制台输出所有文件的统计数据并保证这些数据与真实文件系统一致。” 通过这种约束Agent几乎不可能“空口断言”因为它的结论要和真实执行结果的反馈闭环保持一致。实测提示让Agent在代码里添加一个assert断言来校验自己写的代码逻辑是提升交付质量最便宜的方法。比如它写完排序逻辑让它在执行时强制断言结果确实是升序Assert失败会触发stderr从而迫使模型进入修复流程。7. 常见问题与排查技巧7.1 模型频繁返回无法解析的“伪工具调用”这是最最常见的坑。现象模型输出的内容看起来像JSON工具调用但解析失败比如多了引号、丢了逗号、为了凑格式生成了局部截断。排查思路分三步走查看原始响应内容。大多数情况下模型是把工具调用包装在普通文本叙述里。例如它写了“我准备调用工具参数如下{...}”这样顶层聊天消息的角色不是工具消息框架就按文本处理了。看是否因为强制JSON输出导致模型直接把格式写坏了解决方式是提高模型温度设置或者改用更健壮的解析方式。利用SDK自带的结构化工具调用能力而不是依赖自己从文本里猜。凡是支持原生工具调用的模型响应中都带独立字段不用自己用正则去匹配。7.2 无限循环模型不断尝试去修一个无法修复的问题能触发死循环的常见场景某个第三方库在环境里没有装模型每次执行都收到ModuleNotFoundError它改来改去其实根本没能力处理“要装依赖”这个动作。两个思路第一种是在提示词中写明“如果错误是缺少依赖调用依赖安装工具而不是修改代码”第二种是在主循环里加入“失败转移”逻辑连续三次相同类型的错误就直接终止并报告“当前环境无法满足任务”。我在实际项目中遇到最搞笑的是模型为了修复一个“编码问题”尝试改代码13次把UTF-8、GBK、Latin-1轮了个遍无一成功。因为它压根不知道系统临时目录里放的文件编码是GBK。最后是环境工具介入提醒才终止循环。7.3 上下文爆炸任务还没结束Token先爆了Agent每次循环都会叠加工具结果到上下文。别看一个任务只跑10轮每轮塞入4000 token10轮就是4万token。计费看着不贵但调用频率一上来这笔账就算不清了。解法有几个对话历史裁剪只保留最近N轮的信息摘要压缩用一个小型模型把过去对话压成一两句摘要精简工具返回比如执行代码时只截取输出结果的前1000字符返回给模型避免完整日志全塞进上下文。这个“截断输出返回”的技巧我强烈推荐对降低成本的作用非常直接。7.4 沙箱安全问题最后也是最不能绕过的安全。前面说过教学环境里subprocess可用但实际生产环境下AI生成的代码可能具备危险能力。执行任意代码的沙箱必须做三层保护网络隔离禁止容器访问外网避免敏感数据外传。资源配额限制执行时间、内存上限CPU份额。文件系统隔离只向沙箱暴露指定目录其他目录挂载只读或不可见。在初期学习时请无论如何也在独立的虚拟环境里跑Agent尤其是不要用一个具备真实数据库连接权限的机器做Agent开发测试。我认识的一位开发者用Agent自动生成了一行SQL删除语句差点把一张核心表的记录全清了。那是他第一次对“AI写的代码”有了敬畏心。8. 经验心得与进阶方向8.1 我的技巧用“烟雾弹测试”快速评估Agent能力这个技巧很少看到别人讲。你拿到一个看不见内部实现的“黑盒模型”时先别急着去调完整业务流程而是丢给它一个“烟雾弹任务”给它一段写好的代码故意留一个漏洞看它是否能自主发现并修复。比如让它对一个已有函数补一个边界条件判断然后运行测试用例。如果它能自动执行测试并基于测试失败信息定位并修补代码说明这个模型具备了“代码调试闭环”的基础能力。之后你再设计复杂任务就有信心了。这个办法比我见过的很多“跑通用Demo”更有效因为它能真实验证Agent是否形成了“写代码-执行-反馈-修改”的循环意识而不仅仅是“照着提示词生成一段文本”。8.2 从Demo走到产品级Agent的跳跃点Demo跑通了跟产品落地之间还隔着几条鸿沟。很多带你入门级的教程不会讲这些我补上可观测性团队里你不能只依赖一个黑盒你需要为Agent增加日志面板记录每一步的“模型请求-工具调用-执行结果- token消耗”否则你在生产环境排障时会非常难受。可评测性给Agent准备一套回归测试集。每当更换模型版本、调整提示词或工具定义时跑一遍测试集观察成功率变化。没有评估体系的Agent开发基本等于盲人摸象。可扩展机制把工具数量从5个扩到50个时如何保证模型不会在工具选择上糊涂常见的做法是给工具分组引入路由层先让模型选分组再让模型选具体工具。人与AI协作产品级Agent不需要“全自动”而是“自动人工确认”的混合模式。比如写文件之前需要人工确认或者执行删除操作前弹出审批。这样降低容错成本。这些知识点说实话一篇教程很难全讲透。我现在在做的事情正是在探索整套从“原理”到“工程化”的路径。8.3 最后的一点个人建议如果你现在刚刚开始我建议不要企图一次就把Agent做得面面俱到。第一步先把“生成代码-执行-反馈-修复”这四步闭环跑通即可。测试时找个最简单的任务——比如“编写一个求和的函数并运行”确保框架稳定。第二步再加入规划模块第三步再扩展到更多工具。每前进一步就把当前阶段测试得足够稳定。我做了这么多Agent项目之后最大的体会是要让AI靠谱最靠谱的还是我们自己把工程边界划清楚。AI输出的内容要经过环境的检验、结果的断言、日志的追踪才能真正为我所用。这个思路比单纯去追求某个模型的性能上限要重要得多。如果你按照这套流程把代码跑通了你获得的确定性的增量会让你真切感受到“让AI帮我写代码”到底是什么样的滋味。然后你再回头看那些复杂应用就会发现它们不过是一个会思考的引擎、几张会干活的手和你画的边界。