ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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

说实话,第一次有同事喊我一起做个 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,整体流程可以分为三层:

  1. 路由层:判断用户意图,是退换货、开发票,还是找人工。
  2. 执行层:每个意图对应一个处理单元,调不同的工具、走不同的流程。
  3. 兜底层:前面所有处理单元都搞不定时,转人工客服。

这种分层跟前端组件树的生命周期设计非常像。每个执行单元都有输入、输出、错误处理,可以被单独替换和测试。你不需要从零搭建一个“全能大脑”,你需要的是像搭组件一样搭流程。这也是我不建议一上来就堆复杂编排框架的原因——先把组件树想清楚,比选工具重要得多。

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 第四步:把循环接到前端界面上

后端逻辑跑通之后,前端接入是重头戏。在前端同学看来,这一步反而是最舒服的,因为全都是熟悉的套路:

  1. 流式输出:用 SSE 或 WebSocket 接收模型片段,配合打字机效果展示。
  2. 加载状态:工具调用期间给用户一个“正在查询”的反馈,不能让他们干等。
  3. 可取消:用户点停止时,中断请求并清理循环。
  4. 错误兜底:模型超时、工具报错、返回格式异常时,界面不能崩。

我特别想提醒的一点是:前端界面不要只展示最终回复,要把“中间过程”也暴露出来。我见过用户体验最好的一个内部 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 让我最大的感受是:这活儿本质上是架构活,不是炼丹活。模型的能力已经像水电一样被接好了,真正决定产品好坏的是你如何设计流程、调制工具、管理状态——这些恰好是前端最熟悉的领域。别被“训练模型”四个字吓退,也别浪费时间先啃一堆数学课。直接挑一个最小的场景,手写一个循环,让模型调通第一个工具,你会发现自己的前端功底在这里比想象中值钱得多。等这个循环跑顺了,再回头看那些复杂框架和最佳实践,全都是水到渠成的事。

返回列表