如何以编程方式调用 Github Copilot?

Exp*_*ing 16 python github-copilot

我目前正在探索 GitHub Copilot,并且我对以编程方式使用它感兴趣,即从代码中调用它。据我了解,GitHub Copilot 是一个 IDE 插件,这让我想知道如何自动化或以编程方式控制它。我们知道 Copilot 作为法学硕士在幕后使用 OpenAI 模型。

GitHub Copilot 不提供 API 访问权限来以编程方式控制它。

澄清:

  • 值得注意的是,该插件一旦下载并安装,就会自动完成我的代码。Copilot 在幕后使用了 OpenAI 模型,例如 gpt-3.5 或 gpt-4。我非常了解 OpenAI 聊天或文本完成模型。所以这不是我的问题

  • 我的问题是如何以自动方式捕获 Copilot 提供的前三个建议。

  • 例如,对于 Copilot 的任何给定自动完成任务,任务是记录代码建议并将其保存到文件中。

Sno*_*lyt 8

我刚刚弄清楚如何通过Copilot.vim中提供的语言服务器调用 GitHub Copilot 。我还参考了几个社区实现,例如copilot.luaLSP-copilot来理解这一点。

\n

TL;DR:通过 语言服务器Copilot.vim调用 GitHub Copilot ,该服务器包含在其存储库的目录中。存储库中的那些 vim 脚本实际上充当语言服务器的客户端。dist/Copilot.vim

\n

语言服务器本身是用 Node.js 编写的,并使用 stdio 与客户端通信。您只需下载该dist/目录并通过 运行语言服务器即可node dist/agent.js。然而,仅仅手动运行服务器很难使用,所以我用 Node.js 编写了一个简单的客户端来调用语言服务器并获取结果。

\n

这是一个最小的示例,展示了如何通过从以下内容中提取的语言服务器以编程方式调用 GitHub Copilot Copilot.vim

\n
// @ts-check\n\nconst { spawn } = require("node:child_process");\n\nconst server = spawn("node", ["agent.js"]);\n// use `fork` in `node:child_process` is also OK\n// const server = fork("agent.js", { silent: true });\n// `{ silent: true }` is to make sure that data sent to stdio will be returned normally\n\n/**\n * Send a LSP message to the server.\n */\nconst sendMessage = (/** @type {object} */ data) => {\n  const dataString = JSON.stringify({ ...data, jsonrpc: "2.0" });\n  const contentLength = Buffer.byteLength(dataString, "utf8");\n  const rpcString = `Content-Length: ${contentLength}\\r\\n\\r\\n${dataString}`;\n  server.stdin.write(rpcString);\n};\n\nlet requestId = 0;\n/** @type {Map<number, (payload: object) => void | Promise<void>>} */\nconst resolveMap = new Map();\n/** @type {Map<number, (payload: object) => void | Promise<void>>} */\nconst rejectMap = new Map();\n\n/**\n * Send a LSP request to the server.\n */\nconst sendRequest = (/** @type {string} */ method, /** @type {object} */ params) => {\n  sendMessage({ id: ++requestId, method, params });\n  return new Promise((resolve, reject) => {\n    resolveMap.set(requestId, resolve);\n    rejectMap.set(requestId, reject);\n  });\n};\n/**\n * Send a LSP notification to the server.\n */\nconst sendNotification = (/** @type {string} */ method, /** @type {object} */ params) => {\n  sendMessage({ method, params });\n};\n\n/**\n * Handle received LSP payload.\n */\nconst handleReceivedPayload = (/** @type {object} */ payload) => {\n  if ("id" in payload) {\n    if ("result" in payload) {\n      const resolve = resolveMap.get(payload.id);\n      if (resolve) {\n        resolve(payload.result);\n        resolveMap.delete(payload.id);\n      }\n    } else if ("error" in payload) {\n      const reject = rejectMap.get(payload.id);\n      if (reject) {\n        reject(payload.error);\n        rejectMap.delete(payload.id);\n      }\n    }\n  }\n};\n\nserver.stdout.on("data", (data) => {\n  /** @type {string} */\n  const rawString = data.toString("utf-8");\n  const payloadStrings = rawString.split(/Content-Length: \\d+\\r\\n\\r\\n/).filter((s) => s);\n\n  for (const payloadString of payloadStrings) {\n    /** @type {Record<string, unknown>} */\n    let payload;\n    try {\n      payload = JSON.parse(payloadString);\n    } catch (e) {\n      console.error(`Unable to parse payload: ${payloadString}`, e);\n      continue;\n    }\n\n    handleReceivedPayload(payload);\n  }\n});\n\nconst wait = (/** @type {number} */ ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/* Main */\nconst main = async () => {\n  // Wait for server to start\n  await wait(1000);\n\n  // Send `initialize` request\n  await sendRequest("initialize", {\n    capabilities: { workspace: { workspaceFolders: true } },\n  });\n  // Send `initialized` notification\n  sendNotification("initialized", {});\n\n  // Send `textDocument/didOpen` notification\n  sendNotification("textDocument/didOpen", {\n    textDocument: {\n      uri: "file:///home/fakeuser/my-project/test.py",\n      languageId: "python",\n      version: 0, // The change count (i.e. version) of the document\n      text: "def hello():\\n" + "    print(\'hello, w",\n    },\n  });\n\n  // Send `getCompletions` request to get completions at line 1, character 19\n  // (i.e. after `print(\'hello, w`)\n  const completions = await sendRequest("getCompletions", {\n    doc: {\n      version: 0, // Should be the same as the latest version of the document\n      position: { line: 1, character: 19 },\n      uri: "file:///home/fakeuser/my-project/test.py",\n    },\n  });\n  console.log("Completions:", completions);\n};\n\nvoid main();\n
Run Code Online (Sandbox Code Playgroud)\n

Copilot.vim此示例假设您\xe2\x80\x99 已通过也使用语言服务器(如或 )登录到 GitHub Copilot LSP-copilot。您可以查看LSP-copilot中的相关实现,了解如何以编程方式自动化登录过程(signInInitiate请求和signInConfirm请求)。

\n

然后你可以在控制台中看到类似这样的内容:

\n

控制台中的结果

\n

如果您对LSP不太熟悉,可以参考LSP规范来了解每个请求和通知的含义。initialize像和这里这样的请求textDocument/didOpen是在 LSP 规范中定义的,而getCompletions是由 GitHub Copilot 语言服务器定义的自定义请求。您还可以使用textDocument/didChangetextDocument/didClose告诉语言服务器文档已更改或关闭(不要忘记version在每次更改时更新字段)。

\n

其他一些请求比如getCompletionsCycling可以提供更多的补全,你也可以查看Copilot.vim或者我上面提到的相关社区实现来了解如何使用它们。

\n

也应该可以编写一个 Python 客户端来调用语言服务器,但语言服务器本身是用 Node.js 编写的,因此编写 Node.js 客户端来调用它更容易。

\n


Don*_*iur 1

GitHub 尚未公开发布其 API。

不过,据推测 GitHub Copilot 使用了 OpenAI 的 Codex(现已弃用)。据此您可以使用 OpenAI 的聊天模型来完成代码、建议等。尽管根据我的经验,响应时间各不相同。此外,不能保证它只会输出代码。

检查下面的例子;

import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
    {
     "role": "system",
     "content": "You are a helpful assistant. Assistant will output only and only code as a response."
    },
    {
      "role": "user",
      "content": "Write a Python function that takes as input a file path to an image, loads the image into memory as a numpy array, then crops the rows and columns around the perimeter if they are darker than a threshold value. Use the mean value of rows and columns to decide if they should be marked for deletion."
    }
  ],
  temperature=0,
  max_tokens=1024
)
Run Code Online (Sandbox Code Playgroud)

哪个将输出;

import numpy as np
from PIL import Image

def crop_dark_borders(image_path, threshold):
    # Load the image
    image = Image.open(image_path)
    # Convert the image to a numpy array
    image_array = np.array(image)
    
    # Calculate the mean value of each row and column
    row_means = np.mean(image_array, axis=1)
    col_means = np.mean(image_array, axis=0)

    ...
Run Code Online (Sandbox Code Playgroud)

编辑; 再想一想,我不会使用ChatCompletion这个,因为该任务根本不是基于聊天的。相反,我会使用Completion并将整个代码文件作为输入提供给它。这也有其自身的局限性。例如,您将无法向模型提供光标后面的内容。

  • 做了一些挖掘。捕获数据包对我来说是不行的,无法解密 TLS。所以我检查了 neovim 的 Copilot 支持。他们提到要安装 Nodejs,这很奇怪。然后我找到了一个自定义插件 [LSP-Copilot](https://github.com/TerminalFi/LSP-copilot/tree/78c3f492f63ccfa755b1c7716f548333cfd0e1cc),它需要您安装 [LSP](https://github.com/sublimelsp) /LSP),它使用 JSON-RPC 与 LSP-Copilot 进行通信(对于 neovim 也是如此)。浏览了 LSP-Copilot 的源代码,它看起来很有希望。分享这个是因为有人也可能会有所帮助。 (3认同)