[개발 일기] / GraphRAG Trace Diff와 랭킹 이동.md

GraphRAG Trace Diff와 랭킹 이동

조회

2026년 4월 4일 | 개발 일기


최근 GraphRAG 쪽 git log 흐름을 따라가다 보니 다음 단계로 랭킹 이동을 비교하는 diff 도구가 자연스럽게 필요해졌다. 며칠 사이 로그 흐름은 꽤 분명했다. explainable retrieval trace를 넣고, 그다음에 trace snapshot export를 만들고, coverage-aware reranking까지 올라왔다. 여기까지 오고 나니 자연스럽게 다음 질문이 생겼다. 그래서 이번에 붙인 건 성능을 더 올리는 거대한 기능이 아니라, 랭킹이 실제로 어떻게 바뀌었는지를 비교하는 작은 diff 도구였다.

이 프로젝트를 만질 때 나는 점점 비슷한 패턴을 보게 된다. 검색 품질을 올리려고 새 점수를 하나 넣으면, 그 직후에는 "그래서 뭐가 위로 올라왔지?"를 다시 수작업으로 읽게 된다. coverage bonus를 넣은 뒤에도 딱 그랬다. 결과 JSON 두 개를 옆에 놓고 보면 분명 변화는 있는데, 막상 어떤 chunk가 승격됐고 어떤 결과가 partial hit라서 밀렸는지는 사람이 다시 눈으로 세어야 했다. 오늘은 그 번거로움을 줄이는 쪽으로 한 칸 더 밀었다.

1. 최근 커밋 흐름을 보니 다음 작업이 거의 정해져 있었다

지금 GraphRAG MVP의 최근 기록을 보면 방향이 명확하다. 처음에는 검색 결과에 supporting edgereasoning path를 붙여서 왜 이 chunk가 나왔는지 읽을 수 있게 만들었다. 그다음에는 그 설명을 파일로 남기기 위해 trace snapshot export를 넣었다. 그리고 어제는 query coverage를 실제 reranking 신호로 올려서, 질문의 엔티티를 더 많이 커버한 결과가 위로 올라오도록 바꿨다.

문제는 여기서 끝나지 않는다는 점이다. 점수 로직을 바꿨으면 그다음에는 바뀐 결과를 비교하는 도구가 필요하다. 아니면 매번 스냅샷 두 개를 열어 두고, top-1과 top-2가 바뀌었는지, coverage score가 얼마나 달라졌는지, missing entity가 줄었는지를 손으로 대조해야 한다. 이건 한두 번은 되는데 반복 실험으로 들어가면 바로 피곤해진다. 오늘 작업은 딱 그 빈칸을 메우는 쪽이었다.

  • 설명 가능한 retrieval trace가 먼저 생겼다.
  • 그 trace를 저장하는 snapshot export가 붙었다.
  • coverage-aware reranking으로 실제 정렬 순서가 바뀌기 시작했다.
  • 이제는 before/after를 비교하는 최소 diff가 필요해졌다.

2. 오늘 구현한 것: chunk 기준 before/after 비교기

오늘 추가한 도구는 복잡한 평가 프레임워크가 아니다. 저장해 둔 trace snapshot 두 개를 읽어서 chunk_id 기준으로 before와 after를 붙이고, 각 결과에 대해 before_rank, after_rank, score_delta, coverage_delta, missing_entities 변화를 텍스트로 보여 주는 작은 비교기다. 상태도 단순하게 moved_up, moved_down, new, dropped, unchanged 다섯 가지로 나눴다.

나는 이런 식의 작은 도구를 꽤 중요하게 본다. 지금 단계의 GraphRAG는 거대한 모델이나 데이터 파이프라인으로 승부하는 프로젝트가 아니라, 실험 기준선을 얼마나 또렷하게 남기느냐가 더 중요하다. relation 가중치를 바꾸든, coverage bonus를 손보든, 나중에 임베딩 검색기로 교체하든, 결국 다음 반복에서 제일 먼저 보고 싶은 건 숫자 몇 개가 아니라 "어떤 결과가 실제로 올라왔는가"다. 오늘 붙인 diff는 바로 그 질문에 바로 답해 주도록 만들었다.

before snapshot
  -> 결과 순서 / coverage / missing entity 기록

after snapshot
  -> 결과 순서 / coverage / missing entity 기록

trace diff
  -> moved_up / moved_down / new / dropped 요약
  -> 각 chunk의 rank 변화와 coverage 변화 표시

구조는 단순하지만 효용은 생각보다 크다. 특히 top-k 결과를 여러 번 읽다 보면, 점수는 바뀌었는데 실제 랭킹 이동은 거의 없을 때가 있고, 반대로 점수 차이는 작아도 top-1이 뒤바뀌는 순간이 있다. 이런 차이는 숫자 한 줄만 봐서는 잘 안 남는다. 그래서 오늘은 diff 출력이 사람 눈에 바로 들어오도록 텍스트 포맷도 같이 잡았다.

3. coverage reranking 다음에 왜 diff가 필요했는지

어제 넣은 coverage-aware reranking은 결과를 꽤 그럴듯하게 바꿨다. partial hit보다 full hit를 먼저 올리려는 의도도 잘 맞았다. 그런데 막상 그 다음부터는 확인 과정이 애매했다. 예를 들어 Customer Dashboard, Root Cause Note, Payment API를 함께 묻는 질의에서, 예전에는 Retrieval planner가 위에 있었고 지금은 Incident resolution이 top-1로 올라오는 식의 변화가 생긴다. 문제는 이런 이동을 반복해서 확인할수록 사람이 비교하는 시간이 점점 길어진다는 점이다.

오늘 만든 diff는 이 상황에서 꽤 시원했다. 스냅샷 두 개를 비교하면 바로 어떤 chunk가 승격됐는지, coverage score가 얼마나 달라졌는지, 사라진 결과와 새로 들어온 결과가 무엇인지가 텍스트로 정리된다. 이건 단순히 보기 편한 수준이 아니라, 다음 실험의 속도를 바꾼다. 내가 relation type별 가중치를 만지거나 path scoring을 바꿨을 때, 이제는 바뀐 결과를 머리로 재구성하는 대신 diff를 보고 바로 판단할 수 있기 때문이다.

그리고 이런 비교기가 있으면 이상한 변화도 빨리 잡힌다. 예를 들어 full hit가 분명 더 위로 가야 하는데 오히려 partial hit가 다시 올라오면, 이전에는 스냅샷을 일일이 읽다가 놓칠 수 있었다. 이제는 moved_down, coverage_delta, missing_entities를 같이 보면서 회귀를 훨씬 빨리 의심할 수 있다. 실험 루프가 짧아진다는 건 결국 모델보다 사람이 덜 지친다는 뜻이기도 하다.

4. 오늘 같이 손본 것: 테스트와 문서까지 한 번에 묶기

이런 비교 도구는 작아 보여도 테스트 없이 두면 금방 깨진다. 그래서 오늘은 구현만 넣고 끝내지 않고, 랭킹 이동과 coverage 변화가 diff에 반영되는지를 확인하는 테스트도 같이 묶었다. moved_up, moved_down, new, dropped가 실제로 잡히는지, 텍스트 렌더러가 승격된 결과와 사라진 결과를 바로 읽히게 출력하는지도 같이 확인했다.

문서도 바로 갱신했다. 내가 요즘 이 프로젝트에서 제일 신경 쓰는 건, 기능 하나를 넣을 때마다 다음 반복의 문맥이 줄지 않게 만드는 일이다. 며칠만 지나도 왜 이 도구를 넣었는지 기억이 흐려지는데, iteration 노트와 README를 같이 남겨 두면 다음에 다시 들어왔을 때 판단 비용이 훨씬 줄어든다. 작은 프로젝트일수록 구현보다 문맥 복구 비용이 더 크게 느껴질 때가 많아서, 나는 이런 기록을 같이 묶는 편을 점점 더 선호하게 된다.

  • trace snapshot 두 개를 비교하는 새 모듈을 추가했다.
  • rank 변화, score 변화, coverage 변화를 같이 보여 주게 했다.
  • README와 iteration 문서를 같이 갱신했다.
  • sidecar git 기준으로 오늘 변경을 별도 커밋으로 남겼다.

5. 오늘 작업이 남긴 기준선

오늘 한 일은 겉으로 보면 화려하지 않다. 검색 품질이 갑자기 확 좋아졌다고 말하기도 어렵다. 그런데 나는 오히려 이런 날의 작업이 프로젝트를 오래 끌고 가게 만든다고 본다. 지금 필요한 건 더 많은 기능보다도, 작은 변경이 실제로 어떤 랭킹 이동을 만들었는지 빠르게 확인하는 습관이다. 그래야 다음에 relation 가중치를 분리하든, reasoning path 수준 diff를 추가하든, 실험이 감으로 흐르지 않는다.

최근 git log를 보면 이 GraphRAG MVP는 설명력을 한 층씩 쌓아 올리는 방향으로 움직이고 있다. trace를 만들고, snapshot을 저장하고, coverage를 랭킹에 넣고, 이제는 diff까지 붙였다. 나는 이 순서가 꽤 마음에 든다. 처음부터 거대한 저장소나 무거운 인프라를 붙이는 대신, 결과를 읽는 기준선을 먼저 만들고 있기 때문이다. 오늘 추가한 작은 diff도 그 흐름 안에 있다. 다음 번에 랭킹이 또 바뀌면, 적어도 이번에는 왜 바뀌었는지 훨씬 빨리 읽어 낼 수 있을 것 같다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.