AI 代码补全 — 从原理到实现
揭秘 Copilot / Cursor 背后的技术,用 Java 从零实现一个代码补全引擎。
1. 引入:你每天都在用的 AI 补全
当你在 IDE 中输入 name. 按下 Tab 的那一刻,背后发生了什么?
public Optional<User> findUserById(String id) {
String name = "test";
List<String> tags = new ArrayList<>();
name.| // ← 光标在这里
// IDE 弹出: length(), charAt(), substring(), contains()...
// AI 建议: isEmpty() ? "" : name.trim()
}
IDE 是怎么知道 name 是 String 类型的?补全列表为什么 length() 排第一?AI 生成的整行代码从哪来?为什么有时 10ms,有时 500ms?
今天我们从零实现一个代码补全引擎,回答这些问题。
2. 代码补全的演进
| 阶段 | 时间 | 技术 | 能力 |
|---|---|---|---|
| 手动查文档 | 2000s | — | 靠记忆 |
| IDE 自动补全 | 2010s | 符号表 + 类型系统 | 当前作用域变量/方法 |
| 智能排序 | 2018 | N-gram / LSTM | 按使用频率排序 |
| AI 生成 | 2021+ | Transformer / LLM | 生成整行/整段代码 |
| 自主编程 | 2025+ | Agent + Spec | 理解需求,自主实现 |
我们的 Demo 覆盖了传统补全 + AI 补全的完整链路。
3. 主流 AI 编程工具对比
| 工具 | 模型 | AST 方案 | 特点 |
|---|---|---|---|
| GitHub Copilot | GPT-4 | Tree-sitter | 市占率最高,生态完善 |
| Cursor | GPT-4 / Claude | Tree-sitter | AI-Native IDE,体验最好 |
| 通义灵码 | 通义千问 | 自研 | 中文友好,免费 |
| TabNine | 自研 + GPT | 自研 | 支持本地模型 |
| 我们的 Demo | DeepSeek / 可配置 | JavaParser | 完整链路演示 |
4. 系统架构
GUI / CLI(触发补全)
▼ CompletionRequest
CompletionEngine(核心调度)
▼
┌─────────┬──────────┬─────────┐
│ Cache │ Context │ Ranker │
│ LRU缓存 │ 上下文采集 │ 排序合并 │
└─────────┴──────────┴─────────┘
▼
┌────────────┬──────────┬─────────┐
│LocalSymbol │ Template │ LLM │
│ Trie匹配 │ 代码模板 │ 大模型API│
└────────────┴──────────┴─────────┘
▼
┌──────────┬──────────────┬──────────┐
│Tokenizer │AST(JavaParser)│Trie Tree│
└──────────┴──────────────┴──────────┘
设计模式:
- Strategy — Provider 可插拔替换
- Pipeline — 请求经过 6 个处理阶段
- Builder — Request / Item 灵活构造
- Visitor — AST 遍历提取符号
技术栈:Java 17、JavaParser(真实 AST)、Trie 前缀树、LRU Cache、OkHttp + Jackson(LLM API)、Swing(GUI)
5. 一次补全请求的完整链路
以用户输入 name. 为例:
[1] 构造请求 CompletionRequest { line=25, prefix="", trigger='.' }
[2] 缓存检查 cache.get("UserService.java:25:") → MISS
[3] 上下文采集 ContextCollector.collect()
├─ ASTAnalyzer.analyzePosition() → MEMBER_ACCESS
├─ ASTAnalyzer.findEnclosingClass() → "UserService"
├─ ASTAnalyzer.extractSymbols() → [name:String, tags:List, count:int]
└─ CodeTokenizer → isAfterDot = true
[4] Provider 调用
├─ LocalSymbol: inferType("name") → String → 15 个方法
├─ Template: 不适用(MEMBER_ACCESS 场景)
└─ LLM: buildPrompt → API 调用 → 1~3 个建议
[5] 排序合并 去重 + 多维度加权 → Top 10
[6] 缓存写入 + 返回 CompletionResponse

1390

被折叠的 条评论
为什么被折叠?



