我做了快六年前端,React、Vue、可视化大屏都摸过一遍。上个月,技术总监丢给我一个需求:做一个能自动回答客户售后问题的机器人,接入现有的工单系统。我一开始以为是普通问答机器人,照着文档拼接几个prompt就完事。结果越做越不对劲,客户问的是“我的订单为什么还没发货”,这背后要查订单状态、物流轨迹、仓库库存、客服备注,甚至还有改地址的权限操作。这根本不是对话,这是要把一堆业务系统串起来干活。做完这个项目后我最大的感受是:前端转Agent开发,最好的切入点不是去学一堆概念,而是先接住一个真实需求,让需求逼着你去理解Agent是什么。
1. 从工单系统到Agent:最初的企业需求到底长什么样
1.1 客户想要的不是“聊天”,而是“把事办成”
需求刚从产品那边传过来的时候,描述很简单:做一个在线客服机器人,客户问了之后,机器人能自动回复,减少人工成本。我当时第一反应是做关键词匹配加FAQ,最多再上个RAG(检索增强生成)把企业文档喂进去。
但真正去蹲客服部门看了几天后,我发现真实场景复杂得多。客户问“订单怎么还没到”,客服不是回复一句“请耐心等待”就完事,而是需要完成下面这串动作:
- 登录订单系统,根据客户手机号查出订单号;
- 调第三方物流接口,看包裹当前位置;
- 如果物流超过三天没更新,自动生成一条异常工单;
- 如果订单在“已支付未发货”状态超过48小时,触发催发货通知;
- 最后把可执行的结论(预计送达时间、补偿方案)返回给客户。
这套流程里,模型要做的不是生成漂亮话,而是理解用户意图、拆解任务、调用多个工具、根据返回结果做决策。这才是真正的企业级Agent形态。也就是从这时候起,我决定这个项目不能用传统对话机器人方案来做,而是要按照Agent的路子来设计。
1.2 为什么传统对话机器人在这个场景下必然翻车
传统对话机器人通常是“意图识别 + 槽位填充 + 规则响应”。你定义一堆意图,比如“查订单”“退换货”“投诉”,然后训练模型把用户的话分类到意图里,再提取槽位(订单号、手机号),最后走固定流程返回。
这套逻辑在简单FAQ场景下够用,但一旦出现需要动态决策的环节就崩了:
- 用户说“我昨天买的东西什么时候能到”,系统需要先知道“昨天买的”对应哪个订单,如果用户有多个订单,还要进一步追问;
- 用户说“不想要了,怎么退”,可能涉及“查询订单状态”“判断是否可退”“计算退款金额”“生成退货地址”一系列动作,中间任何一步失败都要有回退策略;
- 用户说“你们怎么又发错货了”,这种情绪化表达里没有明确的工具参数,但Agent需要结合用户历史订单和最近一次客服备注去推断。
这些问题靠预设对话流根本填不完。Agent把这事情反过来做:我不提前定义所有对话分支,而是给模型一套工具和明确的决策边界,让模型在每一步自己决定“调哪个工具”“传什么参数”“下一步做什么”。前端开发里我们常做状态机来解决复杂交互,Agent本质上是一个更动态、更自治的状态机。
1.3 真实业务边界:Agent不是万能钥匙
做项目之前必须先划边界,这个比写代码更重要。我一开始天真地以为,只要把工具给全,Agent什么都能搞定。但实际跑了之后发现,不加边界控制的Agent是企业项目里的一颗定时炸弹。
我最终确定的边界是:
- Agent只负责“查询类”和“标准操作类”任务,比如查订单、查物流、生成工单;
- 涉及退款、改地址、补偿金额这类高风险操作,Agent只负责起草结论,最终由人工确认后执行;
- 所有Agent对外回复都必须带有可追溯的引用来源,客服能在后台点进去看数据来源。
这个边界看起来简单,但它决定了后面的架构设计。Agent只有在可控范围内才叫智能体,出了边界就是事故。前端同学做这个项目有一层天然优势——我们对“用户确认弹窗”这种交互很熟,把同样的思路用在“人工审批节点”上,比后端同学想得还细。
2. 用前端思维理解Agent架构,反而比后端同事更快上手
2.1 Agent核心循环:感知-规划-行动-记忆,对应着前端的渲染-事件-状态
我先解释一个最容易让新手绕晕的事:Agent到底是什么。换个前端视角,立刻清楚起来了。
前端框架的核心是UI = f(state),事件触发状态变更,状态变更触发重新渲染。Agent也类似,它的核心循环是:
- 感知:把用户输入和工具返回结果作为上下文喂给模型;
- 规划:模型根据上下文决定下一步调用哪个工具或直接生成回复;
- 行动:执行工具调用,得到结果;
- 记忆:把中间步骤写入context或外部存储,供下一步决策使用;
- 循环,直到模型认为任务完成。
这跟前端的事件循环没有本质区别。用户点击按钮是“用户输入”,API返回数据是“工具执行结果”,全局状态管理就是“记忆层”。我甚至在给团队讲Agent架构的时候,直接画了一张类似Redux数据流的图,大家秒懂。
2.2 当我说“Agent框架”时,我实际在用哪些东西
前端的“框架”指的是React/Vue这种UI库,Agent领域的“框架”抽象层级更高。市面上常见的Agent框架包括LangChain、LangGraph、Dify、Coze,还有近期火起来的各类轻量级Agent框架。它们的核心能力可以归纳成三块:
- 模型调用层:屏蔽不同大模型厂商的接口差异,统一成OpenAI兼容格式;
- 工具调用层:让你把普通函数注册成LLM可识别的工具(Tool/Skill),并自动完成参数JSON Schema填充;
- 编排层:定义Agent的执行循环,包括单步调用、多步推理、人工审批节点、错误重试等。
我最终选型时没有直接用大而全的LangChain,而是选了LangGraph做编排,配合自己封装的工具注册层。原因很简单:这个业务涉及多步工具调用和人工审批节点,LangGraph把整个流程画成一张图,节点和边都看得见,出问题时能定位到具体节点。
2.3 前端项目里最容易忽略的模型调用层
很多前端同学一上来就想着怎么画聊天界面,结果卡在“模型到底怎么调用的”。这里必须搞清楚一个基础概念:大模型API本身是无状态的。你上一轮问它什么,它下一轮是不记得的,除非你手动把聊天记录拼到上下文里。
所以Agent开发里必须自己维护消息历史。我刚开始用了一个最简单的方式:
const messages = [ { role: 'system', content: systemPrompt }, { role: 'user', content: userQuestion }, ]; // 第一次调用 const response = await callModel(messages); // 把模型回复追加进messages messages.push({ role: 'assistant', content: response.content }); // 判断是否要调用工具 if (response.toolCalls?.length) { for (const call of response.toolCalls) { const result = await executeTool(call); messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result), }); } }这段代码看着简单,但它体现了Agent开发的核心:你不是在写一个“提问-回答”的函数,而是在维护一段会生长的对话状态。前端同学如果熟悉Redux,这里就是把action和reducer换成了messages数组。
3. 第一版落地的技术选型与完整实现路径
3.1 技术选型对比:直接用LLM API还是上Agent框架
我拿到需求后先做了个技术预研,列了一张对比表,逼着自己做决策。
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 直接调LLM API + 自己写循环 | 完全可控,依赖少 | 多步决策逻辑要自己写,开发量大 | 只有一两个工具调用的小项目 |
| LangChain | 生态全,上手快 | 抽象层级多,出问题时不好排查 | 原型验证、标准流程 |
| LangGraph | 图结构编排,节点清晰 | 学习成本中等,概念多 | 需要人工审批和多分支流程 |
| Dify / Coze 这类低代码平台 | 配置即开发,上手最快 | 定制化受限,难接私有化复杂系统 | 业务较标准、不想写太多代码 |
我最后选了LangGraph作为编排底层,原因有三个:第一,业务里有多个人工审批节点,图结构好表达;第二,团队里有前端也有后端,图比代码更容易沟通;第三,它支持状态快照机制,调试的时候能看每个节点的输入输出,这个对排查问题太重要了。
3.2 核心代码拆解:工具注册、任务编排、上下文拼接
整个项目最核心的代码不是聊天界面,而是工具注册和执行层。我先定义了一个统一工具结构,让前端同事也能像写页面组件一样注册新能力:
interface AgentTool<TArgs, TResult> { name: string; description: string; parameters: JSONSchema; // 供模型做参数理解 needsConfirm?: boolean; // 是否走人工审批节点 execute(args: TArgs, context: AgentContext): Promise<TResult>; }然后把业务系统的能力一个个注册进去。比如查询订单:
const queryOrderTool: AgentTool<{ keyword: string }, OrderInfo> = { name: 'queryOrder', description: '根据手机号或订单号查询客户订单信息,包含订单状态、商品明细、金额、下单时间', parameters: { type: 'object', properties: { keyword: { type: 'string', description: '手机号或完整订单号', }, }, required: ['keyword'], }, execute: async ({ keyword }) => { // 调用后端订单服务 const res = await fetch(`/api/orders/search?keyword=${keyword}`); return res.json(); }, };这段代码前端同学应该看得懂。注意description那段话很关键,模型不是靠函数名理解工具的,而是靠这段描述判断“什么时候该用它”。这跟组件命名一个道理,queryOrder谁都会写,但“根据手机号或订单号查询客户订单信息”才是让模型不误用的关键。
3.3 把“前端Skills”的想法搬进Agent工具集
做前端开发时我们强调组件复用、hooks抽取、props设计。做Agent的Skill设计时,我把这套方法论平移了过来。
所谓Skill(技能),本质上是:告诉模型“某些特定场景下你该按什么步骤做事”的预置能力。它可以是一段补充Prompt,也可以是一个组合工具调用链。
举一个例子。客户问“我的订单怎么还没到”,如果只给模型一个queryOrder工具,它可能查完状态就直接回答“请耐心等待”。但我们真正想做的是,让模型进入一个“物流异常排查”技能,按顺序执行下面的步骤:
- 调用
queryOrder获取订单号; - 调用
queryLogistics获取物流轨迹; - 如果物流轨迹超过72小时未更新,生成工单并触发客服介入;
- 把处理结论转成客户能看懂的通俗话术。
这个“技能”在LangGraph里就是一条子图。在前端视角里,它就是抽取出来一个复用组件,不同的地方需要时引用它。这套思路让团队里的后端同事都惊讶:原来前端组件化思想还能这么用在Agent上。
3.4 会话记忆:不能只靠模型上下文硬撑
业务上线后第一个实际问题是:用户和Agent连续聊了五轮,Agent就忘了第一轮说的手机号。原因是消息全塞在上下文里,超过窗口后最前面的信息被截断了。
我做的记忆层分两块:
- 短期记忆:当前会话内,把关键业务参数(手机号、订单号、用户ID)单独抽出来,存进一个JSON字段,每次对话开始时优先注入;
- 长期记忆:把每个用户的历史工单、历史咨询分类、是否投诉过存进外部存储,Agent回答时参考。
前端同学可以这样理解短期记忆:就像是组件里的state,不随渲染丢失;长期记忆像是缓存在localStorage里的用户偏好设置,每次启动页面时读一次。
const contextMemory = { userId: 'U20240001', linkedPhone: '138****1234', orders: ['ORD20241001'], resolvedOrderId: null, lastEvent: 'ORDER_STATUS_QUERY', };关键业务参数结构化后,上下文拼接就稳定多了。这是第一版上线后做的最重要优化之一,效果立竿见影,用户重复提供手机号的次数少了八成。
4. 真实开发中的踩坑记录:不是模型不够聪明,而是边界没定义清楚
4.1 工具调用参数验证:前端同学最拿手却最后才做对
刚开始写工具的时候,我以为只要把parameters写成JSON Schema就完事了。结果上线第二天,模型调用queryOrder时候传了个keyword是空字符串,导致后端报错,Agent就卡死了。
排查链路如下:
- 我先把报错日志拉出来,看到
execute内部抛了“参数不能为空”的错误; - 再往前翻,发现模型确实生成了一个
keyword: ""的工具调用; - 一开始怀疑是模型调用不稳定,后来发现是工具描述的歧义问题——
description里我没写“必须提供非空手机号或订单号”; - 最关键的是,
execute函数里缺少一层防御性校验。
修复方式有两个层面。先在后端工具入口加了一层参数校验:
execute: async ({ keyword }) => { if (!keyword?.trim()) { return { status: 'error', message: 'keyword参数不能为空,请先询问用户的手机号或订单号', }; } // ... }再把工具描述改进了一版,明确加了约束:keyword:必填,且必须是合法手机号或8位以上订单号,若用户没有提供,请先追问用户。
加了这层之后,模型乱传空参数的情况几乎绝迹。这件事让我意识到:Agent的工具调用,跟前端表单校验一个道理,你不能相信用户的输入,同样也不能相信模型生成的参数。
4.2 上下文窗口被撑爆的排查链路
项目跑了一周后,有一天突然大量会话失败,报错提示“token超出限制”。那时候我第一反应是该升级模型版本了,但后来冷静下来一查,发现根本不是模型的问题。
整个排查过程是这样的:
- 我先去模型调用日志里看是什么会话触发的超限;
- 发现全是长会话,用户和Agent聊了十几轮;
- 打开具体会话记录,发现上下文数组里被塞进了大量完整的工单详情、JSON返回值;
- 换了一个小模型单独测,还是超限,说明不是模型问题,是调用方的上下文管理有缺陷。
根因就是我前面说的:我在每轮工具执行后,把完整工具返回结果原封不动塞进messages,导致上下文越来越胖。
解决思路不是粗暴截断,而是要做信息压缩。工具返回的原始JSON里有大量没用字段,我写了一个summarizeToolResult函数,在把工具结果拼回上下文之前,先对返回内容做摘要,只保留对下一步决策有用的字段。比如订单详情原本有40个字段,摘要后只保留订单状态、商品名称、预计送达时间、是否需要人工介入。
上下文管理不是调参问题,而是一个信息架构问题。前端做状态管理时我们时刻提醒自己“别把不相关的数据放进store”,Agent开发里也一样。
4.3 并发与超时:平时写接口,现在要写的是长时间任务管道
这个坑我在联调阶段踩得最痛。前端的接口请求超时设置一般是10到30秒,但Agent处理一个完整任务可能需要30秒甚至更久,因为中间要多次调用模型,模型本身响应就要几秒。
第一版我把整个Agent调用包在一个HTTP请求里,结果网关直接超时断连。后来我改成了异步任务模式:
- 客户端提交问题后,后端立刻返回一个
taskId; - Agent在后台执行,执行进度写入Redis;
- 前端通过WebSocket轮询
taskId获取实时状态; - 执行完成后,把最终结果推送给前端。
这套模式前端同学应该很熟,就是上传大文件那一套:先把任务提交上去,再通过轮询或WebSocket拿进度。只是这次任务不是上传文件,而是一个可能带有多步工具调用的Agent决策流程。
WebSocket这里还要注意一点,不要把整个Agent中间步骤都推到前端。我第一版推了所有模型原始响应,结果聊天界面刷屏,用户根本看不懂。后来只推三个状态:执行中、需要人工确认、完成。中间的工具调用轨迹只留在后台日志里,只有客服端能看到详细时间线。
4.4 人工审批节点:Agent和真实业务系统的握手协议
我之前说,涉及退款、改地址这类高风险操作必须人工确认。这个逻辑在流程图上画出来很简单,真正落地时却有一个非常容易被忽略的问题:Agent发起了审批,然后呢?如果用户后续又发来新问题,Agent是等审批结果还是先处理新问题?
我最终的方案是:Agent在触发人工审批时,先把当前会话锁定在当前子图节点上,同时给用户回复“您的退款申请已提交人工审核,预计30分钟内处理完毕,您也可以继续咨询其他问题”。客服端审批完成后,系统把审批结果推回给Agent,Agent再结合结果继续后续流程。
这个“会话锁”的实现,相当于前端里的互斥锁。我用一句话总结给我的团队:Agent能发起的操作必须小于它能承诺的范围,凡是超出范围的,就必须把人放进链路里。
5. 这次转型对我做前端开发的反哺与学习路线
5.1 组件化思想迁移到Agent的Skill设计
说实话,前端转Agent开发,最大的优势不是会写代码,而是有一套已经训练过的抽象思维。Agent的Skill设计本质上就在做三件事:
- 找共性:把多个业务流程里的重复动作抽出来,比如“查客户”“查订单”“生成工单”都是高频动作;
- 定边界:每个Skill只负责一件事,输入输出都明确,跟“组件props”一模一样;
- 可组合:Skill之间可以互相调用,一个“发货异常处理”Skill内部包含“查订单”“查物流”“建工单”三个基础Skill。
这个思路对我原本的前端工作也有反哺。项目做完后,我再写前端组件时,会更自然地思考“这个组件会被哪个调用方使用”“它的输入输出协议是否清晰”,等于用Agent的视角把前端代码审查了一遍。
5.2 Agent开发学习路线:从今天到这个项目验收我做了哪些事
我整理一下自己从零开始做这个项目的过程,想转Agent的前端同学可以直接按这个节奏走。
第一步,先把“模型调用”这层搞明白。不需要读特别深,但一定要自己调通一次API,理解什么是system prompt、user消息、assistant消息、工具调用返回。整个过程大概一周。
第二步,用一个现成的Agent框架做一个最小闭环。我当时用LangGraph做了一个“查天气然后生成穿衣建议”的Demo,虽然简单,但它把Agent执行循环完整跑通了。这一周的速度主要是踩环境依赖的坑。
第三步,找一个真实业务场景下的小切面,把它做成一个单工具Agent。比如我就选了“查订单状态”这个点,先不接物流、不接工单,只让Agent学会根据手机号查订单并返回简洁结论。
第四步,在这个单工具基础上增加分支,逐步把物流查询、异常判断、工单生成接进来。每加一个工具,跑一遍完整回归,观察模型是否会误用工具、参数是否传对。
第五步,再考虑记忆、并发、上下文压缩、人工审批这些“工程化”问题。
整个项目大概用了六周。前三周每天花三小时左右学习和搭骨架,后三周主要在联调、修边界的坑。前端基础在这里是很加分的,理解API、理解异步、理解状态同步,几乎无障碍平移。
5.3 给同样想转Agent的前端同学,几条实打实的建议
第一,不要一上来就研究特别深的模型原理。你是做工程交付的,不是搞算法研究的。学习模型推理细节不如先写通一次工具调用。
第二,企业项目里,Agent的价值不在于聊天多聪明,而在于能不能真正调通业务系统。前端同学如果能把用户界面、身份认证、业务接口摸透,做出来的Agent会比一个纯后端做的更贴近真实用户。
第三,多看框架的调试工具。LangGraph有一个可视化调试界面,能看到每一轮模型决策、每个节点的输入输出,这个跟前端DevTools里看Network面板一样重要,排错全靠它。
第四,也是我最想强调的:保持“怀疑模型”的心态。模型输出的每一个工具调用参数都要当成用户输入来校验。你之前怎么写表单校验,现在就怎么写工具参数校验。
最后一个实用技巧,Agent项目一定要从第一天就把日志打全。我在每个关键节点都加了结构化日志,记录模型思考、工具选择、参数、耗时、错误信息。后来所有线上问题的排查,几乎都靠这些日志定位,而不是靠用户复述。前端做埋点的经验,在这里完整复用,只是事件名换成了Agent动作名。