RAG에 넓은 질문을 던지면, 왜 답변이 빠지는 걸까?
"전체 정리해줘"에 RAG가 답을 못 한다
CRAG를 붙이고, 리랭크도 2단계로 바꾸고 나서 RAG 검색 품질이 꽤 올라왔거든요. "물류서버 IP가 뭐야?" 같은 좁은 질문에는 정확한 답이 나왔습니다.
그런데 "올해 인프라 이슈 전체 정리해줘" 같은 넓은 질문을 던지니까 상황이 달라지더라고요. 답변이 나오긴 하는데, 빠진 게 많았습니다. 분명히 인증서 갱신 건도 있었고, 서버 교체 건도 있었는데, RAG가 가져온 건 그중 일부뿐이었어요.
top-K가 좁은 질문에만 맞는 구조였다
원인은 top-K 검색의 구조적 한계였습니다. 현재 RAG 파이프라인은 질문을 임베딩하고, 벡터 유사도 상위 20건을 가져오고, 리랭크해서 10건을 LLM에 넘기는 방식이거든요.
좁은 질문에서는 이게 잘 동작합니다. "물류서버 IP"를 물으면 관련 문서가 2~3건이니까, 상위 20건 안에 다 들어와요. 리랭크가 걸러주면 정확한 답이 나옵니다.
넓은 질문은 다릅니다. "올해 인프라 이슈 전체"에 해당하는 문서가 200건이라고 하면, top-K 20건으로는 10%밖에 못 가져오는 거예요. 나머지 180건은 검색 자체가 안 됩니다. 리랭크를 아무리 정교하게 해도, 후보에 안 올라온 문서는 살릴 수가 없거든요.
TOP_K를 200으로 올리면 되지 않냐고 할 수 있는데, 그러면 리랭크 시간이 폭발하고 LLM에 전달하는 토큰도 감당이 안 됩니다. 이전에 TOP_K 100에서 20으로 줄인 이유가 바로 이거였거든요.

개별 문서가 아니라 "덩어리"를 검색하면 된다
top-K를 올릴 수 없으면, 검색 대상 자체를 바꿔야 합니다. 여기서 나온 아이디어가 커뮤니티 탐지였어요.
그래프를 보면, 관련된 문서들은 같은 엔티티를 공유하면서 뭉쳐 있거든요. 인증서 갱신 관련 이메일 15건은 "인증서", "갱신", "물류서버" 같은 노드를 공유하면서 하나의 클러스터를 이루고 있고, 서버 교체 관련 이메일 20건은 또 다른 클러스터를 이루고 있어요.
이 클러스터를 미리 찾아서, 각 클러스터의 내용을 요약해두면 어떨까. "인증서 갱신 클러스터 → 요약 1장", "서버 교체 클러스터 → 요약 1장"으로 압축해두면, 넓은 질문이 왔을 때 개별 문서 200건이 아니라 요약 20~30장을 검색하면 되는 거예요.
Microsoft GraphRAG 논문에서 이걸 "커뮤니티 요약"이라고 부르더라고요. 논문이 제시하는 흐름이 딱 이 문제의 해답이었습니다. 엔티티/관계 그래프에서 밀접하게 연결된 노드 클러스터를 자동으로 발견하고, 각 클러스터를 LLM으로 요약하는 방식이에요.
Leiden 알고리즘으로 클러스터를 찾는다
클러스터를 "자동으로" 발견하는 부분이 커뮤니티 탐지인데, 여기서 쓰는 알고리즘이 Leiden이에요.
Leiden은 그래프에서 밀접하게 연결된 노드 그룹을 찾아주는 알고리즘입니다. 비유하자면, 회사에서 "누가 누구랑 자주 일하는지"를 메일 기록으로 분석하면 팀 단위의 덩어리가 자동으로 보이는 것과 비슷해요. 직접 "이 사람은 물류팀" 하고 태깅하지 않아도, 연결 패턴만 보면 팀이 드러나거든요.
Neo4j에서는 GDS 플러그인에 Leiden 알고리즘이 내장되어 있습니다. GDS가 이미 설치되어 있으면 Cypher 쿼리로 바로 돌릴 수 있어요.
핵심 파라미터는 resolution인데, 이 값에 따라 클러스터의 크기가 달라집니다. resolution이 높으면 작은 덩어리가 많이 나오고, 낮으면 큰 덩어리가 적게 나와요. 이 값을 얼마로 잡느냐에 따라 요약의 세밀함이 달라지기 때문에, 데이터를 보면서 조절해야 하는 부분이에요.

계층으로 쌓으면 줌인/줌아웃이 된다
Leiden의 또 다른 장점은 계층 구조를 만들 수 있다는 점이에요. 작은 클러스터를 먼저 찾고, 그 클러스터끼리 다시 묶어서 더 큰 클러스터를 만드는 방식이거든요.
예를 들면 이런 식입니다. Level 0에서 "인증서 갱신 관련 15건"이 하나의 클러스터, "서버 교체 관련 20건"이 또 하나의 클러스터로 잡혀요. Level 1에서는 이 둘이 합쳐져서 "물류시스템 운영 전반"이라는 더 큰 클러스터가 됩니다. Level 2에서는 "인프라 운영 전반"으로 다시 묶이고요.
이러면 질문의 범위에 따라 검색 레벨을 바꿀 수 있어요. "인증서 갱신 누가 했어?"는 Level 0 요약에서 답을 찾고, "올해 인프라 이슈 정리해줘"는 Level 2 요약에서 답을 찾는 거죠.
모든 문서가 반드시 어떤 커뮤니티에 속하기 때문에 데이터 누락이 없습니다. top-K에서 빠졌던 180건도, 커뮤니티 요약 안에는 다 반영돼 있는 거예요.
파이프라인에서 어디에 들어가는가
커뮤니티 탐지는 엔티티 추출과 그래프 임포트가 끝난 뒤에 실행됩니다. 그래프 구조(노드 간 연결)를 기반으로 클러스터링하는 거니까, 노드와 관계가 다 들어간 상태에서만 의미가 있거든요.
전처리 → 엔티티 추출 → 그래프 임포트 → [커뮤니티 탐지 + 요약] → RAG 검색기존 파이프라인을 건드리지 않고, 그래프 임포트와 RAG 검색 사이에 한 단계를 추가하는 구조예요. 탐지된 커뮤니티 요약은 Neo4j에 별도 노드로 저장하고, Qdrant에도 임베딩으로 넣어서 벡터 검색도 가능하게 합니다.
top-K의 한계를 구조로 우회한 셈이다
정리하면, 좁은 질문에서는 top-K + 리랭크 + CRAG로 충분합니다. 관련 문서가 소수이기 때문에 상위 20건 안에 다 들어오거든요.
넓은 질문에서는 개별 문서를 검색하는 방식 자체가 한계가 있어요. 200건을 다 가져올 수 없으니까요. 대신 그래프에서 밀접한 노드끼리 클러스터를 만들고, 클러스터 요약을 검색하면 20~30장의 요약으로 전체 데이터를 커버할 수 있습니다.
커뮤니티 요약을 만드는 과정과, 넓은 질문에서 실제로 어떻게 검색하는지는 다음 글에서 다루겠습니다.