ollamaを使ってGraphRAGを構築

はじめに

ローカル環境でナレッジグラフを作成し、GraphRAG環境を構築に着手して、既に1週間が経過。ようやくまともに動く状態になった。この投稿は、ローカル環境に構築したOllama、neo4jを使ったGraphRACについて、まとめたものである。

情報源

  1. Llama 3.2とNeo4jでローカルGraphRAG環境を構築 - この記事のコードを実行した。
  2. ChatOllama .invoke method giving ‘NoneType’ object is not iterable #28287 - 回答のリンクを辿っといくと、このように、回避策としてollamaを0.4.0より前のバージョンにせよと。

ナレッジグラフを構築

外部情報とするデータ(テキスト)

WikipediaダンプデータからRAG向けテキストデータ作成」でwikipediaから抽出したテキストは、データ量が多いので、次の6項目のみを抽出した。「wc -m」で抽出したテキストの文字数をカウントすると15,041だった。

  • R過程
  • S過程
  • ハッブル宇宙望遠鏡
  • ナンシーグレースローマン宇宙望遠鏡
  • B2FH論文
  • 原子核物理学

以下がテキストからナレッジグラフを作成するコードである。

必要なパッケージをimport

# パッケージをimport

import os
import pprint
from tempfile import NamedTemporaryFile

from langchain_community.document_loaders import TextLoader
from langchain_community.graphs import Neo4jGraph
from langchain_community.vectorstores import Neo4jVector
from langchain_experimental.graph_transformers import LLMGraphTransformer
from neo4j import GraphDatabase
import pandas as pd

neo4jに接続し、graphを作成

自分の環境では、JupyterLabを動作させているサーバとは別のサーバにneo4jを構築している。

# neo4j用の環境変数を設定

os.environ['NEO4J_URI'] = 'bolt://xxx.xxx.xxx.xxx:7687'
os.environ['NEO4J_USERNAME'] = 'neo4j'
os.environ['NEO4J_PASSWORD'] = 'password'
graph = Neo4jGraph()
graph.query('MATCH (n) DETACH DELETE n;')

テキストファイルを読み込み、chunkに分割

# Load documents from a text file
WIKI_TEXT = "./wiki_text2"
CHUNK_SIZE = 300
CHUNK_OVERLAP = 30

from langchain.text_splitter import RecursiveCharacterTextSplitter

loader = TextLoader(file_path=WIKI_TEXT, encoding = 'UTF-8')
docs = loader.load()
# Split documents into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP)
documents = text_splitter.split_documents(documents=docs)

上記の「wiki_text2」がwikipediaから抽出したテキストファイルである。

ollamaのgemma-2b-jpn-itをLLMに設定

ollamaも別サーバで起動している。

# ollama(gemma-2-2b-jpn)をLLMとして設定

from langchain_ollama.llms import OllamaLLM

llm = OllamaLLM(base_url = "http://xxx.xxx.xxx.xxx:11434", model="schroneko/gemma-2-2b-jpn-it", temperature=0)

テキストからナレッジグラフを作成

llm_transformer = LLMGraphTransformer(llm=llm)

# ここでLLMでgraph化
graph_documents = llm_transformer.convert_to_graph_documents(documents)
graph.add_graph_documents(
    graph_documents,
    baseEntityLabel=True,
    include_source=True
)

pprint.pprint(graph.query("MATCH (s)-[r:!MENTIONS]->(t) RETURN s,r,t LIMIT 50"))

自分の環境では、374秒(約6分)掛かった。

embeddingを設定し、vector indexを作成

from langchain_ollama import OllamaEmbeddings

# ollama embeddings
embeddings = OllamaEmbeddings(
    model="mxbai-embed-large",
    base_url = "http://192.168.11.4:11434",
)

from langchain.vectorstores import Neo4jVector

vector_index = Neo4jVector.from_existing_graph(
    embeddings,
    search_type="hybrid",
    node_label="Document",
    text_node_properties=["text"],
    embedding_node_property="embedding"
)

フルテキストインデックスを作成

# Connect to the Neo4j, then create a full-text index
driver = GraphDatabase.driver(
    uri=os.environ["NEO4J_URI"],
    auth=(os.environ["NEO4J_USERNAME"], os.environ["NEO4J_PASSWORD"])
)

def create_fulltext_index(tx):
    query = '''
    CREATE FULLTEXT INDEX `fulltext_entity_id` 
    FOR (n:__Entity__) 
    ON EACH [n.id];
    '''
    tx.run(query)

try:
    with driver.session() as session:
        session.execute_write(create_fulltext_index)
        print("Fulltext index created successfully.")
except Exception as e:
    print(f"Failed to create index: {e}")

driver.close()

フルテキストインデックスを作成する際に、既にindexがあるとエラーメッセージが出た。ブラウザからneo4jに接続して、cypherでindexをdropした後、再度実行した。

作成後、ブラウザからneo4jに接続し、以下によってグラフの構造をダウンロードした。

MATCH p=()-[]-() RETURN p

graph

GraphRAGを構築

情報源1.のコードを参考にして、ここまでに構築したナレッジグラフを外部情報としてRAGシステムを構築した。

エラーへの対処

質問を渡してinvokeするところで、次のようなエラーが発生した。対処した順番に以下に整理した。

llm.with_structured_output(dict_schema)で、NotImplementedError

「llm = OllamaLLM(・・・)」を「llm = ChatOllama(・・・)」に変更した。

ResponseError: schroneko/gemma-2-2b-jpn-it does not support tools

このエラーは、質問を渡してchain.invoke()するところで発生した。

「llm = ChatOllama(・・model="schroneko/gemma-2-2b-jpn-it”・・)」を「llm = OllamaLLM(・・model ="llma3.3”・・)に変更した。

TypeError: ‘NoneType’ object is not iterable

このエラーも、質問を渡してchain.invoke()するところで発生した。

ネットで検索すると、情報源2.の記事を見つけた。その記事には、0.4.0未満のバージョンのollamaを使え、と書いてあった。自分が使っていたollamaのバージョンは、0.4.3であった。ダウングレードするより先に、最新版(0.4.6)した方が良いと考え、0.4.6にしたが、エラーは回避できなかった。そこで、0.3.3にしたが、やはりエラーは回避できなかった。

改めて、情報源1.を見ると、pythonのバージョンが3.11となっていた。自分の環境のpythonのバージョンは、3.10.12だった。半分ダメもとで、それまで使っていたJupyterLabコンテナのpythonのバージョンを3.11に変更した。それについては、昨日のこの投稿を参照のこと。

変更後のpythonのバージョンは、次の通り。

# python --version
Python 3.11.10

ollamaをLLMとして設定

上記のエラー対応で述べきた通り、ようやく情報源1.のコードを正常に実行できた。色々と試した箇所のコードは次の通り。他の部分は、情報源1.のコードと同じ。

# ollama(gemma-2-2b-jpn)をLLMとして設定

from langchain_ollama.llms import OllamaLLM
from langchain_ollama import ChatOllama

#llm = OllamaLLM(base_url = "http://192.168.11.4:11434", model="schroneko/gemma-2-2b-jpn-it", temperature=0, format="json")
#llm = ChatOllama(base_url = "http://192.168.11.4:11434", model="schroneko/gemma-2-2b-jpn-it", temperature=0, format="json")
#llm = OllamaLLM(base_url = "http://192.168.11.4:11434", model="llama3.2", temperature=0, format="json")
llm = ChatOllama(base_url = "http://192.168.11.4:11434", model="llama3.2", temperature=0, format="json")

実行結果

print(chain.invoke("R過程について教えてください"))
{ "R過程" : "中性子星の衝突などの爆発的な現象によって起こる、元素合成における中性子を多くもつ鉄より重い元素のほぼ半分を合成する過程。迅速かつ連続的に中性子をニッケル56のような核種に取り込むことで起きる。" } 
print(chain.invoke("ナンシー・グレース・ローマン宇宙望遠鏡"))
{"ナンシー・グレース・ローマン宇宙望遠鏡"   : "2020年代半ばに打ち上げを目指し、日本を含む国際協力で進められているアメリカ航空宇宙局 (NASA) の広視野赤外線宇宙望遠鏡計画。主鏡の口径は2.4m、視野はハッブル宇宙望遠鏡よりも100倍広く、焦点距離は短くなる。太陽系外の惑星の撮影や、ダークエネルギーの存在確認に役立てることができると考えられる。}"}
print(chain.invoke("ナンシー・グレース・ローマン宇宙望遠鏡について教えて"))
{"ナンシー・グレース・ローマン宇宙望遠鏡"  		 	 	:"ナンシー・グレース・ローマン宇宙望遠鏡は、2020年代半ばの打ち上げを目指し、日本を含む国際協力で進められているアメリカ航空宇宙局 (NASA) の広視野赤外線宇宙望遠鏡計画。正式名称は「ナンシー・グレース・ローマン宇宙望遠鏡(Nancy Grace Roman Space Telescope)」です。"}
print(chain.invoke("B2FH論文"))
{"B2FH論文" : "元素の起源に関する記念碑的な論文。著者はマーガレット・バービッジ、ジェフリー・バービッジ、ウィリアム・ファウラー、フレッド・ホイルの4名で、1955年から1956年に執筆され、1957年にアメリカ物理学会の査読付き学術誌" }
print(chain.invoke("B2FH論文について教えてください"))
{
  "B2FH論文" : {
    "題名" : "Synthesis of the Elements in Stars",
    "執筆期間" : 1955    
    , "執筆場所" : "ケンブリッジ大学とカリフォルニア工科大学",
    "査読誌" : "Reviews of Modern Physics"
  }
}

まとめ

外部情報をテキストを準備してから1週間以上掛かったが、ようやくGraphRAGを構築することができた。

グラフ作成にそれなりに時間が掛かるので、大規模な外部データを扱う場合には問題ありそうだ。

上記の結果を見てわかるように、質問が文章になっていると、回答が少し乱れるようだ。この部分改善点である。