Skip to content

장기 메모리 구현

InMemoryStore와 RAG 시스템 활용


학습 목표

  • InMemoryStore의 개념과 InMemorySaver와의 차이를 이해한다.
  • Namespace와 Key 기반의 2단계 데이터 구분 구조를 파악한다.
  • put / get / search API를 사용하여 장기 메모리를 관리할 수 있다.
  • InMemoryStore를 그래프에 연동하고 노드에서 활용하는 방법을 익힌다.
  • 임베딩 기반 RAG 시스템으로 유사도 검색을 구현할 수 있다.

1. InMemoryStore 개요

InMemoryStore는 LangGraph에서 Long-term Memory(장기 메모리) 를 구현하기 위한 핵심 구현체다. Short-term Memory를 담당하는 InMemorySaver(checkpointer)와 대응하는 개념으로, 여러 대화 세션에 걸쳐 데이터를 유지하는 역할을 한다.

InMemoryStore 역시 이름 그대로 메모리 기반이므로 기본적으로 휘발성이다. 하지만 데이터베이스와 연동하면 영구 저장이 가능해진다.

InMemorySaver와의 핵심 차이:

구분InMemorySaverInMemoryStore
역할Short-term Memory (체크포인터)Long-term Memory (스토어)
구분 기준thread_id로 구분Namespace 튜플로 구분
범위단일 대화 스레드여러 대화 통합, 사용자/애플리케이션 수준
그래프 연동checkpointer 인자store 인자

2. Namespace와 Key 구조

InMemoryStore의 데이터는 NamespaceKey라는 2단계 구조로 구분된다.

  • Namespace: Python 튜플 값으로 설정하는 구분 키다. 예를 들어 ("users", user_id)처럼 계층적으로 데이터를 분류한다.
  • Key: 네임스페이스 안에서 개별 데이터를 식별하는 고유 키다.

이 2단계 구분을 통해 Namespace로 큰 범위를 지정하고, Key로 세부 항목에 접근하는 구조가 만들어진다.

구조 정리:

계층설명예시
Namespace큰 범위의 구분 키 (튜플)("users",), ("memories", "user_123")
Key네임스페이스 내 세부 식별자 (문자열)"user_123", "1", "language"
Value실제 저장되는 데이터 (딕셔너리){"name": "John Smith", "language": "English"}

InMemoryStore는 세 가지 핵심 API를 제공한다.

put --- 데이터 저장

store.put(namespace, key, value) 형태로 데이터를 저장한다. 같은 namespace + key 조합에 다시 put하면 기존 값을 덮어쓴다.

python
from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

# 사용자 정보 저장
store.put(("users",), "user_123", {"name": "John Smith", "language": "English"})
store.put(("users",), "user_456", {"name": "홍길동", "language": "Korean"})

# 사용자별 메모리 저장
store.put(("memories", "user_123"), "1", {"text": "I love pizza"})
store.put(("memories", "user_123"), "2", {"text": "I'm a plumber"})

get --- 데이터 조회

store.get(namespace, key) 형태로 특정 데이터를 조회한다. 해당 키가 없으면 None을 반환한다.

python
# 특정 사용자 정보 조회
item = store.get(("users",), "user_123")
# → Item(key="user_123", value={"name": "John Smith", "language": "English"})

# 값 접근
print(item.value["name"])  # "John Smith"

search --- 유사도 검색

store.search(namespace, query=..., limit=N) 형태로 네임스페이스 내에서 유사한 데이터를 검색한다. RAG 시스템과 연동할 때 핵심이 되는 API다.

python
# 네임스페이스 내 모든 항목 검색
results = store.search(("memories", "user_123"))

# 쿼리 기반 유사도 검색 (임베딩 설정 시)
results = store.search(("memories", "user_123"), query="I'm hungry", limit=1)
# → "I love pizza" 반환 (음식 관련 메모리와 가장 유사)

4. 그래프 연동

InMemoryStore를 LangGraph 그래프에 연동하는 과정은 간단하다. 그래프 컴파일 시 store 인자로 전달하면, 각 노드 함수 내에서 get_store()로 스토어에 접근할 수 있다.

python
from langgraph.store.memory import InMemoryStore
from langgraph.graph import StateGraph

store = InMemoryStore()

# 그래프 컴파일 시 store 연동
graph = builder.compile(store=store)

노드 함수 내에서의 활용:

python
def my_node(state, config, *, store):
    """노드 함수에서 store 매개변수로 스토어에 접근"""
    # 사용자 ID 가져오기
    user_id = config["configurable"]["user_id"]

    # 장기 메모리에서 사용자 정보 조회
    user_info = store.get(("users",), user_id)

    # 관련 메모리 검색
    memories = store.search(("memories", user_id), query=state["messages"][-1].content, limit=3)

    # 새로운 메모리 저장
    store.put(("memories", user_id), str(uuid4()), {"text": "새로운 기억"})

    return {"messages": [...]}

5. RAG 시스템 활용

InMemoryStore는 단순한 Key-Value 저장소를 넘어, 임베딩 벡터 기반 RAG(Retrieval-Augmented Generation) 시스템으로 활용할 수 있다.

임베딩 설정

InMemoryStore 생성 시 index 옵션에 임베딩 모델을 지정하면, 저장되는 텍스트가 자동으로 벡터로 변환된다.

python
from langgraph.store.memory import InMemoryStore
from langchain_openai import OpenAIEmbeddings

# 임베딩 모델을 지정하여 RAG 기능 활성화
store = InMemoryStore(
    index={
        "embed": OpenAIEmbeddings(model="text-embedding-3-small")
    }
)

저장과 검색 흐름

실전 예시

python
store = InMemoryStore(
    index={"embed": OpenAIEmbeddings(model="text-embedding-3-small")}
)

# 사용자 메모리 저장 — 자동으로 임베딩 벡터로 변환됨
store.put(("user_123", "memories"), "1", {"text": "I love pizza"})
store.put(("user_123", "memories"), "2", {"text": "I'm a plumber"})

# 유사도 검색 — "I'm hungry"와 가장 관련 있는 메모리 반환
results = store.search(("user_123", "memories"), query="I'm hungry", limit=1)
# → [Item(key="1", value={"text": "I love pizza"}, score=0.87)]

위 예시에서 "I'm hungry"라는 쿼리는 음식과 관련이 있으므로, "I love pizza"가 "I'm a plumber"보다 높은 유사도 점수를 받아 우선적으로 반환된다.

시스템 프롬프트에 메모리 주입

검색된 메모리는 시스템 프롬프트에 삽입하여 LLM이 사용자의 과거 정보를 참고하도록 만든다.

python
def chatbot_node(state, config, *, store):
    user_id = config["configurable"]["user_id"]

    # 사용자 메시지와 관련된 메모리만 선별적으로 검색
    user_message = state["messages"][-1].content
    memories = store.search(("memories", user_id), query=user_message, limit=3)

    # 검색된 메모리를 시스템 프롬프트에 포함
    memory_text = "\n".join([m.value["text"] for m in memories])
    system_prompt = f"""You are a helpful assistant.

Memory of user:
{memory_text}

Use the above memories to personalize your response."""

    response = llm.invoke([
        SystemMessage(content=system_prompt),
        *state["messages"]
    ])
    return {"messages": [response]}

이 방식의 장점은 모든 메모리를 컨텍스트에 넣는 것이 아니라, 현재 대화와 관련된 메모리만 선별적으로 포함한다는 것이다. 이를 통해 토큰 사용량을 줄이면서도 관련성 높은 응답을 생성할 수 있다.


6. 두 가지 구현 방식

InMemoryStore를 활용한 장기 메모리는 두 가지 방식으로 구현할 수 있다.

방식 1: CreateReactAgent

LangGraph의 고수준 API인 CreateReactAgent를 사용하는 방식이다. store 인자에 InMemoryStore를 전달하고, tools에 메모리 관련 함수를 등록한다.

python
from langgraph.prebuilt import create_react_agent

store = InMemoryStore(
    index={"embed": OpenAIEmbeddings(model="text-embedding-3-small")}
)

def get_user_info(config, *, store) -> str:
    """사용자 정보를 장기 메모리에서 조회"""
    user_id = config["configurable"]["user_id"]
    items = store.search(("users",), query=user_id, limit=1)
    return items[0].value if items else "정보 없음"

agent = create_react_agent(
    model=llm,
    tools=[get_user_info],
    store=store  # InMemoryStore 전달
)

방식 2: Graph Workflow

StateGraph를 사용하여 직접 그래프를 구성하는 방식이다. 노드 함수에서 store 매개변수로 스토어에 접근하여 읽기/쓰기를 수행한다.

python
from langgraph.graph import StateGraph

store = InMemoryStore(
    index={"embed": OpenAIEmbeddings(model="text-embedding-3-small")}
)

def retrieve_memories(state, config, *, store):
    """메모리 검색 노드"""
    user_id = config["configurable"]["user_id"]
    query = state["messages"][-1].content
    memories = store.search(("memories", user_id), query=query, limit=3)
    return {"context": memories}

def save_memory(state, config, *, store):
    """메모리 저장 노드"""
    user_id = config["configurable"]["user_id"]
    new_memory = extract_memory(state["messages"])
    store.put(("memories", user_id), str(uuid4()), {"text": new_memory})
    return state

builder = StateGraph(State)
builder.add_node("retrieve", retrieve_memories)
builder.add_node("respond", chatbot_node)
builder.add_node("save", save_memory)

graph = builder.compile(store=store)

두 방식 비교

구분CreateReactAgentGraph Workflow
복잡도낮음 (고수준 API)높음 (직접 구성)
유연성제한적높음 (커스텀 노드 자유 구성)
스토어 접근tools에 등록된 함수에서 접근모든 노드에서 직접 접근
적합한 상황빠른 프로토타이핑, 단순한 에이전트복잡한 워크플로우, 세밀한 제어 필요 시

7. 핵심 정리

InMemoryStore는 LangGraph의 Long-term Memory 구현체로, Namespace 튜플과 Key 기반의 2단계 구조로 데이터를 관리한다.

  • InMemorySaver vs InMemoryStore: InMemorySaver는 thread_id로 단일 대화를 관리하고, InMemoryStore는 Namespace 튜플로 여러 대화에 걸친 데이터를 통합 관리한다.
  • 핵심 API: put으로 저장하고, get으로 조회하며, search로 유사도 검색을 수행한다.
  • RAG 활용: 임베딩 모델을 설정하면 텍스트를 자동으로 벡터로 변환하여, search() 호출 시 의미적으로 유사한 메모리를 검색할 수 있다.
  • 선별적 컨텍스트 주입: 모든 메모리를 넣는 것이 아니라, 현재 사용자 메시지와 관련된 메모리만 시스템 프롬프트에 포함하여 효율적으로 활용한다.
  • 두 가지 구현 방식: CreateReactAgent는 빠른 프로토타이핑에, Graph Workflow는 세밀한 제어가 필요한 복잡한 시스템에 적합하다.