「AI × Vim」が便利なので VSCode へ移行できない

# AI < VI

[GitHub Copilot](https://github.com/features/copilot) や [CodeWhisperer](https://aws.amazon.com/jp/codewhisperer/) 等のAIによる支援ツールが開発スタイルを大きく変えつつある一方、どうしても VSCode が優遇されており、私も好きな vim でのサポートは限定的です。

とはいえ AI という理由だけではまだ VSCode に完全移行は出来ません。
なぜなら vim でもAIの力を借りることは可能なので、本記事では実際に私の vim で現時点でできていることを紹介します。

# VI powered by AI

以降のキャプチャは実際の出力結果ですが、AIによる問い合わせ時間を編集で短縮している個所もあります。

ちなみにこんな環境です。
- Windows
- neovim-qt
- 社内向けのAI提供あり（GPT4。使い道は後述）

## Chat

まずは ChatGpt のようなチャット機能です。
簡単なコードを考えてもらっています。

![01_chat.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/028926aa-fa0f-d1a7-2c3c-6fd220cdc39e.gif)

だいたいこんなことしています。
- 新規セッションの開始(`<C-n>`)
- システムプロンプト(`<C-s>`)や設定(`<C-o>`)の確認
- 簡単なプログラムの依頼(`<C-Enter>`)
- コードブロックのみヤンク(`<C-k>`)して本体へペースト

なおシステムプロンプトは自由に変更も可能ですが、デフォルトでも用意されています。
実態は [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv) から取得されているので、そのまま使えるものもちらほら。

![02_act_as.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/2e74ae98-a393-4b51-a0f9-ae958e0d7f21.gif)

## Explain

次にコードの説明をしてもらっている例です。

![03_explain.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/d11d139a-557b-49d3-5141-2045bc7a4ae9.gif)

あるいは先ほどのチャット画面を使う方法もあります。

![04_explain_chat.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/65dae520-20ae-2e46-033c-211838104703.gif)

## Interactive

お次はインタラクティブなコーディング指示です。
こんな流れで使用します。

- コードに対する指示を入力(`<C-Enter>`)
- AIが指示を反映したコードを出力
- 必要に応じてdiff(`<C-d>`)を確認したり手修正(`dp` や `do`)
- 出力内容を新たな入力として取り込み(`<C-i>`)
- 再度与えた指示を反映したコードをAIが出力
- 出力内容を採用(`<C-y`)

![05_edit.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/6d9a5da4-9c0e-d7b7-b24f-9762587edcd8.gif)

↑（文法が間違っていても結構通じるのがAIの賢いところ）

## Quickfix

あとはコードの分析結果を、quickfixes に表示するとかもできます。
（分析する際のプロンプトもカスタマイズ可能）

![06_analyze.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/5cf46d4f-a6ce-3895-98d4-08e59b620d88.gif)

## Completion

補完機能もあります。
ここでは
- テストコードを生成して（AIによる補完）
- テストを実行（vimの機能）
- 終わらないのでテストを止めて、エラーの調査（AIによる分析）
- 修正された内容に基づきコード修正
- テストが通った！

というデモです。

![07_complete_test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/cc55338a-12b0-f015-bffa-775f1cf07ecd.gif)

このように vim が使えればどこでもAIの機能が使えるので、上記のようなターミナル画面、リモートファイル、あるいは以下のようにコミットメッセージもAIに生成してもらえるということです。

![08_commit.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/08dee509-988c-7dbe-51ca-fb75202076ca.gif)

ここで `git ai` と打っていますが、ただの `editor=nvim git commit -v` のエイリアスです。
`-v` オプションで変更内容の差分も出力させ、それに基づきコミットメッセージをAIに考えてもらっているというからくり。

## Agent

後はわざわざ vim から呼び出す必要はないのですが、エージェントを呼び出して数学の問題を解いてもらっています。

![09_math.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/c2b57168-4cce-467a-6c8c-b892efc109e5.png)

背後では Python や Wolfram Alpha を活用してエージェントが問題を解決しています。

この場合だと、こんなことが行われていました。
- wolfram で 2000/1/1 時点の月までの距離を取得 (wolfram)
- 歩く時速を5kmとして所要時間(h)を計算 (python)
- 1日に歩ける時間(13h)から必要となる週を計算 (python)
- さらに7倍して必要な日数に変換 (python)
- 2000/1/1 に日数を加算して結果を得る (python)
- 上記を参考にして答えを生成 (AI)


# 実現方式

## 利用プラグイン

上記の大半は以下のプラグインを導入すれば実現可能です。
ありがとうございます。

[jackMort/ChatGPT.nvim: ChatGPT Neovim Plugin](https://github.com/jackMort/ChatGPT.nvim) 

## アーキテクチャ

ただし上記プラグインをより使いこなすため、以下の構成をとっています。

![arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/e26e0363-35eb-32ad-3e42-c20c296d325d.png)

つまりバックエンドとしてデフォルトの OpenAI を利用せずに、社内向けAIを利用しています。この社内向けAIとは、弊社内で提供されている業務利用可能なAIサービスです。
もともとはこの社内向けAIを活用したいという思いがあり、プラグイン自体をいじったりしていたのですが、間にローカルプロキシを介することで、あらゆるプラグイン、ライブラリを改修することなく利用可能となりました。
ローカルプロキシの役割は後述します。

## 設定内容

プラグイン管理は `lazy.nvim` の例です。

```lua
{
"jackMort/ChatGPT.nvim",
    event = "VeryLazy",
    config = function()
        vim.fn.setenv("OPENAI_API_KEY", "dummy")
        vim.fn.setenv("OPENAI_API_HOST", "http://localhost:8080")
    require("chatgpt").setup({
        predefined_chat_gpt_prompts = "http://localhost:8080/prompts",
        actions_paths = { "~/ChatGpt.json" },
    })
    end,
    dependencies = {
      "MunifTanjim/nui.nvim",
      "nvim-lua/plenary.nvim",
      "nvim-telescope/telescope.nvim",
    },
},
```

ポイントは
- OpenAI は利用しないのでキーは本当に `dummy` とかでも良い
- リクエストURLとしてローカルのプロキシサーバを指定
- システムプロンプトの一覧もプロキシサーバ経由で取得

最後の点はわざわざプロキシを介する必要が無いのですが、弊社内の社内プロキシの都合上、直接参照しにくかったのでその対応として実施しています。
これもこのプロキシ構成の利点の１つです。

あとは [公式](https://github.com/jackMort/ChatGPT.nvim?tab=readme-ov-file#whichkey-plugin-mappings) を参考に好みのキーマップを設定します。

## カスタマイズ

プロンプトや結果の表示方法などは [こちら](https://github.com/jackMort/ChatGPT.nvim/blob/main/lua/chatgpt/flows/actions/actions.json) の json で定義されており、`actions_paths` で指定した json により自由に追加や上書きか可能です。

例えばデモで紹介したコミットメッセージならこんな json を書けば、`:ChatGPTRun commit_message` で呼び出し可能です。
(私はこれにキーマップを設定)

```json
  "commit_message": {
    "type": "chat",
    "opts": {
      "template": "{{input}}",
      "strategy": "prepend",
      "params": {
        "messages": [
          {"role": "system", "content": "Generate the appropriate commit message from the difference information"}
        ],
        "temperature": 0.5
      },
      "trim": true
    }
  }
```

`trim` というオプションはこの構成のみで有効な私のオリジナルです。（後述）

デフォルトでも用意されていますが、翻訳や文法チェック、テストコードの追加など工夫次第でいろいろできます。

# ローカルプロキシの役割

ローカルプロキシを介することで、社内のAIを利用できるわけですが、それ以外にもいくつか利点があります。

- リクエストの加工
- レスポンスの加工
- 履歴の管理
- 参照ソースの添付


## リクエストの加工

ローカルプロキシ側にてリクエスト内容に応じて、任意のコード実行やRAGによる外部ドキュメントの参照、ネット検索なども可能となります。

例えばデモで呼び出したエージェントは [AutoGen](https://github.com/microsoft/autogen) を利用して実現していますが、この AutoGen のバックエンドもローカルプロキシ経由で社内AIを利用しています。
残念ながら社内AIは Function Calling などに対応していませんが、ローカルプロキシにてリクエスト（とレスポンス）を加工して Function Calling を模倣することで、やはり AutoGen 側を変更することなく利用できています。

ちなみにコード実行を伴うため、ローカルプロキシは一応 Docker 上で動かしてます。

## レスポンスの加工

例えばレスポンスとしてプログラムコードだけを期待しているにも関わらず、AIが気を利かせて説明や三連バッククォート(```)などで囲んでくる場合があります。

プロンプトの工夫だけでは対応しきれない場合、プログラム的にバッククォートの除去や置換ができます。前述した `trim` パラメータはその制御に利用しています。

## 履歴の管理

オリジナルのインタラクティブな編集機能では、履歴管理されていないので、過去のやり取りを反映することができません。

そこでプロキシがやりとりを記憶して、必要に応じてリクエスト内容に加えてあげることで履歴管理も実現できます。

例えばこんな感じで、意図せず消えたコメントの内容をAIが覚えているので、リクエスト通りに対応してくれます。

![10_edit_history2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/08800bcf-9a96-34c2-688c-77a3de9242a9.gif)

## 参照ソースの添付

参考情報として任意のコードをプロキシに渡すことで、AIへの問い合わせの際に利用するこもできます。

実際に冒頭のデモでも、こっそりこの機能を使ってました。

1. 対象のソースを参考情報として渡してから、コードの説明をしてもらう
2. テストコードのエラー解析の前に、開いているソースを参考情報として渡す

例えば１番目の例なら、「コードを説明して」と聞いているだけですが、AIには以下のようなプロンプトが渡されているイメージになります。

```
# 参考ソース
'''python
def plus(a, b):
    while b != 0:
        data = a & b
        a = a ^ b
        b = data << 1
    return a
'''

コードを説明して
```

このように回答に必要となりそうな情報を明示的にAIに連携してあげることで、コンテキストに合わせた回答を得られやすくなります。
もちろんユーザに意識させず、開いているバッファをすべて連携したり、関連文書引っ張ることも可能でしょうが、いったん自分の意志で挙動を制御できるようにしています。（評価中）

# Copilot風の補完

Copilot風の以下のようなコード補完も、別のプラグインなどを利用すれば可能です。（以下は自動的な補完ではなく、明示的に補完タイミングを指定）

![11_codeium2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/1da7e74e-567b-2fd6-84be-e229358904b9.gif)

例えば私は [Exafunction/codeium.vim](https://github.com/Exafunction/codeium.vim) というプラグインを利用しています。デフォルトではバックエンドとして [CodiumAI](https://www.codium.ai/) が利用されています。
なおこのサービス、個人利用なら無料ということで気軽に試せます。

ただそのセキュリティがちょっと気になって、以下のような情報が送信されているようです。

- ドキュメント内容 (別タブのバッファなども。意図せず機密情報を送っちゃうかも)
- ドキュメント絶対パス (パスに含まれるユーザ名などもバレるかも)
- カーソル位置 (作業の進め方などがバレるかも)

これらは補完のために必要な情報とはいえ、AIサービスの利用者は、このリスクを理解したうえでその利便性を優先しているのかと思います。

ただしこのプラグインの場合、言語サーバを差し替えることが可能なので、独自の言語サーバに差し替えれば外部のAIサービスに依存しないことも可能です。

```lua
{
    'Exafunction/codeium.vim',
    event = 'BufEnter',
    config = function()
      vim.g.codeium_bin = "独自の言語サーバ"
    end
}
```

ただしやはり専用のAIサービスのほうが精度は高く、処理も高速です。
（とはいえこの補完はやりすぎな場合もあって、そんなに使わないのであまり困っていません。）

業務利用する場合には有償オプションを検討して、そのリスクを回避しつつ利用するのもありだと思います。
もちろんこの部分なら GitHub Copilot の [vim版](https://github.com/github/copilot.vim) を利用してもよいでしょうし。

# まとめ

これでまだまだ vim を利用できそうです。

では最後にAIにも感想を聞いておきます。
AIであるあなたがこの記事を締めてください。

![12_conclusion.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14471/0fea5da7-7c4d-4e27-7491-d029cc4aca63.gif)