AI Code编辑器到底是怎么做出来的?
读文件 → 修改 → 写回 → 跑测试 → 看失败原因 → 再修一轮
1. AI Code编辑器 是什么?
可以把它们理解为:在“聊天大脑”(LLM)外面,加了一层专门为“写代码”设计的壳和工具系统。
大模型本来只会“根据文本预测下一个词”,要变成编程助手,至少得多几个能力:
-
理解工程上下文
- 不只是你当前打开的一个文件
- 还要理解整个项目结构(目录、依赖、配置等)
-
能“动手操作代码”
- 不是只给你一段建议,而是直接替你批量改文件
- 能在多处同步修改、插入注释、重构等
-
能执行一些辅助工具
- 搜索代码
- 跑测试 / 编译
- 调试、查看错误日志
Claude Code / OpenAI CodeX 的“魔法体验”主要来自于后台这几件事做得比较好,而不是靠一个“更神秘的大模型”。
可以先看一个非常简化的架构图(适用于 Claude Code / OpenAI Code / Cursor,一家一套,思路类似):
┌──────────────────────────────┐
│ IDE / 客户端 │
│ - VSCode / Cursor / Web IDE │
└──────────────┬──────────────┘
│
▼
┌──────────────────────────────┐
│ AI 编程“中间层” │
│ - 会话管理(聊天记录) │
│ - 项目索引(文件、符号) │
│ - 上下文构造(prompt 构造) │
│ - 代码操作(apply edits) │
│ - 工具调用(搜索、运行) │
└──────────────┬──────────────┘
│(API 请求)
▼
┌──────────────────────────────┐
│ 大语言模型(LLM) │
│ - Claude / GPT-4.x / o3 等 │
│ - 有“工具调用”能力 │
└──────────────────────────────┘
1.1 IDE / 客户端层
例如:
- Cursor 自己的 IDE
- VSCode 插件(OpenAI / Claude Code)
- Web 版 IDE(像 GitHub Codespaces 内嵌的)
主要负责:
- 监控文件编辑、光标位置
- 把用户操作和当前文件内容传给后台
- 用“差异(highlight) / diff / inline” 的方式展示 AI 建议
1.2 “中间层”是核心:让模型“懂项目、能动手”
可以把中间层当作一个“AI 管家系统”:
-
项目索引 / 语义搜索
- 扫描项目所有文件,构建索引(关键词 + 向量)
- 支持按文件名、符号、语义搜索:
“找到所有调用getUserInfo的地方”
-
上下文构造(prompt engineering)
- 用户提问:“帮我重构这个函数”
- 中间层会去:
- 找到相关文件
- 找到相关类型 / 依赖函数
- 找到报错信息 / commit 记录(视产品而定)
- 然后拼成一条“大而有序的提示词(prompt)”发给 LLM:
- 用户目标
- 当前文件片段
- 相关文件摘要
- 项目结构/约定(如框架、代码风格)
-
工具调用(tool calling / function calling)
- 比如模型说:“我需要搜索全局对
User类型的定义” - 中间层提供一个
search_code(query)工具 - 模型调用 → 中间层去代码库里搜 → 把结果返回给模型
- 如此循环,模型像一个“会问问题的程序员”,一步步探索项目
- 比如模型说:“我需要搜索全局对
-
代码编辑 / diff 应用
- 模型返回的不是“整文件”,而是:
- 修改建议(diff)
- 要插入/替换的片段位置
- 中间层负责:
- 确认冲突
- 应用补丁(patch)
- 可能先展示预览,再让用户确认
- 模型返回的不是“整文件”,而是:
1.3 底层大模型(LLM)负责“脑力活”
- 训练好了的通用大模型(Claude 3.5, GPT-4.1/4.5, o3 等)
- 关键增强点:
- 单次上下文长度较大(几十万 token),才能容纳多文件
- 支持“工具调用”(function calling / tool use)
- 对代码做过特别多训练(代码语料 + RL 代码任务)
2. AI Code编辑器 实现思路分解
-
IDE 监控到:
- 当前文件路径
- 你选中的代码范围
- 编辑器里其他未保存的改动
-
中间层会收集:
- 当前文件的全内容/相关上下文
- 相关类型定义 / interface / class
- 相关文件(根据索引召回:比如调用链上游/下游)
-
构造 prompt,大致结构如下(简化版):
系统指令:
你是一个资深工程师,负责对项目进行安全的局部重构。
避免引入破坏性变化,注意继承现有风格与约定。
项目背景:
- 项目使用 React + TypeScript + Zustand
- 使用 ESLint 规则 XYZ
- ...
用户请求:
请重构下文选中的函数,目标是...
当前文件内容(含选中片段标记):
// ... 上文代码
// [BEGIN_SELECTED]
function ...
// [END_SELECTED]
// ... 下文代码
相关文件摘要:
文件A.ts:...
文件B.ts:...
输出约定:
只返回 JSON 格式的 patch 列表:
[
{
"file": "src/xxx.ts",
"range": {...},
"replacement": "..."
}
]
- 调用 LLM API,模型返回 JSON patch。
模型如何“自己找文件”和“自己看日志”? 依赖的是:工具调用(tool use)机制。
中间层先这样告诉模型(在系统提示词工具描述中):
{
"tools": [
{
"name": "search_code",
"description": "在当前项目中搜索代码",
"parameters": {
"type": "object",
"properties": { "query": { "type": "string"} },
"required": ["query"]
}
},
{
"name": "read_file",
"description": "读取指定文件的内容",
"parameters": { ... }
},
{
"name": "run_tests",
"description": "执行测试命令并返回输出",
"parameters": { ... }
}
]
}
- 模型在回答时,可以输出一段“调用工具”的结构
- 中间层看到这段结构,就去执行实际的函数(比如在本地跑
npm test) - 把执行结果(测试输出 / 搜索结果)再作为“工具结果”塞回给模型
- 模型再继续思考、修改方案
这就让模型:
从“纯文本预测机” → “能主动调用 IDE/CLI 的智能体”。
3. AI Code编辑器 核心技术
不管是 Claude Code、OpenAI Code 还是 Cursor,核心都依赖三类技术:
3.1 大模型本身(LLM)
- 训练数据中大量代码
- 使用专门的训练目标(例如:补全代码、修 bug、执行测试反馈)
- 通过 RL / 反馈学习强化“可执行性”、“正确性”
这决定了“它会不会写、会不会改”。
3.2 上下文管理 & 检索(RAG)
- 如何从超大的代码库中,只挑出“这一问真正相关的几部分”塞给模型
- 如何在有限上下文长度下,构造一个最佳 prompt:
- 用户意图
- 当前文件(重点标记改动区)
- 关键依赖文件的片段 / 摘要
- 错误信息 / 日志
- 项目风格和约定(例如 lint / 框架结构)
这决定了“它能否理解整个工程”。
3.3 工具调用 & 自动化操作
- 通过 tool calling,让模型“主动拉取信息”和“主动执行动作”
- 搜索代码
- 查看某个文件
- 执行测试 / 编译
- 通过 patch 机制精确修改文件
- 小范围 diff
- 多轮迭代(改完再跑测试、再修)
这决定了“它能否自己动手,闭环解决问题”。
4. 一个“最小但完整”的伪代码架构,
基于
run_command(命令行)- 少量高级工具:
read_file/write_file/apply_patch
完成一个闭环:
读文件 → 修改 → 写回 → 跑测试 → 看失败原因 → 再修一轮
4.1 工具定义(提供给 LLM 的 function / tool)
假设你用的是支持 tool calling 的模型(OpenAI / Claude 都类似),你需要给模型声明这些工具:
TOOLS = [
{
"name": "read_file",
"description": "读取项目中的一个文本文件内容",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径,相对于项目根目录"},
},
"required": ["path"],
},
},
{
"name": "write_file",
"description": "写入文件(覆盖),慎用。通常用于应用模型生成的修改。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"},
},
"required": ["path", "content"],
},
},
{
"name": "run_command",
"description": "在受限 shell 中执行命令,返回 stdout/stderr 和退出码。",
"parameters": {
"type": "object",
"properties": {
"cmd": {"type": "string", "description": "要执行的 shell 命令,比如 'pytest' 或 'npm test'"},
"timeout_sec": {"type": "integer", "description": "超时时间(秒)", "default": 120}
},
"required": ["cmd"],
},
},
{
"name": "apply_patch",
"description": "对已有文件内容应用一个统一 diff(unified diff)或简单 patch,返回新内容。",
"parameters": {
"type": "object",
"properties": {
"original_content": {"type": "string", "description": "文件当前的完整内容"},
"patch": {"type": "string", "description": "一个简单的 diff/patch 描述(例如 unified diff)"},
},
"required": ["original_content", "patch"],
},
},
]
这些是 后端真实实现的函数,模型只能通过 tool 调用它们。
4.2 后端工具实现(非常简化版本)
伪代码(Python 风格):
import subprocess
import textwrap
PROJECT_ROOT = "/path/to/your/project"
def tool_read_file(args):
path = args["path"]
with open(PROJECT_ROOT + "/" + path, "r", encoding="utf-8") as f:
return {"content": f.read()}
def tool_write_file(args):
path = args["path"]
content = args["content"]
with open(PROJECT_ROOT + "/" + path, "w", encoding="utf-8") as f:
f.write(content)
return {"ok": True}
def tool_run_command(args):
cmd = args["cmd"]
timeout_sec = args.get("timeout_sec", 120)
try:
result = subprocess.run(
cmd,
shell=True,
cwd=PROJECT_ROOT,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=timeout_sec,
text=True,
)
return {
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
}
except subprocess.TimeoutExpired as e:
return {
"returncode": -1,
"stdout": e.stdout or "",
"stderr": f"TIMEOUT: command exceeded {timeout_sec}s\n" + (e.stderr or ""),
}
def tool_apply_patch(args):
original = args["original_content"]
patch = args["patch"]
# 这里可以:
# - 自己实现一个极简 patch 应用器
# - 或者直接让模型返回“新的全文内容”,这时 apply_patch 可以是 no-op
# 为了简化 Demo,我们假设模型不搞复杂 diff,而是直接输出新文件内容,
# 那 apply_patch 只负责返回它。
# 实际产品里,这里会解析 unified diff,再对 original 应用。
# 在最小 Demo 中,可以忽略 patch,直接返回 patch 作为新内容:
new_content = patch # 假装 patch 就是完整新内容
return {"new_content": new_content}
说明:
真正的apply_patch会解析 diff,这里为了“最小 demo”,可以先让模型直接生成完整的新文件内容,那patch参数就等价于new_content;将来你再换成真正的 diff 应用器即可。
4.3 Agent 主循环(核心逻辑)
这个“主循环”负责:
- 收到用户的任务描述
- 多轮调用 LLM + 工具,直到:
- 测试通过,或者
- 达到最大迭代次数
伪代码:
from some_llm_client import call_llm_with_tools # 你自己封装的 LLM 调用
MAX_ITERATIONS = 5
SYSTEM_PROMPT = """
你是一个自动化代码修复 Agent。
目标:在给定项目中,修复指定的 bug 或让测试通过。
你可以使用这些工具:
- read_file: 阅读某个文件
- write_file: 将修改写入文件
- run_command: 执行测试命令(如 pytest / npm test)
- apply_patch: 根据你生成的 patch 更新文件内容
工作流建议(但你可以自己决策):
1. 先通过 read_file 查看相关文件内容。
2. 分析问题,生成修改方案。
3. 使用 apply_patch 生成新文件内容,再用 write_file 写回。
4. 用 run_command 运行测试命令(如 'pytest')。
5. 如果测试失败,分析 stderr,继续修正。
6. 在你认为问题已经解决、测试通过时,以总结说明结束。
非常重要:
- 不要进行危险操作(删除大量文件、运行与任务无关的命令)。
- 在一次改动中尽量改少量文件,分步进行。
"""
def code_agent_main(user_task: str):
"""
user_task: 用户的自然语言描述,例如:
"请修复这个项目中导致 pytest 失败的 bug,让所有测试通过。"
"""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_task},
]
for step in range(MAX_ITERATIONS):
# 调 LLM,看它要不要用工具
llm_response = call_llm_with_tools(
messages=messages,
tools=TOOLS,
)
if llm_response.type == "tool_call":
# 模型请求调用某个工具
tool_name = llm_response.tool_name
tool_args = llm_response.tool_args
# 根据 tool_name 分派到我们实现的函数
if tool_name == "read_file":
result = tool_read_file(tool_args)
elif tool_name == "write_file":
result = tool_write_file(tool_args)
elif tool_name == "run_command":
result = tool_run_command(tool_args)
elif tool_name == "apply_patch":
result = tool_apply_patch(tool_args)
else:
result = {"error": f"Unknown tool {tool_name}"}
# 把工具结果作为新的 message 反馈给 LLM
messages.append({
"role": "assistant",
"tool_call_id": llm_response.tool_call_id, # 视具体 API 而定
"content": None,
"name": tool_name,
})
messages.append({
"role": "tool",
"tool_call_id": llm_response.tool_call_id,
"content": json.dumps(result),
})
# 然后继续下一轮循环(让 LLM 基于工具结果再想下一步)
else:
# llm_response 是普通自然语言回答,不再请求工具
final_answer = llm_response.content
print("Agent 最终回答:")
print(final_answer)
break
else:
print("达到最大迭代次数,自动停止。")
call_llm_with_tools 的具体实现取决于你用哪家 API:
- OpenAI:
chat.completions+tools/tool_choice - Claude:
messages.create+tools
核心模式都是一样的:
-
把
messages+TOOLS发给 LLM -
如果它想用工具,会返回类似:
{ "type": "tool_call", "tool_name": "run_command", "tool_args": { "cmd": "pytest", "timeout_sec": 120 }, "tool_call_id": "xxx" } -
你执行工具 → 把结果塞回 messages
-
再调一次 LLM,重复直到它不再发出 tool_call,而是“正常回答”(表示完成任务)
4.4 一个典型“修 bug”流程(逻辑视角)
假设用户输入:
“请在这个 Python 项目中修复导致 pytest 失败的问题,让所有测试通过。”
一个完整的典型交互(逻辑层面,大致如下):
-
LLM:
- 调
run_command(cmd="pytest") - 结果:测试失败,stderr 里有堆栈:
E AttributeError: 'User' object has no attribute 'email'inuser_service.py:42
- 调
-
LLM:
- 调
read_file(path="user_service.py") - 看第 42 行附近代码
- 调
-
LLM:
- 分析:
User类在别处定义,没email字段 - 调
read_file(path="models.py")看User定义
- 分析:
-
LLM:
- 决定修改
models.py中的User类,增加email字段,或修掉错误访问 - 生成
patch(在最小 Demo 中可以是“新的完整 file 内容”) - 调
apply_patch(original_content=旧内容, patch=新内容)→ 得到new_content - 再调
write_file(path="models.py", content=new_content)
- 决定修改
-
LLM:
- 再次调
run_command("pytest") - 如果测试通过:返回总结说明,循环结束
- 如果仍然失败:解析新的 stderr,继续第 2 步那样迭代
- 再次调
整个过程就是:
读文件 → 修改 → 写回 → 跑测试 → 看失败原因 → 再修一轮
只不过每一步都通过 tool 调用来实现,agent 只负责“决策和生成 patch”。
更多推荐

所有评论(0)