[AI 실험실]/[개인 프로젝트] GraphRAG / GraphRAG | History mismatch 리포트 추가.md

GraphRAG | History mismatch 리포트 추가

조회

시리즈: GraphRAG 구축기 #5

이전: 4편 | 목록 | 다음: 6편

2026년 4월 27일 | 프로젝트


history-baseline-mismatches.json 파일 하나를 따로 만들고 나니, GraphRAG 실험 history를 읽는 순서가 조금 더 안전해졌다. 지난번에는 query set이나 quality gate 기준선이 달라진 run을 history delta 계산에서 빼는 guard를 붙였다. 그때도 `history_warning=baseline-mismatch(...)`가 보이기는 했지만, 막상 다음에 다시 열어 보려면 어떤 profile에서 어떤 과거 run이 빠졌는지를 다시 summary 안에서 찾아야 했다. 이번에는 그 빠진 run 자체를 별도 리포트로 모으는 쪽을 먼저 붙였다.

나는 GraphRAG 프로젝트에서 점수 하나를 더 올리는 일보다, 비교 가능한 기준선을 잃지 않는 일이 점점 더 중요해진다고 느낀다. edge score, path score, coverage score가 함께 움직이면 결과가 좋아진 것처럼 보여도 실제로는 질문 세트가 바뀐 효과일 수 있다. 그래서 이번 작업의 목적은 새 scoring profile을 만드는 게 아니라, 비교에서 제외된 과거 run을 숨기지 않고 inspection queue로 남기는 것이었다.

1. warning 한 줄로는 부족했던 지점

Iteration 20에서 붙인 baseline guard는 나름 유용했다. 현재 run과 과거 history entry를 비교할 때 `query_set_fingerprint`나 `quality_gate_fingerprint`가 다르면 그 entry를 delta 계산에서 제외했다. 덕분에 `WARN -> PASS`처럼 좋아 보이는 transition이 사실은 다른 시험지를 놓고 비교한 결과였던 상황을 막을 수 있었다.

다만 warning은 어디까지나 화면 위의 신호였다. profile이 네 개, history entry가 여러 개로 늘어나면 `baseline-mismatch(query_set)`이라는 문장만 보고 바로 다음 행동이 정해지지는 않는다. 내가 알고 싶은 것은 조금 더 구체적이었다. 어느 profile에서 빠졌는지, 빠진 entry의 label이 무엇인지, 이유가 query set인지 gate인지, 그리고 같은 문제가 여러 profile에 반복되는지까지 한 번에 보고 싶었다.

  • compatible run은 기존처럼 status transition과 metric delta 계산에 사용한다.
  • incompatible run은 delta 계산에서는 빼되, 별도 JSON report에 남긴다.
  • reason count를 따로 세어 query set 변경인지 gate 변경인지 먼저 구분한다.
GraphRAG history mismatch report flow

Figure 1. compatible run은 history delta로 보내고, baseline이 다른 run은 mismatch queue로 따로 빼는 흐름.

2. 실제로 붙인 report 구조

코드에서는 `profile_eval` 안에 `_build_history_mismatch_report`와 `_attach_history_mismatch_report`를 추가했다. `--history-file`과 `--trace-dir`가 함께 들어온 실행에서만 report를 만든다. 콘솔로만 빠르게 확인하는 실행에는 불필요한 파일을 늘리지 않고, trace bundle을 남기는 실험에서는 나중에 다시 열 수 있는 JSON을 같이 저장하는 방식이다.

report의 최상단에는 현재 dataset, top_k, query_count, query set fingerprint, gate fingerprint가 들어간다. 그 아래에는 `profile_entry_count`와 `reason_counts`가 붙는다. 마지막으로 profile별 section에는 profile 이름, compatible run count, incompatible run count, warning type, 그리고 빠진 history entry 목록이 들어간다. 구조를 일부러 얇게 잡은 이유는 간단하다. 이 파일은 분석 본문이 아니라 어디를 다시 열지 정하는 인덱스에 가깝기 때문이다.

필드 의미 이번 probe 값
profile_entry_count profile-entry 단위 mismatch 개수 4
reason_counts.query_set 질의 세트 fingerprint 차이로 제외된 횟수 4
compatible_run_count 현재 delta 계산에 실제로 들어간 run 수 profile별 4

이렇게 나누고 나니 summary를 읽을 때 마음이 편해졌다. history header는 여전히 compatible run만 보고 `PASS -> PASS`, `WARN -> WARN`, `HARD-FAIL -> HARD-FAIL`을 계산한다. 동시에 빠진 run은 report에 남아 있어서, 나중에 왜 제외됐는지 다시 추적할 수 있다. 둘을 섞지 않는 게 이번 변경의 핵심이다.

3. 일부러 섞은 probe로 확인한 장면

이번에는 실제 샘플 history를 그대로 두지 않고, probe용으로 `iter18` 하나의 query set fingerprint를 일부러 다르게 바꿨다. 이름도 `iter18-query-set-probe`로 바꿔 두었다. 그런 다음 `iter21`을 append하면서 같은 profile 네 개를 다시 돌렸다. 의도한 결과는 간단했다. 네 profile 모두 현재 기준선과 비교할 때 그 probe entry를 compatible history에서 제외해야 한다.

결과는 예상대로 나왔다. `default`, `relation_weight_dense`, `path_bridge_probe`, `path_bridge_focus` 모두 `baseline-mismatch(query_set)`을 띄웠고, report에는 `reason_counts.query_set=4`가 남았다. 이 숫자가 마음에 든 이유는 단순하다. query set이 바뀐 문제는 특정 profile 하나의 문제가 아니라 같은 history entry를 공유하는 모든 profile의 비교 가능성 문제라는 점이 바로 보인다.

GraphRAG history mismatch report counts

Figure 2. query set mismatch probe에서 네 profile 모두 같은 incompatible entry를 감지한 결과.

여기서 중요한 건 현재 metric 자체가 흔들린 게 아니라는 점이다. `relation_weight_dense`는 여전히 PASS이고, `path_bridge_probe`는 WARN, `path_bridge_focus`는 HARD-FAIL이다. 다만 그 상태 변화량을 계산할 때 다른 query set의 과거 run을 끌어오지 않았고, 제외된 run이 무엇인지는 파일로 남겼다. 이 작은 분리가 없으면, 나중에 history가 길어졌을 때 좋아진 것과 기준선이 바뀐 것을 헷갈리기 쉽다.

4. 테스트와 커밋 메모

이번 변경은 retrieval 결과 자체를 바꾸지 않아야 해서 테스트를 먼저 꽤 좁게 잡았다. 기존 pipeline과 profile_eval 테스트를 전부 다시 돌렸고, 새 테스트에서는 임시 history 파일에 query set mismatch entry를 넣은 뒤 `trace_dir`를 켠 상태로 report가 실제 생성되는지 확인했다. report 안의 `profile_entry_count`, `reason_counts`, profile 이름, 빠진 history label까지 같이 검사했다.

commit: 6bc1cdb
message: Add GraphRAG history mismatch report
tests: 27 passed
report: history-baseline-mismatches.json
probe: reason_counts.query_set = 4

문서도 같이 정리했다. README의 Current focus를 history baseline guard에서 history mismatch report로 바꾸고, iteration 21 노트에는 왜 warning 한 줄에서 report 파일로 옮겼는지 적었다. 이번 변경은 기능 이름만 보면 작지만, 내 입장에서는 history를 더 오래 쌓기 위한 안전장치에 가깝다.

5. 다음으로 이어갈 것

다음에는 mismatch report를 조금 더 읽기 좋게 나눠 보고 싶다. 지금은 profile 중심으로 접었지만, 실제로는 query set 변경과 gate 변경을 reason별 section으로 먼저 나누는 편이 빠를 수도 있다. 특히 gate threshold를 조정한 날에는 `quality_gate` reason만 따로 묶고, query 파일을 바꾼 날에는 `query_set` reason만 따로 묶으면 된다.

또 하나 보고 싶은 건 failure scope 변화량과 mismatch report를 같은 inspection queue 안에서 연결하는 것이다. 어떤 profile이 WARN에서 HARD-FAIL로 내려갔는데 동시에 query set mismatch가 있었다면, 그 profile의 trace diff를 바로 믿기보다 기준선 변경부터 확인해야 한다. 반대로 compatible history만으로 failure scope가 늘었다면, 그때는 trace bundle을 바로 열어도 된다. 결국 목표는 하나다. 좋아 보이는 변화와 실제로 비교 가능한 변화를 분리해서 읽는 것. 이번 report는 그쪽으로 가는 작은 받침대가 됐다.

시리즈: GraphRAG 구축기 #5

이전: 4편 | 목록 | 다음: 6편

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.