
AI 기억 검색에 recall gate 도입하기: 오래된 기억, 모호한 일치, 미승인 메모를 그대로 사용하지 않기
요약
AI 에이전트의 장기 기억 검색 시, 부적절한 정보를 필터링하기 위한 'recall gate' 설계 방식을 제안합니다. 유사도 점수뿐만 아니라 스코프, 승인 상태, 최신성 등을 검증하여 답변의 정확도를 높이는 구현 가이드를 다룹니다.
핵심 포인트
- 유사도 점수 외에 스코프(userId, agentId 등)를 통한 필터링이 필수적임
- 검색 엔진 단계에서 스코프를 포함하여 데이터를 좁히는 것이 안전함
- 임계값(Threshold)은 고정하지 말고 로그와 테스트를 통해 동적으로 관리해야 함
- 오래된 정보나 미승인 메모가 프롬프트에 섞이지 않도록 검사 계층을 구축해야 함
AI 에이전트에게 장기 기억 (Long-term Memory)을 부여하면, 검색을 통해 과거의 메모를 불러올 수 있게 됩니다. 하지만 검색에서 유사한 메모가 발견된 것과, 그 메모를 현재의 판단에 사용해도 되는지는 별개의 문제입니다.
오래된 방침, 기각된 안건, 모호하게 닮았을 뿐인 메모, 미승인된 작업 메모를 그대로 프롬프트 (Prompt)에 넣으면, AI는 자신 있게 틀린 답을 내놓습니다.
이 기사에서는 기억 검색 뒤에 배치할 recall gate의 설계를 정리합니다. 상정하고 있는 구성은 MCP 서버나 사내 AI 에이전트가 저장된 메모를 검색하여 프롬프트에 넣는 구조입니다. TypeScript로 구현 가능한 최소 스키마, 제외한 이유를 남기는 로그, dry-run으로 확인하는 테스트 관점까지 포함합니다.
중요한 것은 검색을 강화하는 것이 아니라, 사용해서는 안 될 기억을 섞지 않는 것입니다. AI 파트너의 기억은 늘어날수록 편리해지지만, 동시에 "예전에는 맞았지만 지금은 틀린", "다른 프로젝트에서는 맞지만 현재 사용자에게는 틀린" 사고도 늘어납니다.
recall gate는 검색 결과를 LLM에게 전달하기 전의 검사 계층입니다.
type MemoryHit = {
id: string;
title: string;
...
벡터 검색 (Vector Search)은 MemoryHit[]를 반환합니다. recall gate는 그중에서 사용해도 좋은 기억만을 선택합니다.
검색 엔진이나 RAG 구현에서는 자칫 유사도 점수 (Similarity Score)만 보기 쉽습니다. 하지만 운용 단계에서는 점수 이전에 확인해야 할 것들이 있습니다. userId, agentId, memorySpaceId, 승인 상태, 업데이트 날짜, 공개 범위입니다. 이것들을 확인하지 않고 "유사하니까 넣는다"라고 하면, 다른 사용자의 사정이나 오래된 결정이 현재의 답변에 섞이게 됩니다.
가장 먼저 확인해야 할 것은 유사도가 아니라 스코프 (Scope)입니다. 누구의, 어떤 AI의, 어떤 용도의 기억인지를 좁힌 다음에 검색합니다.
type MemoryScope = {
userId: string;
agentId: string;
...
스코프 외의 기억은 점수가 높더라도 사용하지 않습니다. 유사한 타인의 기억은 가장 위험한 검색 결과입니다.
구현 시에는 벡터 검색 후에 스코프를 확인하는 것이 아니라, 가능하다면 검색 조건에 스코프를 포함시킵니다. DB 측에서 필터링할 수 있다면, 데이터를 가져오기 전에 좁히는 것이 더 안전합니다.
type SearchQuery = {
text: string;
scope: MemoryScope;
...
route_id를 가지게 하면 게시 계정이나 매체의 혼동도 막기 쉬워집니다. 예를 들어 공식 계정용 게시 규칙과 개인 계정용 말투 메모가 유사하더라도, route가 다르면 후보에서 제외할 수 있습니다.
다음으로 유사도 점수의 최저선을 설정합니다.
function enoughScore(hit: MemoryHit): boolean {
return hit.score >= 0.78;
}
임계값 (Threshold)은 고정된 것으로 끝나지 않습니다. 실패 로그를 보면서 조정합니다. 특히 짧은 쿼리에서는 우연한 일치가 늘어나기 때문에 임계값을 높게 설정합니다.
실무에서는 똑같은 0.78이라도 검색기나 임베딩 모델 (Embedding Model)에 따라 의미가 달라집니다. 따라서 설정값은 코드에 직접 작성하기보다, 환경이나 memory space별로 관리하는 것이 운용하기 쉽습니다.
type RecallGateConfig = {
minScore: number;
maxAgeDays: number;
...
AI 에이전트의 답변이 어긋날 때, minScore를 바꾸는 것만으로 개선되는 경우가 있습니다. 반면, 점수를 너무 높이면 필요한 기억을 가져오지 못합니다. 그렇기 때문에 후술할 로그와 테스트를 통해 "제외한 이유"와 "사용한 이유"를 남깁니다.
기억에는 신선도 (Freshness)가 있습니다. 예전에는 맞았지만, 지금은 다른 방침이 되어 있을 수 있습니다.
function isFresh(hit: MemoryHit, now = new Date()): boolean {
const ageMs = now.getTime() - new Date(hit.updatedAt).getTime();
const days = ageMs / 1000 / 60 / 60 / 24;
...
물론 모든 기억을 90일 만에 버리는 것은 아닙니다. 장기 방침은 별도의 태그로 유지합니다. 여기서 중요한 것은 오래된 운용 메모를 무조건 현재의 정답으로 취급하지 않는 것입니다.
장기 방침(Long-term policy)과 단기 운용 메모는 동일한 신선도(freshness) 규칙으로 취급하지 않는 것이 안전합니다. 예를 들어, "브랜드의 핵심"이나 "법무상의 금지 사항"은 오래되었더라도 유효할 수 있습니다. 반면, "이번 주의 게시처", "오늘의 route_id", "일시적인 회피책"은 금방 구식이 됩니다.
function isFresh(hit: MemoryHit, config: RecallGateConfig, now = new Date()): boolean {
if (hit.tags.includes("long_term_policy")) return true;
if (hit.tags.includes("legal_stopline")) return true;
...
신선도(freshness)는 오래된 기억을 삭제하는 메커니즘이 아닙니다. 삭제가 아니라, 실행 판단에 섞이지 않도록 하기 위한 게이트(gate)입니다. 나중에 조사하기 위해서는 오래된 기억도 남겨두고 싶으므로, 검색 대상과 실행 판단 대상을 분리합니다.
작업 메모와 채택된 방침을 분리합니다.
function isApproved(hit: MemoryHit, config: RecallGateConfig): boolean {
if (config.allowDraft) return hit.status !== "rejected";
return hit.status === "approved";
...
AI 에이전트는 중간 안이나 기각된 안도 기록합니다. 그 자체로는 유용합니다. 다만, 실행 판단에 사용하는 기억은 승인된(approved) 상태로 제한합니다.
특히 위험한 것은 Discord 대화나 작업 로그에 있는 "중간 안"입니다. 대화 도중에는 한때 괜찮아 보였던 안이 나중에 기각될 수 있습니다. 이를 메모리(memory)에 남기는 것 자체는 문제가 없지만, draft와 approved를 동일한 가중치로 다루면 다음 세션에서 오래된 안이 부활하게 됩니다.
승인 상태는 수동 라벨로 처리해도 좋지만, 정본(official document)으로 승격되었을 때 자동으로 approved로 설정되도록 설계하는 것이 편리합니다. 반대로, superseded, 旧方針 (구 방침), 使用禁止 (사용 금지)가 포함된 문서는 검색 결과에 나오더라도 실행 판단에는 사용하지 않도록 합니다.
function isNotSuperseded(hit: MemoryHit): boolean {
const text = `${hit.title}\n${hit.body}`.toLowerCase();
return !["superseded", "旧方針", "使用禁止"].some((term) =>
...
최소 구현은 다음과 같습니다.
type RejectedRecall = {
id: string;
title: string;
...
filter()를 연결하는 것만으로도 동작하지만, 운용 측면에서는 탈락시킨 이유가 남지 않으면 곤란합니다. AI가 실수했을 때 "검색을 못한 것"인지, "검색은 되었으나 게이트에서 탈락시킨 것"인지 구분할 수 없기 때문입니다.
recall gate는 처음부터 실제 답변에 포함하기보다, dry-run을 통해 동작을 확인할 수 있도록 해두는 것이 안전합니다.
type RecallAuditLog = {
at: string;
query: string;
...
dry-run에서는 LLM에 실제로 전달하지 않고, 어떤 기억이 채택되는지만 출력합니다. 새로운 메모리 소스(memory source)를 추가했을 때, 임계값(threshold)을 변경했을 때, 혹은 정본화 규칙을 변경했을 때, 갑작스럽게 답변 품질에 영향을 주지 않고 검증할 수 있습니다.
async function previewRecall(query: SearchQuery): Promise<RecallAuditLog> {
const hits = await searchScopedMemory(query);
const result = recallGate(hits);
...
이 로그는 일반적인 애플리케이션 로그와 분리해 두면 다루기 쉽습니다. 사용자의 개인정보를 포함하는 본문을 그대로 출력하지 않고, id, title, reason, scope만 남기는 것이 더 안전합니다.
recall gate는 단위 테스트(unit test)를 작성하기 쉬운 계층입니다. 검색기나 LLM을 모킹(mocking)하여, 입력된 MemoryHit[]에 대해 채택/기각 결과만을 검증합니다.
import { describe, expect, it } from "vitest";
describe("recallGate", () => {
it("rejects stale draft memory even when score is high", () => {
...
최소한의 테스트는 다음 5가지입니다.
- scope 외의 기억이 섞이지 않는지
- score가 부족한 기억을 탈락시키는지
- 오래된 단기 메모를 탈락시키는지
draft와rejected를 실행 판단에 사용하지 않는지superseded나사용 금지를 포함한 정본을 탈락시키는지
나아가, 통합 테스트(integration test)에서는 dry-run 로그를 확인합니다. 예를 들어 "구 정책이 상위에 반환되었으나, 게이트(gate)에서 탈락했다"는 것을 로그로 확인할 수 있다면, 검색기(retriever) 자체를 과도하게 조정할 필요가 없습니다.
MCP 서버에 recall gate를 넣는 경우, 도구(tool)의 반환값에도 주의해야 합니다. search_memory가 가공되지 않은 검색 결과(raw search results)를 반환하는지, 아니면 recall gate를 거친 후의 결과를 반환하는지를 구분해 두면 디버깅이 쉬워집니다.
type SearchMemoryToolResult = {
accepted: Array<Pick<MemoryHit, "id" | "title" | "body" | "updatedAt">>;
audit: {
...
실제 운영 중인 AI 에이전트에게 전달하는 것은 accepted뿐입니다. 반면, 운영자를 위한 로그에는 기각 사유를 남깁니다. 이 둘을 분리하지 않으면, LLM이 "기각된 기억"까지 읽어버리게 되어 게이트의 의미가 퇴색됩니다.
또한, 도구 이름이나 route_id를 명시해 두면 게시 대상 계정의 혼동을 방지할 수 있습니다. Agent Memories처럼 공식 계정, 개인 발신, MCP 연구실 등 여러 경로(route)를 가진 경우에는 기억 검색에도 route의 경계가 필요합니다.
기억 검색은 단순히 유사한 메모를 반환하는 것만으로는 부족합니다. LLM에 전달하기 전에, 사용해도 좋은 기억인지 판정해야 합니다.
최소한의 recall gate는 다음 4가지입니다.
- scope로 먼저 범위를 좁힐 것
- 유사도 임계값(similarity threshold)을 설정할 것
- 오래된 운영 메모를 무조건 사용하지 말 것
- 미승인 메모를 실행 판단에 사용하지 말 것
나아가 실제 운영 환경에서는 superseded의 탐지, dry-run, 채택/기각 로그, Vitest 등의 테스트 관점을 포함해 두면 나중에 원인을 추적하기 쉬워집니다.
AI 파트너가 오래 성장할수록 기억은 늘어납니다. 늘어난 기억을 단순히 채워 넣는 것이 아니라, 현재의 판단에 필요한 것만을 골라내는 층(layer)이 필요합니다. recall gate는 기억을 현명하게 사용하기 위한 필터이자, 오래된 약속이나 타인의 문맥이 섞이지 않도록 하는 안전장치입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기