😸

【GraphRAG】Langchain+Neo4j での実装メモ

2024/10/14に公開

忘れないようにメモ

Neo4j DBの作成方法

クラウド上に作る方法と、ローカルにインストールする方法があるようです。

クラウド上に作る場合、ひとつだけなら無料で試すことができます。

作り方は特に迷うことはありませんでした。

グラフ作成

今回はキングダムの人物関係をベクトルに変換しました。
引き伸ばしが酷くて、誰が誰だか覚えてません。(鄴攻略の途中で挫折しました・・・)

保存前にグラフ表示の部分は必須ではありませんが、DB保存前に納得のいく内容でグラフ化できているか確認はしておいたほうがいいでしょう。

.envのサンプル
OPENAI_API_KEY = "sk-**********************************"

NEO4J_URI=neo4j+s://**********.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=********************************
AURA_INSTANCEID=********
AURA_INSTANCENAME=Instance01
import dotenv
dotenv.load_dotenv()

from langchain_community.graphs import Neo4jGraph
graph = Neo4jGraph()

from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")

llm_transformer = LLMGraphTransformer(llm=llm)

from langchain_core.documents import Document

text = """
『キングダム』は、古代中国の戦国時代を舞台にした物語で、多くの登場人物が複雑な関係性を築いています。主なキャラクターは、主人公の信(しん)と、後に秦の始皇帝となる嬴政(えいせい)です。信は戦争孤児で、天下の大将軍を目指して戦いに身を投じます。嬴政とは、国を治める王として信頼関係を築き、互いに助け合う仲です。

信の軍師であり友人でもある河了貂(かりょうてん)は、知恵で信を支える重要なキャラクターです。また、信のライバルである王賁(おうほん)と蒙恬(もうてん)は同世代の将軍であり、互いに刺激し合いながらも共に成長していきます。

一方で、敵対勢力としては、趙の名将李牧(りぼく)や楚の武将項燕(こうえん)などが登場し、彼らとの戦いが物語の大きな軸となります。戦場での対決や政治的な駆け引きを通じて、キャラクターたちは友情、敵意、忠誠心などを深めていきます。
"""

# テキストをDocument配列に変換(チャンクにまで分けなくて良い)
documents = [Document(page_content=text)]
graph_documents = llm_transformer.convert_to_graph_documents(documents)

print(f"Nodes:{graph_documents[0].nodes}")
print(f"Relationships:{graph_documents[0].relationships}")

# RelationshipとNodeは以下の形
# Relationship(
#     source=Node(id='Kingdom', type='Story', properties={}), 
#     target=Node(id='Ancient China', type='Location', properties={}), 
#     type='SETTING', properties={})
# )

# Node(id='Kingdom', type='Story', properties={})

# 保存前にグラフ表示
import networkx as nx
import matplotlib.pyplot as plt

G = nx.Graph()

edge_labels = {}

for doc in graph_documents:
    G.add_nodes_from([ (node.id, {"type": node.type}) for node in doc.nodes])
    G.add_edges_from([ (rel.source.id, rel.target.id, {"type": rel.type}) for rel in doc.relationships])

    # エッジラベルを設定
    for rel in doc.relationships:
        edge_labels[(rel.source.id, rel.target.id)] = rel.type

# グラフとエッジを描画(同時に描くことはできないみたい)
pos = nx.spring_layout(G)
nx.draw(G, pos=pos, with_labels=True, font_family='Hiragino Sans', node_color='lightblue', node_size=3000, font_size=12)
nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=edge_labels, font_family='Hiragino Sans', font_size=10)

plt.show()

# グラフデータベースに保存
graph.add_graph_documents(graph_documents)

###### 以下の方法はグラフデータベースに保存するデータを制限する方法 #####
# llm_transformer_props = LLMGraphTransformer(
#     llm=llm,
#     # allowed_nodes=["", "Country", "Organization"],
#     # allowed_relationships=["NATIONALITY", "LOCATED_IN", "WORKED_AT", "SPOUSE"],
#     # node_properties=["born_year"],
# )

# graph_documents_props = llm_transformer_props.convert_to_graph_documents(documents)
# graph.add_graph_documents(graph_documents_props)

グラフはこんな感じに描くことができました。

グラフ読み取り

import dotenv
dotenv.load_dotenv()

from langchain_community.graphs import Neo4jGraph 

from langchain.chains.graph_qa.cypher import GraphCypherQAChain

from langchain_openai import ChatOpenAI 

graph = Neo4jGraph() 

llm = ChatOpenAI(model="gpt-4o-mini") 

client = GraphCypherQAChain.from_llm(graph=graph, llm=llm, verbose=True,allow_dangerous_requests=True) 

client.invoke({'query': '信のライバルは?'})

結果

ちゃんと'王賁'と'蒙恬'を取り出せていました。

> Entering new GraphCypherQAChain chain...
Generated Cypher:
MATCH (p:Person {id: '信'})-[:ライバル]->(r:Person) RETURN r
Full Context:
[{'r': {'id': '王賁'}}, {'r': {'id': '蒙恬'}}]

ちょっといじわるして、味方を聞いてみました。グラフを見てもらうとわかるとおり、「味方」という単語は登場していないので、LLMが日本語を解釈する必要があります。
(ちなみに王賁、蒙恬、嬴政、河了貂が味方です)
client.invoke({'query': '信の味方は誰ですか?'})

> Entering new GraphCypherQAChain chain...
Generated Cypher:
MATCH (p:Person {id: '信'})-[:友人]->(friend) RETURN friend
Full Context:
[{'friend': {'id': '河了貂'}}]

LLMは「味方」という言葉を「友人」だけと捉えてしまいました。

Discussion