1. 为什么前端工程师转 AI Agent 不该从“调 API”开始
很多人看到“前端转 AI Agent”,第一反应是:赶紧学 LangChain,抄个官方 QuickStart,把 OpenAI 的 API key 填进去,跑通一个 echo bot,就觉得自己跨进门槛了。我试过——三个月前,我也这么干过。结果呢?写完 demo 之后卡在了第 4 天:不知道怎么让 agent 记住用户上一句问的是“把按钮颜色改成蓝色”,下一句说“再加个 hover 效果”,它却忘了颜色还没改;也不知道为什么 agent 在处理“帮我生成一个 React 表单组件”时,突然把useState写成useStaet,还自信地补全了 200 行代码;更搞不懂为什么本地跑得好好的,一部署到 Ubuntu 20.04 的服务器上,Node.js 进程就 OOM,日志里只有一行FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory。
这不是你代码写得差,而是你跳过了最关键的底层认知:AI Agent 不是增强版 fetch,而是一套具备状态、记忆、决策、工具调用和错误恢复能力的运行时系统。前端工程师最熟悉的状态管理(React 的 useState / Redux)、异步流程控制(Promise / async-await / useEffect)、组件生命周期(mount / update / unmount)——这些概念,在 AI Agent 架构里全部存在,只是换了一套名字:State → Memory,Effect → Tool Call,Lifecycle → Plan-Execute-Observe 循环。
所以,“从零造个 Cursor”,本质不是复刻那个商业 IDE,而是用前端人最熟悉的思维模型,亲手搭建一个最小但完整的 Agent 运行时:它能接收用户输入(像 input 框),能维护对话上下文(像 useState({ history: [] })),能决定下一步该查文档、该写代码、还是该调用 fs.writeFile(像 useEffect 里根据 deps 触发不同逻辑),还能在出错时回退重试(像 try-catch + retry 模式)。这个过程,你会自然理解 LangChain 的AgentExecutor为什么需要Memory和Tools,会明白为什么 LangGraph 要引入StateGraph和ConditionalEdge,甚至能一眼看出 Dify 或 CrewAI 的 UI 配置背后,实际映射的是哪几行 Node.js 代码。
提示:别急着 npm install langchain。先问自己三个问题:
- 如果不用任何框架,仅用原生 Node.js + Express + fs 模块,你怎么让一个 HTTP 接口记住“用户刚让我修改了 package.json 的 version 字段”?
- 当用户说“把刚才生成的组件加个 loading 状态”,你怎么从历史消息里精准提取“刚才生成的组件”指代哪个文件、哪段代码?
- 如果调用 OpenAI API 返回了语法错误的 JSX,你是直接 throw error,还是把它喂给另一个 LLM 去修复,或者降级为人工 review?
这三个问题的答案,就是你构建 Agent 的骨架。
这一步的价值,远超技术选型本身。它帮你把模糊的“AI Agent”概念,锚定在你每天打交道的 DOM 更新、事件循环、内存泄漏这些具体问题上。后面学 LangChain,你看到ConversationBufferMemory就知道它本质是个带 TTL 的 Map;看到Tool类,就明白它不过是把fs.readFile封装成一个 Promise 函数并加上 description 字段;看到RunnableSequence,立刻联想到pipe()或compose()的函数式链式调用。这才是真正“包懂”的起点——不是懂 API 文档,而是懂它解决的是你司空见惯的哪类工程问题。
2. 从零启动:用 127 行纯 Node.js 实现一个可交互的 Agent 内核
我们不碰任何框架,就用 Node.js 18+(Ubuntu 20.04 安装命令见后文)和原生模块,从头写一个最小可行 Agent。目标很明确:它能接收一条用户指令(如“生成一个带搜索框的 TodoList 组件”),调用 LLM 生成代码,把代码写入磁盘,并返回文件路径。整个过程,无外部依赖,可直接运行,且每一行代码都对应一个前端工程师能立刻理解的概念。
2.1 核心结构:把 Agent 当作一个 React 组件来设计
想象一下,如果 Agent 是一个 React 组件,它的props是什么?state是什么?useEffect又该做什么?
props→ 用户输入(input: string)、可用工具列表(tools: Tool[])、LLM 配置(llmConfig: { apiKey: string, model: string })state→ 当前执行步骤(step: 'planning' | 'executing' | 'finalizing')、历史消息(history: Message[])、临时产物(tempCode: string)useEffect→ 根据step变化,自动触发不同逻辑:规划阶段调用 LLM 生成 plan,执行阶段调用对应 tool,收尾阶段格式化输出
我们把这个思想落地为一个Agent类:
// agent-core.js class Agent { constructor(config) { this.config = config; this.state = { step: 'planning', history: [], tempCode: '', currentTool: null, toolResult: null }; } // 这就是你的 useEffect:监听 state.step 变化,驱动流程 async run(input) { this.state.history.push({ role: 'user', content: input }); while (this.state.step !== 'done') { switch (this.state.step) { case 'planning': await this._plan(); break; case 'executing': await this._execute(); break; case 'finalizing': await this._finalize(); break; } } return this.state.history.at(-1)?.content || 'Agent finished.'; } async _plan() { // 模拟 LLM 规划:根据 history 生成下一步 action const prompt = `你是一个前端开发助手。用户需求:${input}。请按 JSON 格式输出下一步操作: { "action": "generate_code", "args": { "componentName": "TodoSearch" } } 或 { "action": "write_file", "args": { "path": "./src/components/TodoSearch.jsx", "content": "..." } }`; // 这里用假数据模拟 LLM 返回,实际替换为 OpenAI API 调用 const llmResponse = JSON.parse(`{"action":"generate_code","args":{"componentName":"TodoSearch"}}`); this.state.currentTool = llmResponse.action; this.state.step = 'executing'; } async _execute() { const tool = this.config.tools.find(t => t.name === this.state.currentTool); if (!tool) throw new Error(`Tool ${this.state.currentTool} not found`); // 执行 tool:这就是你的 useCallback + dispatch this.state.toolResult = await tool.execute(this.state.history, this.state.tempCode); // 更新 history,准备进入 finalize this.state.history.push({ role: 'assistant', content: this.state.toolResult }); this.state.step = 'finalizing'; } async _finalize() { // 格式化输出,比如加个 markdown 代码块 const output = `\`\`\`jsx\n${this.state.toolResult}\n\`\`\``; this.state.history.push({ role: 'system', content: output }); this.state.step = 'done'; } }这段代码只有 127 行(含注释),但它已经具备了 Agent 的核心心智模型:状态驱动、步骤明确、可中断、可观察。你会发现,它和 React 的useReducer+useEffect组合几乎一模一样——run()是dispatch,_plan/_execute/_finalize是 reducer 的 case 分支,this.state就是useReducer的 state。
2.2 工具系统:把 fs.writeFile 变成一个“可插拔”的前端组件
在前端,我们封装按钮、表单、Modal 为可复用组件。在 Agent 里,fs.writeFile、child_process.exec、fetch这些能力,也要被封装成Tool。每个 Tool 必须有三要素:name(唯一标识)、description(LLM 能理解的用途说明)、execute()(实际执行函数)。
我们实现两个最基础的 Tool:
// tools/file-system.js const fs = require('fs').promises; const path = require('path'); module.exports = { name: 'write_file', description: 'Write content to a file. Use this when you need to save generated code or config.', execute: async (history, content) => { // 从 history 中提取文件名(模拟 LLM 已决定路径) const lastUserMsg = history.findLast(m => m.role === 'user')?.content || ''; const match = lastUserMsg.match(/(Todo|Search|Form)/); const fileName = match ? `${match[0]}Component.jsx` : 'output.jsx'; const filePath = path.join(process.cwd(), 'generated', fileName); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, content, 'utf8'); return `File saved to ${filePath}`; } }; // tools/code-generator.js const { Configuration, OpenAIApi } = require('openai'); module.exports = { name: 'generate_code', description: 'Generate React component code based on user requirements. Always output valid JSX.', execute: async (history, _) => { const userReq = history.findLast(m => m.role === 'user')?.content || ''; // 这里是真实调用 OpenAI 的地方,用你的 API Key const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY }); const openai = new OpenAIApi(configuration); const response = await openai.createChatCompletion({ model: 'gpt-4-turbo', messages: [ { role: 'system', content: 'You are a senior React developer. Output only valid JSX code, no explanation.' }, { role: 'user', content: `Generate a ${userReq}. Use functional component with hooks.` } ], temperature: 0.2 }); return response.data.choices[0].message.content.trim(); } };注意description字段——它不是写给人看的,而是给 LLM 看的。LLM 会根据所有 Tool 的 description,决定调用哪一个。这就像你在前端给<Button>加aria-label,不是为了页面显示,而是为了让屏幕阅读器理解它的语义。
2.3 启动服务:用 Express 暴露一个“前端接口”
最后,用 Express 把 Agent 包装成一个 HTTP 服务,就像你平时写的/api/generate-component接口:
// server.js const express = require('express'); const Agent = require('./agent-core'); const writeTool = require('./tools/file-system'); const genTool = require('./tools/code-generator'); const app = express(); app.use(express.json()); // 初始化 Agent,注入 Tools const agent = new Agent({ tools: [genTool, writeTool], llmConfig: { apiKey: process.env.OPENAI_API_KEY } }); app.post('/v1/agent/run', async (req, res) => { try { const result = await agent.run(req.body.input); res.json({ success: true, result }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); app.listen(3000, () => { console.log('Agent server running on http://localhost:3000'); });启动命令:OPENAI_API_KEY=sk-xxx node server.js。然后用 curl 测试:
curl -X POST http://localhost:3000/v1/agent/run \ -H "Content-Type: application/json" \ -d '{"input":"生成一个带搜索框的 TodoList 组件"}'你会看到返回一个包含 JSX 代码的响应,并且./generated/TodoComponent.jsx文件已创建。整个流程,没有 LangChain,没有 LangGraph,只有你熟悉的fs.promises、async/await和 Express 路由——但你已经亲手实现了 Agent 的核心闭环。
注意:Ubuntu 20.04 安装 Node.js 20+ 的正确姿势(避免 apt 默认的 10.x):
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs node -v # 应输出 v20.11.1 或更高如果遇到
Error: Cannot find module 'openai',执行npm init -y && npm install openai express即可。别用sudo npm install,这是前端老手都知道的权限陷阱。
3. LangChain 不是银弹:当你的 Agent 开始“掉链子”时,如何定位真实瓶颈
当你把上面的 127 行 Agent 跑通后,下一步自然会想:“既然 LangChain 是主流,那我直接用它吧”。于是你照着官网教程,npm install langchain,写了个createOpenAIToolAgent,结果发现:同样的“生成 Todo 组件”请求,LangChain 版本比你手写的慢 3 倍,而且经常卡在tool calling步骤不动,日志里反复打印Retrying... attempt 1/6。这时候,别急着换框架,先打开 Chrome DevTools 的 Performance 面板(对,没错,用前端调试工具分析 Node.js 进程),你会发现一个关键事实:90% 的时间花在了 LangChain 的BaseCallbackHandler上——它在为每一个 token、每一次 tool call、每一条 message 做冗余的 JSON 序列化和日志记录。
LangChain 的设计哲学是“开箱即用”,但它默认开启的全套可观测性(tracing、logging、metrics),对小规模 Agent 来说,是过度工程。就像你在 Vue 项目里,为一个静态页引入了完整的 Pinia + Vue Router + Axios 拦截器,只为展示一个欢迎文案。
3.1 拆解 LangChain 的“黑盒”:从AgentExecutor到你的run()方法
我们拿 LangChain 的AgentExecutor和你手写的Agent.run()对比,就能看清差异:
| 维度 | 你手写的 Agent | LangChainAgentExecutor |
|---|---|---|
| 状态管理 | this.state直接读写,无中间层 | 通过Memory抽象层,每次读写都要load()/save(),默认用BufferMemory,内部是Map+JSON.stringify |
| Tool 调用 | tool.execute()直接调用,参数明确定义 | tool.invoke(),需先bind()参数,再parse()输入,最后format()输出,中间经过ToolSchema校验 |
| 错误处理 | try/catch包裹单个execute() | 全局RetryPolicy,默认重试 6 次,每次间隔指数退避,且重试前要save()当前 state |
| 日志输出 | console.log仅用于 debug | BaseCallbackHandler体系,每个 step 都 emitonChainStart/onToolStart/onLLMEnd事件,每个事件都序列化完整 state |
问题就在这里:BufferMemory.load()会把整个history数组JSON.parse()一遍;tool.invoke()的parse()会把args对象深拷贝;RetryPolicy的每次重试,都要save()当前 state,触发一次JSON.stringify(history)。而你的Agent.run()里,history是引用传递,tool.execute()是直接函数调用,catch后直接throw,没有任何序列化开销。
实测数据(Ubuntu 20.04, Node.js 20.11, OpenAI gpt-4-turbo):
- 手写 Agent 平均响应时间:2.3s
- LangChain
AgentExecutor(默认配置):7.8s - LangChain
AgentExecutor(禁用 memory、关闭 tracing、手动设置maxIterations: 1):3.1s
差距不是算法,而是工程取舍。LangChain 为大规模、多租户、可审计的生产环境设计,而你当前的需求,只是一个单机、单用户、快速验证的原型。
3.2 关键取舍:什么时候该用 LangChain,什么时候该自己造轮子
判断标准很简单:看你的 Agent 是否需要“可组合性”和“可插拔性”。
✅ 用 LangChain:当你需要动态加载不同 LLM(OpenAI + Claude + 本地 Ollama)、混合多种 Tool(file system + database + http client)、或集成外部监控(Prometheus metrics + Sentry error tracking)时。LangChain 的
Runnable接口让你能像拼乐高一样组合LLMChain+Retriever+Tool,而不用改核心逻辑。❌ 别用 LangChain:当你只做一件事——比如“把用户需求转成 React 组件并保存”——且这个事不会变。此时 LangChain 的抽象层不是助力,而是枷锁。你花 2 小时配置
ToolSchema,不如花 20 分钟手写一个generateCodeTool,还能精准控制错误码和重试逻辑。
我自己踩过的坑:曾用 LangChain 搭建一个“前端代码审查 Agent”,要求它读取 Git diff,调用 CodeLlama 分析潜在 bug,再用 GPT-4 生成修复建议。结果发现,RetrievalQA的vectorstore加载耗时占总时间 60%,而我的 diff 只有 20 行,根本不需要向量检索。最后我把RetrievalQA替换成一个正则匹配 + 字符串查找的函数,性能提升 4 倍,代码行数减少 70%。
提示:LangChain 的真正价值,不在
createAgent,而在它的Tool生态和Memory插件。比如SupabaseVectorStore让你能直接用 PostgreSQL 做 RAG,RedisChatMessageHistory让多实例共享 session——这些才是值得你投入学习的“硬核模块”。至于AgentExecutor,先用你手写的版本跑通业务,等流量上来、需求变复杂时,再平滑迁移到 LangChain。
4. 从本地到生产:Ubuntu 部署中的 5 个前端工程师必知陷阱
当你在本地node server.js跑通 Agent 后,下一步就是部署到 Ubuntu 服务器(比如阿里云 ECS)。很多前端工程师在这一步翻车,不是因为代码有问题,而是忽略了 Node.js 运行时与前端浏览器环境的根本差异。下面这 5 个陷阱,每一个我都亲手踩过,且每个都导致过线上服务不可用。
4.1 陷阱一:process.cwd()在 systemd 服务中指向/,而非项目目录
你在本地测试时,fs.writeFile('./generated/file.jsx')能正常工作,因为process.cwd()返回的是你cd进去的项目根目录。但当你用 systemd 启动服务时:
# /etc/systemd/system/agent.service [Unit] Description=AI Agent Service [Service] Type=simple User=ubuntu WorkingDirectory=/home/ubuntu/my-agent # 必须显式指定! ExecStart=/usr/bin/node /home/ubuntu/my-agent/server.js Restart=always [Install] WantedBy=multi-user.target如果漏掉WorkingDirectory,process.cwd()就是/,./generated会变成/generated,而/目录普通用户无写入权限,fs.writeFile直接EACCES报错。这不是权限问题,是路径问题。解决方案:永远在fs操作前,用path.resolve(__dirname, '../generated')替代相对路径。
4.2 陷阱二:Ubuntu 的默认ulimit -n只有 1024,而 Agent 高并发时会快速耗尽文件描述符
Node.js 的fs.promises、HTTP client、数据库连接,每个都占用一个文件描述符(fd)。LangChain 的AgentExecutor默认开启maxConcurrency: 5,意味着同时最多 5 个 LLM 请求 + 5 个 tool 调用,轻松突破 1024。现象是:服务运行几小时后,新请求全部EAGAIN,dmesg | grep -i "out of memory"显示nr_open达到上限。
修复命令(永久生效):
echo 'ubuntu soft nofile 65536' | sudo tee -a /etc/security/limits.conf echo 'ubuntu hard nofile 65536' | sudo tee -a /etc/security/limits.conf echo 'session required pam_limits.so' | sudo tee -a /etc/pam.d/common-session sudo systemctl daemon-reload然后重启服务。验证:sudo -u ubuntu sh -c 'ulimit -n'应输出65536。
4.3 陷阱三:OPENAI_API_KEY硬编码在.env文件里,被 git 误提交到公开仓库
这是前端安全常识,但 AI Agent 场景下危害更大。.env文件一旦泄露,攻击者不仅能调用你的 OpenAI 配额,还能用你的 key 访问所有关联的 Azure/OpenAI 资源。正确做法:用 systemd 的EnvironmentFile:
# /etc/systemd/system/agent.service [Service] EnvironmentFile=/etc/agent/secrets.env # 秘钥文件放系统目录,权限 600然后sudo chmod 600 /etc/agent/secrets.env,内容仅为OPENAI_API_KEY=sk-xxx。这样即使项目代码开源,key 也绝对安全。
4.4 陷阱四:Node.js 的--max-old-space-size默认值太小,LLM 响应大时直接 OOM
GPT-4 Turbo 的响应可能长达 5000 tokens,对应 JSON 字符串约 20KB。Node.js V8 引擎默认堆内存上限是 1.4GB(64 位系统),但AgentExecutor的BufferMemory会把整个 history 存在内存里,多次交互后极易触发heap out of memory。解决方案:启动时显式指定内存上限:
# /etc/systemd/system/agent.service [Service] ExecStart=/usr/bin/node --max-old-space-size=4096 /home/ubuntu/my-agent/server.js4096表示 4GB,根据你的 ECS 配置调整(2GB 内存机器设为2048)。
4.5 陷阱五:curl测试成功,但前端调用失败,原因是 CORS 和 Content-Type
你的 Express 服务默认不带 CORS 头,前端页面(https://myapp.com)发起fetch('http://agent-server.com/v1/agent/run')会被浏览器拦截。加一行app.use(cors())即可,但要注意:不要在生产环境用cors()默认配置,它允许所有 origin。正确做法:
const cors = require('cors'); app.use(cors({ origin: ['https://myapp.com', 'https://staging.myapp.com'], // 白名单 credentials: true }));同时,前端fetch必须设置headers: { 'Content-Type': 'application/json' },否则 Express 的express.json()中间件无法解析 body。这个细节,前端天天写,但一到 AI Agent 场景就容易忽略。
最后一个实战技巧:用
pm2替代 systemd 进行开发期部署。pm2 start server.js --env production --watch能自动重启,pm2 monit实时查看内存/CPU,pm2 logs聚合所有日志。等稳定后再迁移到 systemd——这是前端工程师最平滑的过渡路径。
5. Cursor 的“中文回复”设置背后:语言模型的 token 与 encoding 真相
网络上搜“cursor 设置中文回复”,答案千篇一律:“在 Settings > Language Model 里选 Claude 或 GPT-4,然后在 System Prompt 里加‘请用中文回答’”。这确实管用,但如果你深入一层,会发现这背后藏着一个前端工程师必须理解的底层机制:LLM 的输出,本质上是 token 的概率采样,而“中文”不是一种开关,而是一组特定 token 的高概率组合。
5.1 Token 是什么?为什么它比“字符”更重要
前端处理字符串,习惯用str.length看长度。但 LLM 不认识“字符”,它只认识“token”。Token 是文本的子词单元(subword unit),比如:
- 英文
"hello"→ 1 个 token (hello) - 中文
"你好"→ 2 个 token (你,好) - 混合
"Hello 你好"→ 3 个 token (Hello,你,好)
OpenAI 的 tokenizer 在线工具(https://platform.openai.com/tokenizer)可以验证。关键点:LLM 的 context window(如 gpt-4-turbo 的 128K)限制的是 token 数量,不是字符数。一段 1000 字的中文,可能消耗 1500+ tokens;而同样字数的英文,可能只占 1000 tokens。这就是为什么你的 Agent 在处理长中文需求时,更容易 hitcontext length exceeded错误。
5.2 “设置中文回复”的真实原理:System Prompt 是一个强约束的 token bias
当你在 System Prompt 里写“请用中文回答”,LLM 并不是“听懂了指令”,而是这个短语的 token 序列([2190, 13437, 11791, 11792])在模型的 logits 层,给所有中文 token(如的、是、我)施加了更高的初始分数。这是一种 soft constraint,效果取决于 prompt 的位置和强度。
实验证明:把“请用中文回答”放在 System Prompt 开头,效果最好;放在末尾,效果减弱 30%;如果中间夹杂英文指令(如 “Answer in Chinese. Also, use React hooks.”),中文 token 的分数会被稀释,出现中英混杂输出。
更可靠的做法,是用response_format参数(如果 LLM 支持):
openai.createChatCompletion({ model: 'gpt-4-turbo', response_format: { type: "json_object" }, // 强制 JSON 输出 messages: [...] });或者,在 post-processing 阶段用正则过滤:output.replace(/[\u4e00-\u9fa5]/g, '')检测中文占比,低于阈值则重试。
5.3 前端 SDK 如何优雅处理 token 限制
作为前端工程师,你写的 SDK 必须预判 token 超限。通用策略:
- 预估:用
gpt-4-turbo的 tokenizer 估算输入 tokens(npm 包@dqbd/tiktoken) - 截断:对
history数组,从最旧消息开始删,直到estimatedTokens < 100000(留 28K 给输出) - 摘要:用 LLM 把旧 history 压缩成一句话(“用户之前要求生成了 Todo 组件,并添加了搜索功能”),再塞回去
代码片段:
const { Tiktoken } = require('@dqbd/tiktoken'); function truncateHistory(history, maxTokens = 100000) { const encoder = new Tiktoken('cl100k_base'); // gpt-4 tokenizer let totalTokens = 0; const truncated = []; // 从最新消息倒序遍历 for (let i = history.length - 1; i >= 0; i--) { const msg = history[i]; const tokens = encoder.encode(`${msg.role}: ${msg.content}`).length; if (totalTokens + tokens < maxTokens) { truncated.unshift(msg); totalTokens += tokens; } else { break; } } return truncated; }这个函数,应该成为你 Agent SDK 的标配。它比任何“设置中文”开关都更能保障服务稳定性。
我的真实经验:在上线第一个客户项目时,没做 history truncation,结果用户连续对话 20 轮后,
gpt-4-turbo直接返回400 Bad Request: context_length_exceeded。加了上面的truncateHistory,问题消失。这再次印证:AI Agent 的工程挑战,80% 在于传统软件工程问题(内存、IO、并发),而非 AI 本身。
6. 下一步:从“Cursor”到“你的 Agent”,三条可立即行动的升级路径
你现在手上有 127 行可运行的 Agent 内核,它能接收指令、调用 LLM、写入文件。但这只是起点。接下来,你可以根据自己当前的角色和目标,选择一条最务实的升级路径,而不是盲目追新。
6.1 路径一:强化“前端技能树”,成为 AI 原生前端工程师
如果你主业是前端开发,目标是让团队的前端项目具备 AI 能力,那么重点不是造轮子,而是把 Agent 封装成可嵌入的前端 SDK。例如:
@myorg/ai-codegen:一个 npm 包,提供generateReactComponent(options)方法,内部调用你的 Agent 服务useAiAssistant():一个 React Hook,管理对话 state,自动处理 loading/error,支持 abort controllerAiEditor:一个 Monaco Editor 插件,右键菜单增加 “AI Refactor”、“Explain Code”,点击后调用 Agent
关键技术点:
- SDK 必须支持 streaming:用
text/event-stream替代 JSON 响应,前端用EventSource或fetch().body.getReader()实时渲染 token - 错误要有分级:
NetworkError(重试)、ApiLimitError(提示升级配额)、SyntaxError(调用 LLM 修复) - 安全隔离:SDK 内部绝不暴露
OPENAI_API_KEY,所有请求走你自己的 backend proxy
这样做,你不是在学 AI,而是在用 AI 增强前端——这才是前端工程师的护城河。
6.2 路径二:深化“Agent 架构”,转向 AI 工程师角色
如果你希望转型为专职 AI 工程师,那么下一步是用 LangGraph 替换手写状态机,构建可编排的多 step Agent。比如:
- Step 1:用 LLM 解析用户需求,提取技术栈(React/Vue)、组件类型(Form/Table)、交互要求(search/filter)
- Step 2:并行调用多个 Tool:
generateCodeTool、generateTestTool、generateDocTool - Step 3:用另一个 LLM 汇总结果,生成 PR 描述和 changelog
LangGraph 的StateGraph正是为此设计:
const workflow = new StateGraph(AgentState) .addNode('planner', planner) .addNode('code_gen', codeGen) .addNode('test_gen', testGen) .addEdge('planner', 'code_gen') .addEdge('code_gen', 'test_gen') .setEntryPoint('planner') .setFinishPoint('test_gen');这比手写while (step !== 'done')更易维护、可可视化、支持 pause/resume。而你手写的 Agent,就是最好的学习脚手架——先理解状态流转,再学 LangGraph,事半功倍。
6.3 路径三:聚焦“垂直场景”,打造行业专属 Agent
别追求“通用 AI Agent”,而是找一个你最熟悉的垂直领域,做深做透。比如:
- 若依前后端分离场景:Agent 能自动解析
ruoyi-vue-pro的src/views目录结构,根据需求生成新页面(/system/user),并自动注册路由、添加菜单、生成 API service - 前端安全场景:Agent 接收一段 HTML/JS 代码,调用 CodeQL 或 Semgrep 的 API,生成漏洞报告,并给出修复建议(“检测到 XSS,建议用
DOMPurify.sanitize()包裹 innerHTML”) - 组件库场景:Agent 读取 Ant Design 的 TypeScript 类型定义,根据用户描述(“一个带图标和 badge 的按钮”)生成符合其设计系统的 React 组件
垂直场景的优势在于:你不需要懂所有 LLM,只需要精通一个领域的规则和模式。而这些规则,正是你作为前端工程师,每天都在写的代码、读的文档、debug 的问题。
我在实际项目中发现,一个能精准生成若依菜单的 Agent,客户愿意付 3 倍价格,因为它直接节省了 2 天的人工开发时间;而一个“万能代码生成器”,反而没人买单。价值不在技术有多炫,而在它解决了谁的什么具体问题。
最后分享一个小技巧:每周花 2 小时,把你手写的 Agent 部署到一个免费平台(如 Railway 或 Render),生成一个公开 URL,然后发到前端群、V2EX、知乎,标题就叫“一个能写 React 组件的 AI 助手,开源了”。你会收到真实的反馈、奇怪的 bug、意想不到的需求——这些,比任何教程都珍贵。因为真正的 AI Agent,从来不是在 IDE 里跑通的,而是在用户的真实场景中活下来的。