에이전트 메모리유사도 판정임베딩결정

문장 쌍을 모아 유사도 임계값 0.85를 정한 과정

0.9로 시작했다가 문장 쌍을 모아 실제 임베딩 모델로 재서 0.85로 정했다. 두 방향의 실패 비용이 달라 여유는 한쪽으로 몰았고, 검색 경로에서는 임계를 아예 버렸다.

2026년 8월 22일7분

지난 글에서 에이전트에 붙인 기억 서비스 이야기를 했다. 이번엔 그 안에 박혀 있는 숫자 하나에 대한 이야기다.

사용자가 "나 12월에 이사 가"라고 말하면 모델이 그 사실을 기억으로 쓴다. 그런데 지난달에 이미 "11월에 이사 간다"는 기억이 저장돼 있었다면? 새로 쓸 것인가, 기존 것을 덮을 것인가, 아니면 둘 다 남길 것인가.

판단 기준은 두 문장이 같은 사실인가이고, 이건 임베딩 유사도로 본다. 그러면 몇부터 같은 사실로 봐야 할까.

이 숫자가 어디에 박히는가

불일치갱신·신규격상쓰기 요청model → memory해시 대조유사도 대조threshold A격상 판정threshold B저장자동 추출분episode

노드에 커서를 올리면 연결과 설명이 나타납니다.

임계값이 두 개 필요하다. 하나는 같은 분류 안에서 중복을 병합할 때, 다른 하나는 자동으로 쌓인 낡은 기억을 덮어쓸 때. 뒤쪽이 더 위험하다. 잘못 덮으면 다른 사실이 지워지기 때문이다.

감으로 정하면 어떻게 되는가

처음엔 0.9로 시작했다. 높으면 안전할 것 같아서였다.

실환경에서 바로 걸렸다. 사용자가 "12월로 이사 날짜가 바뀌었다"고 정정했는데, 낡은 「11월 이사」 기억이 검색 결과 1위로 계속 올라왔다. 정정이 저장에 실패한 건 아니었다. 정정과 낡은 기억이 둘 다 살아 있었고, 낡은 쪽이 더 잘 검색됐다.

임계를 높이면 안전할 줄 알았는데, 높을수록 정정이 낡은 기억을 덮지 못하고 둘 다 남았다.

문장 쌍으로 확인해 봤다

두 문장 쌍을 모아 실제 운영과 같은 임베딩 모델로 유사도를 냈다. 쌍은 넷으로 나눴다. 무엇을 원하는지가 무리마다 다르기 때문이다.

  • 같은 사실 — 표현만 다르다. 덮어써야 한다
  • 정정 — 값이 바뀌었다. 덮어써야 한다. 낡은 값이 남으면 위의 사고가 난다
  • 상세 추가 — 같은 사실에 근거와 경위가 붙었다. 덮어쓰면 그 부연을 잃는다
  • 별개 사실 — 분류만 같고 내용이 다르다. 덮어쓰면 남의 기억이 사라진다

앞의 둘은 임계 위에, 뒤의 둘은 아래에 있어야 하니, 네 분포를 한 축에 늘어놓고 자를 자리를 찾으면 된다.

격상 임계값 스윕 — 실물 쌍 51개
0.000.250.500.751.00A 같은 사실 · 20D 정정 · 7B 상세 추가 · 4C 별개 사실 · 200.85
채택 0.85
A 같은 사실
18 / 20
잡힘
D 정정
4 / 7
잡힘
B 상세 추가
0 / 4
안 건드림
C 별개 사실
0 / 20
안 건드림

0.80을 눌러 보면 상세 추가 넷 중 둘이 덮인다. 0.90으로 올리면 아무것도 잘못 덮지 않지만 같은 사실 스무 개 중 여섯 개를 놓친다. 0.85에서만 보호해야 할 두 무리가 온전하다.

0.85 양쪽의 여유

0.85를 고른 건 두 방향의 실패 비용이 달라서다.

+0.22
별개 사실까지의 여유. 덮이면 다른 사실이 사라진다
+0.006
상세 추가까지의 여유. 덮여도 사실은 남고 부연만 잃는다

상세 추가가 덮이면 "왜 그랬는지"를 잃지만 "무엇인지"는 남고, 이력에서 복구도 된다. 별개 사실이 덮이면 그 사실 자체가 사라지고, 사라진 줄도 모른다.

그래서 위험한 쪽에 여유를 몰아줬고, 0.22와 0.006의 차이는 그렇게 생긴 것이다.

같은 원칙이 중복 병합 임계에도 적용됐다. 이쪽은 쌍 28개로 따로 확인했다.

무리뜻minmaxmean
근사 중복같은 사실, 표현만 다름 — 병합 대상0.82170.97060.8885
정당 갱신같은 주제, 사실이 바뀜0.75080.95430.8612
별개 사실분류만 같음 — 병합 금지0.32250.71130.5291
병합해야 할 무리와 건드리면 안 되는 무리 사이의 간격
별개 사실 최대
0.7113
근사 중복 최소
0.8217

두 분포가 겹치지 않았다. 간격 0.1104. 이 안 어디를 잘라도 되지만, 병합 금지 쪽에 여유를 크게 둬서 0.80으로 정했다 — 아래로 0.089, 위로 0.022.

유사도로 안 되는 구간

확인하다 보니 임계값으로 풀리지 않는 지대가 드러났다.

같은 성격의 정정인데 유사도가 갈린다
기존에 저장된 기억
사용자는 리텐션 대시보드를 담당한다.
↓
새로 들어온 정정
0.8592 ~ 0.9428사용자는 코호트 분석 대시보드를 담당한다.
통과임계를 넘는다. 낡은 기억이 덮여 사라진다 — 의도대로다.

정정이라는 성격은 같은데, 문장 틀이 유지되면 잡히고 바뀌면 안 잡힌다. 임계를 낮춰서 해결할 수도 없는 게, 낮추면 진짜 별개 사실까지 함께 덮인다.

이건 유사도로 물을 수 있는 질문이 애초에 아닌 것 같다. 모델이 "이 기억을 대체한다"고 직접 지목하게 해야 풀린다고 보고 있다.

검색 쪽에서는 임계를 아예 버렸다

쓰기 경로에서 임계가 통했으니 읽기 경로에도 두면 되겠다고 생각했다. 검색 결과에서 점수가 낮은 것을 잘라내는 흔한 방법이다.

확인해 보니 반대였다.

검색 점수 임계에 따른 회상 품질 변화
임계 0.5
−13.2%p
임계 0.1
+20.6%p

자를수록 나빠졌다. 원인은 질의 유형마다 점수 스케일이 다르다는 것이었다 — 주제를 묻는 질의는 1위가 0.397, 개인 사실을 묻는 질의는 1위가 0.092였다. 같은 축에 놓고 자르면 개인 사실 쪽은 정답부터 잘린다.

같은 0.3이라도 어떤 질의에서는 최상위고 어떤 질의에서는 잡음이었다. 쓰기에서는 두 문장을 직접 비교하니 점수 자체에 의미가 있는데, 검색에서는 질의마다 점수가 놓이는 범위가 달라진다.

그래서 검색에는 절대 임계를 두지 않고 순서만 쓰고, 결과를 좁히는 일은 점수 대신 조건으로 했다. 사용자 범위로 거른 것만으로 재현율이 이만큼 올랐다.

스코프 필터를 건 뒤의 재현율
필터 없음
31%
사용자 범위
51%

+20%p. 스코프 필터를 보안 장치로만 생각했는데, 실제로는 검색 품질 장치이기도 했다. 확실히 아닌 것을 먼저 빼는 편이 점수로 자르는 것보다 낫다.

이 측정으로 어디까지 말할 수 있나

통계적 유의성 검정이 아니고, 실물 쌍의 경험적 분포가 갈라지는지를 본 결정 지원용 측정이다. 임베딩은 결정적이라 같은 쌍은 항상 같은 값을 내니 반복 측정은 의미가 없고, 남는 불확실성은 이 표본이 모집단을 대표하느냐 하나다.

보호해야 할 한쪽이 희소하다. 상세 추가 무리는 쌍이 넷뿐인데, 그 형태의 기억 자체가 코퍼스에 드물어서 2차 채굴에서도 못 늘렸다. 그러니 0.006이라는 간격은 표본을 더 모아서는 못 좁히고, 운영 로그의 점수 분포를 보면서 보정해야 한다.

도메인도 좁다. 세 가지 업무 페르소나의 대화에서 뽑은 쌍이라, 다른 도메인에서도 같은 값이 나올지는 모르겠다.

이제 0.85가 어디서 나왔냐고 물으면 표를 보여주면 된다. 문장 틀이 바뀐 정정은 임계값과는 다른 방법이 필요할 것 같다. 그 전에 0.85 근처 점수가 운영 로그에 얼마나 자주 찍히는지부터 세어볼 생각이다. 자주 찍히면 임계를 올려야 한다.

끝