Skip to content
Published on

GraphRAG 완전 가이드: 지식 그래프가 RAG의 한계를 어떻게 넘는가

Authors

기존 RAG의 한계: 어떤 질문에서 실패하는가

Vector DB 기반 RAG를 운영하다 보면 특정 유형의 질문에서 일관되게 실패하는 패턴을 발견합니다.

실패하는 질문들:

  • "이 분기 보고서들에서 전반적인 리스크 요인을 요약해줘"
  • "우리 제품 리뷰에서 반복적으로 등장하는 불만 패턴은?"
  • "CEO가 바뀐 이후 회사 전략의 변화를 설명해줘"
  • "A 제품과 B 제품의 공통된 결함 특성은?"

왜 실패할까요? 이 질문들의 공통점은 단일 청크로는 답할 수 없고, 전체 지식베이스를 횡단하는 이해가 필요하다는 것입니다.

일반 RAG의 작동 방식을 생각해보면:

  1. 질문을 임베딩
  2. 유사도로 가장 관련성 높은 청크 3-5개 검색
  3. 그 청크들로 답변 생성

"이 분기 보고서들의 전반적인 리스크 요인"을 물으면 RAG는 특정 보고서의 한 섹션을 가져올 뿐, 전체를 가로지르는 패턴을 볼 수 없습니다. 이게 근본적인 한계입니다.


GraphRAG란? (Microsoft Research, 2024)

Microsoft Research에서 2024년 발표한 논문 "From Local to Global: A GraphRAG Approach to Query-Focused Summarization" (Edge et al., 2024)에서 소개된 방법론입니다.

핵심 아이디어는 간단합니다: 단순히 텍스트를 청크로 나누는 대신, 문서에서 엔티티와 관계를 추출해 지식 그래프를 만들고, 그 위에서 검색한다.

일반 RAG:
문서들 → 청크 분할 → 임베딩 → 벡터 DB → 유사도 검색 → 관련 청크

GraphRAG:
문서들 → 엔티티 추출 → 지식 그래프 구성 → 커뮤니티 탐지 →
        → 계층적 요약 생성 → 다단계 검색 (Local + Global)

핵심 구조 이해하기

1. 엔티티와 관계 추출

GraphRAG는 LLM을 사용해 문서에서 엔티티(사람, 조직, 장소, 개념)와 그 관계를 추출합니다.

입력 텍스트:
"삼성전자는 2024년 갤럭시 S25를 출시하여 애플 아이폰 16과 직접 경쟁합니다.
갤럭시 S25는 퀄컴 스냅드래곤 8 Elite 칩을 탑재했습니다."

추출되는 엔티티:
- 삼성전자 (조직)
- 갤럭시 S25 (제품)
- 애플 (조직)
- 아이폰 16 (제품)
- 퀄컴 (조직)
- 스냅드래곤 8 Elite (제품/기술)

추출되는 관계:
- 삼성전자 --[출시]--> 갤럭시 S25
- 갤럭시 S25 --[경쟁]--> 아이폰 16
- 갤럭시 S25 --[탑재]--> 스냅드래곤 8 Elite
- 퀄컴 --[제조]--> 스냅드래곤 8 Elite

2. 커뮤니티 탐지 (Community Detection)

엔티티 그래프에서 Leiden 알고리즘 등을 사용해 서로 밀접하게 연결된 엔티티 그룹(커뮤니티)을 찾습니다.

예: 스마트폰 시장 커뮤니티, 반도체 커뮤니티, 소프트웨어 생태계 커뮤니티

3. 계층적 요약

각 커뮤니티에 대해 여러 수준의 요약을 생성합니다:

Level 0 (최세밀): 개별 엔티티/관계 요약
Level 1: 소규모 커뮤니티 요약
Level 2: 중규모 커뮤니티 요약 (일반적으로 이걸 많이 사용)
Level 3 (최포괄): 전체 주제 요약

코드로 보는 GraphRAG

Microsoft의 공식 graphrag 라이브러리를 사용합니다.

# 설치
pip install graphrag

# 프로젝트 초기화
mkdir my-graphrag-project
graphrag init --root ./my-graphrag-project

초기화하면 settings.yaml이 생성됩니다. 핵심 설정:

# settings.yaml 주요 설정
llm:
  api_key: ${GRAPHRAG_API_KEY}
  type: openai_chat
  model: gpt-4o-mini  # 인덱싱용 (비용 절감 위해 mini 사용)
  model_supports_json: true

embeddings:
  llm:
    model: text-embedding-3-small  # 임베딩용

input:
  type: file
  file_type: text
  base_dir: "input"  # 문서를 여기에 넣기

chunks:
  size: 1200
  overlap: 100
# 인덱싱 (지식 그래프 구축)
# graphrag index --root ./my-graphrag-project
# 이 명령이 내부적으로 하는 일:
# 1. 문서 청킹
# 2. 엔티티 및 관계 추출 (LLM 호출)
# 3. 그래프 구성
# 4. 커뮤니티 탐지
# 5. 각 커뮤니티에 대한 요약 생성 (LLM 호출)

# Python API로 쿼리
import asyncio
from graphrag.query.api import local_search, global_search

# Local Search: 특정 엔티티/관계 중심 질문
async def search_local(query: str):
    result = await local_search(
        config_dir="./my-graphrag-project",
        data_dir="./my-graphrag-project/output",
        root_dir="./my-graphrag-project",
        community_level=2,
        response_type="multiple paragraphs",
        query=query,
    )
    return result.response

# Global Search: 전체 지식베이스 합성이 필요한 질문
async def search_global(query: str):
    result = await global_search(
        config_dir="./my-graphrag-project",
        data_dir="./my-graphrag-project/output",
        root_dir="./my-graphrag-project",
        community_level=2,
        response_type="multiple paragraphs",
        query=query,
    )
    return result.response

# 사용 예시
local_result = asyncio.run(search_local(
    "삼성전자와 TSMC의 관계는 어떻게 되나요?"
))

global_result = asyncio.run(search_global(
    "이 문서들에서 반도체 산업의 주요 트렌드는 무엇인가요?"
))

Local Search vs Global Search: 언제 무엇을 쓸까

GraphRAG의 두 검색 모드를 이해하는 게 핵심입니다.

쿼리 유형최적 모드예시
특정 엔티티에 대한 질문Local Search"김철수의 역할은?"
두 엔티티의 관계Local Search"삼성과 TSMC의 관계는?"
전체 트렌드 파악Global Search"이 문서들의 주요 주제는?"
전반적인 패턴 분석Global Search"업계 전체의 리스크 요인은?"
시간에 따른 변화Local/Global 모두 활용"지난 5년간 전략 변화는?"

Local Search 작동 방식:

  1. 질문에서 관련 엔티티 찾기
  2. 해당 엔티티와 연결된 텍스트 청크, 관계, 커뮤니티 요약 수집
  3. LLM으로 최종 답변 생성

Global Search 작동 방식:

  1. 모든 커뮤니티 요약을 여러 "청크"로 나눔
  2. 각 청크에 대해 부분 답변 생성 (Map phase)
  3. 부분 답변들을 합쳐 최종 답변 생성 (Reduce phase)

지식 그래프 직접 살펴보기

GraphRAG가 생성한 그래프를 시각화하면 통찰을 얻을 수 있습니다.

import pandas as pd
import networkx as nx

# GraphRAG 출력 파일에서 엔티티와 관계 로드
entities_df = pd.read_parquet("./output/entities.parquet")
relationships_df = pd.read_parquet("./output/relationships.parquet")

print(f"총 엔티티 수: {len(entities_df)}")
print(f"총 관계 수: {len(relationships_df)}")

# NetworkX 그래프 구성
G = nx.DiGraph()

for _, entity in entities_df.iterrows():
    G.add_node(entity["title"], type=entity["type"])

for _, rel in relationships_df.iterrows():
    G.add_edge(
        rel["source"],
        rel["target"],
        weight=rel["weight"],
        description=rel["description"]
    )

# 가장 연결이 많은 엔티티 (허브 찾기)
top_hubs = sorted(G.degree(), key=lambda x: x[1], reverse=True)[:10]
print("가장 중요한 엔티티 Top 10:")
for entity, degree in top_hubs:
    print(f"  {entity}: {degree}개 연결")

GraphRAG vs 일반 RAG: 성능 비교

Microsoft의 원논문 결과 (HotPotQA, MuSiQue 데이터셋):

글로벌 질문 (전체 요약 필요):
- 일반 RAG:    포괄성 40%, 다양성 57%
- GraphRAG:    포괄성 72%, 다양성 62%
  → 포괄성 80% 향상!

로컬 질문 (특정 사실):
- 일반 RAG:    정확도 ~65%
- GraphRAG:    정확도 ~70%
  → 약간 개선

속도:
- 일반 RAG:    0.5-2- GraphRAG:    3-10 (커뮤니티 요약 합산 때문)

결론: 전역 질문에서는 GraphRAG가 압도적으로 좋지만, 특정 사실 검색에서는 일반 RAG와 비슷하거나 느립니다.


비용 현실: 솔직한 계산

GraphRAG의 가장 큰 단점은 인덱싱 비용입니다.

# GraphRAG 인덱싱 비용 추정
# 가정: 1,000개 문서, 각 1,000 토큰

num_documents = 1000
tokens_per_doc = 1000
total_tokens = num_documents * tokens_per_doc  # 1M 토큰

# 인덱싱 단계별 LLM 호출 횟수 추정
entity_extraction_calls = total_tokens / 1200  # 청크당 1번
# 각 추출 호출: ~800 토큰 입력 + ~400 토큰 출력

community_summary_calls = 200  # 커뮤니티 수 × 레벨
# 각 요약 호출: ~2000 토큰 입력 + ~500 토큰 출력

# GPT-4o-mini 기준 비용 ($0.15 / 1M input, $0.60 / 1M output)
entity_input_cost = (entity_extraction_calls * 800 / 1_000_000) * 0.15
entity_output_cost = (entity_extraction_calls * 400 / 1_000_000) * 0.60
community_cost = (community_summary_calls * 2500 / 1_000_000) * 0.40

total_indexing_cost = entity_input_cost + entity_output_cost + community_cost
print(f"추정 인덱싱 비용: ${total_indexing_cost:.2f}")
# 1,000개 문서: 약 $1-5 (GPT-4o-mini 사용 시)
# 10,000개 문서: 약 $10-50
# 100,000개 문서: 약 $100-500

# 쿼리 비용 (Global Search)
# Global search는 커뮤니티 요약 전체를 LLM에 보내므로 쿼리당 비용이 높음
# 커뮤니티가 200개, 각 요약 500토큰이면 쿼리당 ~100K 토큰 처리
global_query_cost = (100_000 / 1_000_000) * 2.50  # GPT-4o 기준
print(f"Global search 쿼리당 비용: ${global_query_cost:.3f}")  # $0.25/쿼리

이 비용이 받아들일 만한지 판단하려면:

  • 초기 인덱싱: 문서가 자주 변경되지 않는다면 일회성 비용
  • 쿼리 비용: Global search는 RAG보다 10-100배 비쌀 수 있음

GraphRAG가 적합한 경우 vs 일반 RAG로 충분한 경우

GraphRAG가 가치 있는 경우:

  • 수백~수천 개 문서에 걸친 패턴/트렌드 분석
  • 재무 보고서, 특허, 법률 문서 분석
  • 지식베이스가 거의 변경되지 않는 경우 (인덱싱 비용 일회성)
  • "이 회사에 대해 뭘 알고 있어?"처럼 광범위한 질문이 많은 경우

일반 RAG가 충분한 경우:

  • 특정 사실 검색 ("이 계약서의 만료일은?")
  • 빠른 응답이 필요한 실시간 서비스
  • 문서가 자주 업데이트되는 경우 (재인덱싱 비용)
  • 소규모 지식베이스 (< 100개 문서)
  • 비용 예산이 타이트한 경우

LightRAG: GraphRAG의 더 실용적인 대안

Microsoft의 GraphRAG가 너무 복잡하거나 비싸다면 LightRAG를 고려해보세요.

# pip install lightrag-hku
from lightrag import LightRAG, QueryParam
from lightrag.llm import gpt_4o_mini_complete

rag = LightRAG(
    working_dir="./lightrag-storage",
    llm_model_func=gpt_4o_mini_complete,
)

# 문서 추가
with open("document.txt", "r") as f:
    rag.insert(f.read())

# 쿼리 (naive, local, global, hybrid 4가지 모드)
result = rag.query(
    "주요 트렌드는 무엇인가요?",
    param=QueryParam(mode="global")  # hybrid 모드도 효과적
)

LightRAG는 구현이 더 간단하고 비용도 저렴하며, 소규모-중간 규모 지식베이스에서 충분한 성능을 냅니다.


결론: GraphRAG는 도구이지 모든 것의 답이 아니다

GraphRAG는 강력하지만 모든 상황에 적합하지는 않습니다.

핵심 정리:

  • 전역 질문(global queries)에서 일반 RAG를 크게 능가
  • 인덱싱 비용이 높으므로 지식베이스가 안정적일 때 적합
  • 쿼리 비용도 일반 RAG보다 높음
  • 간단히 시작하려면 Microsoft의 graphrag 또는 LightRAG 라이브러리 활용

투자 대비 효과를 생각하세요. "문서에서 전체적인 패턴을 이해해야 한다"는 명확한 요구사항이 있을 때 GraphRAG를 도입하고, 특정 사실 검색이 주 용도라면 잘 튜닝된 일반 RAG가 더 경제적입니다.