감사(Audit) 보고서 기반 고급 검색·질의응답(QA) 시스템 구축을 위한 GraphRAG 실험 정리본
2025.11 — Local 환경에서 GraphRAG 전체 인덱싱 성공 기록
기존 RAG(Retrieval-Augmented Generation)는 텍스트 chunk 기반의 단순 검색이라
문서 간 관계·의미·구조적 흐름을 놓치는 문제가 존재함.
GraphRAG는 다음을 제공함:
| 기능 | 설명 |
|---|---|
| Graph 기반 Semantic Search | 문서에서 엔티티(조직·부서·사건 등)와 관계를 추출하여 지식그래프 구축 |
| Community Summaries | 문서들의 핵심 클러스터를 자동 요약 |
| 전역(Global) 검색 | 질문과 직접 관련된 문서 뿐 아니라 연관된 개념까지 확장 검색 |
| 정밀 감사/규정 문서 검색에 최적화 | 규정 간 연결, 지적사항–원인–영향 구조 분석 가능 |
| 구분 | 일반 RAG | GraphRAG |
|---|---|---|
| 검색 방식 | 벡터 유사도 기반 | 지식 그래프 탐색 기반 |
| 문맥 이해 | 문장 중심 | 엔티티 및 관계 중심 |
| 설명 가능성 | 약함 | 문제–원인–조치 관계에서 근거 제시 가능 |
| 활용 | 단순 답변형 챗봇 | 설명형, 구조화된 감사 보고서 보조 |
공공기관 감사보고서는 일반적으로 아래와 같은 구조를 가집니다:
1. 지적사항
2. 원인
3. 관련 규정 위반
4. 조치/처분
5. 동일/유사사례
이런 관계형 텍스트를 GraphRAG가 아래와 같이 효과적으로 처리합니다:
graphrag_project/
├─ settings.yaml # GraphRAG 전체 파이프라인 설정 파일
├─ .env # API Key 저장
├─ input/ # PDF 또는 TXT 입력
├─ input_txt/ # 변환된 텍스트 임시 저장
├─ output/ # 결과물 (지식그래프, 요약, 인덱스 등)
├─ scripts/
│ └─ pdf_to_txt.py # PDF → TXT 변환
├─ run_all.bat # 전체 자동 실행 스크립트
1) Conda 가상환경 만들기
conda create -n graphrag_env python=3.11 -y
conda activate graphrag_env
2) GraphRAG 설치 (2.2.0 기준)
pip install graphrag==2.2.0
3) Graph 인덱싱 실행
프로젝트 폴더를 생성 : graphrag_project
GraphRAG 설정 파일 생성 : graphrag init
PDF 파일을 아래 폴더에 복사
graphrag_project/input/pdfs
.env 설정GRAPHRAG_API_KEY=********************
OPENAI_API_KEY=********************
OPENAI_API_BASE=https://api.openai.com/v1
OPENAI_API_TYPE=openai
settings.yaml 수정version: "1.0"
root: .
encoding: utf-8
models:
llm:
type: openai_chat
model: gpt-4o-mini
api_key: ${OPENAI_API_KEY}
embedding:
type: openai_embedding
model: text-embedding-3-small
api_key: ${OPENAI_API_KEY}
default_chat_model:
type: openai_chat
model: gpt-4o-mini
api_key: ${OPENAI_API_KEY}
default_embedding_model:
type: openai_embedding
model: text-embedding-3-small
api_key: ${OPENAI_API_KEY}
input:
type: file
file_type: text
base_dir: "input"
output:
base_dir: "output"
┌───────────────────────────────┐
│ PDF / TEXT │
└───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 인덱싱 단계 (Indexing) │
└─────────────────────────────────────────────────────┘
│
extract_graph
extract_claims
summarize_descriptions
question_gen_system_prompt
↓
community detection
↓
community_report_graph / community_report_text
↓
┌─────────────────────────────────────────────────────┐
│ 검색 단계 (Query) │
└─────────────────────────────────────────────────────┘
basic_search_system_prompt
local_search_system_prompt
global_search_map / reduce / knowledge prompt
drift_search / drift_reduce_prompt
| 프롬프트 | 역할 |
|---|---|
| extract_graph | 엔티티/관계 추출 |
| extract_claims | 핵심 주장(Claim) 추출 |
| summarize_descriptions | 엔티티 설명 요약 |
| community_report_graph | 그래프 기반 커뮤니티 보고서 |
| community_report_text | 텍스트 기반 커뮤니티 보고서 |
| question_gen_system_prompt | 문서 기반 질문 생성 |
| basic_search_system_prompt | 기본 검색 프롬프트 |
| local_search_system_prompt | 특정 문서/클러스터 범위 검색 |
| global_search_map_system_prompt | 전역 검색 MAP 단계 |
| global_search_reduce_system_prompt | 전역 검색 REDUCE 단계 |
| global_search_knowledge_system_prompt | 전역 지식 요약 |
| drift_search_system_prompt | 시계열 변화(드리프트) 검색 |
| drift_reduce_prompt | 드리프트 요약 |
감사 보고서는 표·회계항목이 많고 PDF 구조가 복잡하여 PDF 파싱 오류가 발생할 수 있음
이를 자동 필터링/로그 기록하도록 구성
import os
import sys
import pdfplumber
def convert_pdf_to_txt(pdf_path, txt_path):
"""PDF → TXT 변환 함수"""
try:
with pdfplumber.open(pdf_path) as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text() or ""
except Exception as e:
print(f"[ERROR] 변환 실패 → {pdf_path} | {e}")
return False
# 저장
with open(txt_path, "w", encoding="utf-8") as f:
f.write(text)
return True
def main(pdf_dir, txt_temp_dir, final_txt_dir):
print(f"[INFO] PDF 폴더: {pdf_dir}")
print(f"[INFO] TXT 생성 폴더(temp): {txt_temp_dir}")
print(f"[INFO] TXT 이동 폴더(final): {final_txt_dir}")
# 폴더 생성
os.makedirs(txt_temp_dir, exist_ok=True)
os.makedirs(final_txt_dir, exist_ok=True)
# PDF 목록 읽어오기
pdf_files = [f for f in os.listdir(pdf_dir) if f.lower().endswith(".pdf")]
print(f"[INFO] 변환 대상 PDF 총 {len(pdf_files)}개")
converted_count = 0
skipped_count = 0
for pdf_file in pdf_files:
pdf_path = os.path.join(pdf_dir, pdf_file)
txt_filename = os.path.splitext(pdf_file)[0] + ".txt"
final_txt_path = os.path.join(final_txt_dir, txt_filename)
# 이미 존재하는 TXT는 건너뛰기 (옵션 B의 핵심)
if os.path.exists(final_txt_path):
skipped_count += 1
print(f"[SKIP] 이미 존재 → {txt_filename}")
continue
print(f"[INFO] 변환 중 → {pdf_file}")
tmp_txt_path = os.path.join(txt_temp_dir, txt_filename)
if convert_pdf_to_txt(pdf_path, tmp_txt_path):
# 최종 txt 폴더로 이동
os.replace(tmp_txt_path, final_txt_path)
converted_count += 1
else:
print(f"[ERROR] 변환 실패: {pdf_file}")
print("===============================================")
print(f"[INFO] 새로 변환된 TXT: {converted_count}개")
print(f"[INFO] 스킵된 PDF (이미 TXT 존재): {skipped_count}개")
print("===============================================")
if __name__ == "__main__":
if len(sys.argv) != 4:
print("사용법: python pdf_to_txt.py <PDF_DIR> <TEMP_TXT_DIR> <FINAL_TXT_DIR>")
sys.exit(1)
main(sys.argv[1], sys.argv[2], sys.argv[3])
아래의 작업이 한번에 수행 됨
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul
echo ===============================================
echo PDF → TXT 변환 + GraphRAG Indexing 자동화
echo ===============================================
echo 현재 작업 디렉토리: %cd%
echo.
REM ----------------------------------------------------
REM 1. Conda 환경 확인
REM ----------------------------------------------------
echo [1/6] Conda 환경 확인 중...
conda env list | findstr /c:"audit_graphrag" >nul
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] audit_graphrag 환경이 존재하지 않습니다.
pause
exit /b
)
echo [OK] audit_graphrag 환경 발견
echo.
REM ----------------------------------------------------
REM 2. Conda 환경 활성화 (정상 작동 방식)
REM ----------------------------------------------------
echo [2/6] Conda 환경 활성화...
call C:\Users\Administrator\anaconda3\Scripts\activate.bat audit_graphrag
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] conda activate 실패
pause
exit /b
)
echo [OK] conda activate 완료
echo.
REM ----------------------------------------------------
REM 3. .env 파일 로딩
REM ----------------------------------------------------
echo [3/6] .env 파일 로딩 중...
for /f "usebackq tokens=1,* delims==" %%a in (".env") do (
set %%a=%%b
)
echo [OK] API Key 로딩 완료
echo.
REM ----------------------------------------------------
REM 4. PDF → TXT 변환 수행 (신규/없는 TXT만 처리)
REM ----------------------------------------------------
echo [4/6] PDF → TXT 변환 준비 중...
echo TXT는 없는 파일만 생성됩니다.
echo.
python scripts\pdf_to_txt.py input input_txt input
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] PDF → TXT 변환 실패
pause
exit /b
)
echo [OK] PDF → TXT 변환 완료
echo.
REM ----------------------------------------------------
REM 5. GraphRAG Indexing 실행
REM ----------------------------------------------------
echo [5/6] GraphRAG Indexing 실행...
echo 시작 시간: %date% %time%
echo.
graphrag index --root . --verbose
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] GraphRAG Indexing 실패
pause
exit /b
)
echo [OK] GraphRAG Indexing 완료!
echo.
pause
| 단계 | 설명 | output 위치 |
|---|---|---|
| 1️⃣ 텍스트 chunking | 텍스트를 토큰 단위로 분할 | output/chunks/ |
| 2️⃣ 임베딩 생성 | embedding 모델로 각 chunk 벡터화 | output/embeddings/ |
| 3️⃣ 그래프 추출 | 엔티티·관계 분석 → 지식그래프 생성 | output/graph/ |
| 4️⃣ community detection | 문서 클러스터링 | output/graph/communities |
| 5️⃣ summary 생성 | 각 클러스터별 의미 요약 | output/community_reports/ |
| 6️⃣ 검색 인덱스 구축 | local·global search용 DB | output/search_index/ |
output/
├─ entities.json
├─ relationships.json
├─ graph/
├─ summaries/
├─ embeddings/
├─ community_reports/
├─ search_index/
활용 방식
| 파일 | 용도 |
|---|---|
| entities.json | 문서에서 추출된 엔티티 목록 |
| relationships.json | 엔티티 간 연결 (기관–규정, 문제점–원인 등) |
| community_reports/ | 그래프 클러스터 별 자동 요약 → QA 답변 구성 근거로 사용 |
| search_index/ | Streamlit QA 앱에서 실제로 사용되는 검색 DB |
| embeddings/ | Fast search용 chunk 벡터 |
완성된 output 디렉토리를 기반으로 GraphRAG 기반 지역+전역 검색(Local+Global Search) QA 시스템을 바로 만들 예정