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 的任何给定自动完成任务,任务是记录代码建议并将其保存到文件中。
我刚刚弄清楚如何通过Copilot.vim中提供的语言服务器调用 GitHub Copilot 。我还参考了几个社区实现,例如copilot.lua和LSP-copilot来理解这一点。
\nTL;DR:通过 语言服务器Copilot.vim调用 GitHub Copilot ,该服务器包含在其存储库的目录中。存储库中的那些 vim 脚本实际上充当语言服务器的客户端。dist/Copilot.vim
语言服务器本身是用 Node.js 编写的,并使用 stdio 与客户端通信。您只需下载该dist/目录并通过 运行语言服务器即可node dist/agent.js。然而,仅仅手动运行服务器很难使用,所以我用 Node.js 编写了一个简单的客户端来调用语言服务器并获取结果。
这是一个最小的示例,展示了如何通过从以下内容中提取的语言服务器以编程方式调用 GitHub Copilot Copilot.vim:
// @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();\nRun Code Online (Sandbox Code Playgroud)\nCopilot.vim此示例假设您\xe2\x80\x99 已通过也使用语言服务器(如或 )登录到 GitHub Copilot LSP-copilot。您可以查看LSP-copilot中的相关实现,了解如何以编程方式自动化登录过程(signInInitiate请求和signInConfirm请求)。
然后你可以在控制台中看到类似这样的内容:
\n\n如果您对LSP不太熟悉,可以参考LSP规范来了解每个请求和通知的含义。initialize像和这里这样的请求textDocument/didOpen是在 LSP 规范中定义的,而getCompletions是由 GitHub Copilot 语言服务器定义的自定义请求。您还可以使用textDocument/didChange或textDocument/didClose告诉语言服务器文档已更改或关闭(不要忘记version在每次更改时更新字段)。
其他一些请求比如getCompletionsCycling可以提供更多的补全,你也可以查看Copilot.vim或者我上面提到的相关社区实现来了解如何使用它们。
也应该可以编写一个 Python 客户端来调用语言服务器,但语言服务器本身是用 Node.js 编写的,因此编写 Node.js 客户端来调用它更容易。
\nGitHub 尚未公开发布其 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并将整个代码文件作为输入提供给它。这也有其自身的局限性。例如,您将无法向模型提供光标后面的内容。