[개발 일기] / GraphRAG Explainable Retrieval Trace.md

GraphRAG Explainable Retrieval Trace

조회

2026년 4월 2일 | 개발 일기


GraphRAG 검색 결과를 더 읽을 수 있게 만드는 쪽이 이번 작업의 중심이었다. 지금까지는 hybrid retrieval이 돌아가면 final score, vector score, graph score 정도만 숫자로 확인할 수 있었다. 겉으로 보면 그럴듯한데, 막상 어떤 청크가 왜 위로 올라왔는지 바로 설명하려고 하면 손이 멈췄다. 벡터 점수가 먹힌 건지, 질의 엔티티와 직접 겹친 건지, 아니면 그래프 이웃 때문에 추가 점수를 받은 건지 분해가 잘 안 됐다. 이 상태로 다음 단계 실험을 계속 밀면, 나중에는 결과를 보고도 어디를 손봐야 하는지 애매해질 것 같았다.

그래서 이번에는 retrieval 결과 자체에 설명 흔적을 남기는 쪽으로 방향을 틀었다. 최근 로컬 git 로그 기준으로 정리하면 이번 작업은 Add explainable retrieval trace to GraphRAG MVP 한 줄로 압축된다. 사실 기능 이름은 거창하지만 내가 진짜 원했던 건 단순했다. 점수가 왜 나왔는지를 사람 눈으로 바로 읽을 수 있어야 한다는 것, 그리고 그 설명이 테스트로도 붙잡혀 있어야 한다는 것. 나는 GraphRAG류 프로젝트에서 이 구간이 꽤 중요하다고 본다. 초반에는 성능 최고점을 만드는 것보다, 검색 기준선이 어떻게 동작하는지 해석 가능한 형태로 남기는 편이 훨씬 다음 실험에 도움이 된다.

1. 오늘 무엇을 바꿨는가

핵심 변경은 검색 결과 데이터 구조CLI 출력 두 군데였다. 먼저 retrieval result 안에 단순 점수만 넣던 구조를 바꿔서, 질의와 직접 맞닿은 엔티티와 그래프 이웃으로만 연결된 엔티티를 따로 담도록 했다. direct match, neighbor match를 분리해 두면 같은 graph score 1.5라도 결이 다르다는 걸 바로 알 수 있다. 질의 엔티티가 청크 안에 직접 들어 있는 경우와, 그래프를 한 번 타고 들어가서 연결된 엔티티가 잡힌 경우는 디버깅 포인트가 다르기 때문이다.

두 번째는 supporting edge를 결과에 같이 실어 주는 쪽이었다. 이전에는 상위 청크 텍스트만 보고 내가 머릿속으로 관계를 다시 해석해야 했다. 이제는 어떤 relation이 실제 근거로 걸렸는지를 같이 보여 주게 바꿨다. 예를 들어 Payment API 질의를 던졌을 때 Incident Report가 connects로 붙었는지, Service Node가 uses 관계로 걸렸는지 한 번에 보인다. 말 그대로 검색 결과가 작은 trace를 스스로 들고 나오게 만든 셈이다.

마지막으로 CLI에 text explain 모드를 붙였다. JSON은 기계가 다루기에는 좋은데, 내가 터미널에서 바로 읽기에는 생각보다 눈에 잘 안 들어온다. 그래서 final, vector, graph 점수를 한 줄에서 같이 보여 주고, direct match와 neighbor match를 바로 아래에 붙인 다음, supporting edge와 evidence 문장을 이어서 보게 했다. 지금 단계에서는 화려한 리포트보다 이런 얇은 텍스트 출력이 훨씬 실용적이었다. 질의 하나 던지고 상위 결과 한두 개만 보면, 점수와 관계 근거를 같이 읽을 수 있다.

2. 왜 이걸 먼저 했는지

솔직히 처음에는 임베딩 검색기로 빨리 넘어가고 싶은 마음이 더 컸다. 지금 MVP는 TF-IDF 기반이라 의미 유사도 측면에서는 한계가 분명하다. 그래도 오늘은 일부러 그쪽으로 가지 않았다. 이유는 단순하다. 지금 검색기 품질이 아쉬운 건 맞지만, 품질을 올리기 전에 현재 기준선이 왜 그렇게 움직이는지 설명할 수 있어야 다음 교체가 의미를 갖는다. 설명이 없는 상태에서 임베딩 검색기로 넘어가면 점수가 좋아졌는지, 데이터가 운 좋게 맞았는지, 그래프 점수가 실제로 기여했는지 구분하기가 어려워진다.

GraphRAG는 이름 때문에 자꾸 큰 시스템처럼 느껴지는데, 막상 손으로 만들다 보면 결국 작은 판단의 연속이다. 어떤 청크를 자를지, 어떤 엔티티를 잡을지, 어떤 관계를 edge로 볼지, 그래프 점수를 얼마나 줄지, 상위 결과를 어떤 기준으로 읽을지 같은 결정이 하나씩 쌓인다. 오늘 작업은 그중에서도 검색 결과를 읽는 방식을 먼저 정리한 셈이다. 나는 이 단계를 건너뛰면 나중에 성능 실험이 쌓일수록 오히려 해석이 더 어려워질 거라고 봤다.

3. 손보면서 드러난 문제

재미있었던 건, explain 모드를 붙이는 과정에서 기존 실행 경로의 허술한 부분도 같이 드러났다는 점이다. 처음 테스트를 다시 돌려 보니 import 경로가 애매해서 바로 깨졌다. 패키지 구조는 src 레이아웃인데, 문서와 테스트 쪽에서 실행 경로 가정이 조금씩 어긋나 있었다. 평소에는 손으로 실행하면서 그냥 넘어갈 수도 있는 부분인데, 설명 출력을 고정된 형태로 검증하려고 하니 이런 경계가 더 빨리 드러났다.

이 문제를 보면서 다시 느낀 건, 작은 프로젝트일수록 실행 경로를 대충 두면 나중에 디버깅 비용이 더 커진다는 점이다. 그래서 이번에 README 쪽 실행 예시도 같이 맞췄고, 테스트도 retrieval 결과만 보는 수준에서 한 단계 더 나아가 explain 텍스트 안에 어떤 문구가 실제로 들어가야 하는지까지 확인하도록 바꿨다. direct match가 Payment API만 잡히는 줄 알았는데 실제로는 Database Timeout도 같이 걸려서 기대값을 조정한 것도 그 과정에서 나온 수정이었다. 이런 차이를 지금 잡아 두는 게 중요하다. 나중에 retrieval 로직을 바꾸면 설명 문자열이 달라질 수 있는데, 그 변화가 의도된 건지 아닌지 빨리 감지할 수 있기 때문이다.

또 하나는 supporting edge를 어떤 기준으로 고를지였다. 처음에는 청크 안의 relation을 전부 붙이는 쪽도 생각했는데, 그렇게 하면 explain 출력이 금방 시끄러워진다. 반대로 너무 엄격하게 줄이면 정작 왜 graph score가 붙었는지 보이지 않는다. 이번 버전에서는 질의 엔티티와 닿아 있고, 동시에 청크 안의 매칭 엔티티와 관련 있는 edge만 고르도록 정리했다. 아직 완벽한 기준은 아니지만, 적어도 지금 단계에서는 너무 많은 정보를 쏟지 않으면서 검색 근거를 따라가기에 괜찮은 절충안이었다.

4. 오늘 커밋이 남긴 실제 변화

이번 커밋 이후에는 같은 질의를 던져도 결과를 읽는 감각이 꽤 달라졌다. 예전에는 상위 청크 텍스트와 점수 숫자만 보고 내가 추론을 덧붙여야 했다면, 이제는 직접 맞은 엔티티, 그래프 이웃으로 들어온 엔티티, 근거 relation이 한 묶음으로 나온다. 이 변화가 당장 검색 품질을 올려 주는 건 아니다. 대신 다음 실험을 훨씬 덜 막막하게 만든다. 임베딩 검색기로 바꿨을 때 vector score가 얼마나 달라졌는지, relation 추출기를 교체했을 때 neighbor match가 안정되는지, 특정 질의에서 supporting edge가 사라지는지를 비교할 수 있게 됐다.

나는 개인 프로젝트를 할 때 이런 종류의 변화가 꽤 중요하다고 느낀다. 코드 줄 수가 많이 늘었는가보다, 다음 실험을 더 정확하게 실패시킬 수 있느냐가 더 큰 기준이 된다. 지금 GraphRAG는 아직 규칙 기반 추출기에 TF-IDF 검색을 얹은 정도라 완성형과는 거리가 멀다. 그래도 이번처럼 설명 가능한 retrieval trace를 넣어 두면, 적어도 다음에 뭔가가 나빠졌을 때 어디가 무너졌는지 훨씬 빨리 찾을 수 있다.

5. 다음 단계는 어디를 볼지

다음에는 두 갈래를 이어서 보려고 한다. 하나는 임베딩 기반 검색기를 붙여서 지금 explain 출력과 비교하는 일이다. vector score가 바뀌면 상위 결과 순위가 어떻게 흔들리는지, graph score가 도움이 되는 질의와 거의 의미가 없는 질의가 어떻게 갈리는지 보고 싶다. 다른 하나는 explain 결과 자체를 질의별 스냅샷처럼 저장하는 방향이다. 그러면 retrieval 회귀를 단순 정확도 숫자만이 아니라, 근거 구조 단위로도 비교할 수 있다.

결국 오늘 한 일은 화려한 기능 추가라기보다 기준선을 더 잘 읽게 만드는 작업에 가깝다. 그런데 이런 층이 없으면 GraphRAG는 금방 검은 상자처럼 느껴진다. 나는 그걸 최대한 늦추고 싶다. 적어도 지금 단계에서는, 왜 이 청크가 올라왔고 어떤 연결이 점수에 기여했는지를 내가 직접 따라갈 수 있어야 한다. 오늘 커밋은 딱 그걸 위해 만들었다.

6. 짧게 남겨 두는 실행 메모

이번 작업을 마치고 남긴 메모는 세 가지다. 첫째, 검색 기준선은 성능보다 먼저 읽을 수 있어야 한다. 둘째, explain 출력은 사람이 보기 좋은 문자열로 끝내지 말고 테스트로 묶어 둬야 한다. 셋째, GraphRAG 초반에는 모델 교체보다 비교 가능한 trace를 남기는 쪽이 훨씬 큰 진전이 된다. 당분간은 이 원칙을 기준으로 다음 반복을 계속 밀어 볼 생각이다.

commit: 1eef189
message: Add explainable retrieval trace to GraphRAG MVP
focus: direct_matches / neighbor_matches / supporting_edges / text explain mode

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.