どうやら私はGraphRAGを勘違いしていたらしい[2/2]
はじめに
前回はGraphRAGのコンセプトについてご紹介しました。
本記事では、実際にGraphRAGを実行してそのインパクトを体感しようと思います。
本記事で掲載した内容をまとめたサンプルコードを公開しました。
良ければ参考にしてください。
フレームワーク選び
王道は公式ライブラリ
実行環境やLLMに制約条件がないならMicrosoft公式が出しているライブラリであるGraphRAGを使うのが良いと思います。
前回お話した通り、今話題のGraphRAGはMicrosoftのものなので、その開発元が提供しているものがあるならそれを選ぶのが間違いがないでしょう。
また、GraphRAG Acceleratorというデプロイツールを使うことで、Azure上にGraphRAGパイプラインを簡単に構築できます。
https://github.com/Azure-Samples/graphrag-accelerator READMEより
この通り、GraphRAGはOpenAI API、クラウドで動かすならAzureを前提としています。
ローカルで試す分にはOpenAI APIさえ使えれば大丈夫です。
また、GraphRAGと言うとグラフデータベースが必要というイメージがありますが、そうでもないようです。
ローカルで実行した場合は、インデックスデータはファイルとしてローカルのディレクトリに保存されます。
つまり、グラフDBは登場しません。
アクセラレーターを使った場合は図にある通りに各種ストレージに保存されます。
Cosmos DBはグラフDBとしても利用できるようですが、それ以外はグラフDBではありません。
デフォルトでは、Blob Storageへの保存パイプラインが組まれるようです。
他のプラットフォームでも使いたい
モデルはOpenAIのもの以外使うつもりがなく、インフラはAzureでよいならMS GraphRAGライブラリを使うのが、一番手がかかりません。
しかし、様々な制約から別のモデルやインフラが使いたい場面もあるかと思います。
特にインフラについては、ためらいがちだと思います。
今後どのくらい活用するのか不明瞭な時点で、GraphRAGだけのために新しくAzureを利用するのは気が重いでしょう。
また、自社で主流のLLMがOpenAI APIではないかもしれません。
複数のモデルを比較検討する、ベンダーロックインを避けると言った観点からも選択肢を増やせることは大事な観点かと思います。
加えて、実際に運用する際は、可用性やマイクロサービス的観点からインデックスデータはファイルではなく、外部のデータベースに保存しておきたくなることもあるでしょう。
これらの柔軟性を考えると、公式をラップした派生ライブラリも有用な選択肢となります。
前回の記事でも紹介した以下の記事で選択肢となりそうなライブラリが紹介されていました。
中でも、でも紹介されている以下の2つが有用そうでした。
今回はより直感的に扱えそうなLightRAGを使用して検証してみました。
以下はLightRAGのREADMEに書かれている処理フローです。
MS GraphRAG同様にグローバル検索やローカル検索が可能になっています。
インデックス時の処理フロー https://learnopencv.com/lightrag/ より
生成時の処理フロー https://learnopencv.com/lightrag/ より
LigjtRAGには以下のような特徴があります。
- グローバル検索やローカル検索に加えてそれらのハイブリッド検索も可能
- OpenAI APIはもちろん、他のモデルについても手軽に利用可能
- ClaudeやGeminiなどのOpenAI likeの API
- OllamaやHuggingFaceから取得したローカルLLM
- グラフストレージとしてNeo4JやPostgreSQLが利用可能
- Neo4Jはセルフホストとマネージドのどちらとも接続可能
- PostgreSQLはグラフストレージ、Key-Valueストレージ、ベクトルストレージすべてで使用可能
ご覧の通り、外部との連携柔軟性が非常に高いです。
ローカルモデルも使用できるのでセキュリティ要件の厳しい場面でも利用できます。
LLMはもちろん、ストレージについてもマネージドサービスとの連携が可能なので、外部との通信が許されるなら、運用負荷を下げつつ一定水準の可用性を担保できます。
特にNeo4Jはフリープランがあるので、小規模であれば試しやすいのがうれしいポイントです。
グラフDBは必須ではないですが、可視化の点でもグラフDBが適しています。
まずはチュートリアルを
難しいことは置いておいて、ひとまずLightRAGのチュートリアル通りに進めてみましょう。
本ライブラリはpip
でインストールできます。
pip install lightrag-hku
続いてOpenAIのAPIキーを環境変数にセットします。
export OPENAI_API_KEY="Your OpenAI API Key"
次にGraphRAGの対象とするデータセットを用意します。
ここでは、イギリスの有名な小説「クリスマス・キャロル」を使います。
先に挙げたnano-graphragのリポジトリにサンプルデータとして公開されているのを拝借します。
curl https://raw.githubusercontent.com/gusye1234/nano-graphrag/main/tests/mock_data.txt > ./book.txt
あとはREADMEのQueryセクションに書かれているコードを実行すればGraphRAGが実行できちゃいます。
以下が実行結果の例です。
Question: What are the top themes in this story?
この物語の主題はなんですか?
[Mode: hybrid Search]
Answer:
The top themes in A Christmas Carol revolve around Ebenezer Scrooge's transformation and the power of Christmas. The story emphasizes the importance of compassion, empathy, and generosity, contrasting Scrooge's initial miserliness and isolation with the warmth and joy of those around him who embrace the Christmas spirit. The narrative explores the consequences of selfishness and the potential for redemption, showcasing how Scrooge's interactions with the Ghosts of Christmas Past, Present, and Yet to Come lead him to confront his past mistakes and embrace a more charitable and compassionate life. Family and social responsibility are also prominent themes, highlighted by the Cratchit family's struggles and Scrooge's evolving relationship with them. The stark contrast between Scrooge's initial coldheartedness and his eventual transformation is a major driver of the narrative.
『クリスマス・キャロル』の中心テーマは、エベネザー・スクルージの変化とクリスマスの持つ力についてです。
この物語は思いやり、共感、寛大さの大切さを謳っており、最初のスクルージのけちな性格と孤独と、クリスマスの精神を受け入れている周囲の人々の温かさや喜びが対比的に描写されています。
スクルージは過去、現在、そして未来のクリスマスの幽霊との出会いを通じて、自分の過ちと向き合い、より思いやりと寛大さに満ちた生き方を受け入れていく様子が描かれ、利己主義がもたらす結末と贖罪の可能性を探っています。
家族や社会的責任もまた重要なテーマであり、クラチット家の苦労とスクルージの彼らとの関係の変化によって浮き彫りにされています。
初めは冬の冷気のように人の心を凍らせる冷酷なスクルージが、最後にはクリスマスの炎のように人を温める慈愛の心に目覚める劇的な転換こそ、この物語の魂を形作る核心となっています。
いくつかのブログ記事を拝見しましたが、クリスマス・キャロルの主題は「真の幸福はお金ではなく愛にある」や「人は必ず変われる」、「思いやりの心を持とう」という認識のようです[1][2][3]。
人間が汲み取った解釈に割と近しい回答ができています。
もちろん「物語の主題」は直接は小説中には書かれていません。
それでもそれなりに的を射ていることからGraphRAGの凄さが感じてもらえたかと思います。
コードに戻って解説すると、
async def initialize_rag():
rag = LightRAG(
working_dir="your/path",
embedding_func=openai_embed,
llm_model_func=gpt_4o_mini_complete
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
の部分でRAGの定義を行なっています。
ここで埋め込みモデルやLLMとしてどのモデルを使うかを定義しています。
# Insert text
rag.insert("Your text")
の部分では、GraphRAGのインデックスを作成しています。
使用するデータセットを渡すことで、埋め込み表現に変換したり、知識グラフを構築したりしています。
rag.query(
"What are the top themes in this story?",
param=QueryParam(mode=mode)
)
でクエリとモードを指定してGraphRAGを実行しています。
LightRAGでは5のモードが選択できます。
- naive: 単純な類似検索
- global: グローバル検索
- local: ローカル検索
- hybrid: グローバル検索とローカル検索のハイブリッド
- mix: 知識グラフ+ベクトル検索
mixが一番高コストで高度な回答が可能なモードです。
LLMをGeminiに
OpenAI以外にもOpenAI-likeなAPIを使うこともできます。
今回は無料枠のあって手軽に試せるGemini 1.5 Flash-8Bを利用します[4]。
Geminiはエンドポイントなどを少し変更することでOpenAI APIと互換性のあるリクエストが可能です[5]。
Googleの公式ドキュメントに従って、GeminiのAPIキーを取得します[6]。
LightRAGのREADMEの「Using Open AI-like APIs」を参考に以下のように書き換えます。
import os
from dotenv import load_dotenv
from lightrag import LightRAG, QueryParam
from lightrag.utils import EmbeddingFunc
from lightrag.llm import openai_embedding, openai_complete_if_cache
API_KEY = os.getenv("API_KEY")
BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai"
# LLMモデル関数の定義
async def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs) -> str:
return await openai_complete_if_cache(
"gemini-1.5-flash",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=API_KEY,
base_url=BASE_URL,
**kwargs
)
# 埋め込み関数の定義
async def embedding_func(texts: list[str]) -> np.ndarray:
return await openai_embedding(
texts,
model="text-embedding-004",
api_key=API_KEY,
base_url=BASE_URL,
)
# RAGオブジェクトの初期化
def initialize_rag(working_dir, language="English", graph_storage="NetworkXStorage"):
rag = LightRAG(
working_dir=working_dir,
llm_model_func=llm_model_func,
llm_model_max_async=1,
embedding_func=EmbeddingFunc(
embedding_dim=768,
max_token_size=4096,
func=embedding_func
),
graph_storage=graph_storage,
addon_params={
"language": language,
"entity_types": ["organization", "person", "geo", "event", "category", "product"],
},
)
それぞれモデル定義関数で、gemini-1.5-flash
やtext-embedding-004
など使用するモデルを指定します。
RAGオブジェクトの初期化関数で、埋め込みベクトルの次元数やトークン長、使用する知識グラフのストレージ先や使用言語、エンティティの種類などが定義できます。
それ以後はOpenAIのモデルの場合と同じです。
これでGeminiでもGraphRAGができちゃいます。
試しに、私の過去記事2つを入力して抽象的な質問をしてみます。
Q. この文章の主要なテーマはなんですか?
この文章の主要なテーマは、Apache Solr をバックエンドとした Retrieval Augmented Generation (RAG) システムの構築と、その過程で遭遇した課題と解決策です。 特に、ベクトル検索機能を活用した RAG システム構築における、Solr、Langchain、Embedding モデルの選定、そしてデータのインポートと管理に関する問題点が詳細に議論されています。
具体的には、著者であるsashimimochi氏が、SolrとLangchainを連携させ、ベクトル検索による情報検索とLLMを用いた回答生成を行うRAGシステムを構築しようとした経験が記述されています。その過程で、日本語Embeddingモデルの選定の重要性、DIH(Data Import Handler)を用いたベクトルデータのインデックス作成における課題、MySQLにおけるリスト型データの扱い、そしてLangchainとSolrを連携するためのライブラリである
eurelis-langchain-solr-vectorstore
の利用などが詳細に解説されています。 さらに、ローカル環境でのLLM実行におけるメモリ容量の制約と、量子化によるモデル軽量化の必要性についても言及されています。全体を通して、技術的な詳細な説明と、実践的な課題解決へのアプローチが提示されており、Solrを用いたベクトル検索とRAGシステム構築に関する実践的なガイドとしての役割も担っています。 また、もちっとカフェという同人サークルと、その活動内容についても触れられています。
Q. この文章のタイトルを考えてください
この文章をエンジニア向けブログ記事として捉え、いくつかのタイトル案を提案します。いずれも、記事の内容を的確に伝え、読者の興味を引くことを目指しています。
候補1(簡潔で具体的なタイトル):
LangChainとSolr連携によるRAG実装:ベクトル検索と課題解決
このタイトルは、記事の主要な技術(LangChain、Solr、RAG)を明確に示し、ベクトル検索というキーワードを含めることで、検索エンジニアの関心を惹きつけます。「課題解決」という言葉を添えることで、実践的な内容であることを示唆しています。候補2(メリットを強調したタイトル):
低スペック環境でも実現可能!LangChainとSolrで高性能RAGシステムを構築
このタイトルは、「低スペック環境」という制約を克服した点を強調することで、読者の共感を呼びます。「高性能」という言葉を添えることで、記事で紹介される技術の優位性をアピールしています。候補3(問題解決に焦点を当てたタイトル):
Solrベクトル検索の落とし穴と克服策:LangChainとDIHの活用方法
このタイトルは、Solrベクトル検索における潜在的な問題(落とし穴)に焦点を当てることで、読者の問題意識に訴えかけます。LangChainとDIHの活用方法を示唆することで、具体的な解決策を提供することを示しています。候補4(より専門性の高いタイトル):
Solrを用いたRetrieval Augmented Generation(RAG)システム構築における実践的ガイド
このタイトルは、専門用語を積極的に使用することで、より専門性の高い読者層に訴えかけます。「実践的ガイド」という言葉を添えることで、具体的な手順やノウハウが提供されることを示しています。どのタイトルが最適かは、ターゲットとする読者層やブログ全体のトーンによって異なります。 上記を参考に、さらに最適なタイトルを検討してみてください。
個人的にはなかなかいい線いっていると思います。
以上は汎用的な実装例ですが、google-genaiを使った実装例も用意されていました。
ご都合に合わせて使い分けてください。
グラフストレージをNeo4jに
続いてグラフストレージを外出ししましょう。
LightRAGでは、Neo4jやPostgreSQL、MongoDBなど、様々なデータベースと連携が可能です。
今回はその中でも個人レベルで無料枠があって使い勝手がよさそうなNeo4jをグラフストレージとして利用します。
参考までに、メジャーな各種クラウドサービスにおけるグラフDBとの比較してみました。
リソースの制約こそあるものの、期間を気にせず無料枠を利用できるのがNeo4jの強みです。
サービス | 特徴・費用 | 利点 | 欠点 | 無料枠の有無 |
---|---|---|---|---|
Spanner Graph (GCP) | 高いスケーラビリティとグローバル分散を実現するリレーショナルデータベース。グラフ用途としても利用可能[7]。費用はノード単位の従量課金制で、グローバル展開や高負荷時にコストが上昇する可能性がある。 | 強い一貫性、フルマネージド、グローバル分散、耐障害性に優れる。 | 専用グラフDBとしての機能は限定的で、学習曲線がやや高い点や、利用量次第で費用が急増する可能性がある。 | 新規ユーザー向けに無料トライアルインスタンス(90日間、10GBストレージ)が提供されている。 |
Neptune (AWS) | Property GraphおよびRDFデータモデルに対応する専用のグラフデータベース。費用はインスタンス稼働時間、ストレージ、I/O操作に基づく従量課金制となる。 | AWSエコシステムとの統合が容易。フルマネージドのため運用管理負荷が低く、高いパフォーマンスが期待できる。 | AWS依存(ベンダーロックイン)のリスクがあり、データ量やI/Oが多い場合は費用が急増する可能性がある。 | 新規ユーザー向けに30日間の無料トライアルがあり、750時間分のT3.mediumまたはT4g.mediumインスタンス、1GBストレージ、1,000万I/Oリクエストが提供される[8]。 |
Cosmos DB (Azure) | ドキュメント、キー・バリュー、グラフ(Gremlin API)など複数のデータモデルに対応するグローバル分散型データベース。料金はプロビジョニング済みのRU/sに基づく従量課金制で、利用状況に応じて変動する[9]。 | シームレスなスケーリング、マルチAPI対応、グローバル分散、高速なレスポンスを実現できる。 | 複雑な料金体系のため、リソース設計や費用見積もりが難しく、設定次第で予想以上にコストが発生する可能性がある。 | 無料レベルがあり、新規ユーザーの場合はアカウントあたり1000 RU/sと25GBのストレージが無料(Azure無料アカウントの初年度割引も併用可能)。 |
Neo4j | 専用のグラフデータベースとして、ネイティブなグラフストレージとCypherクエリ言語を採用している。ローカルにセルフホストするコミュニティ版は無償で利用可能。エンタープライズ版やクラウドのNeo4j Auraなどはライセンス費用が発生する。 | 優れたグラフ処理性能、直感的なクエリ言語、豊富なツールやコミュニティサポートにより、複雑な関係性の解析が容易。 | 自己ホスティングの場合は運用管理の負荷が高い。エンタープライズ版やマネージドサービス利用時はコストが高額になる傾向があり、スケーリング面で制限がある場合もある。 | コミュニティ版は無償で利用可能。また、Neo4j AuraDB Freeという無料クラウド版も提供されている(1インスタンスに制限、機能に制約あり)。 |
実装としても容易で、LightRAGの初期化関数にgraph_storage="Neo4JStorage"
と指定するだけです[10]。
from neo4j import GraphDatabase
graph_storage = "Neo4JStorage" if os.getenv("NEO4J_URI") else "NetworkXStorage"
if graph_storage == "Neo4JStorage":
neo4j_uri = os.getenv('NEO4J_URI')
username = os.getenv('NEO4J_USERNAME')
password = os.getenv('NEO4J_PASSWORD')
with GraphDatabase.driver(neo4j_uri, auth=(username, password)) as driver:
driver.verify_connectivity()
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=768,
max_token_size=4096,
func=embedding_func
),
graph_storage=graph_storage,
)
先述の通り、Neo4jはコミュニティ版が利用できます。
Dockerイメージも提供されているので、手軽に構築できます[11]。
例えば、以下のように定義します。
NEO4J_URI="bolt://neo4j:7687"
NEO4J_USERNAME="neo4j"
NEO4J_PASSWORDE="graphrag"
services:
app:
image: python:3.12
container_name: app
environment:
- NEO4J_URI=${NEO4J_URI}
- NEO4J_USERNAME=${NEO4J_USERNAME}
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
networks:
- graphrag
neo4j:
image: neo4j:5.26.0
container_name: neo4j
ports:
- "7474:7474"
- "7687:7687"
volumes:
- ./neo4j/data:/data
environment:
- NEO4J_AUTH=neo4j/${NEO4J_PASSWORD}
networks:
- graphrag
networks:
graphrag:
driver: bridge
適当なブラウザで http://localhost:7474 にアクセスするとNeo4jのWeb UIにアクセスできます。
左ペインのデータベースアイコンを押すと、データベースに入っているNodeやRelationshipの種類などが確認できます。
中央のクエリエディタではCypherのクエリが書けます。
例えば、以下のようなクエリを書くと、すべてのノードとリレーションを取得できます。
MATCH (n)-[r]-() RETURN *;
ノードやリレーションをクリックすると詳細が見れます。
単なるストレージだけでなく、可視化という点でも便利ですね。
ここではコミュニティ版を例に紹介しましたが、クラウド版についてもほぼ同様に利用できます。
具体的には、.env
ファイルに記載した接続先情報をクラウド版に書き換えるだけです。
接続先情報はNeo4j Auraでインスタンスを作成した際に得られるものを使用します。
ローカルと比べるとカラフルになっていて見やすくなっています。
有償版にすれば、サービス運用に必要な可用性も確保できそうです。
またLightRAGでは、グラフストレージ以外にもベクトルストアやKey-Valueストアに関しても外出しが可能なようです。
さまざまなバリエーションに応じた公式サンプルがあるので、参考にしてみてください。
おわりに
今回はLightRAGというライブラリを使って、GraphRAGを実行してみました。
グローバル検索を使うと文中にない全体を俯瞰的に捉えた質問に回答できることが実感できたのではないでしょうか?
まだエコシステムが成熟しきっていないため、扱いが難しいですが、使いこなせるようになるとこれまでのRAGではできなかった課題にアプローチできそうです。
みなさんもぜひ試してみてくださいね。
-
https://versatilebase.com/what-a-christmas-carol-can-tell-us/ ↩︎
-
https://developers.googleblog.com/en/gemini-15-flash-8b-is-now-generally-available-for-use/ ↩︎
-
わかりやすい日本語の解説記事 https://zenn.dev/cloud_ace/articles/aa679d44048555 ↩︎
-
サーバーレスではなくプロビジョンド版を使うと無料利用枠のインスタンスを利用することができる。 https://qiita.com/minorun365/items/a62dec701bef17cf7562 ↩︎
-
無料枠使用上の注意事項 https://acro-engineer.hatenablog.com/entry/2021/02/22/120000 ↩︎
-
PythonからNeo4jを操作するライブラリはこちら https://github.com/neo4j/neo4j-python-driver ↩︎
Discussion