[AI 실험실]/[개인 프로젝트] GraphRAG / GraphRAG | History stale status queue 추가.md

GraphRAG | History stale status queue 추가

조회

시리즈: GraphRAG 구축기 #10

이전: 9편 | 목록 | 다음: 11편

2026년 5월 1일 | 개인 프로젝트 GraphRAG


History status streak를 붙이고 나니, 숫자는 보이는데 다음 행동은 아직 손으로 골라야 했다. `PASSx3`, `WARNx3`, `HARD-FAILx3`가 한 줄에 같이 나오면 처음에는 꽤 시원해 보인다. 그런데 막상 다음 실험을 잡으려고 보면, 나는 다시 summary를 훑으면서 “그래서 어떤 profile부터 열어야 하지?”를 묻고 있었다. 이건 지표가 부족하다기보다, 지표를 작업 큐로 접는 단계가 하나 빠진 상태에 가까웠다.

이번 GraphRAG 반복에서는 그 빈칸을 작게 메웠다. `profile_eval`에 history stale status queue를 추가해서, 같은 non-pass 상태가 일정 횟수 이상 반복된 profile만 별도 JSON report로 뽑게 했다. 말은 조금 길지만 의도는 단순하다. `WARN`이 여러 번 이어진 profile은 관찰 후보로 남기고, `HARD-FAIL`이 여러 번 이어진 profile은 다음 튜닝 후보에서 더 빨리 제외할 수 있게 만드는 것이다.

GraphRAG history stale status queue 흐름도
status streak를 바로 query trace로 열지 않고, 먼저 non-pass stale queue로 한 번 줄이는 흐름.

status_streak만으로는 조금 덜 닫힌 느낌

이전 반복에서는 `latest_status_streak_count`를 붙였다. 예를 들어 `path_bridge_probe`가 `WARNx2`, `path_bridge_focus`가 `HARD-FAILx2`처럼 보이게 만든 것이다. 이 값은 생각보다 유용했다. `WARN->WARN`이라는 transition만 보면 변화가 없다고 느끼기 쉬운데, 옆에 `WARNx2`가 붙으면 “이건 방금 warning으로 내려온 게 아니라, 같은 상태가 반복되고 있구나”를 바로 알 수 있다.

그런데 여기서 한 번 더 막혔다. summary 상단과 profile header가 길어질수록, 나는 여전히 눈으로 `WARNxN`과 `HARD-FAILxN`을 찾고 있었다. GraphRAG 쪽 profile evaluation은 query-level trace가 이미 꽤 두껍다. 그래서 모든 trace를 열기 전에 어떤 profile을 먼저 다시 볼지를 좁히는 profile-level queue가 필요했다. 이번 작업은 점수를 더 바꾸는 일이 아니라, 읽는 순서를 조금 더 기계적으로 고정하는 작업이었다.

이번에 바꾼 것

핵심 변경은 `reports/history-stale-status.json`이다. `profile_eval`이 quality history를 읽고 현재 run까지 합친 뒤, 최신 status streak가 기준 횟수 이상인 profile을 다시 한 번 걸러낸다. 기본값은 `min_streak=2`이고, 기본 동작에서는 `PASS` 상태는 제외했다. 통과 상태가 오래 유지되는 것도 중요한 정보이긴 하지만, inspection queue의 목적은 “다시 열 profile”을 줄이는 데 있으니 처음에는 non-pass만 남기는 쪽이 더 맞다고 봤다.

새 report에는 profile 이름만 넣지 않았다. 나중에 다시 열 때 필요한 최소 맥락을 같이 남겼다.

  • latest_status: 현재 quality gate 상태. 예: `warning`, `hard-fail`.
  • latest_status_streak_count: 같은 상태가 최신 run 기준 몇 번 이어졌는지.
  • latest_status_streak_labels: 그 streak에 포함된 history label 목록.
  • previous_distinct_status: streak 이전에 다른 상태가 있었다면 그 상태.
  • history_context: 기존 history report에서 쓰던 transition, scope delta, namespace 정보를 그대로 재사용.

CLI에는 `--stale-status-min-runs`와 `--stale-status-include-pass`를 붙였다. 평소에는 기본값으로 충분하지만, 나중에 안정 profile까지 같이 감사하고 싶으면 `PASSxN`도 report에 포함할 수 있게 열어 두었다. 나는 이런 옵션을 처음부터 너무 많이 두는 걸 좋아하지 않지만, 이번 건은 threshold가 운영 기준에 가까워서 고정 상수로 묶어 두면 곧 불편해질 것 같았다.

샘플 run에서 바로 보인 결과

이번 iter27 재현 run은 iter26 history를 seed로 복사한 뒤, 같은 `operational` namespace에 현재 run을 붙이는 방식으로 돌렸다. 결과는 꽤 예상한 대로 나왔다.

history-stale-status-report: traces/profile_eval_bundle_iter27_history_stale_status/reports/history-stale-status.json | profiles=2 | min_streak=2 | statuses=hard-fail:1, warning:1

여기서 중요한 건 profile이 네 개였는데 queue에는 두 개만 남았다는 점이다. `default`와 `relation_weight_dense`는 둘 다 `PASSx3`였다. 좋은 소식이지만, 이번 inspection queue에서는 굳이 다시 열 필요가 없다. 반대로 `path_bridge_probe`는 `WARNx3`, `path_bridge_focus`는 `HARD-FAILx3`로 잡혔다. 이 둘은 서로 같은 “문제 있음”이 아니다. 하나는 아직 관찰 후보이고, 다른 하나는 다음 후보에서 빼거나 별도 실험으로 분리해야 할 가능성이 높다.

profile status streak stale queue 내가 읽는 방식
default PASSx3 제외 기준선 유지. 지금은 trace를 열지 않는다.
relation_weight_dense PASSx3 제외 dense profile도 배포 후보 상태를 유지했다.
path_bridge_probe WARNx3 포함 hard-fail은 아니지만, 계속 애매하게 남아 있는 관찰 후보.
path_bridge_focus HARD-FAILx3 포함 다음 튜닝 후보에서 제외하거나 별도 branch로 빼야 할 후보.

내가 마음에 들었던 부분은 query-level trace를 다시 복제하지 않았다는 점이다. stale status report는 profile-level 우선순위만 들고 있다. 실제로 왜 `path_bridge_focus`가 계속 hard-fail인지 보려면 기존 rank-shift report와 trace diff를 열면 된다. report가 모든 답을 품으려고 하면 금방 무거워진다. 이번 queue는 “어느 문을 먼저 열지”만 말해 주는 작은 안내판에 가깝다.

구현하면서 신경 쓴 지점

작업 자체는 크지 않았지만, 경계는 조금 조심했다. 먼저 `PASSx3`를 기본 queue에 넣을지 말지 고민했다. 안정적으로 통과하는 profile도 history 관점에서는 의미가 있다. 하지만 summary 상단에 뜨는 report는 다시 볼 목록을 줄여야 한다. 그래서 기본값은 non-pass만 포함하고, 필요할 때만 `--stale-status-include-pass`로 넓히는 쪽으로 정했다.

두 번째는 정렬 순서였다. queue가 생기면 사람은 위에서부터 본다. 그래서 `HARD-FAIL`처럼 severity가 높은 상태를 먼저 두고, 그다음 streak count와 profile 이름으로 정렬했다. 샘플에서는 `path_bridge_focus`가 먼저 나오고 `path_bridge_probe`가 뒤따른다. 이 작은 순서가 별것 아닌 것 같아도, 나중에 profile이 열 개 이상으로 늘어나면 꽤 차이가 난다.

테스트는 숫자를 너무 강하게 묶지 않으려고 했다. 전체 retrieval 점수나 final score는 profile weight가 조금만 바뀌어도 흔들릴 수 있다. 대신 report 구조가 만들어지는지, non-pass streak만 들어가는지, `status_counts`가 `hard-fail:1, warning:1`로 접히는지, text summary에 `history-stale-status-report`가 노출되는지를 고정했다. 이번 전체 테스트는 31개가 통과했다.

이번 기준선이 남긴 것

이번 변경으로 GraphRAG profile evaluation의 읽는 순서는 조금 더 분명해졌다. 먼저 quality gate 상태를 보고, 그다음 status streak를 보고, 이제는 stale queue를 열어 오래 같은 non-pass 상태에 머문 profile만 골라낼 수 있다. 그 뒤에야 rank-shift report나 trace diff로 내려가면 된다. 예전에는 이 순서를 머릿속으로 기억하고 있었다면, 이제는 산출물 자체가 그 순서를 조금 강제한다.

다음에는 `previous_distinct_status`를 더 써보고 싶다. 같은 `WARNx3`라도 `HARD-FAIL`에서 올라온 뒤 멈춘 warning과, `PASS`에서 내려온 뒤 멈춘 warning은 의미가 다르다. 전자는 개선 후 정체이고, 후자는 악화 후 정체다. 지금은 이 값이 report 안에 들어가 있으니, 다음 반복에서는 이 둘을 따로 label로 나눠볼 수 있을 것 같다.

작게 보면 JSON report 하나가 늘어난 정도다. 그런데 이런 작은 queue가 쌓이면 실험이 덜 감정적으로 굴러간다. 어떤 profile이 마음에 든다거나, 방금 본 trace가 인상적이었다는 이유로 다음 실험을 고르는 대신, 오래 정체된 상태와 새로 커진 failure scope를 먼저 보는 쪽으로 손이 간다. GraphRAG 프로젝트가 아직 작은 MVP인 건 맞지만, 평가 루프만큼은 조금씩 운영 도구에 가까워지고 있다.

시리즈: GraphRAG 구축기 #10

이전: 9편 | 목록 | 다음: 11편

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.