使用 ChatGPT 控制 Vim,有了新发现!
以下为译文:
Vim 是一款基于文本的文本编辑器。作为使用者,你可以从 Vim 中获取的每一项信息都以文本形式表示,你发送给它的每一项信息也都是文本。同样,我们也主要通过文本信息去使用时下流行的 ChatGPT。对于这两款工具而言,都是用文本的形式去使用,那我们是否可以去掉中间的环节,譬如,直接使用 ChatGPT 来控制 Vim?
可在此处获取代码:https://github.com/LachlanGray/vim-agent
天作之合
Vim 非常奇特,它的设计使得你知道如何使用该编辑器,也就意味着你知道如何使用编辑器实现自动化。简而言之,你与编辑器的每一次交互都可以转化为一个 VimScript 程序。任何涉及键入、更改、删除、编辑命令、Shell 命令、打开文件等任意组合的操作序列都可以由一个程序完成。这对人类是否有利还有待商榷,但如果你将其和一个语言模型结合起来,那么这个功能就无价了。
VimScript 已经存在很长时间了,互联网上充斥着用于完成各种乏味任务的 VimScript。VimScript 基本上是一种专门用于操作文本的领域特定编程语言。由于它的年代久远和僵化而受到一些批评,但它在其应该做的事情上表现出色。对我们来说,这意味着 ChatGPT 和它搭档将会非常顺利,理论上可以做我能做的一切与编辑器相关的事情。
Neovim API
ChatGPT 将如何实际控制编辑器呢?方便的是,Neovim 拥有一个 Python 客户端(https://pynvim.readthedocs.io/en/latest/),允许你通过 Python API 来控制编辑器。这非常方便。
首先,你可以通过以下方式启动 Neovim 并设置一个监听地址:
1nvim --listen 127.0.0.1:7777
这将启动一个常规的 Neovim 实例,但它将监听连接。现在,通过 Python API,我们可以告诉 Neovim 执行操作。Python API 使用 TCP(传输控制协议)连接到 Neovim,这是一种网络协议,旨在确保数据被完整接收,并按照发送的顺序到达。我们创建一个 vim 对象来管理 Neovim 的状态:
1vim = attach('tcp', address='127.0.0.1', port=7777)
该 vim 对象为我们提供了对 Neovim 的完全访问权限,其中包括对编辑器状态和功能的编程访问。在众多便捷包装器中,它提供了 vim.request(),这是一个全能工具,可以与 API 文档中的所有内容一起使用(https://neovim.io/doc/user/api.html)。它使我们可以做几乎任何事情,例如运行一个命令:
1vim.request('nvim_command', "q!")
以下是我们的计划:封装我们关心的所有功能,并简化它们,以便能够轻松地与语言模型进行交互。我们将称这个包装器为 VimInstance,它将包含 vim 对象,并具有简单的属性来访问信息,以及控制 Vim 实例的方法:
1class VimInstance:
2 def __init__(self):
3 self.vim = attach('tcp', address='127.0.0.1', port=7777)
4
5 @property
6 def current_buffer_content(self):
7 return self.vim.request(
8 'nvim_buf_get_lines',
9 self.vim.current.buffer,
10 0, -1, True)
11
12 # ...
13
14 def input(self, keys):
15 self.vim.input(keys)
16
17 # ...
然后我们可以做类似的事情:
1vim = VimInstance()
2
3# list containing each line of current file
4text = vim.current_buffer_content
5
6# type "hello" at the start of the file and save
7vim.input("ggIhello<Esc>:w<CR>")
给我代码!
生活中的一个极大的确定性是 ChatGPT 会告诉你它正在做什么。而且通常情况下,即使你不要求,它也会说一些。我们肯定会立刻遇到这个问题。与其哄着模型,不如只是等待代码出现,然后在代码完成时挂断连接可能更容易。
OpenAI 的聊天完成端点的流式传输功能对此很有用。它允许我们在文本块到达时监视它们,并实时决定如何处理。我们可以通过产生类似这样的文本块来迭代处理到达的完成结果:
1import openai
2
3def chat_3(messages: list[dict]):
4 completion = openai.ChatCompletion.create(
5 model = "gpt-3.5-turbo",
6 messages = messages,
7 temperature = 0.9,
8 stream=True
9 )
10 for chunk in completion:
11 if "content" in chunk.choices[0].delta:
12 yield chunk.choices[0].delta["content"]
现在我们可以在不等待模型完成的情况下处理模型的响应:
1def filtered_chat(request: str):
2 messages = [{"role":"user"}, {"content": request}]
3 for chunk in chat_3(messages):
4 if <condition>:
5 yield chunk
要收集代码,我们可以在响应滚动到达时进行监视,并等待开头的代码块模式(```.*?(\n.*)))出现。一旦匹配成功,我们就知道是时候开始监听了。然后我们可以继续监听,直到遇到更多的反引号。如果一切顺利,我们应该只有代码。
GPT 接管控制
现在我们已经拥有了将 ChatGPT 接入编辑器所需的一切。对于基本设置,让我们告诉 ChatGPT 该做什么。一个基本的提示策略如下:
-
给出一条指令,并提供屏幕内容作为上下文。
-
明确要求一个用于完成任务的 VimScript。
-
在 Neovim 中执行每一行。
以下是结果。左侧是打开文件的 Neovim,右侧我正在键入指令。它…有点奏效:
对于基本的操作,比如删除和重新排列屏幕上的内容,它表现得相当不错,但在处理更高级的请求方面,比如将注释转换为猪拉丁语(儿童暗语),它的可靠性不高。好消息是,有数以百万计的改进方式,因此自动文本编辑前景看起来很有希望!下一步是制定一个更好的提示策略,而不是采用这种一刀切的制作脚本方式。一旦我们有了一套良好的基本操作,我想构建一个类似"Voyager"(https://github.com/MineDojo/Voyager)的代理程序,看看它能走多远。