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

한국어 노트와 영어 노트가 안 합쳐지는 이유를 임베딩 유사도로 확인했다

Read in English →

두 변수를 하나로 착각할 뻔했다 · 5/7편

4편까지의 결론은 아직 완전히 마무리되지 않은 채였다 — 노트북 2의 실제 openaiReasoningEffort 값을 확인해야 최종 결론을 내릴 수 있는 상태였다. 그런데 그 확인이 미뤄지는 동안, 완전히 다른 질문 하나가 조사의 방향을 틀었다.

왜 이 두 노트는 안 합쳐질까 — 한국어·영어 중복 노트 병합

1편에서 설명한 위키 정리 기능(refactor)은 같은 걸 다루는 노트 두 개를 찾아 병합을 제안한다. 그런데 이 볼트에는 유독 잘 안 잡히는 패턴이 있었다. “심리적 안전감”이라는 한국어 노트와 “Psychological Safety”라는 영어 노트 — 완전히 같은 개념을 다루는데도 refactor를 몇 번을 돌려도 하나로 합쳐지지 않았다.

코드를 뜯어보니 이 플러그인이 중복 노트를 잡는 방식은 두 겹으로 돼 있었다. 첫 번째는 “완전히 같은 제목의 파일이 두 개 생기면 _dup2라는 접미사를 붙인다”는 규칙에 기대는 방식이다 — 예를 들어 “클로드.md”와 “클로드_dup2.md”처럼 제목 문자열이 글자 그대로 같은 경우, 강제로 같은 묶음에 넣고 우선 병합 후보로 검토하게 한다. 심지어 이 첫 번째 방식이 실수로 놓치는 경우를 대비해, LLM 호출 없이 코드만으로 그 패턴을 다시 한번 스캔하는 두 번째 안전장치까지 이미 마련돼 있었다.

문제는 이 이중 안전장치 둘 다 제목 문자열이 정확히 같아야만 작동한다는 점이었다. “심리적 안전감”과 “Psychological Safety”는 같은 개념이지만 제목 자체가 다른 언어라, 이 안전장치에는 아예 걸리지 않는다. 이 경우엔 노트의 의미를 벡터로 바꿔 서로 얼마나 비슷한지 계산하는 “임베딩 유사도”라는 훨씬 느슨한 판단에만 기대게 되는데, 그 판단이 어느 정도로 느슨한지는 실제로 재보기 전까진 알 수 없었다.

실제 데이터로 재보기로 했다 — 한국어·영어 임베딩 코사인 유사도

그래서 이 볼트의 실제 임베딩 캐시 파일(노트마다 계산해둔 벡터값을 저장해 놓은 파일)을 꺼내 왔다. 벡터는 OpenAI의 text-embedding-3-small로 계산해 256차원으로 줄여 저장한 것이다. 이 파일이 있으면 가짜 예시가 아니라 이 볼트에 실제로 존재하는 노트들의 진짜 유사도를 직접 계산해볼 수 있었다.

결과는 다음과 같았다(코사인 유사도 — 두 벡터가 얼마나 같은 방향을 가리키는지를 0~1 사이 숫자로 나타낸 값, 1에 가까울수록 같은 의미라는 뜻이다).

쌍유사도
같은 언어 안의 중복(Anthropic ↔ Anthropic_dup2, 심리적 안전감 ↔ 심리적 안전감_dup2 등)0.84 ~ 0.95
심리적 안전감 ↔ Psychological Safety(한/영, 같은 개념)0.77 정도
완전히 무관한 두 노트0.35 ~ 0.46

숫자가 명확하게 세 구간으로 나뉘었다. 같은 언어 안의 진짜 중복은 0.84 이상, 완전히 무관한 노트는 0.46 이하. 그 사이, 언어만 다른 같은 개념 쌍은 0.77 정도로 뚜렷하게 낮은 위치에 있었다. 무관한 쌍보다는 확실히 높지만, 같은 언어 중복보다는 눈에 띄게 낮은 “애매한 중간지대”였다.

이미 만들어져 있던 장치, 문턱값만 잘못 맞춰져 있었다 — 임베딩 유사도 임계값

여기서 또 하나 흥미로운 사실이 드러났다. 이 문제를 풀 장치가 사실 이미 코드 안에 있었다. 제목이 다른 노트끼리도 임베딩 유사도로 병합 후보를 찾아 LLM에게 “이 둘, 정말 같은 개념이야?”라고 한 번 더 물어보고 확정하는 검증 파이프라인이 실제 refactor 경로에 이미 연결돼 있었던 것이다. 다만 이 후보를 걸러내는 문턱값이 0.80으로 설정돼 있었는데, 이 값은 같은 언어 중복 사례로만 보정된 값이었다. 실측한 한/영 쌍의 유사도(0.77)는 이 문턱값보다 낮아서, 애초에 “후보”로도 못 들어가고 LLM에게 물어볼 기회조차 없이 걸러지고 있었던 것이다.

병합을 실제로 실행하는 코드도 확인해봤다. 세 개 이상의 노트(예를 들어 “심리적 안전감”, “심리적 안전감_dup2”, “Psychological Safety” 셋을 한 번에)를 합치는 것도 이미 완전히 지원되고 있었다. 막혀 있던 건 딱 하나, 이 셋이 “같은 후보”로 서로를 만나느냐뿐이었다.

20여 분 만에 고치고 릴리스했다

문턱값을 0.80에서 0.70으로 낮췄다. 실측된 한/영 쌍(0.77)보다 0.07 낮게, 무관한 쌍의 상한(0.46)보다는 0.24나 여유 있게 잡은 값이다. 이 문턱값은 최종 판단을 담당하는 게 아니라 “LLM에게 물어볼 후보를 얼마나 넓게 잡을지”만 정하는 역할이라, 낮춘다고 오탐(잘못된 병합)이 늘어날 위험은 없었다 — 최종 정확도는 여전히 그 뒤에 있는 LLM 검증 단계가 책임진다. 유일한 비용은 검증 API 호출이 조금 늘어나는 것뿐이었다. 측정 결과를 기록한 지 20여 분 만에 수정하고 테스트를 거쳐 릴리스했다.

이 발견은 원래 하려던 조사(청크 크기 vs reasoning effort)와는 전혀 다른 갈래에서 나온 것이었지만, 결과적으로 이 볼트가 안고 있던 진짜 약점 하나를 실제 데이터로 증명하고 그 자리에서 고친 사례가 됐다. 그리고 이 수정을 검증하던 중, 똑같은 언어 격차 문제가 아직 남아 있는 다른 구석을 하나 더 발견하게 된다.

6편에서 이어진다.


Share this post:

두 변수를 하나로 착각할 뻔했다

  1. 1. LLM이 정리한 노트 189개가 어느 날 63개가 됐다
  2. 2. LLM 청크 크기를 낮추기만 했는데 fidelity 문제가 사라졌다
  3. 3. 실험을 다시 준비하던 중에 git 충돌 마커가 그대로 커밋됐다
  4. 4. OpenAI reasoning effort는 fidelity 문제의 범인이 아니었다
  5. 5. 한국어 노트와 영어 노트가 안 합쳐지는 이유를 임베딩 유사도로 확인했다
  6. 6. LLM 요청 동시성을 2에서 4로 올렸더니 다른 버그 두 개가 나왔다
  7. 7. 노트북 2의 OpenAI reasoning effort 값이 Medium으로 확인됐다