总览

你是否好奇 GitHub Copilot 如何知道你想写的内容?有时候它聪明得甚至好像读过你项目里其他文件一样,不要怀疑,它确实读过。这篇文章记录了我阅读一个对 Copilot 的逆向工程的笔记,一言以蔽之,Copilot 使用了 Jaccard 相似度获取用户最近访问过的页面里与当前编辑内容最相似的代码片段,并将其作为注释内容加入 prompt 中送给代码生成模型,以下是更加展开的讲解:

总体来说,copilot 分为两个部分:

  • 客户端:VS Code 扩展收集你键入的任何内容,拼装成 prompt,并将其发送到类似 Codex 的模型。无论模型返回什么,它都会显示在编辑器中。
  • 模型端:类似 Codex (现在或许是更先进的版本)的模型接受提示并返回完成提示的建议。

prompt 工程

prompt 示例

先看一个拼装好的 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),否则就是向后补全的模式

      • 根据 codex 官方:插入模式需要使用特殊 token 组装成正确的 prompt 格式,需要更大的 max_length,需要较大温度多采样几次直到 stop_reason 是 'stop',以便更好地连接到后缀代码
    • 可以看到,前缀(prefix)中除了当前光标之前的文档内容,还包含着项目中另一个文件的代码,该 # Compare this snippet from codeviz\\predictions.py: 行及其后续行指代的是与当前文档内容相似的代码片段。模型也就是通过这些相似片段加深对代码上下文的理解https://thakkarparth007.github.io/copilot-explorer/posts/prompt-full

准备 prompt 的过程

那么 prompt 是如何生成的呢,我们一步步来看看:

  1. Entry point:对给定文档和光标位置提取 prompt。 prompt 生成的主要入口点是 extractPrompt(ctx, doc, insertPos)
  2. 从 VSCode 查询当前文档的相对路径和语言 ID getPromptForRegularDoc(ctx, doc, insertPos).

  1. 获取相关文档:借助 VSCode 的插件 API 等能力,拿到最近访问的 20 个相同语言的文件,参阅 getPromptHelper(ctx, docText, insertOffset, docRelPath, docUri, docLangId)

  1. 设置一些选项:

    1. suffixPercent:百分之多少的 token 用于后缀
    2. fimFuffixLengthThreshold:启用 fill-in-middle 的最小后缀长度,默认 -1,也就是后缀里面有字符就会启用
  2. 前缀计算:创建一个 Wishlist,添加不同的“元素”及其优先级,发生在 getPrompt(fs, curFile, promptOpts = {}, relevantDocs = [])

    1. 六种不同的元素BeforeCursor, AfterCursor, SimilarFile, ImportedFile, LanguageMarker, PathMarker 

      1. languageMarker:e.g., #!/usr/bin/env python3 for python, or Language:

      2. PathMarker:当前文件的 path

      3. ImportedFile:add local import context to the wishlist (似乎只对 typescript 使用)

      4. similarFile:Compare this snippet from path/to/file:

        1. getNeighborSnippers:找出符合条件的片段
        2. addSnippersToWishlist:遍历前者,加入 Wishlist
  1. Wishlist 会按优先级和插入顺序排序,将元素添加到 prompt 中直到达到输入 token 数量限制 PromptWishlist.fulfill(tokenBudget)
  2. 一些选项,例如,NeighboringTabsOption 控制从其他文件中提取片段的力度。某些选项仅针对某些语言定义,如 LocalImportContextOption 仅针对 Typescript 定义。
  3. 后缀计算,只需要用光标后缀 token 填充到 token budget

片段提取,Jaccard 评分

继续往里看,重要模块:jaccard-scorer、window-matcher

从最近文档提取片段

getNeighborSnipates 主要的处理流程:忽略太长或空的文件 ️ 选最近 20 个文件 ️ 切片找出所有匹配的片段 (默认使用 bestMatch ,即每个文件找一个最匹配的片段) (注意:nbrMatcher 代表着正在键入的文件的类,每次和一个 curRelFile (相关文件)去匹配出代码片段 snippets 列表和分数)️ 滤掉分数低于阈值的 ️ 按分数排序 ️ 取分数最高的 n 个 matches ️ 格式化

nbrMatcher

默认情况下,nbrMatcher 使用 “Fixed window Jaccard matcher”,将给定文件(要从中提取片段)切成固定大小的滑动窗口。然后,它会计算每个窗口和参考文件(用户正在键入的文件,存在 this.referenceTokens中)之间的 Jaccard 相似性。仅从每个“相关文件”返回最佳窗口(尽管存在返回前 K 代码段的规定,但从未使用过)。默认情况下,窗口大小是 60 行。

  • 使用的 nbrMatcher 类默认是 fixedxxxxxmatcher,构建该类时,使用用户当前键入的文件生成分词器和词元集合,以供 Jarccard 相似度计算;注意 getWindowsDelineadtions 输入的是文件内容列表,每行是一个列表元素,输出的也是列表,每个元素表示窗口的开始位置和结束位置,后面会用到;

  • fixedxxxxxmatcher 继承的原始类型,默认使用 findBestMatch,输入 e (待匹配的相关文件),按行切分成列表,另一边通过 retrieveAllSnippets 将 相关文件 切分成固定窗口的片段,按相似度得分降序排列,最后输出相似度最高的片段

  • 切分片段,计算相似度,并排序的代码如下,首先尝试获取缓存中的代码片段,如果缓存为空,对每行代码分词存入 u。注意外面的 e 和里面的 e 意义不一样(折磨。。。)

  • 分词类,缓存类

  • 计算 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

Inline/GhostText

Main module

  • 提供少量建议(1-3)快速响应
  • 清除快速输入时的抖动
  • 限制某些情况下的请求:如果用户位于一行中间,则仅当光标右侧的字符为空格、右大括号等时,才会发送请求。

通过 Contextual Filter 防止 poor requests

prompt 生成后,该模块会检查 prompt 是否足够好“good enough”决定是否发出请求调用模型。该模块会计算基于一个简单逻辑回归模型计算“contextual filter score”,如果分数低于阈值(默认为 15%),则不会发出请求。模型有 11 个简单特征,例如语言、以前的建议是否被接受/拒绝、先前接受/拒绝之间的持续时间、提示中最后一行的长度、光标前的最后一个字符等。模型权重包含在扩展代码本身中。included in the extension code itself.

例如,以右括号结尾的 prompt 得分就远低于以左括号结尾的 prompt,因为前者一般是用户输入完成了,不希望模型还进行补全。(在插件里还使用了一个机器学习模型也是挺让人惊讶的的)

Copilot Panel

Main moduleCore logic 1Core logic 2.

不显示无帮助的补全

  1. 如果输出重复,建议被丢弃 repeatitive
  2. 如果用户已经输入建议了,丢弃该建议 already typed

遥测

copilot 说:目前有 40% 的代码都是在 ai 帮助下写的?这是怎么测的呢?

如何评测哪些建议被接受?

在插入后的不同时间长度中检查被接受的建议是否存在于代码中;

精确搜索过于严格,因此测量编辑距离,如果插入和串口之间的单词级别编辑距离小于 50%,则建议被视为仍在代码中;

参考

https://thakkarparth007.github.io/copilot-explorer/posts/copilot-internals#secret-sauce-1-prompt-engineering

❤️ GitHub Copilot 读心术揭秘,Copilot 逆向工程笔记的更多相关文章

  1. 《Github入门与实践》读书笔记 蟲咋先生的追求之旅(上)

    <Github入门与实践>作者: [日] 大塚弘记 译者:支鹏浩/刘斌   简介 本书从Git的基本知识和操作方法入手,详细介绍了GitHub的各种功能,GitHub与其他工具或服务的协作 ...

  2. [css]《css揭秘》学习笔记(一)

    一.background-clip属性 <html> <head> <meta charset="utf-8"> <title>背景 ...

  3. 《css揭秘》读书笔记

    第一章 引言 css编码技巧 在引言中,作者提到使用em与inherit来实现css代码的简洁与可维护性.但是根据本司机两年的开发经验来看,在实际开发中很少来使用em这个单位.如何用以及何时去使用,还 ...

  4. C++反汇编与逆向分析技术揭秘

    C++反汇编-继承和多重继承   学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 一.单类继承 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的 ...

  5. Github 安全类Repo收集整理

    作者:天谕链接:https://zhuanlan.zhihu.com/p/21380662来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.刚好这两天对之前github上关 ...

  6. github上最全的资源教程-前端涉及的所有知识体系

    前面分享了前端入门资源汇总,今天分享下前端所有的知识体系. 个人站长对个人综合素质要求还是比较高的,要想打造多拉斯自媒体网站,不花点心血是很难成功的,学习前端是必不可少的一个环节, 当然你不一定要成为 ...

  7. Java8——快速入门手册(学习笔记)

    github博文传送门 Java8特性学习笔记 Java8中新增了许多的新特性,在这里本人研究学习了几个较为常用的特性,在这里与大家进行分享.(这里推荐深入理解Java 8用于理解基础知识)本文分为以 ...

  8. 企业私有源代码上传github致入侵之大疆案判决了

    事件简单回顾: 1.2017年8月28日,大疆宣布“大疆威胁识别奖励计划”,最高3万美元: 2.然而在此之前,大疆农业事业部某员工将企业私有源代码上传到了github: 3.就职于大疆竞对公司Depa ...

  9. Celery配置实践笔记

    说点什么: 整理下工作中配置celery的一些实践,写在这里,一方面是备忘,另外一方面是整理成文档给其他同事使用. 演示用的项目,同时也发布在Github上: https://github.com/b ...

  10. Github & DMCA Takedown Policy

    Github & DMCA Takedown Policy Digital Millennium Copyright Act 数字千年版权法案 https://help.github.com/ ...

随机推荐

  1. 2021-10-23:位1的个数。编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1‘ 的个数(也被称为汉明重量)。提示:请注意,在某些语言(如 Java)中

    2021-10-23:位1的个数.编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量).提示:请注意,在某些语言(如 Java)中 ...

  2. Jenkins - 页面汉化

    Jenkins - 页面汉化 前言 对于不懂英文的人来讲,尤其是第一次使用Jenkins环境,看全英文的Jenkins页面是十分困难的: Jenkins对于不懂英文的用户有做汉化的插件包,但是汉化的不 ...

  3. exclude查询时出掉或排除某个条件的信息

    exclude查询时出掉或排除某个条件的信息 print(Student.objects.all().exclude(nickname='A')

  4. JDBC-Utils层的简单运用

    项目中JDBC的Utils层运行需要以下六个步骤 //1.定义属性为空 private static String driver = null; private static String url = ...

  5. Mybatis查询

    查询 查询的数据为单条实体类 使用实体类进行接受即可,或者使用list,map接口均可.后面两者比较浪费 使用实体类接受 mapper接口: User selectUserById(int useri ...

  6. 一分钟学一个 Linux 命令 - ls

    前言 大家好,我是 god23bin.今天我给大家带来的是 Linux 命令系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天,我们要介绍的是一个常用而又强大的命令:ls(list). 什 ...

  7. 20个Golang片段让我不再健忘

    前言 本文使用代码片段的形式来解释在 go 语言开发中经常遇到的小功能点,由于本人主要使用 java 开发,因此会与其作比较,希望对大家有所帮助. 1. hello world 新手村的第一课,毋庸置 ...

  8. BGP 反射器;BGP联盟

    目录 BGP反射器 实验拓扑 实验需求 实验步骤 在R2上查看BGP邻居表 在R2上做反射器 在R3上查看1.1.1.1 路由表,进行观察 在R3上查看1.1.1.1 路由详细信息 BGP联盟实验 实 ...

  9. 【技术积累】JavaSciprt中的函数【一】

    什么是函数?如何声明函数? JavaScript中的函数是一段可重复使用的代码块,它可以接受输入并返回输出. 在JavaScript中,函数是一种特殊的对象,因此可以将其存储在变量中,将其作为参数传递 ...

  10. Win11右键菜单改回win10

    右键以管理员身份运行终端 reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\In ...