본문 바로가기
Agentic AI/Memory

12. 장기기억(9) - PostgreSQL의 memories 테이블과 ChromaDB연결 단계(3)

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

이제부터가 진짜 AI Agent의 장기기억 구조를 Agent에 연결하는 단계입니다.

이번에는 서두르지 않고,

① agent_with_memory.py가 현재 어떻게 되어 있는지 → ② 무엇이 문제인지 → ③ ChromaDB를 어떻게 연결하는지 → ④ 전체 코드를 수정 → ⑤ 테스트

순서로 진행하겠습니다.

 

[요약]

# PostgreSQL의 memories 테이블과 ChromaDB연결 단계(3)
# step1. agent_with_memory.py 전반적으로 수정(전체 파일 교체)
# step2. memory.py의 build_memory_context() 코드 교체
# step3. 테스트(agent_with_memory.py 코딩)
    ===>python agent_with_memory.py 실행
    ===>질문: 내가 지금 어떤 분야를 공부하고 있고 어떤 AI Agent를 만들려고 하고 있지?

 


1. 현재까지 완성된 구조

지금까지 만든 것을 먼저 하나의 그림으로 정리해 보겠습니다.

                    ┌──────────────────┐
                    │   사용자 질문     │
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │  search_memory() │
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │     ChromaDB     │
                    │  의미 기반 검색   │
                    └────────┬─────────┘
                             │
                             ▼
                    관련 Memory 5개
                             │
                             ▼
                    ┌──────────────────┐
                    │  Memory Context  │
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │       LLM        │
                    └────────┬─────────┘
                             │
                             ▼
                          답변 생성
 

그리고 Memory를 새로 발견하면:

사용자 ↔ Agent 대화
       │
       ▼
extract_memory()
       │
       ▼
새로운 Memory
       │
       ├───────────────┐
       ▼               ▼
 PostgreSQL        ChromaDB
  원본 저장         검색 Index
 

이것이 최종 목표입니다.


2. 그런데 현재 agent_with_memory.py에는 문제가 있습니다

현재 Agent는 예전 방식입니다.

기존 코드에서는 대략:

 
memories = search_memory(
    conn,
    user_id,
    limit=10,
)
 

처럼 호출하고 있습니다.

그런데 우리가 방금 search_memory()를:

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

로 바꾸었습니다.

따라서 이제 Agent도 반드시:

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

처럼 호출해야 합니다.

즉, Agent가 사용자의 질문을 ChromaDB에 검색어로 전달해야 합니다.


3. 이것이 아주 중요한 변화입니다

예전 Agent의 생각은 사실 이런 것이었습니다.

"이 사용자가 가지고 있는 기억 중에서 중요도가 높은 것 10개를 가져오자."

그래서:

PostgreSQL
    ↓
importance DESC
    ↓
상위 Memory
 

였습니다.

하지만 이제 Agent의 생각은:

"사용자가 지금 무엇을 물어봤지?
그 질문과 관련된 기억이 무엇인지 찾아보자."

입니다.

그래서:

사용자 질문
    ↓
"Python과 AI Agent"
    ↓
ChromaDB
    ↓
의미적으로 가까운 Memory
 

가 됩니다.

이 차이가 장기기억 AI Agent에서 상당히 중요합니다.


4. run_once()를 먼저 살펴봅시다

현재 구조를 단순화하면:

 
def run_once(conn, user_id, user_message):

    memories = search_memory(
        conn,
        user_id,
        limit=10,
    )

    memory_context = build_memory_context(memories)

    answer = call_model(
        user_message,
        memory_context,
    )

    new_memories = extract_memory(
        user_message,
        answer,
    )

    save_memories(
        conn,
        user_id,
        new_memories,
    )

    return answer
 

입니다.

여기서 첫 번째 부분만 바꾸면 됩니다.


5. 기존 방식

 
memories = search_memory(
    conn,
    user_id,
    limit=10,
)
 

이것은:

"PostgreSQL에서 이 사용자의 Memory를 가져와."

라는 의미입니다.


6. 새로운 방식

이제:

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

입니다.

이것은:

"현재 사용자의 질문과 의미적으로 가까운 Memory 5개를 찾아줘."

라는 의미입니다.


7. 그런데 여기서 또 하나의 문제가 있습니다

우리가 방금 만든 search_memory()는 결과를 이런 dictionary로 반환합니다.

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

그런데 현재 build_memory_context()는 PostgreSQL tuple을 기준으로 작성되어 있을 가능성이 높습니다.

예를 들어:

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

이런 방식입니다.

그러면 이제 문제가 발생합니다.


8. tuple과 dictionary의 차이

예전 PostgreSQL 결과:

 
memory = (
    27,
    8,
    "project",
    "사용자는 Python으로 AI Agent를 만들고 있으며...",
    9,
    datetime(...)
)
 

그래서:

 
memory[0]
memory[1]
memory[2]
 

를 사용했습니다.

그런데 이제 ChromaDB 검색 결과는:

 
memory = {
    "memory_id": 27,
    "user_id": 8,
    "memory_type": "project",
    "content": "...",
    "importance": 9,
    "distance": 0.749
}
 

이므로:

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

를 사용해야 합니다.


9. 그래서 build_memory_context()도 변경해야 합니다

이것은 단순한 수정처럼 보이지만 사실 중요한 설계 변경입니다.

이제 Memory를 내부적으로 dictionary 형태로 통일하겠습니다.

즉:

PostgreSQL
   ↓
tuple
   ↓
memory_tuple_to_dict()
   ↓
dictionary
 

그리고:

ChromaDB
   ↓
result
   ↓
chroma_results_to_memories()
   ↓
dictionary
 

따라서 Agent 입장에서는 항상:

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

형태로 다루게 됩니다.

이렇게 하면 데이터 출처가 PostgreSQL이든 ChromaDB든 Agent는 신경 쓰지 않아도 됩니다.


10. build_memory_context()를 이렇게 바꿉니다

현재 memory.py의 build_memory_context()를 다음 코드로 교체하세요.

 
def build_memory_context(memories):
    """
    검색된 Memory 목록을
    LLM에게 전달할 Context 문자열로 변환한다.
    """

    if not memories:
        return "저장된 장기기억이 없습니다."

    lines = []

    for memory in memories:

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

        lines.append(
            f"- [{memory_type}] "
            f"{content} "
            f"(importance={importance}, memory_id={memory_id})"
        )

    return "\n".join(lines)
 

11. 이 함수가 하는 일을 하나씩 보겠습니다

예를 들어 ChromaDB에서:

 
[
    {
        "memory_id": 29,
        "user_id": 8,
        "memory_type": "interest",
        "content": "사용자는 Python으로 AI Agent를 만들고 있다.",
        "importance": 8,
        "distance": 0.7528,
    },
    {
        "memory_id": 32,
        "user_id": 8,
        "memory_type": "interest",
        "content": "사용자는 Python을 활용한 AI Agent 개발에 관심이 있다.",
        "importance": 7,
        "distance": 0.7566,
    }
]
 

가 들어오면:

[interest] 사용자는 Python으로 AI Agent를 만들고 있다. (importance=8, memory_id=29)
[interest] 사용자는 Python을 활용한 AI Agent 개발에 관심이 있다. (importance=7, memory_id=32)
 

라는 문자열이 만들어집니다.

그리고 이것을 LLM에게 전달합니다.


12. 여기서 distance를 Context에 넣지 않은 이유

이번에는 일부러:

 
distance
 

를 LLM Context에 넣지 않았습니다.

distance는 검색 알고리즘이 얼마나 가까운지 판단하기 위한 내부 값이지, 사용자의 기억 자체가 아니기 때문입니다.

즉:

importance
→ Memory의 중요도

distance
→ 검색 질문과 Memory의 의미적 거리
 

입니다.

둘은 완전히 다른 개념입니다.

이 차이를 지금부터 명확하게 구분해 두는 것이 좋습니다.


13. 이제 agent_with_memory.py를 수정합니다

이번에는 전체 파일을 교체하는 방식으로 가겠습니다.

현재 프로젝트에서 우리가 사용하고 있는 구조를 기준으로 다음과 같이 수정하세요.

 
# ============================================================
# agent_with_memory.py
#
# ChromaDB 기반 장기기억 AI Agent
#
# 동작 흐름:
#
# 1. 사용자 질문 입력
# 2. ChromaDB에서 관련 Memory 검색
# 3. Memory Context 생성
# 4. LLM에게 질문 + Memory 전달
# 5. 답변 생성
# 6. 대화에서 새로운 Memory 추출
# 7. 새로운 Memory를 PostgreSQL에 저장
#
# ※ 현재 단계에서는 새 Memory의 ChromaDB 동기화는
#    다음 단계에서 추가한다.
# ============================================================

import os

from dotenv import load_dotenv
from openai import OpenAI

from database import (
    connect_db,
    get_or_create_user,
)

from memory import (
    extract_memory,
    save_memories,
    search_memory,
    build_memory_context,
)


# ============================================================
# 1. 환경 변수
# ============================================================

load_dotenv()

MODEL_NAME = os.getenv(
    "MODEL_NAME",
    "gpt-4o-mini",
)

client = OpenAI()


# ============================================================
# 2. System Prompt
# ============================================================

SYSTEM_PROMPT = """
당신은 사용자의 장기기억을 참고하여 답변하는 AI Agent입니다.

사용자의 장기기억이 제공되면 답변에 도움이 되는 경우
자연스럽게 활용하세요.

단, 장기기억에 없는 내용을 기억하고 있다고 주장하지 마세요.

기억이 불확실하면 솔직하게 모른다고 답하세요.

답변은 친절하고 이해하기 쉽게 작성하세요.
"""


# ============================================================
# 3. LLM 호출
# ============================================================

def call_model(
    user_message,
    memory_context,
):
    """
    사용자 질문과 장기기억 Context를
    LLM에게 전달하여 답변을 생성한다.
    """

    prompt = f"""
[사용자의 장기기억]
{memory_context}

[현재 사용자 질문]
{user_message}
"""

    response = client.responses.create(
        model=MODEL_NAME,
        instructions=SYSTEM_PROMPT,
        input=prompt,
    )

    return response.output_text


# ============================================================
# 4. Agent 한 번 실행
# ============================================================

def run_once(
    conn,
    user_id,
    user_message,
):
    """
    사용자 질문 1회에 대한
    전체 Agent 실행 과정
    """

    # --------------------------------------------------------
    # STEP 1.
    # 현재 질문과 관련된 장기기억 검색
    # --------------------------------------------------------

    print("\n🧠 관련 장기기억 검색 중...")

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

    print(
        f"☑️ 관련 Memory {len(memories)}개 검색 완료"
    )


    # --------------------------------------------------------
    # STEP 2.
    # 검색된 Memory를 LLM Context로 변환
    # --------------------------------------------------------

    memory_context = build_memory_context(
        memories
    )


    # --------------------------------------------------------
    # STEP 3.
    # LLM에게 질문 + Memory 전달
    # --------------------------------------------------------

    print("\n🤖 AI가 답변을 생성하는 중...")

    answer = call_model(
        user_message=user_message,
        memory_context=memory_context,
    )


    # --------------------------------------------------------
    # STEP 4.
    # 현재 대화에서 새로운 Memory 추출
    # --------------------------------------------------------

    print("\n🧠 새로운 Memory 추출 중...")

    conversation = (
        f"사용자: {user_message}\n"
        f"AI: {answer}"
    )

    new_memories = extract_memory(
        conversation
    )


    # --------------------------------------------------------
    # STEP 5.
    # 새로운 Memory를 PostgreSQL에 저장
    # --------------------------------------------------------

    if new_memories:

        print(
            f"☑️ 새로운 Memory "
            f"{len(new_memories)}개 발견"
        )

        save_memories(
            conn,
            user_id,
            new_memories,
        )

    else:

        print("☑️ 새롭게 저장할 Memory가 없습니다.")


    # --------------------------------------------------------
    # STEP 6.
    # 답변 반환
    # --------------------------------------------------------

    return answer


# ============================================================
# 5. Main
# ============================================================

def main():

    print("=" * 70)
    print("ChromaDB 기반 장기기억 AI Agent")
    print("=" * 70)


    # --------------------------------------------------------
    # PostgreSQL 연결
    # --------------------------------------------------------

    conn = connect_db()

    try:

        # ----------------------------------------------------
        # 사용자 조회 / 생성
        # ----------------------------------------------------

        user_id = get_or_create_user(
            conn,
            user_name="스티븐김",
            location="대한민국",
            preferences="Python과 AI Agent 학습",
        )

        print(f"\n☑️ 사용자 ID: {user_id}")


        # ----------------------------------------------------
        # 사용자 질문
        # ----------------------------------------------------

        user_message = input(
            "\n사용자 질문: "
        )


        # ----------------------------------------------------
        # Agent 실행
        # ----------------------------------------------------

        answer = run_once(
            conn=conn,
            user_id=user_id,
            user_message=user_message,
        )


        # ----------------------------------------------------
        # 답변 출력
        # ----------------------------------------------------

        print("\n" + "=" * 70)
        print("🤖 AI 답변")
        print("=" * 70)

        print(answer)

        print("=" * 70)


    finally:

        conn.close()

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


# ============================================================
# 6. 프로그램 시작점
# ============================================================

if __name__ == "__main__":
    main()
 

14. 여기서 아주 중요한 점

제가 위 코드에서 일부러 새로운 Memory를 ChromaDB에 넣는 코드는 아직 추가하지 않았습니다.

즉 현재는:

검색
↓
ChromaDB
 

까지만 변경했습니다.

새 Memory 저장은 아직:

extract_memory()
↓
PostgreSQL
 

입니다.

왜 이렇게 하느냐 하면 한 번에 두 가지를 바꾸면 어디에서 문제가 생겼는지 알기 어려워지기 때문입니다.

지금 우리가 검증하려는 것은 정확히:

"Agent가 사용자 질문을 기반으로 ChromaDB에서 관련 기억을 찾아서 LLM에게 전달할 수 있는가?"

입니다.


15. 하지만 아주 중요한 문제가 하나 남아 있습니다

현재 Agent를 실행해서 새로운 Memory가 저장되면:

PostgreSQL
    ↓
새 Memory 저장
 

은 됩니다.

그러나:

ChromaDB
 

에는 자동으로 들어가지 않습니다.

따라서 새로운 Memory는:

PostgreSQL에는 있음
ChromaDB에는 없음
 

상태가 됩니다.

예를 들어 Agent와 대화하면서:

"나는 앞으로 AI Agent를 활용해서 기업용 서비스를 만드는 것을 목표로 하고 있어."

라는 새로운 기억이 생겼다고 합시다.

현재 구조에서는:

extract_memory()
       ↓
PostgreSQL 저장
       ↓
끝
 

입니다.

다음 질문에서 ChromaDB가 그 기억을 검색할 수 없습니다.


16. 그래서 다음 단계에서는 이것을 해결합니다

최종적으로:

새로운 Memory 발견
       │
       ▼
   save_memories()
       │
       ├──────────────┐
       ▼              ▼
 PostgreSQL        ChromaDB
   원본 저장         Index 저장
 

이 되어야 합니다.

그런데 여기에서도 우리가 이미 만들어 놓은 함수가 있습니다.

바로:

 
sync_memories_to_chroma()
 

입니다.

따라서 다음 단계에서는:

extract_memory()
       ↓
PostgreSQL 저장
       ↓
저장된 실제 memory_id 확보
       ↓
ChromaDB upsert
 

라는 연결을 만들어야 합니다.

이 부분은 이번 단계가 정상적으로 작동하는 것을 확인한 다음 진행하는 것이 좋습니다.


17. 지금은 테스트를 해봅시다

수정한 뒤:

 
cd C:\Dev\book_agentic_ai\agent_project
 

그리고:

 
python agent_with_memory.py
 

실행하세요.


18. 테스트 질문은 이것을 추천합니다

첫 번째 테스트에서는 이미 ChromaDB에 존재하는 Memory와 매우 관련성이 높은 질문을 해보겠습니다.

입력:

내가 지금 어떤 분야를 공부하고 있고 어떤 AI Agent를 만들려고 하고 있지?
 

그러면 ChromaDB는 현재 저장되어 있는:

memory_27
memory_29
memory_19
memory_32
memory_31
 

등을 검색할 가능성이 높습니다.

그리고 LLM은 이 Memory Context를 보고 답변하게 됩니다.


19. 이번 테스트에서 특히 봐야 할 부분

터미널에:

🧠 관련 장기기억 검색 중...
☑️ 관련 Memory 5개 검색 완료
 

가 나오는지 확인하세요.

그리고 AI가 단순히 현재 질문만 보고 답하는 것이 아니라:

Python
AI Agent
저작권 안내 전화 AI

등의 기존 Memory를 활용하는지 확인합니다.


20. 그리고 아주 중요한 주의사항

이번 실행에서 새로운 Memory가 PostgreSQL에 추가될 가능성이 높습니다.

예를 들어 질문과 AI 답변을 보고 LLM이:

interest
goal
project
 

등을 새롭게 추출할 수 있습니다.

그러면 PostgreSQL의 Memory 개수가 8개에서:

8 → 9
 

또는

8 → 10
 

등으로 증가할 수 있습니다.

이것은 정상입니다.

다만 아직 그 새 Memory가 ChromaDB에는 자동 동기화되지 않습니다.


21. 지금 우리가 배우고 있는 핵심 개념

이번 단계가 끝나면 지금까지의 구조가 상당히 명확해집니다.

과거

질문
 ↓
PostgreSQL
 ↓
중요도 높은 기억
 ↓
LLM
 

현재

질문
 ↓
ChromaDB
 ↓
질문과 의미적으로 가까운 기억
 ↓
Memory Context
 ↓
LLM
 

최종

질문
 ↓
ChromaDB
 ↓
관련 Memory 검색
 ↓
Memory Context
 ↓
LLM
 ↓
답변
 ↓
새로운 Memory 추출
 ↓
┌──────────────┐
│ PostgreSQL   │ ← 원본
└──────────────┘
        │
        ▼
┌──────────────┐
│ ChromaDB     │ ← 검색 Index
└──────────────┘
 

이것이 우리가 만들고 있는 장기기억을 가진 AI Agent의 기본 아키텍처입니다.


지금 할 일은 딱 하나입니다

memory.py의 build_memory_context()를 위 코드로 변경하고, agent_with_memory.py를 위 전체 코드로 교체한 후:

 
python agent_with_memory.py
 

를 실행해 주세요.

 

[결과 예시]

======================================================================
ChromaDB 기반 장기기억 AI Agent
======================================================================
☑️ PostgreSQL 연결 성공

☑️ 사용자 ID: 8

사용자 질문: 내가 지금 어떤 분야를 공부하고 있고 어떤 AI Agent를 만들려고 하고 있지?

🧠 관련 장기기억 검색 중...
☑️ 관련 Memory 5개 검색 완료

🤖 AI가 답변을 생성하는 중...

🧠 새로운 Memory 추출 중...


======================================================================
🧠 LLM Memory Extraction 결과
======================================================================
[
    {
        "memory_type": "interest",
        "content": "사용자는 Python을 활용하여 AI Agent를 개발하는 데 관심이 있다.",
        "importance": 8
    },
    {
        "memory_type": "goal",
        "content": "사용자는 저작권 미구매 고객에게 자동으로 전화를 걸고 구매 요청을 하는 AI Agent를 만들고 싶어 한다.",
        "importance": 9
    }
]
☑️ 새로운 Memory 2개 발견
🧠기억 저장 완료 (memory_id=35)
🧠기억 저장 완료 (memory_id=36)

======================================================================
🤖 AI 답변
======================================================================
당신은 Python을 활용하여 AI Agent를 개발하고 있으며, 특히 저작권 미구매 고객에게 자동으로 전화를 걸고 구매 요청을 하는 AI Agent를 만들고 싶어 하고 있습니다. AI Agent 개발에 대한 관심이 높아 보이네요! 필요한 정보나 도움이 필요하면 언제든지 말씀해 주세요.
======================================================================

📴 PostgreSQL 연결 종료

반응형