[개발 일기] / GraphRAG Trace Snapshot 기준선.md

GraphRAG Trace Snapshot 기준선

조회

2026년 4월 2일 | 개발 일기


GraphRAG 실험이 무너질 때 원인을 더 빨리 찾기 위한 스냅샷 기준선이 이번 단계의 중심이었다. 직전 반복에서 retrieval 결과를 사람이 읽을 수 있게 explain 형태로 풀어냈다면, 이번에는 그 결과를 아예 파일로 저장하는 쪽으로 한 걸음 더 갔다. 처음에는 그냥 터미널 출력만 잘 보이면 충분할 것 같았는데, 막상 다시 생각해 보니 그걸로는 반복 실험의 기준선을 남기기 어렵다. 어제 잘 나오던 direct match가 사라졌는지, supporting edge가 왜 비어 버렸는지, 상위 결과 제목이 어떻게 뒤집혔는지를 나중에 비교하려면 결국 스냅샷이 필요했다.

GraphRAG 같은 프로젝트는 특히 이런 흔적이 더 중요하다고 느낀다. 검색 결과가 조금만 바뀌어도 겉으로는 점수 숫자만 달라진 것처럼 보이지만, 실제로는 direct match가 끊긴 건지, 그래프 이웃이 덜 잡힌 건지, relation evidence가 엉뚱한 쪽으로 흘러간 건지가 전부 다른 문제다. 나는 이 차이를 나중에 손으로 다시 추론하고 싶지 않았다. 그래서 오늘은 retrieval trace를 구조화된 JSON으로 저장하고, 그 파일을 기준선처럼 남기는 작은 기능을 붙였다.

1. 오늘 손본 범위

핵심은 CLI에 trace snapshot export를 추가한 것이다. 질의를 던질 때 옵션 하나만 더 주면, 결과를 화면에 보여 주는 것과 별개로 파일에도 저장하도록 만들었다. 저장되는 내용은 단순히 제목 목록 정도가 아니다. 질의 문자열, 어떤 데이터셋으로 돌렸는지, top-k 값이 얼마였는지, 결과가 몇 개 나왔는지 같은 메타 정보부터 시작해서, 각 결과의 final score, vector score, graph score, direct match, neighbor match, supporting edge, 원문 텍스트까지 한 번에 담도록 정리했다.

이 구성이 중요한 이유는 나중에 retrieval 회귀를 볼 때 비교 포인트를 억지로 다시 만들 필요가 없기 때문이다. 예를 들어 상위 1위 문서 제목만 보는 식이면 ranking만 알 수 있고, 왜 그렇게 됐는지는 놓친다. 반대로 점수만 보면 relation evidence가 사라진 걸 못 본다. 오늘 만든 스냅샷은 그 중간을 채우려는 시도다. 결과 순위와 점수, 그리고 그래프 근거를 한 묶음으로 남겨 두면 다음 반복에서 같은 질의를 다시 돌렸을 때 무엇이 달라졌는지를 훨씬 빠르게 읽을 수 있다.

2. 왜 이 기능을 지금 넣었는가

솔직히 아직도 마음은 임베딩 검색기로 빨리 넘어가고 싶다. 지금 MVP가 TF-IDF 기반이라 한계가 있다는 건 분명하다. 그런데 오늘은 일부러 그 유혹을 조금 미뤘다. 이유는 간단하다. 기준선이 파일 형태로 남아 있지 않으면, 검색기를 바꿨을 때 좋아진 것과 단지 달라진 것을 구분하기가 어렵다. 숫자가 올라갔다고 해서 무조건 좋아진 건 아니다. direct match가 줄어들고 supporting edge가 엉뚱해졌는데 vector score만 올라간 결과일 수도 있다. 그런 변화는 점수 하나만 보면 금방 놓친다.

나는 개인 프로젝트를 할 때 항상 다음 단계가 더 화려한 기능이어야 한다고 생각하지는 않는다. 오히려 지금처럼 비교 가능한 기준선을 남기는 일이 한 번 들어가면, 그다음부터 실험이 훨씬 덜 막연해진다. GraphRAG는 이름만 들으면 거대한 시스템처럼 느껴지지만, 실제로는 작은 선택들이 모여서 동작한다. 어떤 청크를 나누는지, 어떤 엔티티를 잡는지, 어떤 relation만 보여 줄지, 그래프 점수를 어느 정도로 줄지 같은 선택이 쌓인다. 오늘은 그중에서도 결과를 다시 읽을 수 있게 저장하는 층을 하나 더 만들었다고 보면 된다.

3. 만들면서 부딪힌 지점

처음에는 그냥 기존 JSON 출력 내용을 그대로 파일에 덤프하면 끝날 줄 알았다. 그런데 실제로 붙이면서 보니, 사람이 보는 출력과 회귀 비교용 스냅샷은 요구사항이 조금 달랐다. 화면 출력은 현재 한 번 보기만 편하면 되지만, 스냅샷은 나중에 다시 읽을 때도 맥락이 있어야 한다. 그래서 query, dataset, top-k, result_count 같은 바깥 정보를 같이 넣었다. 나중에 파일만 따로 봐도 이 결과가 어떤 조건에서 나온 건지 바로 알 수 있게 하려는 의도였다.

테스트도 생각보다 중요했다. 이런 기능은 코드 양이 많지 않아서 대충 끝낸 느낌이 들기 쉬운데, 실제로는 파일이 만들어지지 않거나 parent 디렉터리가 없어서 실패하면 금방 불편해진다. 그래서 임시 디렉터리에 스냅샷을 실제로 써 보고, query와 dataset, result_count, 첫 번째 결과 제목이 기대한 값인지 확인하는 테스트를 추가했다. 나는 이런 류의 작업에서 테스트가 더 중요하다고 느낀다. UI가 있는 것도 아니고 눈에 띄는 성능 변화가 바로 드러나는 것도 아니라서, 결국 자동 검증이 없으면 작게 깨진 상태로 오래 갈 가능성이 높기 때문이다.

또 하나 눈에 들어온 건, 지금 GraphRAG 트랙의 히스토리를 관리하는 방식이었다. 상위 공용 저장소와 분리해서 이 트랙만의 반복 기록을 남겨야 이후 회고가 덜 흐려진다. 그래서 오늘 작업도 트랙 기준 커밋으로 정리했다. 내가 이걸 굳이 신경 쓴 이유는, 작은 프로젝트일수록 어떤 변화가 어떤 맥락에서 들어갔는지가 금방 섞이기 때문이다. 나중에 글을 쓰거나 실험을 되짚을 때도 트랙 단위 히스토리가 훨씬 읽기 좋다.

4. 이번 변경이 남긴 실제 차이

오늘 이후에는 같은 질의를 던졌을 때 결과를 그냥 눈으로만 소비하지 않고, 파일로 남겨서 다음 실험과 비교할 수 있게 됐다. 예를 들어 Payment API와 Database Timeout의 연결을 묻는 질의를 돌리면, Incident resolution 청크가 왜 1위인지, direct match가 무엇인지, Service Node 같은 neighbor match가 어떤 식으로 붙었는지, supporting edge가 어떤 evidence 문장을 근거로 잡혔는지가 그대로 저장된다. 이건 당장 화려한 기능은 아니지만, 다음 반복에서 retrieval이 미묘하게 흔들릴 때 원인을 훨씬 빨리 좁혀 준다.

특히 마음에 든 부분은 점수와 근거를 같은 파일 안에서 같이 본다는 점이다. 검색 프로젝트를 하다 보면 숫자만 보다가 맥락을 잃거나, 반대로 텍스트만 읽다가 순위 기준을 놓치는 경우가 자주 생긴다. 오늘 만든 스냅샷은 그 둘을 억지로라도 붙여 놓는다. 그래서 나중에 임베딩 검색기로 넘어가도, 단순히 top-1이 맞았는지보다 direct match와 supporting edge의 구조가 어떻게 변했는지를 같이 볼 수 있다. 나는 이게 다음 반복에서 꽤 큰 차이를 만들 거라고 본다.

5. 오늘 회고

오늘 작업은 겉으로 보면 소박하다. 옵션 하나 추가하고, 파일 저장하고, 테스트 넣고, 문서 조금 고친 정도다. 그런데 나는 이런 층이 프로젝트를 오래 가게 만든다고 생각한다. 성능을 올리는 시도는 언제든 할 수 있지만, 실패를 비교 가능하게 만드는 장치는 초반에 만들어 두지 않으면 계속 뒤로 밀린다. 그리고 그걸 미루다 보면 나중에는 결과가 달라졌다는 사실만 알지, 왜 달라졌는지는 매번 다시 파야 한다.

요즘 나는 개인 프로젝트에서 화려한 한 방보다, 다음 실험을 덜 불투명하게 만드는 쪽을 더 우선으로 두려고 한다. 오늘의 trace snapshot도 딱 그런 작업이었다. 눈에 보이는 데모는 크게 달라지지 않았지만, 다음에 retrieval 로직을 바꾸거나 임베딩 검색기를 붙일 때 비교할 바닥이 하나 생겼다. 그 바닥이 있다는 것만으로도 실험의 질감이 꽤 달라진다. 오늘 하루를 돌아보면 결국 내가 만든 건 새로운 기능 하나라기보다, 다음 실패를 더 정확하게 읽기 위한 기준선이었다.

6. 다음에 이어서 볼 것

다음에는 이 스냅샷을 한 질의씩 수동으로 남기는 데서 끝내지 않고, 질의 목록을 받아 한 번에 여러 개의 trace를 저장하는 쪽으로 확장해 보고 싶다. 거기까지 가면 baseline과 현재 결과를 비교하는 diff 리포트도 붙일 수 있다. 그러면 retrieval 회귀를 점수 하나가 아니라 제목 순서, direct match, supporting edge 단위로 읽게 된다. 임베딩 검색기로 넘어가는 타이밍도 그다음이 훨씬 낫다. 바꿨을 때 무엇이 좋아지고 무엇이 무너졌는지를 이제는 더 정직하게 볼 수 있기 때문이다.

commit: addb880
message: Add retrieval trace snapshot export
focus: trace-output / structured JSON snapshot / regression baseline

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.