본문 바로가기
Agentic AI/Memory

11. 장기기억(8) - PostgreSQL의 memories 테이블과 ChromaDB연결 단계(2)

by 아톨 2026. 9. 12.
반응형

전장의 테스트 결과는 정상입니다. 오히려 이번 결과에서 아주 중요한 것이 몇 가지 확인되었습니다.

특히,

PostgreSQL → Dictionary → ChromaDB → user_id 필터 → 의미 기반 검색

이라는 우리가 만들고 있는 구조가 실제 데이터로 정상 동작하고 있습니다.

이제 말씀하신 대로 다음 단계인 search_memory()의 ChromaDB 전환으로 넘어가겠습니다.


1. 먼저 이번 테스트 결과를 해석해 보겠습니다

① PostgreSQL 쪽은 정상

8개의 Memory가 정확하게 조회되었습니다.

예를 들어:

memory_id   : 30
user_id     : 8
memory_type : goal
content     : 사용자는 저작권 미구매 고객에게 ...
importance  : 9
created_at  : ...
 

우리가 확인하고 싶었던 tuple 구조가 정확히:

[0] memory_id
[1] user_id
[2] memory_type
[3] content
[4] importance
[5] created_at
 

으로 들어오고 있습니다.

이것은 아주 중요합니다.


2. PostgreSQL → ChromaDB 동기화도 정상

☑️ChromaDB에 8개 기억 저장 완료
 

8개의 PostgreSQL Memory가 ChromaDB에 들어갔습니다.

그리고 전체 개수가:

ChromaDB 저장된 전체 Memory 개수: 15
 

인 것도 문제가 아닙니다.

앞서 test_chroma.py에서 넣었던:

memory_1
memory_2
memory_3
 

등의 테스트 데이터가 이미 있기 때문입니다.

즉,

PostgreSQL
    ↓
실제 Memory 8개
    ↓
ChromaDB
    ↓
기존 테스트 데이터 + 새 Memory
    ↓
총 15개
 

인 것입니다.

 


3. 가장 중요한 의미 검색도 정상입니다

검색 질문:

나는 파이썬과 AI 에이전트 개발을 공부하고 있다.
 

그런데 PostgreSQL에서 importance 순으로 가져왔다면 단순히:

importance 9
importance 9
importance 9
importance 9
importance 8
...
 

순서가 될 가능성이 높습니다.

하지만 ChromaDB에서는:

memory_27
memory_29
memory_19
memory_32
memory_31
 

가 나왔습니다.

즉, importance가 아니라 질문의 의미와 얼마나 가까운가를 기준으로 검색되고 있습니다.

이것이 우리가 ChromaDB를 추가하는 핵심 이유입니다.


4. 그런데 한 가지 재미있는 것도 발견됩니다

예를 들어:

memory_30
저작권 미구매 고객에게 자동으로 저작권 안내 전화를 걸고 구매 요청...
 

은 importance가 9입니다.

하지만 이번 질문:

나는 파이썬과 AI 에이전트 개발을 공부하고 있다.

와는 상대적으로 의미가 덜 가깝기 때문에 상위 5개에서 빠졌습니다.

반면:

memory_29
사용자는 Python으로 AI Agent를 만들고 있다.
 

는 질문과 매우 가깝습니다.

이것이 바로:

importance 기반 검색
 

semantic similarity 기반 검색
 

의 차이입니다.


5. 이제 진짜 중요한 단계로 갑니다

현재 memory.py의 search_memory()는 아직:

 
def search_memory(conn, user_id, limit=10):
    memories = get_memories(
        conn,
        user_id=user_id,
        limit=limit,
    )
    return memories
 

입니다.

즉 현재 Agent가 질문을 하면:

사용자 질문
   ↓
search_memory()
   ↓
PostgreSQL
   ↓
importance가 높은 Memory
 

를 가져옵니다.

우리가 앞으로 만들 구조는 이것입니다.

사용자 질문
    │
    ▼
search_memory()
    │
    ▼
ChromaDB
    │
    │ 의미적으로 가까운 Memory 검색
    ▼
관련 Memory ID
    │
    ▼
필요하면 PostgreSQL
    │
    ▼
정확한 Memory 정보
    │
    ▼
Agent
 

즉,

search_memory()를 "PostgreSQL에서 중요도 높은 기억을 가져오는 함수"에서 "ChromaDB에서 질문과 의미적으로 가까운 기억을 찾는 함수"로 바꾸는 것입니다.


6. 그런데 여기서 아주 중요한 설계 결정을 하나 합니다

단순하게 생각하면 이렇게 만들 수도 있습니다.

 
def search_memory(query, user_id, limit=5):
    return search_similar_memories(...)
 

그러면 ChromaDB 결과를 그대로 반환하면 됩니다.

하지만 저는 지금 학습 단계에서는 그렇게 하지 않는 것을 권합니다.

왜냐하면 우리의 전체 구조를 생각하면:

PostgreSQL

원본 Memory
 

ChromaDB

검색용 Index
 

이기 때문입니다.

따라서:

PostgreSQL = 진짜 원본

ChromaDB = 원본을 빠르게 찾기 위한 검색 색인

이라는 원칙을 유지하는 것이 좋습니다.


7. 그러면 search_memory()가 어떻게 동작해야 할까요?

예를 들어 사용자가:

"내가 요즘 공부하고 있는 AI 관련 내용이 뭐였지?"

라고 질문했다고 합시다.

① ChromaDB에 질문을 던집니다

"내가 요즘 공부하고 있는 AI 관련 내용이 뭐였지?"
 

ChromaDB

memory_29
memory_19
memory_32
...
 

를 찾습니다.


② ChromaDB가 알려준 Memory ID를 얻습니다

예:

memory_29
memory_19
memory_32
 

여기서:

memory_29
 

에서 숫자:

29
 

를 추출할 수 있습니다.


③ 그 ID를 PostgreSQL 원본과 연결합니다

개념적으로:

ChromaDB

memory_29
    ↓
PostgreSQL memory_id = 29
    ↓
원본 Memory
 

이렇게 되는 것입니다.

이 구조를 만들면 나중에 아주 강력해집니다.

예를 들어 ChromaDB에는:

memory_id
user_id
memory_type
importance
 

정도만 저장해도,

실제 상세 정보는 PostgreSQL에서 가져올 수 있습니다.


8. 하지만 이번 단계에서는 한 번에 너무 많이 하지 않겠습니다

우리는 지금까지 아주 좋은 방식으로 진행했습니다.

① PostgreSQL Memory
       ↓
② tuple → dict
       ↓
③ ChromaDB 저장
       ↓
④ ChromaDB 의미 검색
       ↓
⑤ 실제 결과 확인
 

이제 다음 단계에서는 딱 하나만 바꾸겠습니다.

memory.py의 search_memory()를 ChromaDB 기반으로 바꿉니다.

그리고 바로 Agent 전체를 바꾸지 않습니다.


9. 먼저 memory.py import를 수정합니다

현재 memory.py에는 대략 이런 import가 있을 것입니다.

 
from database import (
    save_memory as db_save_memory,
    get_memories,
)
 

여기에 ChromaDB 검색 함수를 추가합니다.

다음처럼 수정하세요.

 
from database import (
    save_memory as db_save_memory,
    get_memories,
)

from vector_store import search_similar_memories
 

즉:

memory.py
   │
   ├── PostgreSQL
   │
   └── vector_store.py
            ↓
        ChromaDB
 

라는 연결이 생깁니다.


10. search_memory()를 수정합니다

기존 코드는:

 
def search_memory(conn, user_id, limit=10):
    memories = get_memories(
        conn,
        user_id=user_id,
        limit=limit,
    )
    return memories
 

였죠.

이것을 다음과 같이 바꿉니다.

 
def search_memory(query, user_id, limit=5):
    results = search_similar_memories(
        query=query,
        user_id=user_id,
        limit=limit,
    )

    return results
 

여기서 아주 중요한 변화가 있습니다.

기존에는:

 
search_memory(conn, user_id, limit=10)
 

이었습니다.

이제는:

 
search_memory(
    query="나는 Python과 AI Agent를 공부하고 있다.",
    user_id=8,
    limit=5,
)
 

가 됩니다.

왜냐하면 ChromaDB가 질문의 의미를 기준으로 검색해야 하기 때문입니다.


11. conn이 사라진 이유

이 부분이 처음에는 조금 헷갈릴 수 있습니다.

기존:

 
def search_memory(conn, user_id, limit=10):
 

에서 conn이 필요했던 이유는:

search_memory()
       ↓
PostgreSQL
       ↓
conn 필요
 

했기 때문입니다.

그런데 이제:

search_memory()
       ↓
ChromaDB
       ↓
query 필요
 

가 됩니다.

그래서:

 
conn
 

대신:

 
query
 

가 중요해집니다.

즉:

기존

search_memory(
    conn,
    user_id,
    limit
)
 

변경

search_memory(
    query,
    user_id,
    limit
)
 

입니다.


12. 그런데 여기서 중요한 문제가 하나 생깁니다

현재 search_memory()의 반환값은 PostgreSQL tuple이었습니다.

예:

 
[
    (30, 8, "goal", "...", 9, datetime(...)),
    (28, 8, "goal", "...", 9, datetime(...)),
]
 

그런데 ChromaDB의 query() 결과는:

 
{
    "ids": [...],
    "documents": [...],
    "metadatas": [...],
    "distances": [...]
}
 

형태입니다.

반환 데이터 구조가 완전히 달라집니다.

따라서 이것을 먼저 이해해야 합니다.


13. ChromaDB 검색 결과 구조

현재 테스트 결과에서:

IDs:
[['memory_27', 'memory_29', 'memory_19', 'memory_32', 'memory_31']]
 

였죠.

이것은 Python으로 보면:

 
results["ids"]
 

이고 실제 구조는:

 
[
    [
        "memory_27",
        "memory_29",
        "memory_19",
        "memory_32",
        "memory_31"
    ]
]
 

입니다.

왜 리스트 안에 리스트가 있느냐?

ChromaDB의 query()는 여러 query를 한 번에 검색할 수 있도록 설계되어 있기 때문입니다.

현재 우리는 query 하나만 넣었으므로:

queries 1개
   ↓
결과 리스트 1개
   ↓
그 안에 검색 결과 5개
 

가 됩니다.

그래서:

 
results["ids"][0]
 

을 하면:

 
[
    "memory_27",
    "memory_29",
    "memory_19",
    "memory_32",
    "memory_31"
]
 

가 됩니다.


14. 다음 테스트를 위해 search_memory()를 조금 더 친절하게 만들겠습니다

저는 지금 단계에서는 ChromaDB의 원본 결과를 그대로 Agent에 넘기기보다, 사용하기 좋은 dictionary list 형태로 변환하는 것을 추천합니다.

memory.py에 다음 함수를 추가합니다.

 
def chroma_results_to_memories(results):
    """
    ChromaDB 검색 결과를
    Memory dictionary 목록으로 변환한다.
    """

    if not results or not results.get("ids"):
        return []

    ids = results["ids"][0]
    documents = results["documents"][0]
    metadatas = results["metadatas"][0]
    distances = results["distances"][0]

    memories = []

    for i in range(len(ids)):
        metadata = metadatas[i]

        memories.append({
            "memory_id": metadata["memory_id"],
            "user_id": metadata["user_id"],
            "memory_type": metadata.get("memory_type", "general"),
            "content": documents[i],
            "importance": metadata.get("importance", 5),
            "distance": distances[i],
        })

    return memories
 

이 함수의 역할은 아주 단순합니다.

ChromaDB 결과
      ↓
chroma_results_to_memories()
      ↓
일관된 Memory dictionary
 

입니다.


15. 그러면 search_memory()는 이렇게 됩니다

 
def search_memory(query, user_id, limit=5):
    results = search_similar_memories(
        query=query,
        user_id=user_id,
        limit=limit,
    )

    memories = chroma_results_to_memories(results)

    return memories
 

이제 구조가 상당히 깔끔해집니다.


16. 전체 흐름을 코드로 보면

사용자 질문
    │
    │ "나는 Python과 AI Agent를 공부하고 있다."
    ▼
search_memory()
    │
    ▼
search_similar_memories()
    │
    ▼
ChromaDB
    │
    ├── memory_27
    ├── memory_29
    ├── memory_19
    ├── memory_32
    └── memory_31
    │
    ▼
chroma_results_to_memories()
    │
    ▼
[
    {
        "memory_id": 27,
        "user_id": 8,
        "memory_type": "project",
        "content": "...",
        "importance": 9,
        "distance": 0.749
    },
    ...
]
 

이제 Agent가 훨씬 다루기 쉬워집니다.


17. 그래서 memory.py의 이번 단계 수정본

현재까지 우리가 만든 구조를 기준으로 하면 memory.py에서 핵심적으로 다음 부분이 들어가면 됩니다.

 
from database import (
    save_memory as db_save_memory,
    get_memories,
)

from vector_store import search_similar_memories
 

그리고:

 
def memory_tuple_to_dict(row):
    return {
        "memory_id": row[0],
        "user_id": row[1],
        "memory_type": row[2],
        "content": row[3],
        "importance": row[4],
        "created_at": row[5],
    }


def memory_tuples_to_dicts(rows):
    return [memory_tuple_to_dict(row) for row in rows]


def chroma_results_to_memories(results):
    """
    ChromaDB 검색 결과를
    Memory dictionary 목록으로 변환한다.
    """

    if not results or not results.get("ids"):
        return []

    ids = results["ids"][0]
    documents = results["documents"][0]
    metadatas = results["metadatas"][0]
    distances = results["distances"][0]

    memories = []

    for i in range(len(ids)):
        metadata = metadatas[i]

        memories.append({
            "memory_id": metadata["memory_id"],
            "user_id": metadata["user_id"],
            "memory_type": metadata.get("memory_type", "general"),
            "content": documents[i],
            "importance": metadata.get("importance", 5),
            "distance": distances[i],
        })

    return memories


def search_memory(query, user_id, limit=5):
    """
    사용자 질문과 의미적으로 가까운 Memory를
    ChromaDB에서 검색한다.
    """

    results = search_similar_memories(
        query=query,
        user_id=user_id,
        limit=limit,
    )

    memories = chroma_results_to_memories(results)

    return memories
 

18. 그런데 바로 Agent를 실행하지 마세요

이 부분이 중요합니다.

아직:

agent_with_memory.py
 

는 예전 방식으로:

 
search_memory(
    conn,
    user_id,
    limit=10
)
 

을 호출하고 있습니다.

그런데 지금 search_memory()는:

 
search_memory(
    query,
    user_id,
    limit=5
)
 

로 바뀌었습니다.

따라서 지금 Agent를 실행하면 오류가 납니다.

그것은 정상적인 상황입니다.

우리는 아직 Agent를 수정하지 않을 것입니다.


19. 대신 작은 테스트를 하나 만들겠습니다

새 파일을:

test_search_memory.py
 

로 만드세요.

전체 코드는 다음과 같습니다.

 
# ============================================================
# test_search_memory.py
#
# ChromaDB 기반 search_memory() 테스트
#
# 테스트 흐름:
#
# 1. PostgreSQL 연결
# 2. 사용자 조회
# 3. 사용자의 질문 준비
# 4. search_memory()
# 5. ChromaDB 의미 검색 결과 확인
# ============================================================

from database import (
    connect_db,
    get_user_by_name,
)

from memory import search_memory


TEST_USER_NAME = "스티븐김"


def main():

    print("=" * 70)
    print("ChromaDB 기반 search_memory() 테스트")
    print("=" * 70)

    print("\n[STEP 1] PostgreSQL 연결")

    conn = connect_db()

    try:

        print("\n[STEP 2] 테스트 사용자 조회")

        user = get_user_by_name(
            conn,
            TEST_USER_NAME,
        )

        if user is None:
            print(f"❎ 사용자를 찾을 수 없습니다: {TEST_USER_NAME}")
            return

        user_id = user[0]

        print("☑️ 사용자 발견")
        print(f"   user_id   : {user_id}")
        print(f"   user_name : {user[1]}")

        print("\n[STEP 3] 검색 질문")

        query = "나는 파이썬과 AI 에이전트 개발을 공부하고 있다."

        print(f"   query : {query}")

        print("\n[STEP 4] search_memory() 실행")

        memories = search_memory(
            query=query,
            user_id=user_id,
            limit=5,
        )

        print(f"☑️ 검색된 Memory 개수: {len(memories)}")

        print("\n[STEP 5] 검색 결과")
        print("-" * 70)

        for i, memory in enumerate(memories, start=1):

            print(f"[{i}]")
            print(f"memory_id   : {memory['memory_id']}")
            print(f"user_id     : {memory['user_id']}")
            print(f"memory_type : {memory['memory_type']}")
            print(f"content     : {memory['content']}")
            print(f"importance  : {memory['importance']}")
            print(f"distance    : {memory['distance']}")

            print("-" * 70)

        print("\n🎉 search_memory() 테스트 완료")

    finally:

        conn.close()

        print("\n📴 PostgreSQL 연결 종료")


if __name__ == "__main__":
    main()
 

20. 실행

터미널에서:

 
cd C:\Dev\book_agentic_ai\agent_project
 

그리고:

 
python test_search_memory.py
 

를 실행하세요.


21. 이번 테스트에서 우리가 확인할 것

결과가 대략 다음과 같은 형태이면 성공입니다.

======================================================================
ChromaDB 기반 search_memory() 테스트
======================================================================

[STEP 1] PostgreSQL 연결
☑️ PostgreSQL 연결 성공

[STEP 2] 테스트 사용자 조회
☑️ 사용자 발견
   user_id   : 8
   user_name : 스티븐김

[STEP 3] 검색 질문
   query : 나는 파이썬과 AI 에이전트 개발을 공부하고 있다.

[STEP 4] search_memory() 실행
☑️ 검색된 Memory 개수: 5

[STEP 5] 검색 결과
----------------------------------------------------------------------

[1]
memory_id   : 27
user_id     : 8
memory_type : project
content     : 사용자는 Python으로 AI Agent를 만들고 있으며...
importance  : 9
distance    : 0.749...

----------------------------------------------------------------------

[2]
memory_id   : 29
...
 

이렇게 나오면 아주 좋습니다.


22. 여기서 한 가지 꼭 기억해 주세요

이번 단계에서 PostgreSQL을 완전히 검색에서 제거한 것은 아닙니다.

현재 구조는:

사용자 정보 조회
      ↓
PostgreSQL
 

그리고

Memory 의미 검색
      ↓
ChromaDB
 

입니다.

즉:

PostgreSQL
├─ User Profile
└─ 원본 Memory 저장

ChromaDB
└─ Memory Semantic Search
 

입니다.

이렇게 역할을 분리하는 것이 우리가 원하는 구조입니다.


23. 그리고 다음 단계가 훨씬 재미있습니다

이번 테스트가 성공하면 그 다음에는 드디어:

agent_with_memory.py
 

를 수정합니다.

현재:

사용자 질문
   ↓
PostgreSQL에서 중요도 높은 Memory
   ↓
LLM
 

사용자 질문
   ↓
ChromaDB에서 의미적으로 관련된 Memory
   ↓
Memory Context 생성
   ↓
LLM
 

으로 바꾸는 것입니다.

그리고 그 다음에는 새로운 Memory가 생성될 때 PostgreSQL과 ChromaDB에 동시에 저장되도록 연결합니다.

최종적으로는:

                    ┌───────────────┐
                    │  사용자 질문   │
                    └───────┬───────┘
                            ↓
                    ┌───────────────┐
                    │ search_memory │
                    └───────┬───────┘
                            ↓
                       ┌─────────┐
                       │ChromaDB │
                       └────┬────┘
                            ↓
                    관련 Memory 검색
                            ↓
                    ┌───────────────┐
                    │ Memory Context│
                    └───────┬───────┘
                            ↓
                         LLM
                            ↓
                          답변
                            │
                            ↓
                     새로운 Memory?
                            ↓
                    ┌───────┴───────┐
                    ↓               ↓
              PostgreSQL        ChromaDB
               원본 저장          Index 저장
 

이 구조가 완성됩니다.

따라서 지금은 memory.py 수정 → test_search_memory.py 실행까지만 진행하세요.

 

[결과 예시]

======================================================================
ChromaDB 기반 search_memory() 테스트
======================================================================

[STEP 1] PostgreSQL 연결
☑️ PostgreSQL 연결 성공

[STEP 2] 테스트 사용자 조회
☑️ 사용자 발견
   user_id   : 8
   user_name : 스티븐김

[STEP 3] 검색 질문
   query : 나는 파이썬과 AI 에이전트 개발을 공부하고 있다.

[STEP 4] search_memory() 실행
☑️ 검색된 Memory 개수: 5

[STEP 5] 검색 결과
----------------------------------------------------------------------
[1]
memory_id   : 27
user_id     : 8
memory_type : project
content     : 사용자는 Python으로 AI Agent를 만들고 있으며, 저작권 미구매 고객에게 자동으로 저작권 안내 전화를 걸고 구매 요청을 하는 AI를 만들고 싶어 한다.
importance  : 9
distance    : 0.7492944598197937
----------------------------------------------------------------------
[2]
memory_id   : 29
user_id     : 8
memory_type : interest
content     : 사용자는 Python으로 AI Agent를 만들고 있다.
importance  : 8
distance    : 0.7528510689735413
----------------------------------------------------------------------
[3]
memory_id   : 19
user_id     : 8
memory_type : interest
content     : 사용자는 Python으로 AI Agent를 만들고 있다.
importance  : 8
distance    : 0.7528510689735413
----------------------------------------------------------------------
[4]
memory_id   : 32
user_id     : 8
memory_type : interest
content     : 사용자는 Python을 활용한 AI Agent 개발에 관심이 있다.
importance  : 7
distance    : 0.7566316723823547
----------------------------------------------------------------------
[5]
memory_id   : 31
user_id     : 8
memory_type : project
content     : 사용자는 Python으로 AI Agent를 개발하여 저작권 미구매 고객에게 자동으로 전화를 걸도록 설계하고 있다.
importance  : 8
distance    : 0.7820292711257935
----------------------------------------------------------------------

🎉 search_memory() 테스트 완료

📴 PostgreSQL 연결 종료

반응형