Field Log · Entry
Knowledge Graph RAG 기초: Entity·Relation·Path (1/10)
오늘의 결론
- Vector search는 의미가 비슷한 text를 찾고, graph search는 무엇이 무엇과 어떤 관계인지를 따라갑니다.
- Graph의 node와 edge는 원문에서 추출한 가설입니다. 반드시 source span, confidence, valid time을 함께 저장합니다.
Apple회사와 사과를 합치거나 같은 회사를 두 node로 나누는 entity resolution 오류가 관계 추론 전체를 망칩니다.- Graph RAG도 원문 chunk를 버리지 않습니다. Graph는 탐색과 집계를 돕고 최종 claim은 원문 evidence로 검증합니다.
- 단순한 사실 검색에 graph를 추가하면 비용과 오류만 늘 수 있습니다. 관계·multi-hop·corpus-wide 질문에서 이득을 검증합니다.
앞 글까지 기본 RAG와 Agent harness를 완성했습니다. 이제 다음 질문을 생각해 봅시다.
“프로젝트 Atlas의 공급업체 중
지난 분기에 보안 사고가 있었고
현재 계약 갱신 대상인 회사는 어디인가?”
정답에 필요한 문장이 한 chunk에 모두 들어 있을 가능성은 낮습니다.
문서 A: Atlas는 Nova Systems의 API를 사용한다.
문서 B: Nova Systems는 2026년 4월 보안 사고를 공지했다.
문서 C: 계약 C-91은 Nova Systems와 연결되며 8월 갱신 예정이다.
Vector search는 질문과 비슷한 chunk를 각각 찾을 수 있습니다. 하지만 Atlas → Nova Systems → 사고, Nova Systems → 계약 C-91 → 갱신이라는 연결을 명시적으로 다루려면 graph가 유용합니다.
그림 1. Graph는 원문을 대체하지 않는다. Query에서 seed entity를 찾고 relation path를 좁힌 뒤 각 edge의 source span을 다시 context로 가져온다.
Vector Search와 Graph Search의 질문이 다르다
Vector Search
Query와 chunk를 embedding vector로 바꾼 뒤 가까운 항목을 찾습니다.
query: “Nova의 보안 이슈”
→ 의미가 비슷한 chunk top-k
잘하는 것:
- 표현이 달라도 같은 주제 찾기
- 긴 문서에서 관련 문단 후보 찾기
- 구조가 없는 corpus를 빠르게 검색
- 새 문서를 비교적 싸게 추가
어려운 것:
- 정확히 몇 hop 떨어진 관계인지 구분
- 동일 이름 entity 분리
- 여러 문서의 관계를 명시적으로 결합
- 전체 corpus의 관계 집계
Graph Search
Entity와 relation을 node·edge로 표현하고 path나 neighborhood를 찾습니다.
(Atlas)-[:USES_VENDOR]->(Nova Systems)
(Nova Systems)-[:REPORTED]->(Incident-2026-04)
(Contract-C91)-[:WITH_VENDOR]->(Nova Systems)
(Contract-C91)-[:RENEWS_AT]->(2026-08-01)
잘하는 것:
- 특정 entity 주변 관계 확장
- multi-hop path 찾기
- relation type과 방향 검사
- 연결 구조와 community 집계
- source가 다른 claim 연결
어려운 것:
- 자연어에서 정확한 node·edge 추출
- entity 중복·동명이인 해결
- graph가 커질 때 탐색 폭발 방지
- 추출 오류가 path 전체에 전파
둘은 경쟁자가 아니라 보완재입니다.
vector: seed와 관련 원문을 찾는다
graph: 관계 후보와 탐색 범위를 좁힌다
reranker: query에 유용한 path·chunk를 정렬한다
generator: provenance가 있는 evidence로 답한다
Knowledge Graph의 네 가지 기본 요소
1. Entity
식별 가능한 대상입니다.
Person · Organization · Product · Project
Contract · Incident · Regulation · Location
좋은 entity에는 stable ID와 type이 있습니다.
{
"entity_id": "org:nova-systems",
"canonical_name": "Nova Systems",
"type": "ORGANIZATION",
"aliases": ["Nova", "노바 시스템즈"]
}
문서 안에서 한 번 등장한 모든 명사를 node로 만들 필요는 없습니다. Query와 업무 rule에 필요한 entity type부터 시작합니다.
2. Relation
두 entity가 어떻게 연결되는지 나타냅니다.
Project --USES_VENDOR--> Organization
Contract --WITH_VENDOR--> Organization
Organization --REPORTED--> Incident
Relation은 방향과 type이 중요합니다.
(A)-[:SUPPLIES_TO]->(B)
≠
(B)-[:SUPPLIES_TO]->(A)
3. Claim
원문이 주장하는 사실 단위입니다. Relation 하나로 압축하기 어려운 조건, 시간, 부정, 수치가 포함됩니다.
{
"claim_id": "claim:884",
"subject": "org:nova-systems",
"predicate": "REPORTED_SECURITY_INCIDENT",
"object": "incident:2026-04-nova",
"qualifiers": {
"reported_at": "2026-04-18",
"severity": "high",
"status": "resolved"
}
}
4. Provenance
이 node·edge·claim을 어느 source의 어느 span에서 만들었는지 기록합니다.
{
"source_id": "notice:nova-2026-04",
"revision": "3",
"chunk_id": "chunk:218",
"char_span": [432, 517],
"extractor_version": "kg-extract@5",
"confidence": 0.91,
"recorded_at": "2026-07-16T03:21:00Z"
}
Provenance가 없으면 edge가 틀렸을 때 확인할 원문이 없고 source가 삭제·수정돼도 파생 graph를 갱신할 수 없습니다.
Triple과 Property Graph
Knowledge Graph를 표현하는 대표적인 두 관점이 있습니다.
RDF Triple
W3C RDF는 statement를 subject-predicate-object triple로 표현합니다.
<project:atlas> <usesVendor> <org:nova>
<org:nova> <reported> <incident:nova-2026-04>
Global identifier와 vocabulary를 이용해 서로 다른 data source를 연결하기 좋습니다. RDF 1.2는 triple의 subject나 object 자리에 triple term을 사용할 수 있는 방향도 정의해 statement 자체에 provenance 같은 정보를 연결할 수 있게 합니다.
Labeled Property Graph
Node와 edge에 label과 property를 직접 붙입니다.
(:Project {id: "atlas"})
-[:USES_VENDOR {valid_from: "2025-01-01"}]->
(:Organization {id: "nova"})
Application graph와 traversal query를 직관적으로 구현하기 좋습니다.
| 관점 | RDF | Property Graph |
|---|---|---|
| 기본 단위 | triple | labeled node·edge |
| 식별 | IRI 중심 | implementation ID/property |
| schema | vocabulary·ontology | label·constraint |
| query 예 | SPARQL | Cypher/GQL 계열 |
| 적합성 | data integration·semantic web | application traversal |
처음부터 어느 DB를 살지 결정하기보다 query pattern, update 방식, provenance 요구를 먼저 적습니다.
Ontology와 Schema를 작게 시작하기
Ontology는 domain에서 어떤 type과 relation을 허용하는지 정의합니다.
entities:
Project: [id, name]
Organization: [id, canonical_name]
Contract: [id, status, renewal_date]
Incident: [id, category, occurred_at]
relations:
USES_VENDOR:
from: Project
to: Organization
WITH_VENDOR:
from: Contract
to: Organization
REPORTED:
from: Organization
to: Incident
나쁜 시작:
모든 명사와 동사를 자유롭게 추출
→ relation type 수천 개
→ 동의어 edge 중복
→ query와 evaluation 불가능
좋은 시작:
- 실제 질문 50~100개를 모은다.
- 질문에 필요한 entity와 relation을 표시한다.
- 작은 schema와 unknown bucket을 만든다.
- extraction precision을 사람이 검토한다.
- coverage가 부족할 때만 schema를 확장한다.
Entity Extraction과 Entity Resolution은 다르다
Entity Extraction
문장에서 mention을 찾습니다.
“Nova는 Atlas에 결제 API를 공급한다.”
mentions: Nova, Atlas, 결제 API
Entity Resolution
Mention이 어떤 canonical entity인지 결정합니다.
Nova
Nova Systems
노바 시스템즈
org-1042
→ 같은 entity인가?
그리고 다음은 다른 entity일 수 있습니다.
Apple Inc. organization
apple fruit
Jordan Lee employee A
Jordan Lee customer B
Resolution에 사용할 signal:
- exact identifier와 registry ID
- type·tenant·namespace
- alias table
- 주변 relation과 co-occurring entity
- document metadata
- embedding similarity
- valid time
Embedding similarity만으로 merge하지 않습니다. Merge는 split보다 되돌리기 어려우므로 애매하면 별도 node로 유지하고 후보 link를 둘 수 있습니다.
{
"mention": "Nova",
"candidates": [
{"entity_id": "org:nova-systems", "score": 0.93},
{"entity_id": "product:nova-db", "score": 0.61}
],
"decision": "org:nova-systems",
"resolution_rule": "tenant_registry_exact_alias@3"
}
Relation Extraction은 Source Claim이다
LLM이 relation을 출력했다고 사실이 되는 것은 아닙니다.
원문: “Nova가 Atlas의 공급사라는 보도는 사실이 아니라고 회사는 밝혔다.”
잘못된 추출: (Nova)-[:SUPPLIES]->(Atlas)
부정, 추측, 인용 주체, 시간 범위를 보존해야 합니다.
{
"subject": "org:nova-systems",
"predicate": "SUPPLIES_TO",
"object": "project:atlas",
"polarity": "NEGATED",
"modality": "ASSERTED_BY_SOURCE",
"valid_from": null,
"valid_to": null,
"source_span": "span:778"
}
고위험 domain에서는 LLM extraction 뒤 deterministic rule, registry lookup, human review를 조합합니다.
시간과 상태를 Edge에 포함하기
관계는 영원하지 않습니다.
Nova --SUPPLIES_TO--> Atlas
valid_from: 2025-01-01
valid_to: 2026-06-30
status: terminated
현재 공급업체 질문은 relation type뿐 아니라 valid time을 검사해야 합니다.
26편의 memory처럼 두 시간을 분리하면 late-arriving data를 다룰 수 있습니다.
valid time: 현실에서 관계가 유효했던 기간
recorded time: system이 relation을 알게 된 시점
Graph Construction Pipeline
source ingest
→ text unit + metadata
→ entity mention extraction
→ canonical entity resolution
→ relation/claim extraction
→ schema validation
→ provenance binding
→ deduplication and conflict handling
→ graph store + vector indexes
각 stage의 output을 versioned artifact로 남깁니다.
{
"graph_snapshot": "kg@20260716-02",
"corpus_snapshot": "corpus@20260716-01",
"ontology": "vendor-risk@4",
"extractor": "kg-extract@5",
"resolver": "entity-resolve@3",
"embedding": "embed-model@2"
}
Extractor를 바꿨는데 graph snapshot version을 유지하면 실험을 재현할 수 없습니다.
Query-Time Graph RAG
1. Query Understanding
질문: “Atlas의 갱신 대상 공급업체 중 보안 사고가 있었던 곳”
entities: project:atlas
constraints: contract.renewal=upcoming, incident.exists=true
required path: Project→Organization←Contract and Organization→Incident
2. Seed Retrieval
Exact lookup, alias, lexical, embedding을 조합해 시작 node를 찾습니다.
3. Bounded Traversal
무제한 BFS를 하지 않습니다.
max_hops: 3
allowed_relations:
- USES_VENDOR
- WITH_VENDOR
- REPORTED
- RENEWS_AT
max_neighbors_per_node: 20
valid_at: 2026-07-16
min_edge_confidence: 0.80
4. Path Scoring
Path 길이만 짧다고 좋은 것은 아닙니다.
score(path) =
query relevance
× relation compatibility
× edge confidence
× source authority
× temporal validity
× diversity bonus
× length penalty
실제 구현에서는 곱셈의 underflow와 한 요소의 과도한 영향 때문에 log score나 weighted sum을 쓸 수 있습니다. 중요한 것은 각 factor를 별도로 관측하는 것입니다.
5. Evidence Hydration
선택한 path의 edge만 prompt에 넣지 않습니다. 각 edge가 연결된 원문 span과 주변 context를 다시 가져옵니다.
path candidate
→ edge provenance
→ source revision 확인
→ original text spans
→ rerank and deduplicate
→ evidence ledger
6. Grounded Generation
Claim별로 source를 붙이고 path가 연결되지 않으면 답을 유보합니다.
간단한 Bounded Traversal 예시
from collections import deque
def bounded_paths(graph, start, allowed_relations, max_hops=3, max_paths=50):
queue = deque([(start, [])])
results = []
while queue and len(results) < max_paths:
node, path = queue.popleft()
if path:
results.append(path)
if len(path) == max_hops:
continue
edges = graph.out_edges(node)
edges = [
edge for edge in edges
if edge.type in allowed_relations
and edge.confidence >= 0.80
and edge.is_valid_at("2026-07-16")
]
for edge in sorted(edges, key=lambda e: e.confidence, reverse=True)[:20]:
if edge.target in {start, *(e.source for e in path)}:
continue
queue.append((edge.target, [*path, edge]))
return results
Production에서는 query-aware relation score, ACL, tenant filter, node degree cap, cycle detection을 추가합니다.
ACL은 Traversal 전에 적용하기
Graph는 연결을 통해 민감한 관계를 추론할 수 있습니다.
공개 node A
→ 비공개 project B
→ 공개 vendor C
결과에서 B만 숨겨도 A와 C의 연결 자체가 정보를 누출할 수 있습니다.
- node·edge·source에 tenant와 ACL을 둡니다.
- traversal expansion 전에 authorization을 검사합니다.
- unauthorized node를 통과하는 path도 금지합니다.
- community summary가 private claim을 섞지 않았는지 lineage를 유지합니다.
- cache key에 principal·tenant·policy version을 포함합니다.
Graph RAG가 유리할 가능성이 높은 질문
관계 중심
“A와 B가 어떤 계약·프로젝트를 통해 연결되는가?”
Multi-hop
“이 부품을 공급하는 회사의 모회사가 받은 제재는?”
Corpus-wide 집계
“문서 전체에서 반복되는 핵심 조직과 갈등 구조는?”
Entity-centric 탐색
“Nova와 관련된 계약, 사고, 담당자, 제품을 보여 줘.”
Explainable Path
“왜 이 업체를 고위험으로 분류했는지 연결 경로를 보여 줘.”
Graph RAG가 불필요할 가능성이 높은 질문
- 한 문단에 답이 있는 FAQ
- 정확한 문서 번호 lookup
- corpus가 작고 관계 질문이 거의 없음
- entity schema를 안정적으로 정의할 수 없음
- 문서 변경이 너무 빨라 graph rebuild 비용이 큼
- latency budget이 매우 짧음
Baseline vector RAG와 비교해 이득을 측정하기 전에는 graph를 기본 route로 만들지 않습니다.
Graph Quality를 먼저 평가하기
Entity Extraction
- mention precision·recall
- type accuracy
- boundary accuracy
Entity Resolution
- pairwise precision·recall
- cluster B-cubed precision·recall
- false merge rate
- false split rate
Relation과 Claim
- relation type precision·recall
- direction accuracy
- negation·modality accuracy
- qualifier·time accuracy
- provenance span correctness
Retrieval
- answer-supporting path Recall@k
- path precision
- hop accuracy
- source coverage
- graph-only vs vector-only vs hybrid
End-to-End
- answer correctness·faithfulness
- unsupported path rate
- latency·indexing cost
- token cost per successful answer
GraphRAG-Bench 같은 최근 연구도 graph construction, retrieval, generation을 분리해 언제 graph가 vanilla RAG보다 이득인지 평가해야 한다는 문제를 다룹니다. 특정 benchmark 결과를 우리 domain의 보장으로 보지 말고 같은 분해 방식을 가져옵니다.
최소 실험 Matrix
A BM25 + dense + reranker
B A + entity metadata filter
C A + one-hop graph expansion
D A + bounded multi-hop graph
E D + source hydration + path reranker
질문 유형도 분리합니다.
single-fact · entity-centric · multi-hop · global-summary · no-answer
Graph가 single-fact에서는 비용만 늘고 multi-hop에서만 좋아질 수 있습니다. 전체 평균 하나로 보면 이 trade-off가 사라집니다.
흔한 실패 패턴
1. 모든 명사와 동사를 Graph로 변환
Schema가 폭발하고 같은 relation이 여러 이름으로 생깁니다.
처방: 실제 query에서 시작한 작은 ontology를 만듭니다.
2. Entity Extraction만 하고 Resolution 생략
같은 entity가 여러 node로 갈라져 path가 끊깁니다.
처방: canonical ID, alias, type, context 기반 resolution을 별도 stage로 둡니다.
3. LLM Edge를 사실로 저장
부정·추측·시간 조건을 잃습니다.
처방: claim qualifier와 source provenance를 유지합니다.
4. Graph Edge만 Prompt에 넣기
추출 오류를 검증할 원문이 없습니다.
처방: path에서 source span을 다시 hydrate합니다.
5. Hop 수만 늘리기
High-degree node에서 후보가 폭발하고 약한 edge가 섞입니다.
처방: relation allowlist, confidence, degree, time, ACL budget을 적용합니다.
6. Graph가 항상 Vector보다 낫다고 가정
단순 질문에서 indexing·latency·오류 비용만 늘 수 있습니다.
처방: 질문 유형별 vector-only baseline과 ablation을 유지합니다.
Production 체크리스트
- Graph가 필요한 relation·multi-hop 질문을 실제 log에서 확인했다.
- entity type과 relation type을 작은 ontology로 정의했다.
- entity mention과 canonical entity를 분리했다.
- merge·split 가능한 entity resolution record가 있다.
- relation에 direction, negation, modality, valid time을 보존한다.
- 모든 node·edge·claim이 source span으로 돌아간다.
- corpus·ontology·extractor·resolver·graph snapshot을 versioning한다.
- query seed를 exact·lexical·vector로 찾는다.
- traversal에 hop·degree·relation·confidence·time budget이 있다.
- ACL을 path 결과가 아니라 expansion 전에 검사한다.
- 선택 path의 원문을 evidence ledger로 다시 hydrate한다.
- graph construction·retrieval·generation을 따로 평가한다.
- vector-only, graph-only, hybrid ablation이 있다.
스스로 확인하기
- Vector similarity와 relation traversal이 각각 답하는 질문은 무엇인가?
- Entity extraction과 entity resolution은 어떻게 다른가?
- Edge에 provenance와 valid time이 필요한 이유는 무엇인가?
- Unauthorized node를 최종 결과에서만 숨기면 왜 부족한가?
- 현재 corpus에서 graph가 이득일 질문 10개를 실제로 적을 수 있는가?
다음 글에서는 Microsoft GraphRAG 논문과 공식 구현을 바탕으로 entity·relation 추출, Leiden community, community report, local·global·DRIFT search가 어떻게 다른지 해부합니다.