목차
- 문제와 접근
- GraphRAG 프로젝트 생성
- settings.yaml 모델 설정
- .env와 input 준비
- GraphRAG indexing
- gleaning 비교 실험
- artifact 로드
- entities / relationships 분석
- NetworkX 변환과 시각화
- GraphRAG query 실행
- run_query 래퍼
- local/global 비교
- 핵심 개념 총정리
- 자주 나오는 실수/주의사항
- 설정 변경의 효과를 산출물로 확인하기
1. 문제와 접근
GraphRAG CLI를 사용할 때는 명령 실행 성공과 생성된 인덱스의 품질을 따로 확인한다. 설정을 바꾸어 인덱싱하고 산출물을 읽는 흐름을 통해 추출 결과와 검색 방식의 차이를 살펴본다. 실험마다 입력, 설정, 캐시 사용 여부를 분리해 비교 가능한 조건을 남긴다.
1
2
3
4
5
6
7
8
9
10
11
| graphrag init
↓
settings.yaml 수정
↓
input 문서 복사
↓
graphrag index
↓
parquet artifact 분석
↓
graphrag query local/global/drift
|
2. GraphRAG 프로젝트 생성
1
2
3
4
5
6
7
8
9
10
11
| import os
import json
import time
import shutil
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
PROJECT_ROOT = Path("ragtest_0612")
PROJECT_ROOT.mkdir(exist_ok=True)
|
1
| !graphrag init --root {PROJECT_ROOT} --force
|
3. settings.yaml 모델 설정
기본 chat model과 embedding model을 수업에서 쓰는 모델로 바꾼다.
1
2
3
4
5
6
7
8
9
10
11
| import yaml
settings_path = PROJECT_ROOT / "settings.yaml"
s = yaml.safe_load(settings_path.read_text())
s["models"]["default_chat_model"]["model"] = "gpt-4o-mini"
s["models"]["default_embedding_model"]["model"] = "text-embedding-3-small"
settings_path.write_text(
yaml.safe_dump(s, allow_unicode=True, sort_keys=False)
)
|
GraphRAG는 .env의 GRAPHRAG_API_KEY를 사용한다.
1
2
3
| env_path = PROJECT_ROOT / ".env"
api_key = os.getenv("OPENAI_API_KEY", "")
env_path.write_text(f"GRAPHRAG_API_KEY={api_key}\n")
|
1
2
3
4
5
6
| input_dir = PROJECT_ROOT / "input"
input_dir.mkdir(exist_ok=True)
src = Path("data/council.txt")
dst = input_dir / "council.txt"
shutil.copy(src, dst)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def prepare_input(root: Path, source_files: list):
input_dir = root / "input"
input_dir.mkdir(exist_ok=True)
copied = []
total = 0
for src in source_files:
src = Path(src)
dst = input_dir / src.name
shutil.copy(src, dst)
copied.append(src.name)
total += dst.stat().st_size
return {"copied": copied, "total_bytes": total}
|
5. GraphRAG indexing
1
2
3
| t0 = time.time()
!graphrag index --root {PROJECT_ROOT}
print(f"{(time.time() - t0) / 60} min")
|
인덱싱 단계
1
2
3
4
5
6
7
| 1. chunking
2. entity / relation 추출
3. graph merge, entity 정규화
4. community detection
5. community report 생성
6. embedding
7. parquet artifact 저장
|
6. gleaning 비교 실험
추가 추출 횟수를 비교할 때는 엔티티·관계 개수와 함께 실제로 새로 얻은 근거를 확인한다. 개수 증가만으로 품질 향상을 판단하지 않고, 중복이나 잘못된 관계도 늘었는지 살펴본다. 처리 시간은 캐시를 사용한 실행과 분리해서 비교한다.
max_gleanings는 한 chunk에서 추가 추출을 몇 번 더 시도할지 정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| def setup_project_with_gleaning(suffix, n_gleanings):
root = Path(f"ragtest_0612_g{suffix}")
root.mkdir(exist_ok=True)
if not (root / "settings.yaml").exists():
os.system(f"graphrag init --root {root} --force")
sp = root / "settings.yaml"
s = yaml.safe_load(sp.read_text())
s["models"]["default_chat_model"]["model"] = "gpt-4o-mini"
s["models"]["default_embedding_model"]["model"] = "text-embedding-3-small"
s.setdefault("extract_graph", {})["max_gleanings"] = n_gleanings
sp.write_text(yaml.safe_dump(s, allow_unicode=True, sort_keys=False))
(root / ".env").write_text((PROJECT_ROOT / ".env").read_text())
(root / "input").mkdir(exist_ok=True)
for txt in input_dir.glob("*.txt"):
shutil.copy(txt, root / "input" / txt.name)
return root
|
1
2
3
4
5
| def compare_gleaning(gleaning_runs):
print(f"{'gleaning':<10}{'entities':>10}{'relations':>12}{'time(s)':>12}")
for r in gleaning_runs:
t = f"{r['elapsed_s']:.1f}" if r["elapsed_s"] else "(cached)"
print(f"{r['gleaning']:<10}{r['entities']:>10}{r['relationships']:>12}{t:>12}")
|
7. artifact 로드
GraphRAG 결과는 output/*.parquet로 저장된다.
1
2
3
4
5
6
7
8
9
10
11
12
| import pandas as pd
def load_artifacts(root):
output_dir = root / "output"
out = {}
for pq in sorted(output_dir.glob("*.parquet")):
out[pq.stem] = pd.read_parquet(pq)
return out
arts = load_artifacts(PROJECT_ROOT)
|
1
2
| for name, df in arts.items():
print(f"{name} shape = {df.shape}, cols = {list(df.columns)}")
|
8. entities / relationships 분석
degree가 높은 entity를 확인한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| def top_entities(arts, k=5):
df = arts["entities"].copy()
sort_col = "degree"
df = df.sort_values(sort_col, ascending=False).head(k)
out = []
for _, row in df.iterrows():
out.append({
"title": row.get("title", ""),
"type": row.get("type", ""),
sort_col: int(row.get(sort_col, 0)),
"description_short": str(row.get("description", ""))[:100],
})
return out
|
1
2
3
| reports_df = arts["community_reports"]
print(f"shape : {reports_df.shape}")
print(f"columns : {list(reports_df.columns)}")
|
9. NetworkX 변환과 시각화
parquet artifact를 NetworkX 그래프로 바꾼다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| import networkx as nx
def to_networkx(arts) -> nx.DiGraph:
G = nx.DiGraph()
for _, row in arts["entities"].iterrows():
G.add_node(
row["title"],
type=row.get("type", "unknown"),
degree=int(row["degree"]),
description=str(row.get("description")),
)
for _, row in arts["relationships"].iterrows():
if row["source"] not in G:
G.add_node(row["source"], type="unknown")
if row["target"] not in G:
G.add_node(row["target"], type="unknown")
G.add_edge(
row["source"],
row["target"],
description=str(row.get("description", "")),
weight=float(row["weight"]),
)
return G
|
1
2
| g_ms = to_networkx(arts)
g_ms.number_of_nodes(), g_ms.number_of_edges()
|
10. GraphRAG query 실행
CLI로 local/global query를 실행한다.
1
| !graphrag query --root {PROJECT_ROOT} --method local --query "콰빅스 연구소는 무엇이고, 모리카이 센과의 관계는?"
|
1
| !graphrag query --root {PROJECT_ROOT} --method global --query "이 코퍼스의 주요 주제는 무엇인가요?"
|
1
| !graphrag query --root {PROJECT_ROOT} --method local --query "모리카이 센의 멘토가 쓴 논문의 제목은 무엇인가요?"
|
11. run_query 래퍼
Python 코드에서 CLI를 호출하고 stdout에서 답변을 추출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import subprocess
def run_query(method: str, question: str, root: Path = PROJECT_ROOT) -> str:
result = subprocess.run(
["graphrag", "query", "--root", str(root), "--method", method, "--query", question],
capture_output=True,
text=True,
timeout=300,
)
if result.returncode != 0:
return f"ERROR: {result.stderr[:500]}"
out = result.stdout
for marker in [
"SUCCESS: Local Search Response:",
"SUCCESS: Global Search Response:",
"Response:",
"Answer:",
]:
if marker in out:
return out.split(marker, 1)[1].strip()
return out.strip()
|
12. local/global 비교
이 절의 함수는 답변과 시간을 모으는 비교 도구다. 실행 결과를 남기기 전에는 어느 방식이 더 빠르거나 정확하다고 결론 내리지 않는다. 특정 엔티티 질문과 코퍼스 전체를 묻는 질문을 나누어 같은 기준으로 비교한다.
같은 질문을 local/global로 모두 실행해서 시간과 답변을 비교한다.
1
2
3
4
5
6
7
8
9
10
| def compare_methods(question: str) -> dict:
out = {}
for method in ("local", "global"):
t0 = time.time()
ans = run_query(method, question)
out[method] = {
"answer": ans,
"elapsed_sec": round(time.time() - t0, 2),
}
return out
|
1
2
3
4
5
6
| q = "에빈 브로스와 콰빅스 연구소는 어떤 관계인가요?"
r = compare_methods(q)
for method, d in r.items():
print(f"\n[{method.upper()}] ({d['elapsed_sec']}s)")
print(d["answer"][:400])
|
추가 query 방식
1
| !graphrag query --root {PROJECT_ROOT} --method drift --query "모리카이 센의 멘토가 쓴 논문의 제목은 무엇인가요?"
|
1
| !graphrag query --root {PROJECT_ROOT} --method global --community-level 2 --query "이 코퍼스의 주요 주제는?"
|
13. 핵심 개념 총정리
| 개념 | 역할 |
|---|
| graphrag init | 프로젝트 템플릿 생성 |
| settings.yaml | 모델, 청킹, 추출 설정 |
| GRAPHRAG_API_KEY | GraphRAG용 API 키 |
| graphrag index | 그래프 인덱싱 실행 |
| max_gleanings | 추가 추출 횟수 |
| entities.parquet | 엔티티 결과 |
| relationships.parquet | 관계 결과 |
| community_reports.parquet | 커뮤니티 요약 |
| local query | 특정 엔티티 주변 답변 |
| global query | 전체 커뮤니티 요약 기반 답변 |
| drift query | local/global 중간 성격의 탐색 |
14. 자주 나오는 실수/주의사항
실수 1: .env 키 이름 착각
1
| OPENAI_API_KEY가 아니라 GraphRAG 프로젝트 안에서는 GRAPHRAG_API_KEY를 사용한다.
|
graphrag index 전에 input 문서가 들어 있어야 한다.
실수 3: artifact 컬럼을 고정 가정
1
| print(list(df.columns))
|
GraphRAG 버전에 따라 parquet 컬럼이 달라질 수 있으니 먼저 확인한다.
실수 4: local/global 사용처 혼동
1
2
| local: 특정 인물/조직/관계 질문
global: 전체 코퍼스의 주제/경향 질문
|
15. 설정 변경의 효과를 산출물로 확인하기
설정 하나를 바꾼 실험마다 별도의 결과를 보관하면 어떤 관계와 요약이 달라졌는지 확인하기 쉽다. 입력 코퍼스와 모델 조건을 고정하고, 최종 답변이 달라진 질문에서 관련 산출물까지 거슬러 살펴본다.
CLI의 출력 형식과 산출물 컬럼을 사용하는 코드는 설치 버전과 함께 관리한다. 실행 시간을 비교할 때도 초기 인덱싱과 캐시 재사용을 구분해야 설정 변경에 필요한 비용을 해석할 수 있다.