EmacsでローカルLLMのEllamaを日本語で扱う設定

## はじめに

皆さん、英語は得意ですか？
私は苦手です。
ChatGPTに「〜〜〜、翻訳して」とよく聞いています。

さて、Emacsで使えるロカールLLMにEllamaというパッケージがあります。
EllamaはローカルLLMを呼び出す便利なコマンドを多数用意してくれています。
例えば、

- `ellama-summarize`：指定箇所の要約をする
- `ellama-code-review`：指定箇所のコードレビューをする
- `ellama-translate`：指定箇所を翻訳する

などがあります。

大変便利そうなのですが、しかしEllamaはデフォルトで英語で応答します。
`ellama-translate`コマンドがあるので、実行結果を翻訳することはできます。
とはいえ、これだとLLMの実行回数が増えるため、時間もかかり手間です。

そこで、本稿ではEllamaの応答を日本語化する方法を解説します。

## 本稿で説明しないこと

- Ollamaのインストール方法
  - 公式ドキュメントに従ってください
  - ﾀﾌﾞﾝｺｺｶﾞｲﾁﾊﾞﾝﾀｲﾍﾝ
- Ellamaのインストール方法
  - package.elでMelpaからインストールできます

## Ellamaについて

ローカルLLMツールであるOllamaをEmacsで動かすためのパッケージです。
先日の[東京Emacs勉強会 サマーフェスティバル2024](https://tokyo-emacs.connpass.com/event/321255/)にて発表された、tomoyaさんの記事にて詳しく解説されています。

https://blog.tomoya.dev/posts/emacs-on-local-llm/

やはりEmacsはAIと相性が良いんですね！

https://x.com/tadsan/status/1813799288004399504

## Ellamaの中身

LLMは、入力トークン列（プロンプト）から次に続くトークンを予想しています。
実装ファイルの`ellama.el`を覗いてみると、この各種プロンプトのテンプレートが`defcustom`で設定してあります。
本稿の時点での最新版である「20240721.1138」では、15個のプロンプトテンプレートが存在していました。

つまり、このプロンプトを日本語に設定してやれば、Ellamaの返答を日本語化できるはずです。

```elisp:ellama.elに定義されたプロンプトテンプレートの例
...（省略）
(defcustom ellama-define-word-prompt-template "Define %s"
  "Prompt template for `ellama-define-word'."
  :group 'ellama
  :type 'string)
...（省略）
```

## Ellamaを日本語化するEmacsの設定

例えば、以下のコードのように全てのプロンプトテンプレートを日本語化すればOKです。
もちろん、より良い結果が得られるよう好みでカスタマイズしてみてください。

ただし、今後のバージョンアップでプロンプトテンプレートが変更や増減する可能性があります。

```elisp:Ellama日本語化の設定例
(setq ellama-define-word-prompt-template "%s の定義を教えて")
(setq ellama-summarize-prompt-template "Text:\n%s\n要約して")
(setq ellama-code-review-prompt-template "以下のコードのレビューと改善案をだして:\n```\n%s\n```")
(setq ellama-change-prompt-template "以下のテキストを「%s」と変更して、引用符なしで出力して:\n%s")
(setq ellama-improve-grammar-prompt-template "誤字脱字・文法を校正して")
(setq ellama-improve-wording-prompt-template "語句を推敲して")
(setq ellama-improve-conciseness-prompt-template "できるだけ簡潔にして")
(setq ellama-code-edit-prompt-template "以下のコードを「%s」と変更して、プロンプト無しでコードだけを\n```language\n...\n```\nの形式で出力して:\n```\n%s\n```\n")
(setq ellama-code-improve-prompt-template "以下のコードを改善して、プロンプト無しでコードだけを\n```language\n...\n```の形式で出力して:\n```\n%s\n```\n")
(setq ellama-code-complete-prompt-template "以下のコードの続きを書いて、プロンプト無しでコードだけを\n```language\n...\n```の形式で出力して:\n```\n%s\n```\n")
(setq ellama-code-add-prompt-template "Context: \n```\n%s\n```\nこのコードを文脈として、%s、プロンプト無しでコードだけを\n```\n...\n```\nの形式で出力して\n")
(setq ellama-generate-commit-message-template "あなたは熟練プログラマーです。後の変更点をもとに簡潔なコミットメッセージを書いてください。コミットメッセージの形式は、1行目は変更点の要約、2行目は空行、それ以降の行は変更全体の詳細な説明、です。出力はプロンプト無しで最終的なコミットメッセージだけにしてください。\n\n変更点:\n%s\n")
(setq ellama-make-format-prompt-template "以下のテキストを%sの形式に変換して:\n%s")
(setq ellama-make-list-prompt-template "Markdownのリスト形式にして")
(setq ellama-make-table-prompt-template "Markdownのテーブル形式にして")
```

自分はLeaf.elでinit.elを設定しています。
参考までに自分の設定を以下に貼っておきます。

```elisp:Leaf.elを使ったEllama全体の設定例
(leaf ellama
  :if (executable-find "ollama")
  :ensure t
  :defer-config
  (require 'llm-ollama)
  :custom
  (ellama-major-mode . 'markdown-mode)
  (ellama-naming-scheme . 'ellama-generate-name-by-time)
  (ellama-providers
   . '(("deepseek-coder-v2"
        . #s(llm-ollama nil nil nil "http" "localhost" 11434
                        "deepseek-coder-v2:16b-lite-instruct-q4_K_S"
                        "deepseek-coder-v2:16b-lite-instruct-q4_K_S"))
       ("gemma2"
        . #s(llm-ollama nil nil nil "http" "localhost" 11434
                        "gemma2:2b-instruct-q4_K_S" "gemma2:2b-instruct-q4_K_S"))
       ))
       
  ;; translation
  (ellama-language . "日本語")
  (ellama-translation-provider
   . #s(llm-ollama nil nil nil "http" "localhost" 11434
                   "gemma2:2b-instruct-q4_K_S" "gemma2:2b-instruct-q4_K_S"))
  (ellama-translation-template . "%s で話して。%s 、を %s に翻訳して。")
  
  ;; code generation
  ;; cl-defstructのmake-llm-ollama展開後を使う
  (ellama-provider
   . #s(llm-ollama nil nil nil "http" "localhost" 11434
                   "deepseek-coder-v2:16b" "deepseek-coder-v2:16b"))
  (ellama-define-word-prompt-template . "%s の定義を教えて")
  (ellama-summarize-prompt-template . "Text:\n%s\n要約して")
  (ellama-code-review-prompt-template . "以下のコードのレビューと改善案をだして:\n```\n%s\n```")
  (ellama-change-prompt-template . "以下のテキストを「%s」と変更して、引用符なしで出力して:\n%s")
  (ellama-improve-grammar-prompt-template . "誤字脱字・文法を校正して")
  (ellama-improve-wording-prompt-template . "語句を推敲して")
  (ellama-improve-conciseness-prompt-template . "できるだけ簡潔にして")
  (ellama-code-edit-prompt-template
   . "以下のコードを「%s」と変更して、プロンプト無しでコードだけを\n```language\n...\n```\nの形式で出力して:\n```\n%s\n```\n")
  (ellama-code-improve-prompt-template
   . "以下のコードを改善して、プロンプト無しでコードだけを\n```language\n...\n```の形式で出力して:\n```\n%s\n```\n")
  (ellama-code-complete-prompt-template
   . "以下のコードの続きを書いて、プロンプト無しでコードだけを\n```language\n...\n```の形式で出力して:\n```\n%s\n```\n")
  (ellama-code-add-prompt-template
   . "Context: \n```\n%s\n```\nこのコードを文脈として、%s、プロンプト無しでコードだけを\n```\n...\n```\nの形式で出力して\n")
  (ellama-generate-commit-message-template
   . "あなたは熟練プログラマーです。後の変更点をもとに簡潔なコミットメッセージを書いてください。コミットメッセージの形式は、1行目は変更点の要約、2行目は空行、それ以降の行は変更全体の詳細な説明、です。出力はプロンプト無しで最終的なコミットメッセージだけにしてください。\n\n変更点:\n%s\n")
  (ellama-make-format-prompt-template . "以下のテキストを%sの形式に変換して:\n%s")
  (ellama-make-list-prompt-template . "Markdownのリスト形式にして")
  (ellama-make-table-prompt-template . "Markdownのテーブル形式にして"))
```

### 追記

2024-08-27に翻訳に関する設定を追加。
デフォルト翻訳設定でgemma2のようなおしゃべりなLLMを使うと、`ellama-translate`したときに英語で補足情報を喋ります。
そのため、`ellama-language`と`ellama-translation-template`の設定をするとよいです。

## 日本語でEllamaが動作する様子

例えば、`ellama-summarize`を動かすとスクリーンショットのようになります。
ちゃんと日本語で答えてくれますね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42721/e487e40a-e159-909b-dfde-1c0a1da474d2.png)

## 終わりに

本稿では、

- Ellamaを簡単に紹介しました
- Ellamaの応答を日本語化する設定を紹介しました

皆さんも私も、日本語がネイティブに喋れるくらい得意だと思います。
今回紹介した方法で、ぜひEmacsとローカルLLMのコラボレーションを使ってみてください。