前端转AI Agent实战:从零构建迷你Cursor
发布时间:2026/10/8 4:41:52 作者:尧图编辑部 阅读量:1,286

最近总有前端朋友问我同一个问题想转 AI Agent从哪里下手我给的答案一直很直接别急着去啃 LangChain、别一上来就看 Agent 平台的白皮书先尝试从零造一个迷你版 Cursor。我说这话不是敷衍而是自己把这条路走过一遍之后的真实感受。Cursor 看着是个高端 AI 编程工具拆开之后其实就是编辑器 对话面板 上下文管道三件事而这三件事恰好全是前端工程师的日常。你每天打交道的数据结构、组件状态、事件监听、UI 渲染在 Cursor 里全部原样出现只是换了个场景。这篇文章从头到尾只做一件事带你把一个能看懂你代码、能跟你对话、能动手改文件的最小 AI Agent 造出来。造完之后你再回头看 Cursor 的免费额度怎么用、中文怎么设置、Token 为什么烧得那么快心里都会清晰很多。适合的人群是有一定 React/Vue 基础、想转 AI 产品或 Agent 方向的前端开发以及想搞懂 AI 编程工具底层逻辑的爱好者。1. 前端转 AI Agent 的捷径先造一个迷你 Cursor前端转 AI Agent最常见的学习误区是先去背概念。Agent、Function Calling、RAG、Workflow……名词背了一大堆真到写代码的时候还是不知道怎么跟大模型配合。原因很简单Agent 不是一个单一技术而是一套模型 工具 上下文的组合工程。想理解这套组合最好的方式不是看书而是亲手做一个最小的完整闭环。1.1 Cursor 这个产品去掉包装之后是什么你天天用 Cursor可能没想过它本质上是个什么。它是披着编辑器外衣的聊天软件不对。它是长着聊天界面的 IDE也不全对。我的理解是它是一个把对话和编辑器状态绑定在一起的前端应用核心组件就三个。第一个是编辑器内核。Cursor 用的是 VS Code 同款的 Monaco Editor只是外面包了一层自己的 UI 和交互。第二个是对话面板。它维护了一个消息列表把用户问题、模型回复、代码上下文都渲染成一条条消息。第三个是上下文管道这是最容易被忽略但最关键的模块。它负责在用户点击发送的那一瞬间把当前打开的文件、选中的代码、光标位置、甚至整个项目的文件结构组装成一段模型能读懂的 Prompt。这三个组件的关系很微妙编辑器负责展示对话面板负责交互上下文管道负责把所有信息翻译成大模型的输入格式。只要你想造一个类 Cursor 工具逃不开这三层。换句话说你不需要发明任何新东西你只需要把一个编辑器、一个聊天框、一个 Prompt 组装器组合起来。1.2 前端开发者的天然优势我说前端适合转 Agent不是客气话。一个 AI 编程工具本质上是一个对界面交互极其敏感的软件。前端工程师天天干的事就是处理用户选中了什么、鼠标在哪里、状态怎么同步、长列表怎么渲染、异步请求怎么展示 loading 和错误——这些能力放到 Cursor 里全部原样复用。再往细了说Agent 的体验好不好往往不在模型多聪明而在用户能不能看懂模型在干什么。前端对过程可视化的敏感度恰恰是很多后端出身的人欠缺的。你看到一个 Agent 在改文件、在跑命令、在读代码第一反应是它有没有乱动我的东西而不是它用了多好的模型。能把这个过程用 UI 清晰展示出来的就是前端工程师的活。所以我的结论是前端转 Agent 的最大壁垒不在 UI而在上下文工程。模型能力是通用的工具调用协议是标准的真正拉开差距的是你知不知道怎么把代码库变成模型看得懂、用得好的上下文。这个手感造一个迷你 Cursor 能练出一大半。1.3 一个周末能做完的最小功能清单把目标拆小一点。一个周末能做完、又能代表 Agent 核心思路的最小版本包含四个功能就够打开一个代码文件并且能显示在编辑器里选中一段代码右键或快捷键唤起对话面板把当前文件 选中代码 用户问题发给大模型流式显示回复模型返回修改后的代码编辑器里用 Diff 展示用户可以一键接受或丢弃这四条看起来像个玩具但它已经覆盖了 Agent 的完整闭环感知编辑器状态、构建上下文、调用模型、应用变更。做完这四条主流 Agent 架构里的 Context、Model、Tools 三个概念你会天然理解一半。另一半等第 4 章加了 Function Calling 之后也补齐了。2. 拆开骨架编辑器内核、对话面板、上下文管道既然要造 Cursor第一件事不是选模型而是搭骨架。很多新手上来就去找大模型 API结果 UI 还没影呢Token 烧了几百块。我的建议是先把本地 UI 跑通再考虑接模型。骨架选型直接决定后面所有代码长什么样值得花点时间讲清楚。2.1 编辑器内核Monaco 还是 CodeMirror选编辑器内核只有两个正经理由Monaco 和 CodeMirror。Monaco 就是 VS Code 的编辑器本体VSCode 有的高亮、智能提示、光标选区、Diff 编辑器它都有。CodeMirror 是轻量级选手包体积小很多适合做在线文档、表单里的代码块但功能要自己拼装得多一些。我自己做迷你 Cursor 时选的 Monaco理由是省心。你想要一个看起来像 Cursor的产品Monaco 几乎零成本给你提供最大限度的现成能力尤其是选区监听和 Diff 展示这两个功能对 AI 编程工具是刚需。代价是包体比较大首次加载会慢一点但开发期根本不用在意。前端接入 Monaco 非常快。#### 2.2 对话面板的交互模型对话面板是整个产品的操作台交互模型核心只有一个怎么把用户意图跟编辑器状态绑定在一起。我的做法是参考 Cursor 的交互路径用户先选中一段代码然后按快捷键比如 CmdL 或自定义组合键唤起对话浮窗浮窗里自动带上选中的代码上下文用户输入问题后回车发送。这里的重点不在界面多炫而在意图分类。用户看到选中代码之后问的问题通常是三种解释这段代码在干嘛、帮我修 bug、把这段代码改成另一种写法。你如果让模型每次一上来就自由发挥结果经常会答非所问。我的经验是在发请求之前先做一个意图判断不用太智能正则或简单关键词就行如果问题里带解释什么意思讲一下就在 Prompt 里要求模型以说明为主带改重构优化就要求输出完整的新代码带报错bug为什么就要求先分析原因再给修法。这个意图分类看着土效果却非常明显。它能让模型回答的格式稳定下来是后面做工具调用之前最好的一个热身动作。2.3 上下文管道真正的灵魂所在UI 做得再好看Prompt 组装得稀烂这个工具也是废的。上下文管道的职责是在用户点发送的那一刻把所有必要信息变成一个结构清晰的 JSON。我维护的结构大概是这样的openFile当前打开文件的完整内容selection选中的代码片段和起止行号language文件的语言类型TS、Python 等fileTree当前项目的目录结构只列文件名和层级不读内容instruction用户的问题这里有个容易被忽略的细节不是所有信息都要塞给模型。文件内容可能几千行全塞进去既浪费 Token 又干扰模型判断。我的做法是分级处理如果选区存在优先把选区内容完整放入文件全文只摘取光标附近的前后各 50 行如果没有选区就放整个文件的前 200 行剩下的告诉模型文件较长已截取前半部分。这个截断粒度可以根据模型上下文窗口灵活调但思路是一致的只给模型完成当前任务所需的最少上下文。另一个细节是显示什么、发送什么要分开。用户界面里我让用户在对话面板能看到完整上下文方便他确认自己有没有选错代码但真正发给模型的 JSON 是精简过的。前端界面是给人看的Prompt 是给模型看的两者不一致很正常别为了 UI 好看把上下文集装得过大那是给模型增加噪音。3. 从零搭一个纯前端 API 代理的实现路径骨架清楚了可以动手写代码了。我推荐的技术栈是前端纯静态 一个薄薄的 API 代理层。为什么要代理后面专门讲安全时细说现在你只需要知道大模型 API 密钥不能放浏览器里必须有一个后端或边缘函数帮你转发请求。3.1 技术栈与仓库结构我用的组合是 React Vite TypeScript Monaco 大模型 SDK。React 管对话面板状态Vite 做开发服务器Monaco 负责编辑器区域大模型 SDK 在后端代理里负责真正的模型调用。如果你熟悉 Vue完全可以把 React 换成 Vue逻辑不变。仓库建议拆成两个目录哪怕你和我一样是个人在做这个拆分也能让你少踩很多坑web前端项目纯静态页面可部署到任意静态托管平台serverAPI 代理服务暴露 /api/chat 之类的接口内部调用大模型前端不要直接放 model 的 SDK所有对模型的请求都走 server。这样整个系统中只有 server 知道密钥前端永远不知道模型密钥长什么样。这个习惯越早养成越好后面接任何 AI 功能都不会被动。3.2 五步接好编辑器与光标监听接入编辑器本身不难但有一些细节操作直接决定后面的体验。我把完整的接入过程压缩成五个步骤每一部都有坑可避第一步初始化项目并安装依赖。Vite 建项后装monaco-editor/react和monaco-editor两个包就够了。第二步在页面上渲染一个编辑器组件。注意要给编辑器一个能自适应高度的容器大多数初始化失败都发生在容器高度为 0 的情况。第三步通过onMount拿到 editor 实例把它存到 ref 里。这是后续所有交互的入口你要是这一步没做后面想监听光标、选代码、建 diff 都会无从下手。第四步监听光标和选区变化。关键代码是editor.onDidChangeCursorSelection每次用户选中代码、移动光标都会触发回调。回调里拿到当前 model然后用model.getValueInRange(selection)把选中的文本抠出来同步到 React 状态里。第五步把当前打开的文件信息、选中文本、语言类型存成一个全局状态对象。这就是前面说的上下文管道的雏形。这五步跑通之后你已经拥有一个能感知用户正在看什么代码的编辑器应用了。这一步做完项目进度感一下就有了。3.3 让对话流式返回顺手解决中文设置编辑器跑通之后接大模型对话。我强烈建议从一开始就做流式输出不要等模型完整返回再渲染。原因很简单大模型生成几百个字往往要十几秒如果让用户干等体验直接归零。流式输出让用户看到字一个一个蹦出来心理等待时间会大幅缩短这也是为什么 Cursor 回复时像打字机一样的原因。前端的流式处理用一个ReadableStream读取接口返回的分块。后端代理把模型的流式输出通过 HTTP 的 chunked 转发给前端前端用TextDecoder一段一段解出来追加到消息列表的最后一条。这里的要点是不要把整个响应体读完了再渲染而是边读边渲染。顺带说一个很多人问过的Cursor 中文怎么设置问题。这事的官方案在 settings 里找语言选项但如果你自己造工具最核心的其实是 system prompt。你只要在系统提示里写死请使用简体中文回复保留代码部分不做翻译模型就会自然用中文跟你对话。我见过不少人在这上面纠结半天其实原理就是一提 Prompt 的事。3.4 一份可直接复用的上下文 Prompt 模板上下文组装最后落在一条 Prompt 上。我的模板大概是这样的前端代码中将这些拼进 system user 消息你是嵌入在代码编辑器中的 AI 编程助手。请使用简体中文回复。 当前文件路径{path} 文件语言{language} 当前文件内容 {fileContent} 用户选中代码 {selection} 如未选区此处留空 用户的需求是{instruction}别嫌这模板简单重点在于它明确了四个事实你是谁、用户在看哪个文件、用户选了哪段代码、用户想干嘛。大模型拿到这四个事实输出稳定性会显著提升。你以后做任何 AI 工具Prompt 的骨架都可以照这个来——先固定身份再给上下文最后下指令。这种模板逻辑本质跟你给新同事交接工作时说的话一样我是谁我要做啥现在手上有什么材料目标是什么。模型不比你聪明到哪去它也需要一个清晰的上下文背景板。4. 从问答到动手干活Function Calling 与 Agent 循环做到第 3 章结束你得到的是一个能聊天、能看代码的 AI 插件但它还不能算 Agent。真正的 Agent 是能调用工具的。分水岭在哪里分水岭在模型是对着你输出文本还是对着你的代码库输出操作。4.1 分水岭生成文本 vs 调用工具举个最简单的例子。用户选中一段 JavaScript说把这段变量命名改成 camelCase。没有工具调用的方案模型只会回复一段新的代码文本然后等着用户自己复制粘贴替换。这体验非常割裂你复制还有可能漏改。有工具调用之后模型可以在回复里不只发文本还发一个结构化指令调用 write_file 工具写入新内容。你的程序拿到这个指令后自动把文件内容替换掉。用户看到的只有一个提问动作后面的修改是 Agent 自己完成的。这一步从问答工具到Agent的跃迁就是 Function Calling。前端工程师理解 Function Calling 有一个天然优势你可以把它类比成前端的事件回调模型说我要调用这个函数参数是这些你的前端代码就像 EventEmitter 收到消息一样执行回调、更新界面。4.2 注册三个最基础的工具read_file / write_file / run_command工具调用要与模型约定协议。主流格式是让模型输出一个 JSON 数组里面声明要调用的函数名和参数然后你的代码负责真正执行。我建议第一版只做三个工具够用且安全read_file读取任意文件的指定行范围用于让 Agent 自己查看代码不必依赖用户手动粘贴write_file写入或覆盖文件内容用于让 Agent 真正动手改代码run_command在项目目录下执行命令比如npm test、git diff让 Agent 能验证自己的修改在代码里你需要用一个tools数组把这些函数的定义给到模型大语言模型会根据用户问题和当前上下文决定调用哪个工具。举个例子write_file的定义大概长这样{ name: write_file, description: 写入或覆盖指定文件的内容替换整个文件, parameters: { type: object, properties: { path: { type: string, description: 要写入的文件路径 }, content: { type: string, description: 完整的文件内容 } }, required: [path, content] } }这里有个实操重点工具定义里的 description 一定要写细。模型靠 description 判断什么时候用这个工具、参数该填什么描述越含糊模型越倾向于不调用工具光给你打嘴炮。4.3 Agent 循环工具请求、执行、再回传加上工具之后系统不再是一次请求一次响应那么简单而是进入循环。流程是这样的用户提问带着上下文模型返回可能是一段文本也可能是一个工具调用请求也可能是两者都有如果是工具调用请求你的代码执行对应函数把执行结果作为一条新消息回传给模型模型根据执行结果决定继续调用工具还是输出最终答案这个循环什么时候停两种终止条件。一种是最多迭代 N 次比如 10 次防止模型无限循环反复调工具烧钱另一种是模型不再返回工具调用、只返回最终文本视为任务完成。整个循环的伪代码大概是这样let messages [buildContextPrompt()] let maxIterations 10 while (maxIterations--) { const response await callModel(messages, tools) if (response.toolCalls?.length) { for (const toolCall of response.toolCalls) { const result await executeTool(toolCall) messages.push(toolCall.toMessage()) messages.push(result.toMessage()) } continue } // 没有工具调用了输出最终回复退出循环 break }前端在这里的任务不只是傻傻地发请求而是要把 Agent 每一步动作实时渲染出来。比如抽屉里显示一条时间线正在读取 app.js → 正在执行 npm test → 修改了变量名用户会感觉自己真的在指挥一个程序员干活。这个可视化层正是前端的价值所在。4.4 编辑器怎么接收 Agent 的修改Agent 把文件改了用户不能稀里糊涂接受。体验好的产品必须展示变更之后再做决定。我的做法是Agent 执行完 write_file 之后先把原文件内容缓存下来然后新建一个临时模型把旧内容和新内容做对比用monaco editor.createDiffEditor展示给用户。用户看到的是左右对比左边是旧代码右边是 Agent 改过的新代码改动的地方高亮显示。底下放两个按钮接受、丢弃。这跟 Cursor 里的 Accept / Dismiss 是一个意思。这个机制非常重要它让用户始终保有是否采纳 AI 修改的否决权既是体验问题也是安全感问题。代码上并不复杂维护一个agentChange状态内容是改前的字符 → 改后的字符。用户点接受就把新的字符同步到真实文件点丢弃就什么也不做只清空临时差异。5. 项目要上线绕不开的 Token、安全与部署你的迷你 Cursor 功能上已经闭环了但要给别人用、要部署上线马上会遇到三个现实问题Token 怎么控、密钥怎么藏、部署到哪不花钱还稳。5.1 Token 到底是什么、一次会话烧多少Token 是模型处理文本的基本单位不是字符也不是单词你可以简单理解为词的碎片。英文里大致是一个词 1~2 个 Token中文则大约 1 个字 1~2 个 Token。一个含 800 个汉字的消息文本大致会吃掉 1000~1500 Token。这个估算精确度对开发期完全够用真要精确计算可以用模型服务商提供的 Tokenizer 工具但没必要每次都算到个位。我得提醒一个极易踩的坑计算消耗不光算你发出去的指令模型输出也占 Token而且上下文是累积的。每轮对话你都要把之前的全部聊天记录重新跟在后面。5 轮对话下来消耗的 Token 可能是一个文件内容的 5 倍以上。控制 Token 的有效手段有三个一是上下文裁剪尽量只发送定位相关的代码而非全文件二是历史消息做近端截断只保留最近几轮摘要三是把不同难度的任务路由到不同档位的模型简单解释先用便宜模型复杂重构再上贵模型。这个便宜模型打杂、贵模型攻坚的思路是真能替钱包挣命的。5.2 为什么密钥永远不能写在前端这个坑我见过太多人踩。有人图省事把大模型 API Key 直接写在 React 代码里上线结果被人开了浏览器开发者工具就拿到密钥一天之内被刷爆额度。前端所有代码天然是透明的无论你用什么技术栈、怎么压缩混淆。你说的防止查看页面源码禁用右键都只是防君子不防小人——任何在浏览器里执行的代码用户都有办法看到真实逻辑。所以正确做法一定是前端只跟自己的后端代理通信后端代理读取密钥、调用模型、返回结果。密钥永远只存在于环境变量或服务端配置里。这也是我在第 3 章坚持让你做 server 层的原因。别嫌多写几十行接口代码这部分是安全底线省不得。5.3 免费额度与纯前端免费部署怎么选很多人做个人项目最关心钱。模型侧各家大模型服务商基本都有免费额度或极低价档位开发期用免费额度足够了但你要注意免费额度一般有速率限制不能拿去做压测。Cursor 的免费额度也是类似的逻辑官方页面写得清清楚楚以那里的说明为准外部教程的数字可能会过期。Token 这事没有永久免费的白嫖方案正确心态是开发期用免费档稳定后按量付费。部署侧既然你的前端是纯静态的可以白嫖各种静态托管平台比如 Vercel、Cloudflare Pages 这类服务对个人项目都有很可观的免费层支持自动部署动不动就要写后端代理——其实这些平台都支持无服务器函数把代理写在函数里就行。这样你的整个项目前端免费托管 无服务器函数做代理 模型服务商按量付费一个月下来基本就是模型消耗的钱。5.4 代码隐私与合规给别人的代码做 Agent一定要考虑隐私边界。如果用户上传的是公司内部代码你把整个仓库内容直接转发给模型服务存在不小的泄露风险。我的几个实用建议第一默认只上传用户选中或主动请求的代码段不做全仓库盲目上传第二提供一个脱敏模式选项发送前用正则替换掉疑似密钥、IP、手机号等敏感文本第三如果隐私要求严格把模型接入换成完全本地部署的轻量模型比如用 Ollama 跑一个小尺寸模型虽然能力弱一些但数据不出机器。合规这事不复杂核心就是一句话用户没同意的东西不要自作主张发给第三方模型。你在产品设置里明示哪些数据会交给模型处理用户自己决定开不开。透明的选择权就是最好的合规。6. 踩坑复盘与下一步扩展做一个项目最值钱的部分其实是坑。我在做这个迷你 Cursor 的过程中翻过几次车把典型的三个问题记下来你要做的时候大概率也会遇到。6.1 最容易翻车的三个地方第一个坑是流式中断。用户网络抖动或者模型服务端报错流式输出会突然断掉前端 UI 永远停在半句话上特别尴尬。我的解法是在前端维护一个是否已正常结束的标志流结束或报错时都更新它如果没到正常结束就显示一个回复中断点击重试的提示并把中断消息存进历史支持断点续跑。第二个坑是上下文超长。项目文件一大直接把全文塞给模型模型服务直接拒绝请求。处理办法前面说过做分级截断。但截断策略要显性化我建议在发送之前把已截断前 200 行这种标记直接写进 Prompt让模型知道自己看不到完整文件避免它基于残缺上下文给出错误结论。第三个坑是 Agent 乱改文件。模型调用 write_file 的路径如果不够安全可能会覆盖掉用户没打算动的文件。我的解决办法所有写操作默认进入暂存区必须先展示 diff、用户点接受才真正落盘同时限制工具只能操作工作区白名单内的路径任何越权路径直接拒绝并报错给模型。这个安全护栏一分钱不花但能救项目一命。6.2 再往前一步从迷你 Cursor 到真 Agent 平台做完了上面这些你已经拥有一个手工打造的 Agent 最小闭环。这时候再去碰主流 Agent 架构你会发现一切都变得好懂你写的上下文组装对应的是 Context Layer工具调用循环对应的是 Tool Use / Agent Loop那块 Diff 展示对应的是 Human-in-the-loop。架构名词不再是书上的黑话而是你亲手敲过的一套代码。再往前扩展的方向我提几个按难度递增一是加仓库索引用向量库把整个项目代码片段做成可检索的 RAG让模型不再盲人摸象二是加 Skills把重构类型定义写测试用例这类高频任务做成预设的 Prompt 模版加工具组合让用户一键触发三是支持多文件同时修改这需要设计批量的 diff 队列和冲突检查。这些方向每一个都够写一篇长文展开。我自己的体会是做了这个小项目之后再看市面上任何 AI 编程工具、任何 Agent 平台第一反应不再是这东西原理多神而是它上下文是怎么管理的、工具边界在哪、用户有没有否决权。这个思维模式的转变才是前端转 AI Agent 真正意义上的第一步。希望你做完之后也能有同样的感觉。