惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
Y
Y Combinator Blog
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
S
SegmentFault 最新的问题
J
Java Code Geeks
V
Visual Studio Blog
H
Help Net Security
Stack Overflow Blog
Stack Overflow Blog
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
B
Blog RSS Feed
The Cloudflare Blog
MyScale Blog
MyScale Blog
月光博客
月光博客
Microsoft Security Blog
Microsoft Security Blog
美团技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
10x Faster LLM Memory Testing: From Manual Verification t...
BAOFUFAN · 2026-06-23 · via DEV Community

It was 1 a.m. when a colleague messaged me: our smart customer service bot had amnesia again — “The user just told us the return address, and in the very next turn the bot asked ‘What would you like to return?’”

I opened the logs and started eyeballing memory variables across dozens of conversation turns. An hour later I finally nailed it: the k parameter in ConversationBufferWindowMemory was set wrong, keeping only the single most recent exchange. At that moment, I thought: do we really have to test LLM memory by chatting line by line, by hand? This can’t go on.

Breaking down the problem

Once you give an LLM-powered application a Memory component, its behavior becomes subtle. Is memory being written at the right time? Is it keeping or forgetting information as expected? Under multi-turn conversations, memory types like summary, buffer, and entity stack on top of each other; a tiny misconfiguration leads to the model completely forgetting what was just said.

Manual validation usually means opening a terminal, entering a few rounds of conversation, and manually inspecting memory.load_memory_variables({}). Sometimes you even have to infer the memory state from the model’s replies. This approach has fatal flaws:

  • Not repeatable – The inputs, order, and timing of a manual chat are nearly impossible to reproduce exactly. Intermittent bugs are impossible to catch.
  • Low coverage – A human tester will only cover a few happy paths. Edge cases like a full memory buffer, token truncation, or multiple memory components working together rarely get tested.
  • Slow feedback – Change one memory config, restart the service, chat through several turns, and visually compare results. A single regression test run easily takes 30+ minutes.

Why not just print() the memory variable somewhere inside the code? Because in a real-world Chain the calls are often asynchronous and streamed — the intermediate printed state may be inaccurate, and you still rely on a human to read the output. This can’t be integrated into CI. We need a way to turn memory state verification into an automated, repeatable, and quantifiable testing process.

Solution design

Core idea: use LangChain’s BaseCallbackHandler to automatically capture the memory state at the end of every LLM call, then write test cases with Pytest to assert on it.

Why Pytest instead of unittest? Pytest’s fixture system lets you easily build a Chain instance equipped with memory; parametrized tests are a natural fit for validating different memory configurations in bulk. Why not just call memory.load_memory_variables() directly? Because many memory updates happen inside the Chain’s internal logic (e.g., inside ConversationChain._call). Calling from the outside may give you an intermediate, inconsistent state. We need a mechanism that “peeks” at the memory right after the chain finishes execution. A custom callback can be hooked onto on_chain_end or on_llm_end, guaranteeing the correct timing.

Architecturally, we agreed on a test flow: each test case receives a pre-configured Chain (including a specified Memory) via a fixture; after executing chain.run(user_input), the test asserts on the memory variables exposed by the callback. Memory serialization uses the dict returned by load_memory_variables, which is sufficient for arbitrary comparisons.

Compared with other approaches:

  • Using environment variables or global variables to stash memory state: pollutes the environment and breaks under concurrent tests.
  • Directly accessing internal attributes like memory.chat_memory.messages: highly invasive; different Memory subclasses have different implementations, making tests fragile.
  • This solution is based on the public interface, works with any Memory subclass, and can be extended freely.

Core implementation

1. Custom callback to capture memory state

This code solves the problem of “how to get a memory snapshot after a Chain run.” We write a MemoryCaptureCallback that collects memory variables on on_chain_end and stores them in a thread-safe list for test assertions.

from typing import Any, Dict, List
from langchain.callbacks.base import BaseCallbackHandler
from langchain.schema import BaseMemory

class MemoryCaptureCallback(BaseCallbackHandler):
    """在 Chain 结束时捕获 Memory 状态,供测试断言使用。"""

    def __init__(self, memory: BaseMemory):
        super().__init__()
        self.memory = memory
        # 每次运行的记忆快照列表,每个元素是一次 chain 调用结束后的状态
        self.snapshots: List[Dict[str, Any]] = []

    def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None:
        # 关键:必须在 chain 完全结束后读取,否则可能拿到未更新的数据
        snapshot = self.memory.load_memory_variables({})
        self.snapshots.append(dict(snapshot))  # 复制一份,防止后续变化影响

2. Pytest fixture to build a Chain with memory

This fixture solves the problem of “how each test case can quickly obtain a working conversation Chain.” Here we use ConversationBufferMemory as an example; you can replace it with any Memory.

import pytest
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain.chat_models import ChatOpenAI
# 假设已有可 mock 的 LLM,实际测试中建议替换成轻量 mock 或测试专用模型

@pytest.fixture
def memory_capture_chain():
    """返回一个装配 MemoryCaptureCallback 的 ConversationChain 和捕获器实例。"""
    memory = ConversationBufferMemory(return_messages=True)
    llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo")  # 测试可用 mock
    chain = ConversationChain(llm=llm, memory=memory, verbose=False)

    # 把自定义 callback 加入 chain
    capture = MemoryCaptureCallback(memory)
    chain.callbacks = [capture]  # 或者用 chain.verbose=False