티스토리 뷰

반응형

LangChain memory와 RAG는 어떻게 다를까? 사용자 기억과 문서 검색을 한 서비스에서 같이 설계하는 방법

LangChain에서 memoryRAG는 비슷해 보여도 역할이 완전히 다릅니다. 공식 문서 기준으로 short-term memory는 같은 대화(thread) 안의 문맥을 기억하는 것이고, long-term memory는 여러 세션을 넘어가는 사용자별 또는 앱 레벨 정보를 저장하는 것입니다. 반면 retrieval/RAG는 질문에 답하기 위해 외부 문서나 지식베이스에서 관련 정보를 찾아오는 흐름입니다. LangChain 문서는 retriever를 “비정형 쿼리를 받아 관련 문서를 반환하는 인터페이스”로 설명하고, memory 문서는 short-term memory는 thread-scoped, long-term memory는 namespace-scoped라고 구분합니다. 즉, 아주 짧게 말하면 memory는 ‘이 사용자/이 대화에 대해 기억하는 것’이고, RAG는 ‘외부 문서에서 근거를 찾는 것’입니다. (LangChain Docs)

이거 진짜 많이 헷갈립니다.

저도 처음엔 그랬어요.
문서를 찾아오는 것도 결국 “기억” 같고,
사용자 선호를 저장하는 것도 결국 “검색” 같아 보여서요.

근데 실제 서비스 만들다 보면 둘을 섞어 쓰면 금방 꼬입니다.

  • 사용자가 “짧게 답해줘”라고 한 걸 문서 저장소에 넣어버리거나
  • 회사 정책 문서를 long-term memory처럼 다뤄버리거나
  • 사용자 선호와 공식 규정을 한 retrieval에서 같이 끌어오거나

이러면 답변이 이상해집니다.

그래서 이번 글에서는
LangChain memory vs RAG 차이,
그리고 둘을 한 서비스 안에서 어떻게 같이 설계하면 좋은지를 검색형으로 정리해보겠습니다.


한 줄 요약

  • memory: 사용자/대화에 대한 기억
  • RAG: 외부 문서/지식에 대한 검색
  • 같이 쓸 때 핵심: memory는 개인화, RAG는 근거 제공

LangChain memory overview는 short-term memory를 thread-scoped state, long-term memory를 user/application-level data across sessions로 설명하고, retrieval 문서는 retriever를 쿼리로부터 관련 문서를 가져오는 구성요소로 설명합니다. (LangChain Docs)


이 글에서 다루는 내용

  • LangChain memory와 RAG의 정확한 차이
  • 어떤 정보는 memory에 넣고, 어떤 정보는 RAG로 보내야 하는지
  • 사내 도우미 예시로 같이 설계하는 방법
  • FastAPI + LangChain 구조에서 어느 레이어에 둘지
  • memory와 RAG를 섞을 때 자주 하는 실수
  • 바로 실행해볼 수 있는 최소 코드

LangChain memory란 무엇인가?

공식 문서 기준으로 memory는 “이전 상호작용에 대한 정보”를 기억하는 시스템입니다. short-term memory는 한 thread 안의 대화 기록과 상태를 관리하고, long-term memory는 세션을 넘어가는 사용자별 또는 애플리케이션 레벨의 정보를 저장합니다. (LangChain Docs)

쉽게 말하면:

short-term memory

  • 지금 대화창에서 방금 무슨 말을 했는지
  • 직전 질문과 답변
  • 방금 tool이 어떤 결과를 냈는지

long-term memory

  • 이 사용자는 답변을 짧게 좋아한다
  • 이 사용자는 재무팀 소속이다
  • 이 사용자는 다크 모드를 선호한다

즉, memory는 기본적으로
“이 사람” 혹은 “이 대화”에 대한 정보입니다. (LangChain Docs)


LangChain RAG란 무엇인가?

RAG는 Retrieval-Augmented Generation입니다. LangChain retrieval 문서는 retriever를 관련 문서를 반환하는 인터페이스로 설명하고, RAG 문서는 질문에 답하기 위해 외부 소스에서 관련 정보를 검색해 모델에 전달하는 흐름을 설명합니다. (LangChain Docs)

쉽게 말하면:

  • 회사 정책 문서 검색
  • 사내 위키 검색
  • 제품 매뉴얼 검색
  • FAQ 문서 검색
  • 업로드된 PDF 검색

이런 건 memory가 아니라 RAG 영역입니다.

왜냐면 이건
“사용자에 대한 기억”이 아니라
외부 지식과 근거 문서를 찾는 일이기 때문입니다. (LangChain Docs)


memory와 RAG의 가장 중요한 차이

이건 정말 한 번에 정리해두는 게 좋습니다.

구분memoryRAG

목적 사용자/대화 기억 외부 문서 검색
범위 thread 또는 user knowledge base / docs
대표 질문 “이 사용자는 뭘 선호하지?” “정책 문서에 뭐라고 되어 있지?”
데이터 성격 개인화 정보 공식 정보 / 근거
thread_id, user_id, namespace retriever, vector store, document IDs

이 표가 핵심입니다.

  • memory는 personalized
  • RAG는 grounded

공식 문서 표현을 빌리면, long-term memory는 user-specific data across sessions이고, retrieval은 query에 relevant documents를 반환하는 구성입니다. (LangChain Docs)


어떤 정보는 memory에 넣고, 어떤 정보는 RAG로 보내야 할까?

반응형

이게 실무에서 제일 중요합니다.

memory에 넣을 만한 것

  • 답변 스타일 선호
  • 자주 쓰는 팀/역할 정보
  • 반복되는 사용자 제약
  • 사용자별 선호 설정

예:

  • “앞으로는 짧게 답해줘”
  • “나는 재무팀이야”
  • “관리자 권한은 없어”

RAG에 넣을 만한 것

  • 회사 정책 문서
  • 출장비 정산 규정
  • 온보딩 문서
  • 기술 문서
  • FAQ
  • PDF/위키/매뉴얼

예:

  • “재택근무 정책은 뭐야?”
  • “출장비는 언제까지 정산해야 해?”
  • “VPN 신청 절차 알려줘”

애매해 보이지만 보통 RAG인 것

  • 회사 공통 규정
  • 조직 공통 룰
  • 공식 프로세스 문서

이건 사용자 개인 기억이 아니라
모든 사람이 공유하는 외부 지식이기 때문입니다.

LangChain memory overview는 long-term memory를 facts about a user 같은 semantic memory 예로 설명하고, retrieval 문서는 external knowledge source에 대한 question answering을 설명합니다. (LangChain Docs)


왜 둘을 섞으면 문제가 생길까

예를 들어 사용자가
“나는 답변 짧은 게 좋아”
라고 한 걸 RAG 문서 저장소에 넣어보죠.

그러면 문서 검색 결과에
회사 정책, 출장비 규정, 사용자 선호가 같이 섞일 수 있습니다.

반대로 회사 정책 문서를 long-term memory에 넣어보면,
그건 개인 기억도 아니고, 바뀔 수도 있고, 근거 문서 검색도 잘 안 됩니다.

즉 둘을 섞으면 생기는 문제는 이겁니다.

  • memory가 너무 무거워짐
  • RAG가 너무 개인화 정보에 오염됨
  • 답변 근거와 사용자 선호가 뒤섞임

그래서 저는 늘 이렇게 말하는 편입니다.

memory는 사람 중심, RAG는 문서 중심

이렇게만 잡아도 서비스 구조가 훨씬 덜 꼬입니다.


LangChain 공식 문서 흐름으로 보면

공식 문서들을 같이 보면 이 차이가 더 선명합니다.

memory 쪽

  • short-term memory: thread-scoped state
  • long-term memory: namespace-scoped memory
  • hot path / background update
  • profile / collection 전략

(LangChain Docs)

RAG 쪽

  • retriever
  • vector store
  • relevant documents
  • build a RAG agent
  • unstructured data source

(LangChain Docs)

즉 문서 구조 자체가 이미 다릅니다.
memory와 retrieval은 공식 개념 단계에서부터 분리돼 있어요.


그럼 실제 서비스에서는 어떻게 같이 쓰나?

이게 진짜 핵심입니다.

예를 들어 사내 도우미를 만든다고 해봅시다.

사용자가 이렇게 말합니다.

“출장비 정책 알려줘. 그리고 나는 답변 짧은 게 좋아.”

여기서 해야 할 일은 둘로 나뉩니다.

  1. 정책 정보 찾기
    → RAG
  2. 앞으로 답변 스타일 기억하기
    → long-term memory

그리고 다음 질문이 오면:

“그럼 7일 이내 정산이 맞아? 짧게 말해줘.”

여기서는

  • 정책 근거는 RAG에서 다시 찾고
  • 짧게 말하는 스타일은 memory에서 가져옵니다

이게 가장 자연스러운 조합입니다.

즉 하나의 답변 안에서도

  • 사실 근거는 RAG
  • 답변 스타일은 memory

로 역할이 나뉠 수 있습니다.


최소 실행 예제: memory와 RAG를 같이 쓰는 작은 코드

아래 코드는 아주 작은 예제입니다.

  • 사용자 선호는 in-memory long-term memory로 저장
  • 정책 문서는 간단한 리스트에서 검색
  • 답변은 사용자 선호를 반영해서 짧게 출력

실행 가능한 순수 Python 예제로 만들었습니다.

from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, List


@dataclass
class UserProfile:
    prefers_short_answers: bool = False


class MemoryStore:
    def __init__(self) -> None:
        self._profiles: Dict[str, UserProfile] = {}

    def get_profile(self, user_id: str) -> UserProfile:
        if user_id not in self._profiles:
            self._profiles[user_id] = UserProfile()
        return self._profiles[user_id]

    def update_short_answer_preference(self, user_id: str, prefers_short: bool) -> None:
        profile = self.get_profile(user_id)
        profile.prefers_short_answers = prefers_short


class PolicyRetriever:
    def __init__(self) -> None:
        self._docs: List[dict] = [
            {
                "title": "재택근무 정책",
                "content": "재택근무는 팀 운영 원칙에 따라 주 2회까지 가능하며, 예외는 팀장 승인 후 가능합니다.",
                "source": "policy-remote-work",
            },
            {
                "title": "출장비 정산 정책",
                "content": "출장비는 귀사 후 7일 이내에 경비 시스템에서 정산해야 합니다.",
                "source": "policy-expense-travel",
            },
        ]

    def search(self, query: str) -> dict | None:
        query_lower = query.lower()
        for doc in self._docs:
            haystack = f"{doc['title']} {doc['content']}".lower()
            if any(token in haystack for token in query_lower.split()):
                return doc
        return None


class AssistantService:
    def __init__(self, memory_store: MemoryStore, retriever: PolicyRetriever) -> None:
        self.memory_store = memory_store
        self.retriever = retriever

    def ask(self, user_id: str, question: str) -> str:
        q = question.strip()

        # memory update
        if "짧게 답해" in q or "짧게 말해" in q:
            self.memory_store.update_short_answer_preference(user_id, True)
            return "알겠습니다. 앞으로 짧게 답변할게요."

        profile = self.memory_store.get_profile(user_id)

        # RAG-like retrieval
        doc = self.retriever.search(q)
        if doc is None:
            return "관련 문서를 찾지 못했습니다."

        answer = doc["content"]

        # personalize with memory
        if profile.prefers_short_answers:
            # 아주 단순한 짧은 답변 예시
            if "출장비" in doc["title"]:
                return "출장비는 귀사 후 7일 이내 정산하면 됩니다."
            if "재택근무" in doc["title"]:
                return "재택근무는 주 2회까지 가능하고, 예외는 팀장 승인입니다."

        return f"{answer} (출처: {doc['source']})"


def main() -> None:
    memory_store = MemoryStore()
    retriever = PolicyRetriever()
    assistant = AssistantService(memory_store, retriever)

    user_id = "alice"

    print(assistant.ask(user_id, "앞으로 짧게 답해줘"))
    print(assistant.ask(user_id, "출장비 정책 알려줘"))
    print(assistant.ask(user_id, "재택근무 정책 알려줘"))


if __name__ == "__main__":
    main()

이 코드는 진짜 단순하지만, 구조는 꽤 본질적입니다.

  • MemoryStore
    사용자 선호 저장
  • PolicyRetriever
    문서 검색
  • AssistantService
    둘을 조합해서 답변 생성

즉 memory와 RAG를 같은 클래스에 억지로 섞지 않고,
역할별로 분리한 다음 서비스 계층에서 합치는 방식입니다.

이 감각이 정말 중요합니다.


FastAPI + LangChain 백엔드에서는 어디에 둘까?

프로젝트 구조 관점에서 보면 저는 보통 이렇게 둡니다.

app/
├── agents/
├── memory/
│   ├── long_term.py
│   └── short_term.py
├── retrieval/
│   └── policy_retriever.py
├── services/
│   └── assistant_service.py
├── routers/
└── schemas/

memory/

  • 사용자 선호
  • long-term store
  • profile/collection 관리

retrieval/

  • 정책 문서 retriever
  • vector store
  • document loader / embeddings

services/

  • 현재 사용자 정보와 질문을 받아
  • memory + RAG를 조합
  • 최종 응답 생성

이 구조가 좋은 이유는
나중에 둘을 따로 고칠 수 있기 때문입니다.

예를 들어:

  • 문서 검색 품질이 안 좋으면 retrieval만 손봄
  • 개인화가 이상하면 memory만 손봄

문제가 분리돼 보입니다.


memory와 RAG를 같이 쓸 때 추천 순서

이건 실제 구현 순서로도 좋습니다.

1단계

RAG만 먼저 붙인다
공식 문서/정책/위키 기반 답변이 안정적인지 본다. (LangChain Docs)

2단계

short-term memory를 붙인다
같은 대화 안에서 문맥이 이어지는지 본다. (LangChain Docs)

3단계

long-term memory를 아주 작게 붙인다
답변 스타일 선호 정도만 저장한다. (LangChain Docs)

4단계

memory와 RAG를 함께 조합한다
문서 기반 사실 + 사용자 선호를 함께 쓰게 만든다.

이 순서가 좋은 이유는
문제가 어디서 나는지 분리해서 보기 쉽기 때문입니다.

처음부터 다 붙이면,
이상한 답이 RAG 탓인지, memory 탓인지, prompt 탓인지 알기 어려워집니다.


자주 하는 실수

실수 1. 사용자 선호를 RAG 문서에 섞는다

이건 진짜 흔합니다.
개인화 정보와 공식 문서는 분리하는 편이 맞습니다.

실수 2. 문서 근거를 memory처럼 다룬다

정책 문서는 long-term memory가 아니라, 변경 가능하고 버전도 바뀌는 외부 지식입니다. 보통 RAG가 맞습니다.

실수 3. memory를 너무 많이 저장한다

공식 문서도 long-term memory는 one-size-fits-all이 아니고, 무엇을 언제 업데이트할지 고민하라고 말합니다. (LangChain Docs)

실수 4. memory만으로 personalization을 다 해결하려고 한다

실제론 답변 스타일, 톤, 자주 쓰는 context 정도가 가장 좋은 시작입니다.

실수 5. retrieval query와 memory query를 똑같이 본다

문서 검색은 “정책 내용”을 찾는 것이고, memory 검색은 “이 사용자 선호/사실”을 찾는 것입니다. 성격이 다릅니다.


비교: memory vs RAG vs 같이 쓰기

방식잘하는 것약한 것언제 좋나

memory만 개인화, 선호 반영 공식 문서 근거 부족 개인 assistant
RAG만 근거 기반 답변 개인화 부족 문서 Q&A
memory + RAG 개인화 + 근거 모두 가능 설계가 복잡해짐 사내 도우미, 고객 지원, 업무 assistant

이 비교표가 핵심입니다.

실제 서비스에선 종종 마지막 칸으로 갑니다.
하지만 처음부터 그렇게 만들 필요는 없습니다.


FAQ

Q. LangChain memory와 RAG 중 뭐부터 붙이는 게 좋나요?

대부분은 RAG부터가 더 낫습니다.
문서 기반 답변 품질을 먼저 안정화한 뒤, memory를 아주 작게 붙이는 편이 문제를 분리해서 보기 좋습니다. retrieval 문서는 RAG를 외부 데이터 소스 기반 QA 구조로 설명하고, memory 문서는 별도 개념으로 다룹니다. (LangChain Docs)

Q. 사용자 선호는 short-term memory에 넣어도 되나요?

한 대화 안에서만 필요하면 가능하지만, 여러 세션에서 계속 써야 하는 선호라면 long-term memory가 더 맞습니다. 공식 문서도 short-term은 thread-scoped, long-term은 across sessions라고 설명합니다. (LangChain Docs)

Q. 회사 정책을 long-term memory에 저장해도 되나요?

보통은 권장하지 않습니다. 정책은 공용 공식 정보이고, 바뀔 수 있으며, 출처가 중요합니다. 이런 건 RAG로 다루는 편이 더 자연스럽습니다.

Q. memory와 RAG를 한 prompt에 같이 넣어도 되나요?

네, 가능합니다. 오히려 실전에서는 자주 그렇게 합니다. 다만 역할은 분리해서 넣는 편이 좋습니다. 예를 들면:

  • 사용자 기억 섹션
  • 문서 근거 섹션

Q. long-term memory는 꼭 semantic search를 붙여야 하나요?

꼭은 아니지만 collection 방식에서는 semantic search가 꽤 잘 맞습니다. LangGraph add-memory 문서도 store에 embedding index를 붙여 semantic search를 하는 예시를 보여줍니다. (LangChain Docs)


핵심 요약

이번 글을 진짜 짧게 줄이면 이겁니다.

  • memory는 사람을 기억한다
  • RAG는 문서를 찾는다
  • 같이 쓰면 personalized + grounded 응답이 가능하다

그리고 이 감각이 잡히면 설계가 훨씬 쉬워집니다.

  • 답변 스타일 → memory
  • 회사 정책 → RAG
  • 사용자 팀 정보 → memory
  • 공식 프로세스 문서 → RAG

이렇게만 나눠도 절반은 정리됩니다.


마무리

저는 memory와 RAG를 처음 배울 때
둘이 거의 같은 건 줄 알았습니다.

둘 다 “무언가를 다시 꺼내 쓰는 것”처럼 보여서요.

근데 실제로 서비스를 만들면서 느낀 건,
이 둘은 닮았지만 꽤 다른 축이라는 거였습니다.

memory는 관계를 만들고,
RAG는 근거를 만듭니다.

그리고 좋은 서비스는 보통 둘 다 필요하더라고요.

  • 사용자를 기억해주고
  • 근거는 문서에서 찾고
  • 답은 그 둘을 적당히 섞어서 한다

저는 그 구조가 꽤 마음에 들었습니다.
그때부터 AI 서비스가 조금 더 현실적으로 보였거든요.


다음 글 예고

다음 글에서는
LangChain memory와 RAG를 같이 쓸 때 품질은 어떻게 올릴까? 검색 우선순위, prompt 조합, 충돌 처리 전략
으로 이어가겠습니다.

이제부터는 둘을 함께 붙이는 걸 넘어서,
둘이 서로 부딪힐 때 어떤 기준으로 정리해야 하는지 보게 될 거예요.


출처

  • LangChain Memory overview: short-term memory는 thread-scoped, long-term memory는 sessions across user/application-level data라고 설명. semantic memory, hot path/background, profile/collection도 함께 설명. (LangChain Docs)
  • LangChain Retrieval: retriever는 query로부터 relevant documents를 반환하는 인터페이스라고 설명. (LangChain Docs)
  • LangChain RAG: external text data source 기반으로 RAG agent를 만드는 흐름 설명. (LangChain Docs)
  • LangGraph Add memory: long-term memory store, namespace, embedding index, semantic search 예시 제공. (LangChain Docs)

LangChain, RAG, memory, long-term memory, short-term memory, AI Agent, LangGraph, 사내도우미, 생성형AI, 주니어개발자

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함
반응형