나는 6시간을 투자해 LangChain의 ConversationBufferMemory를 고쳤다 — 당신이 필요한 자동화 테스트

발행: (2026년 5월 4일 AM 10:07 GMT+9)
7 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you’ve already provided) here? Once I have it, I’ll translate it into Korean while preserving the original formatting, markdown, and code blocks.

Background

금요일 오후 4시 59분에 QA 동료가 지원 봇이 사용자를 Zhang San으로 기억했지만, 주문 번호를 물었을 때는 Li Si인 것처럼 답변했다고 보고했습니다. 로그에서는 LangChain의 ConversationBufferMemory가 세션 간에 채팅 기록을 섞고 있음을 보여주었습니다. 다음 사고가 한밤중에 발생하기 전에 메모리 저장의 정확성과 일관성을 확보하기 위해 자동화된 테스트 스위트가 필요하다는 것이 명확해졌습니다.

LLM 기반 채팅 제품의 과제

  • 메모리 모듈은 여러 턴에 걸쳐 컨텍스트를 유지해야 합니다 (예: “나는 베이징에 삽니다” → 이후 날씨 문의).
  • ConversationBufferMemory는 대화를 일반 텍스트로 저장합니다. 데이터가 RAM에 들어갈 때는 정상 작동하지만 Redis나 데이터베이스에 영속화하면 문제가 발생합니다.
  • 영속화는 직렬화/역직렬화 문제, 동시 읽기/쓰기, 그리고 오래된 메시지의 트리밍을 야기합니다.
  • 수동 QA는 레이스 컨디션 및 trim_messages가 Redis 연결이 끊어질 때 인접 세션을 혼동하는 등 엣지 케이스를 놓칠 수 있습니다.

실제 운영에서는 고객 서비스 봇이 수백 명의 동시 사용자를 처리했으며, 모두 하나의 Redis 인스턴스를 공유했습니다. 수동 테스트에서는 세션 간 누수가 발견되지 않았지만, 실제 트래픽에서는 벌레 잡기 게임처럼 버그가 금방 나타났다 사라지는 현상이 빠르게 드러났습니다.

Solution Overview

목표는 실제 LLM이나 Redis 인스턴스 없이 CI에서 핵심 메모리 로직을 실행하여 코드가 병합되기 전에 회귀를 잡아내는 것이었습니다.

  • 테스트 프레임워크: pytest – 고정(fixture) 시스템을 이용해 다양한 메모리 인스턴스를 깔끔하게 조립합니다.
  • Redis 시뮬레이션: fakeredis – 부작용이 전혀 없는 인‑메모리 모의 Redis입니다.
  • LLM 호출: unittest.mock 으로 모킹합니다. 핵심은 메모리이며, 언어 생성은 대상이 아닙니다.

내장된 langchain.tests는 얕은 인터페이스만 다루고 메시지‑형 변환이나 다중 세션 격리와 같은 시나리오를 놓치기 때문에, 커스텀 테스트 스위트가 필요했습니다. 실제 Redis 컨테이너를 실행하면 CI 빌드 시간이 약 3분 정도 늘어나는데, 이는 허용할 수 없는 수준이었습니다.

Architecture

  1. conftest.pyfake_redis_memory fixture 를 정의합니다.
  2. 해당 fixture 를 사용해 다양한 Memory 서브클래스(ConversationBufferMemory, ConversationSummaryMemory)를 구성합니다.
  3. 헬퍼 함수를 이용해 다회전 대화를 시뮬레이션합니다.
  4. load_memory_variables 가 완전하고 세션이 격리된 히스토리를 반환하는지 검증합니다.

모든 테스트는 네트워크 요청을 전혀 하지 않아야 하며, 각각 0.3 초 이하로 완료되어야 합니다.

테스트 픽스처 (conftest.py)

# conftest.py
import pytest
from unittest.mock import MagicMock
from langchain.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from fakeredis import FakeRedis

@pytest.fixture
def fake_redis_memory():
    """Create a factory that returns a ConversationBufferMemory backed by a fake Redis."""
    fake_redis_client = FakeRedis()

    def _create_memory(session_id: str):
        # Inject the fake Redis client to guarantee session isolation.
        history = RedisChatMessageHistory(
            session_id=session_id,
            redis_client=fake_redis_client
        )
        # `return_messages=True` yields Message objects, making assertions easier.
        memory = ConversationBufferMemory(
            chat_memory=history,
            return_messages=True
        )
        return memory

    return _create_memory

테스트 케이스 (test_memory_accuracy.py)

# test_memory_accuracy.py
from langchain.schema import HumanMessage, AIMessage

def test_buffer_memory_keeps_all_messages(fake_redis_memory):
    memory = fake_redis_memory("session_1202")

    # First turn
    memory.save_context(
        {"input": "我叫张三"},
        {"output": "你好张三"}
    )
    # Second turn
    memory.save_context(
        {"input": "我的订单号是多少"},
        {"output": "你的订单号是 #1123"}
    )

    variables = memory.load_memory_variables({})
    history = variables.get("history", [])

    # Expect 4 messages: two human inputs and two AI responses.
    assert len(history) == 4
    assert isinstance(history[0], HumanMessage)
    assert isinstance(history[1], AIMessage)
    assert isinstance(history[2], HumanMessage)
    assert isinstance(history[3], AIMessage)
    assert history[0].content == "我叫张三"
    assert history[1].content == "你好张三"
    assert history[2].content == "我的订单号是多少"
    assert history[3].content == "你的订单号是 #1123"

이 테스트는 모든 메시지가 올바르게 저장되고 검색됩니다 를 검증하여 “두 줄을 저장했는데 하나만 반환된다”는 버그를 제거합니다.

결론

pytest, fakeredis, 그리고 모의 LLM을 활용하여 빠르고 신뢰성 있으며 CI‑친화적인 테스트 스위트를 구축했습니다. 이 스위트는 다음을 수행합니다:

  • 세션 간 오염을 감지합니다.
  • 직렬화/역직렬화 로직을 검증합니다.
  • 트리밍 및 영속성이 예상대로 동작함을 보장합니다.

이 스위트는 테스트당 0.3초 이하로 실행되며, 외부 서비스가 필요 없고 ConversationBufferMemory가 실제 운영 부하에서도 올바르게 동작한다는 확신을 제공합니다.

0 조회
Back to Blog

관련 글

더 보기 »