Skip to content
게으른 엔지니어의 기술 블로그
Go back

확신을 실측 앞에 세워봤다 9편 — 답은 두 문서 안이 아니라, 밖에 있었다

확신을 실측 앞에 세워봤다 · 9/9편

8편에서 “이미 만들어진 모놀리식 노트를 나중에 찾아 쪼개라”는 방향을 여러 갈래로 시도하고, 최선이 37.5%라 프로덕션에 넣기엔 부족하다고 판단해 보류했다. 그런데 이야기는 거기서 안 끝났다.

방향을 바꿨다 — 사후 탐지 대신 사전 개입

지금까지 시도한 건 전부 “이미 병합된 뒤에” 문제를 알아채는 쪽이었다. 방향을 뒤집어서, 병합을 결정하는 바로 그 순간에 개입하면 어떨까 싶었다. /refactor의 병합 판단(rule 1)은 두 후보 문서를 나란히 놓고 비교하는 중이니까, 그 비교하는 시점에 “합쳤을 때도 하나의 개념이 맞는지”를 같이 물어보면, 145개 다른 노트와 안 섞인 좁은 조건에서 판단할 수 있겠다는 가설이었다.

새 출력 형식을 만드는 대신, 이미 있는 merge/split 타입을 그대로 재사용하기로 했다 — “결과가 독립된 여러 개념을 담게 되면, 두 원본을 임시 target으로 병합하는 제안과, 그 target을 source로 하는 분할 제안을 함께 내라”는 문구를 rule 1에 추가하는 식으로. 실행 순서를 맞춰주는 로직도 예전에 병합·분할이 연쇄로 이어질 때의 실행 엔진을 설계하면서 이미 만들어둔 게 있어서, 코드 변경 없이 프롬프트만 바꾸면 되는 상황이었다.

실제로 이 노트를 만들어낸 원본 두 문서(재구성이 아니라 실제 병합 직전 상태, git 히스토리에서 그대로 복원)를 놓고 테스트했다. 결과는 8번 중 0번 — 문구만으로도, 구체적인 JSON 출력 예시를 붙여서도 마찬가지였다. “병합해라”는 지시를 받으면 모델이 단일 행동으로 끝맺으려는 경향이 강해서, “사실 이건 병합+분할이어야 한다”는 재해석을 스스로 안 하는 것 같았다.

만들어내라는 대신, 확인만 시켜보기로 했다

여기서 다시 방향을 좁혔다. 모델에게 새로운 구조를 만들어내라고 하는 대신, 이미 나온 제안 하나를 두고 “이거 정말 병합해도 돼?”라는 예/아니오 질문만 던지기로 했다. 사용자가 낸 아이디어였다 — “제안이 나오고 나서 사용자에게 보여주기 전에, 안 맞으면 빼버리면 어떠냐”는 것. 병합 자체는 지금처럼 그대로 제안되고 실행되게 두고, 그 제안이 화면에 뜨기 직전에 조용히 검증만 하나 더 끼워넣는 방식이다. 병합을 막는 게 아니라, 사용자에게 안 보여주는 것뿐이니 UX도 안 건드린다.

같은 두 원본으로 “합쳐도 하나의 개념이 맞아?”만 물어봤다. 결과는 다시 8번 중 0번이었다 — 본문 전체를 다 줘도 마찬가지였다. 답변들은 하나같이 “둘 다 같은 개념을 정의·배경·실천·프로세스·팀전환·보안 등 서로 보완적인 관점에서 설명한다”였다. 그리고 이 판단, 딱히 틀린 것도 아니었다 — 두 문서만 놓고 보면 정말 그렇게 보인다. 제목도 똑같고, 다루는 얘기도 겹친다.

왜 안 되는지, 이번엔 사용자가 먼저 짚었다 — LLM 판단 근거 부족 문제

“헤딩이 많으면 병합하면 안 된다는 것도 아닌 거 같은데”라는 질문이 들어왔다. 맞는 지적이었다. 진짜 기준은 헤딩 개수가 아니라, 이 플러그인이 원래 갖고 있는 원칙(아토믹화: 한 노트는 하나의 명확한 개체만 설명한다) 이었다. 그리고 뒤이어 더 날카로운 질문이 왔다 — “지금 볼트 다른 곳에 왜 노트가 남아 있고 refactoring에 포함 안 되는 거지?”

답을 찾다 보니 진짜 이유가 드러났다. 이 두 문서가 다루는 하위주제들 (Specification-driven development, Critical verification 등)은 이미 vault 다른 곳에 독립된 노트로 실제로 존재하고 있었다 — /ingest가 같은 원본 자료를 처리하면서 예전에 이미 아토믹하게 쪼개놓은 것들이었다. 그런데 이 사실은 지금 감사하고 있는 두 문서 안에는 전혀 없다. 본문을 아무리 꼼꼼히 읽어도, “이 내용이 vault 다른 곳에 이미 노트로 있다”는 건 그 두 문서만 봐서는 알 도리가 없다. 필요한 증거가 애초에 질문 안에 없었던 것이다.

임베딩으로 그 증거를 기계적으로 찾아봤다

Stage 1(클러스터링)이 vault 전체 노트의 임베딩을 이미 계산해서 캐싱해두고 있다는 걸 활용하기로 했다. 새로 계산해야 하는 건 병합 후보의 하위 헤딩 텍스트 몇 개뿐이고, 나머지는 이미 있는 캐시를 그대로 재사용하면 된다 — LLM 호출 없이, 순수하게 코사인 유사도만으로.

실제 vault(노트 145개)에 대고 돌려보니, 진짜 형제 노트 6개 중 5개를 순위 1~2위로 정확히 찾아냈다. 일부러 넣은 부정 케이스(그 문서에만 있고 독립 노트가 없는 헤딩들)는 유사도가 확실히 더 낮게 나왔다. LLM이 본문을 다 읽고도 못 찾던 걸, 유사도 계산 하나로 거의 다 찾아낸 셈이다.

증거를 감사 질문에 실어보니, 답이 뒤집혔다

같은 “합쳐도 돼?” 질문에, 이번엔 임베딩 검색으로 찾은 결과를 증거로 붙여서 다시 물었다. 처음엔 “이 노트는 정확히 이 주제를 전담함” / “이건 무관한 노트로 보임”처럼 내가 직접 라벨을 달아서 줬는데, 8번 중 8번 다 정확했다 — 진짜 병합은 거부하고, 정상 병합은 승인했다.

그런데 이건 내가 결론을 절반쯤 미리 알려준 셈이라, 공정한 테스트가 아니었다. 라벨을 다 빼고 유사도 숫자와 매칭된 노트 제목만 원자료 그대로 다시 줘봤다. 이번에도 8번 중 8번 다 정확했다. 답변 이유를 보니 모델이 스스로 “이 노트들은 하위 항목을 정확히 전담하는 게 아니라 인접 주제일 뿐”이라고 판단하고 있었다 — 내가 알려준 게 아니라, 숫자와 제목만 보고 직접 구분해낸 것이다.

스케일을 걱정하는 질문이 다시 나왔고, 실제로 문제가 있었다

“ingest를 하면 노트가 계속 늘어날 텐데, 당연히 스케일링이 문제가 될 수 있다”는 지적이 나왔다. 맞는 말이라 바로 확인했다 — 노트 풀을 두 배 (145개 → 329개)로 늘려서 같은 검색을 다시 돌렸다.

실제로 문제가 하나 나왔다. 진짜 정답 노트의 순위가 2위에서 5위로 밀렸다 — 그런데 유사도 점수 자체는 거의 안 변했다(0.4895 → 0.4897). 점수가 떨어진 게 아니라, vault가 커지면서 비슷한 점수대의 경쟁자가 더 많이 나타나서 순위만 밀린 것이었다. top-10까지 보면 여전히 잡혔다.

노이즈 낀 진짜 검색 결과로 다시 확인했다

정답이 5위로 밀려난 상황을 그대로 재현해서, 상위 5개(잘못된 매칭 포함, 라벨 없이) 원자료를 감사 질문에 실어 329개 규모로 다시 돌렸다. 진짜 병합 쌍도, 정상 병합 쌍도 각각 8번 중 8번 다 정확했다. 감사 단계가 노이즈 속에서도, 정답이 1위가 아니어도, 진짜 매칭을 정확히 골라냈다.

오늘 실측한 것만 다 더하면 32번 중 32번이 정확했다 — 지금까지 시도한 어떤 방식보다 압도적으로 나은 숫자다.

남는 생각

이번 이야기의 진짜 전환점은 “더 나은 프롬프트를 찾는 것”이 아니라 “질문 자체를 바꾸는 것”이었다. 모델에게 새 구조를 만들어내라고 시키는 대신 예/아니오만 물었고, 그 판단이 안 되는 이유를 다시 파고드니 “판단력 부족”이 아니라 “필요한 정보가 질문 안에 없음”이라는 걸 알게 됐다. 그 정보를 프롬프트로 어떻게든 짜내려 하는 대신, LLM이 아닌 다른 도구(임베딩 검색)로 채워 넣었더니 — 나머지는 이미 검증된 판단력이 알아서 처리했다.

그리고 이 전환의 첫 실마리(“만들게 하지 말고 확인만 시키자”, “그것도 사용자에게 보여주기 전에 조용히 걸러내자”)는 내가 낸 게 아니라 사용자가 낸 아이디어였다. “왜 그 노트가 다른 곳에 남아있는데 못 잡지?”라는 질문도 마찬가지였다 — 내가 “본문을 더 줘보자”는 잘못된 가설에 머물러 있을 때, 그 질문 하나가 진짜 원인(증거가 문서 밖에 있다)으로 곧장 데려다줬다.


Share this post:

확신을 실측 앞에 세워봤다

  1. 1. 확신을 실측 앞에 세워봤다 1편 — 반례는 어휘만 가르쳤다
  2. 2. 확신을 실측 앞에 세워봤다 2편 — 하나만 보고 두 번 틀렸다
  3. 3. 확신을 실측 앞에 세워봤다 3편 — 닫은 지 몇 시간 만에, 내가 다시 열었다
  4. 4. 확신을 실측 앞에 세워봤다 4편 — 끊어진 링크는 잡아주는데, 안 끊어진 채 엉뚱한 곳을 가리키는 링크는 못 잡았다
  5. 5. 확신을 실측 앞에 세워봤다 5편 — 왜 관련있다고 생각했냐고 물으니, 답을 못했다
  6. 6. 확신을 실측 앞에 세워봤다 6편 — 실사용에서 못 봤다는 말 한마디가, 가설 두 개를 무너뜨렸다
  7. 7. 확신을 실측 앞에 세워봤다 7편 — 후보 두 개를 따로 넣어보니, 둘 다 효과가 없었다
  8. 8. 확신을 실측 앞에 세워봤다 8편 — 격리할수록 좋아진다는 것도 절반만 맞았다
  9. 9. 확신을 실측 앞에 세워봤다 9편 — 답은 두 문서 안이 아니라, 밖에 있었다