1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

グラフDBとは何か、Neo4jをPythonとLLMで使うチュートリアル

1
Posted at

はじめに

グラフDB、名前は聞いたことあるけど実際に触ったことない人向けに書きます。

この記事では:

  1. グラフDBって何なのかを理解する
  2. Neo4jをDockerで立ち上げる
  3. Pythonから繋いでデータを操作する
  4. LLMからNeo4jに自然言語で問い合わせる

ここまでを一本で通します。


1. グラフDBって何が違うの?

スクリーンショット 2026-07-12 003509.png

まず普通のRDB(MySQLとかPostgres)と何が違うのか。

RDBの場合

ユーザーと友達関係を表現するとき、こうなる。

users テーブル
| id | name  |
|----|-------|
| 1  | Alice |
| 2  | Bob   |
| 3  | Carol |

friends テーブル
| user_id | friend_id |
|---------|-----------|
| 1       | 2         |
| 2       | 3         |

「Aliceの友達の友達は誰?」を取得するには JOIN を2回かける必要がある。さらに「3ホップ先の友達」となると JOIN が増えて、深くなるほど重くなる。

スクリーンショット 2026-07-12 003631.png

グラフDBの場合

グラフDBはデータをノード(点)エッジ(線) で表現する。

(Alice) -[友達]-> (Bob) -[友達]-> (Carol)

「3ホップ先の友達を全員出して」みたいなクエリが、グラフの構造をそのままトレースするだけなので速い。

向いてるユースケース

向いてる 向いてない
人間関係・SNSグラフ 単純な CRUD
知識グラフ・RAG 集計・分析系
推薦システム RDBで十分なもの
サプライチェーン -

LLMとの相性がいい理由は「知識グラフ」として使えるから。エンティティ同士の関係性をそのまま持てる。


2. Neo4jをDockerで立ち上げる

compose.yml を作る:

services:
  neo4j:
    image: neo4j:5
    ports:
      - "7474:7474"
      - "7687:7687"
    environment:
      - NEO4J_AUTH=neo4j/password
      - NEO4J_PLUGINS=["apoc"]
    volumes:
      - ./data:/data

NEO4J_PLUGINS=["apoc"] はLangChainがスキーマ取得に使うため必須。ないと接続時にエラーになる。

起動:

docker compose up -d

停止:

docker compose down

ブラウザで http://localhost:7474 にアクセスするとNeo4jのWebコンソールが開く。

  • ユーザー名: neo4j
  • パスワード: password

ログインできたらOK。


3. Pythonからつなぐ

プロジェクトセットアップ

mkdir neo4j-llm-tutorial
cd neo4j-llm-tutorial
uv init
uv add neo4j langchain-neo4j langchain-ollama

接続確認

connect.py:

from neo4j import GraphDatabase

URI = "bolt://localhost:7687"
AUTH = ("neo4j", "password")

driver = GraphDatabase.driver(URI, auth=AUTH)

with driver.session() as session:
    result = session.run("RETURN 'Hello, Neo4j!' AS message")
    print(result.single()["message"])

driver.close()
uv run connect.py
# Hello, Neo4j!

4. データを入れてみる

Neo4jのクエリ言語は Cypher という独自の言語を使う。SQLと雰囲気は似てる。

ノードを作る

from neo4j import GraphDatabase

URI = "bolt://localhost:7687"
AUTH = ("neo4j", "password")

driver = GraphDatabase.driver(URI, auth=AUTH)

def create_sample_data(tx):
    # ノード(人物)を作成
    tx.run("CREATE (a:Person {name: 'Alice', age: 30})")
    tx.run("CREATE (b:Person {name: 'Bob', age: 25})")
    tx.run("CREATE (c:Person {name: 'Carol', age: 28})")

    # 関係(エッジ)を作成
    tx.run("""
        MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
        CREATE (a)-[:FRIENDS_WITH]->(b)
    """)
    tx.run("""
        MATCH (b:Person {name: 'Bob'}), (c:Person {name: 'Carol'})
        CREATE (b)-[:FRIENDS_WITH]->(c)
    """)

with driver.session() as session:
    session.execute_write(create_sample_data)
    print("データ作成完了")

driver.close()
uv run seed.py

seed.py を複数回実行するとデータが重複する。やり直す場合はWebコンソールで MATCH (n) DETACH DELETE n を実行してからもう一度。

データを確認する

WebコンソールでCypherを直接打って確認できる。

MATCH (n) RETURN n

ノードと関係がグラフとして描画されるのが見える。これがグラフDBの直感的なとこ。

Cypherで問い合わせ

def find_friends_of_friends(tx, name):
    result = tx.run("""
        MATCH (p:Person {name: $name})-[:FRIENDS_WITH*2]->(fof)
        RETURN fof.name AS name
    """, name=name)
    return [record["name"] for record in result]

with driver.session() as session:
    result = session.execute_read(find_friends_of_friends, "Alice")
    print(f"Aliceの友達の友達: {result}")
    # Aliceの友達の友達: ['Carol']

[:FRIENDS_WITH*2]*2 が「2ホップ」を意味する。*1..5 と書けば1〜5ホップ以内を全部たどれる。これがグラフDBの強み。


5. LLMからNeo4jを使う

スクリーンショット 2026-07-12 003555.png

いよいよ本題。自然言語でNeo4jに問い合わせる。

APIキー不要でローカルで動く Ollama を使う。

Ollamaのセットアップ

Ollama公式サイト からインストールしてモデルを取得する。

ollama pull llama3.1:8b

llama3.2:3b などの軽量モデルでも動くが、回答の精度が落ちやすい。4GB以上のVRAMがあれば llama3.1:8b 推奨。

GraphCypherQAChainを使う

LangChainには GraphCypherQAChain というNeo4j用のチェーンが用意されている。自然言語をCypherに変換して実行してくれる。

llm_query.py:

from langchain_neo4j import Neo4jGraph, GraphCypherQAChain
from langchain_ollama import ChatOllama
from langchain_core.prompts import PromptTemplate

# Neo4jに接続
graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="password"
)

# グラフのスキーマを確認(LLMに渡すため)
print(graph.schema)

# 回答生成用のプロンプト(日本語で答えさせる)
CYPHER_QA_PROMPT = PromptTemplate(
    input_variables=["context", "question"],
    template="""以下のNeo4jクエリの結果をもとに、質問に日本語で答えてください。
結果が空でなければ必ず答えを述べてください。「わからない」とは言わないでください。

クエリ結果: {context}
質問: {question}
回答:"""
)

# LLM + Chainをセットアップ
llm = ChatOllama(model="llama3.1:8b", temperature=0)

chain = GraphCypherQAChain.from_llm(
    llm=llm,
    graph=graph,
    verbose=True,  # 生成されたCypherを表示
    allow_dangerous_requests=True,
    qa_prompt=CYPHER_QA_PROMPT,
)

# 自然言語で問い合わせ
questions = [
    "AliceはBobと友達ですか?",
    "Aliceの友達の友達は誰ですか?",
    "25歳の人は誰ですか?",
]

for q in questions:
    print(f"\n質問: {q}")
    result = chain.invoke({"query": q})
    print(f"回答: {result['result']}")
uv run llm_query.py

実行するとこんな感じの出力になる:

質問: 25歳の人は誰ですか?

> Entering new GraphCypherQAChain chain...
Generated Cypher:
MATCH (p:Person) WHERE p.age = 25 RETURN p.name
Full Context:
[{'p.name': 'Bob'}]
> Finished chain.

回答: Bobです。

verbose=True にしておくと、LLMがどんなCypherを生成したか見えるのがおもしろい。

動作の仕組み

ユーザーの自然言語
    ↓
LLM(グラフのスキーマ情報も渡す)
    ↓ Cypherクエリを生成
Neo4j
    ↓ クエリ実行結果
LLM(結果を自然言語に変換)
    ↓
回答

GraphCypherQAChain は内部でグラフのスキーマ(どんなノードと関係が存在するか)をLLMに渡している。これがあるからLLMが正しいCypherを生成できる。


まとめ

やったこと 使ったもの
Neo4j起動 Docker + APOC
Python接続・データ操作 neo4j ドライバ、Cypher
自然言語で問い合わせ LangChain GraphCypherQAChain + Ollama

次回はアダプターを自作する。今回使った GraphCypherQAChain の中身を自分で実装することで、グラフDBとLLMを繋ぐ仕組みを深く理解する。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?