上个月,一个写前端多年的朋友突然问我:“隔壁组搞 Agent,开口闭口就是模型、微调、算力,我一个写页面的,是不是得先回炉去学几个月机器学习才能跟上?”我当时反问他:“Chrome 的 V8 引擎是 C++ 写的,你觉得用 JS 的工程师需要先去造引擎吗?”他愣了两秒,半天没回话。
这是个很典型的认知错位。大部分前端看到“Agent”“智能体”这些词,第一反应是“这是算法团队的事”。但 Agent 开发这个事,本质上和模型训练隔着一整条产业链。它做的是拿现成的模型能力,去组装、编排、包装成一个能实际跑业务逻辑的系统——这件事需要的不是推公式,而是工程能力。而工程能力,恰好是前端每天都在练的东西。
这篇文章我想把“Agent 开发到底在做什么”这件事彻底讲清楚:它和模型训练的分界线在哪里、前端的哪些技能可以直接迁移、真正需要补的新课有哪些、以及一个最小可跑的 Agent 到底长什么样。适合两类人:一类是被“AI 门槛”吓住、想碰又不敢碰的前端同学;另一类是已经开始调 LLM API、但总觉得自己“不算真的在做 AI”的开发者。看完你会知道,你不缺能力,缺的只是一份准确的认知地图。
1. 大家管它叫“智能体”,但 Agent 首先是个工程问题
1.1 模型训练与 Agent 开发的分界点:从一张 GPU 订单看差异
先做一件事:打开两家公司的招聘页面,搜“算法工程师”和“Agent 工程师”,对比一下岗位要求。算法岗十有八九写着“熟悉 Transformer、有分布式训练经验、了解模型压缩”,而 Agent 岗写的是“熟悉 LLM API、有工具调用与编排经验、具备系统工程能力”。这是两个完全不同的工种,却在舆论里被搅成了一锅粥。
模型训练的实质,是改变模型内部的参数分布。你要准备海量数据,要处理清洗、去重、配比,要设计训练和评估流程,要在 GPU 集群上烧钱跑几周,最后产出一个“权重文件”。这个活的核心工具是数学和分布式系统,跟前端日常用的 DOM、框架、网络请求几乎没有任何交集。
Agent 开发完全不同。它面对的是一个已经训练好的黑盒模型,工作内容是把模型的“对话能力”接到真实的业务流程里。举个例子:我要做一个能帮忙查天气、订闹钟、写邮件的桌面助手。模型本身不知道天气接口长什么样,也不知道邮件服务的鉴权方式,更不知道用户说“帮我定个明天早上八点的闹钟”这个指令应该落到哪个系统。把这些东西接起来、让模型能正确调用、处理各种异常分支、保证整个流程不崩——这些全是工程问题。
我用一张表把两者的差异列出来,方便直观对比:
| 维度 | 模型训练 | Agent 开发 |
|---|---|---|
| 产出物 | 一组参数权重 / 模型文件 | 一个可运行的业务系统 |
| 核心基础设施 | GPU 集群、数据标注平台、训练框架 | 应用服务器、数据库、第三方 API、消息队列 |
| 核心技术 | 数学、统计学习、分布式训练 | 工程架构、API 编排、状态管理、逻辑控制 |
| 迭代方式 | 以天/周为周期跑实验,重验证轻上线 | 以小时/天为周期,写完逻辑立刻部署 |
| 与前端的关系 | 几乎为零,需要另起炉灶 | 大量重叠,很多思路可直接复用 |
看到差别了吗?模型训练解决的是“模型会不会说话”的问题,Agent 开发解决的是“模型能不能干活”的问题。前者像发动机的燃料配方研究,后者像基于一个成熟的发动机去设计整车。前端要切入 AI 领域,入口在整车设计这一侧,而不是燃料配方那一侧。
1.2 Agent 开发每天都在写什么代码
抛开那些玄乎的术语,一个 Agent 工程师的日常工作台面相当朴素,拆开就是四件事:
第一,写模型调用的封装层。把某个 LLM API 包一层,处理超时、重试、流式输出、上下文拼接,做成一个小工具函数。第二,写工具定义。告诉模型“你有哪些能力可以用”,比如查询订单、发送消息、读取文件,每个能力用 JSON Schema 描述清楚输入输出。第三,写循环逻辑。模型返回结果之后判断是否要调用工具,调用完工具之后把结果回传模型,再等它给出最终答案——这个循环是 Agent 的核心骨架。第四,写状态和边界。用户聊到一半换了个问题,怎么处理?工具报错了,怎么让模型重试?上下文太长,怎么裁剪?
看到没?这里面没有一行代码在调权重,没有一步操作在碰 GPU。整个工作链条里的每一步,都是纯正的软件工程。你把这些代码拿去给任何一个有经验的工程师看,他可能猜不到这是“AI 项目”,但一定能看懂每一行在干什么。Agent 开发的难度不在于“AI”,而在于“把一个透明不可控的模型,变成一个稳定可控的业务组件”——这种把不确定性收敛成确定性的能力,恰恰是前端在浏览器兼容性、多种设备适配里练出来的老手艺。
2. 拆开 Agent 的三个核心组件:前端技能全都能对号入座
2.1 一份“技能迁移对照表”:DOM、fetch、状态管理去哪了
我见过很多前端,一接触 Agent 就往“我不会机器学习”的死胡同里钻。其实换个角度,把你已有的技能摆出来,跟 Agent 开发的技术点做一次映射,你会发现迁移率惊人。下面这张表是我自己做 Agent 项目时总结出来的对照关系:
| 前端已有的技能 | Agent 开发里的对应能力 |
|---|---|
| fetch / axios / HTTP 封装 | LLM API 调用、工具服务的接口对接 |
| TypeScript 类型系统 / JSON 数据处理 | 工具 Schema 定义、消息结构校验 |
| React/Vue 的状态管理 | Agent 会话状态、多轮对话的上下文管理 |
| 事件循环 / 异步并发 | 多个工具并行调用、流式返回处理 |
| 组件化思维 / 模块设计 | 把 Prompt、工具、参数校验封装成可复用模块 |
| 错误边界 / 兜底方案 | 模型输出异常、工具调用失败的降级处理 |
| 性能优化(缓存、按需加载) | Token 用量估算、上下文裁剪策略 |
其中最微妙的一项,是 TypeScript 类型系统。前端写接口对接时,习惯定义interface User { name: string; age: number },然后在编译期就拦下类型错误。Agent 的工具 Schema 本质上是同一件事:你要把工具的输入输出描述清楚,让模型“照着 JSON 格式”去填参数。区别只在于,TS 的类型检查是编译器在跑,而工具 Schema 的“检查”是靠模型的语义理解在跑。前端的接口意识在这里可以直接平移,你要做的只是换一种语言去向模型描述“这个接口长什么样”。
举个例子,给 Agent 定义一个查天气的工具,工具 Schema 长这样:
{ "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的当前天气信息", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如:杭州" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位,默认 celsius" } }, "required": ["city"] } } }把它当成一个“面向 LLM 的 TypeScript 接口定义”就对了。前端同学写惯的必填字段、枚举值、字段描述,在这里一个都不少。区别是你写的这套“类型系统”的消费方不是编译器,而是一个能读懂自然语言的模型——写得越清楚,它填参数出错就越少。
2.2 真正需要补的新课只有三块:LLM API、Prompt 工程、工具循环
技能迁移并不意味着零学习成本。从我个人的实操体会看,前端转 Agent 开发,真正要补的课只有三块,而且每一块都不需要啃大部头。
第一块是 LLM API 的基本玩法。你要知道一次模型调用本质上是一个“文本进,文本出”的 HTTP 请求:往messages数组里塞消息,消息有system(系统设定)、user(用户输入)、assistant(模型回复)、tool(工具执行结果)四种角色;然后模型返回一个 completion,里面可能是纯文本,也可能带tool_calls——也就是模型想调用某个工具的请求。会了 fetch,会了 JSON 解析,学这块最多半天。
第二块是 Prompt 工程的基础认知。别被这个词吓到,它其实就是“学会如何把需求表达得让模型听得懂”。你要知道system消息的权重最高,要在里面把角色、任务目标、输出格式、边界条件一次讲清楚;要知道给模型提供 few-shot 示例能显著拉高输出的稳定性;要知道模型对长文本末尾的内容注意力会衰减,关键指令最好放在开头和结尾。这些全是有规律可循的经验,不是玄学。
第三块是 Agent 核心循环,也就是所谓“工具调用闭环”。这一块没有现成前端经验可以迁移,但逻辑非常直白:用户提问 → 模型给出回答,或者提出“我想调用某个工具,参数是这样” → 你执行工具,把真实结果回塞给模型 → 模型基于结果继续回答,或继续调用其他工具 → 直到模型不再请求工具,输出最终答案。整个流程画成伪代码就一目了然,这也是下一章我要演示的东西。
这三块学的过程中,我强烈建议用“最小可用”的方式去学,不要一上来就上重型框架。先用几十行代码把最原始的循环跑通,再去看框架源码,你会觉得那些原来遥不可及的“智能体”,其实就是这几十行代码的封装和增强。
3. 手写一个 60 行代码的最小 Agent,把“循环决策”跑明白
3.1 骨架设计:一个带天气工具的 Agent
抽象的原理讲再多,不如跑一段真实的代码。下面这个最小 Agent 是我当时用来理解工具调用的原型,大概 60 行,没有任何框架依赖,只依赖一个假想的chat()函数——你可以把它理解为某个 LLM API 的简化封装,它会返回模型回复和工具调用请求。
// 一个最小 Agent 骨架:模型 + 工具循环 import { chat, type ChatMessage } from './llm-client'; // 1. 工具注册表:名字 -> 实际处理函数 const tools = { get_weather: async (args: { city: string; unit?: string }) => { // 这里接真实天气 API,搓一个假的方便演示 return JSON.stringify({ city: args.city, temp: 26, condition: '晴' }); } }; const toolSchemas = [ { type: 'function', function: { name: 'get_weather', description: '获取指定城市的当前天气信息', parameters: { type: 'object', properties: { city: { type: 'string', description: '城市名,例如:杭州' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'] } }, required: ['city'] } } } ]; async function runAgent(userInput: string) { // 2. 初始化消息队列:system 指令 + 用户输入 const messages: ChatMessage[] = [ { role: 'system', content: '你是一个助手。如果需要天气信息,请调用 get_weather 工具,不要编造数据。' }, { role: 'user', content: userInput } ]; // 3. 核心循环:最多允许迭代 10 轮,防止死循环 for (let i = 0; i < 10; i++) { const response = await chat({ messages, tools: toolSchemas }); // 4. 模型不请求工具,说明可以给出最终答案了 if (!response.toolCalls || response.toolCalls.length === 0) { return response.content; } // 5. 把模型的“思考请求”加入消息历史 messages.push({ role: 'assistant', content: response.content || '', toolCalls: response.toolCalls }); // 6. 逐个执行工具,把结果作为 tool 消息回传给模型 for (const call of response.toolCalls) { const handler = (tools as any)[call.function.name]; const args = JSON.parse(call.function.arguments); const result = await handler(args); messages.push({ role: 'tool', toolCallId: call.id, content: result }); } // 7. 下一轮迭代,模型会看到工具结果,然后决定继续调用还是收尾 } throw new Error('Agent 循环超过最大迭代次数,退出'); } // 试试效果 runAgent('杭州今天怎么样?要不要带伞?').then(console.log);这个骨架的每一行都值得盯着看一遍,因为它是所有 Agent 框架的“最小公约数”。不管外面包装成多么复杂的框架,核心就是这样一个循环:模型想要工具,你给工具结果,模型再想,再到没有工具需求为止。
3.2 跑起来才知道的三个反直觉现象
我头一次把这个循环跑通时,觉得处处反直觉。这几个现象如果你也准备入坑,提前知道会少很多困惑。
第一个现象:一次用户提问,背后可能是好几轮模型调用。你问一句“杭州今天怎么样”,模型表面只回了一段话,但内部可能已经经历了“请求天气工具 → 拿到结果 → 再次推理”三个步骤。我一开始用日志看调用次数,看到一次提问背后有两次甚至三次模型请求,第一反应是“是不是死循环了”。后来才明白,这就是 Agent 的本质——它不是个一问一答的聊天框,而是一个会“先查资料再回答”的执行者。
第二个现象:模型不一定按你想的方式用工具。我给工具定义了单独的get_weather,本以为模型会老老实实查完天气就回答。结果有一次它明明已经拿到了天气结果,仍然在最后回答里说“我无法确认具体天气数据”。原因是我在 system 消息里只说了“需要天气信息请调用工具”,没有说明“调用工具后,基于工具结果回答”。模型把工具当成了“可选项”而不认为必须信它。所以把工具的执行结果在 system 里“设为必须依赖的权威数据源”,是我踩过这个坑后的第一个教训。
第三个现象:Token 消耗比你想象中快得多。多轮迭代意味着每一次工具调用都要把历史消息全部重新发给模型,包括 system 指令、前几轮的对话、工具返回的 JSON。做个粗算:假设每轮上下文 1000 token,来回 3 轮,就是 3000 token,而一次工具返回的 JSON 可能就占了其中一半。我跑了几次测试后被迫学会了给工具结果精简格式——原来后端工程师老说的“返回字段别太多”,在 Agent 里关乎真金白银的成本。
另外补充一个实操心得:上面的for (let i = 0; i < 10; i++)那个上限必须设,而且必须打印每次迭代的消息角色,否则一旦模型陷入“反复调用同一个错误工具”的循环,你的账单会先帮你交学费。我的习惯是在每个循环入口加一行日志:console.log([Agent] 迭代 ${i}, 角色: ${response.role}, 工具数: ${response.toolCalls?.length})。调 Agent 最常用的调试方式不是断点,是日志。
4. 前端做 Agent 的隐性优势,恰好是后端工程师最头疼的环节
4.1 异步并发与状态管理:后端跨不过的“会话”坎,你早就跨过了
如果去问一个后端工程师,做 Agent 最难的是什么,很大概率会听到“状态管理”。一个 Agent 会话里,消息数组在不停增长,工具调用可能有嵌套,用户可能在对话中途切换语境,还可能有多个会话同时在跑。数据库里存什么、缓存里放什么、什么时候清理历史,这些都是让人烧脑的问题。
而这些东西对前端来说,几乎是肌肉记忆。浏览器里的应用状态比 Agent 会话更复杂:多个组件共享数据、用户的滚动位置、表单草稿、路由参数,都需要做状态同步和持久化。前端早就习惯了“状态是易变的、需要在多个环节同步、随时可能被用户打断”这种思维方式。把这些经验平移过来,Agent 的会话管理就是一场“更简单的 Redux 状态维护”——只是状态变成了消息数组,持久化目标从 localStorage 变成了数据库。
再往深一层说,Agent 经常会遇到“多个工具要并行调用”的场景。比如用户问“北京和上海明天的天气对比”,理论上是两个互相独立的工具调用,完全可以并发处理。这种并发任务的编排思维,前端在Promise.all、并发请求控制、防抖节流里早就练得滚瓜烂熟。很多不熟悉异步的后端同学,在 Agent 里处理并行工具调用时习惯性地串行等结果,白白浪费延迟——而你会条件反射地想到并发,这就是实打实的差距。
4.2 用前端的调试习惯去看 Agent:日志、流式输出、可视化链路
我观察到一个有意思的现象:后端工程师调试 Agent,习惯看“返回结果对不对”;而前端工程师调试 Agent,第一反应是看“过程长什么样”。后者恰好是 Agent 开发里更稀缺的视角。
Agent 的调试和传统程序完全不同。传统程序有确定的输入输出,出错了你断点一打、调用栈一查,定位很快。Agent 则是个概率系统,同样的 Prompt 可能这次输出好下次输出差,你没法用传统手段去“复现 bug”。这时候就需要把“过程”可视化——每一步模型输出了什么、它想调用哪个工具、参数是怎么填的、工具返回了什么,全记录下来,做成一条可回放的链路。这个能力在后端团队里很少被系统化建设,但在前端团队里太常见了:这就是一个“Agent 版 DevTools”。
我在自己的项目里做过一个很小的调试面板,把每次运行 Agent 的消息数组渲染成一棵树:system 指令在第一层,用户输入在第二层,模型请求工具在第三层,工具结果在第四层,最终回答在第五层。哪个环节给出错误回答,一眼就能在树形结构里定位到。做完后团队里所有同事调 Prompt 的效率都上来了——因为大家第一次能“看到”模型在每一步是怎么想的。
而且还有一个纯前端的杀手锏:流式输出。Agent 内部多轮调用可能耗时好几秒,如果全部等完成再渲染,用户早就失去了耐心。前端在流式渲染上有着天然经验:把模型返回的内容用流式的方式逐步渲染成大屏上的文字,配合打字机效果、Markdown 代码高亮、加载状态的实时展示,让用户感受到“它正在思考”。这层体验设计正是 Agent 产品能否落地的关键一环,而它天然属于前端的主场。
我在多次项目合作里越来越确信:Agent 团队里最缺的往往不是会训模型的人,而是能把模型的思考过程变成可视化、可交互产品体验的人。这个人大概率来自前端。
5. 入坑前必须想明白的几个认知问题
5.1 “要不要训模型”的答案只有一句话
先说结论:除非你有创业做模型底座的打算,否则不要训练模型。绝大多数业务场景的根本需求,是“把大模型的能力接入我的业务流程”,而不是“重新造一个模型”。调用现成的 LLM API,配合精心设计的 Prompt 和工具编排,已经能满足 90% 以上产品需求。
我确实见过有团队抱着“要做一个独一无二的 Agent”的想法,决定自己微调模型,结果数据整理花了两周、训练资源烧了一笔钱、模型效果还不如直接调 API 配合好的 Prompt。这不是说微调没有价值,而是说它的价值集中在特殊领域知识注入和私有数据适应,而不是解决“让 Agent 会调用工具”这种工程问题。前端入坑,一定要把这个包袱彻底放下——你越早接受“模型是供应商”,你就越早能专注于自己真正有优势的系统集成工作。
5.2 别被“长上下文”绑架:上下文管理才是真功夫
现在市面上的大模型都在卷上下文窗口,100K、200K,听起来好像能塞一本书进去。但实际做 Agent 的时候你会发现,上下文越长不见得越好:一是成本线性上升,二是模型对超长上下文中关键信息的注意力会稀释,三是多轮工具调用会把经典信息越滚越远。
真正的挑战在于“如何用有限的上下文完成有效的决策”。我实践下来比较好用的策略有几种:一是给工具返回值做精简,只保留模型决策需要的核心字段,无关字段在回传前就过滤掉;二是对历史对话做裁剪,把早期轮次压缩成摘要,而不是全部保留原始消息;三是把低频变动的信息(比如企业知识库、产品说明)放到 system 消息里,避免每轮重复携带过大的数据。这些策略的实现逻辑,和前端的性能优化思路非常像——能用缓存解决的不要重复请求,能按需加载的不要全量渲染。上下文是你的内存,内存溢出的时候,你得会 GC。
5.3 框架怎么选:我的建议是从“裸写”开始
现在社区里 Agent 框架一大堆,有的帮你管理状态、有的内置了工具调用、有的几乎是个完整的平台。很多人一上来就选最重的那个,结果被框架的抽象层搞晕,出了问题都不知道去哪查。
我的建议是从上一章那种“裸写循环”开始,至少亲手把一个 Agent 从零喊到一起。这个过程里你能彻底理解消息数组如何流转、工具如何注册、错误如何冒泡。跑通之后,再去看框架的文档,会突然有一种“看源码像是在读自己写得比较规范的代码”的感觉。到那一步,再根据实际场景选框架,你就有能力判断它帮你解决了什么问题、又带来了哪些新的复杂度。选框架的本质是选择一个“帮你管理复杂度的人”,你得先知道自己手里的复杂度长什么样,才选对人。
我还想特别提醒一个心态问题:不要觉得“我不知道该学哪个框架”是入门障碍。框架会持续迭代,今天最流行的可能明年就被替代,但你对“模型调用循环”“上下文管理”“工具编排”这三个底层机制的理解不会过时。把时间花在这些不变的东西上,才是一个前端在 Agent 领域最值得做的投资。
最后再分享一个我自己的小习惯:每次调试 Agent 时,我都会把工具调用日志完整打印出来,包括模型填的那些参数。看着它在某个参数上犹豫、填错、被工具报错、又修正重试,你会慢慢建立起对模型“思考过程”的直觉。这种感觉有点像看一个不太熟练的新同事写代码——你不一定喜欢它的每一次决策,但你能学会如何引导它、约束它、让它稳定输出。Agent 开发真正迷人的地方就在这儿:它不是盯着一个完美模型做残差计算,而是靠工程手段把一个不完美的黑盒,塑造成你满意的产品。这条路,前端走到一半的时候就会发现,自己其实早就握着半张地图了。