--- title: DevLog @ 2025.04.14 category: DevLog date: 2025-04-14 --- ## 들어가며 [지난번](../DevLog-2025.04.06/#기억-시스템)에는 AIRI 의 기억 시스템을 이야기했습니다. 오늘은 이렇게 복잡한 기억 시스템을 어떻게 구현할지 더 깊이 파고들고 앞으로의 전망도 살펴보겠습니다. ## 검색 엔진에서 시작하기 검색 엔진은 검색 성능 요구가 높습니다. 이를 위해 시스템은 2단계 정렬 과정을 구현합니다: - **기본 정렬 (Coarse Ranking, 조 정렬)** - **비즈니스 정렬 (Fine Ranking, 정밀 정렬)** 기본 정렬은 1차 선별 역할로, 검색 결과에서 양질의 문서를 빠르게 골라 상위 N 개를 추출합니다. 그다음 정밀 정렬로 세밀하게 점수를 매겨 최종적으로 최적의 결과를 사용자에게 돌려줍니다. **즉 기본 정렬은 성능에 큰 영향을 주고, 비즈니스 정렬은 최종 랭킹 품질에 영향을 줍니다.** 따라서 기본 정렬은 최대한 단순하고 효과적이어야 하며, 비즈니스 정렬에서 핵심 요소만 뽑아 와야 합니다. 현재 기본 정렬과 비즈니스 정렬 모두 정렬 표현식으로 구성됩니다. ### OpenSearch / Wentian 엔진 DSL [^1] Neko 가 많이 써 온 알리바바 클라우드 OpenSearch 를 예로 들어 보겠습니다. 검색 엔진에는 재정렬을 위한 내장 함수들이 있습니다: #### `static_bm25` 정적 텍스트 연관도. 전통적인 NLP 방식으로, 질의와 문서의 일치 정도를 측정합니다. RAG 의 _similarity score_ 와 비슷합니다. 값 범위: 0~1 #### `exact_match_boost` 사용자가 지정한 질의어의 최대 가중치를 얻습니다. score boost 함수라고도 합니다. 입력한 키워드가 토크나이즈 전에 문서 필드(제목, 본문 등)의 "내용"에 적중하는 경우에 해당합니다. 예를 들어 "How to make Neurosama" 를 검색할 때, "Neurosama" 라는 정확한 구절을 담은 문서와 페이지가 "Neuro" 와 "sama" 가 따로 나오는 문서보다 높은 점수를 받아야 합니다. #### `timeliness`, `timeliness_ms` 시의성 점수. 더 새로운 내용일수록 연관도가 높습니다. ### 데이터는 어떻게 저장되나? 알리바바 클라우드의 OpenSearch, Grafana 내장 Loki, 그 이전의 ElasticSearch 엔진(일부 영상 사이트는 ElasticSearch 기반으로 개발됐습니다) 등 어떤 검색 엔진이든, 사용하기 전에 데이터를 그 검색 엔진 안의 **별도 데이터 구조로 재가공**해야 합니다. 이 재가공은 어떻게 구현할까요? 여기에 DTS 가 필요합니다. #### DTS [^2] **DTS** 라는 개념을 좀 더 소개하겠습니다. Data Transformation Services 는 비즈니스 데이터베이스와 검색 엔진 인스턴스 사이의 **통신과 데이터 동기화**를 담당하는 시스템입니다. 구현 원리: MySQL 과 Postgres 의 네이티브 watch/subscribe 이벤트 기능으로 테이블 변경을 감지한 뒤 데이터를 검색 엔진으로 동기화합니다. 이 과정에서 데이터는 원하는 형식으로 직렬화되며 데이터 구조 변환(ETL: extract, transform, load)을 거칩니다. 조 정렬 검색을 수행할 때, 데이터베이스의 _뷰_ 에서 검색하는 것과 비슷하지 않나요? 가상 테이블처럼요. 어느 정도 맞는 이해입니다. 다만 뷰는 보통 데이터베이스와 같은 하부 자료구조(B+ 트리)를 쓰는 반면, 검색 엔진은 그래프나 특화된 인덱스 키-값 데이터베이스 같은 다양한 전용 자료구조를 쓸 수 있습니다. ### 토크나이제이션? 전통적인 검색 엔진에서 중국어 문서를 입력하면 이런 과정을 거칩니다: - 문장 분할 (큰 문단을 문장으로 쪼개기) - 단어 분할 (문장을 단어/글자, 명사, 동사 등으로 쪼개기) - 병음 변환 - 현재 사전 커버리지 설정에 따라 앞의 결과를 매핑하고 덮어쓰기 - 기본적인 벡터화와 특징 추출 수행 - 저장 계층에 기록 영어도 토크나이제이션이 필요하지만 훨씬 간단합니다. 공백이 단어 경계 역할을 하니까요. ### 성능은 어떻게 최적화하나? - 연산 집약적 - 내부 작업 스케줄러를 여러 개 두고 데이터를 천천히 인덱싱 - 해밍 거리, 코사인 거리 같은 전통 NLP 기법은 미리 계산해 저장 가능 - 인기 검색어는 토크나이제이션과 정렬 결과를 캐시 가능 - 데이터 레이크하우스? AWS 에서 흔히 쓰이며 보통 여러 데이터베이스나 데이터 소스를 아우르는 집계 질의에 씁니다. 매우 느려서 사실상 데이터 분석과 BI 에만 사용합니다 ### Recall 이란? Recall(재현/검색)은 키워드를 입력했을 때 기대한 문서를 실제로 찾아낼 수 있는지를 말합니다. Search 와의 차이는? Search 는 "사용자가 시작하는 동작"이고, Recall 은 "검색에 응답하기 위해 기계가 하는 일"입니다. ### Reranking 이란? Reranking 의 의미는 이렇습니다. 임베딩 모델 벡터를 기반으로 ANN(Approximate Nearest Neighbor)과 KNN(K-Nearest Neighbor)의 벡터 거리 정렬에만 의존하면 실제로는 편향이 생깁니다. 앞서 OpenSearch 에서 소개한 exact_match_boost 와 timeliness 함수가 더는 존재하지 않게 되기 때문입니다. 검색된 문서에 다른 필드와 단계를 기준으로 한 정렬을 추가하고 싶다면 어떻게 할까요? RAG 는 이제 reranking model 이라는 새로운 과정을 대중화했습니다. 본질적으로 **별도의 전문가 모델을 써서 1차로 검색된 데이터를 자동으로 재정렬**하는 것입니다. 하지만 reranking 으로도 기억 계층의 여러 문제를 풀 수는 없습니다. 망각 곡선, 기억 강화, 무작위 기억 회상, 감정이 영향을 미치는 재정렬 점수 같은 것들은 reranking 모델이 다룰 수 있는 영역이 아닙니다. AIRI 를 위한 좋은 기억 계층을 만들려면 RAG 의 기본 능력과 과거 검색 엔진의 재정렬 경험을 결합해 좋은 재정렬 메커니즘을 세워야 합니다. ## 기억 계층 실험 플랫폼 [Project AIRI Memory Driver @duckdb/duckdb-wasm Playground](https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-decay) ![](/en/blog/DevLog-2025.04.14/assets/memory-driver.avif) 왼쪽에 강조된 "half life" 는 기억의 반감기입니다. 기본값으로 시간은 1초 = 1일 로 흐르므로, 7초가 지나면 기억 점수가 절반이 됩니다. 기억 점수란 무엇일까요? 기억 점수는 주로 이것으로 제어됩니다: ![](/en/blog/DevLog-2025.04.14/assets/memory-controler.avif) 그 결과로 나온 점수가 현재 점수입니다. original 은 무엇일까요? 초기화 시점의 점수입니다. 예: 원래 점수가 523 인데 현재 점수는 실제로 서서히 줄어들고 있습니다: ![](/en/blog/DevLog-2025.04.14/assets/memory-decay.avif) 계속하기 전에 짚고 넘어가면, 이 망각 곡선 SQL 은 무상태(stateless)입니다. 무상태란 무슨 뜻일까요? 점수를 갱신하기 위해 데이터베이스에서 실시간 작업을 돌릴 필요 없이, "현재 시각"을 기준으로 망각 함수를 바로 적용해 점수를 계산한다는 뜻입니다. 그럼 현재 점수가 떨어지면 어떻게 할까요? 이 문제를 풀려면 **기억을 강화하는** 방법이 필요합니다. ## 인간의 기억 시스템에 빗대어 간격 반복에서 언급되는 망각 곡선과 심리학의 기억 시스템 기본 작동 원리를 바탕으로 [^3] 인간의 기억은 몇 가지로 나눌 수 있다는 걸 압니다: - 작업 기억 - 단기 기억 - 장기 기억 - 근육 기억 작업 기억은 기억해 둘 필요성이 가장 낮습니다. 단기 기억은 망각 곡선에 따라 강도(점수)가 점차 감쇠합니다. 이 시점에는 이 과정을 모델링할 단기 기억 시뮬레이션 함수가 필요합니다. 장기 기억은 중요하며 반감기가 길고, 단기 기억에서 진화한 것입니다. 마지막으로 근육 기억은 기억의 한 종류라기보다 이미 형성된 조건 반사에 가깝습니다. ## AIRI 는 어떻게 설계해야 할까? 여기서 AIRI 의 구현 원리를 엿볼 수 있습니다: - 작업 기억은 messages 배열 같은 것 - 단기 기억은 잘 회상되지 않는 RAG 기억 항목 같은 것으로, 새로운 것일수록 회상되기 쉬움 - 장기 기억은 쉽게 회상되지만 흐릿해지는 RAG 항목 같은 것으로, 과거 회상 횟수가 많을수록 회상되기 쉬움 - 근육 기억은 고정된 패턴 같은 것으로, A 가 나타나면 ActionA 와 MemoryA 가 함께 나타나는, 정확한 매칭 메커니즘에 가까움 그런데 이 설계가 맞을까요? 분명 여기서는 시간적 연관도와 검색 횟수라는 두 차원만 도입했습니다. 더 복잡한 시스템을 추구하기 시작하면 이것만으로는 한계에 부딪힙니다. ### 빠른 복습 DevLog 에서 언급한 정렬 표현식을 복습해 보면 이해에 도움이 될 겁니다. ![](/en/blog/DevLog-2025.04.14/assets/review-1.avif) 코사인 거리는 "연관도" 이며, 가장 기본적인 조 정렬입니다: ![](/en/blog/DevLog-2025.04.14/assets/review-2.avif) 이제 시간을 개입시켜야 하므로 시간 거리를 저장할 필드를 하나 더 추가하고, 결합 점수 `(1.2 * similarity) + (0.2 * time_relevance)` 를 저장할 별도 필드를 만듭니다. 여기서 의미적 연관도는 1.2배 가중치(증폭 계수이며 1 미만일 필요는 없습니다), 시간 거리 연관도는 0.2배 가중치를 갖습니다. 이렇게 하면 무상태 다중 필드 연관도 정렬 SQL 을 깔끔하게 구현하면서 매개변수(1.2 와 0.2)로 조정도 할 수 있습니다. 기억 상세 카드에서는 "simulate retrieval" 을 클릭해 기억 회상을 능동적으로 트리거할 수 있습니다. ![](/en/blog/DevLog-2025.04.14/assets/memory-retrieval.avif) 현재 데모에서는 원본 테이블의 검색 횟수 필드에 UPDATE 문으로 +1 을 더하는 단순한 방식으로 구현되어 있습니다. 여기에 숨은 함정이 있습니다. 이건 여전히 단일 차원 계산이라 "회상 = 강화" 와 같습니다. 하지만 현실 세계는 그렇지 않습니다. 기억은 슬플 수도 기쁠 수도 있고, 슬픔은 부정적 피드백을, 기쁨은 긍정적 피드백을 가져옵니다. 그래서 이 부분은 아직 완성하지 못했습니다. ## 감정? https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-simulator 이 새 시뮬레이터에는 감정 관련 시뮬레이션이 포함되어 있습니다: ![](/en/blog/DevLog-2025.04.14/assets/memory-emotional-simulator.avif) ### 감정은 기억과 관련이 있을까? 사탕을 먹고 싶은데 못 먹는 건 단순한 문제입니다. 못 먹으면 당연히 기분이 나쁘죠. 그러다 보면 감정이 사실 기억과 관련되어 있다는 걸 알게 됩니다. "과거의 어떤 기억이 즐거워서 다시 겪고 싶은데", "그 기억 속 상황을 지금은 재현할 수 없어서", 그래서 "얻지 못해 기분이 나쁜" 것이죠. 기억 데이터베이스에 "기쁨" 과 "혐오" 점수를 저장할 수 있습니다: ![](/en/blog/DevLog-2025.04.14/assets/memory-emotional-score.avif) ### PTSD? PTSD 에는 보통 "trigger" 와 "flashback" 이라는 두 단어가 따라옵니다. 분명 PTSD 관련 기억은 억제되어야 하고, 혐오와 트라우마 점수가 높아야 합니다. 하지만 실제로 PTSD 관련 기억은 갑자기 떠오를 수 있습니다. 생체 모방과 데이터 시뮬레이션 관점에서는 난수를 써서 이 효과를 구현할 수 있습니다. https://yutsuki.moe/2019/09/a0d0fa1b/ 의 감정 모델을 참고할 수 있습니다. ![](/en/blog/DevLog-2025.04.14/assets/memory-emotional-model.avif) ## 아직 할 일이 많습니다... 예를 들어 지금 ReLU 의 감정은 어떤가요? ReLU 는 누군가에 대한 나쁜 기억을 갖고 있을까요? 기억은 기쁨과 슬픔이 함께 극성을 이루는 항목으로 나타날까요? 욕구는요? 소원 시스템을 만들어야 할까요? _백그라운드 작업_ 과 비슷하게, 발생한 기억을 하나씩 처리하고 인덱싱하면서 최근 경험을 바탕으로 과거 기억의 여러 점수를 수정하는 꿈꾸기 에이전트나 잠재의식 에이전트를 만들 수도 있습니다. 하지만 반드시 "꿈꾸기" 과정이 필요한 건 아니고, 그냥 "백그라운드 작업" 이면 됩니다. 재인덱싱 관점에서 보면 꿈꾸기 에이전트와 잠재의식 에이전트는 인덱스를 다시 만드는 일과 같습니다. 여기까지 오면 [Mem0](https://docs.mem0.ai/overview) 나 [Zep Memory](https://help.getzep.com/memory) 같은 라이브러리가 롤플레잉과 감정 AI 에서는 전혀 쓸모없다는 걸 알게 됩니다 :( 갈 길이 멀고, 저희는 계속 노력해야 합니다. ## 참고 문헌 [^1]: https://help.aliyun.com/zh/open-search/industry-algorithm-edition/rough-sort-functions [^2]: https://help.aliyun.com/zh/open-search/industry-algorithm-edition/configure-dts-real-time-synchronization [^3]: https://zh.wikipedia.org/wiki/%E9%81%97%E5%BF%98%E6%9B%B2%E7%BA%BF