ローカルLLMでGraphRAGを実装して「クリスマスキャロル」を分析してみた

**「クリスマスキャロル」の真のテーマは何だったのか？ローカル環境で構築した最強の質問応答AIで、その謎に迫ります！**

最近、ChatGPTをはじめとする大規模言語モデル(LLM)が注目を集めていますが、LLMは大量のテキストデータから学習するため、特定の分野の質問に対しては、的確な回答を返すことが難しい場合があります。

そこで今回は、**GraphRAG**と呼ばれる技術を使って、LLMが「クリスマスキャロル」の内容をより深く理解し、高度な質問に答えられるようにする方法を紹介します。そして、実際にGraphRAGを使って「クリスマスキャロル」を分析し、その結果を考察します。

> 実験の時間短縮のため「クリスマスキャロル」のデータセットを大幅に減らして実験を行っています。

## GraphRAGとは？

GraphRAGは、Microsoft Researchが開発した、**LLMの知識ベースを強化するためのオープンソースのパイプライン**です。

複雑な文章や専門的な知識を必要とする質問に対して、LLMがより正確で詳細な回答を生成できるようサポートします。

具体的には、GraphRAGは、テキストデータから**知識グラフ**と呼ばれる構造化データを自動的に作成し、LLMと連携させることで、LLMがより高度な推論を可能にします。

**例：**

- **従来のLLM**: 「クリスマスキャロルのテーマは？」→「家族愛、慈愛、 forgiveness...」
- **GraphRAG適用後**: 「クリスマスキャロルのテーマは？」→「主人公スクルージの**贖罪**と**社会的責任**の重要性を描いた物語であり、読者に対し、真の豊かさとは何かを問いかける作品です。」

このように、GraphRAGを使用することで、LLMはより深く文章を理解し、人間のように文脈を踏まえた回答を生成できるようになります。

---

https://hamaruki.com/graphrag-beginners-guide/

https://hamaruki.com/building-global-search-graphrag-ai-data-analysis/

## GraphRAGをローカル環境に構築する

今回は、GraphRAGをローカル環境で動かすために、Dockerとdocker-composeを利用します。

### 必要なもの

* Docker: コンテナ化技術。アプリケーションの実行環境をパッケージ化し、ポータブルにすることができます。
* docker-compose: 複数のDockerコンテナを定義・実行するためのツール。複数のコンテナを連携して動作させるアプリケーションを簡単に構築できます。
* Python: プログラミング言語。GraphRAGはPythonで実装されています。
* Git: バージョン管理システム。GraphRAGのソースコードを取得するために使用します。
* llama.cppで動かすモデル（今回は「models\Llama-3-ELYZA-JP-8B-q4_k_m.gguf」と「ggml-e5-mistral-7b-instruct-q4_k_m.gguf」を使用）

https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B-GGUF

https://huggingface.co/dranger003/e5-mistral-7b-instruct-GGUF

### Docker/docker-composeのインストール

Dockerのインストールは[こちら](https://docs.docker.com/desktop/install/windows-install/)を参考にしてください。

### プロジェクトの作成

まず、任意のディレクトリを作成し、その中に以下の３つのファイルを作成します。

- Dockerfile.llama
- Dockerfile
- docker-compose.yml

### Dockerfile.llama

```dockerfile
# ビルドステージ
ARG CUDA_IMAGE="12.5.0-devel-ubuntu22.04"
FROM nvidia/cuda:${CUDA_IMAGE}

# 外部からのアクセスを許可するために、ホストを0.0.0.0に設定する必要がある
ENV HOST 0.0.0.0

# 必要なパッケージのインストールと OpenCL の設定
RUN apt-get update && apt-get upgrade -y \
    && apt-get install -y git build-essential \
    python3 python3-pip gcc wget \
    ocl-icd-opencl-dev opencl-headers clinfo \
    libclblast-dev libopenblas-dev \
    && mkdir -p /etc/OpenCL/vendors && echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd

# アプリケーションディレクトリの設定
WORKDIR /app

# llama.cpp リポジトリのクローン
RUN git clone https://github.com/ggerganov/llama.cpp.git
WORKDIR /app/llama.cpp

# ビルド関連の環境変数の設定
ENV CUDA_DOCKER_ARCH=all
ENV LLAMA_CUBLAS=1

# 依存関係のインストール
RUN python3 -m pip install --upgrade pip pytest cmake scikit-build setuptools fastapi uvicorn sse-starlette pydantic-settings starlette-context

# CMakeの実行
RUN mkdir build && cd build && \
    cmake .. -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=all

# makeコマンドの実行（エラー時の再試行とログ出力を含む）
RUN cd build && \
    make VERBOSE=1 -j4 || \
    (echo "Make failed. Retrying with more details..." && \
    make VERBOSE=1 && \
    echo "If make succeeded this time, there might be a concurrency issue.") && \
    cp bin/* ..

# コンテナ起動時のデフォルトコマンド
CMD ["/bin/bash"]
```

#### Dockerfile.llamacpp の解説

* `FROM nvidia/cuda:${CUDA_IMAGE}`: NVIDIA CUDAのDockerイメージをベースイメージとして使用します。
* `ENV HOST 0.0.0.0`: コンテナが外部からのアクセスを受けられるようにホストを設定します。
* `RUN apt-get ...`: 必要なパッケージをインストールし、OpenCLを設定します。
* `WORKDIR /app`: 作業ディレクトリを`/app`に設定します。
* `RUN git clone ...`: llama.cppのリポジトリをクローンします。
* `WORKDIR /app/llama.cpp`: 作業ディレクトリを`/app/llama.cpp`に変更します。
* `ENV CUDA_DOCKER_ARCH=all`: CUDAアーキテクチャをすべて有効にします。
* `ENV LLAMA_CUBLAS=1`: CUBLASを有効にします。
* `RUN python3 -m pip ...`: 必要なPythonパッケージをインストールします。
* `RUN mkdir build && cd build && cmake .. -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=all`: CMakeを実行してビルドファイルを生成します。
* `RUN cd build && make ...`: ビルドを実行します。
* `CMD ["/bin/bash"]`: コンテナ起動時にbashを実行します。

### Dockerfile

```dockerfile
# Dockerfile
FROM python:3.10

WORKDIR /app

RUN apt-get update && apt-get install -y git

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
```

#### Dockerfileの解説

* `FROM python:3.10`: Python 3.10のDockerイメージをベースイメージとして使用します。
* `WORKDIR /app`: 作業ディレクトリを`/app`に設定します。
* `RUN apt-get ...`: Gitをインストールします。
* `COPY requirements.txt .`: `requirements.txt`をコンテナ内のカレントディレクトリにコピーします。
* `RUN pip install --no-cache-dir -r requirements.txt`: `requirements.txt`に記載されているPythonパッケージをインストールします。

### docker-compose.yml

```yaml
version: '3.8'
services:
  app:
    build: .
    volumes:
      - .:/app
    tty: true
    env_file:
      - .env
    extra_hosts:
      - "host.docker.internal:host-gateway"
      
  llama-cpp-embedding:
    build: 
      context: .
      dockerfile: Dockerfile.llama
    volumes:
      - ./models:/models
    ports:
      - "8081:8081"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [ gpu ]
    tty: true
    command: >
      ./llama-server
      -m /models/ggml-e5-mistral-7b-instruct-q4_k_m.gguf
      --port 8081
      --host 0.0.0.0
      -n -1
      --n-gpu-layers 10
      --embedding

  llama-cpp:
    build: 
      context: .
      dockerfile: Dockerfile.llama
    volumes:
      - ./models:/models
    ports:
      - "8080:8080"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [ gpu ]
    tty: true
    command: >
      ./llama-server
      -m /models/Llama-3-ELYZA-JP-8B-q4_k_m.gguf
      --port 8080
      --host 0.0.0.0
      -n -1
      --n-gpu-layers 10
```

#### docker-compose.ymlの解説

* `version: '3.8'`: docker-composeのバージョンを指定します。
* `services`: 複数のコンテナを定義します。
    * `app`: アプリケーションのメインコンテナです。
        * `build: .`: カレントディレクトリの`Dockerfile`を使ってイメージをビルドします。
        * `volumes: - .:/app`: ホストのカレントディレクトリをコンテナの`/app`ディレクトリにマウントします。
        * `tty: true`: コンテナをインタラクティブモードで起動します。
        * `env_file: - .env`: `.env`ファイルから環境変数を読み込みます。
        * `extra_hosts: - "host.docker.internal:host-gateway"`: ホスト名を解決できるようにします。
    * `llama-cpp-embedding`: embedding用のllama.cppコンテナです。
        * `build: ...`: `Dockerfile.llama`を使ってイメージをビルドします。
        * `volumes: - ./models:/models`: ホストの`./models`ディレクトリをコンテナの`/models`ディレクトリにマウントします。
        * `ports: - "8081:8081"`: コンテナのポート8081をホストのポート8081にバインドします。
        * `deploy: ...`: デプロイに関する設定を行います。ここでは、GPUを使用するように設定しています。
        * `tty: true`: コンテナをインタラクティブモードで起動します。
        * `command: ...`: コンテナ起動時に実行するコマンドを指定します。ここでは、`llama-server`を実行するように設定しています。
    * `llama-cpp`: メインのllama.cppコンテナです。
        * `build: ...`: `Dockerfile.llama`を使ってイメージをビルドします。
        * `volumes: - ./models:/models`: ホストの`./models`ディレクトリをコンテナの`/models`ディレクトリにマウントします。
        * `ports: - "8080:8080"`: コンテナのポート8080をホストのポート8080にバインドします。
        * `deploy: ...`: デプロイに関する設定を行います。ここでは、GPUを使用するように設定しています。
        * `tty: true`: コンテナをインタラクティブモードで起動します。
        * `command: ...`: コンテナ起動時に実行するコマンドを指定します。ここでは、`llama-server`を実行するように設定しています。


### settings.yaml

```yaml

encoding_model: cl100k_base
skip_workflows: []
llm:
  api_key: ollama #${GRAPHRAG_API_KEY}
  type: openai_chat # or azure_openai_chat
  model: llama3
  model_supports_json: true # recommended if this is available for your model.
  # max_tokens: 4000
  # request_timeout: 180.0
  api_base: http://llama-cpp:8080/v1 #https://<instance>.openai.azure.com
  # api_base: http://host.docker.internal:11434/api
  # api_version: 2024-02-15-preview
  # organization: <organization_id>
  # deployment_name: <azure_model_deployment_name>
  # tokens_per_minute: 150_000 # set a leaky bucket throttle
  # requests_per_minute: 10_000 # set a leaky bucket throttle
  requests_per_minute: 25 # set a leaky bucket throttle
  # max_retries: 10
  # max_retry_wait: 10.0
  # sleep_on_rate_limit_recommendation: true # whether to sleep when azure suggests wait-times
  # concurrent_requests: 25 # the number of parallel inflight requests that may be made

parallelization:
  stagger: 0.3
  # num_threads: 50 # the number of threads to use for parallel processing

async_mode: threaded # or asyncio

embeddings:
  ## parallelization: override the global parallelization settings for embeddings
  async_mode: threaded # or asyncio
  llm:
    api_key: ${GRAPHRAG_API_KEY}
    # api_key: ollama #${GRAPHRAG_API_KEY}
    # type: openai_embedding # or azure_openai_embedding
    model: mxbai-embed-large #text-embedding-3-small
    # model: text-embedding-3-small
    # api_base: http://host.docker.internal:11434/v1 #https://<instance>.openai.azure.com
    # api_base: http://llama-cpp:8000/v1 #https://<instance>.openai.azure.com
    api_base: http://llama-cpp-embedding:8081/v1
    # api_version: 2024-02-15-preview
    # organization: <organization_id>
    # deployment_name: <azure_model_deployment_name>
    # tokens_per_minute: 150_000 # set a leaky bucket throttle
    # requests_per_minute: 10_000 # set a leaky bucket throttle
    # max_retries: 10
    # max_retry_wait: 10.0
    # sleep_on_rate_limit_recommendation: true # whether to sleep when azure suggests wait-times
    # concurrent_requests: 25 # the number of parallel inflight requests that may be made
    # batch_size: 16 # the number of documents to send in a single request
    # batch_max_tokens: 8191 # the maximum number of tokens to send in a single request
    # target: required # or optional
  
chunks:
  size: 300
  overlap: 100
  group_by_columns: [id] # by default, we don't allow chunks to cross documents
    
input:
  type: file # or blob
  file_type: text # or csv
  base_dir: "input"
  file_encoding: utf-8
  file_pattern: ".*\\.txt$"

cache:
  type: file # or blob
  base_dir: "cache"
  # connection_string: <azure_blob_storage_connection_string>
  # container_name: <azure_blob_storage_container_name>

storage:
  type: file # or blob
  base_dir: "output/${timestamp}/artifacts"
  # connection_string: <azure_blob_storage_connection_string>
  # container_name: <azure_blob_storage_container_name>

reporting:
  type: file # or console, blob
  base_dir: "output/${timestamp}/reports"
  # connection_string: <azure_blob_storage_connection_string>
  # container_name: <azure_blob_storage_container_name>

entity_extraction:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  prompt: "prompts/entity_extraction.txt"
  entity_types: [organization,person,geo,event]
  max_gleanings: 0

summarize_descriptions:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  prompt: "prompts/summarize_descriptions.txt"
  max_length: 500

claim_extraction:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  # enabled: true
  prompt: "prompts/claim_extraction.txt"
  description: "Any claims or facts that could be relevant to information discovery."
  max_gleanings: 0

community_report:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  prompt: "prompts/community_report.txt"
  max_length: 2000
  max_input_length: 8000

cluster_graph:
  max_cluster_size: 10

embed_graph:
  enabled: false # if true, will generate node2vec embeddings for nodes
  # num_walks: 10
  # walk_length: 40
  # window_size: 2
  # iterations: 3
  # random_seed: 597832

umap:
  enabled: false # if true, will generate UMAP embeddings for nodes

snapshots:
  graphml: false
  raw_entities: false
  top_level_nodes: false

local_search:
  # text_unit_prop: 0.5
  # community_prop: 0.1
  # conversation_history_max_turns: 5
  # top_k_mapped_entities: 10
  # top_k_relationships: 10
  # max_tokens: 12000

global_search:
  # max_tokens: 12000
  # data_max_tokens: 12000
  # map_max_tokens: 1000
  # reduce_max_tokens: 2000
  # concurrency: 32

```

## 実行

```bash
docker-compose up -d --build
```

## GraphRAGの実行

```bash
python -m graphrag.index --root ./ragtest
python -m graphrag.query \
--root ./ragtest \
--method global \
"この物語の主要なテーマは何ですか？"
```


これで、ローカル環境でGraphRAGを実行することができました。


## 回答例

Global Search Response: **Summary of Key Themes**

Based on the reports from multiple analysts, the primary themes in the eBook 'A Christmas Carol' are: 

### Social Justice and Morality

The eBook explores themes of social justice, morality, and redemption, which are highly relevant to contemporary society, potentially sparking public debate and controversy. These themes are crucial in understanding the characters' motivations and the story's implications. (Importance Score: 80)

### Cultural Significance and Community Cohesion

The eBook's cultural significance could lead to a sense of shared identity and community cohesion, contributing to the community's resilience and adaptability. This theme highlights the importance of the story in shaping the community's dynamics and values. (Importance Score: 70)

### Influence on Community Dynamics

The eBook's messages and themes could influence the community's dynamics and implications, potentially leading to significant consequences. This theme emphasizes the potential impact of the story on the community's social fabric. (Importance Score: 60)

These themes are interconnected and central to the narrative, making them the primary focus of the eBook. They shall continue to spark public debate and controversy, and may influence the community's dynamics and implications.

Note: The reports do not provide further information on the specific record ids related to these themes.


## 結果と考察

GraphRAGのグローバルRAG回答は、「クリスマスキャロル」の主要なテーマとして、社会正義と道徳、文化的重要性とコミュニティの結束、コミュニティのダイナミクスへの影響を挙げました。

一見すると、これらのテーマは妥当に見えます。しかし、AIの回答は、分析が浅く、一般的な言葉の羅列が目立ちます。例えば、「公の議論と論争を引き起こす可能性」、「コミュニティの回復力と適応力への貢献」、「コミュニティのダイナミクスと意味合いへの影響」といった表現は、具体性に欠け、「クリスマスキャロル」のテーマを的確に捉えているとは言えません。

また、「クリスマスの精神」や「贖罪」といった、物語の中心的なテーマに触れていない点は、大きな欠点と言えるでしょう。

さらに、AIは回答の中で、「複数のアナリストからの報告に基づいて」と述べていますが、実際にはそのような情報は存在しません。これは、AIがもっともらしいが、実際には根拠のない情報を生成する「ハルシネーション」と呼ばれる現象の一例です。

要約すると、AIの回答は、表面的なレベルでは妥当に見えますが、深く分析すると、具体性、正確性、そして洞察力の点で不足していると言えます。 

## まとめ

今回は、GraphRAGを使ってLLMの質問応答能力を向上させる方法を紹介しました。

GraphRAGは、LLMの可能性を大きく広げる技術です。ぜひ、ご自身のプロジェクトにも活用してみてください。 

## 参考サイト

https://github.com/microsoft/graphrag/issues/374

https://mp.weixin.qq.com/s?__biz=MzI0OTAzNTEwMw==&mid=2247484079&idx=1&sn=b79a12567425ef19304bd79667be71a4&chksm=e996e29fdee16b89113018d874b95040c71f3ab8fbde75be8a3687ec1b42dd600577d5ded38b&token=2106481296&lang=zh_CN#rd

https://github.com/microsoft/graphrag

https://microsoft.github.io/graphrag/posts/get_started/