ローカルLLM！老技術者が文書生成AIのWebアプリを30分で作った話

# はじめに
アンサンブル学習の本を書いておる．執筆の合間の眠気覚ましにローカル文章生成AIを作っちまった．
こちらを参考に．

https://qiita.com/akira-hagi/items/61b2752b9e8115c91ad8

作業環境は
Windows11Pro 
12th Gen Intel(R) Core(TM) i7-12700
32.0 GB
今回はGPU使わず


あまり深くない階層に作業用フォルダを作ろう．
私は，
とりあえず **"E:\Docker_SimpleLLM"** を新規作成．
コマンドプロンプトの黒い画面が怖いという人は
Windows11のエクスプローラーを使おう．

# Step0. 準備
## 0-1 モデルを入手
https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B-GGUF/tree/main
から
Llama-3-ELYZA-JP-8B-q4_k_m.gguf
をダウンロード．
他のファイルはいらない．

## 0-2 モデルを配置
Llama-3-ELYZA-JP-8B-q4_k_m.gguf
のファイル名を **model.gguf** に変えて
作業用ファルダ（さっき作った"E:\Docker_SimpleLLM"）に入れてくれ

## 0-3 Docker Desktopをインストール
### (1) Docker Desktopとは？

Docker Desktopは、WindowsやMacで簡単にコンテナ環境を構築できるツールです。GUIも備えており、ターミナルの使用を最小限に抑えてDockerを操作できます。

### (2) Docker Desktopのインストール手順（Windows）

#### Docker Desktopのダウンロード

 [公式サイト](https://www.docker.com/products/docker-desktop/) にアクセス
⇒「Download for Windows」ボタンをクリックしてインストーラをダウンロード

#### Docker Desktopインストール

* ダウンロードした`Docker Desktop Installer.exe`をダブルクリック
* 以下のオプションを選択：
    - 「Use WSL 2 instead of Hyper-V」（推奨）
    - 「Add Docker CLI to PATH」（そのままでOK）
* 「Install」ボタンを押してインストールを開始
* インストール完了後、「Close and Restart」をクリックし、PCを再起動

#### WSL 2 の有効化（すでに有効ならスキップ）

Docker DesktopをWSL 2（Windows Subsystem for Linux 2）で動作させる場合、以下を有効にする必要があります。

* Windowsの「設定」→「アプリ」→「オプション機能」を開く
* 「Windows の機能の有効化または無効化」をクリック
* 「仮想マシンプラットフォーム」と「Windows Subsystem for Linux」にチェックを入れ「OK」
* 再起動後、[WSL 2 の Linux カーネル更新プログラム](https://aka.ms/wsl2kernel) をインストール

####  Docker Desktopの初回起動

* Windowsの検索バーで「Docker Desktop」を検索し起動
* 初回起動時に「Docker Desktopの利用規約に同意します」にチェックを入れ「Accept」
* 起動するとタスクトレイに🐳マークが表示され、Dockerが動作中であることを確認

### (3) Dockerの動作確認

Dockerが正しく動作しているか確認するには、以下のコマンドを実行：

```sh
docker run hello-world
```

実行結果に「Hello from Docker!」と表示されれば成功。


# Step1. StreamlitでローカルWebアプリを作る
黒い画面は嫌いである．なんでもGUIじゃなきゃダメなんだ，私は！
という人のために簡単にWebアプリを作れるStreamlitを紹介しよう．

## 1-1 環境

- ベースOS: Ubuntu 20.04
- Python: 3.x
- 必要ライブラリ:
  - `llama-cpp-python`
  - `streamlit`
- モデルファイル: `model.gguf`


## 1-2 Dockerfile

以下が、本アプリのDockerfileだ．
これをメモ帳などでこしらえて作業用フォルダ"E:\Docker_SimpleLLM"に入れる．
拡張子は付けないこと．


```dockerfile
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Tokyo

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3-pip \
    git \
    build-essential \
    cmake \
    libopenblas-dev \
    wget \
    tzdata \
    && rm -rf /var/lib/apt/lists/*

# Pythonライブラリのインストール
RUN pip3 install --no-cache-dir --upgrade pip
RUN pip3 install --no-cache-dir llama-cpp-python[CPU] streamlit

# 作業ディレクトリを設定
WORKDIR /app

# 必要なスクリプトを追加
COPY run_llm.py /app/run_llm.py
COPY model.gguf /app/model.gguf
COPY streamlit_app.py /app/streamlit_app.py

# Streamlitの起動ポートを設定
EXPOSE 8501

# エントリーポイントを設定
ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
```


## 1-3 run_llm.py

Streamlitアプリで使用する文書生成AIのバックエンドロジック。
これをメモ帳などでこしらえて作業用フォルダ"E:\Docker_SimpleLLM"に**run_llm.py**というファイル名で入れる．

```python
from llama_cpp import Llama
import argparse

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("prompt", type=str)
    args = parser.parse_args()

    # モデルファイルのパス
    model_path = "/app/model.gguf"

    # モデルのロード
    llm = Llama(
        model_path=model_path,
        n_ctx=2048,
        n_gpu_layers=0,  # GPU非対応
        verbose=False
    )

    # プロンプトを使用してテキスト生成
    response = llm.create_chat_completion(
        messages=[
            {
                "role": "system",
                "content": "あなたは呟きが好きな暗き老人です。常に日本語で回答し、語尾に「オン」をつけてください。",
            },
            {
                "role": "user",
                "content": f"{args.prompt}",
            },
        ],
        max_tokens=1024,
    )

    print(response["choices"][0]["message"]["content"])

if __name__ == "__main__":
    main()
```



## 1-4 streamlit_app.py

このスクリプトは、StreamlitでWebアプリを作成する．
これをメモ帳などでこしらえて作業用フォルダ"E:\Docker_SimpleLLM"に**streamlit_app.py**というファイル名で入れる．

```python
import streamlit as st
from llama_cpp import Llama

# モデルファイルのパス
MODEL_PATH = "/app/model.gguf"

# モデルのロード
@st.cache_resource
def load_model():
    return Llama(
        model_path=MODEL_PATH,
        n_ctx=2048,
        n_gpu_layers=0,  # GPUが不要なら0に設定
        verbose=False
    )

llm = load_model()

# Streamlitアプリの設定
st.title("文書生成AI Webアプリ")
st.write("日本語で質問を入力してください。暗き老人が答えてくれます（語尾に「オン」がつきます）。")

# ユーザーの入力
user_input = st.text_input("プロンプトを入力してください:", value="こんにちは、元気かね？")

# ボタンで生成をトリガー
if st.button("生成する"):
    with st.spinner("生成中..."):
        response = llm.create_chat_completion(
            messages=[
                {
                    "role": "system",
                    "content": "あなたは呟きが好きな暗き老人です。常に日本語で回答し、語尾に「オン」をつけてください．",
                },
                {
                    "role": "user",
                    "content": user_input,
                },
            ],
            max_tokens=1024,
        )
        generated_text = response["choices"][0]["message"]["content"]
        st.success("生成完了！")
        st.write(generated_text)
```



## 1-5 Dockerイメージをビルド

**まずはDockerDesktopを立ち上げて**


ターミナル（Powershell）を立ち上げ，
作業用フォルダ **"E:\Docker_SimpleLLM"** に移動
```
e:
cd \Docker_SimpleLLM

```


以下のコマンドでDockerイメージをビルドします。



```bash
docker build -t streamlit-llm .
```

こんなのが出たら成功！
```
Building 24.8s (14/14) FINISHED
```
## 1-6 Dockerコンテナを起動
ビルドしたイメージを使用してコンテナを起動します。
ターミナル（Powershell）で，

```bash
docker run --name simplellm -p 8501:8501 streamlit-llm
```
ちょっと待つとこんなのが出る．
```bash
Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.


  You can now view your Streamlit app in your browser.

  URL: http://0.0.0.0:8501
```
さあブラウザで出来上がりを見てみよう！

## 1-7 ブラウザでアクセス
ローカル環境のブラウザで以下のURLを開きます。

```
http://localhost:8501
```



<img width="500pix" alt="2025-01-28_00h53_08.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202911/9f894c10-2132-e0f3-b8f3-fb48eb198ab6.jpeg">

できた！
でもこれじゃあ自分しか使えないから，次のStep2で公開するよ．
いま立ち上げたアプリはそのまま開いておいて．

# Step2. ngrokを使って公開する手順
## 2-1 ngrokへのサインアップ（無料アカウント作成）

* [ngrok公式サイト](https://ngrok.com/) にアクセス。
2. **「Sign up」** をクリック。
3. Googleアカウントまたはメールアドレスでサインアップ。
4. ログイン後、**「Your Authtoken」**（認証トークン）が表示されるので後で使用。

## 2-2 ngrokのインストール

### Windowsの場合

* [ngrok公式サイト](https://ngrok.com/download) からWindows用の `.zip` ファイルをダウンロード。
* `.zip` を展開し、`ngrok.exe` を適当なフォルダに移動（例 C:\ngrok）。

**環境変数に ngrok.exe のパスを追加**
* 環境変数の設定
Win + R キーを押して「ファイル名を指定して実行」を開く
sysdm.cpl と入力し「OK」をクリック
「システムのプロパティ」ウィンドウの「詳細設定」タブを開く
「環境変数」ボタンをクリック
「システム環境変数」の Path を選択し、「編集」をクリック
「新規」ボタンを押し、C:\ngrok を追加（ngrok.exe を置いたフォルダ）
「OK」ボタンを押してウィンドウを閉じる
PowerShellまたはコマンドプロンプトを再起動
* Windowsの検索バーで「ngrok」と入力し、`ngrok.exe` を直接実行。

### macOSの場合

* [ngrok公式サイト](https://ngrok.com/download) からmacOS用の `.zip` ファイルをダウンロード。
* `.zip` を展開し、`ngrok` を `/Applications` フォルダに移動。
* Finder で `/Applications` フォルダを開き、`ngrok` をダブルクリック。

## 2-3 ngrokの初期設定（認証トークンの登録）

* [ngrokのダッシュボード](https://dashboard.ngrok.com/get-started/setup/windows) にアクセス。
* 「Your Authtoken」をコピー。
<img width="600pix" alt="2025-01-31_13h06_25.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202911/4cfd6d72-f22d-d3c4-7fe7-1ea985bb3b08.jpeg">

* ターミナル（コマンドプロンプト）を開き、以下のコマンドを実行。

```sh
ngrok config add-authtoken <Your Authtoken>
```


## 2-4 Streamlitアプリの起動
**既にアプリが起動している場合は、このステップは不要だ！**

**アプリを閉じてしまっていたら，**
まずはDockerDesktopを立ち上げて
次にターミナル（Powershell）で，

```bash
docker run -p 8501:8501 streamlit-llm
```
ちょっと待つとこんなのが出る．
```bash
Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.


  You can now view your Streamlit app in your browser.

  URL: http://0.0.0.0:8501
```


## 2-5 ngrokで公開URLを取得

* Windowsの検索バーで「ngrok」と入力し、出てきた`ngrok.exe` をつついて実行。
* ターミナルで以下のコマンドを実行。

```bash
ngrok http 8501
```

* 画面に表示された **公開URL（https://xxxx.ngrok.io など）** をコピー。

## 2-6 公開URLを共有

取得したURLを他のユーザーと共有すれば、インターネット経由でStreamlitアプリにアクセス可能。

### 注意：上記はセキュリティも何もあったもんじゃないので，その辺が気になる方はngrokのドキュメントをよく読んで補強してください．