기능 하나를 다 만들고 되돌린 적이 있습니다. 검색 결과에서 약한 인용을 점수로 잘라내는 필터. 코드는 멀쩡히 돌아갔고 화면에도 더 깔끔한 결과가 떴습니다. 그런데 평가 하네스에 올려보니 잘라낸 양이 겨우 2%였습니다. 정답과 노이즈의 점수가 거의 겹쳐 있던 탓입니다. 임계값을 어디에 둬도 차이는 거의 그대로. 그날 제가 한 일은 기능을 더한 게 아니었습니다. 오히려 그 반대. 만든 코드를 지웠고, 지우게 만들어준 측정 덕을 봤습니다.
이 글은 검색 엔진을 잘 만든 무용담이 아닙니다. K-IFRS 회계기준서를 찾아주는 개인용 검색 엔진을 만들면서, 모든 개선을 50문항짜리 평가 하네스로 재본 기록입니다. 하네스가 한 좋은 일은 두 가지였습니다. 하나는 효과 있는 기능을 통과시킨 것. 다른 하나는 그럴듯해 보이던 기능 두 개를 음성으로 걸러내, 헛기능이 제품에 남지 않게 막아준 것입니다. 뒤쪽이 이 글의 진짜 주제입니다.
- 01검색 엔진을 만들기 전에 먼저 50문항짜리 평가 하네스를 깔았습니다. 무엇을 맞혔다고 볼지 정의하지 않으면 개선도 후퇴도 구분할 수 없습니다.
- 02lexical에서 hybrid, 질의 확장, 리랭킹, 계층 검색까지 누적 개선은 전부 같은 하네스 위에서 수치로 확인했습니다.
- 03같은 하네스가 그럴듯해 보이던 기능 두 개를 음성으로 걸러냈습니다. 측정이 없었다면 둘 다 추가됨으로 남았을 기능입니다.
- 04검색 품질이 올라도 답변 품질이 자동으로 따라오지는 않습니다. 검색은 판정 시스템의 한 층일 뿐입니다.
검색기보다 시험지를 먼저 깔았습니다
처음 한 일은 검색 코드를 잘 짜는 게 아니었습니다. 무엇을 맞혔다고 볼지부터 정하는 일. 회계 질문 50개를 골라, 각 질문마다 답을 결정짓는 기준서 문단을 정답으로 박았습니다. 검색기가 그 문단을 상위 몇 위 안에 데려오는지가 recall입니다. 정답이 위로 올수록 높아지는 MRR과 nDCG도 함께 봤습니다.
이 시험지가 있어야 비로소 개선이라는 말이 성립합니다. 점수가 오르면 개선, 떨어지면 후퇴. 단순한 규칙이지만 이게 없으면 판단할 근거가 사라집니다. 시험지가 없으면 무엇에 기대게 될까요. 결국 화면을 보고 "좋아진 것 같다"는 느낌에 기댑니다. 그 느낌은 자주 틀립니다. 검색은 상위 몇 개만 눈에 들어오기 때문입니다. 정작 중요한 정답이 6위에서 11위로 밀려나도 사람은 잘 못 느낍니다.
골드셋은 외부 API 없이 로컬에서만 돌게 만들었습니다. 회계기준서 원문과 평가 문항은 저작권과 사용 범위 때문에 밖으로 내보내지 않습니다. 점수를 내는 데 필요한 건 검색기와 시험지뿐. 둘 다 본인 PC 안에 있습니다. 기능을 하나 바꿀 때마다 같은 50문항을 다시 돌립니다. 1점 차이까지 같은 잣대로 비교했습니다.
누적 개선은 실제로 먹혔습니다
하네스가 생기자 검색 방식을 한 단계씩 올릴 수 있었습니다. 출발점은 단어가 그대로 겹치는 lexical 검색. 여기에 의미가 가까운 문단을 찾는 임베딩 검색을 붙인 뒤, 둘의 순위를 합치는 hybrid로 넘어갔습니다. 한국어 기준서는 같은 개념을 조금씩 다른 단어로 적는 경우가 많습니다. 그 간극을 의미 검색이 메워줍니다.
그다음은 질의 확장. 질문을 그대로 넣지 않고 관련 표현까지 함께 검색하도록 넓혔습니다. 상위 후보를 다시 정밀하게 점수 매기는 리랭킹도 붙였습니다. 마지막은 계층 검색. 조와 항 같은 섹션 단위를 1차 검색 단위로 삼는 방식입니다. 섹션 제목이 질문과 맞는 정답을 끌어올려 줍니다.
| 바꾼 것 | 검색 품질 변화 |
|---|---|
| lexical 단독 → hybrid(lexical + 의미검색 결합) | recall 60% → 80% |
| 질의 확장 추가 | recall@20 0.847 → 0.907 |
| 상위 후보 cross-encoder 리랭킹 | recall@5 0.597 → 0.640, MRR 0.509 → 0.612 |
| 섹션 단위 계층 검색 | recall@10 0.763 → 0.827, MRR 0.509 → 0.542 |
중요한 건 이 표의 모든 줄이 같은 50문항 위에서 나왔다는 점입니다. 각 단계는 앞 단계보다 점수가 올랐을 때만 남겼습니다. 한 군데선 인코딩을 GPU로 옮겼고, 재색인 시간이 32분에서 1분 안쪽으로 줄었습니다. 속도가 빨라지니 실험을 더 자주 돌립니다. 실험이 잦아지니 판단도 빨라집니다. 작은 선순환이 생깁니다.

그런데 하네스가 두 번 아니라고 했습니다
여기까지는 측정이 손을 들어주는 이야기입니다. 진짜 값어치를 한 순간은 따로 있었습니다. 그럴듯해 보이던 기능 두 개를 하네스가 음성으로 걸러낸 때입니다.
첫 번째는 글 첫머리에 적은 저신뢰 임계값 필터. 리랭킹 점수가 낮은 인용은 약한 근거일 테니, 일정 점수 아래를 잘라내면 결과가 깨끗해질 거라 기대했습니다. 측정해보니 정답 문단의 점수 중앙값은 0.993이었습니다. 노이즈 문단은 0.921. 너무 가깝지 않나요. 임계값을 0.1로 잡아도 잘려나가는 노이즈는 2%뿐이었습니다. 잘라낼 저신뢰 꼬리 자체가 거의 없던 셈입니다. 효과가 없으니 곧장 보류.
두 번째는 리랭킹에 넣는 후보 풀을 더 좋은 것으로 바꾸는 시도. 나은 검색 방식으로 후보를 모아 리랭커에 먹이면 최종 결과도 좋아질 거라 봤습니다. 측정 결과는 더 분명합니다. 후보 50개 기준 recall이 양쪽 모두 0.923으로 같았습니다. 리랭커는 후보의 원래 순서를 무시하고 다시 점수를 매깁니다. 그러니 최종 출력이 한 글자도 다르지 않았습니다. 바뀐 게 없는 변경. 코드를 되돌렸습니다.
| 시도한 기능 | 기대 | 측정 결과 | 결정 |
|---|---|---|---|
| 저신뢰 임계값 필터 | 약한 인용을 점수로 잘라낸다 | 정답 0.993 vs 노이즈 0.921, 잘린 양 2% | 보류 |
| 리랭킹 후보 풀 교체 | 더 나은 풀로 최종 결과 개선 | recall 0.923 = 0.923, 출력 동일 | 되돌림 |
두 기능 모두 코드로는 멀쩡히 동작했습니다. 데모만 봤다면 "추가했다"고 적고 넘어갔을 겁니다. 차이를 만든 건 같은 시험지에 올려 숫자로 확인한 절차 하나입니다.
헛기능의 비용은 눈에 잘 안 보입니다
안 되는 기능을 막는 일은 되는 기능을 만드는 일보다 티가 안 납니다. 추가한 기능은 변경 목록에 한 줄로 남습니다. 막아낸 기능은 아무 흔적도 안 남습니다. 측정이 없으면 헛기능은 그렇게 조용히 쌓입니다.
쌓인 헛기능은 나중에 비용으로 돌아옵니다. 임계값 필터를 넣었다면 어떻게 됐을까요. 어느 날 정답이 잘려나가도 원인을 한참 뒤에야 찾았을 겁니다. 후보 풀을 바꿨다면 효과도 없는 코드 경로가 유지보수 대상으로 남았을 것입니다. 당장은 둘 다 무해해 보입니다. 그러나 시스템을 조금씩 무겁게, 그리고 설명하기 어렵게 만듭니다.
작은 팀일수록 이 차이가 큽니다. 사람이 많으면 누군가 나중에 "이거 효과 있나요"라고 물어볼 여지라도 있습니다. 혼자거나 둘이면 그렇게 물어줄 사람이 없습니다. 그 질문을 시스템이 대신 던지게 만드는 게 하네스입니다. 기능을 올릴 때마다 같은 시험지가 묻습니다. 그래서 점수가 올랐나요.
작은 팀이 가져갈 수 있는 것
거창한 평가 인프라가 필요한 일은 아니었습니다. 핵심은 순서. 검색기를 짜기 전에 시험지를 먼저 만들고, 그 위에서만 기능을 판단한다는 순서 하나면 충분했습니다.
시험지는 작아도 됩니다. 50문항이면 한 번 돌리는 데 오래 걸리지 않습니다. 그 정도만 돼도 개선과 후퇴, 무변화를 가릅니다. 정답을 박을 때는 답을 결정짓는 근거 하나를 분명히 정해두면 좋습니다. 근거가 많다고 좋은 게 아닙니다. 결론을 뒤집는 결정적 근거가 무엇인지 아는 쪽이 점수를 의미 있게 만듭니다.
음성 결과를 존중하는 태도도 필요합니다. 며칠 들여 만든 기능이 효과 0으로 나오면 아깝습니다. 그래도 그 0이야말로 하네스가 가장 정직하게 일한 순간 아닐까요. 만들지 않아도 될 것을 만들지 않게 해준 셈입니다. 효과 없는 코드를 지우는 일은 손해가 아닙니다. 다음 실험을 가볍게 시작할 여유를 벌어줍니다.
검색이 좋아져도 끝이 아닙니다
마지막으로 솔직하게 적어둘 한계가 있습니다. 이 모든 측정은 검색 품질을 잰 것이지 답변 품질을 잰 것이 아닙니다. 정답 문단을 상위에 데려오는 비율이 올라도, 그 문단을 사실관계에 맞게 적용했는지는 또 다른 문제입니다. 검색은 판정 시스템의 한 층일 뿐. 몸통은 아닙니다.
본문에 아예 없는 표현은 아무리 좋은 검색기도 찾지 못합니다. 코퍼스 밖에 있는 근거는 더 그렇습니다. recall에도 천장이 있습니다. 어느 지점부터는 검색 방식을 더 바꿔도 점수가 잘 안 오릅니다. 그 천장을 알게 된 것 역시 하네스 덕입니다.
그래서 검색 엔진을 키우는 일은 좋은 기능을 쌓는 일이 아니었습니다. 무엇이 실제로 효과가 있는지 계속 재는 일에 가까웠습니다. 다음에 새 검색 방식을 붙이고 싶어질 때, 저는 아마 먼저 같은 50문항을 떠올릴 겁니다. 만들기 전에 한 번 시험지에 물어보는 습관. 그 습관 하나가 헛수고를 가장 많이 줄여줬습니다.
- 0150문항 시험지
- 02결정적 근거 박기
- 03기능 변경
- 04같은 시험지 재측정
- 05오르면 채택, 무변화면 폐기
- domain-ai-is-not-rag
- harness-before-after
- tax-ai-verify-not-fluent



