HyperGraphRAG
HyperGraphRAG는 기존 지식 그래프의 이진 관계(엔티티-관계-엔티티) 한계를 넘어, 하이퍼그래프 구조로 다자 관계를 표현하는 RAG 아키텍처입니다. 하나의 하이퍼엣지가 여러 엔티티를 동시에 연결하여, 복합 관계를 자연스럽게 표현합니다.
핵심 아이디어
일반 지식 그래프는 두 엔티티 간의 관계만 표현할 수 있습니다(삼중항: subject-predicate-object). 그러나 현실 세계의 정보는 종종 3개 이상의 엔티티가 동시에 참여하는 관계를 포함합니다.
일반 그래프의 한계:
- “논문 X는 저자 A, B가 작성”을 표현하려면 (논문X, 저자, A)와 (논문X, 저자, B)로 분리해야 함
- 분리된 삼중항은 “A와 B가 함께 작성했다”는 공동 관계를 명시적으로 표현하지 못함
- 복합 이벤트를 하나의 관계로 표현 불가
- 관계의 원자성이 깨지며, 검색 시 관련 엔티티 누락 위험
HyperGraphRAG의 해결:
- 하이퍼엣지 하나가 관련된 모든 엔티티를 동시에 연결
- 복합 관계를 분해 없이 원래 구조 그대로 저장
- 하나의 엔티티를 검색하면 해당 하이퍼엣지의 모든 관련 엔티티를 함께 반환
- 정보 손실 없이 다자 관계의 맥락을 완전히 보존
동작 방식
Graph vs HyperGraph
| 항목 | 일반 Graph | HyperGraph |
|---|
| 엣지 연결 | 2개 노드 (이진 관계) | N개 노드 (다자 관계) |
| 관계 표현 | subject-predicate-object | 엔티티 집합 + 관계 설명 |
| 복합 이벤트 | 여러 삼중항으로 분해 필요 | 하나의 하이퍼엣지로 표현 |
| 검색 방식 | 엔티티 → 이웃 노드 탐색 | 엔티티 → 포함된 하이퍼엣지 → 관련 엔티티 전체 |
| 정보 손실 | 관계 분해 시 맥락 손실 가능 | 원래 관계 구조 보존 |
| 표현력 | 제한적 (쌍방 관계만) | 풍부 (N-ary 관계 지원) |
LangGraph 구현
상태 정의
from typing import TypedDict, List
class HyperGraphState(TypedDict):
question: str
matched_entities: List[str]
hyperedges: List[dict]
context: str
answer: str
하이퍼그래프 구조
# 하이퍼그래프: 하이퍼엣지 ID → 엔티티 집합 + 관계 설명
# 실제 구현에서는 DB에 저장하며, 여기서는 dict로 시뮬레이션
HYPERGRAPH = {
"he_001": {
"entities": ["Transformer", "Vaswani", "Google Brain", "Attention Mechanism", "NeurIPS 2017"],
"description": "Vaswani et al.이 Google Brain에서 Attention Mechanism 기반의 Transformer를 제안하여 NeurIPS 2017에 발표",
},
"he_002": {
"entities": ["BERT", "Devlin", "Google AI", "Transformer", "사전학습"],
"description": "Devlin et al.이 Google AI에서 Transformer 기반의 BERT 사전학습 모델을 제안",
},
"he_003": {
"entities": ["GPT-3", "OpenAI", "Few-shot Learning", "Transformer", "언어 모델"],
"description": "OpenAI가 Transformer 기반의 GPT-3 대규모 언어 모델로 Few-shot Learning 능력을 입증",
},
}
노드 함수
쿼리 엔티티 추출
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = init_chat_model("gpt-4o-mini", temperature=0)
def extract_query_entities(state: HyperGraphState) -> HyperGraphState:
"""질문에서 핵심 엔티티를 추출합니다."""
question = state["question"]
prompt = ChatPromptTemplate.from_messages([
("system", (
"질문에서 핵심 엔티티(인물, 조직, 기술, 개념 등)를 추출하세요.\n"
"쉼표로 구분하여 출력하세요."
)),
("human", "{question}"),
])
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"question": question})
entities = [e.strip() for e in result.split(",") if e.strip()]
return {"matched_entities": entities}
하이퍼엣지 탐색
def find_hyperedges(state: HyperGraphState) -> HyperGraphState:
"""매칭된 엔티티를 포함하는 하이퍼엣지를 탐색합니다."""
matched_entities = set(state["matched_entities"])
relevant_edges = []
for edge_id, edge_data in HYPERGRAPH.items():
# 쿼리 엔티티와 하이퍼엣지의 엔티티가 겹치면 관련 있음
overlap = matched_entities & set(edge_data["entities"])
if overlap:
relevant_edges.append({
"id": edge_id,
"entities": edge_data["entities"],
"description": edge_data["description"],
"overlap": list(overlap),
})
return {"hyperedges": relevant_edges}
컨텍스트 조립
def expand_context(state: HyperGraphState) -> HyperGraphState:
"""하이퍼엣지의 모든 엔티티와 관계를 컨텍스트로 조립합니다."""
hyperedges = state["hyperedges"]
if not hyperedges:
return {"context": "관련 정보를 찾을 수 없습니다."}
context_parts = []
all_entities = set()
for edge in hyperedges:
all_entities.update(edge["entities"])
desc = edge["description"]
ents = ", ".join(edge["entities"])
context_parts.append(f"[관계] {desc}\n 관련 엔티티: {ents}")
context = f"발견된 엔티티: {', '.join(all_entities)}\n\n" + "\n\n".join(context_parts)
return {"context": context}
답변 생성
def generate(state: HyperGraphState) -> HyperGraphState:
"""하이퍼그래프 컨텍스트를 기반으로 답변을 생성합니다."""
question = state["question"]
context = state["context"]
prompt = ChatPromptTemplate.from_messages([
("system", (
"다음은 하이퍼그래프에서 검색한 엔티티 관계 정보입니다.\n"
"이 정보를 참고하여 질문에 답변하세요.\n\n{context}"
)),
("human", "{question}"),
])
chain = prompt | llm | StrOutputParser()
answer = chain.invoke({"context": context, "question": question})
return {"answer": answer}
그래프 구성
from langgraph.graph import StateGraph, START, END
workflow = StateGraph(HyperGraphState)
# 노드 추가
workflow.add_node("extract_query_entities", extract_query_entities)
workflow.add_node("find_hyperedges", find_hyperedges)
workflow.add_node("expand_context", expand_context)
workflow.add_node("generate", generate)
# 엣지 연결
workflow.add_edge(START, "extract_query_entities")
workflow.add_edge("extract_query_entities", "find_hyperedges")
workflow.add_edge("find_hyperedges", "expand_context")
workflow.add_edge("expand_context", "generate")
workflow.add_edge("generate", END)
# 컴파일
app = workflow.compile()
result = app.invoke({
"question": "Transformer 아키텍처가 후속 연구에 미친 영향과 관련 연구자들은?"
})
print(result["answer"])
GraphRAG vs HyperGraphRAG
| 항목 | GraphRAG | HyperGraphRAG |
|---|
| 그래프 구조 | 일반 그래프 (이진 엣지) | 하이퍼그래프 (N-ary 하이퍼엣지) |
| 관계 단위 | 삼중항 (A-관계-B) | 엔티티 집합 + 관계 설명 |
| 복합 관계 표현 | 여러 엣지로 분해 | 하나의 하이퍼엣지로 표현 |
| 검색 결과 | 이웃 노드 순회 | 하이퍼엣지 단위로 관련 엔티티 전체 반환 |
| 커뮤니티 탐지 | Leiden 등 그래프 알고리즘 | 하이퍼그래프 커뮤니티 탐지 |
| 구현 복잡도 | 중간 (Neo4j 등 활용) | 높음 (하이퍼그래프 전용 처리 필요) |
| 적합한 도메인 | 이진 관계 중심 데이터 | 다자 관계가 핵심인 데이터 |
위 구현에서는 하이퍼그래프를 Python dict로 시뮬레이션했습니다. 실제 프로덕션 환경에서는 하이퍼그래프 전용 데이터베이스나 관계형 DB에 하이퍼엣지 테이블을 구성하여 대규모 데이터를 효율적으로 관리합니다.
하이퍼그래프의 구축 과정에서 LLM이 다자 관계를 정확히 추출하는 것이 핵심입니다. 일반 그래프 대비 관계 추출의 난이도가 높으며, 추출 품질이 검색 성능에 직접적인 영향을 미칩니다.
적용 시나리오
논문-저자-기관-학회-주제 간의 복합 관계를 하이퍼엣지로 표현하여, “특정 주제에 대해 어떤 기관의 연구자들이 협력했는가?”와 같은 질문에 효과적으로 답변합니다.
회의, 프로젝트, 거래 등 여러 참여자와 속성이 동시에 관여하는 이벤트를 분석할 때, 이벤트의 전체 맥락을 하나의 하이퍼엣지로 보존합니다.
약물-질병-유전자-부작용 간의 다자 관계를 표현하여, 복합적인 의학적 질문에 누락 없이 답변합니다.
참고 논문
| 논문 | 학회 | 링크 |
|---|
| HyperGraphRAG: Hypergraph-Driven Retrieval-Augmented Generation (Feng et al., 2025) | - | arXiv 2503.21322 |