前端转Agent开发实战:从零搭建最小可用Agent系统 1. 一个前端为什么会去碰 Agent 开发先说结论Agent 开发跟模型训练基本是两码事。我做了六年前端从 jQuery 时代一路写到 React Server Components去年开始因为项目需要接触 Agent 方向踩了一圈坑之后最大的感受就是——前端转 Agent 开发其实比转模型训练要顺得多但前提是你得先把脑子里那套“AI 等于炼丹”的认知扔掉。什么叫 Agent你可以把它理解成一个“会自己决定下一步干什么的程序”。传统前端写的是确定性逻辑点按钮触发函数函数返回结果渲染 UI。Agent 的逻辑是给它一个目标它自己拆步骤、选工具、看结果、再决定下一步。这中间它可能会调用搜索、读写文件、发请求、操作数据库甚至调用另一个 Agent。整个过程不是一条直线而是一个带分支和回环的决策图。那为什么前端做这个有优势因为 Agent 开发的核心工作量压根不在训练模型上。你不需要准备几万条标注数据不需要调学习率不需要盯着 loss 曲线看它收不收敛。你需要做的是设计工具接口、编排调用流程、处理流式输出、管理状态和上下文、做错误重试和降级。这些东西前端天天在干。你写过的那些异步编排、状态管理、接口容错、流式渲染几乎可以原封不动搬过来。我刚开始也以为要先去学 PyTorch后来发现完全走偏了。真正上手之后日常打交道最多的是 JSON Schema、HTTP 接口、SSE 流、状态机这些东西。模型本身是别人训好的你通过 API 调用就行。你要做的是把模型的能力“接”到实际业务里让它能干活、干对活、干完活还能把结果整理清楚。这个“接”的功夫才是 Agent 开发的主战场。这篇文章适合谁看如果你是前端想往 AI 应用方向转但不知道从哪下手或者你已经会用大模型 API 写个聊天框但不知道怎么让它真正“动起来”去完成任务再或者你是其他方向的开发者想搞清楚 Agent 开发和模型训练到底差在哪——那这篇应该能帮你省下不少瞎折腾的时间。下面我会从整体设计思路、核心细节、实操流程、常见问题几个角度把我这一路摸出来的东西摊开讲。2. Agent 开发的整体设计思路拆解2.1 为什么说 Agent 开发本质是“编排”而不是“训练”先把一个容易混淆的概念掰清楚。模型训练是你有一堆数据通过反向传播调整模型参数让模型学会某种能力。这个过程产出的是一个模型文件它本身不会主动做任何事你给它输入它给输出仅此而已。Agent 开发是你有一个已经训好的模型通过一套编排逻辑让它去完成复杂任务。这个过程产出的是一个应用系统模型只是其中一个组件。打个比方。模型训练像是造一台发动机你要研究燃烧效率、材料、工艺。Agent 开发像是造一辆车发动机你可以直接买现成的你要操心的是传动系统怎么接、方向盘怎么连、刹车怎么调、仪表盘怎么显示。前端做 Agent就是造车这个层面的事。你不需要会造发动机但你需要非常清楚发动机的输出特性——它什么时候给力、什么时候会抖、什么情况下会熄火。这个认知转变很关键。我见过不少前端朋友一上来就去啃 Transformer 原理啃了两周发现跟自己要做的功能八竿子打不着然后就放弃了。其实你完全可以从“怎么调 API”开始边做边补理论。实际工作中90% 的 Agent 开发问题都不是模型本身的问题而是编排逻辑的问题工具描述写得不清楚、上下文塞得太满、错误没处理好、状态没同步对。这些问题全是工程问题不是算法问题。2.2 前端技能栈在 Agent 开发中的直接映射我整理了一张对照表把前端日常用的技能和 Agent 开发里的对应点列出来你可以看看自己其实已经会了多少前端技能Agent 开发中的对应说明异步编程Promise/async-await多步工具调用的编排Agent 每一步都是异步的串行并行都要处理状态管理Redux/Zustand对话上下文与任务状态管理多轮对话、任务进度、中间结果都需要状态容器接口容错与重试工具调用失败降级模型可能返回格式错误工具可能超时都要兜底流式渲染SSE/WebSocket模型流式输出展示打字机效果、增量渲染前端最熟组件化思维工具函数封装每个工具就是一个独立可复用的“组件”表单校验工具入参 Schema 校验JSON Schema 校验跟表单校验逻辑几乎一样路由与导航Agent 决策分支根据条件走不同路径本质是状态机看完这张表你应该有感觉了Agent 开发对前端来说不是从零学一门新语言而是把已有的工程能力换一个场景用。你之前写的那些 loading 状态、错误提示、重试按钮、进度条在 Agent 里全都有对应物。区别只是触发这些状态的不再是用户点击而是模型的决策。2.3 一个 Agent 系统的最小构成别被“Agent”这个词吓到一个能跑起来的最小系统其实就四块模型接口层负责跟大模型通信发 prompt、收 response。这层你要处理的是 API 调用、流式解析、token 计数、超时重试。工具层定义 Agent 能用的“手脚”。每个工具就是一个函数有名字、有描述、有入参 Schema、有执行逻辑。比如“查天气”是一个工具“发邮件”是一个工具“读文件”也是一个工具。编排层核心大脑。它决定什么时候调模型、什么时候调工具、调哪个工具、拿到结果之后下一步干什么。这层通常是一个循环模型输出决策 → 解析决策 → 执行工具 → 把结果喂回模型 → 再决策直到任务完成。状态层存对话历史、任务进度、中间结果、用户偏好。这层决定了 Agent 有没有“记忆”能不能处理多轮复杂任务。这四层里模型接口层和工具层是相对固定的编排层和状态层才是你真正要花心思的地方。编排逻辑写得好不好直接决定 Agent 是“智能助手”还是“人工智障”。我见过太多 demo 能跑但一上真实场景就崩的案例问题基本都出在编排层——要么循环没设终止条件要么工具返回结果没做截断要么上下文越滚越大最后爆 token。2.4 方案选型自己编排还是用现成框架这里有个绕不开的选择是自己从零写编排逻辑还是用 LangChain、LlamaIndex 这类现成框架我的建议是第一个项目自己写第二个项目再考虑框架。原因很简单框架帮你封装了很多东西但如果你不知道它封装了什么出了问题你根本不知道怎么排查。自己写一遍你会清楚知道一次完整的 Agent 循环里发生了什么prompt 怎么拼的、工具调用怎么解析的、结果怎么回填的、循环怎么终止的。有了这个底子再用框架就是如虎添翼而不是被框架牵着走。自己写的话核心就是一个 while 循环加一个状态对象。伪代码大概长这样async function runAgent(goal, tools, maxSteps 10) { const messages [{ role: user, content: goal }]; let step 0; while (step maxSteps) { const response await callModel(messages, tools); if (response.type final_answer) { return response.content; } if (response.type tool_call) { const result await executeTool(response.toolName, response.args); messages.push({ role: assistant, content: response.raw }); messages.push({ role: tool, content: JSON.stringify(result) }); } step; } return 达到最大步数限制任务未完成; }就这么点东西。当然实际生产环境要复杂得多——要处理流式输出、要并发调多个工具、要做结果截断、要处理模型返回格式错误——但骨架就是这么简单。你先把这个骨架跑通再往上加东西比一上来就啃框架源码要高效得多。3. 核心细节解析与实操要点3.1 工具描述怎么写才能让模型“会用”工具层是 Agent 的手脚但模型怎么知道该用哪只手全靠你写的工具描述。这是整个 Agent 开发里最容易被低估、也最影响效果的环节。我踩过的第一个坑就是工具描述写得太随意。当时写了个“查询数据”的工具描述就一句话“查询数据库”。结果模型要么不用它要么乱用传的参数驴唇不对马嘴。后来我把描述改成“根据用户提供的订单号查询订单详情返回订单状态、金额、下单时间。订单号格式为 16 位数字字符串。”效果立刻不一样了。工具描述要包含这几个要素功能说明这个工具是干什么的一句话说清楚。使用场景什么情况下应该用这个工具什么情况下不该用。参数说明每个参数是什么含义、什么格式、是否必填、取值范围。返回说明返回什么结构的数据方便模型理解结果。边界说明有什么限制比如最多返回多少条、超时时间多长。参数 Schema 用 JSON Schema 写这个前端应该很熟跟表单校验规则几乎一样{ name: search_orders, description: 根据用户ID查询订单列表最多返回最近20条, parameters: { type: object, properties: { userId: { type: string, description: 用户唯一标识32位字符串 }, status: { type: string, enum: [pending, paid, shipped, completed], description: 订单状态筛选不传则返回全部 } }, required: [userId] } }注意工具描述里的每一个字都会占用 token而且会进入每一轮对话的上下文。所以描述要精确但不要啰嗦把关键信息说清楚就行别写小作文。还有一个实操心得工具数量不要一次给太多。我试过给模型挂 20 多个工具结果它选择困难经常挑错。后来按场景分组每次只给当前任务相关的 5 到 8 个工具准确率明显提升。这就像你给一个人递工具一次递一把他很快就能选对一次递一箱他反而懵了。3.2 上下文管理别让对话历史撑爆 tokenAgent 跑多轮任务的时候对话历史会越来越长。每一轮的工具调用和返回结果都会追加到 messages 数组里如果不做处理很快就会超出模型的上下文窗口。我遇到过一次线上问题一个数据分析 Agent 跑了十几步之后突然报错查了半天发现是上下文超了。模型直接返回了一个截断的错误信息前端展示得莫名其妙。后来加了上下文管理策略才解决。常用的策略有这么几种我一般组合使用滑动窗口只保留最近 N 轮对话。简单粗暴但有效适合大多数场景。N 取多少要看你的任务复杂度我一般设 10 到 15 轮。摘要压缩把早期的对话让模型总结成一段摘要用摘要替代原始对话。这个适合长任务但摘要本身也要调模型有额外成本。关键信息提取把任务目标、已确认的事实、当前进度这些关键信息单独存一个对象每轮都带上原始对话该丢就丢。工具结果截断工具返回的结果如果太长只保留关键字段。比如查数据库返回 100 条记录你没必要全塞给模型取前 5 条加个总数就够了。function truncateToolResult(result, maxLength 2000) { const str JSON.stringify(result); if (str.length maxLength) return str; // 数组只保留前5条 if (Array.isArray(result)) { return JSON.stringify({ items: result.slice(0, 5), total: result.length, truncated: true }); } return str.slice(0, maxLength) ...[已截断]; }提示上下文管理没有银弹要根据你的具体任务来调。我的经验是先把滑动窗口加上观察一段时间看哪些信息丢了会导致任务失败再针对性地做保留。3.3 流式输出的处理前端的主场流式输出这块前端做起来是真的顺手。模型返回的 token 是一个一个来的你要做的就是边收边渲染。SSE 也好WebSocket 也好前端处理这些本来就是日常。但 Agent 场景的流式输出比普通聊天要复杂一点因为中间可能夹杂着工具调用的状态。用户看到的应该是模型在思考 → 决定调用某个工具 → 工具执行中 → 工具返回结果 → 模型继续思考 → 给出最终答案。这一串状态都要实时展示出来不然用户等半天不知道发生了什么。我的做法是把整个流程抽象成事件流每个事件有类型和载荷// 事件类型定义 const EventType { THINKING: thinking, // 模型思考中 TOOL_CALL: tool_call, // 决定调用工具 TOOL_RESULT: tool_result, // 工具返回结果 ANSWER: answer, // 最终答案 ERROR: error // 出错 }; // 前端消费事件流 eventStream.on(event, (event) { switch (event.type) { case EventType.THINKING: showThinkingIndicator(); break; case EventType.TOOL_CALL: showToolCall(event.data.toolName, event.data.args); break; case EventType.TOOL_RESULT: showToolResult(event.data.result); break; case EventType.ANSWER: appendAnswer(event.data.content); break; } });这样用户全程都知道 Agent 在干什么等待焦虑会小很多。实测下来同样的响应时间有状态展示的用户满意度明显更高。3.4 错误处理与降级让 Agent 别那么容易崩Agent 系统比普通应用更容易出错因为多了模型这个不确定因素。模型可能返回格式不对的 JSON、可能调用不存在的工具、可能陷入死循环、可能把参数传错。这些都要有兜底。我总结了几类常见错误和对应的处理方式错误类型表现处理方式JSON 解析失败模型返回的 tool_call 格式不对重试一次附带格式纠正提示工具不存在模型调了没定义的工具返回错误信息让模型重新选参数校验失败参数类型或格式不对返回具体校验错误让模型修正工具执行超时工具调用超过设定时间中断并返回超时信息死循环反复调同一个工具设最大步数超了强制终止上下文超限token 超出窗口触发上下文压缩或截断重试策略我一般用指数退避第一次失败等 1 秒第二次等 2 秒第三次等 4 秒最多重试 3 次。超过就降级给用户一个明确的错误提示而不是一直转圈。async function callWithRetry(fn, maxRetries 3) { for (let i 0; i maxRetries; i) { try { return await fn(); } catch (err) { if (i maxRetries - 1) throw err; await sleep(Math.pow(2, i) * 1000); } } }注意重试不是万能的。如果是模型能力问题导致的错误重试多少次都一样。这时候要做的是优化 prompt 或工具描述而不是加更多重试。4. 实操过程与核心环节实现4.1 从零搭一个最小可用 Agent 的完整步骤光说理论没意思我带你走一遍从零搭一个 Agent 的完整流程。这个 Agent 的功能很简单根据用户的问题决定是直接回答还是查资料后回答。麻雀虽小五脏俱全。第一步确定模型接口我用的是标准的 Chat Completions 接口支持 function calling。请求体大概长这样const requestBody { model: your-model-name, messages: messages, tools: tools.map(t ({ type: function, function: { name: t.name, description: t.description, parameters: t.parameters } })), stream: true };关键点是tools字段把工具定义传进去模型就知道有哪些工具可用。stream: true开启流式返回。第二步定义工具这个例子只需要一个工具搜索。const searchTool { name: web_search, description: 搜索互联网获取最新信息。当问题涉及实时数据、新闻、或你不确定的事实时使用。, parameters: { type: object, properties: { query: { type: string, description: 搜索关键词尽量精确 } }, required: [query] }, execute: async ({ query }) { // 实际项目中这里调搜索 API const results await searchAPI(query); return results.slice(0, 3).map(r ({ title: r.title, snippet: r.snippet })); } };第三步写编排循环这是核心。循环里要做的事调模型 → 解析返回 → 如果是工具调用就执行 → 把结果塞回 messages → 继续循环 → 如果是最终答案就返回。async function runAgent(userQuestion) { const messages [ { role: system, content: 你是一个助手可以搜索资料来回答问题。 }, { role: user, content: userQuestion } ]; const tools [searchTool]; const maxSteps 5; for (let step 0; step maxSteps; step) { const response await callModel(messages, tools); // 没有工具调用直接返回答案 if (!response.toolCalls || response.toolCalls.length 0) { return response.content; } // 有工具调用逐个执行 messages.push(response.rawMessage); for (const toolCall of response.toolCalls) { const tool tools.find(t t.name toolCall.name); let result; try { result await tool.execute(toolCall.args); } catch (err) { result { error: err.message }; } messages.push({ role: tool, tool_call_id: toolCall.id, content: JSON.stringify(result) }); } } return 抱歉我尝试了多次但未能完成这个任务。; }第四步接上流式输出把callModel改成流式版本边收边解析。工具调用的参数是分片传过来的要拼完整才能解析。async function callModelStream(messages, tools, onEvent) { const stream await fetchModelStream(messages, tools); let contentBuffer ; let toolCallBuffer {}; for await (const chunk of stream) { const delta chunk.choices[0]?.delta; if (delta?.content) { contentBuffer delta.content; onEvent({ type: thinking, data: delta.content }); } if (delta?.tool_calls) { for (const tc of delta.tool_calls) { const idx tc.index; if (!toolCallBuffer[idx]) { toolCallBuffer[idx] { id: , name: , args: }; } if (tc.id) toolCallBuffer[idx].id tc.id; if (tc.function?.name) toolCallBuffer[idx].name tc.function.name; if (tc.function?.arguments) toolCallBuffer[idx].args tc.function.arguments; } } } // 流结束后解析工具调用 const toolCalls Object.values(toolCallBuffer).map(tc ({ id: tc.id, name: tc.name, args: JSON.parse(tc.args) })); return { content: contentBuffer, toolCalls }; }第五步前端展示前端拿到事件流之后按类型渲染。思考内容用打字机效果工具调用显示成一个卡片工具结果折叠展示最终答案突出显示。这套跑下来一个最小可用的 Agent 就成型了。代码量不大但该有的环节都有了。你可以在这个基础上加更多工具、加记忆、加多轮对话都是在这个骨架上长出来的。4.2 参数调优温度、最大步数、超时怎么定Agent 开发里几个关键参数我一个个说我的经验值。温度temperature这个参数控制模型输出的随机性。Agent 场景我一般设 0 到 0.3 之间。为什么这么低因为 Agent 需要稳定地做决策温度高了它可能这次选这个工具下次选那个行为不可预测。只有在需要创意输出的环节比如让 Agent 写文案才会把温度调到 0.7 以上。最大步数maxSteps这个决定了 Agent 最多能走多少步。设太小任务完不成设太大可能死循环烧钱。我的经验是简单任务 5 步中等复杂度 10 步复杂任务 15 到 20 步。超过 20 步的基本上说明任务拆解有问题应该优化 prompt 而不是继续加步数。单步超时每次模型调用或工具调用设一个超时我一般设 30 秒。超过就中断返回超时错误让 Agent 决定下一步。不设超时的话一个卡住的请求能把整个流程拖死。总超时整个任务设一个总时间上限我一般设 2 到 5 分钟。到了就强制终止给用户返回已完成的部分结果。最大 token 数单次模型调用的输出上限我一般设 2000 到 4000。设太小可能答案被截断设太大浪费。工具调用的参数一般不会太长2000 够用最终答案如果预期较长可以单独放宽。这些参数没有标准答案要根据你的具体场景调。我的建议是先用保守值跑起来观察实际表现再逐步调整。调参的时候一次只改一个不然你都不知道是哪个参数起的作用。4.3 多工具协作的编排模式单个工具的场景比较简单但真实任务往往需要多个工具配合。我总结了几种常见的编排模式。串行链工具 A 的输出是工具 B 的输入一步步往下传。比如先搜索再根据搜索结果查数据库再根据数据库结果生成报告。这种模式最简单按顺序执行就行。并行扇出多个工具同时执行结果汇总。比如同时查三个数据源然后合并。这种模式要注意并发控制和结果合并逻辑。条件分支根据中间结果决定走哪条路。比如如果搜索到了结果就直接用没搜到就换个关键词再搜。这种模式本质是状态机要处理好分支条件。循环迭代反复执行某个工具直到满足条件。比如不断搜索直到找到满意答案或者不断修改直到通过校验。这种模式一定要设最大迭代次数不然容易死循环。实际任务往往是这几种模式的组合。我的做法是把每种模式封装成独立的编排函数然后像搭积木一样组合起来。这样逻辑清晰也方便复用和测试。// 串行编排 async function chain(steps, input) { let result input; for (const step of steps) { result await step(result); } return result; } // 并行编排 async function parallel(tasks, input) { const results await Promise.all( tasks.map(task task(input)) ); return results; } // 条件编排 async function branch(condition, truePath, falsePath, input) { const shouldGoTrue await condition(input); return shouldGoTrue ? truePath(input) : falsePath(input); }提示多工具协作的时候工具之间的数据格式要对齐。我踩过的坑是工具 A 返回的是数组工具 B 期望的是对象中间没做转换结果 B 一直报错。后来定了个规矩所有工具的输出都统一成{ success, data, error }的结构谁用谁自己从 data 里取。5. 常见问题与排查技巧实录5.1 模型不调用工具怎么办这是新手最常遇到的问题明明定义了工具模型就是不用直接自己瞎编答案。原因通常有三个。第一工具描述写得太模糊模型不知道什么时候该用。第二系统提示词里没强调要用工具。第三模型本身的能力问题有些小模型对 function calling 支持不好。排查顺序先看工具描述把使用场景写清楚明确告诉模型“遇到 XX 情况必须用这个工具”。然后在 system prompt 里加一句“你需要使用提供的工具来获取信息不要凭记忆回答”。如果还不行换个对 function calling 支持更好的模型试试。我遇到过一个案例工具描述里写了“查询天气”但没写“当用户问天气时使用”。模型就理解成这个工具是备用的不主动用。加上使用场景说明之后调用率从 30% 涨到 90%。5.2 工具调用参数传错怎么修模型传错参数也很常见比如该传字符串的传了数字该传数组的传了单个值或者干脆漏传了必填参数。处理方式分两层。第一层是 Schema 校验在工具执行前先校验参数不合格直接返回错误信息让模型重试。第二层是描述优化把参数格式在描述里写得更明确最好给个示例。function validateArgs(args, schema) { const errors []; for (const [key, def] of Object.entries(schema.properties)) { if (schema.required?.includes(key) args[key] undefined) { errors.push(缺少必填参数: ${key}); continue; } if (args[key] ! undefined def.type) { const actualType Array.isArray(args[key]) ? array : typeof args[key]; if (actualType ! def.type) { errors.push(参数 ${key} 类型错误: 期望 ${def.type}实际 ${actualType}); } } } return errors; }校验失败的时候把具体的错误信息返回给模型它通常能自己修正。我实测下来加上参数校验和错误反馈之后工具调用的成功率能从 70% 左右提到 95% 以上。5.3 Agent 陷入死循环怎么破死循环的表现是Agent 反复调同一个工具或者在两个工具之间来回跳始终不给最终答案。根本原因通常是工具返回的结果没有让模型获得新信息模型觉得问题没解决就再调一次结果还是一样就再调。如此往复。破解方法有几个。最直接的是设最大步数到了就强制终止。但这只是兜底治标不治本。更好的做法是在工具返回结果里加上“这是第几次调用”的信息让模型知道它已经试过了。或者在编排层检测重复调用如果连续三次调同一个工具且参数相同就中断并提示模型换策略。function detectLoop(history, threshold 3) { const recent history.slice(-threshold); const signatures recent.map(h ${h.toolName}:${JSON.stringify(h.args)} ); return new Set(signatures).size 1; }还有一个技巧是在 system prompt 里加一句“如果你已经尝试过某个方法但没有成功请换一种方法不要重复同样的操作。”这句话能减少不少无意义的重复调用。5.4 常见问题速查表问题现象可能原因排查方向解决方式模型不调工具描述模糊/提示词没强调检查工具描述和 system prompt补充使用场景强调必须用工具参数传错Schema 不清晰/模型能力不足看错误日志里的参数加校验错误反馈优化描述死循环工具结果无新信息看调用历史是否重复设最大步数加重复检测上下文超限历史太长/结果太大看 token 计数滑动窗口结果截断响应太慢步数太多/工具慢看每步耗时优化工具并行调用设超时答案不准确工具结果没用好看最终答案和工具结果的关系优化 prompt让模型引用工具结果格式解析失败模型输出不稳定看原始返回内容加重试用更稳定的模型5.5 几个我踩过的坑和对应的心得坑一工具返回结果太大。有一次工具返回了一个 50KB 的 JSON直接塞进上下文下一轮模型调用就超限了。后来所有工具返回都加了截断逻辑超过 2000 字符就只保留关键字段。坑二没做并发控制。模型一次返回了 5 个工具调用我直接 Promise.all 全发出去了结果把下游服务打挂了。后来加了并发上限最多同时执行 3 个。坑三错误信息直接暴露给用户。早期版本工具报错的时候我把原始错误信息直接展示给用户了里面包含了一些内部细节。后来统一做了错误包装对外只展示友好的提示详细信息记日志。坑四没考虑模型返回格式的多样性。不同模型返回 tool_call 的格式有差异有的用function.arguments有的用parameters。我一开始只按一种格式解析换个模型就崩了。后来写了个适配层兼容多种格式。坑五忘了处理流式输出中的工具调用。流式模式下工具调用的参数是分片到达的必须拼完整才能 JSON.parse。我一开始每收到一个分片就解析一直报错。后来改成先缓存流结束后统一解析。这些坑说起来都不复杂但没踩过就是想不到。我的建议是第一个版本先把主流程跑通别追求完美然后拿真实场景去测测出问题一个个补。Agent 开发就是个不断打补丁的过程别指望一次写对。6. 一些关于前端转 Agent 的个人体会写了这么多最后聊几句我自己的感受。前端转 Agent 开发最大的障碍不是技术是心态。很多人一听到“AI”就觉得高深莫测觉得自己没学过机器学习肯定做不了。但实际上Agent 开发里用到的机器学习知识几乎为零。你不需要知道反向传播怎么算不需要知道注意力机制的公式你只需要知道模型能做什么、不能做什么、怎么跟它配合。前端的工程思维在这个领域特别值钱。你知道怎么处理异步、怎么管理状态、怎么做容错、怎么优化体验。这些东西在 Agent 开发里全是核心能力。我甚至觉得一个经验丰富的前端转 Agent 开发比转模型训练要快得多因为前者是能力迁移后者是从零开始。当然也有要补的课。比如 prompt 工程、上下文管理、工具设计这些是 Agent 开发特有的需要专门花时间琢磨。但这些都不难多做几个项目就有感觉了。我自己的路径是先用现成 API 写个聊天机器人然后加工具调用然后做多轮任务然后优化错误处理和体验。一步步来每一步都有正反馈。如果你现在还在犹豫要不要入这个方向我的建议是别想太多先动手写个最小 demo。一个能查天气的 Agent代码量不到 200 行一个周末就能跑起来。跑起来之后你自然就知道下一步该学什么了。这个领域变化快但底层的东西——编排、状态、容错、体验——是相对稳定的而这些恰好是前端最擅长的。