포스트

RAG 기술 아키텍처 세미나 - (3) Knowledge-based GraphRAG 심화 이해

RAG 기술 아키텍처 세미나 - (3) Knowledge-based GraphRAG 심화 이해

온톨로지 기반 지식그래프와 Neo4j를 활용한 정밀 관계 탐색

아키텍처팀 기술 세미나 — 보조 자료
원본 문서: Neo4j 기반 GraphRAG를 활용한 Hybrid RAG 시스템 구현
작성일: 2026-05-11


관련글


목차

  1. Knowledge-based GraphRAG란 무엇인가
  2. 핵심 구성 스택 — Neo4j와 공식 Python 라이브러리
  3. 지식그래프 구축 파이프라인 — SimpleKGPipeline 중심
  4. 스키마(Schema) 설계 — LLM 추출을 안내하는 청사진
  5. 엔터티 해소(Entity Resolution) — 그래프 품질의 핵심
  6. 렉시컬 그래프(Lexical Graph) — 문서와 엔터티의 연결
  7. 검색기(Retriever) 유형과 선택 전략
  8. Cypher — 관계 탐색의 질의 언어
  9. 전체 SW 아키텍처 설계
  10. 실제 구현 예시 코드 — IT 시스템 도메인
  11. 대규모 실사례 분석
  12. 한계와 주요 과제
  13. 결론 — 정밀성과 설명 가능성의 GraphRAG

1. Knowledge-based GraphRAG란 무엇인가

1.1 정의

Knowledge-based GraphRAG는 특정 도메인에 대해 온톨로지(Ontology) 또는 스키마(Schema)를 사전에 정의하고, 그 구조에 따라 문서에서 엔터티와 관계를 추출하여 지식그래프를 구축한 뒤, 이 그래프를 탐색하여 LLM 응답을 생성하는 방식입니다.

이 접근법의 핵심 철학은 단순합니다. “무엇을 알고 싶은지를 먼저 정의하고, 그에 맞게 지식을 구조화한다.” 무작위로 텍스트에서 정보를 추출하는 것이 아니라, 미리 설계한 청사진에 따라 일관된 방식으로 지식을 쌓아가는 것입니다.

앞선 세미나 자료에서 다룬 Index-based GraphRAG(Microsoft GraphRAG)와의 핵심 차이는 다음과 같습니다.

1
2
3
4
5
[Index-based GraphRAG]
문서 → LLM이 자유롭게 엔터티/관계 추출 → Leiden 알고리즘으로 커뮤니티 분류 → 커뮤니티 요약 생성 → 글로벌 질의 대응

[Knowledge-based GraphRAG]
온톨로지/스키마 설계 → 스키마 기반 LLM 추출 (제어된 추출) → Neo4j에 정형화된 그래프 구축 → Cypher 기반 정밀 관계 탐색 → 구체적 관계 질의 대응

1.2 왜 Knowledge-based인가

아키텍처팀이 일상적으로 마주하는 업무 질문들을 다시 생각해봅시다.

  • “Log4j 2.14.1을 사용하는 애플리케이션이 배포된 서버 목록을 뽑아줘.”
  • “결제서비스가 의존하는 라이브러리 중 최근 3개월 내 취약점이 발견된 것은?”
  • “운영팀 B가 관리하는 서비스들의 외부 노출 현황은?”

이런 질문들은 “어떤 문서에 관련 정보가 있는가?”가 아니라 “어떤 엔터티들이 어떤 관계로 연결되어 있는가?”를 묻습니다. 관계 구조가 명확하고, 답변도 정확해야 하며, 근거가 추적 가능해야 합니다.

이 세 가지 요구사항 — 정밀성, 정확성, 추적 가능성 — 이 Knowledge-based GraphRAG가 특히 강점을 발휘하는 영역입니다.


2. 핵심 구성 스택 — Neo4j와 공식 Python 라이브러리

2.1 Neo4j

Neo4j는 세계에서 가장 널리 사용되는 그래프 데이터베이스입니다. 노드(Node), 관계(Relationship), 속성(Property)으로 이루어진 프로퍼티 그래프(Property Graph) 모델을 기반으로 하며, Cypher라는 선언형 그래프 질의 언어를 제공합니다.

Neo4j가 Knowledge-based GraphRAG에 적합한 이유는 다음과 같습니다.

네이티브 그래프 저장: 노드와 관계를 인접 리스트(Adjacency List) 방식으로 저장하기 때문에, 관계 탐색이 테이블 조인 없이 포인터 추적만으로 이루어집니다. 그래프 탐색 성능이 관계형 DB에 비해 월등히 빠릅니다.

벡터 인덱스 내장 (Neo4j 5.x 이후): 별도의 벡터 DB 없이 Neo4j 내부에서 벡터 유사도 검색을 수행할 수 있습니다. 그래프 탐색과 벡터 검색을 단일 쿼리에서 결합하는 것이 가능합니다.

전문 검색 인덱스(Full-text Index): Lucene 기반 전문 검색 인덱스를 지원하여 BM25 스타일의 키워드 검색도 Neo4j 내에서 수행할 수 있습니다.

Cypher 질의 언어: 그래프 패턴 매칭에 최적화된 직관적인 질의 언어로, 복잡한 멀티홉 관계 탐색을 간결하게 표현할 수 있습니다.

2.2 neo4j-graphrag-python — 공식 Python 라이브러리

neo4j-graphrag-python은 Neo4j가 공식 지원하는 Python 라이브러리로, 지식그래프 구축(KG Builder)과 GraphRAG 검색(Retriever + Generator) 두 가지 핵심 기능을 제공합니다. 이전에 neo4j-genai라는 이름으로 배포되다가 neo4j-graphrag로 이름이 바뀌었습니다.

graph LR
    subgraph neo4j_graphrag["neo4j-graphrag-python 패키지"]
        subgraph KGBuilder["지식그래프 구축"]
            SKP["SimpleKGPipeline\n(간편 KG 구축)"]
            PP["Pipeline\n(커스텀 파이프라인)"]
            COMP["구성 컴포넌트\n(TextSplitter, EntityExtractor\nEmbedder, KGWriter, Resolver)"]
        end

        subgraph Retrieval["검색기 (Retrievers)"]
            VR["VectorRetriever\n(벡터 유사도)"]
            VCR["VectorCypherRetriever\n(벡터 + Cypher 확장)"]
            HR["HybridRetriever\n(벡터 + BM25)"]
            HCR["HybridCypherRetriever\n(벡터 + BM25 + Cypher)"]
            T2C["Text2CypherRetriever\n(자연어 → Cypher)"]
            TR["ToolsRetriever\n(LLM이 도구 선택)"]
        end

        subgraph Generation["응답 생성"]
            GRAG["GraphRAG\n(검색 + LLM 응답)"]
        end
    end

3. 지식그래프 구축 파이프라인 — SimpleKGPipeline 중심

3.1 전체 파이프라인 구조

지식그래프를 구축하는 파이프라인은 다음과 같은 단계로 구성됩니다.

flowchart TB
    subgraph Input["입력"]
        PDF["PDF 문서"]
        TXT["텍스트 문서"]
        SCHEMA["스키마 정의\n(엔터티/관계/패턴)"]
    end

    subgraph Pipeline["SimpleKGPipeline"]
        LOAD["① 문서 로더\n(PDF/Markdown Loader)"]
        SPLIT["② 텍스트 분할\n(FixedSizeSplitter\nchunk_size=500, overlap=100)"]
        EMBED["③ 청크 임베딩\n(Embedder)"]
        LEXICAL["④ 렉시컬 그래프 생성\n(Document → Chunk 연결)"]
        EXTRACT["⑤ 엔터티/관계 추출\n(LLM + 스키마 가이드)"]
        RESOLVE["⑥ 엔터티 해소\n(Entity Resolution\n동명 노드 병합)"]
        WRITE["⑦ Neo4j 적재\n(KGWriter)"]
    end

    subgraph Output["Neo4j 저장 결과"]
        DOC_NODE["Document 노드"]
        CHUNK_NODE["Chunk 노드\n(+ 벡터 임베딩)"]
        ENT_NODE["Entity 노드\n(Application, Library 등)"]
        REL["관계 엣지\n(USES, DEPLOYED_ON 등)"]
    end

    PDF --> LOAD
    TXT --> LOAD
    SCHEMA --> EXTRACT

    LOAD --> SPLIT
    SPLIT --> EMBED
    SPLIT --> LEXICAL
    EMBED --> LEXICAL
    LEXICAL --> EXTRACT
    EXTRACT --> RESOLVE
    RESOLVE --> WRITE

    WRITE --> DOC_NODE
    WRITE --> CHUNK_NODE
    WRITE --> ENT_NODE
    WRITE --> REL

3.2 각 단계 상세 설명

① 문서 로더: PDF, Markdown, 일반 텍스트 파일을 읽어 처리 가능한 형태로 변환합니다. neo4j-graphrag 라이브러리는 PDF와 Markdown에 대한 기본 로더를 제공하며, 커스텀 로더를 구현할 수도 있습니다.

② 텍스트 분할(Text Splitting): 문서를 처리 가능한 크기의 청크로 나눕니다. FixedSizeSplitterchunk_sizechunk_overlap을 파라미터로 받으며, approximate=True 설정 시 단어 중간에서 잘리지 않도록 조정합니다. LangChain의 RecursiveCharacterTextSplitter나 LlamaIndex 분할기도 어댑터를 통해 사용할 수 있습니다.

③ 청크 임베딩: 각 청크를 임베딩 모델로 벡터화합니다. 생성된 벡터는 청크 노드의 속성으로 저장되고, Neo4j 벡터 인덱스에 등록됩니다. OpenAI, VertexAI, SentenceTransformer 등 다양한 임베딩 모델을 지원합니다.

④ 렉시컬 그래프 생성: 청크와 원본 문서 간의 계층 관계를 그래프로 구성합니다. Document → CONTAINS → Chunk, Chunk → NEXT_CHUNK → Chunk 등의 관계로 문서 구조를 보존합니다. 이 구조가 나중에 엔터티와 원본 문서를 연결하는 역할을 합니다.

⑤ 엔터티/관계 추출(Entity & Relation Extraction): 스키마(또는 온톨로지)를 참조하여 LLM이 각 청크에서 엔터티와 관계를 추출합니다. 스키마로 추출 대상을 제한하면 LLM이 무관한 정보를 추출하는 것을 방지하고 일관성을 높일 수 있습니다. OpenAI, VertexAI처럼 구조화 출력(Structured Output)을 지원하는 LLM을 사용하면 추출 품질이 더욱 향상됩니다.

⑥ 엔터티 해소(Entity Resolution): 기본적으로 SimpleKGPipeline은 각 파이프라인 실행 후 동일한 레이블과 이름 속성을 가진 노드들을 자동으로 병합합니다. perform_entity_resolution=True가 기본값으로, 이를 통해 동일한 실세계 개체가 여러 청크에서 중복 추출되더라도 하나의 노드로 통합됩니다. 더 정밀한 해소가 필요하면 FuzzyMatchResolver(문자열 유사도 기반)나 SpaCySemanticMatchResolver(의미 기반)를 추가할 수 있습니다.

⑦ Neo4j 적재: 최종적으로 추출된 엔터티, 관계, 청크, 임베딩이 Neo4j 데이터베이스에 저장됩니다.


4. 스키마(Schema) 설계 — LLM 추출을 안내하는 청사진

4.1 스키마가 왜 중요한가

스키마 없이 LLM에게 “문서에서 엔터티와 관계를 추출하라”고 지시하면, LLM은 자신이 중요하다고 판단하는 것을 추출합니다. 이는 운영마다 결과가 달라지고, 동일한 개념이 서로 다른 방식으로 표현되며, 우리가 실제로 필요한 정보가 누락될 수 있다는 문제를 낳습니다.

스키마는 LLM에게 “이 도메인에서 무엇이 중요한 엔터티이고, 어떤 관계를 추출해야 하는가?”를 명시적으로 알려주는 청사진입니다. neo4j-graphrag-python에서 스키마는 세 가지 모드로 동작합니다.

스키마 모드동작 방식사용 시점
"FREE"스키마 강제 없음. LLM이 자유롭게 추출도메인이 불명확하거나 탐색적 분석 시
"EXTRACTED"LLM이 텍스트에서 스키마를 자동 추론도메인 파악 단계, PoC 초기
명시적 딕셔너리node_types, relationship_types, patterns 직접 정의도메인이 명확한 운영 시스템

4.2 IT 시스템 도메인 스키마 설계 예시

아키텍처팀 업무에 적합한 IT 시스템 도메인 스키마를 구체적으로 설계하면 다음과 같습니다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# IT 시스템 도메인 스키마 정의 예시
NODE_TYPES = [
    # 단순 레이블
    "Organization",
    
    # 설명 포함
    {
        "label": "Application",
        "description": "소프트웨어 애플리케이션 또는 서비스 컴포넌트"
    },
    {
        "label": "Library",
        "description": "애플리케이션이 의존하는 외부 소프트웨어 라이브러리 또는 프레임워크"
    },
    
    # 속성 포함
    {
        "label": "Vulnerability",
        "description": "알려진 보안 취약점 (CVE 등)",
        "properties": [
            {"name": "cve_id",    "type": "STRING", "required": True},
            {"name": "cvss_score","type": "FLOAT"},
            {"name": "severity",  "type": "STRING"},  # CRITICAL/HIGH/MEDIUM/LOW
        ]
    },
    {
        "label": "Server",
        "description": "애플리케이션이 배포된 물리/가상 서버 또는 컨테이너",
        "properties": [
            {"name": "hostname",    "type": "STRING", "required": True},
            {"name": "environment", "type": "STRING"},  # prod/stg/dev
            {"name": "region",      "type": "STRING"},
        ]
    },
    {
        "label": "Service",
        "description": "외부 또는 내부에 제공되는 API/서비스",
        "properties": [
            {"name": "exposure_type", "type": "STRING"},  # external/internal
            {"name": "protocol",      "type": "STRING"},  # HTTP/gRPC 등
        ]
    },
    {
        "label": "Team",
        "description": "서비스나 시스템을 운영하는 담당 조직 또는 팀"
    },
    {
        "label": "Version",
        "description": "라이브러리의 특정 버전",
        "properties": [
            {"name": "version_number",  "type": "STRING", "required": True},
            {"name": "is_deprecated",   "type": "BOOLEAN"},
            {"name": "is_patched",      "type": "BOOLEAN"},
        ]
    },
]

RELATIONSHIP_TYPES = [
    {"label": "USES",        "description": "애플리케이션이 특정 라이브러리를 사용함"},
    {"label": "HAS_VERSION", "description": "라이브러리가 특정 버전을 보유함"},
    {"label": "AFFECTS",     "description": "취약점이 특정 버전에 영향을 미침"},
    {"label": "DEPLOYED_ON", "description": "애플리케이션이 특정 서버에 배포됨"},
    {"label": "PROVIDES",    "description": "애플리케이션이 특정 서비스를 제공함"},
    {"label": "MANAGED_BY",  "description": "서비스가 특정 팀에 의해 관리됨"},
    {"label": "DEPENDS_ON",  "description": "애플리케이션이 다른 애플리케이션에 의존함"},
    {
        "label": "GOVERNED_BY",
        "description": "서비스가 특정 규정/정책의 적용을 받음",
        "properties": [{"name": "since_date", "type": "STRING"}]
    },
]

# 허용되는 관계 패턴 (Triple 형태)
PATTERNS = [
    ("Application", "USES",        "Library"),
    ("Library",     "HAS_VERSION", "Version"),
    ("Vulnerability","AFFECTS",    "Version"),
    ("Application", "DEPLOYED_ON", "Server"),
    ("Application", "PROVIDES",    "Service"),
    ("Service",     "MANAGED_BY",  "Team"),
    ("Application", "DEPENDS_ON",  "Application"),
    ("Service",     "GOVERNED_BY", "Regulation"),
]

4.3 스키마 모드별 동작 비교

flowchart LR
    subgraph FREE["FREE 모드"]
        F1["LLM이 자유 추출\n→ 일관성 낮음\n→ 탐색에 유용"]
    end

    subgraph EXTRACTED["EXTRACTED 모드"]
        E1["LLM이 스키마 자동 추론\n→ 도메인 파악 단계\n→ PoC에 적합"]
    end

    subgraph EXPLICIT["명시적 스키마"]
        S1["온톨로지 기반 정의\n→ 제어된 추출\n→ 운영 시스템에 적합"]
    end

    FREE --> |"더 많은 제어"| EXTRACTED
    EXTRACTED --> |"더 많은 제어"| EXPLICIT

5. 엔터티 해소(Entity Resolution) — 그래프 품질의 핵심

5.1 엔터티 중복 문제의 심각성

Knowledge-based GraphRAG에서 가장 흔하고 심각한 품질 이슈는 엔터티 중복입니다. 동일한 실세계 개체가 서로 다른 표현으로 여러 노드로 등록되면 그래프 탐색이 무너집니다.

예를 들어, 100개의 문서에서 “Log4j”를 추출했을 때 다음과 같은 노드들이 만들어질 수 있습니다.

1
2
3
4
5
- Log4j (Library)
- Apache Log4j (Library)
- log4j-core (Library)
- Log4j 2 (Library)
- log4j2 (Library)

이 다섯 개가 모두 별도 노드로 존재하면, “Log4j를 사용하는 애플리케이션”을 Cypher로 찾을 때 하나의 노드에만 연결된 엣지만 탐색되어 불완전한 결과가 나옵니다.

5.2 엔터티 해소 전략

neo4j-graphrag-python에서 제공하는 엔터티 해소 방법은 다음 세 가지입니다.

flowchart TD
    RAW["추출된 엔터티 노드들\n(중복 포함)"]

    subgraph R1["기본 Name-based Resolution"]
        NB["동일 레이블 + 동일 name 속성\n→ 자동 병합\n(perform_entity_resolution=True)"]
    end

    subgraph R2["FuzzyMatchResolver"]
        FM["RapidFuzz 기반 문자열 유사도\n→ 임계값 이상이면 병합\n예: 'Log4j' ≈ 'log4j' ≈ 'Log4J'"]
    end

    subgraph R3["SpaCySemanticMatchResolver"]
        SM["spaCy 의미 임베딩 기반\n→ 의미적으로 유사한 엔터티 병합\n예: '결제서버' ≈ '페이먼트 서버'"]
    end

    RAW --> NB
    RAW --> FM
    RAW --> SM

    NB --> MERGED["병합된 엔터티 노드\n(고품질 그래프)"]
    FM --> MERGED
    SM --> MERGED

실무에서는 이 세 가지를 조합하여 사용하는 것이 효과적입니다. 먼저 Name-based Resolution으로 완전히 동일한 이름을 병합하고, 그다음 FuzzyMatchResolver로 표기 변형을 처리하며, 마지막으로 SpaCySemanticMatchResolver로 의미적 동의어를 처리합니다.

5.3 사전 정규화의 중요성

자동 해소 외에도, 데이터 적재 전에 사전 정규화(Pre-normalization) 를 수행하는 것이 좋습니다. 예를 들어 라이브러리 이름에 대한 정규화 사전을 만들어 LLM 추출 전에 표준 명칭으로 통일하거나, 추출 프롬프트에 “항상 ‘Apache Log4j’라는 공식 이름을 사용하라”는 지시를 포함하는 방식입니다.


6. 렉시컬 그래프(Lexical Graph) — 문서와 엔터티의 연결

렉시컬 그래프는 Knowledge-based GraphRAG에서 종종 간과되지만 매우 중요한 개념입니다. 지식그래프가 엔터티와 관계의 추상적 구조를 표현한다면, 렉시컬 그래프는 그 추상적 지식이 어떤 원본 문서의 어느 청크에서 왔는지를 추적합니다.

graph LR
    subgraph LexicalGraph["렉시컬 그래프 (Lexical Graph)"]
        DOC["Document\n아키텍처 명세서 v2.3"]
        C1["Chunk 1\n'결제서비스는\nLog4j를 사용한다'"]
        C2["Chunk 2\n'결제서비스는\n운영서버-001에 배포'"]
        C3["Chunk 3\n'운영서버-001은\nAWS ap-northeast-2에 위치'"]

        DOC --"CONTAINS"--> C1
        DOC --"CONTAINS"--> C2
        DOC --"CONTAINS"--> C3
        C1 --"NEXT_CHUNK"--> C2
        C2 --"NEXT_CHUNK"--> C3
    end

    subgraph KG["지식그래프 (Knowledge Graph)"]
        APP["Application\n결제서비스"]
        LIB["Library\nLog4j"]
        SRV["Server\n운영서버-001"]

        APP --"USES"--> LIB
        APP --"DEPLOYED_ON"--> SRV
    end

    C1 --"MENTIONS"--> APP
    C1 --"MENTIONS"--> LIB
    C2 --"MENTIONS"--> APP
    C2 --"MENTIONS"--> SRV

렉시컬 그래프가 있으면, 그래프 탐색 결과에 원본 문서의 근거 청크를 함께 반환할 수 있습니다. 이는 응답의 근거 추적(Provenance Tracking) 을 가능하게 하여, LLM이 “해당 내용은 ‘아키텍처 명세서 v2.3’의 3번 청크에 근거합니다”라는 식으로 출처를 명시할 수 있습니다.


7. 검색기(Retriever) 유형과 선택 전략

7.1 neo4j-graphrag-python의 검색기 종류

neo4j-graphrag-python은 다음 여섯 가지 검색기를 제공합니다. 각각의 특성을 정확히 이해하면 질의 유형에 맞는 최적의 검색기를 선택할 수 있습니다.

flowchart TD
    subgraph Retrievers["검색기 유형"]
        VR["VectorRetriever\n\n벡터 유사도만 사용\n의미 기반 청크 검색"]
        VCR["VectorCypherRetriever\n\n벡터 검색 후\nCypher로 그래프 확장"]
        HR["HybridRetriever\n\n벡터 + Full-text(BM25)\n단순 결합"]
        HCR["HybridCypherRetriever\n\n벡터 + BM25 후\nCypher로 그래프 확장"]
        T2C["Text2CypherRetriever\n\n자연어 → Cypher 자동 변환\n스키마 기반 그래프 탐색"]
        TR["ToolsRetriever\n\nLLM이 필요한 검색기를\n자동 선택 및 실행"]
    end

7.2 검색기별 상세 특성

VectorRetriever는 가장 단순한 형태로, 질의를 임베딩하여 코사인 유사도가 높은 청크를 반환합니다. 그래프 탐색이 없어 관계 추론은 불가능하지만, 의미 기반 문서 검색에는 충분히 효과적입니다. 빠르고 구현이 간단하여 시작점으로 적합합니다.

VectorCypherRetriever는 Knowledge-based GraphRAG의 핵심 검색기입니다. 먼저 벡터 유사도로 관련 청크나 엔터티를 찾은 다음, retrieval_query 파라미터로 지정한 Cypher 쿼리를 이용하여 그래프를 추가로 탐색합니다. 벡터 검색으로 진입점(entry point)을 찾고, Cypher로 인접 관계를 확장하는 방식입니다.

HybridRetriever는 벡터 인덱스와 전문 검색(Full-text) 인덱스를 동시에 사용하여 결과를 결합합니다. BM25 스타일의 키워드 검색 강점과 벡터 검색의 의미 기반 강점을 함께 활용합니다. 그래프 탐색은 포함되지 않습니다.

HybridCypherRetriever는 HybridRetriever에 Cypher 그래프 확장을 추가한 것입니다. 벡터와 키워드 검색으로 관련 노드를 찾은 후, Cypher로 그래프를 탐색하여 연결된 추가 정보를 수집합니다.

Text2CypherRetriever는 사용자가 자연어로 질의하면 LLM이 이를 Cypher 쿼리로 자동 변환하여 실행합니다. 그래프 스키마(Neo4j 스키마 정보)를 LLM에게 제공하면 더 정확한 Cypher를 생성합니다. 복잡한 관계 질의를 사용자가 Cypher를 몰라도 수행할 수 있다는 장점이 있지만, LLM이 생성한 Cypher가 항상 정확하지 않을 수 있다는 한계도 있습니다.

ToolsRetriever는 가장 최근에 추가된 검색기로, LLM이 주어진 검색기 도구 목록에서 질의에 맞는 도구를 자동으로 선택하고 실행합니다. 여러 검색기를 도구로 등록해두면, LLM이 질의 유형에 따라 VectorRetriever, Text2CypherRetriever 등을 동적으로 선택합니다.

7.3 검색기 선택 가이드

flowchart TD
    Q["어떤 검색기를 선택할 것인가?"]

    Q --> A{"질의 유형은?"}

    A --> |"의미 기반 문서 검색"| VR["VectorRetriever\n빠르고 간단"]

    A --> |"의미 검색 + 관계 확장"| VCR["VectorCypherRetriever\n가장 추천"]

    A --> |"키워드 + 의미 검색"| B{"관계 탐색 필요?"}
    B --> |"불필요"| HR["HybridRetriever"]
    B --> |"필요"| HCR["HybridCypherRetriever"]

    A --> |"복잡한 관계 질의\n(사용자가 Cypher 모름)"| T2C["Text2CypherRetriever"]

    A --> |"다양한 유형 혼합"| TR["ToolsRetriever\nLLM이 자동 선택"]

8. Cypher — 관계 탐색의 질의 언어

8.1 Cypher 기본 개념

Cypher는 Neo4j의 선언형 그래프 질의 언어입니다. 그래프의 패턴을 ASCII-art 스타일로 직관적으로 표현할 수 있어, SQL에 익숙한 개발자도 비교적 쉽게 배울 수 있습니다.

기본 문법 구조는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 기본 패턴: (노드)-[관계]->(노드)
MATCH (n:Application {name: '결제서비스'})
RETURN n

-- 관계 탐색
MATCH (app:Application)-[:USES]->(lib:Library)
WHERE app.name = '결제서비스'
RETURN lib.name

-- 멀티홉 탐색 (2단계)
MATCH (app:Application)-[:USES]->(lib:Library)
      -[:HAS_VERSION]->(ver:Version)
WHERE app.name = '결제서비스'
RETURN lib.name, ver.version_number

-- 변수 길이 경로 탐색 (1~3)
MATCH (start:Application {name: '결제서비스'})-[*1..3]-(connected)
RETURN connected

8.2 VectorCypherRetriever에서 사용하는 Cypher 패턴

VectorCypherRetriever는 벡터 검색으로 찾은 청크나 엔터티를 $node 변수로 전달받아, 이를 기반으로 그래프를 확장하는 Cypher 쿼리를 실행합니다.

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
# IT 시스템 도메인 예시: 취약 라이브러리 영향도 분석
retrieval_query = """
    -- 벡터 검색으로 찾은 Library 노드($node)에서 시작
    MATCH ($node)-[:HAS_VERSION]->(ver:Version)
          <-[:AFFECTS]-(vuln:Vulnerability)
    MATCH (app:Application)-[:USES]->($node)
    MATCH (app)-[:PROVIDES]->(svc:Service)
    MATCH (svc)-[:MANAGED_BY]->(team:Team)
    
    RETURN
        $node.name                      AS library,
        ver.version_number              AS version,
        vuln.cve_id                     AS cve,
        vuln.severity                   AS severity,
        app.name                        AS affected_application,
        svc.name                        AS affected_service,
        team.name                       AS responsible_team,
        $node.text                      AS chunk_text  -- 원본 청크 텍스트
"""

retriever = VectorCypherRetriever(
    driver=driver,
    index_name="library-vector-index",
    retrieval_query=retrieval_query,
    embedder=embedder,
)

8.3 실무에서 자주 사용되는 Cypher 패턴

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
--  특정 취약점의 영향 범위 전체 탐색
MATCH (vuln:Vulnerability {cve_id: 'CVE-2021-44228'})
      -[:AFFECTS]->(ver:Version)
      <-[:HAS_VERSION]-(lib:Library)
      <-[:USES]-(app:Application)
      -[:PROVIDES]->(svc:Service)
      -[:MANAGED_BY]->(team:Team)
RETURN
    lib.name      AS library,
    ver.version_number AS version,
    app.name      AS application,
    svc.name      AS service,
    svc.exposure_type AS exposure,
    team.name     AS team
ORDER BY svc.exposure_type DESC  -- 외부 노출 서비스 우선

--  특정 서비스의 의존성 체인 역추적
MATCH (svc:Service {name: '외부결제 API'})
      <-[:PROVIDES]-(app:Application)
      -[:USES]->(lib:Library)
      -[:HAS_VERSION]->(ver:Version)
OPTIONAL MATCH (vuln:Vulnerability)-[:AFFECTS]->(ver)
RETURN
    app.name          AS application,
    lib.name          AS library,
    ver.version_number AS version,
    vuln.cve_id       AS vulnerability,
    vuln.severity     AS severity
ORDER BY vuln.cvss_score DESC

--  팀별 관리 서비스와 관련 취약점 현황
MATCH (team:Team {name: '결제개발팀'})
      <-[:MANAGED_BY]-(svc:Service)
      <-[:PROVIDES]-(app:Application)
      -[:USES]->(lib:Library)
      -[:HAS_VERSION]->(ver:Version {is_patched: false})
      <-[:AFFECTS]-(vuln:Vulnerability)
WHERE vuln.severity IN ['CRITICAL', 'HIGH']
RETURN
    team.name      AS team,
    svc.name       AS service,
    app.name       AS application,
    lib.name       AS library,
    ver.version_number AS version,
    vuln.cve_id    AS cve_id,
    vuln.cvss_score AS cvss
ORDER BY vuln.cvss_score DESC

--  특정 애플리케이션의 영향받는 하위 서비스 탐색 (가변 )
MATCH (app:Application {name: '인증서버'})
      -[:DEPENDS_ON*1..3]->(downstream:Application)
      -[:PROVIDES]->(svc:Service)
RETURN DISTINCT
    downstream.name AS dependent_application,
    svc.name        AS service,
    length(path)    AS hop_count
ORDER BY hop_count

9. 전체 SW 아키텍처 설계

9.1 컴포넌트 구성도

flowchart TB
    subgraph UserLayer["사용자 계층"]
        WEB["웹 챗봇 UI"]
        API_C["REST API 클라이언트"]
        ANALYST["분석가 대시보드"]
    end

    subgraph APILayer["API 계층"]
        GW["API Gateway\n(인증 / Rate Limit / 라우팅)"]
    end

    subgraph AppLayer["애플리케이션 계층"]
        direction TB
        ORCH["RAG 오케스트레이터\n(GraphRAG 파이프라인 조율)"]
        QP["질의 프로세서\n(엔터티 인식 / 쿼리 라우팅)"]
        CTX["컨텍스트 빌더\n(검색 결과 → 프롬프트 구성)"]
        PROV["근거 추적기\n(Provenance Tracker)"]
    end

    subgraph RetrievalLayer["검색 계층"]
        HCR_INST["HybridCypherRetriever\n(기본 검색)"]
        T2C_INST["Text2CypherRetriever\n(자연어 Cypher 변환)"]
        TOOLS["ToolsRetriever\n(LLM이 검색기 선택)"]
    end

    subgraph StorageLayer["저장 계층"]
        NEO4J_DB["Neo4j Graph DB\n(그래프 + 벡터 인덱스\n+ 전문 검색 인덱스)"]
        ONTO_STORE["온톨로지 저장소\n(JSON Schema / OWL)"]
    end

    subgraph LLMLayer["LLM 계층"]
        EMB_MODEL["임베딩 모델\n(text-embedding-3-large)"]
        GEN_MODEL["생성 LLM\n(GPT-4o / Claude)"]
        EXT_MODEL["추출 LLM\n(SimpleKGPipeline용)"]
    end

    subgraph IngestionLayer["수집 파이프라인"]
        SCHED["스케줄러\n(Airflow / Cron)"]
        SKP_INST["SimpleKGPipeline\n(문서 → KG 자동 구축)"]
        VALID["품질 검증\n(엔터티 해소 / 중복 제거)"]
    end

    WEB --> GW
    API_C --> GW
    ANALYST --> GW
    GW --> ORCH

    ORCH --> QP
    QP --> HCR_INST
    QP --> T2C_INST
    QP --> TOOLS

    HCR_INST --> NEO4J_DB
    T2C_INST --> NEO4J_DB
    TOOLS --> NEO4J_DB

    HCR_INST --> CTX
    T2C_INST --> CTX
    TOOLS --> CTX
    CTX --> PROV
    CTX --> GEN_MODEL
    GEN_MODEL --> ANS["최종 응답\n(근거 포함)"]

    ONTO_STORE --> SKP_INST
    ONTO_STORE --> T2C_INST
    EMB_MODEL --> NEO4J_DB
    EXT_MODEL --> SKP_INST

    SCHED --> SKP_INST
    SKP_INST --> VALID
    VALID --> NEO4J_DB

9.2 Neo4j 내부 인덱스 구성

Neo4j 단일 인스턴스 안에 세 종류의 인덱스가 구성됩니다.

flowchart LR
    subgraph Neo4j["Neo4j 데이터베이스"]
        subgraph GraphIndex["그래프 저장소"]
            NODES["엔터티 노드\n(Application, Library, Vulnerability 등)"]
            RELS["관계 엣지\n(USES, AFFECTS, DEPLOYED_ON 등)"]
            CHUNKS["Chunk 노드\n(렉시컬 그래프)"]
        end

        subgraph VectorIndex["벡터 인덱스 (ANN)"]
            VI["entity-vector-index\n(엔터티 임베딩)\nchunk-vector-index\n(청크 임베딩)"]
        end

        subgraph FulltextIndex["전문 검색 인덱스 (Lucene/BM25)"]
            FTI["entity-fulltext-index\n(엔터티 이름 + 설명)\nchunk-fulltext-index\n(청크 텍스트)"]
        end
    end

    VectorIndex -.->|"ANN 검색 후\nCypher 확장"| GraphIndex
    FulltextIndex -.->|"키워드 검색 후\nCypher 확장"| GraphIndex

10. 실제 구현 예시 코드 — IT 시스템 도메인

10.1 지식그래프 구축 (SimpleKGPipeline)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import asyncio
from neo4j import GraphDatabase
from neo4j_graphrag.embeddings import OpenAIEmbeddings
from neo4j_graphrag.experimental.pipeline.kg_builder import SimpleKGPipeline
from neo4j_graphrag.llm import OpenAILLM
from neo4j_graphrag.experimental.components.text_splitters.fixed_size_splitter import (
    FixedSizeSplitter,
)

# Neo4j 연결
driver = GraphDatabase.driver(
    "neo4j://localhost:7687",
    auth=("neo4j", "password")
)

# LLM 및 임베더 설정
llm = OpenAILLM(
    model_name="gpt-4o",
    model_params={"temperature": 0, "max_tokens": 2000}
)
embedder = OpenAIEmbeddings(model="text-embedding-3-large")

# IT 도메인 스키마 (앞서 정의한 것 사용)
schema = {
    "node_types": NODE_TYPES,
    "relationship_types": RELATIONSHIP_TYPES,
    "patterns": PATTERNS,
}

# SimpleKGPipeline 구성
kg_builder = SimpleKGPipeline(
    llm=llm,
    driver=driver,
    embedder=embedder,
    schema=schema,
    text_splitter=FixedSizeSplitter(chunk_size=500, chunk_overlap=100),
    perform_entity_resolution=True,  # 기본 엔터티 해소 활성화
    from_file=True,
)

# 문서 일괄 처리
async def build_kg():
    doc_paths = [
        "docs/architecture_spec_v2.pdf",
        "docs/service_dependencies.md",
        "docs/vulnerability_report_2025.pdf",
    ]
    for path in doc_paths:
        print(f"처리 중: {path}")
        result = await kg_builder.run_async(
            file_path=path,
            document_metadata={"source": path, "processed_date": "2026-05-11"}
        )
        print(f"완료: {result}")

asyncio.run(build_kg())

10.2 GraphRAG 검색 (HybridCypherRetriever)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from neo4j_graphrag.retrievers import HybridCypherRetriever
from neo4j_graphrag.generation import GraphRAG
from neo4j_graphrag.llm import OpenAILLM

# 취약점 영향도 분석용 Cypher 확장 쿼리
IMPACT_ANALYSIS_QUERY = """
    MATCH (node)  -- 벡터+BM25 검색으로 찾은 노드
    OPTIONAL MATCH (node)-[:HAS_VERSION]->(ver:Version)
                   <-[:AFFECTS]-(vuln:Vulnerability)
    OPTIONAL MATCH (app:Application)-[:USES]->(node)
    OPTIONAL MATCH (app)-[:PROVIDES]->(svc:Service)
                   -[:MANAGED_BY]->(team:Team)
    RETURN
        node.name            AS entity_name,
        labels(node)         AS entity_type,
        ver.version_number   AS version,
        vuln.cve_id          AS cve_id,
        vuln.severity        AS severity,
        app.name             AS application,
        svc.name             AS service,
        team.name            AS responsible_team,
        node.text            AS source_chunk
"""

# HybridCypherRetriever 설정
retriever = HybridCypherRetriever(
    driver=driver,
    vector_index_name="entity-vector-index",
    fulltext_index_name="entity-fulltext-index",
    retrieval_query=IMPACT_ANALYSIS_QUERY,
    embedder=embedder,
)

# GraphRAG 파이프라인 구성
gen_llm = OpenAILLM(model_name="gpt-4o", model_params={"temperature": 0})
rag = GraphRAG(retriever=retriever, llm=gen_llm)

# 질의 실행
result = rag.search(
    query_text="Log4j 취약점에 영향받는 서비스와 담당 팀을 알려줘",
    retriever_config={"top_k": 10}
)
print(result.answer)

10.3 Text2Cypher 검색

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
29
30
31
32
33
from neo4j_graphrag.retrievers import Text2CypherRetriever

# Neo4j 스키마를 LLM에게 제공 (Cypher 생성 품질 향상)
NEO4J_SCHEMA = """
Node properties:
- Application {name: STRING, version: STRING, status: STRING}
- Library {name: STRING, language: STRING}
- Version {version_number: STRING, is_deprecated: BOOLEAN, is_patched: BOOLEAN}
- Vulnerability {cve_id: STRING, severity: STRING, cvss_score: FLOAT}
- Server {hostname: STRING, environment: STRING, region: STRING}
- Service {name: STRING, exposure_type: STRING}
- Team {name: STRING}

Relationships:
- (Application)-[:USES]->(Library)
- (Library)-[:HAS_VERSION]->(Version)
- (Vulnerability)-[:AFFECTS]->(Version)
- (Application)-[:DEPLOYED_ON]->(Server)
- (Application)-[:PROVIDES]->(Service)
- (Service)-[:MANAGED_BY]->(Team)
- (Application)-[:DEPENDS_ON]->(Application)
"""

t2c_retriever = Text2CypherRetriever(
    driver=driver,
    llm=llm,
    neo4j_schema=NEO4J_SCHEMA,
)

# 자연어 질의 → Cypher 자동 생성 및 실행
result = t2c_retriever.search(
    query_text="결제개발팀이 관리하는 서비스 중 외부에 노출된 것은?"
)

11. 대규모 실사례 분석

11.1 엔터프라이즈 데이터 플랫폼 사례

실제 엔터프라이즈 환경에서 Knowledge-based GraphRAG를 적용한 사례를 살펴보면, 한 도소매 유통 기업의 14개 데이터 소스를 통합한 플랫폼에서 Neo4j 지식그래프가 1,200만 개 노드와 8,900만 개 관계로 성장한 사례가 있습니다. 이 그래프는 고객, 주문, 제품, 부품, 공급업체 간의 복잡한 관계를 모델링했습니다.

이 시스템에서 GraphRAG는 전체 질의의 약 7%를 담당했습니다. 나머지 93%는 단순 문서 검색이나 키워드 매칭으로 충분히 처리할 수 있는 질의였습니다. GraphRAG가 담당한 7%는 “공급업체 X가 납품 불가 상황이 되면 어떤 고객이 영향을 받는가?”처럼 멀티홉 관계 추론이 필요한 질의들이었으며, 이는 순수 벡터 RAG로는 처리가 불가능한 영역이었습니다.

이 사례가 주는 중요한 시사점은 두 가지입니다. 첫째, Knowledge-based GraphRAG는 모든 질의를 처리하는 단독 시스템이 아니라, 전체 검색 스택의 일부로 통합될 때 효과적입니다. 멀티홉 관계 질의가 아닌 일반적인 문서 검색은 기존 방식으로 처리하고, GraphRAG는 관계 탐색이 반드시 필요한 질의만 담당합니다. 둘째, 대규모 그래프에서도 Neo4j의 네이티브 그래프 저장은 충분한 성능을 발휘합니다.

11.2 적정 규모 가이드라인

Knowledge-based GraphRAG를 처음 도입할 때 적정 규모와 점진적 확장 경로는 다음과 같습니다.

gantt
    title Knowledge-based GraphRAG 적정 규모 확장 경로
    dateFormat X
    axisFormat 단계 %s

    section 파일럿
    핵심 도메인 온톨로지 설계 (10~20개 엔터티/관계 유형)  :a1, 0, 1
    소규모 문서 (100~500개) 적재                           :a2, 1, 2
    기본 Cypher 질의 검증                                  :a3, 2, 3

    section 확장
    온톨로지 확장 (50개 이상 엔터티/관계 유형)             :b1, 3, 5
    중규모 문서 (1,000~10,000개) 적재                      :b2, 4, 6
    HybridCypherRetriever 통합                             :b3, 5, 6

    section 운영
    대규모 (100,000개+ 문서) 적재                          :c1, 6, 8
    자동화 갱신 파이프라인                                  :c2, 7, 8
    다중 도메인 온톨로지 관리                               :c3, 7, 8

12. 한계와 주요 과제

12.1 온톨로지 설계의 선행 비용

Knowledge-based GraphRAG는 도입 전에 도메인 전문가가 온톨로지를 설계해야 합니다. 이 작업은 단순한 기술적 과제가 아니라 도메인 지식 정리의 과제입니다. IT 시스템 도메인에서 어떤 엔터티와 관계가 중요한지를 결정하는 것은 기술 아키텍트와 비즈니스 도메인 전문가의 협업이 필요합니다.

이 과정을 단축하는 방법으로, "EXTRACTED" 스키마 모드로 소규모 문서 집합에 대해 LLM이 자동으로 스키마를 추론하게 한 다음, 그 결과를 사람이 검토하여 공식 스키마로 정제하는 방식을 권장합니다.

12.2 LLM 기반 추출의 불확실성

LLM이 텍스트에서 엔터티와 관계를 추출할 때, 항상 정확하지는 않습니다. 특히 암묵적 관계(문서에 명시되지 않고 추론해야 하는 관계)의 경우 추출 오류가 발생할 수 있습니다. 구조화 출력을 지원하는 LLM을 사용하고, 추출 결과에 대한 검증 단계를 파이프라인에 포함하는 것이 중요합니다.

12.3 Text2Cypher의 불안정성

Text2CypherRetriever는 사용 편의성이 높지만, LLM이 생성한 Cypher가 문법적으로 올바르지 않거나 의도와 다른 결과를 반환할 수 있습니다. 중요한 업무 질의에는 사전에 검증된 Cypher 템플릿을 직접 사용하는 VectorCypherRetriever나 HybridCypherRetriever가 더 신뢰할 수 있습니다.

12.4 그래프 갱신과 최신성

시스템이 변경될 때마다 그래프도 갱신되어야 합니다. 새 서버가 추가되거나, 라이브러리 버전이 업데이트되거나, 담당 팀이 바뀌는 경우 이를 자동으로 감지하여 그래프에 반영하는 파이프라인이 필요합니다. 이 갱신 지연(lag)이 발생하면 그래프 기반 응답의 정확성이 떨어질 수 있습니다.

12.5 한계 요약

한계심각도권장 대응
온톨로지 설계 선행 비용높음“EXTRACTED” 모드로 초안 자동 생성 후 사람이 정제
LLM 추출 오류중간구조화 출력 LLM 사용 + 추출 후 검증 단계
엔터티 중복 (해소 불완전)높음FuzzyMatchResolver + 사전 정규화
Text2Cypher 불안정중간핵심 질의는 Cypher 템플릿 직접 작성
그래프 갱신 지연중간자동화 갱신 파이프라인 + 변경 감지 이벤트
운영 복잡도중간표준화된 DevOps 파이프라인 + 모니터링

13. 결론 — 정밀성과 설명 가능성의 GraphRAG

Knowledge-based GraphRAG는 Index-based GraphRAG가 “전체 데이터를 조망하는 능력”에서 강점을 가지는 것과 달리, 특정 관계를 정밀하게 탐색하고, 결과를 추적 가능하게 설명하는 능력에서 독보적입니다.

아키텍처팀이 다루는 업무 — 시스템 영향도 분석, 보안 취약점 대응, 서비스 의존성 파악, 변경 관리 — 는 대부분 이 범주에 속합니다. “어떤 엔터티가 어떤 관계를 통해 어떤 결과를 만드는가?”라는 질문에 정확하고 추적 가능한 답변을 요구합니다.

Neo4j와 neo4j-graphrag-python은 이 요구사항을 충족하는 성숙한 기술 스택을 제공합니다. SimpleKGPipeline으로 지식그래프 구축을 자동화하고, 다양한 Retriever로 질의 유형에 맞는 검색 전략을 선택하며, Cypher로 정밀한 관계 탐색을 수행합니다.

핵심은 기술 도구에 있지 않습니다. 잘 설계된 온톨로지와 품질 높은 그래프 데이터가 Knowledge-based GraphRAG의 성패를 가릅니다. 모델이 아무리 뛰어나도, 그래프의 엔터티와 관계가 부정확하거나 불완전하면 탐색 결과도 부정확합니다. 도메인 전문가와 기술 아키텍트가 함께 온톨로지를 설계하고, 지속적으로 그래프 품질을 관리하는 것이 장기적 성공의 열쇠입니다.


참고 자료

  • Neo4j 공식 문서: neo4j-graphrag-python User Guide — Knowledge Graph Builder (2025)
  • Neo4j 공식 문서: neo4j-graphrag-python User Guide — RAG (검색기 유형)
  • Neo4j Blog: “Unleashing the Power of Schema: What’s New in the Neo4j GraphRAG Package for Python” (August 2025)
  • Neo4j Blog: “Effortless RAG with Text2CypherRetriever” (November 2024)
  • deepsense.ai: “Ontology-Driven Knowledge Graph for GraphRAG” (April 2025)
  • Particula Tech: “GraphRAG Implementation: What 12 Million Nodes Taught Us” (February 2026)
  • Neo4j GraphRAG Python Package — PyPI (neo4j-graphrag)
  • Neo4j NODES 2025: “Hands-On Hybrid Retrieval & RAG with Neo4j” Workshop

작성일: 2026-05-11
작성자: 아키텍처팀
관련 문서: Neo4j 기반 GraphRAG를 활용한 Hybrid RAG 시스템 구현
관련 문서: Index-based GraphRAG 심화 이해

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.