2026년 4월 15일 | 프로젝트
profile_eval 출력이 길어지기 시작하면, 결국 다시 볼 query를 사람이 눈으로 골라야 하는 순간이 남는다. GraphRAG 쪽은 점수 축이 여러 개라서 이 마지막 한 칸이 은근히 거슬렸다. stable한 query가 대부분인데도, rank shift가 난 몇 개를 다시 손으로 추려야 하면 메모가 먼저 흐려진다.
그래서 이번에는 새 scoring profile을 하나 더 늘리기보다, 흔들린 query만 바로 다시 여는 판독면을 먼저 붙였다. 이름은 rank-shift focus report다. trace bundle을 남길 때 custom profile별로 실제로 움직인 query만 따로 모아 주고, 같은 자리에서 trace before / after / diff 경로까지 바로 따라가게 만들었다. 덕분에 summary를 읽고 난 뒤 다시 "무엇을 볼지"를 손으로 고르는 단계가 훨씬 짧아졌다.
1. 왜 여기서 한 번 더 막혔는가
직전 반복에서 이미 query별 trace JSON과 default 대비 diff JSON은 잘 남기고 있었다. 겉으로 보면 필요한 재료는 다 있는 셈이었다. 그런데 실제로는 그 재료가 너무 넓게 펼쳐져 있었다. query가 세 개일 때는 그냥 눈으로 봐도 되지만, 실제 문서셋으로 넘어가면 stable한 결과가 훨씬 많아질 가능성이 크다. 그때는 rank shift가 난 query만 빨리 좁혀 보는 면이 없으면, 다시 사람이 summary를 읽고 메모를 달고 파일명을 따라 들어가게 된다.
나는 요즘 GraphRAG를 검색 공식 자체를 계속 바꾸는 단계라기보다, 바뀐 결과를 재진입 가능한 실험 기록으로 남기는 단계로 보고 있다. 그래서 이번 작업의 핵심도 성능 향상보다 inspection queue를 코드 안으로 끌어오는 일에 가깝다. 흔들린 query만 따로 모아 두면, 다음 반복에서 다시 열어볼 입력 집합이 자동으로 생긴다.
- summary는 전체 profile 성격을 본다.
- rank-shift report는 다시 볼 query만 좁힌다.
- trace diff는 왜 흔들렸는지 근거 구조를 확인한다.
2. 이번에 실제로 붙인 것
변경은 단순하다. `profile_eval`이 trace bundle을 만들 때 이제는 `reports/{profile}-rank-shifts.json`도 같이 쓴다. 이 report 안에는 rank_shift_count, top1_changed_count, stable_top_k_break_count, rank_shift_query_labels가 먼저 들어가고, query 항목에는 원문 query, default/custom top title, moved_up/down, 그리고 trace before/after/diff 경로가 함께 들어간다.
여기에 더해 text summary 한 줄만 봐도 profile 성격이 드러나게 하려고 path delta와 coverage delta도 붙였다. edge만 올랐는지, path를 더 강하게 탔는지, coverage bonus를 얼마나 깎았는지를 한 줄에서 같이 읽고 싶었기 때문이다. top-1이 안 바뀌었다고 해서 변화가 없었던 것은 아니니, 이 정도 분해는 남겨 두는 편이 다음 조정에서 훨씬 덜 헷갈린다.
PYTHONPATH=src:. python -m graphrag_mvp.profile_eval --dataset data/sample_documents.json --queries-file data/sample_queries.json --profile configs/relation_weight_dense.json --profile configs/path_bridge_focus.json --top-k 3 --trace-dir traces/profile_eval_bundle_iter13
이 한 번의 실행으로 지금은 profile별 trace, default 대비 diff, rank-shift report, 텍스트 요약, JSON summary가 같이 남는다. 내가 원한 건 정확히 이런 묶음이었다. 산출물이 많아지는 게 목적이 아니라, 다음 행동으로 넘어가는 순서를 고정하는 게 목적이다.
3. 샘플에서 바로 읽힌 숫자
이번 샘플에서는 두 개의 custom profile을 같이 봤다. relation_weight_dense는 direct relation 기여를 조금 더 세게 읽는 안정형 기준선이고, path_bridge_focus는 coverage를 덜 보고 path/bridge 쪽을 더 세게 보는 실험용 기준선이다. 숫자를 놓고 보면 두 profile의 역할이 꽤 또렷하게 갈린다.
| profile | top-1 agreement | stable top-k | avg edge delta | avg path delta | avg coverage delta | rank shift | 내가 읽은 의미 |
|---|---|---|---|---|---|---|---|
| relation_weight_dense | 100% | 100% | +0.1500 | +0.1667 | -0.2500 | 0건 | 랭킹을 건드리지 않고 edge/path 기여를 조정하는 안정형 기준선 |
| path_bridge_focus | 67% | 67% | -0.5667 | +0.4833 | -1.0222 | 1건 | coverage를 포기하는 대신 path를 더 밀어 주며 실제 rank shift를 만드는 실험형 기준선 |
여기서 마음에 들었던 부분은 dense profile의 shift가 0건이라는 점과, 반대로 path_bridge_focus는 정확히 1건만 흔들었다는 점이다. 실험용 profile이 지나치게 시끄러우면 어디를 다시 봐야 할지 오히려 더 헷갈린다. 지금은 `partial-coverage` 하나만 또렷하게 튀어나왔기 때문에, report가 inspection queue 역할을 하기에 딱 좋다.
4. partial-coverage query에서 실제로 벌어진 일
이번 report가 비어 있지 않다는 걸 가장 잘 보여 주는 장면은 partial-coverage query다. default에서는 Incident resolution이 1위였고 coverage도 100%였다. 그런데 path_bridge_focus를 적용하면 Retrieval planner가 1위로 올라온다. coverage는 67%로 낮아졌지만 path bonus가 더 강하게 작동하면서 top-1이 뒤집힌 셈이다.
rank-shift report는 이 장면을 꽤 간결하게 요약한다. rank_shift_count는 1, 라벨은 partial-coverage 하나뿐이다. 그리고 query 항목 안에는 top1 score delta -0.9334, edge delta -0.7000, path delta +0.5000, coverage delta -1.0667가 같이 들어간다. 숫자만 봐도 이번 profile이 어떤 교환을 했는지 읽힌다. edge와 coverage는 줄었고, path는 늘었고, 그 결과 top-1이 바뀌었다.
- moved up: Retrieval planner가 3위에서 1위로 이동
- moved down: Incident resolution 두 청크가 1위와 2위에서 한 칸씩 밀림
- trace diff 경로까지 report 안에 같이 들어 있어 바로 deeper inspection으로 내려갈 수 있음
이런 report가 있으면 다음 행동이 단순해진다. 전체 summary를 다시 읽을 필요 없이, 흔들린 query 하나를 바로 열고 trace diff를 보면 된다. 결국 내가 원했던 건 더 많은 숫자가 아니라 다음에 무엇을 다시 봐야 하는지 자동으로 좁혀 주는 구조였다.
5. 이번 기준선이 남기는 것
이번 작업은 retrieval 품질을 직접 끌어올린 단계라고 말하기는 어렵다. 대신 튜닝 결과를 잃어버리지 않는 기준선은 분명히 하나 더 생겼다. GraphRAG처럼 vector, edge, path, coverage가 같이 섞이는 프로젝트에서는, 좋은 조정 하나를 찾는 일만큼 나쁜 조정이나 과한 조정을 빨리 알아보는 판독면도 중요하다. rank-shift focus report는 그쪽에 더 가깝다.
이번 변경 뒤에는 테스트 16개를 다시 돌려 통과를 확인했고, 구현 노트와 trace bundle, 시각 자료도 같이 남겨 뒀다. 다음 단계는 이제 더 분명하다. 샘플 문서셋을 넘어 실제 문서셋용 query 세트를 만들고, 이 report가 어느 정도로 시끄러운지부터 확인해 보는 일이다. 거기서도 신호가 너무 많지 않게 유지된다면, 그다음에는 query cluster나 failure type 단위로 묶는 레이어를 얹어볼 생각이다.
요즘 GraphRAG 쪽에서 덜 허무한 작업은 대체로 이런 쪽이다. 공식을 하나 더 얹는 일보다, 바뀐 결과를 다시 읽는 비용을 줄이는 구조를 붙일 때 다음 반복의 밀도가 더 또렷하게 올라간다.
'[AI 실험실] > [개인 프로젝트] GraphRAG' 카테고리의 다른 글
| GraphRAG | Query Cluster 집계 추가 (0) | 2026.04.16 |
|---|---|
| GraphRAG | Rank Shift Reason Labels (0) | 2026.04.15 |
| GraphRAG | Profile Eval Trace Bundle (0) | 2026.04.13 |
| GraphRAG Relation-Weighted Scoring (0) | 2026.04.05 |
| GraphRAG MVP 1차: 그래프 추출·하이브리드 검색 (0) | 2026.04.02 |