ProPilot 开发手记(下):团队协作、性能优化与测试评估
从独狼到团队
前期只有一个人在写代码,后端已经积累了约 2500 行代码。小学期正式开始后迎来了 5 个人的团队——前端同学、后端同学和模型侧同学。
第一周的进展:
- VSCode 前端基本完成
- 后端路由部分完成
- 向量数据库和模型嵌入部分完成性能测试与选型
- 模型数据管道搭建完成
- Prompt 初次迭代
- 文档上传与解析部分完成
代码量从前端 4000+ 行(两个前端)加上后端 6000+ 行,迅速突破了一万行。
后端架构概览
项目后端采用了清晰的模块化设计:
├── api/
│ ├── routes/ # 路由层:propilot, users, milvus 等
│ ├── services/ # 业务逻辑层
│ │ ├── completion_service.py # 补全核心
│ │ ├── doc_service.py # 文档处理
│ │ ├── search_engine_service.py # 搜索引擎
│ │ ├── image_service.py # 图片搜索
│ │ ├── web/ # 网络搜索(jina, langchain)
│ │ └── downloader/ # 文档下载器
│ ├── dto/ # 数据传输对象
│ └── models.py # ORM 模型
├── search/
│ └── rag/ # RAG 管道:chunker, hierarchy, prehandler
├── finetune/ # 模型微调相 关
├── coherence/ # 连贯性模型
├── prompts/ # Prompt 模板
├── tests/ # 测试:API、Benchmark、ndcg 评估等
├── utils/ # 工具:logger, parser, reranker 等
├── milvus/ # 向量数据库
├── mineru/ # 文档解析(Docker)
└── typesense/ # 搜索引擎
系统级性能瓶颈:LLM 的困境
随着系统跑通,一个严峻的问题浮出水面——全部的时延和精度压力都在 LLM 上。
不算 LLM 的其他地方可以做到 100 QPS+(即使有 rerank,reranker 的加速比 LLM 简单多了),但整个系统只要有 1 QPS 就是成功。
两难困境
补全系统有两种 LLM 调用场景:
场景一:给定多篇参考文档 + 用户上下文,LLM 总结生成补全。如果用 32B 以上的模型,基本能很好地理解参考文档;但本地跑不起 32B 的批量推理,只能用 7B 小模型,性能下滑严重。
场景二:如果 reranker 能足够精确(top-k = 1 甚至 0),LLM 的任务就变成简单的句子改写,对小模型会友好得多。但问题是 reranker 只考虑相关性的话,筛选力度不够,top-k 还是得比较大。
Tokenization 的坑
一个更深层的问题是 tokenization:
用户的上下文可能断在奇怪的地方。例如 "另一部分问题,可能是更加严","严峻" 这个词本来是一个 token,被拆开后变成了 "加严",而要从 "加严" 生成 "峻" 是一个相对困难的事情。
如果让 LLM 复读上文再输出,一是浪费 token,二是小模型会抄不准确(32B 以上基本没有这个问题,但 7B 很明显)。
量化也救不了
尝试了 Int4 量化来跑更大的模型(如 Qwen3-30B-A3B),结果发现量化直接把模型搞得不像人类,输出变成了古神的低语。
最终的选型结论:Mistral Small 的 FP8 量化版本在 32B 以下表现最好,远强于 Qwen、GLM、Phi 等同规模模型。
Copilot 的缓存设计参考
在性能优化过程中,深入分析了 VS Code Copilot 的 InlineEdits(Tab 补全)技术实现,发现其缓存机制非常精巧,对我们很有参考价值:
三层缓存架构
- 最近显示缓存(RecentlyShownCache):< 50ms,用户快速移动光标时立即恢复之前的建议
- 主缓存(NextEditCache):< 300ms,直接命中或通过"重定基"技术调整旧建议
- 拒绝列表:避免重复提供已拒绝的建议
重定基(Rebasing)技术
这是最核心的创新——即使用户在建议生成后继续编辑,系 统也能智能地调整缓存的建议:
原始文档: "function add"
AI 建议: "function add(a, b) { return a + b; }"
用户继续输入: "function addNumbers"
重定基结果: "function addNumbers(a, b) { return a + b; }"
实现上采用了三方合并算法:将大编辑分解为原子操作,然后对 AI 编辑和用户编辑进行合并,解决位置偏移和冲突。
赛马机制
同时向 LLM 提供者和诊断提供者发起请求,选择最佳结果:
- 诊断建议优先(基于具体代码问题,准确率高)
- LLM 建议作为后备
- 如果 LLM 快速返回空结果,额外等待诊断提供者 1 秒
对我们的启示
这套缓存设计对我们有很强的借鉴意义。尤其是在 RAG 场景下,如果事先把 chunk 都过一遍 vLLM 的 KV Cache,补全时 RAG 得到的 chunk 全部都能命中缓存,这些 chunk 的 KV 计算几乎可以省掉。
不过 GPU 的 KV Cache 容量有限(实测 L40 约 45 万 tokens),全量缓存不太现实。社区建议使用 LMCache 将 KV Cache offload 到 CPU 内存或硬盘,实现冷热分离。
前端开发经验
VSCode Extension 的坑
VSCode 扩展的定制能力其实很差。很多地方有安全性限制,没法自定义。例如 Copilot 的侧边栏实际上是 TreeView + Markdown string,只能嵌入预定义的东西(如图片),不支持自定义样式。
而且 VSCode 里开关 Copilot 太麻烦了,插件冲突是巨大的问题。很多逻辑必须裸写 TypeScript,没法用 React。
Office Add-in 比 WPS JS 好用
前端同学发现 Office 的开发文档远好于 WPS JS,而且 WPS 的文档完全就是抄 Office 的(三四年没更新了)。于是决定改开发 Office 插件。
Claude 写前端
一个共识:Claude 写传统 React 前端如入无人之境。Office 这种自带 React 框架的情况更是 AI 直接法力无边。前端核心代码其实也就六七百行(一些 AI 处理不了的逻辑),剩下的 AI 都能搞定。
测试评估框架
核心问题:如何评估补全系统?
开发补全应用时,一个必须面对的问题是:如何评估整个系统的成效?当然可以让 LLM 评估,但耗费大量 token 不说,整体迭代速度也会慢下来,还有组件太多调参困难的问题。