ブログ

LlamaIndex、Elasticsearch、Mistral を使用した RAG (検索拡張生成)

LlamaIndex、Elasticsearch、ローカルで実行される Mistral を使用して RAG (Retrieval Augmented Generation) システムを実装する方法を学びます。

このブログでは、Elasticsearch をベクター データベースとして使用し、RAG テクニック (Retrieval Augmented Generation) を使用して Q&A エクスペリエンスを実装する方法について説明します。LlamaIndex とローカルで実行されている Mistral LLM を使用します。

始める前に、いくつかの用語を見てみましょう。

用語について

LlamaIndex は、LLM (大規模言語モデル) アプリケーションを構築するための主要なデータ フレームワークです。LlamaIndex は、RAG (Retrieval Augmented Generation) アプリケーションの構築のさまざまな段階に抽象化を提供します。LlamaIndex や LangChain などのフレームワークは、アプリケーションが特定の LLM の API に密結合されないように抽象化を提供します。

ElasticsearchElasticによって提供されています。Elastic は、精度の高い全文検索、意味理解のためのベクトル検索、両方の長所を生かしたハイブリッド検索をサポートする検索および分析エンジンである Elasticsearch を提供する業界リーダーです。Elasticsearch はスケーラブルなデータ ストアおよびベクター データベースです。このブログで使用している Elasticsearch の機能は、Elasticsearch の無料およびオープン バージョンで利用できます。

検索拡張生成 (RAG)は、LLM に外部知識を提供してユーザークエリへの応答を生成する AI テクニック/パターンです。これにより、LLM 応答を特定のコンテキストに合わせて調整できるようになり、応答がより具体的になります。

Mistral は、オープンソースと最適化されたエンタープライズ グレードの LLM モデルの両方を提供します。このチュートリアルでは、ラップトップで実行されるオープン ソース モデルmistral-7bを使用します。ラップトップでモデルを実行したくない場合は、代わりにクラウド バージョンを使用することもできます。その場合、適切な API キーとパッケージを使用するようにこのブログのコードを変更する必要があります。

Ollama は、ラップトップ上で LLM をローカルに実行するのに役立ちます。Ollama を使用して、オープンソースの Mistral-7b モデルをローカルで実行します。

埋め込みは、テキスト/メディアの意味を数値的に表現したものです。それらは高次元情報の低次元表現です。

LlamaIndex、Elasticsearch、Mistral を使用した RAG アプリケーションの構築: シナリオの概要

シナリオ:

架空の住宅保険会社のエージェントと顧客間のコールセンター会話のサンプル データセット (JSON ファイル) があります。次のような質問に答えられるシンプルなRAGアプリケーションを構築します。

Give me summary of water related issues.

高レベルフロー

Ollama を使用して Mistral LLM をローカルで実行しています。

次に、JSON ファイルからの会話をDocumentsとしてElasticsearchStore (Elasticsearch を基盤とする VectorStore) に読み込みます。ドキュメントをロードする際に、ローカルで実行されている Mistral モデルを使用して埋め込みを作成します。これらの埋め込みは会話とともに LlamaIndex Elasticsearch ベクター ストア ( ElasticsearchStore ) に保存されます。

LlamaIndex IngestionPipeline を設定し、使用するローカル LLM (この場合は Ollama 経由で実行される Mistral) に提供します。

「水に関する問題の概要を教えてください。」のような質問をすると、Elasticsearch はセマンティック検索を実行し、水問題に関連する会話を返します。これらの会話は元の質問とともに、ローカルで実行されている LLM に送信され、回答が生成されます。

RAGアプリケーションの構築手順

Mistralをローカルで実行する

Ollamaをダウンロードしてインストールします。Ollamaをインストールした後、このコマンドを実行してmistralをダウンロードして実行します。

ollama run mistral

モデルを初めてローカルにダウンロードして実行するには数分かかる場合があります。以下のような「雲についての詩を書いてください」という質問をして、ミストラルが実行されているかどうかを確認し、その詩が気に入ったものかどうかを確認します。後でコードを通じてミストラル モデルとやり取りする必要があるため、ollama を実行したままにしておきます。

Elasticsearchをインストール

クラウド デプロイメントを作成する (手順はこちら) か、Docker で実行する (手順はこちら) ことで、Elasticsearch を起動して実行します。ここから開始して、Elasticsearch の実稼働グレードのセルフホスト型デプロイメントを作成することもできます。

クラウド デプロイメントを使用している場合は、手順に記載されているように、デプロイメント用の API キーとクラウド ID を取得します。後で使用します。

RAGアプリケーション

参考までに、コード全体はこのGithub リポジトリにあります。以下のコードを実行するため、リポジトリのクローン作成はオプションです。

お気に入りの IDE で、以下の 3 つのファイルを使用して新しい Python アプリケーションを作成します。

  • index.py データのインデックス作成に関連するコードが配置される場所。

  • query.py クエリと LLM の相互作用に関連するコードが配置される場所。

  • .env API キーなどの構成プロパティが配置される場所。

いくつかのパッケージをインストールする必要があります。まず、アプリケーションのルート フォルダーに新しい Python仮想環境を作成します。

python3 -m venv .venv

仮想環境をアクティブ化し、以下の必要なパッケージをインストールします。

source .venv/bin/activate
pip install llama-index 
pip install llama-index-embeddings-ollama
pip install llama-index-llms-ollama
pip install llama-index-vector-stores-elasticsearch
pip install sentence-transformers
pip install python-dotenv

データのインデックス作成

架空の住宅保険会社の顧客とコール センター エージェント間の 会話 が含まれる conversations.json ファイルをダウンロードします。アプリケーションのルートディレクトリに、2つのPythonファイルと.envファイルと一緒にファイルを配置します。先ほど作成したファイル。以下はファイルの内容の例です。

{
    "conversation_id": 103,
    "customer_name": "Sophia Jones",
    "agent_name": "Emily Wilson",
    "policy_number": "JKL0123",
    "conversation": "Customer: Hi, I'm Sophia Jones. My Date of Birth is November 15th, 1985, Address is 303 Cedar St, Miami, FL 33101, and my Policy Number is JKL0123.\nAgent: Hello, Sophia. How may I assist you today?\nCustomer: Hello, Emily. I have a question about my policy.\nCustomer: There's been a break-in at my home, and some valuable items are missing. Are they covered?\nAgent: Let me check your policy for coverage related to theft.\nAgent: Yes, theft of personal belongings is covered under your policy.\nCustomer: That's a relief. I'll need to file a claim for the stolen items.\nAgent: We'll assist you with the claim process, Sophia. Is there anything else I can help you with?\nCustomer: No, that's all for now. Thank you for your assistance, Emily.\nAgent: You're welcome, Sophia. Please feel free to reach out if you have any further questions or concerns.\nCustomer: I will. Have a great day!\nAgent: You too, Sophia. Take care.",
    "summary": "A customer inquires about coverage for stolen items after a break-in at home, and the agent confirms that theft of personal belongings is covered under the policy. The agent offers assistance with the claim process, resulting in the customer expressing relief and gratitude."
}

index.pyに、json ファイルを読み取ってドキュメントのリストを作成するget_documents_from_fileという関数を定義します。ドキュメントオブジェクトは、LlamaIndex が扱う情報の基本単位です。

# index.py
import json, os
from llama_index.core import Document, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.vector_stores.elasticsearch import ElasticsearchStore
from dotenv import load_dotenv

def get_documents_from_file(file):
   """Reads a json file and returns list of Documents"""

   with open(file=file, mode='rt') as f:
       conversations_dict = json.loads(f.read())
      
   # Build Document objects using fields of interest.
   documents = [Document(text=item['conversation'],
                         metadata={"conversation_id": item['conversation_id']})
                for
                item in conversations_dict]
   return documents

IngestionPipelineを作成する

まず、 Install Elasticsearchセクションで取得した Elasticsearch CloudID と API キーを.envファイルに追加します。.envファイルは以下のようになります (実際の値を使用)。

ELASTIC_CLOUD_ID=<REPLACE WITH YOUR CLOUD ID>
ELASTIC_API_KEY=<REPLACE WITH YOUR API_KEY>

LlamaIndex IngestionPipeline を使用すると、複数のコンポーネントを使用してパイプラインを構成できます。以下のコードをindex.pyファイルに追加します。

# index.py

# Load .env file contents into env
# ELASTIC_CLOUD_ID and ELASTIC_API_KEY are expected to be in the .env file.
load_dotenv('.env')

# ElasticsearchStore is a VectorStore that
# takes care of ES Index and Data management.
es_vector_store = ElasticsearchStore(index_name="calls",
                                     vector_field='conversation_vector',
                                     text_field='conversation',
                                     es_cloud_id=os.getenv("ELASTIC_CLOUD_ID"),
                                     es_api_key=os.getenv("ELASTIC_API_KEY"))


def main():
    # Embedding Model to do local embedding using Ollama.
    ollama_embedding = OllamaEmbedding("mistral")

    # LlamaIndex Pipeline configured to take care of chunking, embedding
    # and storing the embeddings in the vector store.
    pipeline = IngestionPipeline(
        transformations=[
            SentenceSplitter(chunk_size=350, chunk_overlap=50),
            ollama_embedding,
        ],
        vector_store=es_vector_store
    )

    # Load data from a json file into a list of LlamaIndex Documents
    documents = get_documents_from_file(file="conversations.json")

    pipeline.run(documents=documents)
    print(".....Done running pipeline.....\n")


if __name__ == "__main__":
    main()

前述のように、LlamaIndex IngestPipeline は複数のコンポーネントで構成できます。パイプラインの行pipeline = IngestionPipeline(...に 3 つのコンポーネントを追加しています。

  • SentenceSplitter : get_documents_from_file()の定義からわかるように、各ドキュメントには、json ファイルにある会話を保持するテキスト フィールドがあります。このテキスト フィールドは長いテキストです。セマンティック検索がうまく機能するには、小さなテキストのチャンクに分割する必要があります。SentenceSplitterクラスがこれを実行します。これらのチャンクは、LlamaIndex 用語ではノードと呼ばれます。ノードには、それが属するドキュメントを指すメタデータが存在します。あるいは、このブログで示されているように、Elasticsearch Ingestpipeline を使用してチャンク化することもできます。

  • OllamaEmbedding : 埋め込みモデルは、テキストの一部を数値 (ベクトルとも呼ばれます) に変換します。数値表現を使用すると、単なるテキスト検索ではなく、単語の意味と一致する検索結果を表示するセマンティック検索を実行できます。IngestionPipeline にOllamaEmbedding("mistral")を提供します。SentenceSplitter を使用して分割したチャンクは、Ollama を介してローカル マシンで実行されている Mistral モデルに送信され、Mistral によってチャンクの埋め込みが作成されます。

  • ElasticsearchStore : LlamaIndex ElasticsearchStore ベクター ストアは、作成される埋め込みを Elasticsearch インデックスにバックアップします。ElasticsearchStore は、指定された Elasticsearch インデックスの内容の作成と入力を担当します。ElasticsearchStore ( es_vector_storeで参照) を作成する際に、作成する Elasticsearch インデックスの名前 (この場合はcalls )、埋め込みを保存するインデックスのフィールド (この場合はconversation_vector )、およびテキストを保存するフィールド (この場合はconversation ) を指定します。要約すると、設定に基づいて、 ElasticsearchStore Elasticsearch に新しいインデックスを作成し、 conversation_vectorconversationフィールドとして(他の自動作成されたフィールドとともに)使用します。

これらすべてを結び付けて、 pipeline.run(documents=documents)を呼び出してパイプラインを実行します。

index.py スクリプトを実行して、取り込みパイプラインを実行します。

python index.py

パイプラインの実行が完了すると、Elasticsearch にcallsという新しいインデックスが表示されます。開発コンソールを使用して単純な elasticsearch クエリを実行すると、埋め込みとともに読み込まれたデータが表示されるはずです。

GET calls/_search?size=1

これまでに行ったことをまとめると、JSON ファイルからドキュメントを作成し、それをチャンクに分割し、それらのチャンクの埋め込みを作成し、埋め込み (およびテキスト会話) をベクター ストア (ElasticsearchStore) に保存しました。

クエリ

llamaIndex VectorStoreIndex を使用すると、関連するドキュメントを取得したり、データをクエリしたりできます。デフォルトでは、 VectorStoreIndex は埋め込みをSimpleVectorStore内のメモリ内に格納します。ただし、埋め込みを永続化するために、代わりに外部のベクトル ストア ( ElasticsearchStoreなど) を使用することもできます。

query.pyを開いて以下のコードを貼り付けます

# query.py
from llama_index.core import VectorStoreIndex, QueryBundle, Response, Settings
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama import Ollama
from index import es_vector_store

# Local LLM to send user query to
local_llm = Ollama(model="mistral")
Settings.embed_model= OllamaEmbedding("mistral")

index = VectorStoreIndex.from_vector_store(es_vector_store)
query_engine = index.as_query_engine(local_llm, similarity_top_k=10)

query="Give me summary of water related issues"
bundle = QueryBundle(query, embedding=Settings.embed_model.get_query_embedding(query))
result = query_engine.query(bundle)
print(result)

Ollama 上で実行されている Mistral モデルを指すようにローカル LLM ( local_llm ) を定義します。次に、先ほど作成した ElasticssearchStore ベクトル ストアから VectorStoreIndex ( index ) を作成し、インデックスからクエリ エンジンを取得します。クエリ エンジンを作成するときに、応答に使用するローカル LLM を参照し、ベクター ストアから取得して LLM に送信して応答を取得するドキュメントの数を構成する ( similarity_top_k=10 ) も提供します。

RAG フローを実行するには、 query.pyスクリプトを実行します。

python query.py

クエリGive me summary of water related issuesを送信します ( queryは自由にカスタマイズできます)。関連するドキュメントとともに提供される LLM からの応答は次のようになります。

提供されたコンテキストでは、水に関連する損害の補償について顧客が問い合わせた例がいくつかあります。2件のケースでは洪水により地下室が損傷し、別のケースでは屋根の漏水が問題となった。代理店は、両方のタイプの水害がそれぞれの保険でカバーされていることを確認しました。したがって、浸水や屋根の漏水などの水関連の問題は、通常、住宅保険でカバーされます。

注意点:

このブログ投稿は、Elasticsearch を使用した RAG テクニックの初心者向け紹介であるため、この開始点を本番環境に移行できるようにする機能の構成については省略しています。実稼働ユースケース向けに構築する場合は、ドキュメント レベルのセキュリティでデータを保護したり、Elasticsearch取り込みパイプラインの一部としてデータをチャンク化したり、GenAI/チャット/Q&A ユースケースで使用されているのと同じデータで他のML ジョブを実行したりするなど、より高度な側面を考慮する必要があります。

Elastic Connectorsを使用して、さまざまな外部ソース (Azure Blob Storage、Dropbox、Gmail など) からデータを取得し、埋め込みを作成することも検討してください。

Elastic は、上記すべてとそれ以上のことを可能にし、GenAI ユースケースなどに対応する包括的なエンタープライズ グレードのソリューションを提供します。

What’s next?

  • お気づきかもしれませんが、応答を作成するために、ユーザーの質問とともに 10 件の関連する会話が LLM に送信されています。これらの会話には、名前、生年月日、住所などの PII (個人を特定できる情報) が含まれる場合があります。私たちの場合、LLM はローカルなので、データ漏洩は問題になりません。ただし、クラウドで実行される LLM (OpenAI など) を使用する場合は、PII 情報を含むテキストを送信することは望ましくありません。次回のブログでは、RAG フローで外部 LLM に送信する前に PII 情報をマスキングする方法について説明します。

  • この投稿ではローカル LLM を使用しました。RAG での PII データのマスキングに関する次の投稿では、ローカル LLM からパブリック LLM に簡単に切り替える方法について説明します。

よくあるご質問

LlamaIndexとは何ですか?

LlamaIndex は、LLM (大規模言語モデル) アプリケーションを構築するための主要なデータ フレームワークです。

ミストラルとは何ですか?

Mistral は、オープンソースと最適化されたエンタープライズ グレードの LLM モデルの両方を提供する企業です。

関連記事

描くのではなく、説明する:MCPとES|QLによるAIネイティブのKibanaダッシュボード

Stratoula Kalafateli

Elasticsearchによるエンティティ解決、パート4:究極のチャレンジ

Jessica Moszkowicz

ElasticsearchとLLMによるエンティティ解決(第2部):LLM判定とセマンティック検索によるエンティティのマッチング

Jessica Moszkowicz

Elastic Agent BuilderとStrands Agents SDKの使用を開始

Jonathan Simon

LangGraph.jsとElasticsearchを使用して金融AI検索ワークフローを構築

Jeffrey Rengifo

最先端の検索体験を構築する準備はできましたか?

十分に高度な検索は 1 人の努力だけでは実現できません。Elasticsearch は、データ サイエンティスト、ML オペレーター、エンジニアなど、あなたと同じように検索に情熱を傾ける多くの人々によって支えられています。ぜひつながり、協力して、希望する結果が得られる魔法の検索エクスペリエンスを構築しましょう。

はじめましょう