
1. 从“调模型”到“搭系统”前端视角下的Agent开发认知纠偏很多前端同行第一次听到“Agent开发”这个词脑子里蹦出来的第一反应往往是这是不是又要搞模型训练是不是得先学会PyTorch再啃几篇Transformer的论文最好还能自己微调一个模型出来我当初也是这么想的甚至一度觉得这玩意儿离自己挺远——毕竟前端日常打交道的是DOM、组件生命周期、状态管理跟梯度下降、损失函数八竿子打不着。但真正上手做了几个Agent项目之后我发现这个认知偏差大得离谱。Agent开发的核心根本不是模型训练而是系统编排。模型是别人训好的你拿过来用就行就像你写前端不会自己去实现一个浏览器内核一样。你要做的事情是把模型当成一个能力很强的“外部服务”然后围绕它设计一套能稳定运转的流程。这个认知转变非常关键。一旦你把它理解成“系统编排”前端工程师的很多既有技能就立刻变得有用了状态管理、异步流程控制、错误处理、接口设计、用户体验——这些全是前端每天都在干的事情。你不需要成为算法工程师你需要的是一个“AI应用架构师”的思维。这篇文章就是写给那些对Agent开发感兴趣、但被“模型训练”四个字吓住的前端同行。我会把Agent开发拆成几个核心模块讲清楚每个模块到底在干什么、为什么这么设计、前端背景的人怎么快速上手。全文不会涉及任何模型训练的技术细节因为那根本不是入门阶段该关心的事。提示如果你现在还在纠结“要不要先学深度学习”我的建议是先放下这个念头。Agent开发的门槛在工程侧不在算法侧。先把系统跑起来再回头补理论效率高得多。2. Agent的本质一个带工具调用能力的循环系统2.1 为什么说Agent不是“更聪明的聊天机器人”很多人对Agent的理解停留在“能聊天的AI”这个理解会导致你在设计系统时走偏。聊天机器人的核心是“一问一答”而Agent的核心是“目标驱动的循环”。这两者的区别就像“计算器”和“会自己按计算器的助手”之间的区别。一个典型的Agent运行逻辑是这样的用户给一个目标Agent先思考需要做什么然后选择一个工具去执行拿到执行结果后再思考下一步如此循环直到目标完成或者达到终止条件。这个“思考-行动-观察”的循环才是Agent的灵魂。我用一个前端熟悉的类比来解释聊天机器人像是一个纯函数输入A输出B没有副作用而Agent像是一个带有状态机的异步任务队列每一步的输出都会影响下一步的输入而且中间会调用各种外部API。你写过的那些带轮询、带重试、带状态流转的前端逻辑本质上就是Agent循环的简化版。2.2 循环系统的三个核心组件拆开来看一个最小可用的Agent循环需要三个东西第一是决策器。它负责根据当前状态决定下一步做什么。这个决策器通常就是大语言模型你给它一段提示词它输出一个结构化的决策结果比如“调用搜索工具参数是XXX”。注意这里模型只负责“决策”不负责“执行”。第二是工具集。工具就是Agent能调用的外部能力可以是一个HTTP接口、一个数据库查询、一个文件读写操作甚至是一个前端页面上的按钮点击。工具的定义要清晰输入什么、输出什么、什么情况下用。这部分工作对前端来说非常亲切本质上就是设计API。第三是状态管理器。它记录Agent执行过程中的所有信息用户目标、历史步骤、每步的观察结果、当前进度。状态管理做得好不好直接决定Agent能不能处理复杂任务。前端的状态管理经验在这里能直接迁移过来。2.3 一个容易踩的坑把决策和执行混在一起我见过不少初学者写的Agent把“决定调用哪个工具”和“实际调用工具”写在同一个函数里模型输出什么就直接执行什么。这种写法在Demo阶段没问题但一旦任务变复杂就会失控。正确的做法是把决策和执行彻底分离。模型只输出一个结构化的决策对象比如{ action: search, params: { query: 前端性能优化方案 }, reasoning: 需要先获取相关资料 }然后由你的代码去解析这个对象校验参数合法性再决定是否真的执行。这样做的好处是你可以在执行前加校验、加日志、加人工确认也可以在模型输出格式错误时优雅降级。这个思路跟前端处理用户输入是一样的——永远不要信任外部输入哪怕这个输入来自模型。3. 工具调用前端工程师最容易上手的部分3.1 工具定义的本质就是接口设计如果说Agent开发有什么部分是前端能无缝衔接的那一定是工具调用。工具的定义、注册、调用、错误处理这一整套流程跟前端设计SDK接口几乎一模一样。一个工具的定义通常包含这几个要素名称、描述、参数schema、执行函数。名称和描述是给模型看的模型根据这些信息判断什么时候该用这个工具。参数schema用来约束输入格式执行函数就是实际干活的代码。我习惯用这样的结构来定义一个工具const searchTool { name: web_search, description: 根据关键词搜索网页信息返回摘要列表, parameters: { type: object, properties: { query: { type: string, description: 搜索关键词 }, limit: { type: number, description: 返回结果数量默认5 } }, required: [query] }, execute: async ({ query, limit 5 }) { // 实际调用搜索接口 const results await searchApi(query, limit); return results.map(r ({ title: r.title, snippet: r.snippet })); } };这个结构是不是很眼熟跟你在前端封装一个请求函数没有本质区别。唯一的区别是你需要额外写一段“给模型看的描述”让模型知道这个工具是干什么的。3.2 描述写得好不好直接决定Agent聪不聪明这里有一个非常关键的细节工具描述的质量比工具本身的实现更重要。模型只能通过描述来理解工具的用途如果描述写得含糊模型就会用错工具或者该用的时候不用。我踩过的一个坑是早期我写了一个“获取用户信息”的工具描述只写了“获取用户信息”。结果模型在需要查订单的时候也调这个工具因为它觉得“用户信息”可能包含订单。后来我把描述改成“根据用户ID查询用户的基本资料包括昵称、注册时间、会员等级不包含订单和交易记录”模型的使用准确率立刻上去了。写工具描述有几个经验说清楚“做什么”和“不做什么”边界要明确参数说明要具体最好给示例值如果有使用前提一定要写出来比如“需要先获取用户ID”描述长度控制在两三句话太长了模型反而抓不住重点3.3 工具调用的错误处理策略工具调用一定会出错网络超时、参数错误、接口返回异常这些都是常态。前端处理接口错误的经验在这里完全适用但有一点需要注意错误信息要反馈给模型而不是直接抛给用户。举个例子如果搜索工具返回了“请求超时”你不应该直接告诉用户“搜索失败了”而应该把错误信息作为观察结果传回给模型让模型决定是重试、换工具、还是告诉用户暂时无法完成。这个设计思路叫“错误即观察”是Agent鲁棒性的关键。我通常会把工具执行结果统一成这样的格式{ success: true/false, data: {...}, // 成功时的数据 error: ..., // 失败时的原因 suggestion: ... // 可选的修复建议 }模型看到success为false时会结合error和suggestion来决定下一步。实测下来这种设计能让Agent在遇到偶发错误时自动恢复而不是直接卡死。4. 提示词工程不是写作文是设计协议4.1 把提示词当成API契约来写前端工程师对“契约”这个概念应该很熟悉前后端约定好接口格式各自按约定实现。Agent的提示词本质上就是你和模型之间的契约——你告诉模型输入是什么格式、输出是什么格式、有哪些约束条件。很多人写提示词像写作文堆一大堆形容词结果模型输出不稳定。正确的做法是像写接口文档一样写提示词结构化、无歧义、有示例。一个我常用的提示词骨架是这样的你是一个任务执行助手。你的目标是帮助用户完成指定任务。 你可以使用以下工具 {tools_description} 请严格按照以下JSON格式输出你的决策 { thought: 你的思考过程, action: 工具名称或finish, action_input: {参数对象} } 约束条件 1. 每次只能调用一个工具 2. 如果任务已完成action设为finish 3. 参数必须符合工具定义的schema这种结构化提示词的好处是输出格式稳定解析起来不容易出错。前端处理JSON解析的容错逻辑在这里能直接复用。4.2 少样本示例比长篇说明更有效如果你发现模型总是输出格式不对最有效的解决办法不是加更多说明文字而是给两三个示例。模型对示例的敏感度远高于对规则描述的敏感度。我一般会准备三类示例一个正常调用的、一个任务完成的、一个参数复杂的。这三类覆盖了大部分场景模型看了之后输出稳定性会明显提升。注意示例要真实可信不要编造不存在的工具名或参数。示例里的工具必须和实际注册的工具一致否则模型会混淆。4.3 提示词版本管理别把提示词硬编码在代码里这是一个很容易被忽视的工程问题。初期大家习惯把提示词直接写在代码里改一次就要重新部署。但提示词是需要反复迭代的硬编码会让迭代效率极低。我的做法是把提示词抽成独立的配置文件或者模板文件代码里只负责读取和填充变量。更进一步可以给提示词加版本号记录每次修改的效果对比。这个思路跟前端做AB测试是一样的——提示词就是你的实验变量。5. 状态管理与上下文控制Agent的记忆问题5.1 为什么Agent需要“记忆”Agent执行任务往往需要多轮循环每一轮的决策都依赖之前的结果。如果模型没有记忆它就会重复调用同一个工具或者忘记已经获取的信息。所以你需要把历史步骤整理成上下文每次调用模型时一起传进去。但这里有个矛盾模型的上下文长度是有限的任务步骤多了之后历史信息会超出限制。这就引出了Agent开发中最核心的工程问题之一上下文管理。5.2 三种上下文压缩策略我实践下来比较有效的策略有三种第一种是滑动窗口。只保留最近N步的历史更早的丢弃。简单粗暴但会丢失早期的重要信息。适合步骤少、每步信息量小的任务。第二种是摘要压缩。把早期步骤交给模型总结成一段简短摘要只保留摘要和最近几步的详细记录。这种方式能保留关键信息但摘要本身可能丢失细节。第三种是结构化记忆。把历史信息按类型拆开存储比如“已获取的事实”“已执行的行动”“待完成的目标”每次只把相关的部分放进上下文。这种方式最复杂但效果最好。实际项目中我通常是组合使用近期步骤用滑动窗口保留原文远期步骤用摘要压缩关键事实用结构化存储单独维护。5.3 前端状态管理经验的直接迁移如果你用过Redux或者Pinia你会发现Agent的状态管理思路跟它们高度相似都是维护一个中心化的状态树每次操作产生一个新的状态快照UI或者模型根据当前状态渲染或者决策。区别在于前端的状态变更通常是同步的、可预测的而Agent的状态变更依赖模型输出带有不确定性。所以你需要额外的校验层确保模型输出的状态变更符合预期。这个校验层的设计可以参考前端表单校验的思路定义schema、逐字段校验、给出明确的错误提示。6. 从Demo到可用那些只有踩过才知道的坑6.1 循环终止条件别让Agent陷入死循环Agent最危险的情况是无限循环模型一直调用工具但永远不认为任务完成。我遇到过最夸张的一次Agent连续调用了二十多次搜索工具每次都说“还需要更多信息”。解决办法是设置硬性终止条件最大循环次数、最大执行时间、连续无进展检测。其中“连续无进展检测”最有用——如果连续三步的观察结果高度相似就强制终止并返回当前结果。另外提示词里要明确告诉模型“如果信息足够就尽快结束”并且给出判断标准。模型有时候会过度谨慎你需要引导它果断收尾。6.2 工具返回结果太长怎么办有些工具返回的数据量很大比如一个搜索接口返回了二十条结果每条都有几百字。如果全部塞进上下文很快就会撑爆。我的处理方式是在工具执行层做预处理只返回最相关的几条并且截断过长的文本。比如搜索结果只返回前三条的标题和摘要摘要超过两百字就截断。这个预处理逻辑放在工具内部模型不需要知道原始数据有多长。这个思路跟前端做列表虚拟滚动是一样的——数据可以很多但渲染出来的只有可视区域那部分。6.3 模型输出格式不稳定解析失败的兜底方案不管你提示词写得多好模型偶尔还是会输出格式错误的内容。这时候不能直接崩溃要有兜底方案。我的做法是分三层处理第一层用正则提取JSON部分第二层用JSON.parse尝试解析第三层如果还失败就调用一次模型让它重新格式化。三层都失败才报错。实测下来第一层就能解决大部分问题因为模型通常只是多输出了一些前后缀文字。6.4 成本控制Token是要花钱的Agent每循环一次就要调用一次模型Token消耗是普通对话的好几倍。如果不加控制一个复杂任务可能烧掉大量Token。控制成本的手段有几个精简提示词、压缩上下文、限制循环次数、对简单任务用更小的模型。其中“对简单任务用更小的模型”效果最明显——决策步骤用大模型格式化、摘要这类任务用小模型就够了。我在项目里会做一个简单的路由根据任务复杂度选择模型。判断复杂度的依据包括历史步骤数、工具数量、用户输入的模糊程度。这个路由逻辑本身不需要模型用规则判断就行。7. 前端做Agent开发的独特优势与需要补的课7.1 优势工程化思维和用户体验敏感度前端工程师做Agent开发有两个天然优势。第一是工程化思维你知道怎么组织代码、怎么管理状态、怎么处理异步、怎么做错误边界。这些在Agent开发里全是核心技能。第二是用户体验敏感度你知道怎么给用户反馈进度、怎么展示中间状态、怎么设计交互流程。Agent执行任务往往需要几秒到几十秒这期间的体验设计非常关键。我见过一些算法背景的人做的Agent功能没问题但用起来很难受——没有进度提示、错误信息直接抛给用户、中间状态完全黑盒。这些问题前端一眼就能看出来也最知道怎么解决。7.2 需要补的课理解模型的“脾气”前端需要补的课主要是对模型行为的理解。模型不是确定性函数同样的输入可能给出不同的输出模型会有幻觉会编造不存在的信息模型对提示词的措辞很敏感换个说法结果可能完全不同。这些特性意味着你不能用写传统代码的思维来写Agent。传统代码里if-else是确定的Agent里模型的决策是不确定的。你需要接受这种不确定性并通过工程手段去约束它、引导它、兜底它。我的建议是花几天时间专门做实验同一个提示词改几个字看输出怎么变同一个任务跑十次看结果稳不稳定。这种“摸脾气”的过程是必须的没有捷径。7.3 一个实用的学习路径如果你是一个前端想入门Agent开发我建议的路径是这样的第一步先用现成的API跑通一个最简单的循环不需要任何框架就是手写一个while循环调用模型、解析输出、执行工具、把结果塞回上下文。这一步的目的是理解Agent的运行机制。第二步把工具调用封装成标准接口加上参数校验和错误处理。这一步的目的是建立工程规范。第三步处理上下文管理实现滑动窗口或者摘要压缩。这一步的目的是让Agent能处理稍复杂的任务。第四步加上日志和可观测性记录每一步的输入输出。这一步的目的是方便调试和优化。第五步才是考虑用框架。LangChain这类框架能帮你省一些事但也会带来抽象泄漏的问题。我建议先手写一遍理解每个环节在干什么再用框架。8. 关于Agent开发我踩过之后才明白的几件事第一个体会是Agent的瓶颈往往不在模型能力而在工程细节。模型再聪明如果工具描述写得烂、上下文管理做得差、错误处理不到位Agent照样跑不起来。反过来一个中等能力的模型配上扎实的工程能做出相当可用的东西。第二个体会是不要追求一步到位。我一开始总想设计一个能处理所有情况的通用Agent结果越做越复杂最后什么都做不好。后来改成针对具体场景做专用Agent每个Agent只解决一类问题反而效果好得多。这跟前端做组件是一样的——先做专用组件有共性了再抽象。第三个体会是可观测性比什么都重要。Agent执行过程是个黑盒如果不记录每一步的输入输出出了问题根本没法排查。我现在每个Agent项目第一件事就是加日志把每次模型调用、每次工具执行、每次状态变更都记下来。这个习惯帮我省了无数调试时间。第四个体会是提示词是需要持续迭代的资产。不要指望一次写好要建立快速迭代的机制。我现在的做法是把提示词放在独立文件里改完立刻能测测完记录效果。积累下来这些提示词模板就是项目最值钱的部分。第五个体会是前端做Agent有天然优势但别被优势限制住。工程化思维是优势但也要主动去理解模型的行为特性。两者结合才能做出真正好用的Agent产品。