☰
前端转AI Agent实战:从零构建迷你Cursor
2026/10/8 4:41:51 网站建设 项目流程

最近总有前端朋友问我同一个问题:想转 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 的交互路径:用户先选中一段代码,然后按快捷键(比如 Cmd+L 或自定义组合键)唤起对话浮窗,浮窗里自动带上选中的代码上下文,用户输入问题后回车发送。

这里的重点不在界面多炫,而在"意图分类"。用户看到选中代码之后问的问题,通常是三种:解释这段代码在干嘛、帮我修 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:前端项目,纯静态页面,可部署到任意静态托管平台
  • server:API 代理服务,暴露 /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 循环:工具请求、执行、再回传

加上工具之后,系统不再是一次请求一次响应那么简单,而是进入循环。流程是这样的:

  1. 用户提问,带着上下文
  2. 模型返回:可能是一段文本,也可能是一个工具调用请求,也可能是两者都有
  3. 如果是工具调用请求,你的代码执行对应函数
  4. 把执行结果作为一条新消息回传给模型
  5. 模型根据执行结果决定继续调用工具还是输出最终答案

这个循环什么时候停?两种终止条件。一种是最多迭代 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 真正意义上的第一步。希望你做完之后,也能有同样的感觉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询