2026년 4월 3일 | 개발 일기
GraphRAG MVP를 사람 눈에 맞게 읽히도록 다듬는 작업이 이번 단계의 중심이었다. 지금까지는 hybrid retrieval 결과를 보면 vector score와 graph score가 같이 나오고, supporting edge랑 reasoning path도 같이 확인할 수 있었다. 여기까지만 해도 왜 이 청크가 올라왔는지는 꽤 설명이 됐는데, 막상 긴 질문이나 엔티티가 여러 개인 질문을 던지면 하나가 계속 남았다. 이 결과가 질문 전체를 어느 정도 받쳐주고 있는지가 바로 안 보인다는 점이었다.
예를 들어 Customer Dashboard, Payment API, Root Cause Note를 한 번에 묻는 질문을 던졌을 때, 어떤 결과는 Payment API와 Root Cause Note는 잘 잡는데 Customer Dashboard는 놓친다. 그런데 기존 출력만 보면 이게 그냥 점수가 조금 낮은 결과인지, 아니면 질문의 한 축이 빠진 partial hit인지 다시 읽어서 해석해야 했다. 오늘은 딱 그 불편함을 줄이는 쪽으로 손봤다.
1. 오늘 손본 방향: 점수 말고 질문 커버리지를 같이 보기
이번에 추가한 건 거창한 모델 교체가 아니라, 검색 결과 설명에 붙는 기준선 하나다. 각 결과마다 query_entities, missing_entities, coverage_ratio를 계산해서 같이 내보내도록 바꿨다. 말 그대로 지금 올라온 top-k 결과가 질문 안의 엔티티를 얼마나 커버하고 있는지를 숫자와 목록으로 바로 보게 만든 셈이다.
이 변화가 좋은 이유는 단순하다. 나는 retrieval 실험을 할 때 점수 자체보다 왜 이 결과를 믿어도 되는지를 먼저 확인하고 싶다. 특히 GraphRAG처럼 그래프 연결과 텍스트 유사도가 같이 들어가는 구조에서는, 점수 하나만 보면 실제로는 질문의 절반만 잡아도 그럴듯하게 보일 때가 있다. 그래서 설명 계층에 coverage를 넣어 두는 게 다음 반복의 기준선을 훨씬 단단하게 만든다.
- 질문에서 뽑힌 엔티티를 결과마다 다시 대조했다.
- 직접 맞은 엔티티와, 이웃 관계를 통해 간접적으로 받쳐지는 엔티티를 구분해 봤다.
- 최종적으로는 질문 기준에서 무엇이 잡혔고 무엇이 빠졌는지 한 번에 읽히게 했다.
2. 같이 고친 부분: 질문문 첫 단어가 엔티티처럼 잡히던 문제
coverage를 붙이면서 바로 드러난 이상한 점도 하나 있었다. 질의를 넣어 보니 How 같은 질문문 첫 단어가 엔티티처럼 잡히는 경우가 있었다. 규칙 기반 추출기를 쓰고 있으니 대문자로 시작하는 단어를 너무 곧이곧대로 받아들이고 있었던 셈이다. 이 상태에서는 coverage ratio가 의미 없이 흔들릴 수밖에 없다. 질문을 잘못 읽고 있는데, 그 위에 설명 지표를 올려도 결국 해석이 틀어진다.
그래서 오늘은 coverage 계산만 붙인 게 아니라, 질의에서 자주 튀는 질문 단어들을 stop entity 쪽으로 정리했다. 아주 큰 수정은 아니지만 이런 작은 정리가 설명 지표에는 생각보다 중요하다. 지표는 계산식보다도 입력 해석이 얼마나 덜 어긋나느냐에서 훨씬 많이 흔들리기 때문이다.
3. 실제로 확인한 출력: partial hit가 이제 바로 드러난다
오늘 확인에 쓴 질문은 Customer Dashboard, Root Cause Note, Payment API를 같이 묻는 형태였다. 기존에는 top result가 그럴듯하게 보여도 어떤 축이 빠졌는지 눈으로 다시 세어야 했다. 지금은 text explain 출력에서 query coverage: 67%, missing query entities: Customer Dashboard 같은 식으로 바로 드러난다. 이 정도만 돼도 결과를 읽는 속도가 훨씬 빨라진다.
재밌었던 건 두 번째 결과였다. 첫 번째 결과는 텍스트 매치가 강해서 위로 올라오지만 질문의 한 축을 놓치고 있었고, 두 번째 결과는 그래프 이웃과 경로를 타고 들어가면서 세 엔티티를 모두 받쳐주는 쪽으로 읽혔다. 이 차이를 오늘처럼 coverage로 분리해 두면, 다음에는 reranking 단계에서 full hit를 partial hit보다 우선하는 규칙을 붙여 볼 수 있다. 즉 오늘 작업은 화면에 설명 하나 붙인 게 아니라, 다음 점수 설계의 재료를 미리 심어 둔 셈이다.
top-1
query coverage: 67%
missing query entities: Customer Dashboard
top-2
query coverage: 100%
missing query entities: -
이 출력만 봐도 지금 랭킹이 완전히 틀렸다고 단정할 일은 아니지만, 적어도 무엇이 아쉬운 결과인지는 훨씬 명확해졌다. 나는 이런 식의 기준선이 있어야 다음 실험에서 덜 헤맨다. 그냥 점수만 바뀌면 기분만 바쁘고 판단은 흐려지는데, coverage처럼 사람이 바로 읽을 수 있는 단서가 붙으면 실험이 조금 덜 소모적으로 바뀐다.
4. 테스트와 기록도 같이 묶었다
오늘은 코드만 고치고 끝내지 않고 테스트와 문서도 같이 정리했다. 질문 단어가 엔티티에서 빠지는지, coverage와 missing entity가 trace snapshot에도 남는지, text renderer에서 partial hit가 실제 문장으로 드러나는지를 같이 확인했다. 이 부분을 빼먹으면 다음번 변경에서 설명 계층이 조용히 무너져도 한동안 모를 가능성이 크다.
이 프로젝트를 만지면서 계속 느끼는 건, GraphRAG 초기 단계에서는 거대한 인프라보다 판단 기준을 얼마나 잘 남기느냐가 더 중요하다는 점이다. 벡터 DB나 외부 그래프 저장소를 붙이는 건 나중에도 할 수 있지만, 지금은 작은 샘플 데이터셋에서 결과가 왜 그렇게 나왔는지를 분해해서 볼 수 있어야 한다. 오늘 작업도 딱 그 축에 있다. 검색 품질 자체를 확 끌어올린 건 아니지만, 품질을 읽는 눈금을 한 칸 더 세분화했다.
- retrieval result에 coverage 관련 필드를 추가했다.
- CLI text explain과 trace snapshot 출력까지 같이 맞췄다.
- 질문 단어 stop entity 정리와 테스트 보강도 함께 묶었다.
- 오늘 변경은 sidecar git 저장소 기준으로 커밋까지 남겼다.
5. 랭킹을 다시 보게 된 순간: top-1이 늘 더 좋은 건 아니었다
오늘 제일 좋았던 건 결과를 다시 읽는 기준이 생겼다는 점이다. 첫 번째 결과는 텍스트 매치가 강해서 위로 올라오지만, 질문 안의 모든 축을 같이 들고 있지는 않았다. 반대로 두 번째 결과는 점수는 조금 낮아도 질문 전체를 더 많이 설명하는 쪽이었다. 전에는 이런 상황이 나오면 내가 출력 전체를 한참 읽고 나서야 "아, 이건 위에 있지만 덜 완전하네"라고 판단했다.
이제는 그 판단이 훨씬 빨라졌다. top-1이 왜 top-1인지와 그래도 당장 믿기엔 뭐가 빠졌는지를 한 번에 볼 수 있기 때문이다. 나는 이런 종류의 기준이 있어야 retrieval를 덜 감으로 만지게 된다. 특히 나중에 reranker를 붙일 때도, 지금 남겨 둔 coverage 로그가 있으면 "점수는 유지됐는데 full hit 비율이 늘었는가" 같은 질문을 바로 던질 수 있다.
6. 오늘 회고: 검색 품질보다 먼저, 설명 품질을 다듬는 날
오늘 작업은 겉으로 보면 꽤 소박하다. 새로운 모델을 붙인 것도 아니고, 정답률을 크게 끌어올린 것도 아니다. 그런데 나는 이런 날이 프로젝트를 오래 끌고 가는 데 더 중요하다고 느낀다. 검색이 조금 좋아지는 것만큼, 좋아졌는지 아닌지를 내가 빨리 판별할 수 있는 구조를 만드는 일이 중요하기 때문이다.
특히 GraphRAG처럼 설명 가능성을 강점으로 가져가고 싶은 프로젝트에서는, 결과 설명이 단순한 부가 정보가 아니라 거의 인터페이스에 가깝다. 오늘 넣은 coverage는 아직 랭킹 점수에 직접 들어가진 않지만, 다음 단계에서 reranking feature로 충분히 이어질 수 있다. 내일이나 다음 반복에서는 partial hit를 한 단계 아래로 밀어내는 규칙을 붙이거나, query entity pair마다 path coverage를 따로 계산해 볼 생각이다. 오늘은 성능을 크게 올린 날이라기보다, 성능을 덜 착각하게 만드는 장치를 하나 더 심은 날에 가까웠다.
그리고 이런 변화는 커밋 단위로 남겨 둘수록 의미가 커진다. 오늘도 sidecar git 기준으로 변경을 묶어 두었기 때문에, 다음 반복에서 coverage를 실제 reranking에 반영했을 때 어느 지점부터 판단 기준이 달라졌는지 비교하기 쉬워졌다. 작은 개선이지만 이런 기록이 쌓여야 프로젝트가 감이 아니라 축적 위에서 움직인다.
'[개발 일기]' 카테고리의 다른 글
| GNN Baseline: Supervised Scoring 추가 (0) | 2026.04.04 |
|---|---|
| GraphRAG Trace Diff와 랭킹 이동 (0) | 2026.04.04 |
| 세 줄짜리 git log가 이 자동화 파이프라인의 병목을 더 잘 보여줬다 (0) | 2026.04.03 |
| GraphRAG Trace Snapshot 기준선 (0) | 2026.04.02 |
| GraphRAG Explainable Retrieval Trace (0) | 2026.04.02 |