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

资讯详情

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

前端转Agent开发实战:从售后报修流程搭建企业级智能助手

前端转Agent开发实战:从售后报修流程搭建企业级智能助手 去年年底我还在写 React 组件和微前端骨架今年年初我已经在给公司的售后流程搭 Agent 了。说这个不是炫耀转型速度而是想给同样在职的前端朋友们一个样本前端转 Agent 开发其实不用先从论文和框架源码啃起从公司内部一个真实、重复、规则模糊的业务需求入手反而走得最快。这篇就用我自己最近这一个月踩出来的项目复盘从接到需求、拆解流程、选型、写编排逻辑到最后上线一个能稳定跑起来的企业级 Agent 的完整过程。这里面没有高深的算法也不需要重学一门语言。我用的还是 TypeScript核心工作就是三件事把业务规则拆清楚、把工具函数写好、把大模型“关”在一个可控的流程里。适合那些已经会写前端、会调接口、想转入 AI 应用开发但一直找不到抓手的人参考。1. 为什么从“真实企业需求”开始转 Agent 开发1.1 前端背景的人转 Agent 开发的天然优势很多前端同行觉得 Agent 开发是大模型算法工程师的事情自己过去只能做做界面。我一开始也这么想直到真正上手才发现Agent 开发的核心难点根本不在模型训练而在工程化。Agent 本质上是“大模型 工具 流程控制”的组合体模型负责理解和决策工具负责执行具体动作流程控制负责约束它不乱跑。这三部分里前端背景的人至少占两项优势工具函数其实就是 API 调用和数据处理这是前端天天在做的事情。流程控制涉及状态管理、异步编排、异常捕获写过复杂前端页面的人都熟。更关键的是前端开发者的日常就是跟“用户需求”打交道。Agent 项目最难的不是技术而是把业务人员脑子里的模糊流程变成计算机能理解的规则。前端出身的人天然擅长理解他人需求再用代码表达出来这个能力在 Agent 开发里非常吃香。1.2 什么样的企业需求最适合当第一个项目我见过不少想转 Agent 开发的人第一件事就是跑去读 LangChain 文档或者去 Hugging Face 上刷模型排行榜结果越看越迷茫。我的建议刚好相反先找一个合适的业务需求让需求牵引你学什么。那什么叫“合适”我总结了三个判断标准重复且耗时这个流程员工每天都要做人工操作需要跨越多个系统一次可能要十几分钟。有明确规则但存在动态分支不是纯固定流程那种用脚本就行也不是完全自由发挥那种模型也做不稳定而是中间有若干判断节点。可量化收益处理一个工单节省的时间、减少的差错率做出来以后能跟老板讲清楚价值。我所在的公司做设备销售和售后服务客户报修之后售后客服需要在 ERP 里查销售订单、在售后系统里查历史维修记录、看保修期怎么算、判断是保内还是保外维修、再决定要不要派单。这套流程涉及两个系统、三个查询动作、若干个判断分支每天几十个工单全靠人工切换页面复制粘贴。这就是典型的“规则清晰但分支多、系统割裂但数据都有”的场景。1.3 我选中的场景售后报修跨系统核对这个需求最初是售后主管在周会上提的说客服每天大量时间花在“查来查去”上想让我做一个“自动查询助手”。如果放在以前我的第一反应是写一个按键精灵式的脚本把页面操作录制下来自动化执行。但深入了解后我发现这件事用传统脚本做非常脆弱因为每个工单的情况都不一样查询路径也不一样有的客户有历史维修记录需要判断最近一次维修是否跟当前故障相关。有的设备超过保修期但可能购买了延保服务这时候需要去合同系统再查一茬。有的设备是经销商代售的销售订单在经销商名下需要额外走一层确认。这些“查一下”“再判断一下”的动态分支正是传统 RPA 和脚本最头疼的地方。但大模型天然擅长理解这种上下文配合工具调用它可以根据每一步的查询结果决定下一步查什么。就这么着我从一个“想做 Agent”的人变成了一个“被真实需求逼着做 Agent”的人。2. 整体架构设计先别碰框架把业务跑通再说2.1 技术选型为什么继续用 TypeScript确定需求之后第一件事是选技术栈。市面上主流的 Agent 框架几乎都是 Python 生态什么 LangChain、LlamaIndex、AutoGPT 都是 Python 为主。我当时也纠结过要不要为了学 Agent 顺便把 Python 也学了。后来跟一个做 AI 应用的朋友聊了很久他的一句话点醒了我框架可以换但你最熟的语言能让你把注意力集中在 Agent 的核心逻辑上。于是我用 TypeScript理由如下现有前端团队全会 TypeScript后续维护不需要单独招人。大模型 API 调用本质是 HTTP 请求 JSON 解析TypeScript 的类型系统正好可以约束工具函数的入参出参写起来非常舒服。Node.js 环境下有很多成熟的企业集成方案比如数据库客户端、企业微信机器人、Excel 处理库这些都是 Agent 要接的“工具层”。我并不是说 Python 不好而是对于一个前端转 Agent 的初学者来说先用自己最熟的工具做出一个能跑的项目建立起对 Agent 的整体认知比一上来就用不熟的语言搏杀框架生态更重要。等你理解了三板斧再去看 Python 生态的框架会发现概念都是通用的。2.2 Agent 的最小闭环由哪几个模块组成当我决定不用重型框架、先用原生 TypeScript 手写一个最小闭环时很多人问我为什么我当时的回答是只有手写过一遍 Agent 循环你才能真正理解框架帮你做了什么。整个系统我拆成了四个核心模块编排器Orchestrator这是大脑中枢负责循环调用大模型、解析模型输出、调用工具、把结果反馈给模型直到模型认为任务完成。工具注册表Tool RegistryAgent 能调用哪些外部能力都通过这个模块注册。订单查询、维修记录查询、延保判断每一个都是独立的工具函数。上下文管理器Context ManagerAgent 在运行过程中会积累多轮对话和工具执行结果上下文管理器负责把这些信息组织好按需传给模型。终端会话层Session因为 Agent 是一个异步交互过程用户可能在一个会话里提交多个工单会话层负责管理多任务并行和状态隔离。这个架构跟我们前端写 React 应用其实有点像。编排器是 reducer工具注册表是 API 层上下文管理器是全局状态会话层是路由。这么一对比前端同学就能迅速找到自己的知识映射了。2.3 架构数据流一个报修工单的完整旅程用文字描述一下整个架构的数据流配合这套理解再去看代码就清晰了用户在企业微信里发一条消息“帮我查一下设备编号 DEV-2024-00123 的保修情况。”会话层收到消息创建或恢复一个会话把消息追加到上下文管理器。编排器把当前上下文发给大模型大模型分析发现需要调用“查订单工具”。编排器解析出模型的工具调用请求去工具注册表找到对应函数传入参数执行。工具函数调用 ERP 接口拿到销售订单数据返回给编排器。编排器把工具结果追加到上下文再次发给大模型。大模型根据新的信息决定继续查维修记录还是直接生成最终结论。这个过程会循环好几轮直到模型输出一个明确的“最终回答”标记。每一轮循环就是一个 Agent Step。这个机制并不神秘它本质上就是一个普通的 while 循环循环条件是“模型还没说结束”。3. 核心实操从 SOP 到可运行的 Agent3.1 第一步把业务方的“人肉操作”翻译成结构化流程做 Agent 开发最容易犯的错是拿到需求就直接开写代码。我花了一整天的时间把售后主管和两个客服叫到会议室让他们把一次完整的报修处理过程事无巨细地演示给我看。我不光记录他们点了哪个按钮更重要的是记录他们“为什么会这么判断”。这一步非常关键因为人工处理流程中的“隐性规则”正是 Agent 最需要学习的逻辑。最终我整理出了一份结构化的处理流程步骤动作判断分支数据来源1根据设备编号查询销售订单无订单标记为“非本公司销售设备”并结束ERP 订单接口2根据销售订单获取购买日期和保修期限是否在保修期内ERP 订单接口3查询该设备历史维修记录是否有同故障码记录售后维修系统4判断是否延保在保走保内维修不在保且无延保推荐付费维修合同管理系统5汇总所有信息生成处理建议输出结构化报告以上全部这里要特别提醒不要把 Agent 设计成一个全自动决策机器。初期最重要的目标是让 Agent 把 80% 的信息查询和基础判断做掉但最终决定权保留给人。客服拿到 Agent 生成的处理建议之后看一眼确认没问题再执行。这样既提高了工作效率又避免了模型出错时造成的事故。3.2 第二步工具层设计与实现工具层是整个 Agent 系统的“手脚”它直接决定了 Agent 能做什么。每个工具函数都有统一的接口规范包括工具名称、描述、入参定义、执行函数。描述特别重要因为大模型就是靠工具名称和描述来决定“什么时候调用哪个工具”的。我定义工具的 TypeScript 代码如下type ToolDefinition { name: string; description: string; parameters: { type: object; properties: Recordstring, any; required: string[]; }; execute: (args: any) Promiseany; }; const orderQueryTool: ToolDefinition { name: query_sales_order, description: 根据设备编号查询销售订单信息返回购买日期、保修期限、客户名称等。只在需要确认设备是否为本公司销售时使用。, parameters: { type: object, properties: { deviceId: { type: string, description: 设备编号通常是 DEV 开头的字符串, }, }, required: [deviceId], }, async execute({ deviceId }) { // 调用 ERP 系统的 HTTP 接口 const order await queryErpOrder(deviceId); if (!order) { return { found: false, message: 未找到对应销售订单 }; } return { found: true, customerName: order.customerName, purchaseDate: order.purchaseDate, warrantyMonths: order.warrantyMonths, deviceModel: order.deviceModel, }; }, };这段代码看起来平淡无奇但细节都在魔鬼里。description 字段里我特意写了“只在需要确认设备是否为本公司销售时使用”这其实是给大模型的一句潜台词没有十足把握别乱调这个工具检查订单状态是一个昂贵操作不要每一步都查一遍。前端同学写惯了函数的调用方是确定的但在 Agent 生态里调用方是大模型工具的“自我描述”越准确模型越不容易乱来。工具返回的 JSON 结构也值得花心思。我会刻意控制返回数据的体积能返回 3 个字段就不返回 10 个字段。因为所有工具返回结果都会进入上下文发给大模型数据越多Token 消耗越大模型也越容易被无关信息干扰。3.3 第三步系统提示词Prompt的设计如果说工具是 Agent 的手脚那系统提示词就是 Agent 的“工作手册”。我从这个项目里学到最重要的一件事是好的系统提示词不是一段漂亮话而是一份可执行的业务规则说明书。我最终用的系统提示词大概长这样摘录核心部分你是一名售后保修助理职责是帮助客服人员查询设备信息并给出保修处理建议。 你必须严格遵循以下流程 1. 当用户提供设备编号时先调用 query_sales_order 确认销售信息。 2. 如果设备不在销售记录中直接输出结论非本公司销售设备不要再调用其他工具。 3. 如果在保调用 query_repair_history 查询历史维修记录然后综合生成报告。 4. 如果不在保调用 check_extended_warranty 查询延保服务。 5. 生成报告时必须包含设备编号、客户名称、购买日期、保修状态、处理建议、数据来源。 铁律 - 在每个工具调用之后必须根据工具返回结果判断下一步不能一次性把三个工具全调了。 - 如果工具调用失败要如实报告错误原因不要猜测或编造数据。 - 最终回答使用简体中文格式为结构化列表。写系统提示词几个技巧供参考用“必须/禁止”而不是“可以/请不要”。大模型对强约束的遵从性远比弱约束好。明确告诉模型“工具调用之后要做什么”。很多人写的提示词只告诉模型能调哪些工具却没告诉它调用完工具之后该干嘛模型就像一个没有目标的人会在多个工具之间反复横跳。给模型一个“拒绝路径”。比如查询不到数据的时候让模型直接说不知道而不是让它硬编一个答案。这个非常重要Agent 的胡说八道很多时候是提示词没有约束好模型以为自己在“合理推测”。3.4 第四步编排主循环与容错处理编排器是 Agent 的心脏我手写了一个最简版本去掉框架包装后核心逻辑大约 100 行代码。它做的事情就是把上下文交给模型、解析模型的输出、如果是普通回复就结束、如果是工具调用就执行工具然后循环。核心代码如下async function runAgent(systemPrompt: string, userInput: string, tools: ToolDefinition[]) { const messages [ { role: system, content: systemPrompt }, { role: user, content: userInput }, ]; const maxSteps 10; // 防止 Agent 无限循环 for (let step 0; step maxSteps; step) { const response await callLLM(messages, tools); // 模型输出是最终回答 if (response.finishReason stop) { return response.content; } // 模型请求调用工具 if (response.toolCalls response.toolCalls.length 0) { const toolCall response.toolCalls[0]; const tool tools.find((t) t.name toolCall.function.name); if (!tool) { messages.push({ role: tool, toolCallId: toolCall.id, content: JSON.stringify({ error: 未找到工具 ${toolCall.function.name} }), }); continue; } try { const result await tool.execute(JSON.parse(toolCall.function.arguments)); messages.push({ role: tool, toolCallId: toolCall.id, content: JSON.stringify(result), }); } catch (error: any) { messages.push({ role: tool, toolCallId: toolCall.id, content: JSON.stringify({ error: error.message }), }); } } } throw new Error(Agent 执行超过最大步数可能陷入循环); }这段代码的一切设计都有原因maxSteps 限制为 10是为了防止模型陷入“调用工具-看结果-再调用工具”的死循环。真实场景中一个售后咨询通常最多 4-5 步就能完成10 步已经非常宽裕。工具调用失败时不直接抛异常而是把错误信息传给模型。这招是从实践中学到的模型经常具备“自我纠错”能力如果它第一次调用工具时参数传错了你把错误信息告诉它它下一次可能就自己修正了。只取第一个 toolCall。虽然接口支持一次返回多个工具调用但初期我特意限制只处理第一个。因为多个工具并行调用会打乱流程顺序导致 Agent 对工具结果的解读混乱。等稳定之后再逐步放开也不迟。3.5 第五步交互界面前端的老本行派上用场Agent 写完了差一个入口。这里我的前端功力派上了用场。我没有做复杂的 Web 管理后台而是接入了企业微信机器人因为售后客服每天已经用企业微信办公不需要再学一个新系统。企业微信机器人接收消息、调用 Agent、把结果返回给用户这段逻辑用 TypeScript 写起来非常简单。核心就是注册一个消息回调router.post(/webhook/wecom, async (req, res) { const { Content: content, FromUserName: userId } req.body; // 把消息交给 Agent 处理 const result await runAgent(systemPrompt, content, tools); // 回复给用户 await sendWecomMessage(userId, result); res.status(200).send(ok); });如果不想接企业微信最简方案是先做成一个命令行工具。当时我先跑通了命令行版本在终端里输入设备编号就能看到 Agent 一步步查询、判断、输出结果调试起来非常直观。命令行版本跑稳定之后才接的企业微信。前端同学在这里有一个隐藏优势你对交互流程的敏感性会让你在产品上线之后更清楚用户客服在哪个环节用得不顺手从而反向推动 Agent 流程的优化。4. 常见问题与排查实录4.1 模型输出不稳定怎么办做 Agent 开发第一个会遇到的问题就是模型输出不稳定。同一个输入有时候正常返回结构化报告有时候突然开始自由发挥输出一大段没用的废话。这个问题在前端开发里几乎不存在——后端接口返回什么就是什么字段结构是约定好的。但大模型不是这样它的输出天然具有概率性永远不能保证格式 100% 一致。我的应对方案是三层防护第一层系统提示词里严格定义输出格式要求“使用结构化列表”并给出示例。第二层在代码里做格式校验解析失败就自动让模型重试一次把这轮生成结果作为错误反馈重新提交。实测下来强制重试一次能把成功率从 80% 拉到 95% 以上。第三层最终落到数据库或发送给用户之前加一个正则提取兜底只截取核心信息字段其余全部丢掉。关于“让模型重试”这个方案前端同学会觉得这不就是“接口重试”吗但实际上有本质区别。接口重试是请求相同的参数而模型重试时会自动根据上一次的失败调整输出格式。这就是我觉得 Agent 开发有意思的地方你在跟一个“有学习能力”的系统协作而不只是调用一个死板的 API。4.2 工具调用参数总是出错第二个高频问题是工具调用的参数错位。大模型理解了要查什么但在生成 JSON 参数的时候把字段名写错了或者把字段类型传错了。比如我定义了 deviceId模型却传了一个 device_id导致查询接口 404。这种情况在开发的第三天集中爆发当时我一度怀疑是不是我的工具定义有问题。后来我找到了两个有效的优化手段在工具参数的描述里尽可能给“范例”。在 description 里写明“设备编号通常是 DEV-xxxx 格式”模型就会倾向于按这个格式传入。参数解析兼容大小写和驼峰-下划线变体。写一个小工具函数在解析模型返回的参数时做一次 key 归一化把 device_id 统一映射为 deviceId。这相当于给模型加了一层“容错”。前端同学对 JSON 序列化和反序列化不陌生但在这里要特别注意 JSON 解析的异常捕获。大模型返回的工具调用参数偶尔会有残缺的 JSON最常见的处理方法是截取大括号内容用 JSON.parse 加 try-catch失败就让模型重发格式化后的参数。4.3 上下文爆炸与成本失控Agent 运行过程中每轮工具调用结果都会累计到上下文里发送给模型这就带来一个前端同学不太容易察觉的问题Token 消耗会线性甚至指数增长。有一次我测试一个复杂工单模型来回调用了 6 次工具等最终生成结果的时候单次请求的输入 Token 已经涨到了接近 3 万换算成成本大约是一次几毛钱。平时测试感觉不出来一旦上线每天处理几百个工单这个成本就相当可观了。我采取的控制手段包括控制工具返回体积能精简就精简我在返回给模型前对每个工具结果做了一次 JSON 压缩去掉不必要的空字段。历史会话压缩当步骤超过三轮就把前面几轮的完整内容压缩成摘要。虽然会丢失部分细节但对最终结论影响不大。设置单次任务 Token 上限超过就直接终止。这既是成本控制也是安全性保障。4.4 前端思维惯性给我挖的几个坑转型过程中最大的障碍其实不是技术而是思维惯性。以下三个坑是我真金白银踩出来的过度设计状态管理。一开始写编排器我用了 Redux 的思路设计了复杂的 store、action、reducer每个 Agent 步骤都派发 action 修改状态。结果发现这个模型根本不符合 Agent 的运行方式。Agent 的核心其实是一个线性循环根本不需要那么复杂的事件驱动架构一个数组存消息就够了。前端同学转入 Agent 开发时要克制住过度设计状态管理的冲动。觉得“直接调用 API”就完事了。前端开发中调用后端 API 是确定性的你传什么参数后端固定返回什么。但 Agent 的工具调用是模型决定的这个“不确定性”需要接受它而不是用代码去消灭它。有一次我试图用一层层的 if-else 去穷举模型的每一种行为写了几百行判断逻辑最后发现自己是在给一个混沌系统写确定性规则纯属徒劳。忽略了可观测性。前端有浏览器控制台、Network 面板、React DevTools你可以随时看到页面发生了什么。但 Agent 是一个服务端运行的异步过程如果不做日志你完全不知道模型在内部调用了什么工具、返回了什么结果。后来我加了一套完整的日志链路记录每一步的输入输出、工具执行耗时、Token 消耗调试才真正有了抓手。5. 给想转型的人几句掏心窝的话项目上线跑了三周日处理工单量稳定在 40-60 个客服人均处理时长从 12 分钟降到了 4 分钟左右这是当初立项时没想到的效果。售后主管现在已经开始主动问我能不能把质检报告生成和配件推荐也做进去团队里的前端同事也来了兴趣问我怎么开始学。我自己的体会是前端转 Agent 开发真正的门坎不在模型知识而在于你敢不敢用自己已经熟悉的技术栈直接去解决一个真实的问题。如果一定要给几条可执行建议先找一个跟你业务贴近的小需求用手写循环跑通最小闭环过程中逼自己把业务规则翻译成提示词这是核心功力最后再回过头看框架文档你会发现里面那些“高级抽象”其实都在解决你踩过的具体问题。这个方向现在还在高速变化但底层的那套工程思维短期之内是不会变的。
返回列表