1 평가: Neo4j GraphRAG vs 메타데이터 기반 GraphRAG
1.1 두 방식 요약
| 항목 | 메타데이터 기반 | Neo4j GraphRAG |
|---|---|---|
| 핵심 | 벡터 스토어 메타데이터 = 엣지 | LLM 추출 → 명시적 KG → Neo4j |
| 그래프 DB | 불필요 | Neo4j 필수 |
| KG 구축 비용 | 없음 | LLM 호출 비용 발생 |
| 관계 표현력 | 단순 (동일 값 연결) | 풍부 (타입, 속성, 방향) |
| 쿼리 방식 | BFS 탐색 | Cypher |
| 전체 그래프 분석 | 불가 | GDS (PageRank, Louvain 등) |
| 구축 시간 | 수 분 | 수 시간 ~ 수 일 |
1.2 정량적 비교 실험
동일한 테스트 세트로 두 방식을 비교한다.
from langchain_graph_retriever import GraphRetriever
from langchain_neo4j import Neo4jGraph, GraphCypherQAChain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
# 공통 설정
llm = ChatOpenAI(model="gpt-4o", temperature=0)
ANSWER_PROMPT = PromptTemplate.from_template("""
다음 정보를 바탕으로 질문에 답하세요. 정보에 없는 내용은 "모름"으로 답하세요.
질문: {question}
정보: {context}
답변:
""")
# 테스트 케이스
test_cases = [
# 1-hop (단순 조회)
{
"question": "Tesla는 어디에 본사가 있는가?",
"ground_truth": "Austin, Texas",
"type": "1-hop",
},
# 2-hop (관계 연결)
{
"question": "일론 머스크가 설립한 회사들이 위치한 도시는?",
"ground_truth": "Austin (Tesla), Hawthorne (SpaceX)",
"type": "2-hop",
},
# 3-hop (복잡한 추론)
{
"question": "Tesla를 설립한 인물이 공동 창업한 다른 회사는?",
"ground_truth": "PayPal (공동 창업: Peter Thiel과)",
"type": "3-hop",
},
# 집계 (전체 패턴)
{
"question": "AI 업계에서 가장 많은 회사를 설립한 인물은?",
"ground_truth": "Elon Musk",
"type": "aggregation",
},
]1.2.1 메타데이터 기반 평가
from langchain_graph_retriever import GraphRetriever
from graph_retriever.strategies import Eager
metadata_retriever = GraphRetriever(
store=metadata_vector_store, # 사전 구축된 메타데이터 벡터 스토어
edges=[("origin", "origin"), ("category", "category")],
strategy=Eager(select_k=10, start_k=3, max_depth=2),
)
def metadata_rag_answer(question: str) -> str:
docs = metadata_retriever.invoke(question)
context = "\n".join(doc.page_content for doc in docs)
return llm.invoke(
ANSWER_PROMPT.format(question=question, context=context)
).content
metadata_answers = {
case["question"]: metadata_rag_answer(case["question"])
for case in test_cases
}1.2.2 Neo4j GraphRAG 평가
# 방법 1: Text2Cypher
graph = Neo4jGraph(url="bolt://localhost:7687",
username="neo4j", password="password")
cypher_chain = GraphCypherQAChain.from_llm(
llm=llm, graph=graph, verbose=False, validate_cypher=True
)
def neo4j_cypher_answer(question: str) -> str:
try:
return cypher_chain.invoke(question)["result"]
except Exception as e:
return f"오류: {e}"
# 방법 2: Hybrid (벡터 + Cypher)
def neo4j_hybrid_answer(question: str) -> str:
vector_results = neo4j_vector_store.similarity_search(question, k=5)
vector_context = "\n".join(doc.page_content for doc in vector_results)
entity_ids = [doc.metadata.get("id") for doc in vector_results if doc.metadata.get("id")]
graph_contexts = []
for eid in entity_ids[:2]:
rows = graph.query("""
MATCH (n {id: $id})-[r*1..2]-(m)
RETURN n.id, [rel IN r | type(rel)] AS rels, m.id
LIMIT 10
""", params={"id": eid})
graph_contexts.extend([f"{r['n.id']} → {r['rels']} → {r['m.id']}" for r in rows])
context = f"문서:\n{vector_context}\n\n그래프:\n" + "\n".join(graph_contexts)
return llm.invoke(ANSWER_PROMPT.format(question=question, context=context)).content1.2.3 LLM-as-Judge 평가
JUDGE_PROMPT = PromptTemplate.from_template("""
질문에 대한 답변을 1~5점으로 평가하세요.
5: 정답과 완전히 일치
4: 대부분 정확, 사소한 누락
3: 부분적으로 정확
2: 관련 있지만 중요한 오류
1: 완전히 틀리거나 무관
질문: {question}
정답: {ground_truth}
답변: {answer}
점수만 출력 (숫자 1개):
""")
def judge(question, ground_truth, answer) -> int:
response = llm.invoke(
JUDGE_PROMPT.format(question=question, ground_truth=ground_truth, answer=answer)
)
try:
return int(response.content.strip())
except ValueError:
return 1
# 비교 실험
results = []
for case in test_cases:
q, gt = case["question"], case["ground_truth"]
m_ans = metadata_rag_answer(q)
c_ans = neo4j_cypher_answer(q)
h_ans = neo4j_hybrid_answer(q)
results.append({
"question": q,
"type": case["type"],
"metadata_score": judge(q, gt, m_ans),
"cypher_score": judge(q, gt, c_ans),
"hybrid_score": judge(q, gt, h_ans),
})
# 결과 출력
print(f"{'질문유형':<12} {'메타데이터':>10} {'Text2Cypher':>12} {'하이브리드':>10}")
print("-" * 50)
for r in results:
print(f"{r['type']:<12} {r['metadata_score']:>10} {r['cypher_score']:>12} {r['hybrid_score']:>10}")
# 평균
avg_meta = sum(r['metadata_score'] for r in results) / len(results)
avg_cypher = sum(r['cypher_score'] for r in results) / len(results)
avg_hybrid = sum(r['hybrid_score'] for r in results) / len(results)
print("-" * 50)
print(f"{'평균':<12} {avg_meta:>10.2f} {avg_cypher:>12.2f} {avg_hybrid:>10.2f}")1.3 예상 결과 패턴
질문유형 메타데이터 Text2Cypher 하이브리드
─────────────────────────────────────────────
1-hop 4.2 4.8 4.5
2-hop 3.1 4.5 4.6
3-hop 2.0 4.2 4.4
aggregation 1.5 4.7 4.3
─────────────────────────────────────────────
평균 2.7 4.6 4.5
해석: - 1-hop: 메타데이터 방식도 준수. Neo4j 큰 이점 없음 - 2~3-hop: Neo4j 명확한 우위. 메타데이터 방식 급격히 떨어짐 - 집계: 메타데이터 방식 거의 불가. Cypher 집계 함수 강점 발휘
1.4 구축 비용 비교
cost_comparison = {
"메타데이터 기반": {
"구축 비용 (LLM)": "$0",
"구축 시간": "수 분",
"인프라": "기존 벡터 스토어만",
"운영 복잡도": "낮음",
"100만 문서 처리": "$0 (임베딩 비용만)",
},
"Neo4j GraphRAG": {
"구축 비용 (LLM)": "문서당 $0.01~0.05",
"구축 시간": "수 시간~수 일",
"인프라": "Neo4j + 벡터 스토어",
"운영 복잡도": "높음",
"100만 문서 처리": "$10,000~50,000",
},
}1.5 의사결정 가이드
데이터에 이미 메타데이터가 있는가?
YES → 메타데이터 기반으로 먼저 시도
NO → 텍스트에서 관계 추출 필요 → Neo4j 검토
질문 유형이 주로 무엇인가?
단순 검색/유사도 → Vector RAG 충분
1~2 hop 관계 → 메타데이터 기반 충분
3+ hop, 집계 → Neo4j GraphRAG 필요
예산이 있는가?
제한적 → 메타데이터 기반 (구축 비용 없음)
충분 → Neo4j GraphRAG (더 높은 품질)
운영 팀 역량?
Cypher 모름 → 메타데이터 기반
Neo4j 경험 → Neo4j GraphRAG
결론:
프로토타입/소규모 → 메타데이터 기반
프로덕션/복잡한 질문 → Neo4j GraphRAG
1.6 이 시리즈를 마치며
GraphRAG 스펙트럼:
Vector RAG 메타데이터 기반 GraphRAG Neo4j GraphRAG
(가장 단순) (중간) (가장 복잡)
│ │ │
비용 낮음 비용 낮음 비용 높음
구현 쉬움 구현 쉬움 구현 어려움
단순 질문에 강함 관계 있는 데이터에 강함 복잡한 추론에 강함
이 두 시리즈(메타데이터 기반 + Neo4j 기반)를 통해 GraphRAG의 전체 스펙트럼을 이해했다.
실무에서는 항상 단순한 것부터 시작하고, 충분한 이유가 생겼을 때 복잡한 방식으로 전환하는 것이 권장된다.