资讯动态

前端工程师转Agent开发:不训练模型,只编排工具与上下文

发布时间:2026/10/22 11:58:16 来源:尧图企业网站定制
说实话第一次有同事喊我一起做个 Agent 的时候我的第一反应是我又不会训练模型找错人了吧。这种反应我在不少前端同行身上都见过——大家被“Agent”这个词吓住以为这是算法岗的专属舞台门槛高得离谱。后来真的上手做才发现Agent 开发和模型训练压根是两码事。你把 Agent 看成“一个会自己决定调什么接口的程序”而不是“一个需要喂数据练出来的神经网络”整个认知就顺了。这篇文章就是给前端背景的朋友们做一次认知校正。我会把 Agent 开发到底在做什么、跟模型训练的边界在哪、前端经验能怎么复用、从 0 到 1 怎么动手一层层拆开讲。不管你是刚听说 Agent 这个词还是已经看过一堆资料但被“训练”“微调”“强化学习”这些词劝退这篇都能让你少走弯路。1. 误会的源头Agent 和“训练模型”到底哪里长得像1.1 信息茧房里被放大的“炼丹”声音打开社交平台聊 AI 的帖子十有八九在讨论损失函数、训练技巧、GPU 集群。算法工程师晒 loss 曲线、聊数据清洗前端工程师看着这些内容很容易产生一个错觉所有 AI 相关的工作都建立在“训练模型”的基础上不会训练等于做不了 AI。这个印象是被信息流放大的。真正在社交媒体上发声的往往是算法岗的大神因为他们的工作成果就是模型本身天然适合拿出来讲。而把大模型接进业务、做工具调用、设计交互流程的人分享欲没那么强内容也没那么“性感”。于是你看到的世界里AI 约等于炼丹Agent 约等于炼丹的进阶版。实际上Agent 开发在绝大多数公司里属于应用工程归后端或者前端团队管不是算法团队管。一个 Agent 产品能不能落地决定因素往往不是模型多聪明而是工具调用稳不稳、上下文管理好不好、用户等不等得起——这些全是工程问题。1.2 训练、微调、Agent 开发三个完全不同的工种把三件事放一起对比边界一下就清楚了工作类型核心做什么依赖什么谁来做预训练让模型学会语言规律和常识海量语料、大规模算力算法工程师微调 / 对齐改变模型的回答风格或特定能力标注数据、人工反馈算法工程师Agent 开发让模型会“用工具”、按流程完成任务工具接口、提示词、系统架构应用工程师前后端预训练和微调是“造人”的过程Agent 开发是“给人安排工作”的过程。前端开发者干的是第三种你不需要知道一个人大脑里的神经元怎么长的只需要知道怎么把任务拆给他、告诉他工具有哪些、验收他交回来的活。我见过 A 同学纯前端背景第一次做 Agent 的时候怕得要死觉得必须先去补一个月的机器学习课。结果上手两周做出来的东西已经在内部工具里跑起来了。他补的那些数学知识一个都没用上。1.3 什么时候你才真的需要碰训练也不是说应用开发永远不用碰训练。判断标准很简单当你把提示词调出花来、把工具做得足够好模型还是搞不定任务时才需要考虑换更强的模型或者微调。举个例子。你要做一个客服 Agent需要它能准确区分“退款”和“退货”两个流程。如果模型经常混淆你先改提示词把流程边界写清楚还不行就给它一个分类工具让它先调工具做意图识别再不济换一个能力更强的模型。走到最后一步仍然不满意才轮到微调——给一批带标注的对话样本让模型学会你的业务语境。所以对前端来说90% 的场景根本到不了训练那一步。你真正需要练的是描述任务、声明工具、处理模型返回结果的能力。这些能力有一个专门的词叫提示词工程它本质上是文字版的接口设计。2. Agent 开发真正的活儿编排、工具和上下文2.1 一次 Agent 请求的本质是什么把 Agent 的“思考过程”拆开看它就是一个循环接收输入 → 拼装消息 → 调用模型 → 看模型要什么 → 执行工具 → 把结果带回 → 再次调用模型 → 直到模型给出最终答案。这个循环在业界叫 Agent Loop听着高深其实就是前端再熟悉不过的事件循环。你写过一个while循环处理用户点击就能理解 Agent 在循环里处理模型请求。区别只在于循环的“事件源”变成了模型输出。以 TypeScript 伪代码为例最朴素的 Agent 循环长这样async function runAgent(userInput: string) { // 1. 组装本次请求的消息列表 const messages [ { role: system, content: SYSTEM_PROMPT }, { role: user, content: userInput }, ]; // 2. 进入循环最多允许模型调用若干次工具 for (let i 0; i MAX_ROUNDS; i) { const response await chatModel(messages); // 3. 模型想调用工具 if (response.toolCalls?.length) { for (const call of response.toolCalls) { const result await executeTool(call.name, call.arguments); messages.push({ role: tool, toolCallId: call.id, content: JSON.stringify(result), }); } continue; } // 4. 模型给出最终回复跳出循环 return response.content; } }这个伪代码去掉网络请求细节跟你在前端写的setInterval复杂度差不多。模型的每次返回都带着结构化指令要么是给你一段自然语言要么是一组“请调用某某工具参数是什么”的请求。你的代码要做的就是读懂这个请求并执行。2.2 工具调用给模型的一本“接口文档”模型本身不会联网、不会查数据库、不会读用户文件它只能生成文字。Agent 开发中最核心的一步就是通过函数声明Function Calling告诉模型“你有这几个函数可以用签名如下需要的时候自己调。”这个机制对前端来说太好理解了。你平时调用后端接口要先看接口文档知道路径、参数、返回结构。Function Calling 就是给模型一份同样的接口文档只不过由你来写const tools [ { name: get_user_order, description: 根据订单号查询用户的订单详情, parameters: { type: object, properties: { orderId: { type: string, description: 订单号 }, }, required: [orderId], }, }, // 可以继续声明更多工具 ];模型看到这份 JSON Schema 之后如果用户问“我上周买的杯子发货了吗”它不会自己去猜而是会输出一个结构化调用指令大概长这样{ toolCall: { name: get_user_order, arguments: { orderId: PO20240501 } } }你的代码只需要执行这个调用把真实订单数据填回消息里再交给模型它就能根据真实数据生成回答。整个过程里你写的是“接口描述”和“调用执行”跟写前端请求封装没什么本质区别。2.3 上下文管理比前端状态管理更“吃紧”模型没有持久记忆。你这次调它它不知道上次聊过什么。所谓“多轮对话”不过是你把历史消息一次次带上去而已。这就像前端页面刷新之后状态丢失你得从 URL、localStorage 里把状态捞回来。但这个状态管理比前端更苛刻因为模型有上下文窗口限制。窗口是有限的比如常见的几十万 token看着很大但塞进去一个几十页的文档再累积多轮对话很快就会触顶。你不可能像 localStorage 一样无限存。常规做法是把历史消息做摘要。用户聊了二十轮之后你不再把完整二十轮对话发给模型而是先用模型生成一段摘要“用户已咨询退货流程当前卡在填地址步骤”再把摘要和新消息一起发过去。这个过程叫上下文压缩本质上是前端熟悉的“缓存淘汰策略”只是策略由模型来执行。还有一种做法是检索增强RAG把文档切成小块按相关性筛出最相关的几块拼进上下文。你可以把它理解成前端做搜索时的“分页加载”只加载用户最需要的那一页而不是把整个数据库拖到页面上。2.4 工作流编排把 Agent 当组件树来设计稍微复杂一点的 Agent 产品不会只有单一循环。常会有多个子任务、多个决策节点甚至多个子 Agent 协作。写代码的时候我建议你把每个子任务当成一个前端组件来设计。比如做一个“智能客服”Agent整体流程可以分为三层路由层判断用户意图是退换货、开发票还是找人工。执行层每个意图对应一个处理单元调不同的工具、走不同的流程。兜底层前面所有处理单元都搞不定时转人工客服。这种分层跟前端组件树的生命周期设计非常像。每个执行单元都有输入、输出、错误处理可以被单独替换和测试。你不需要从零搭建一个“全能大脑”你需要的是像搭组件一样搭流程。这也是我不建议一上来就堆复杂编排框架的原因——先把组件树想清楚比选工具重要得多。3. 前端的“老手艺”恰好是 Agent 开发的加分项3.1 状态管理能力直接迁移到记忆管理用 React 写过复杂应用的人一定被状态管理折磨过。什么状态放全局、什么状态放局部、什么时候要同步、什么时候可以丢弃这套经验迁移到 Agent 上下文管理上几乎无缝。你完全可以这样想短期记忆 组件局部 state只存活在当前对话回合。长期记忆 全局 store跨会话保存用户偏好、历史摘要。上下文压缩 状态归一化把冗余信息折叠成精简结构。我第一次给 Agent 做记忆系统的时候直接画了一张 React 状态管理图哪些数据放哪一层、什么时候持久化标得清清楚楚。之后照着图写代码一次就通过了。前端做了几年的人其实早就积累了处理“有限状态空间”的直觉只是没人告诉你这玩意儿在 Agent 里同样值钱。3.2 异步、流式、竞态前端早就习惯了前端是处理异步最频繁的工种之一。接口请求、定时器、用户事件、取消请求、竞态处理每一件都是日常。Agent 开发中这些问题一个不少而且更严重。模型响应慢动辄几秒到几十秒你不能让用户盯着空白页面。这时候需要流式输出SSE 或 WebSocket边生成边输出文字前端做打字机效果这个你早就会了模型调工具的过程中用户可能点了取消你要中断当前循环这就跟你取消 fetch 请求一样多个请求同时进行返回顺序不确定你需要处理竞态防止旧响应覆盖新状态。这些细节在整个 Agent 项目的代码量里占比不高但决定了体验好不好。前端出身的人处理这些天然比只做过 CRUD 的后端同事更有手感。我的经验是写 Agent 的时候把那套“处理接口竞态”的手感拿出来你会发现自己比想象中从容。3.3 提示词就是另一种“用户体验设计”提示词写得好不好直接决定模型的回答质量。很多人把它当成“求模型办事的话术”其实它就是产品文案、是用户手册、是交互约束。我见过不少后端同事写提示词写得像系统日志一堆感叹号和命令式语句而前端同事写提示词会天然地带上有“边界感”的描述——比如“如果用户没有提供订单号请先询问用户”“不要臆造用户不知道的信息”。这种对边界和引导的敏感就跟你在设计表单校验和报错提示一样。写提示词有几个特别值得前端借鉴的心得把系统提示词当成“设计规范”来写明确角色、语气、限制、回退方案。把少样本示例当成“组件演示”来写每个示例就是你希望模型复刻的交互模式。把模型幻觉当成“空数据兜底”来处理永远给它一条“不知道就说不知道”的退路。我团队里一个做 UI 的同事第一次写提示词就把客服 Agent 的礼貌程度调得比另外几个后端写的版本好一个档次。她说这跟写 UI 文案没区别只不过用户变成了模型。这个说法我一直记得。3.4 JSON 与数据转换Agent 交互的通用语言Agent 开发中模型和工具之间传数据全靠 JSON。工具声明的参数是 JSON Schema模型返回的工具调用是 JSON 对象工具执行结果也要序列化成 JSON 再回填给模型。这种“数据转换”的活儿对前端来说太熟悉了。平时你要把后端返回的字段名order_id映射成前端的orderId把数组换成树结构把时间字符串换成 Date 对象。Agent 开发里你做的是同样的事只不过目标是让模型能看懂。有一个特别容易踩的坑是工具返回的结果格式混乱。有的接口返回的是嵌套了好几层的数据模型一旦解析不了就会开始瞎编。我的经验是给模型看的工具返回结果宁可牺牲信息量也要保证结构简洁。你可以自己写一个适配层把接口原始返回“拍平”成一个简单的 JSON{ status: 已发货, tracking: { company: 顺丰, number: SF1234567890, latestEvent: 已到杭州转运中心 } }这一步做完模型基本不会再出错。学会做这种“面向模型的数据整形”比学任何高阶 prompt 技巧都管用。4. 前端友好的 Agent 入门实操四步走出第一个“工具型 Agent”4.1 第一步设计一个最小场景别一上来就做一个“全能助理”你会被各种边界问题淹没。选一个能体现工具调用的最小场景比如“订单查询助手”。需求定义成这样用户提供订单号。Agent 调用订单查询工具获取订单状态和物流信息。Agent 用口语化的方式把信息回复给用户。这个范围够小但完整覆盖了 Agent 开发的核心链路解析用户意图、提取参数、调用工具、回填结果、生成回答。跑通它你就理解了 80% 的 Agent 开发流程。4.2 第二步写一个最朴素的 Agent 循环我不建议第一个项目就引入编排框架。先用裸的模型接口把主循环写通感受一下模型输出、工具调用、消息回填的节拍。下面是一个可以跑的 TypeScript 骨架import { chatModel } from ./llm-client; import { orderTools } from ./tools/order; const SYSTEM_PROMPT 你是订单查询助手。 如果用户没有提供订单号请先向用户索要。 拿到订单号后调用 query_order_detail 工具查询。; export async function handleOrderQuery(userMessage: string, history: Message[]) { const messages: Message[] [ { role: system, content: SYSTEM_PROMPT }, ...history, { role: user, content: userMessage }, ]; for (let step 0; step 5; step) { const response await chatModel({ messages, tools: Object.values(orderTools) }); if (response.toolCalls?.length 0) { for (const call of response.toolCalls) { const result await executeTool(call.name, call.arguments); messages.push({ role: tool, toolCallId: call.id, content: JSON.stringify(result), }); } continue; } return response.content; } return 抱歉处理本次请求的步骤太多请重新描述你的问题。; }这个循环里有三个关键点值得注意tools参数把工具声明传给模型模型只会在这些工具里选择。tool角色的消息必须带上toolCallId这是把工具结果和某次具体调用关联起来的关键。MAX_ROUNDS一定要设上限否则模型可能陷入调工具的死循环白白浪费 token。4.3 第三步为模型写好工具声明写工具声明的时候脑子里要时刻想着“模型是在读文档”。描述要具体参数要清晰每个字段的用途要说人话。拿“查订单”这个工具举例我一般这样写const queryOrderDetail { name: query_order_detail, description: 根据订单号查询订单当前状态、物流公司、物流单号以及最新物流进度, parameters: { type: object, properties: { orderId: { type: string, description: 用户提供的订单号例如 PO20240501, }, }, required: [orderId], }, };这里最容易犯的错是把 description 写太短。模型不是人它不会凭直觉猜“orderId”的意思。写清楚“例如”部分模型提取参数的准确率会明显提升。你可以试试把两版描述各跑十次对比参数提取成功率数据会说明一切。4.4 第四步把循环接到前端界面上后端逻辑跑通之后前端接入是重头戏。在前端同学看来这一步反而是最舒服的因为全都是熟悉的套路流式输出用 SSE 或 WebSocket 接收模型片段配合打字机效果展示。加载状态工具调用期间给用户一个“正在查询”的反馈不能让他们干等。可取消用户点停止时中断请求并清理循环。错误兜底模型超时、工具报错、返回格式异常时界面不能崩。我特别想提醒的一点是前端界面不要只展示最终回复要把“中间过程”也暴露出来。我见过用户体验最好的一个内部 Agent 工具界面上会实时显示当前进度“正在分析订单号 → 正在查询物流 → 正在生成回复”。用户有了进度感等待时间就变得可以忍受。要做到这一步后端需要把 Agent 循环中的每一步状态推送到前端。实现方式很简单定义一个事件通道循环每走一步就往通道里塞一条进度消息前端订阅并渲染。// 后端推送中间状态 const events new EventEmitter(); events.emit(agent:step, { type: tool_calling, tool: query_order_detail }); // 前端监听状态变更 eventSource.addEventListener(agent:step, (event) { const step JSON.parse(event.data); renderProgress(step); });4.5 日志与回溯把 Agent“想什么”暴露出来调试 Agent 跟前端调试最大的不同是你没法打断点看数据流。模型每次决策都是黑箱出错了你只能猜。所以我在开发时一定会做一件事——把每个回合的完整消息历史落日志。开发阶段的日志我要求记录下来这些字段每次请求发送给模型的完整消息列表。模型返回的原始内容包括 toolCalls 里参数的前后变化。每次工具执行耗时和返回结果。整个循环一共跑了几轮、每轮消耗了多少 token。有一次线上客服 Agent 突然胡言乱语我翻日志发现它在第三轮循环里调用了一个已经废弃的工具。原因是测试环境里的工具列表配置没更新。这种问题不把中间过程打到日志里根本不可能定位。所以从第一天起就养好这个习惯别等出事故了再补。5. 实战三周后的避坑清单与策略取舍5.1 小场景先用手写循环别上重框架很多人一上来就玩编排框架结果配置还没配明白就被抽象的“节点”“边”“状态机”搞懵了。框架解决的是复杂场景的问题前提是你已经理解了简单场景的工作原理。我的建议是第一个项目完全手写。裸模型接口加一个 for 循环稳定跑通了再去研究框架。这时候你看框架的文档每个抽象都能对上号理解速度至少快一倍。反过来如果你先学框架再回来补基础你会很难判断哪些问题是框架引起的、哪些是你自己的逻辑写错了。5.2 提示词要版本化像改代码一样做回归我见过团队里改提示词全凭感觉谁觉得回答不对劲就随手改两句改完上线效果好就留下不好就再改回去。这是把提示词当“玄学”在调后期必然失控。提示词应该像代码一样管理。我一般会把每个 Agent 的系统提示词抽出来单独建文件纳入代码仓库。每次修改都记录版本和变更原因上线之前用固定的一套测试用例跑一遍比对输出是否满足预期。这套机制其实就是前端的组件测试思想。只要有过一次因为改提示词把业务流程改坏的经历你就会跟我一样老老实实建一个提示词回归用例集。测试用例不用多覆盖每个工具调用的正确路径和错误路径就够了。5.3 上下文不是越大越好收着点用有一种直觉是“既然模型上下文窗口很大我多塞点资料总没错”。这个想法在实践里很害人。上下文塞太满会出现几个问题模型注意力被无关信息稀释回答变得模棱两可。响应时间变长token 费用变高。越塞越接近窗口上限后面的对话可能直接被截断。正确的做法是像做性能优化一样去审视上下文每条消息有没有必要历史摘要能不能再压缩文档是不是只保留用户当前关心的片段我常跟团队说一句话上下文就像移动端的内存看着很多但真正可用的永远比你想的少。5.4 给 Agent 设计“认怂”机制模型一定会遇到它处理不了的情况。比如用户问了一个工具覆盖范围之外的问题或者连续几轮都提取不到有效参数。这时候最怕的是模型硬答编造一个不存在的订单状态。所以一定要在系统提示词里写清楚退路同时在代码里做兜底。我的常规做法是如果在有限轮次内没法完成任务就明确告诉用户“我暂时无法处理正在为你转接人工”而不是让模型自由发挥。这个“认怂”机制在提示词层面、代码层面各写一遍双保险。别觉得多此一举生产环境里模型编出一个不存在的物流信息比“我不会”严重得多用户投诉率不是一个量级。5.5 延迟与成本前端体验的隐形技术债Agent 工具链跑起来你会痛苦地发现一次用户请求可能内部调用模型三四次每次都要等好几秒。前端等得起用户等不起。等用户体验不好的时候你再回头优化代价就大了。所以前期设计就要为延迟和成本做规划。几个思路供参考能一步完成的不要绕多轮提示词写清楚“如果参数齐全直接查”减少无谓的来回。小任务用小模型判断意图这种简单分类用响应更快的轻量模型把昂贵的大模型留给最终生成答案。结果缓存同样的订单号、同样的问题短期内直接读缓存不用重新跑循环。我在第一个项目上线后一周发现 token 消耗比预估高了 40%。复盘原因就是模型经常“多此一举”明明用户已经给了订单号它还要先确认一遍再查。我在提示词里加了一句“不要重复确认用户已提供的信息”消耗立刻降了下来。这种优化靠的不是代码而是对模型行为习惯的观察。写在最后的经验之谈做 Agent 让我最大的感受是这活儿本质上是架构活不是炼丹活。模型的能力已经像水电一样被接好了真正决定产品好坏的是你如何设计流程、调制工具、管理状态——这些恰好是前端最熟悉的领域。别被“训练模型”四个字吓退也别浪费时间先啃一堆数学课。直接挑一个最小的场景手写一个循环让模型调通第一个工具你会发现自己的前端功底在这里比想象中值钱得多。等这个循环跑顺了再回头看那些复杂框架和最佳实践全都是水到渠成的事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑