Gemini APIを活用したLangChainとChromaDBによる検索拡張生成（RAG）Q&Aツール

# はじめに

## 技術記事の概要

LangChainフレームワークとベクトルデータベース「ChromaDB」を組み合わせ、Gemini APIを活用して高度な検索拡張生成（RAG）を実現するQ&Aツールの詳細とそのメリットについて解説します。

作成したレポジトリはこちら

https://github.com/sugiyama404/practice_langchain_rag

# 検索拡張生成（RAG）とは

## 概要と重要性

### 概要

検索拡張生成（RAG, Retrieval-Augmented Generation）は、情報検索と生成モデルを組み合わせたアプローチです。この手法は、以下のステップで動作します。

![rag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/731268/bf65d460-f1ad-0e47-a8b2-bda014e032a8.png)

1. **検索フェーズ（Retrieval Phase）**：
    入力された質問やクエリに対して、ベクトルデータベースや他の検索エンジンを使用して、関連する情報や文書を検索します。この段階では、質問の文脈に基づいて最も関連性の高いデータを抽出します。


2. **生成フェーズ（Generation Phase）**：
    検索フェーズで得られた情報を利用して、生成モデル（Gemini API）を使用して回答を生成します。このモデルは、提供されたコンテキストを基にして、自然言語で最適な回答を作成します。


この方法により、生成モデルはより豊富で関連性の高い情報をもとに回答を生成でき、単独の生成モデルよりも精度の高い応答を提供します。

### 重要性

RAGは従来の情報検索と生成モデルの限界を克服するために非常に重要です。以下にその重要性を示します：

1. **精度の向上**
    検索フェーズで関連する情報を収集することで、生成フェーズのモデルはより具体的で正確な回答を生成することができます。これにより、誤った情報や不正確な回答が減少します。

2. **情報の豊富さ**
    ベクトルデータベースや検索エンジンからの広範なデータソースを利用することで、より詳細で深い知識を含む回答を提供することが可能です。

3. **効率の向上**
    RAGは、単一の生成モデルが大量の情報を保持する必要がなくなるため、効率的に機能します。検索フェーズが必要な情報を迅速に提供し、生成フェーズがその情報を基に回答を構築します。

4. **柔軟性**
    多様な情報ソースからのデータを取り込むことができるため、幅広い質問に対して柔軟に対応することができます。

参考文献

https://www.nri.com/jp/knowledge/glossary/lst/alphabet/rag


## 主要な応用分野

RAGの応用分野は非常に広範であり、以下のような領域で特に有効です。

1. **カスタマーサポート**
    顧客からの問い合わせに対して、迅速かつ正確な回答を提供するためにRAGを活用します。これにより、サポートエージェントの負担を軽減し、顧客満足度を向上させることができます。

2. **教育分野**
    学生や教育者が特定のトピックに関する詳細な情報を求める際に、RAGは迅速に関連資料を提供し、理解を深めるための具体的な回答を生成します。

3. **医療情報提供**
    医療従事者や患者が特定の病状や治療法についての情報を探す際に、RAGは最新の研究や論文を基にした正確な情報を提供し、診断や治療の助けとなります。

4. **リサーチアシスタント**
    研究者が特定の研究課題に関する文献やデータを検索し、それを基にした要約や解説を生成するために使用されます。これにより、研究の効率が向上します。

5. **コンテンツ生成**
    記事、ブログ、レポートなどのコンテンツ生成において、RAGはトピックに関連する情報を検索し、質の高いコンテンツを生成するために利用されます。

6. **ビジネスインテリジェンス**
ビジネスリーダーやアナリストが市場動向や競合情報を収集し、それを基にした戦略的なレポートや提案を作成する際にRAGが役立ちます。


# 技術の概要と統合方法の理解

## LangChainフレームワークの基本的な機能とその役割

LangChainは、大規模言語モデル（LLM）を活用してアプリケーションを効率的に開発するためのフレームワークです。IBM Research AIによって開発され、2022年9月にオープンソース化されました。

#### LangChainの主な機能は以下の通りです。

+ **シンプルなAPI**: LangChainは、Pythonなどの一般的なプログラミング言語で記述できるシンプルなAPIを提供します。これにより、データサイエンティストやソフトウェア開発者など、幅広いユーザーがLLMアプリケーションを簡単に開発することができます。
+ **モジュラーアーキテクチャ**: LangChainはモジュラーアーキテクチャを採用しており、様々なコンポーネントを組み合わせてアプリケーションを構築することができます。このモジュラー性により、開発者は既存のコンポーネントを再利用したり、独自のカスタムコンポーネントを作成したりすることができます。
+ **エージェント**: LangChainには、エージェントと呼ばれるコンポーネントが含まれています。エージェントは、ユーザーとのインタラクション、LLMへのクエリの実行、アプリケーションロジックの処理など、様々なタスクを実行することができます。
+ **チェーン**: LangChainには、チェーンと呼ばれるコンポーネントが含まれています。チェーンは、複数のエージェントを組み合わせることで、複雑なワークフローを構築することができます。
+ **インデックス**: LangChainには、インデックスと呼ばれるコンポーネントが含まれています。インデックスは、外部データソース（PDFファイル、Excelファイル、CSVファイルなど）へのアクセスを提供します。これにより、LLMアプリケーションは外部データに基づいて推論や生成を行うことができます。


#### LangChainの役割は以下の通りです。

+ **LLMアプリケーション開発の効率化**: LangChainは、LLMアプリケーション開発に必要な様々な機能を提供することで、開発プロセスを効率化します。
+ **LLMアプリケーションの品質向上**: LangChainは、モジュラーアーキテクチャやエージェントなどの機能を提供することで、LLMアプリケーションの品質向上を支援します。
+ **LLMアプリケーションのデプロイと運用**: LangChainは、LLMアプリケーションのデプロイと運用に必要なツールを提供します。

LangChainは、まだ比較的新しいフレームワークですが、すでに様々なLLMアプリケーション開発に使用されています。例えば、チャットボット、自動要約ツール、質問応答システムなどが開発されています。

参考文献

https://python.langchain.com/v0.1/docs/integrations/providers/ibm/

## ChromaDBの特徴とベクトルデータベースとしての利点

ChromaDBは、大規模な言語モデル（LLM）アプリケーションの開発・構築のために設計されたオープンソースのベクトルデータベースです。Redis上に構築されており、高速、効率的、使いやすいという特徴があります。

#### ChromaDBの特徴

+ **高速で効率的**: Redis上に構築されているため、ベクトルの格納とクエリが非常に高速に行えます。
+ **使いやすさ**: シンプルで直感的なAPIが備わっており、初めてでも簡単に始めることができます。
+ **スケーラビリティ**: 水平方向にスケールできるため、データ量やクエリ量が増加しても処理能力を維持できます。
+ **統合性**: LangChainやLlamaIndexなどの他のツールと簡単に統合できます。
+ **オープンソース**: オープンソースなので、無料で利用でき、コミュニティからのサポートを受けることができます。

参考文献

https://www.trychroma.com/

https://qiita.com/jingwora/items/d39635b2d727725bf4ca

#### ベクトルデータベースとしての利点

+ **ベクトル検索**: テキスト、画像、音声などのデータをベクトル形式で格納することで、類似性の高いデータ効率的に検索することができます。
+ **次元削減**: 高次元ベクトルを低次元ベクトルに変換することで、計算コストを削減し、処理速度を向上させることができます。
+ **機械学習**: LLMアプリケーションの学習や推論に役立てることができます。

ChromaDBは、ベクトルデータベースの分野で注目されている有望な技術です。その高速性、使いやすさ、スケーラビリティ、統合性、オープンソース性により、様々なLLMアプリケーションで利用されています。

## Gemini APIの活用方法とその特性

Gemini APIは、Google AIが提供する強力な生成AIモデルのAPIです。テキストと画像の両方の入力を処理し、様々なクリエイティブなタスクを実行することができます。

#### 活用方法

Gemini APIは、以下の様な様々な活用方法があります。

+ **文章生成**: 詩、コード、台本、音楽作品、メール、手紙などの創作テキストを生成することができます。文章の要約や翻訳、言語変換なども可能です。
+ **画像生成**: テキストの説明に基づいて、画像を生成することができます。既存の画像を編集したり、複数の画像を合成したりすることもできます。
+ **質問応答**: 質問に対して、包括的かつ有益な回答を提供することができます。質問がオープンエンド、挑戦的、または奇妙であっても、回答することができます。
+ **その他**: コード生成、チャットボット作成、ゲーム作成など、様々な用途に使用することができます。

#### 特性
Gemini APIは以下の特性を持っています。

+ **マルチモーダル**: テキストと画像の両方の入力を処理することができます。
+ **クリエイティブ**: 詩、コード、台本、音楽作品、メール、手紙などの創作テキストを生成することができます。
+ **有益**: 質問に対して、包括的かつ有益な回答を提供することができます。
+ **強力**: 様々な複雑なタスクを実行することができます。
+ **使いやすい**: Python、Java、JavaScriptなどの様々なプログラミング言語から利用することができます。

参考文献

https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview?hl=ja

# 実際のアーキテクチャ設計とデータフローの理解

## 検索拡張生成（RAG）Q&Aツールの全体的なアーキテクチャ

![structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/731268/0da0147c-5bd4-323f-87ff-c88e4543be0c.png)


## 各コンポーネントの相互作用とデータフローの詳細

1. **質問の受け取り**
    ユーザーからの質問をテキスト入力として受け取ります。

2. **ベクトル化と類似検索**
    入力された質問をベクトルデータに変換します。変換したベクトルデータを用いて、ChromaDBを使用し類似質問を検索します。

3. **Gemini APIへの問い合わせ**
    類似検索の結果に基づいて、Gemini APIに問い合わせを行います。Gemini APIから最適な回答を生成します。

4. **回答の提供**
    最終的な回答をユーザーに提供します。

# 実装方法の具体的なステップバイステップガイド

## コード例とその解説を通じて、実際の実装を支援

### Dockerfile

ChromaDBを利用するため、開発はpython:3.11-bookwormイメージが必須です。

```Dockerfile:Dockerfile
# 開発用ステージ
FROM python:3.11-bookworm AS developer

ENV PYTHONUNBUFFERED=1
WORKDIR /app

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

COPY . .

# ビルドステージ
FROM python:3.11-bookworm AS builder
WORKDIR /app

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

COPY . .

# プロダクトステージ
FROM gcr.io/distroless/python3-debian12:nonroot AS production

WORKDIR /app

COPY --from=builder /usr/local/lib/python3.11 /usr/local/lib/python3.11

ENV PYTHONPATH=/usr/local/lib/python3.11/site-packages
COPY --from=builder /app/ .

USER nonroot

CMD ["/app/opt/main.py"]
```

### chromaDBに学習される文章

以下、chromaDBに学習される文章例です。

```text:apserver/opt/docs/sample.txt
## DARKER THAN BLACKについて
## ストーリー概要
物語は、謎の空間「地獄門（ヘルズゲート）」が突如東京に出現し、その影響で超能力を持つ「契約者（コントラクター）」と呼ばれる人々が現れた世界を舞台にしています。契約者たちは、人間離れした能力を持つ一方で、その使用には何らかの代償（例：特定の行動を取る、体力を消耗するなど）を払わなければなりません。
## あらすじ
10年前突如として東京を襲った異変、通称「地獄門」（ヘルズ・ゲート）といわれる未知の領域が出現して以降、この世界は本当の“空”を失い、夜空を覆う満天の星は偽りの星達のものとなった。また、時を同じくして「契約者」と呼ばれる、特別な能力を身につけた者達が現れはじめる。人間らしい感情や「契約対価」という代償と引き換えに、人外の能力を得た存在である彼らを利用して、このゲートに関する情報を得ようと、各国の諜報機関が東京にエージェントを送り込む。
主人公・黒（ヘイ）もまたそうした契約者の一人だった。彼はある“組織”に所属しており、他の諜報機関などからは「BK-201」または「黒の死神」と呼ばれている。そして、同じ“組織”のメンバーである銀（イン）・猫（マオ）・黄（ホァン）と共に、ゲートに関連する情報を集め“組織”の任務を実行していくことになる。
## 組織
作中では正式名称が明かされないため、単に“組織”と呼ばれている。各国諜報機関の間ではそれなりに有名。各国警察・諜報機関に根を張り、国連機関PANDORAすら隠れ蓑に使う巨大な組織。構成員のコードネームは色に由来するものが多い。その活動内容の目的はゲートを閉じること（消失させることではない）、ひいては契約者を消滅させることだった。

黒達4人のメンバーはコードネームや偽名に中国語を用いているが、中国人ではない。構成員の多くがその例と同様で、必ずしも中国籍というわけではない。
## 主なキャラクター
黒（ヘイ） - 主人公の契約者。コードネームは「黒の死神」。彼はエレクトリックショックの能力を持ち、冷酷かつプロフェッショナルな暗殺者として活動しています。
銀（イン） - ドール（感情を持たない人造人間）で、観測霊を通じて情報を収集する能力を持っています。黒と行動を共にする。
黄（ホァン） - 黒たちのチームのリーダーで、かつては警察官だったが、現在は契約者たちを指揮している。
紫苑（シオン） - 黒の双子の妹で、謎に包まれた存在。物語のキーとなるキャラクター。
```

参考文献

https://ja.wikipedia.org/wiki/DARKER_THAN_BLACK_-黒の契約者-

### ヘルパー関数

このコードは、ChromaDBデータベースから文書を取得し、重複を削除して参考文献リストを作成するヘルパー関数です。

```python:apserver/opt/utils/main.py
from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings
import chromadb
from chromadb.config import Settings
import warnings
warnings.simplefilter('ignore')

def get_embedding_model()->HuggingFaceEmbeddings:
    """
    HuggingFaceの事前学習済みのモデルをロードして返す関数
    戻り値:
        HuggingFaceEmbeddingsオブジェクト: 事前学習済みのモデルをラップしたオブジェクト
    """
    return HuggingFaceEmbeddings(
        model_name="intfloat/multilingual-e5-large"
    )

def get_db_conn()->chromadb.HttpClient:
    """
    ChromaDBデータベースへの接続を確立して返す関数
    戻り値:
        Chromadb.HttpClientオブジェクト: ChromaDBデータベースへの接続を表現するオブジェクト
    """
    client = chromadb.HttpClient(
        host="db",
        port=8000,
        settings=Settings(allow_reset=True, anonymized_telemetry=False),
        )
    return client

def remove_duplicates(data:list)->str:
    """
    入力されたリストの重複要素を削除し、sourceの値のみを出力する関数
    引数:
        data: 重複を含むリスト
    戻り値:
        重複要素を除いたsourceの値のリスト
    """
    unique_sources = []

    for item in data:
        for i in item:
            source = i['source']
            if source not in unique_sources:
                unique_sources.append(source)

    docs = "[参考文献]"
    for source in unique_sources:
        docs += f"\n{source}"

    return docs
```

### DB保存処理

このコードは、ドキュメントをChromaDBと呼ばれるデータベースにインデックス化するものです。ChromaDB は、テキストをベクトル化して保存するデータベースです。このコードは、以下の手順でドキュメントをインデックス化します。

1. ドキュメントを /app/opt/docs ディレクトリから読み込む
2. テキストをチャンクに分割する
3. チャンクをベクトル化する
4. ベクトルを ChromaDB に保存する

```python:apserver/opt/save.py
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.vectorstores import Chroma

import sys
sys.path.append('/app')
from opt.utils.main import get_embedding_model, get_db_conn

def save():
    """
    ドキュメントをChromaDBにインデックス化する関数
    """
    # データベース接続とエンベディングモデルを取得
    embedding_model = get_embedding_model()
    client = get_db_conn()

    # ドキュメントローダーを初期化
    loader = DirectoryLoader("/app/opt/docs")

    # テキストをチャンクに分割
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=100,
        chunk_overlap=10
    )
    split_texts = loader.load_and_split(text_splitter=text_splitter)

    # ChromaDBにドキュメントをインデックス化
    chroma_db = Chroma(
        collection_name="my_collection",
        embedding_function=embedding_model,  # エンベディング関数を直接指定
        client=client,
    )

    chroma_db.add_documents(documents=split_texts)
    print("インデックス化が完了しました")

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

### 類似文書検索処理

このコードは、ChromaDB に保存されたドキュメントの中から、クエリと類似性の高いドキュメントを検索するものです。

```python:apserver/opt/load.py
import sys
sys.path.append('/app')
from opt.utils.main import get_embedding_model, get_db_conn

def similer_documents_search(query:str)->list:
    """
    クエリの類似文書を検索する関数
    Args:
        query (str): 検索クエリ
    戻り値:
        クエリの類似文書を検索した値のリスト
    """
    # データベース接続とエンベディングモデルを取得
    client = get_db_conn()
    embedding_model = get_embedding_model()

    collection = client.get_collection(name="my_collection")
    # クエリのベクトル化
    query_vector = embedding_model.embed_documents(query)
    # 類似度検索の実行
    results = collection.query(query_vector, n_results=3)
    return results

if __name__ == "__main__":
    # 類似度検索クエリ
    query = "黒（ヘイ）について教えてください"
    results = similer_documents_search(query)
    print(results['documents'])
    print(results['metadatas'][0])
```

### main関数

このコードは、以下の機能を実現します。

1. ユーザーから「darker than black」に関する質問を入力
2. 類似ドキュメント検索
3. 検索結果と質問を元に Gemini モデルにプロンプトを作成
4. Gemini モデルを使用して、100文字以内の回答を生成
5. 生成された質問と回答を出力


```python:apserver/opt/main.py
import os
from dotenv import load_dotenv
import google.generativeai as genai

import sys
sys.path.append('/app')
from opt.load import similer_documents_search
from opt.utils.main import remove_duplicates

load_dotenv()
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
genai.configure(api_key=GOOGLE_API_KEY)
GEMINI_MODEL = os.getenv('GEMINI_MODEL')

def question(query: str)->str:
    results = similer_documents_search(query)
    references = remove_duplicates(results['metadatas'])

    prompt = f"""
    [参考情報]に基づき100字以内で[質問]に答えてください。
    [質問]
    {query}
    [参考情報]
    {results['documents']}
    """

    gemini_pro = genai.GenerativeModel(GEMINI_MODEL)
    response = gemini_pro.generate_content(prompt)
    result = response.text + "\n" + references
    return result

if __name__ == "__main__":
    # query = "黒（ヘイ）について教えてください"
    query = input("「darker than black」に関する質問を入力してください：")
    res = question(query)
    print(f"[質問]\n{query}\n[回答]\n{res}")
```

## 実行

Gemini apiからapiキーをコピーし
apserver/opt/.envに貼り付けて下さい。

```text:apserver/opt/.env
GOOGLE_API_KEY="apiキー"
GEMINI_MODEL="gemini-pro"
```

以下を実行して下さい。

```console
docker compose up
docker compose exec api python opt/save.py
docker compose exec api python opt/main.py
```

実行例は以下の通りです。

![QandA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/731268/c363684c-2d8a-9173-7ca6-f858b5e95115.png)