十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从聊天到动手:AI Agent原理与最小实现指南

从聊天到动手:AI Agent原理与最小实现指南 最近总有朋友拿同一个问题问我ChatGPT已经能写代码、能写文案了还要AI Agent做什么我的回答是它就算写出花来也得你亲手去执行。只会聊天的AI和能动手的AI差的不是一点点而是从“出主意”到“干活”的一整条路径。这篇文章我想从原理讲到实操拆一拆聊天AI和AI Agent到底差在哪儿再带你自己跑通一个最小可用的AI Agent。主要面向正打算做AI应用、开发智能体或者单纯想用AI帮自己省点事的朋友看完你至少能回答自己两个问题该选普通聊天模型还是该上Agent方案以及真的做一个Agent时最该注意什么1. 先给“会聊天”和“能动手”画个清晰的边界1.1 聊天AI一张只会出主意的嘴聊天AI的本质是一个文本生成器。你把问题丢给它它负责生成一段看起来合理的文字。你可以让它帮你写一段Python脚本它写得头头是道但脚本能不能跑通、跑完会不会把数据删了它不知道也不负责。你把它生成的代码复制到本地终端回车屏幕报错这时候还是得你来处理。这就是聊天AI最大的局限它只有语言能力没有行为能力。它没法访问你的文件系统没法调用外部API没法把“计划”变成“结果”。很多人觉得聊天AI已经很强了会写诗、会写合同、会写代码但细想一下这些全都是“输出文本”。真正的做事比如把文件从A目录移到B目录、给客户发一封邮件、部署一台服务器它一样都干不了。我经常打一个比方聊天AI是一个超级厉害的顾问你问什么它都能给出建议但真正跑腿的永远是你自己。它会告诉你“你应该把备份放到另一个存储桶里”但不会替你去点那个“创建桶”的按钮。这种模式的体验一开始新鲜用久了你会觉得不对劲——我是不是只是把它当成一个高级搜索框在用1.2 动手AI嘴、手、眼、记性全配齐能动手的AI现在通常被叫做AI Agent或者智能体。它的核心变化是除了语言模型那颗“大脑”它还长出了手、眼睛和记性。手是指它可以调用外部工具比如搜索引擎、代码解释器、数据库查询、文件读写、邮件发送、浏览器自动化等等。眼睛是指它能看到工具执行后返回的结果比如文件是否移动成功、API返回了什么数据。记性是指它能把多轮执行过程中的中间信息记录下来而不是每走一步都失忆。最直观的类比是聊天AI是顾问Agent是员工。你的员工不只是给你提建议他还会去查资料、做表格、发邮件然后回来向你汇报。当然现在的Agent还没法像真人一样负责所有事它的自主性是有边界的需要你在系统里给它搭好舞台、规定动作范围。但对很多重复性、流程性工作来说它已经能实打实地帮你省下时间。所以“只会聊天”和“能动手”的差距不是功能数量上的差距而是AI从“信息输出”跨越到了“任务执行”。它不再只是告诉你“怎么做”而是真的去做了再告诉你做成了没有。2. 让AI“动手”的三块地基工具、记忆、反馈2.1 工具调用把“会说话”变成“会做事”的分水岭实现Agent底层能力的关键技术叫做function calling也叫工具调用。简单说你在调用大模型API时先声明一批“这个AI可以使用的工具”每个工具都包含名字、描述、参数结构。大模型在生成回复时不再局限于输出纯文本它会根据用户的需求输出一个结构化的工具调用指令比如“我要调用move_file工具参数是源路径和目标路径”。应用层收到这个指令后负责真正执行对应的函数然后把执行结果返回给模型。模型再基于这个结果决定下一步是继续调用工具还是输出最终答案。这个“模型出决策、系统做执行、结果回喂给模型”的循环就是Agent能动手的本质。别被“Agent框架”这些名词吓到底层原理就是这么简单。我见过很多项目一开始就上LangChain、CrewAI结果一出问题根本不知道是哪一环错了。等你理解了工具调用这个循环很多框架对你来说只是锦上添花而不是救命稻草。需要注意的是工具调用本身是一个需要模型专门训练过的能力不是随便一个聊天模型都能稳定做对。模型需要学会两件事什么时候该调工具什么时候不该调以及怎么把用户的需求转换成正确的参数。如果你发现某个模型工具调用经常出错不要怀疑是自己写错了它可能只是没被调校好。2.2 记忆与上下文管理临时工和正式工的差距聊天AI也有上下文窗口能在一次对话里记住你前面说了什么。但Agent需要在更长的任务链路里工作它的记忆体系要复杂得多。我通常会把Agent的记忆分成三类。第一类是短期记忆也就是当前任务里的中间变量和结果比如刚才搜索到了什么、文件夹里有哪些文件这些直接放在模型上下文里就能用。第二类是长期记忆用来跨会话保存用户偏好、项目背景、历史决策一般需要落到外部存储里比如数据库、向量数据库。第三类是工作记忆指的是每个子任务的阶段性结论比如“已经处理了图片文件待办清单还剩3个文件”这类信息需要不断更新避免Agent重复劳动或者漏掉步骤。没有记忆的Agent就像一个刚入职就失忆的员工你每次布置任务它都要重新问一遍背景。以前我写过一版Agent每处理完一个文件就忘记之前处理过哪些结果同一个文件被反复操作直到我给它加了一个“已处理清单”才解决。用向量数据库做长期记忆是个很好的方向但也要注意不是所有信息都值得存存太多反而让检索结果变吵模型抓不住重点。2.3 反馈循环AI是怎么知道自己做对了的真正让Agent“动起来还能停下来”的是反馈循环。整个循环是这样的模型先生成一个决策系统去执行工具工具返回一个结构化结果模型分析这个结果再生成下一个决策。如此循环直到任务完成或者达到最大步数限制。这个模式在学术界叫ReAct也就是Reason和Act的合体先推理再行动看结果再推理。Agent做对事情靠的不是模型多么聪明而是这个“行动—观察—再行动”的回路。如果第一次尝试失败了模型能根据错误信息修正自己的动作。比如移动文件时发现目标目录不存在模型可以先调用create_directory再重新执行移动这种容错能力是固定脚本做不到的。我自己的经验是反馈给模型的内容一定要结构化。我们之前有个工具返回大段带颜色、带排版的说明文字模型反而抓不住重点经常把状态判断错。后来统一改成JSON格式明确给status、data、error字段模型的判断准确率明显提升。说到底Agent也是个“读文档”的同事你给的报告越清爽它干活越靠谱。3. 亲手做一个“能动手”的AI从零搭一个文件整理Agent3.1 需求拆解与工具设计理论讲再多不如直接跑一个例子。这次我带着大家做一个文件整理Agent目标很具体把一个乱糟糟的下载目录按文件类型自动分类图片放到images文档放到docs压缩包放到archives其他不认识的放到other最后生成一份整理报告。你可能会说这种需求写个Python脚本不就行了为什么非用Agent区别在于脚本的规则是写死的碰见一个没见过的扩展名就只能往other里扔。而Agent能在运行中自己做判断碰到.db这种冷门扩展名它会去查一下这到底属于数据库文件还是文档再决定归类。这种“边干边想”的能力就是Agent的价值。为了让它“能动手”我需要给它设计四个工具list_files(path)列出目录下所有文件返回文件名、大小、修改时间和状态。get_extension_info(ext)查本地映射表返回这个扩展名属于哪一类。move_file(src, dst)执行文件移动返回是否成功以及错误信息。write_report(content)把整理报告写入Markdown文件。工具不要设计得太大一个工具只做一件事。我之前吃过亏把“列文件分类移动”全塞进一个工具里模型没法细分操作稍微异常一点就不知道怎么处理。3.2 核心代码实现一个最简单的Agent循环我给你写一个最小可跑的Agent循环不依赖任何框架只用标准库加一个支持function calling的模型SDK。为了说明核心原理代码里把工具函数体简化了你真正使用的时候把路径参数补上就行。import json from openai import OpenAI client OpenAI() def list_files(path): # 实际代码用 os.listdir 逐个取文件信息 return { status: ok, files: [ {name: 1.jpg, size: 1024, mtime: 2025-01-01}, {name: 2.pdf, size: 2048, mtime: 2025-01-02} ] } def move_file(src, dst): # 实际代码用 shutil.move并确保目标目录存在 if src.endswith(.jpg): return {status: ok, src: src, dst: dst} return {status: failed, error: file not found} def get_extension_info(ext): mapping {jpg: image, png: image, pdf: document, zip: archive} return {status: ok, extension: ext, category: mapping.get(ext, unknown)} def write_report(content): # 实际代码把 content 写入文件 return {status: ok, path: report.md} tools [ { type: function, function: { name: list_files, description: 列出目录下所有文件, parameters: { type: object, properties: { path: {type: string, description: 目录路径} }, required: [path] } } }, # 其余工具的 schema 结构类似这里省略 ] def run_agent(user_task, max_steps10): messages [{role: user, content: user_task}] for step in range(max_steps): resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto, temperature0.1 ) msg resp.choices[0].message messages.append(msg) if msg.tool_calls: for tc in msg.tool_calls: args json.loads(tc.function.arguments) if tc.function.name list_files: result list_files(**args) elif tc.function.name move_file: result move_file(**args) elif tc.function.name get_extension_info: result get_extension_info(**args) else: result write_report(**args) messages.append({ role: tool, tool_call_id: tc.id, content: json.dumps(result, ensure_asciiFalse) }) else: return msg.content return 达到最大步数任务结束 if __name__ __main__: print(run_agent(请把 /downloads 下的文件按扩展名分类整理最后写报告))这个循环就是核心。每次API返回的消息里如果带了tool_calls系统就执行对应的工具把结果以role: tool的形式追加回消息列表然后继续调用模型。什么时候模型觉得事情办完了它会只输出普通内容我们的循环就结束了。这里面有几个细节值得注意tool_choiceauto表示让模型自己决定要不要调工具temperature0.1让决策更稳定不会回回给出不同方案max_steps是保底防止它一直循环下去。我在自己项目里还会把每一步的工具调用和返回值打印出来方便看它到底在干什么。3.3 运行效果与参数调整实际跑一次你会发现模型的动作过程大致是这样的先调用list_files拿到文件列表然后对每个文件调用get_extension_info判断类型再逐个调用move_file移动文件全部搞定后调用write_report最后输出一段“整理完成共处理了12个文件”的总结。整个过程不需要你手动干预它能自己拆解任务。不过别期待第一次跑就完美。我调试的时候遇到过几个典型情况模型跳过某些文件说“这些文件不常用了”就自作主张不处理也有模型搞错参数把/downloads写成了/downloads/导致路径拼接出问题。这些都需要你通过调整system prompt和参数来约束。参数调整上我给你几个实实在在的建议。第一max_steps不要设得太大普通文件整理任务10步以内足够设太大只会让它在错误路径上多烧钱。第二任务越具体越好在system prompt里写明“必须处理所有文件不得跳过任何文件”。第三同一个目录下的文件数量太多时别让Agent一个文件一个文件地处理这会非常慢正确的做法是让它先用一个list_files拿到全量清单再分类批量移动成本能差出十倍。我把一个1000个文件的目录交给Agent时第一次跑了一百多步还没跑完改成批量处理后几个工具调用就结束了。4. 实操中最容易踩的坑4.1 循环失控与死循环Agent最常见的事故就是陷入某个工具的调用里出不来。典型表现是同一批文件被反复列出、反复判断模型像失忆一样每次都说“好的我现在处理”但外界状态根本没变化。为什么会这样一是反馈信息不足模型不知道“这个文件已经处理过了”二是工具副作用太大模型每次调用都生成新东西但判断条件一直不满足。我在项目里的解决办法有三个。第一设置max_steps硬上限只要超过步数就强制终止不要指望模型自己幡然醒悟。第二在工具返回值里加入状态标记比如already_moved: true模型看到这个字段就不会再折腾。第三给工具做幂等性设计比如move_file在执行前先判断目标是否已存在存在就直接返回成功同一个操作重复做也不会引发新问题。幂等这个词看着高级其实就是让AI“反复点同一个按钮也不出事”。4.2 Token预算失控Agent每循环一步都要把所有历史消息重新发送给模型。如果你不做上下文管理一个多步任务的token消耗会是普通对话的几十倍账单会给你一个惊喜。更现实的问题是上下文一旦变长模型响应速度会变慢而且容易在旧信息里迷失方向。我处理这类问题常用三个手段。第一及时做摘要把已经完成的中间步骤压缩成几句话而不是让完整工具返回一直留在上下文里。第二控制工具返回内容长度只返回模型做决策真正需要的字段那些用不着的调试信息走日志不要进上下文。第三更彻底的办法是把任务拆成多个子Agent每个Agent只负责一个小环节任务结束就清空上下文下一个Agent重新开始。不要试图让一个Agent记住所有事情那是拿钱和时间在硬扛。4.3 工具调用格式错误与参数幻觉模型调用工具时偶尔会给出不符合要求的参数比如把不存在的文件名当作参数传进来或者参数类型和schema不匹配。这其实是幻觉在工具调用场景下的变体非常常见尤其是用较小模型的时候。你的程序要对这个有预期而不能假设模型永远按规矩来。我现在的做法是在执行工具前加一层参数校验不合法参数直接返回错误结果把这个错误结果喂回给模型让它自己修正。这恰恰是Agent相对固定脚本的优势它足够灵活能在错误发生后自我纠偏。不过校验逻辑必须做得严格尤其在涉及路径的操作上我会把所有目标路径先规范化处理再校验是否在允许的工作目录里。这既是防幻觉也是防安全问题。5. 从“能动手”到“放心用”工程化落地的关键5.1 权限边界与安全控制一个能调用工具、访问文件系统、甚至执行代码的AI权限问题就是头等大事。我的原则是最小权限给Agent一个受限的工作目录它碰不到系统目录和其他敏感区域所有涉及删除、覆盖的操作一律先进“确认模式”由你来点最后一下调用外部API时用单独的key并设好调用额度免得它把费用跑穿。我在文件整理这个例子里就刻意做了个缓冲移动文件时不会直接覆盖目标路径已有的同名文件而是先改名或者移动到回收站目录。“能撤回”这件事对Agent特别重要因为它可能基于一次误判把所有文件都归错了类要是直接删了就凉了。如果你做的Agent会执行代码那么更稳妥的方案是把它放进容器或者沙箱里运行这是成本最低的隔离方式。5.2 可观测性与日志审计Agent的决策链路很长可能上一秒还在列文件下一秒就调用了一个外部搜索API。如果系统里没有完整的日志排查问题只能靠猜那会非常痛苦。我现在所有Agent项目都会结构化记录每一步工具名、参数、返回值、模型输出、耗时、token花费、错误信息全部落成JSON日志。实际调试时你会发现回放日志比看实时输出有用得多。因为Agent的很多决策看似随机但日志里能还原它的完整思考过程。比如它为什么把一个文件放到了other里你翻到上一步会发现原来get_extension_info返回了一个“unknown”这个结果在交互界面上根本看不到。有了日志这种问题一眼就能定位。5.3 成本与性能优化Agent应用要上生产成本问题是绕不开的。不少人在demo阶段跑得很欢乐一到真实场景就发现模型调用量太大、响应太慢、账单太高。优化思路主要有几个方向一是在入口加一个“路由器”模型用便宜的小模型判断用户请求是不是简单查询只有复杂任务才交给大模型Agent处理二是给外部工具调用设置超时时间防止某个慢API把整个链路拖死三是设计工具时尽量批量操作减少循环次数这比优化模型参数便宜得多。我之前处理的那批1000个文件刚开始逐个处理模型调用了上千次又慢又贵。后来我优化了工具层让模型先一次性拿到全量清单然后分组批量移动整个任务只用了几次模型调用就完成。这个例子说明Agent项目的性能瓶颈经常不在模型而在你的工具粒度设计上。工具设计得好Agent事半功倍工具设计得烂再强的大模型也救不回来。这套东西我前前后后折腾了快一个月最大的感受是AI Agent不是魔法它只是把“想清楚怎么做”这件事从人身上转移到了系统里。别再纠结你的AI会不会聊天了先让它动手做一件小事哪怕只是挪几个文件。等它真帮你省下了一堆重复操作的时间你自然就明白“会聊天”和“能动手”到底差在哪儿了。如果你也正在做类似的东西建议先从最小的工具循环开始踩过几个坑之后你对Agent的理解会比看一百篇理论文章都深刻。
返回列表