获取 GitHub Copilot Chat Completions API 完整响应及文档注入
2024-12-15 12:24:44
获取GitHub Copilot Chat Completions API响应
在使用GitHub Copilot Chat Completions API构建扩展时,直接获取 Copilot LLM 响应文本是一个常见需求。上述代码示例虽然实现了流式响应,但获取完整文本内容还需要进一步处理。 本文将探讨如何解决这个问题。
问题分析
现有代码通过 fetch API 从 GitHub Copilot Chat Completions API 获取流式响应。 流式响应的好处是能即时显示结果,提高用户体验。但如果需要对完整响应进行处理,例如注入文档片段,则需要将流转换为完整的文本字符串。 问题在于,尝试使用 Response.text()
方法获取文本时,得到的是一系列包含 data:
前缀的 Server-Sent Events (SSE) 格式的事件消息,而不是纯粹的文本。这些事件消息中包含了部分响应内容、元数据以及内容过滤结果等。
解决方案
解决这个问题的关键在于正确解析 SSE 格式的响应,并将多个事件消息中的文本内容拼接起来。以下提供两种方案:
方案一: 使用 JavaScript 解析 SSE 流
这种方案使用 JavaScript 遍历并解析 SSE 流,提取其中的 delta.content
并拼接成完整的文本。
代码示例:
async function getCopilotResponse(messages, tokenForUser) {
const copilotLLMResponse = await fetch(
"https://api.githubcopilot.com/chat/completions",
{
method: "POST",
headers: {
authorization: `Bearer ${tokenForUser}`,
"content-type": "application/json",
},
body: JSON.stringify({
messages,
stream: true,
}),
}
);
if (!copilotLLMResponse.ok) {
throw new Error(`HTTP error! status: ${copilotLLMResponse.status}`);
}
if (!copilotLLMResponse.body) {
throw new Error("Empty response body");
}
const reader = copilotLLMResponse.body.getReader();
const decoder = new TextDecoder();
let responseText = "";
let partialLine = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
partialLine += decoder.decode(value);
let eolIndex;
while ((eolIndex = partialLine.indexOf('\n')) >= 0) {
const line = partialLine.slice(0, eolIndex);
partialLine = partialLine.slice(eolIndex + 1);
if (line.startsWith("data:")) {
const data = line.slice(5).trim(); // remove 'data: ' prefix
if (data === '[DONE]') break;
try {
const parsedEvent = JSON.parse(data);
responseText += parsedEvent?.choices?.[0]?.delta?.content || "";
} catch(e) {
console.error("Error parsing JSON:", e, "Invalid JSON String",data);
continue; // skip the broken line
}
}
}
}
return responseText;
}
操作步骤:
- 将以上代码复制到你的扩展代码中。
- 调用
getCopilotResponse(messages, tokenForUser)
函数,传入用户消息messages
和用户 TokentokenForUser
。 - 该函数会返回一个 Promise,resolve 后得到完整的 Copilot 响应文本。
原理与安全建议:
此方案利用了 TextDecoder
和字符串操作来解析 SSE 格式的响应。 reader.read()
方法从流中读取数据块,TextDecoder
将字节流解码为字符串。代码逐行读取响应,并通过 startsWith("data:")
来判断是否为有效的事件消息,然后提取 delta.content
内容并拼接。错误处理机制保证在发生JSON解析错误时能够跳过错误行并记录日志,确保程序健壮性。另外, 应妥善保管用户的 tokenForUser
, 避免泄露, 可考虑将其存储在安全的环境中, 如操作系统的凭据管理器或加密存储. 同时对请求体进行验证和过滤,避免注入攻击。
方案二: 使用第三方库解析 SSE 流
为了简化代码和提高可维护性,可以使用现成的第三方库来解析 SSE 流。例如,可以使用 eventsource
库。
代码示例:
- 首先安装依赖:
npm install eventsource
- 使用
eventsource
解析 SSE 流:
import EventSource from 'eventsource';
async function getCopilotResponse(messages, tokenForUser) {
return new Promise((resolve, reject) => {
let responseText = "";
const eventSource = new EventSource("https://api.githubcopilot.com/chat/completions", {
headers: {
authorization: `Bearer ${tokenForUser}`,
"content-type": "application/json",
},
method: "POST",
body: JSON.stringify({
messages,
stream: true,
}),
});
eventSource.onmessage = (event) => {
if (event.data === "[DONE]") {
eventSource.close();
resolve(responseText);
} else {
try {
const parsedEvent = JSON.parse(event.data);
responseText += parsedEvent?.choices?.[0]?.delta?.content || "";
} catch (e) {
console.error("Error parsing JSON:", e, "Invalid JSON String",event.data);
// Do nothing and ignore it since it's not critical
// or implement other way to handle such cases
}
}
};
eventSource.onerror = (error) => {
eventSource.close();
reject(error);
};
});
}
操作步骤:
- 确保已安装
eventsource
库。 - 将以上代码复制到你的扩展代码中。
- 调用
getCopilotResponse(messages, tokenForUser)
函数,传入用户消息messages
和用户 TokentokenForUser
。 - 该函数会返回一个 Promise,resolve 后得到完整的 Copilot 响应文本。
原理与安全建议:
该方案使用了eventsource
库, 该库会自动处理 SSE 流的连接、重连和错误处理。 代码通过监听 onmessage
事件来获取每个事件消息,解析 JSON 并拼接 delta.content
。同时监听了onerror
事件来捕获错误. 此方案代码更简洁,可读性更强。使用第三方库时, 务必选择信誉良好、活跃维护的库,以降低安全风险. 同时要检查库的版本和依赖, 及时更新到最新版本, 防止安全漏洞。 同样的,务必保证用户的 tokenForUser
得到妥善保管,并且对请求体进行验证和过滤。
实现文档注入
获取到完整的 Copilot 响应文本后,就可以实现文档注入了。 基本思路是在用户消息中加入提示词,引导 Copilot 参考提供的文档内容。 具体实现方式可以根据扩展的功能和需求进行调整。例如,可以在用户消息前拼接文档内容,或者将文档内容作为上下文信息传递给 Copilot LLM。
代码示例 (基于方案一):
async function handleMessage(userInput, documentation, tokenForUser) {
const messages = [
{
role: "system",
content: "You are a helpful AI assistant. Please use the following documentation to answer the user's question:\n" + documentation,
},
{
role: "user",
content: userInput,
},
];
const copilotResponse = await getCopilotResponse(messages, tokenForUser);
// 处理 copilotResponse,例如显示在 UI 上
console.log(copilotResponse);
return copilotResponse;
}
//使用方法
const documentation = "这是一个示例文档。这里应该放一些相关的文档内容,例如函数签名、用法说明等。\n例如: function add(a, b) { return a + b; }";
const userInput = "如何使用 add 函数?";
const tokenForUser = "YOUR_GITHUB_COPILOT_TOKEN";
handleMessage(userInput,documentation,tokenForUser).then(response => {
// 使用响应
console.log(response);
}).catch(error => {
// 处理错误
console.error("error:",error);
});
这段代码展示了如何将文档内容注入到 Copilot LLM 的 system 消息中,引导 Copilot 在回答用户问题时参考文档。 handleMessage
函数接收用户输入、文档内容和用户 token 作为参数,构建 Copilot LLM 所需的消息,然后调用 getCopilotResponse
获取响应。 可以根据实际需求修改 system
消息的内容和格式,以达到更好的效果。
相关资源
- [GitHub Copilot Chat Completions API 文档](https://docs.github.com/en/