본문 바로가기
Agentic AI/Memory

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

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

지금 단계는 단순히 "코드가 실행되느냐"가 아니라,

PostgreSQL에 저장된 Memory를 ChromaDB에도 정확히 전달할 수 있는 구조를 만드는 단계

라고 이해하면 좋습니다.

[요약]

# PostgreSQL의 memories 테이블과 ChromaDB연결 단계(4.2)
=> memory.py -> save_memories()가 저장된 Memory 정보를 반환하도록 수정
# step 1. memory.py 에서 save_memories()만 수정
# step 2. 기 만들어 놓은, test_save_memories.py 실행해서 테스트


1. 먼저 현재 memory.py의 전체 구조를 보겠습니다

현재 구조는 크게 이렇게 되어 있습니다.

memory.py
│
├─ 1. 환경 설정
│
├─ 2. OpenAI Client
│
├─ 3. extract_memory()
│      └─ 대화 → Memory 추출
│
├─ 4. save_memory()
│      └─ Memory 1개 → PostgreSQL 저장
│
├─ 5. save_memories()
│      └─ Memory 여러 개 → PostgreSQL 저장
│
├─ 6. search_memory()
│      └─ ChromaDB에서 의미 검색
│
├─ chroma_results_to_memories()
│      └─ Chroma 결과 → Python dictionary
│
├─ 7. memory_tuple_to_dict()
│      └─ PostgreSQL tuple → dictionary
│
├─ 8. memory_tuples_to_dicts()
│      └─ PostgreSQL tuple 목록 → dictionary 목록
│
└─ 9. build_memory_context()
       └─ 검색 Memory → LLM Context
 

이 구조 자체는 상당히 좋습니다.

특히 지금까지 진행하면서 데이터 형태가

PostgreSQL
    ↓
tuple
    ↓
dictionary
    ↓
ChromaDB
    ↓
dictionary
    ↓
LLM Context
 

로 정리되고 있습니다.


2. 그런데 현재 memory.py에서 반드시 수정해야 할 것이 하나 있습니다

여기입니다.

현재 코드:

 
return {
    "meomory_id":row[0],
    "user_id":row[1],
    "memory_type": row[2],
    "content": row[3],
    "importance": row[4],
    "created_at": row[5],
}
 

여기:

 
"meomory_id"
 

가 오타입니다.

정확히는:

 
"memory_id"
 

여야 합니다.

즉,

 
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],
    }
 

로 수정해야 합니다.

왜 중요할까요?

ChromaDB에서 우리가 사용하는 핵심 ID가:

 
memory_id
 

이기 때문입니다.

예를 들어 PostgreSQL에서:

memory_id = 37
 

이면 ChromaDB에서는:

memory_37
 

이라는 ID를 사용합니다.

따라서

 
"meomory_id"
 

라는 잘못된 이름이 들어가면 나중에 이런 코드에서 문제가 생길 수 있습니다.

 
memory["memory_id"]
 

즉, 지금 발견한 이 오타는 지금 바로 수정하는 것이 좋습니다.


3. 그리고 핵심 질문: save_memories()를 수정하는 것이 맞는가?

결론부터 말씀드리면:

네. 수정하는 것이 맞습니다.

현재 save_memories()는:

 
def save_memories(conn, user_id, memories):

    saved_ids = []

    for memory in memories:

        memory_id = save_memory(
            conn,
            user_id,
            memory
        )

        if memory_id is not None:
            saved_ids.append(memory_id)

    return saved_ids
 

입니다.

그러면 결과가:

 
[38, 39]
 

처럼 ID만 반환됩니다.

그런데 앞으로 ChromaDB에 저장하려면 이것만으로는 부족합니다.

ChromaDB의 add_memories()는 다음과 같은 정보가 필요합니다.

 
{
    "memory_id": 38,
    "user_id": 8,
    "memory_type": "interest",
    "content": "...",
    "importance": 8
}
 

즉,

ID만 있으면 부족
 

합니다.


4. 그래서 우리가 방금 save_memories()를 바꾸려고 한 것입니다

우리가 원하는 결과는:

 
[
    {
        "memory_id": 38,
        "user_id": 8,
        "memory_type": "interest",
        "content": "사용자는 AI Agent 개발에 관심이 있다.",
        "importance": 8
    },
    {
        "memory_id": 39,
        "user_id": 8,
        "memory_type": "goal",
        "content": "사용자는 AI Agent 개발 역량을 높이려 한다.",
        "importance": 9
    }
]
 

입니다.

이렇게 하면 다음 단계에서:

save_memories()
       │
       ↓
PostgreSQL 저장
       │
       ↓
saved_memories
       │
       ↓
ChromaDB 저장
 

이라는 흐름을 만들 수 있습니다.


5. 따라서 save_memory()는 현재 구조를 유지하는 것이 좋습니다

현재 save_memory()는 상당히 잘 되어 있습니다.

 
def save_memory(conn, user_id, memory):

    memory_type = memory.get("memory_type","other")
    content = memory.get("content")
    importance = memory.get("importance", 5)
 

그리고:

 
if not content:
    print("⚠️ content가 없는 Memory는 저장하지 않습니다.")
    return None
 

좋습니다.

또:

 
try:
    importance = int(importance)
except (TypeError, ValueError):
    importance = 5

importance = max(1, min(10, importance))
 

이것도 좋습니다.

LLM이 혹시:

 
"importance": 15
 

라고 만들어도:

10
 

으로 제한하고,

 
"importance": 0
 

이면:

1
 

로 보정합니다.

그리고 가장 중요한 부분:

 
memory_id = db_save_memory(
    conn,
    user_id=user_id,
    memory_type=memory_type,
    content=content,
    importance=importance,
)
 

여기서 database.py의 save_memory()가 실제 PostgreSQL의 memory_id를 반환하도록 우리가 앞에서 수정했습니다.

그래서:

 
return memory_id
 

가 가능합니다.

이 부분은 그대로 유지하면 됩니다.


6. 이제 save_memories()만 수정합니다

현재:

 
def save_memories(conn, user_id, memories):
    """
    여러 개의 Memory를 PostgreSQL에 저장한다.
    반환값: 저장된 memory_id 목록
    """
    saved_ids = []

    for memory in memories:
        memory_id = save_memory(conn, user_id, memory)

        if memory_id is not None:
            saved_ids.append(memory_id)

    return saved_ids
 

이 부분을 통째로 교체하는 것을 권합니다.

수정 코드

 
# ============================================================
# 5. Memory 여러 개 저장
# ============================================================
def save_memories(conn, user_id, memories):
    """
    여러 개의 Memory를 PostgreSQL에 저장하고
    실제로 저장된 Memory 정보를 반환한다.

    반환값:
    [
        {
            "memory_id": 35,
            "user_id": 8,
            "memory_type": "interest",
            "content": "...",
            "importance": 8,
        },
        ...
    ]
    """

    saved_memories = []

    for memory in memories:

        memory_type = memory.get(
            "memory_type",
            "other",
        )

        content = memory.get(
            "content",
            "",
        )

        importance = memory.get(
            "importance",
            5,
        )

        # ----------------------------------------------------
        # PostgreSQL에 저장
        # ----------------------------------------------------
        memory_id = db_save_memory(
            conn=conn,
            user_id=user_id,
            memory_type=memory_type,
            content=content,
            importance=importance,
        )

        # ----------------------------------------------------
        # 실제 저장된 Memory 정보를 dictionary로 구성
        # ----------------------------------------------------
        saved_memories.append(
            {
                "memory_id": memory_id,
                "user_id": user_id,
                "memory_type": memory_type,
                "content": content,
                "importance": importance,
            }
        )

    return saved_memories
 

7. 그런데 여기서 한 가지 생각해볼 부분이 있습니다

혹시 이런 생각이 들 수 있습니다.

"이미 save_memory()가 있는데 왜 save_memories()에서 똑같이 memory_type, content, importance를 다시 꺼내지?"

맞습니다.

사실 현재 구조에서는 조금 중복됩니다.

우리가 이미:

 
save_memory()
 

라는 함수를 가지고 있기 때문입니다.

그래서 저는 학습 단계에서는 조금 더 깔끔하게 다음처럼 만드는 것도 추천합니다.

 
def save_memories(conn, user_id, memories):

    saved_memories = []

    for memory in memories:

        memory_id = save_memory(
            conn,
            user_id,
            memory,
        )

        if memory_id is None:
            continue

        saved_memories.append(
            {
                "memory_id": memory_id,
                "user_id": user_id,
                "memory_type": memory.get(
                    "memory_type",
                    "other",
                ),
                "content": memory.get(
                    "content",
                    "",
                ),
                "importance": memory.get(
                    "importance",
                    5,
                ),
            }
        )

    return saved_memories
 

저라면 이쪽을 더 추천합니다.

왜냐하면 데이터 검증의 책임을 save_memory()에 계속 유지할 수 있기 때문입니다.


8. 그런데 왜 제가 앞서 제안한 코드에서는 db_save_memory()를 직접 호출했을까요?

여기에는 설계적인 이유가 있습니다.

현재 save_memory()는:

 
memory.py
 

에 있고,

그 내부에서:

 
db_save_memory()
 

를 호출합니다.

즉:

save_memories()
       ↓
save_memory()
       ↓
db_save_memory()
       ↓
PostgreSQL
 

구조입니다.

이것이 더 자연스럽습니다.

따라서 지금 학습하고 있는 단계에서는 save_memories()가 save_memory()를 호출하는 구조를 유지하는 것이 더 좋습니다.

그러므로 최종적으로 저는 다음 버전을 추천합니다.


9. 제가 최종 추천하는 save_memories() 코드

 
# ============================================================
# 5. Memory 여러 개 저장
# ============================================================
def save_memories(conn, user_id, memories):
    """
    여러 개의 Memory를 PostgreSQL에 저장하고
    실제로 저장된 Memory 정보를 반환한다.

    반환값:
    [
        {
            "memory_id": 35,
            "user_id": 8,
            "memory_type": "interest",
            "content": "...",
            "importance": 8,
        },
        ...
    ]
    """

    saved_memories = []

    for memory in memories:

        # ----------------------------------------------------
        # 1. 하나의 Memory를 PostgreSQL에 저장
        # ----------------------------------------------------
        memory_id = save_memory(
            conn,
            user_id,
            memory,
        )

        # ----------------------------------------------------
        # 2. 저장 실패한 Memory는 제외
        # ----------------------------------------------------
        if memory_id is None:
            continue

        # ----------------------------------------------------
        # 3. 실제 저장된 Memory 정보를 dictionary로 구성
        # ----------------------------------------------------
        saved_memories.append(
            {
                "memory_id": memory_id,
                "user_id": user_id,
                "memory_type": memory.get(
                    "memory_type",
                    "other",
                ),
                "content": memory.get(
                    "content",
                    "",
                ),
                "importance": memory.get(
                    "importance",
                    5,
                ),
            }
        )

    return saved_memories
 

이렇게 하면 역할이 명확합니다.

save_memory()

Memory 1개를 검증하고 PostgreSQL에 저장
→ memory_id 반환
 

save_memories()

Memory 여러 개를 반복해서 저장
→ 저장된 Memory 정보를 모아서 반환
 

이렇게 되는 것입니다.


10. 그리고 memory_tuple_to_dict() 오타도 같이 수정합니다

현재:

 
def memory_tuple_to_dict(row):

    return {
        "meomory_id":row[0],
        "user_id":row[1],
        "memory_type": row[2],
        "content": row[3],
        "importance": row[4],
        "created_at": row[5],
    }
 

다음으로 수정:

 
# ============================================================
# 7. Memory tuple → dictionary
# ============================================================
def memory_tuple_to_dict(row):
    """
    PostgreSQL에서 가져온 Memory tuple을
    dictionary로 변환한다.

    PostgreSQL tuple 구조:
    (
        memory_id,
        user_id,
        memory_type,
        content,
        importance,
        created_at
    )
    """

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

이것만 수정하면 됩니다.


11. 그러면 현재 memory.py의 중요한 데이터 흐름이 이렇게 됩니다

이 부분이 이번 단계에서 가장 중요합니다.

① LLM이 Memory를 추출

 
new_memories = extract_memory(conversation)
 

결과:

 
[
    {
        "memory_type": "interest",
        "content": "사용자는 AI Agent 개발에 관심이 있다.",
        "importance": 8
    }
]
 

② save_memories() 호출

 
saved_memories = save_memories(
    conn,
    user_id,
    new_memories,
)
 

③ PostgreSQL에 저장

예를 들어 DB가:

memory_id = 40
 

을 생성합니다.


④ save_memories()가 결과를 반환

 
[
    {
        "memory_id": 40,
        "user_id": 8,
        "memory_type": "interest",
        "content": "사용자는 AI Agent 개발에 관심이 있다.",
        "importance": 8
    }
]
 

12. 바로 여기까지가 이번 memory.py 수정의 목적입니다

여기서 굉장히 중요한 점이 있습니다.

이번 단계에서는 아직 ChromaDB에 저장하지 않습니다.

즉 memory.py의 책임은:

LLM Memory 추출
       ↓
PostgreSQL 저장
       ↓
저장된 Memory 정보를 반환
 

까지만 합니다.

그 다음:

saved_memories
       ↓
ChromaDB
 

는 agent_with_memory.py가 담당하게 합니다.

이것이 제가 지금 단계에서 추천하는 구조입니다.


13. 왜 memory.py에서 ChromaDB까지 저장하지 않을까요?

물론 기술적으로는 가능합니다.

예를 들어:

 
def save_memories(...):

    PostgreSQL 저장

    ChromaDB 저장
 

으로 만들 수도 있습니다.

하지만 그러면 memory.py가 점점 많은 일을 담당하게 됩니다.

memory.py
├─ LLM
├─ PostgreSQL
├─ ChromaDB
├─ 검색
├─ Context
...
 

나중에는 관리하기 어려워집니다.

지금 구조에서는:

database.py
    ↓
PostgreSQL 담당

vector_store.py
    ↓
ChromaDB 담당

memory.py
    ↓
Memory 의미/변환 담당

agent_with_memory.py
    ↓
전체 흐름을 연결
 

로 나누는 것이 좋습니다.

이것을 관심사의 분리(Separation of Concerns)라고 생각하면 됩니다.


14. 현재 코드에서 get_memories는 어떻게 해야 할까요?

현재 상단에:

 
from database import (
    save_memory as db_save_memory, 
    get_memories
)
 

가 있습니다.

그런데 현재 search_memory()는 PostgreSQL의 get_memories()를 사용하지 않습니다.

현재:

 
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
 

이기 때문입니다.

따라서 지금은:

 
get_memories
 

가 사용되지 않습니다.

하지만 당장 삭제하지 않는 것을 추천합니다.

왜냐하면 지금 우리가 PostgreSQL → ChromaDB 구조를 배우는 과정이고,

 
memory_tuples_to_dicts()
 

도 PostgreSQL 데이터를 ChromaDB에 넣는 과정에서 사용할 수 있기 때문입니다.

조금 뒤 구조가 안정되면 정리해도 됩니다.


15. 또 하나: chroma_results_to_memories()는 현재 아주 잘 되어 있습니다

이 부분:

 
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],
})
 

좋습니다.

결과적으로 ChromaDB가 반환하는 복잡한 결과를 우리가 사용하기 좋은:

 
{
    "memory_id": ...,
    "user_id": ...,
    "memory_type": ...,
    "content": ...,
    "importance": ...,
    "distance": ...
}
 

형태로 통일합니다.

그리고 build_memory_context()는:

 
memory["memory_id"]
memory["memory_type"]
memory["content"]
memory["importance"]
 

를 사용합니다.

즉 현재:

ChromaDB
   ↓
chroma_results_to_memories()
   ↓
dictionary
   ↓
build_memory_context()
 

가 정확히 연결되어 있습니다.


16. 따라서 지금 memory.py에서 실제로 수정할 것은 딱 2가지입니다

수정 ① save_memories()

기존:

 
saved_ids = []
 

에서

 
saved_memories = []
 

로 바꾸고, 실제 Memory dictionary를 반환합니다.

수정 ② 오타

 
"meomory_id"
 

 
"memory_id"
 

17. 나머지는 지금 건드리지 않는 것을 추천합니다

특히 다음은 그대로 둡니다.

 
extract_memory()
 

그대로.

 
save_memory()
 

그대로.

 
chroma_results_to_memories()
 

그대로.

 
search_memory()
 

그대로.

 
build_memory_context()
 

그대로.

즉 이번 단계에서는 두 군데만 수정합니다.


18. 수정 후 전체적인 구조

수정 후 memory.py의 핵심 흐름은:

                    ┌─────────────────┐
                    │  extract_memory │
                    │   LLM Memory    │
                    │      추출       │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  save_memories  │
                    └────────┬────────┘
                             │
                 ┌───────────┴───────────┐
                 ▼                       ▼
        ┌────────────────┐       saved_memories
        │  save_memory   │              │
        └───────┬────────┘              │
                ▼                       │
        ┌────────────────┐              │
        │  PostgreSQL    │              │
        │  Source of     │              │
        │     Truth      │              │
        └────────────────┘              │
                                        │
                                        ▼
                              [다음 단계]
                              ChromaDB 저장
 

그리고 검색은 반대 방향입니다.

사용자 질문
     │
     ▼
search_memory()
     │
     ▼
ChromaDB
     │
     ▼
의미적으로 가까운 Memory
     │
     ▼
chroma_results_to_memories()
     │
     ▼
dictionary
     │
     ▼
build_memory_context()
     │
     ▼
LLM
 

이제 구조가 상당히 명확해졌습니다.


19. 지금은 바로 테스트하는 것이 좋습니다

따라서 지금은 제가 앞서 드린 test_save_memories.py를 실행하는 순서가 맞습니다.

수정 후:

 
python test_save_memories.py
 

를 실행하세요.

정상이라면 대략:

[STEP 4] save_memories() 실행

🧠기억 저장 완료 (memory_id=38)
🧠기억 저장 완료 (memory_id=39)

[STEP 5] 저장 결과 확인

[1]
memory_id   : 38
user_id     : 8
memory_type : test
content     : save_memories() 첫 번째 테스트 Memory입니다.
importance  : 1

[2]
memory_id   : 39
user_id     : 8
memory_type : test
content     : save_memories() 두 번째 테스트 Memory입니다.
importance  : 2

🎉 테스트 성공!
 

처럼 나와야 합니다.

여기서 중요한 것은 단순히 "성공"이라는 문구가 나오는 것이 아니라, saved_memories가 실제 dictionary 목록으로 반환되는지를 확인하는 것입니다.


그리고 다음 단계의 의미

이 테스트가 성공하면 이제 상당히 중요한 지점에 도달합니다.

지금까지는:

PostgreSQL ← Memory 저장
 

까지만 되어 있었습니다.

다음 단계에서는:

새 Memory
   ↓
PostgreSQL
   ↓
ChromaDB
 

라는 Dual Write를 완성하게 됩니다.

즉, 우리가 처음 설계했던

PostgreSQL은 원본(Source of Truth), ChromaDB는 검색용 Index

라는 구조가 실제 Agent 안에서 완성되기 시작합니다.

저는 다음 단계에서 agent_with_memory.py까지 연결한 뒤, PostgreSQL과 ChromaDB의 memory_id가 실제로 동일하게 유지되는지 확인하는 통합 테스트까지 이어가고 싶습니다.

 

[결과 예시]

======================================================================
memory.py save_memories() 테스트
======================================================================

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

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

[STEP 3] 테스트 Memory 준비
   테스트 Memory 개수 : 2

[STEP 4] save_memories() 실행
🧠기억 저장 완료 (memory_id=39)
🧠기억 저장 완료 (memory_id=40)

[STEP 5] 저장 결과 확인
----------------------------------------------------------------------
[1]
memory_id   : 39
user_id     : 8
memory_type : test
content     : save_memories() 첫 번째 테스트 Memory입니다.
importance  : 1
----------------------------------------------------------------------
[2]
memory_id   : 40
user_id     : 8
memory_type : test
content     : save_memories() 두 번째 테스트 Memory입니다.
importance  : 2
----------------------------------------------------------------------

🎉 테스트 성공!
save_memories()가 모든 Memory를 PostgreSQL에 저장하고 memory_id를 포함한 결과를 정상적으로 반환했습니다.

📴 PostgreSQL 연결 종료

반응형