自从 AI 大模型在编程中普及以来,我已经很少写技术文章了。有时候突然想写点什么,但转念一想,这些问题问一下 AI 不就解决了吗?于是写作的动力又消失了。不过最近收到一些朋友的反馈,说读了我的文章后得到了帮助,这让我觉得写文章还是有价值的。恰好最近我在更新Dodo Reader插件------这个插件自 2013 年以来从未更新,慢慢积累下来也有将近 3k 下载量了。我在想,如果阅读时能有 AI 辅助,会不会让体验更好?于是,我决定给这个项目集成 Copilot AI。Copilot 在编程中确实很有帮助,但有一些更智能、更个性化的功能,可能还是需要通过扩展来完成,比如对文件的高级处理,而且我相信一些朋友可能还有更多的奇思妙想。
那么接下来就来看看怎么接入 Copilot AI。VSCode 主要提供了两种接入方式:一是聊天参与者,通过 Copilot AI 增加聊天角色;另一个是通过 API 集成到自己的界面。此外,官方提到的语言模型工具,个人理解其实是对这两种方式的扩展。
聊天参与者
聊天参与者就是集成到 Copilot 的聊天界面的,通过@
来调用,如图:
- 注册参与者,就是告诉 VSCode 我要注册一个什么名称、用来干什么的角色。这个需要在
package.json
添加:
json
{
"contributes": {
"chatParticipants": [
{
"id": "chat-ai.dodo-reader",
"fullName": "Dodo Reader",
"name": "dodo-reader",
"description": "我是一个EPUB文件阅读助手,你有什么可以问我哦~",
"isSticky": true,
"commands": [
{
"name": "translate",
"description": "输入你要翻译的目标语言"
},
{
"name": "summarize",
"description": "总结这段文本"
}
]
}
]
}
}
- 创建参与者,通过参与者
id
去创建:
js
export function activate(context: vscode.ExtensionContext) {
// handler 是对请求的处理
const tutor = vscode.chat.createChatParticipant('chat-ai.dodo-reader', handler)
// 定义角色的头像
tutor.iconPath = vscode.Uri.joinPath(context.extensionUri, 'dist/web/logo.png')
}
- 处理请求并响应,
handler
中你能够接收到用户输入内容和指令:
js
const handler: vscode.ChatRequestHandler = async (
request: vscode.ChatRequest,
context: vscode.ChatContext,
stream: vscode.ChatResponseStream,
token: vscode.CancellationToken
): Promise<ICatChatResult> => {
if (request.command === 'translate') {
// translate 指令处理
return
}
if (request.command === 'summarize') {
// summarize 指令处理
return
}
// 给模型发起请求
const chatResponse = await request.model.sendRequest(
[vscode.LanguageModelChatMessage.User(request.prompt)],
{},
token
)
// text 是一个异步迭代器
for await (const fragment of chatResponse.text) {
// 通过 stream.markdown 给聊天输出内容
stream.markdown(fragment)
}
}
stream.markdown
可以将模型返回的 markdown 内容输出,其实这只是一种形式,另外还有其他类型:
js
stream.button // 发送按钮,可以调用扩展命令
stream.filetree // 发送文件树
stream.progress // 发送进度消息
stream.reference // 发送引用
stream.anchor // 发送内联引用
如果你想要联系上下文进行聊天,还需要把历史记录一起发送,通过context.history
组合新的消息数组,然后再发起请求:
js
const messages = []
context.history.forEach((h) => {
if (h instanceof vscode.ChatRequestTurn) {
messages.push(vscode.LanguageModelChatMessage.User(h.prompt))
} else if (h instanceof vscode.ChatResponseTurn) {
let fullMessage = ''
h.response.forEach((r) => {
const mdPart = r as vscode.ChatResponseMarkdownPart
fullMessage += mdPart.value.value
})
messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage))
}
})
messages.push(vscode.LanguageModelChatMessage.User(request.prompt))
模型 API
可能你并不需要 Copilot 聊天界面,你有自己的 webview 界面希望输出模型的聊天结果,或者直接输出到编辑器里面,这时候模型 API 就能派上用场。
你可以列举所有的内置模型:
js
const models = await vscode.lm.selectChatModels({
vendor: 'copilot',
})
接下来,假设你有个 webview 界面,接收到 webview 发来的消息,你需要进行模型请求:
js
const panel = vscode.window.createWebviewPanel(/* webview 参数 */)
panel.webview.html = 'HTML内容'
panel.webview.onDidReceiveMessage(async (message) => {
if (message.command === 'chat') {
// 接收到 webview 请求,发起模型请求
const [model] = await vscode.lm.selectChatModels({ vendor: 'copilot', family: 'gpt-4o' })
// 使用 gpt-4o 进行请求发送
const request = await model.sendRequest(
vscode.LanguageModelChatMessage.User(message.text),
{},
new vscode.CancellationTokenSource().token
)
for await (const fragment of chatResponse.text) {
// 处理请求结果
console.log(fragment)
}
}
})
示例很简单,请求的方法sendRequest
其实跟前面是一样的,只是获取模型的方式不同:前面是回调传递过来的 Copilot 选中模型,这里是主动设置的模型。在实际开发中,你可以创建一个设置界面让用户自行选择模型。
最后,本文只是讲解如何调用模型,关于创建命令、创建webview等其他内容,你可以回顾我之前的文章查看详细说明。