从“千问点奶茶”到“手写Cursor”:AI Agent 时代的开发革命
一、一杯奶茶引发的技术革命
2026 年的春节,互联网圈没有等来传统的红包雨,却等来了一场史无前例的AI 狂欢。
阿里通义千问 APP 推出的“30 亿免单送奶茶”活动,在上线短短 9 小时内就突破了1000 万单,瞬间挤爆了全国各地的奶茶店,甚至导致服务器数次宕机。用户只需对着手机说一句:“帮我点杯少冰三分糖的伯牙绝弦”,AI 就能自动定位、筛选门店、计算优惠、调用支付宝支付,最后骑手将奶茶送到手中。
这不仅仅是一次成功的营销,更是AI Agent(智能体)技术从实验室走向大众生活的里程碑事件。
为什么“点奶茶”如此重要?
在过去,大模型(LLM)更像是一个被关在房间里的博学智者:它能写诗、能 coding、能回答百科知识,但它无法触达现实世界。它不知道现在的天气,无法访问你的外卖账号,更不能帮你下单。
而“千问点奶茶”标志着大模型完成了关键进化:
- 感知与决策:理解模糊的自然语言指令(“少冰三分糖”)。
- 工具调用(Tools) :自主调用地图 API 定位、调用库存系统查货、调用支付接口结账。
- 任务闭环:从“对话”变成了“办事”,真正实现了Autonomous Agent(自主智能体) 。
正如行业所言:AI 的下半场,不再是比拼谁的参数更大,而是比拼谁能更丝滑地调用工具,解决实际问题。
当 AI 能够替我们点奶茶时,它离替我们写代码、做报表、运营公司还远吗?答案是不远。今天,我们将顺着这股浪潮,深入探讨如何从零开始,手写一个最小版本的 Cursor——那个能像“点奶茶”一样,自动帮你创建项目、读写文件、运行命令的编程智能体。
二、Agent 是什么?从 LLM 到全栈工程师的进化
如果说 LLM 是大脑,那么 Agent 就是给这个大脑装上了手、脚和记忆。
AI Agent = llm + Memory + Tool + RAG
核心公式
-
LLM (Large Language Model) :负责思考、规划、推理。它是核心引擎,比如 Qwen-Coder、Gemini 3.1 Pro。
-
Memory (记忆) :
- 短期记忆:当前的对话上下文。
- 长期记忆:记住用户的偏好、历史项目的架构规范(通过向量数据库实现)。
-
Tools (工具) :这是 Agent 区别于普通聊天的关键。它让 LLM 能执行具体操作,如:读取文件、写入代码、执行 Shell 命令、搜索网页、调用 API。
-
RAG (Retrieval-Augmented Generation) :检索增强生成。让 Agent 能查阅公司内部文档、私有代码库,回答基于特定上下文的问题。
简单来说:
- 普通 LLM:你问“怎么写一个 TodoList?”,它给你一段代码文本。
- AI Agent (如 Cursor) :你说“帮我创建一个 React TodoList 项目”,它自动创建文件夹、生成
package.json、安装依赖、编写组件代码、甚至运行测试,最后告诉你“项目已就绪”。
三、实战:手写最小版 Cursor
我们要打造的核心功能很简单:让大模型拥有读写文件和执行命令的能力,从而自动完成编程任务。
1. 📦 依赖导入 (Imports)
import 'dotenv/config'; // 加载 .env 文件中的环境变量 (如 API Key)
import { ChatOpenAI } from '@langchain/openai'; // 引入大模型接口 (兼容 OpenAI 协议,如通义千问)
import { tool } from '@langchain/core/tools'; // 引入工具封装函数
import { HumanMessage, SystemMessage, ToolMessage } from '@langchain/core/messages'; // 引入三种消息类型
import fs from 'node:fs/promises'; // 引入 Node.js 原生文件系统 (Promise 版本,支持 async/await)
import { z } from 'zod'; // 引入数据校验库,用于定义工具参数的结构 (Schema)
- 作用:准备好所有需要的“武器库”:大脑 (LLM)、手 (fs)、规则 (Zod)、沟通协议 (Messages)。
2. 🧠 初始化大脑 (Model Initialization)
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME, // 模型名称,如 "qwen-coder-turbo"
apiKey: process.env.OPENAI_API_KEY, // 你的 API 密钥
configuration: {
baseURL: process.env.OPENAI_BASE_URL // 接口地址 (如果是国内模型,这里指向代理地址)
},
temperature: 0, // 温度为 0,让模型输出最确定、最逻辑化的结果 (适合写代码/调用工具)
});
- 关键点:
temperature: 0非常重要。对于需要精确调用工具的场景,我们不希望模型“发散思维”,而是希望它严格遵循指令。
3. 🛠️ 定义工具 (Tool Definition)
这是 Agent 拥有“超能力”的关键。
const readFileTool = tool(
// 1. 执行逻辑 (Function Body)
async ({ path }) => {
const content = await fs.readFile(path, 'utf-8'); // 真正读取文件
console.log(`[工具调用] 成功读取 ${content.length} 字节`);
return content; // 返回文件内容给 AI
},
// 2. 工具元数据 (Metadata) - 告诉 AI 这个工具是干嘛的
{
name: 'read_file',
description: `用此工具来读取文件内容...`,
// 3. 参数 Schema (Zod) - 强制 AI 按格式传参
schema: z.object({
path: z.string().describe('要读取的文件路径')
})
}
);
-
作用:
- 描述 (
description) :AI 通过阅读这段文字,知道“哦,当用户想看文件时,我应该用这个工具”。 - Schema (
z.object) :AI 必须生成一个包含path字符串的 JSON 对象。如果 AI 瞎编(比如传了个数字),Zod 会在运行前拦截报错。 - 执行函数:这是真正在本地运行的代码,拥有读取你电脑文件的权限。
- 描述 (
4. 🔗 绑定工具 (Binding Tools)
const tools = [readFileTool];
const modelwithTools = model.bindTools(tools);
- 作用:
bindTools是 LangChain 的魔法方法。它将工具的定义转换成 System Prompt 的一部分,告诉大模型:“你现在不仅会聊天,还可以调用以下函数:read_file”。 - 结果:
modelwithTools是一个增强版的模型实例,它的输出可能包含tool_calls字段。
5. 💬 构建对话上下文 (Context Building)
const messages = [
new SystemMessage(`...工作流程...`), // 人设与规则:告诉 AI 它是谁,该怎么一步步做
new HumanMessage('请读取 tool-file-read.mjs 文件内容并解释代码') // 用户指令
];
- SystemMessage:这是“宪法”。它规定了 ReAct 的流程:先调用工具 -> 等结果 -> 再分析。没有这个,AI 可能会直接瞎编文件内容。
- HumanMessage:触发任务的起点。
6. 🔄 核心循环:ReAct Engine (The Loop)
这是整个 Agent 的“心脏”,负责驱动思考与行动的循环。
第一步:初次思考
let response = await modelwithTools.invoke(messages);
messages.push(response);
- AI 收到指令,发现需要读文件,但它自己读不了。
- 于是它返回一个消息,里面包含
tool_calls: [{ name: 'read_file', args: { path: '...' } }]。 - 把这个消息加入历史记录。
第二步:While 循环 (行动与观察)
while (response.tool_calls && response.tool_calls.length > 0) {
// 1. 检测到有工具需要执行
console.log(`检测到 ${response.tool_calls.length} 个工具调用`);
// 2. 并行执行所有工具 (Promise.all)
const toolResults = await Promise.all(
response.tool_calls.map(async (toolCall) => {
// 找到对应的工具函数
const tool = tools.find(t => t.name === toolCall.name);
// 执行它,传入参数
const result = await tool.invoke(toolCall.args);
return result;
})
);
// 3. 将结果包装成 ToolMessage 并加入历史
response.tool_calls.forEach((toolCall, index) => {
messages.push(
new ToolMessage({
content: toolResults[index], // 文件的真实内容
tool_call_id: toolCall.id // 关联 ID,告诉 AI 这是对哪个请求的回应
})
);
});
// 4. 【关键】再次调用模型 (Reflection)
// 此时 messages 里已经有了:用户问 + AI 请求读 + 文件真实内容
response = await modelwithTools.invoke(messages);
// 将 AI 的新回复加入历史 (可能是最终答案,也可能是新一轮工具调用)
messages.push(response);
}
-
循环逻辑:
- 只要 AI 还想调用工具 (
tool_calls不为空),循环就继续。 - 执行:真的去读文件。
- 反馈:把读到的内容塞回给 AI。
- 再思考:AI 看到内容后,这次它就能回答问题了,不再需要调用工具。
- 退出:AI 返回纯文本回答 (
content),tool_calls为空,循环结束。
- 只要 AI 还想调用工具 (
7. 🏁 程序结束
当 while 循环结束时,response 变量中保存的就是 AI 生成的最终回答(即对代码的解释)。
虽然代码最后没有打印 response.content,但此时程序已经成功完成了任务。
四、从 Demo 到产品:还需要什么?
上面的代码只是一个“最小可行性产品”(MVP)。要让它成为真正的 Cursor 或 Manus,我们还需要在以下几个维度进行扩展:
-
记忆系统 (Memory)
- 问题:目前的 Agent 记不住上周聊过的架构设计。
- 方案:引入向量数据库(如 Chroma, Pinecone),将历史对话和项目文档向量化。每次交互前,先检索相关记忆注入 Context。
-
多 Agent 协作 (Multi-Agent)
-
问题:单一模型既要写代码又要测试,容易出错。
-
方案:参考“OpenClaw 养虾”或 Manus 的模式,拆解为多个角色:
- 产品经理 Agent:拆解需求,生成任务列表。
- 架构师 Agent:设计文件结构。
- 工程师 Agent:具体编写代码。
- 测试 Agent:自动编写 Jest/Mocha 测试用例并运行。
- 审查 Agent:检查代码规范和安全性。
-
-
沙箱与安全 (Sandbox)
- 风险:
execute_command极其危险,可能误删文件或运行恶意脚本。 - 方案:所有命令必须在 Docker 容器或 WebAssembly 沙箱中运行,限制文件系统权限和网络访问。
- 风险:
-
RAG 知识库
- 场景:基于公司内部私有 API 文档生成代码。
- 实现:预先索引内部 Wiki 和 Swagger 文档,Agent 在写代码前先检索最新的 API 定义。
五、结语:一人公司的黎明
过去,我们花费大量时间调试 Prompt,试图让模型“说对话”;
未来,我们将花费精力设计工具链、记忆机制和协作流程,让模型“做对事”。
未来的开发者可能不再需要庞大的团队。一个人,加上一个由多个专业 Agent 组成的“虚拟员工团队”,就能完成从产品设计、编码、测试到部署的全流程。
AI Agent 时代已经到来,现在,轮到你亲手打造你的第一个智能员工了。