生物物理屋がローカルLLMをAPIサーバーにして遊んでみた話

## はじめに
この記事は「[ただただアウトプットを癖付けるための Advent Calendar 2024](https://qiita.com/advent-calendar/2024/newbie2024)」に投稿した記事です。

[最初の記事](https://qiita.com/yotabouya/items/e63c53c5e18b3cf6a454#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)にも書いた通り、私は生物物理の実験を専門にしている研究者です。
最近はデータ解析のため機械学習のコード開発も行っており、幸いにもその成果が[NeurIPSに採択されました](https://neurips.cc/virtual/2024/poster/93465)。

[前回の記事](https://qiita.com/yotabouya/private/136a5222f69cd3774e16)で、ローカルLLMを導入して遊んでみました。
この導入の密かな目的として、ローカルLLMをAPIサーバーにして、各種のRAGなどのモデルを使って遊びたいと考えていました。
今回はその実装について書いていきます。

## 関連記事

#### 前の記事「[生物物理屋がローカルLLMを導入して遊んでみた話](https://qiita.com/yotabouya/private/136a5222f69cd3774e16)」

#### 次の記事「[生物物理屋がローカルLLMでサーベイ論文生成を試してみた話](https://qiita.com/yotabouya/private/e2e247d2150793e362e1)」

## 参考記事
[OllamaでローカルLLMを稼働させAPIサーバ化する](https://zenn.dev/oyashiro846/articles/797312443fb506)

## 実装
前回の記事で、Ollamaを使ってローカルLLMを稼働させることができました。
ローカルLLMとは言いつつ、実際にはリモートサーバーで稼働させていましたが、今回はそのリモートサーバーをAPIサーバーにして、外部からアクセスできるようにします。

### Ollamaの設定
上の記事にもある通り、Ollamaは何も設定しなくても、port 11434をlistenしているので、これを外部からアクセスできるようにすればいいです。
これは、OLLAMAの環境変数```OLLAMA_ORIGINS```に、ローカルPCのIPアドレスを設定することで実現できます。
同一ネットワーク上でのアクセスであればローカルIPアドレスを設定すればいいですが、外部からアクセスする場合は、グローバルIPアドレスを設定する必要があるので、[whatismyip](https://www.whatismyip.com/)などで調べて設定してください。
設定後に```Ollama serve```でOllamaを再起動することで、設定が反映されます。

### 接続を確立
今回は異なるネットワーク上でのアクセスです。
しかもssh経由でのアクセスなので、ポートフォワーディングを使って、リモートサーバーの11434ポートにローカルPCの11434ポートをフォワードします。
sshでのポートフォワーディングは、以下のようにします。
```bash
ssh -L 11434:localhost:11434 user@remote
```
これで、ローカルPCの11434ポートにアクセスすると、リモートサーバーの11434ポートにアクセスできるようになります。
もちろん、ポート開放などの設定も必要です。

### Pythonでのアクセス
Pythonでのアクセスは、requestsを使って簡単にできます。
```python
import requests

url = 'http://localhost:11434/api/chat'

def main():
    headers = {"Content-Type": "application/json"}
    json = {
        "model": "llama3.1",
        "messages": [{
            "role": "user",
            "content": "Hello",
        }]
    }

    response = requests.post(API_SERVER_URL, headers=headers, json=json)
    response.raise_for_status()

    print(response.text)

main()
```

このようにアクセスすることで、以下のような返答を得ることができました。

```json
{"model":"llama3.1","created_at":"2024-11-26T16:12:11.996438344Z","message":{"role":"assistant","content":"Hello"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.009597744Z","message":{"role":"assistant","content":"!"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.021561388Z","message":{"role":"assistant","content":" How"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.033510867Z","message":{"role":"assistant","content":" can"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.045075816Z","message":{"role":"assistant","content":" I"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.056668956Z","message":{"role":"assistant","content":" help"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.068113805Z","message":{"role":"assistant","content":" you"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.079076735Z","message":{"role":"assistant","content":" today"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.08993112Z","message":{"role":"assistant","content":"?"},"done":false}
{"model":"llama3.1","created_at":"2024-11-26T16:12:12.100929679Z","message":{"role":"assistant","content":""},"done_reason":"stop","done":true,"total_duration":195820077,"load_duration":37560827,"prompt_eval_count":11,"prompt_eval_duration":25000000,"eval_count":10,"eval_duration":132000000}
```
一単語ずつ返答が返ってくるので、それを適宜処理する必要がありますね。

## ローカルLLMをCopilotとして使う

VSCodeのContinueを用いて、ローカルLLMをCopilotのように使うこともできるようです。
[こちらの記事](https://qiita.com/Tadataka_Takahashi/items/0485159e444056892529)に載っていました。

Continueは少々アップデートされているようで、やや見た目が違いますが、基本的な使い方は変わっていません。
ContinueのAdd Chat Modelから、Ollamaを選択し、モデルはllama3.1 chatを選択しました。
このままだとローカルPCにインストールされているOllamaに接続しようとするので、先ほどのポートフォワーディングを使って、リモートサーバーの11434ポートに接続します。
このためには、.continue/config.jsonの"models"のなかに以下のような項目を追加します。
```json
    {
      "title": "Llama3.1 remote",
      "model": "llama3.1",
      "provider": "ollama",
      "apiBase": "http://localhost:11434"
    }
```
（プリセットでClaude3.5の項目があるので、それと似たような形式で追加すればよさそうです）

これで、VSCodeのContinueからリモートサーバー上のローカルLLMを使うことができるようになりました。
ただしこれは、ssh接続している状況でないと使えないので、注意してください。