2026년 5월 1일 | 개발 일기
GraphRAG profile_eval의 history 줄은 점점 길어지고 있었다. 처음에는 실행 결과를 한 번 더 저장해 두는 정도였는데, 어느 순간부터는 quality gate, history transition, baseline mismatch, namespace, scope growth queue까지 붙었다. 기능이 늘어난 건 좋은데, 내가 실제로 summary를 볼 때 제일 먼저 알고 싶은 건 더 단순했다. 이 profile이 지금 막 바뀐 건지, 아니면 같은 상태로 계속 버티고 있는지였다.
그래서 이번에는 점수를 올리는 쪽이 아니라, 상태가 몇 run째 반복되는지를 보여 주는 작은 필드를 붙였다. 이름은 코드 안에서는 latest_status_streak_count로 잡았다. text summary에서는 더 짧게 status_streak=PASSx2, status_streak=WARNx2, status_streak=HARD-FAILx2처럼 보이게 했다. transition이 WARN->WARN이라고만 나오면 그냥 변화 없음으로 읽히는데, 그 옆에 WARNx2가 붙으면 “두 번 연속 같은 warning이다”라는 판단이 바로 선다.
왜 transition만으로는 부족했나
PASS->PASS는 얼핏 보면 별 정보가 없어 보인다. 하지만 내가 보고 싶은 건 “계속 괜찮다”와 “방금 괜찮아졌다”의 차이다. 전자는 다음 단계로 넘겨도 되는 후보에 가깝고, 후자는 조금 더 지켜봐야 하는 후보일 수 있다. WARN->WARN도 마찬가지다. 한 번의 warning은 기준선이 빡빡해서 생긴 것일 수 있지만, 몇 run째 같은 warning이면 profile 자체를 보류하거나 별도 실험으로 빼야 한다.
기존 history summary에는 history_runs와 display_status_sequence가 있었다. 이론상으로는 그 sequence를 사람이 읽으면 같은 상태가 몇 번 이어졌는지 알 수 있다. 그런데 summary가 길어질수록 사람은 거기까지 차분히 세지 않는다. 특히 GraphRAG처럼 profile이 네 개 이상이고, 각 profile 아래에 query별 trace diff가 붙으면 상태열은 금방 배경으로 밀린다. 그래서 이번에는 사람이 세지 않아도 되게 만들었다.
붙인 필드
이번 변경은 큰 구조 변경이 아니다. _summarize_profile_history()에서 status sequence를 만든 뒤, 뒤쪽부터 최신 status와 같은 값이 얼마나 이어지는지만 훑는다. 같은 구간의 history label도 같이 모아서 latest_status_streak_labels에 넣었다. 그리고 처음으로 다른 status를 만나면 previous_distinct_status로 남겼다.
- latest_status_streak_count: 최신 status가 연속된 run 수
- latest_status_streak_labels: 그 streak에 포함된 history label 목록
- previous_distinct_status: streak 직전에 있던 다른 status
- status_streak: text summary에서 사람이 읽는 축약 표현
이 필드가 좋은 점은 기존 report를 깨지 않는다는 것이다. transition은 그대로 둔다. mismatch report도 그대로 둔다. 다만 그 옆에 “이 상태가 얼마나 오래 이어졌는가”라는 작은 눈금을 하나 더 올려 둔다. 나는 이런 식의 얇은 신호가 실험 도구에서는 꽤 중요하다고 본다. 큰 metric을 새로 만드는 것보다, 이미 있는 metric을 사람이 덜 헷갈리게 읽도록 돕는 쪽이 다음 행동을 더 빨리 만든다.
이번 run 결과
이번에는 직전 iter25-operational history를 seed로 복사한 뒤, 같은 operational namespace에서 iter26-status-streak을 append했다. 점수 자체는 바뀌지 않았다. 대신 text summary에 각 profile의 streak가 바로 보이기 시작했다.
| profile | latest status | status streak | 내가 읽은 의미 |
|---|---|---|---|
| default | PASS | PASSx2 | 기본 profile은 두 operational run 연속 기준선을 통과했다. |
| relation_weight_dense | PASS | PASSx2 | dense profile도 안정 후보로 남겨둘 수 있다. |
| path_bridge_probe | WARN | WARNx2 | 완전 탈락은 아니지만 warning 상태가 반복되고 있다. |
| path_bridge_focus | HARD-FAIL | HARD-FAILx2 | 다음 후보군에서 빼고 별도 실험으로 돌리는 편이 맞다. |
가장 눈에 들어온 건 path_bridge_probe였다. 이 profile은 hard-fail은 아니지만, warning 상태가 두 번 연속으로 고정됐다. 예전 summary였다면 나는 WARN->WARN만 보고 “별일 없네” 정도로 넘겼을 가능성이 크다. 그런데 WARNx2라고 보이면 조금 다르게 읽힌다. 더 좋아지고 있는 중이라기보다, warning band 안에서 멈춰 있는 후보에 가깝다.
테스트로 잡은 부분
회귀 테스트도 하나 추가했다. history에 HARD-FAIL 한 번, WARN 한 번을 넣어 둔 뒤, 현재 run이 다시 WARN이 되도록 만들었다. 이때 summary는 run 세 개를 보되, 최신 streak는 WARNx2로 잡혀야 한다. 그리고 streak label은 iter22, iter23만 들어가야 한다.
전체 테스트는 30개를 다시 돌렸다. 여기서 중요한 건 기능이 복잡해졌다는 사실보다, history 계산이 기존 namespace와 mismatch report를 건드리지 않았다는 점이다. namespace가 다른 sample entry는 여전히 ignored count로 빠지고, 같은 namespace 안에서만 streak가 계산된다. 이 경계를 지키지 않으면 sample fixture의 오래된 실패가 operational run의 상태 반복으로 섞일 수 있다.
남은 생각
이번 변경은 작다. 하지만 GraphRAG 트랙을 계속 만지다 보니 이런 작은 판독값이 쌓이는 게 꽤 중요하다는 쪽으로 생각이 기울고 있다. profile을 더 세게 튜닝하기 전에, 지금 보고 있는 summary가 다음 행동을 제대로 가리키는지 먼저 봐야 한다. PASSx2는 유지 후보, WARNx2는 관찰 후보, HARD-FAILx2는 분리 후보처럼 읽을 수 있으면 실험 루프가 덜 흔들린다.
다음 단계로는 이 streak를 별도 queue로 뽑아 보고 싶다. 예를 들어 WARN이 세 번 이상 반복된 profile만 모아 stale-status report를 만들면, “계속 애매한 후보”를 따로 접어 둘 수 있다. 반대로 PASS가 여러 번 반복된 profile은 기본 후보군으로 승격해도 된다. 오늘 붙인 건 그 판단을 위한 작은 카운터에 가깝다.
'[개발 일기]' 카테고리의 다른 글
| GraphRAG | Action report 기반 registry 갱신 (0) | 2026.05.13 |
|---|---|
| GNN | FP/FN 비용 queue 추가 (0) | 2026.05.07 |
| GraphRAG | History Baseline Guard (1) | 2026.04.25 |
| GNN | GraphSAGE hybrid 입력 비교 (0) | 2026.04.16 |
| GNN | Structural mean avg/gap 정리 (0) | 2026.04.13 |