[GraphDB] GraphRAG(Neo4j)
최근 여러 RAG 기법들을 활용하고 있습니다. 여러 방식으로 데이터를 파싱해보고 RAG 구조를 구현해보며 각 방식의 장점과 단점을 확인해보았습니다.
개요
최근 여러 RAG 기법들을 활용하고 있습니다. 여러 방식으로 데이터를 파싱해보고 RAG 구조를 구현해보며 각 방식의 장점과 단점을 확인해보았습니다. 하지만 데이터를 아무리 잘 다듬더라도 데이터의 다양성과 데이터베이스의 구조로 인해 한계가 있을 수 있다는 생각을 하게 되었고, 데이터의 활용성을 넓히기 위해 GraphDB를 사용해보고자 포스트를 참고하여 테스트해보았습니다.
GraphDB란
GraphDB는 그래프 데이터베이스의 한 종류로, 데이터를 그래프 구조로 저장하고 관리하는 데이터베이스 시스템입니다. 주요 특징과 개념을 간단히 설명해 드리겠습니다:
- 데이터 모델:
- 노드(Nodes): 개체를 나타냅니다.
- 엣지(Edges): 노드 간의 관계를 나타냅니다.
- 속성(Properties): 노드와 엣지에 추가 정보를 부여합니다.
- 쿼리 언어:
- 대부분의 GraphDB는
SPARQL
이나Cypher
같은 그래프 특화 쿼리 언어를 지원합니다.
- 대부분의 GraphDB는
- 장점:
- 복잡한 관계 모델링에 적합
- 연결된 데이터 탐색이 효율적
- 유연한 스키마
- 대표적인 GraphDB 시스템:
- Neo4j
- Amazon Neptune
- 성능:
- 관계 기반 쿼리에서 전통적인 관계형 데이터베이스보다 우수한 성능을 보입니다.
GraphDB는 복잡한 관계를 가진 데이터를 다루는 데 특히 유용하며, 데이터 간의 연결성을 중심으로 분석과 처리를 수행해야 하는 애플리케이션에 적합합니다.
Neo4j(GraphDB)
개요
Neo4j는 현재 Langchain과 통합되어 사용 가능합니다. LLM을 통해 Cyper 쿼리를 생성(1)하고 쿼리를 날려 결과를 가져와(2) 답변을 생성(3)하는 3가지 단계로 구현됩니다.

Neo4j 구축 테스트
Neo4j는 현재 Sandbox 활용이 가능하며 무료로 테스트 데이터 및 인스턴스를 구축하여 테스트할 수 있습니다.
여러 데이터베이스가 사전 구축되어있으며 이를 활용하여 테스트 해볼 수 있습니다.

테스트를 위해 Movies
데이터셋을 활용하였습니다.
GraphDB 연동 확인
# pip3 install neo4j-driver
# python3 example.py
from neo4j import GraphDatabase, basic_auth
driver = GraphDatabase.driver(
"bolt://44.203.223.83:7687",
auth=basic_auth("neo4j", "tooth-superintendent-capes"))
cypher_query = '''
MATCH (movie:Movie {title:$favorite})<-[:ACTED_IN]-(actor)-[:ACTED_IN]->(rec:Movie)
RETURN distinct rec.title as title LIMIT 20
'''
with driver.session(database="neo4j") as session:
results = session.read_transaction(
lambda tx: tx.run(cypher_query,
favorite="The Matrix").data())
for record in results:
print(record['title'])
driver.close()
Cloud Atlas
V for Vendetta
The Matrix Revolutions
The Matrix Reloaded
Something's Gotta Give
The Replacements
Johnny Mnemonic
The Devil's Advocate
Langchain 연동 확인
Langchain 연동을 위해선 Username
, Password
, Bolt URL
을 등록해야합니다.

import os
os.environ["NEO4J_URI"] = "<BOLT_ADDRESS>:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "<YOUR_NEO4J_PASSWORD>"
또한 성능평가를 위해 gemini-1.5-flash 모델을 활용하였습니다.
사용하시는 LLM으로 바꿔 사용할 수 있습니다.
llm_sonnet = ChatBedrock(model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",)
llm_gemini = ChatGoogleGenerativeAI(model="gemini-1.5-flash-latest")
GraphCypherQAChain 생성하기
GraphCypherQAChain
을 LCEL로 생성하기 위해선 아래 파라미터가 포함되어야 합니다.
from_llm
을 통해 구현했을 땐 기본적인 cyper_prompt
와 qa_prompt
를 사용할 수 있지만 LCEL로 구현할 경우 해당 프롬프트를 필수적으로 전달해야합니다.
현 문서에서는 테스트를 위해 기본 프롬프트를 가져와 작성하였고 해당 프롬프트는 아래 링크를 통해 확인할 수 있습니다.
1.cypher_generation_chain
유저 input을 기반으로 LLM이 cypher를 생성하여, graph store에 질의를 입력하는 chain argument입니다.
여기에 전달해야하는 구조는 LLMChain을 선언하여 저장해야하며 LCEL 문법은 현재 지원되지 않습니다.
또한 해당 템플릿에서는 Cypher를 생성할 수 있도록 스키마, 사용자 지문 전달이 필요합니다.
CYPHER_GENERATION_TEMPLATE = """Task:Generate Cypher statement to query a graph database.
Instructions:
Use only the provided relationship types and properties in the schema.
Do not use any other relationship types or properties that are not provided.
Schema:
{schema}
Note: Do not include any explanations or apologies in your responses.
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
Do not include any text except the generated Cypher statement.
The question is:
{question}"""
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question"], template=CYPHER_GENERATION_TEMPLATE
)
cypher_generation_chain = LLMChain(llm=llm_gemini, prompt=CYPHER_GENERATION_PROMPT)
2.qa_chain
retrieval 결과를 기반으로 유저에게 전달되는 answer 를 생성하는 chain argument입니다
cypher_generation_chain
을 거쳐 생성된 cyper를 GraphDB로 쿼리로 사용해 데이터를 가져옵니다.
이때 가져온 데이터를 context로 사용하여 llm에게 전달하고, 최종 응답을 생성해야하므로 프롬프트내 context와 사용자 질문을 넣을 question이 필요합니다.
CYPHER_QA_TEMPLATE = """You are an assistant that helps to form nice and human understandable answers.
The information part contains the provided information that you must use to construct an answer.
The provided information is authoritative, you must never doubt it or try to use your internal knowledge to correct it.
Make the answer sound as a response to the question. Do not mention that you based the result on the given information.
Here is an example:
Question: Which managers own Neo4j stocks?
Context:[manager:CTL LLC, manager:JANE STREET GROUP LLC]
Helpful Answer: CTL LLC, JANE STREET GROUP LLC owns Neo4j stocks.
Follow this example when generating answers.
If the provided information is empty, say that you don't know the answer.
Information:
{context}
Question: {question}
Helpful Answer:"""
CYPHER_QA_PROMPT = PromptTemplate(
input_variables=["context", "question"], template=CYPHER_QA_TEMPLATE
)
qa_chain = LLMChain(llm=llm_gemini, prompt=CYPHER_QA_PROMPT)
3.graph
Retrieval 대상인 Graph Knowledge Base를 의미합니다.
여기에서 graph schema를 가져오고 cyper_qa_chain이 생성한 query가 입력되어 결과 값을 출력합니다.
from langchain_community.graphs import Neo4jGraph
graph = Neo4jGraph()
schema = graph.get_schema
구조
GraphCypherQAChain
의 총 구조는 다음과 같으며 Langchain에서 순차적으로 실행됩니다.

verbose값을 True로 설정하면 내부에서 동작하는 Cyper를 확인할 수 있습니다.
cypher_chain = GraphCypherQAChain(
cypher_generation_chain=cypher_generation_chain,
qa_chain=qa_chain,
graph=graph,
graph_schema=schema,
verbose=True,
)
GraphCypherQAChain 실행하기
모든 Chain을 선언하고 전달하였다면 해당 체인을 실행해볼 수 있습니다.
cypher_chain.invoke("2000년대 개봉한 영화들의 주연을 알려주세요")
실행 과정 다음과 같습니다
> Entering new GraphCypherQAChain chain...
Generated Cypher:
MATCH (a:Person)-[r:ACTED_IN]->(m:Movie)
WHERE m.released >= 2000 AND m.released < 2010
RETURN m.title AS movie, a.name AS actor, r.roles AS roles
ORDER BY m.released DESC
Full Context:
[{'movie': 'Ninja Assassin', 'actor': 'Rain', 'roles': ['Raizo']}, {'movie': 'Ninja Assassin', 'actor': 'Ben Miles', 'roles': ['Ryan Maslow']}, {'movie': 'Ninja Assassin', 'actor': 'Rick Yune', 'roles': ['Takeshi']}, {'movie': 'Ninja Assassin', 'actor': 'Naomie Harris', 'roles': ['Mika Coretti']}, {'movie': 'Frost/Nixon', 'actor': 'Sam Rockwell', 'roles': ['James Reston, Jr.']}, {'movie': 'Frost/Nixon', 'actor': 'Michael Sheen', 'roles': ['David Frost']}, {'movie': 'Frost/Nixon', 'actor': 'Frank Langella', 'roles': ['Richard Nixon']}, {'movie': 'Frost/Nixon', 'actor': 'Oliver Platt', 'roles': ['Bob Zelnick']}, {'movie': 'Frost/Nixon', 'actor': 'Kevin Bacon', 'roles': ['Jack Brennan']}, {'movie': 'Speed Racer', 'actor': 'Emile Hirsch', 'roles': ['Speed Racer']}]
> Finished chain.
결과는 다음과 같습니다.
{'query': '2000년대 개봉한 영화들의 주연을 알려주세요',
'result': '2000년대 개봉한 영화들의 주연 배우들은 다음과 같습니다:\\n\\n1. "Ninja Assassin"의 주연:\\n - Rain (라이조 역)\\n - Ben Miles (라이언 마슬로우 역)\\n - Rick Yune (타케시 역)\\n - Naomie Harris (미카 코레티 역)\\n\\n2. "Frost/Nixon"의 주연:\\n - Michael Sheen (데이비드 프로스트 역)\\n - Frank Langella (리처드 닉슨 역)\\n - Sam Rockwell (제임스 레스턴 주니어 역)\\n - Oliver Platt (밥 젤닉 역)\\n - Kevin Bacon (잭 브레넌 역)\\n\\n3. "Speed Racer"의 주연:\\n - Emile Hirsch (스피드 레이서 역)\\n\\n이 영화들은 모두 2000년대에 개봉된 작품들로, 각각 다양한 장르와 주제를 다루고 있습니다.'}
Reference
GraphRAG 참고 블로그
GraphCypherQAChain 문서
Neo4j 샌드박스 환경 구성
Langchain 파이프라인 데모
LCEL 문서
더 자세한 GraphRAG 구조를 확인할 수 있습니다.