你是否好奇 GitHub Copilot 如何知道你想写的内容?有时候它聪明得甚至好像读过你项目里其他文件一样,不要怀疑,它确实读过。这篇文章记录了我阅读一个对 Copilot 的逆向工程的笔记,一言以蔽之,Copilot 使用了 Jaccard 相似度获取用户最近访问过的页面里与当前编辑内容最相似的代码片段,并将其作为注释内容加入 prompt 中送给代码生成模型,以下是更加展开的讲解:
总体来说,copilot 分为两个部分:
先看一个拼装好的 prompt 示例:
{
"prefix": "# Path: codeviz\app.py\n# Compare this snippet from codeviz\predictions.py:\n# import json\n# import sys\n# import ti...,
"suffix": "if __name__ == '__main__':\r\n app.run(debug=True)",
"isFimEnabled": true,
"promptElementRanges": [
{ "kind": "PathMarker", "start": 0, "end": 23 },
{ "kind": "SimilarFile", "start": 23, "end": 2219 },
{ "kind": "BeforeCursor", "start": 2219, "end": 3142 }
]
}
一个实际的案例如上图所示
如果有后缀(suffix), 那么会启用插入模式 (模型使用 fill-in-middle 任务的 prompt),否则就是向后补全的模式
可以看到,前缀(prefix)中除了当前光标之前的文档内容,还包含着项目中另一个文件的代码,该 # Compare this snippet from codeviz\\predictions.py:
行及其后续行指代的是与当前文档内容相似的代码片段。模型也就是通过这些相似片段加深对代码上下文的理解https://thakkarparth007.github.io/copilot-explorer/posts/prompt-full
那么 prompt 是如何生成的呢,我们一步步来看看:
设置一些选项:
前缀计算:创建一个 Wishlist,添加不同的“元素”及其优先级,发生在 getPrompt(fs, curFile, promptOpts = {}, relevantDocs = [])
Wishlist 会按优先级和插入顺序排序,将元素添加到 prompt 中直到达到输入 token 数量限制 PromptWishlist.fulfill(tokenBudget)
一些选项,例如,NeighboringTabsOption 控制从其他文件中提取片段的力度。某些选项仅针对某些语言定义,如 LocalImportContextOption 仅针对 Typescript 定义。
后缀计算,只需要用光标后缀 token 填充到 token budget
继续往里看,重要模块:jaccard-scorer、window-matcher
getNeighborSnipates
主要的处理流程:忽略太长或空的文件 ️ 选最近 20 个文件 ️ 切片找出所有匹配的片段 (默认使用 bestMatch
,即每个文件找一个最匹配的片段) (注意:nbrMatcher
代表着正在键入的文件的类,每次和一个 curRelFile
(相关文件)去匹配出代码片段 snippets 列表和分数)️ 滤掉分数低于阈值的 ️ 按分数排序 ️ 取分数最高的 n 个 matches ️ 格式化
默认情况下,nbrMatcher 使用 “Fixed window Jaccard matcher”,将给定文件(要从中提取片段)切成固定大小的滑动窗口。然后,它会计算每个窗口和参考文件(用户正在键入的文件,存在 this.referenceTokens中)之间的 Jaccard 相似性。仅从每个“相关文件”返回最佳窗口(尽管存在返回前 K 代码段的规定,但从未使用过)。默认情况下,窗口大小是 60 行。
nbrMatcher
类默认是 fixedxxxxxmatcher
,构建该类时,使用用户当前键入的文件生成分词器和词元集合,以供 Jarccard 相似度计算;注意 getWindowsDelineadtions
输入的是文件内容列表,每行是一个列表元素,输出的也是列表,每个元素表示窗口的开始位置和结束位置,后面会用到;计算 Jaccard 相似性
function computeScore(e, t) {
const n = new Set();
e.forEach((e) => {
if (t.has(e)) {
n.add(e);
}
});
return n.size / (e.size + t.size - n.size);
}
Jaccard分数(Jaccard score),也称为Jaccard相似系数(Jaccard similarity coefficient),是一种用于比较两个集合相似性的度量指标。它衡量的是两个集合中共有元素占总元素数量的比例。
Jaccard分数的计算方法如下:
1. 定义两个集合的交集为共有元素的集合,定义两个集合的并集为两个集合中所有不重复的元素的集合。
2. Jaccard分数等于交集的大小除以并集的大小。
数学公式表示为:
Jaccard分数 = |A ∩ B| / |A ∪ B|
Jaccard分数的取值范围是0到1之间,其中0表示两个集合没有共有元素,1表示两个集合完全相同。分数越高表示两个集合越相似。
使用两个 UI 提供补全, (a) Inline/GhostText and (b) Copilot Panel
prompt 生成后,该模块会检查 prompt 是否足够好“good enough”决定是否发出请求调用模型。该模块会计算基于一个简单逻辑回归模型计算“contextual filter score”,如果分数低于阈值(默认为 15%),则不会发出请求。模型有 11 个简单特征,例如语言、以前的建议是否被接受/拒绝、先前接受/拒绝之间的持续时间、提示中最后一行的长度、光标前的最后一个字符等。模型权重包含在扩展代码本身中。included in the extension code itself.
例如,以右括号结尾的 prompt 得分就远低于以左括号结尾的 prompt,因为前者一般是用户输入完成了,不希望模型还进行补全。(在插件里还使用了一个机器学习模型也是挺让人惊讶的的)
Main module, Core logic 1, Core logic 2.
copilot 说:目前有 40% 的代码都是在 ai 帮助下写的?这是怎么测的呢?
在插入后的不同时间长度中检查被接受的建议是否存在于代码中;
精确搜索过于严格,因此测量编辑距离,如果插入和串口之间的单词级别编辑距离小于 50%,则建议被视为仍在代码中;
手机扫一扫
移动阅读更方便
你可能感兴趣的文章