2026년 7월 14일 | 개인 프로젝트 · GraphRAG
manual-incident 질의는 GraphRAG, Product Manual, Incident Report 세 엔터티의 연결을 묻는다. 여기서 Full-Coverage는 검색 결과가 이 세 엔터티를 전부 직접 또는 이웃 관계로 설명할 수 있다는 뜻이다. 이전 회차에서 근거 비교 리포트를 붙인 뒤, 1위인 Retrieval planner가 세 엔터티 중 두 개만 커버하면서도 supporting edge 두 개의 점수로 완전 커버 후보를 앞선다는 것을 확인했다.
원인은 찾았지만 고치는 방식이 더 까다로웠다. coverage weight를 전역으로 올리면 이 질의는 돌아오지만, 이미 안정적인 incident 질의까지 같이 움직일 수 있다. 그래서 이번 회차에는 모든 질의에 통하는 새 점수 공식을 만들지 않았다. 대신 문서 브리지 질의라는 범위, query tag, 후보의 coverage와 edge 모양이 모두 맞을 때만 작동하는 작은 순위 보정을 구현했다.
전역 점수를 더 건드리지 않은 이유
현재 검색 점수는 벡터 유사도, relation edge, reasoning path, query coverage를 더해 만든다. manual-incident에서 Retrieval planner는 벡터와 coverage에서 불리했지만, retrieves와 links 두 엣지의 점수로 그 손해를 뒤집었다. 반대편 GraphRAG overview는 세 엔터티를 모두 커버했지만 주요 supporting edge가 uses 하나였다.
처음에는 coverage bonus를 조금만 올리면 될 것처럼 보였다. 그런데 이 값은 문서 브리지 질의만 보는 스위치가 아니다. 샘플 전체에 적용되므로 한 건을 고치다가 다른 질의의 top-k가 다시 바뀔 수 있다. 나는 이런 상황에서 전역 상수를 계속 미세 조정하면 실험 기록은 늘어나도 “어떤 실패 유형을 고친 값인지”가 흐려지는 편이라고 느꼈다.
이번 보정은 일반적인 성능 향상 규칙이 아니라 재현된 회귀 하나에 대한 명시적 정책으로 한정했다. context가 없거나 cluster가 다르거나 필수 tag가 빠지면 이전 점수와 순위를 그대로 유지한다. 정책이 개입한 검색과 개입하지 않은 검색을 trace에서 구분할 수 있다는 점도 전역 가중치 수정과 다른 부분이다.
cluster와 tag를 실제 검색 입력으로 연결
평가용 query 파일에는 이미 cluster와 tags가 있었다. 지금까지는 결과를 cluster별로 묶어 보는 분석 메타데이터에 가까웠고, 실제 retriever는 이 값을 알지 못했다. 이번에는 RetrievalContext를 추가해 pipeline, CLI, profile evaluator가 같은 context를 검색 단계까지 전달하게 했다.
narrowed profile에는 아래처럼 scope가 좁은 rule을 넣었다. doc-bridges cluster이면서 docs tag가 있는 질의만 후보가 된다. 현재 1위는 일부 엔터티를 놓친 multi-edge 결과여야 하고, 대안은 엔터티를 모두 커버한 single-edge 결과여야 한다. 마지막으로 두 후보의 base score 차이가 0.35 이하여야 한다.
{
"rule_id": "doc-bridges-full-coverage-single-edge-v1",
"cluster": "doc-bridges",
"required_tags": ["docs"],
"policy": "prefer_full_coverage_single_edge",
"max_base_score_gap": 0.35,
"promotion_margin": 0.0001,
"partial_supporting_edge_count": 2,
"preferred_supporting_edge_count": 1
}
rule ID 중복, NaN이나 Infinity 같은 비유한 숫자, 너무 작은 promotion margin, 모호한 edge count는 설정을 읽는 단계에서 거부했다. JSON 설정은 문법만 맞으면 값이 이상해도 통과하기 쉬운데, 순위 정책에서는 잘못된 숫자 하나가 모든 결과를 조용히 바꿀 수 있어서 이 부분을 먼저 막았다.
0.3339의 차이만 좁게 뒤집는 계산
보정 전 Retrieval planner의 base score는 3.7085, GraphRAG overview는 3.3746이었다. 차이는 0.3339로 설정한 최대 gap 0.35 안에 들어왔다. 여기에 promotion margin 0.0001을 더한 0.3340을 완전 커버 후보에 적용해 최종 점수를 3.7086으로 만들었다.
| 후보 | Coverage | Supporting edges | Base score | 최종 점수 |
|---|---|---|---|---|
| Retrieval planner | 66.67% | 2 | 3.7085 | 3.7085 |
| GraphRAG overview | 100% | 1 | 3.3746 | 3.7086 |
0.0001 차이는 꽤 작다. 의도도 그렇다. 원래 점수 차이를 무시하고 큰 bonus를 얹는 것이 아니라, 설정한 경계 안에서만 순서를 한 칸 바꾸고 기존 상대 차이는 가능한 한 보존하려 했다. 반올림 뒤 두 점수가 다시 같아지는 경우도 있어서, 최종 점수가 기존 1위보다 엄격하게 커지는지 한 번 더 확인했다.
top_k=1에서 사라지던 비교 근거
구현 뒤 테스트를 늘리면서 한 번 더 막혔다. 검색기는 전체 후보를 정렬하고 guard를 적용한 뒤 top-k를 자르기 때문에 top_k=1에서도 승격 자체는 정상적으로 일어났다. 하지만 trace에는 승격된 한 건만 남았다. 비교 대상이 top-k 밖으로 사라지면 guard_compared_chunk_id만 있고, 왜 0.3340이 계산됐는지 다시 확인하기 어려웠다.
그래서 guard가 발동하는 순간 guard_evidence를 같이 만들었다. 여기에는 승격 후보와 비교 후보의 base score, coverage ratio, supporting-edge 수, 실제 gap, 허용 gap, adjustment, query context가 들어간다. 이제 결과 목록에 comparator가 없어도 해당 보정이 어떤 조건으로 일어났는지 trace 하나만으로 재현할 수 있다.
이 부분은 기능보다 운영에 가깝지만, 나는 꽤 중요하게 봤다. 점수 보정은 결과를 더 좋아 보이게 만드는 장치가 아니라 예외 정책이다. 예외가 생겼다면 나중에 그 이유를 지울 수 있어야 하고, 그러려면 적용 시점의 근거가 별도 로그에 남아야 한다.
6개 회귀 질의에서 바뀐 것은 하나
샘플 6개 질의를 다시 실행하니 context에 의해 실제 Top-1이 바뀐 것은 manual-incident 한 건뿐이었다. 같은 doc-bridges cluster에 속한 안정 질의 planner-root-cause는 기존 1위를 유지했다. incident-core와 coverage-sensitive 질의에도 이번 rule은 개입하지 않았다.
| 지표 | 이전 | 이번 회차 |
|---|---|---|
| manual-incident Top-1 | Retrieval planner | GraphRAG overview |
| Top-1 hit rate | 83.33% | 100% |
| Full-coverage rate | 83.33% | 100% |
| Default Top-1 agreement | 83.33% | 100% |
| Stable top-k rate | 66.67% | 83.33% |
| Quality gate | warning | PASS |
전체 단위 테스트는 62개가 통과했고 compile check와 diff check도 함께 통과했다. 프로젝트 저장소에는 구현, 설정, 테스트, 실험 문서를 같은 커밋으로 묶어 올렸다. 아직 샘플이 작은 만큼 “순위 정책이 일반화됐다”고 말할 단계는 아니다. 다만 실패 하나를 전역 숫자에 숨기지 않고, 적용 범위와 근거가 보이는 코드로 옮긴 것은 다음 실험을 시작하기 좋은 기준선이 됐다.
다음 실험은 경계값에서 시작한다
현재 guard-positive fixture는 manual-incident 하나다. 다음에는 base gap이 정확히 0.35인 경우와 그보다 조금 큰 경우를 넣어 경계 밖 후보가 승격되지 않는지 확인할 생각이다. 완전 커버 single-edge 후보가 둘 이상일 때 가장 높은 base score 후보만 선택되는지도 별도 테스트가 필요하다.
남은 rank reorder 한 건은 coverage-sensitive cluster에 있다. 그 문제를 이번 rule에 억지로 합치지는 않으려 한다. 문서 브리지와 부분 커버 질의는 실패 모양이 다르므로, 다음 회차에서도 먼저 evidence를 읽고 별도의 scope가 필요한지 판단하는 쪽이 낫다. 작은 예외 정책이 두 번째 실패까지 삼키기 시작하면 다시 전역 튜닝과 다를 것이 없어지기 때문이다.
'[AI 실험실] > [개인 프로젝트] GraphRAG' 카테고리의 다른 글
| GraphRAG | Rank Guard 적용·거절 근거 리포트 (1) | 2026.07.17 |
|---|---|
| GraphRAG | 1위 결과 전환의 엣지 근거 비교 (0) | 2026.07.10 |
| GraphRAG | Scoring Profile 후보 검증 (0) | 2026.07.07 |
| GraphRAG | Exit Policy preview action 적용 (0) | 2026.07.03 |
| GraphRAG | Exit Policy action preview 리포트 (0) | 2026.06.30 |