Middleware#
The langgraph-checkpoint-redis library includes a middleware system for
LangGraph agents. Middleware intercepts model calls and tool calls to add
caching, routing, and memory capabilities – all backed by Redis.
Middleware implements LangChain’s AgentMiddleware protocol and works with
create_agent and LangGraph’s prebuilt agents.
Available Middleware#
SemanticCacheMiddleware#
Caches LLM responses by semantic similarity. When a new request is semantically close to a previously cached one, the cached response is returned without calling the LLM.
from langgraph.middleware.redis import SemanticCacheMiddleware, SemanticCacheConfig
config = SemanticCacheConfig(
redis_url="redis://localhost:6379",
name="llmcache",
distance_threshold=0.1, # Lower = stricter matching
ttl_seconds=3600, # Cache entries expire after 1 hour
cache_final_only=True, # Only cache responses without tool calls
)
cache_middleware = SemanticCacheMiddleware(config)
Configuration#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Redis index name for the cache |
|
|
|
Max vector distance for a cache hit |
|
|
|
Cache entry TTL in seconds |
|
|
|
Only cache responses without tool calls |
|
|
|
Tools whose results are safe to cache through |
ToolResultCacheMiddleware#
Caches tool call results using exact-match lookup. Same tool name plus same
arguments produces the same cache key. Uses direct Redis GET/SET rather
than vector similarity.
from langgraph.middleware.redis import ToolResultCacheMiddleware, ToolCacheConfig
config = ToolCacheConfig(
redis_url="redis://localhost:6379",
name="toolcache",
ttl_seconds=7200,
cacheable_tools=["web_search", "calculate"],
excluded_tools=["send_email"],
)
tool_middleware = ToolResultCacheMiddleware(config)
Tool Metadata for Cacheability#
Tools can declare metadata to control caching behavior:
from langchain_core.tools import tool
@tool(metadata={
"cacheable": True, # Explicitly mark as cacheable
"read_only": True, # Does not produce side effects
"idempotent": True, # Same input always gives same output
})
def search_database(query: str) -> str:
"""Search the database for information."""
...
@tool(metadata={
"destructive": True, # Produces side effects
"volatile": True, # Results change over time
})
def delete_record(record_id: str) -> str:
"""Delete a record from the database."""
...
The middleware respects these metadata fields:
Metadata Field |
Effect |
|---|---|
|
Explicitly allow caching |
|
Explicitly prevent caching |
|
Never cache (side effects) |
|
Never cache (time-dependent results) |
|
Safe to cache |
|
Safe to cache |
ToolCacheConfig Options#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Allowlist of tools to cache (None = all non-excluded) |
|
|
|
Tools to never cache |
|
|
|
Arg names that prevent caching (e.g., |
|
|
|
Arg names stripped from cache key |
|
|
|
Tool name prefixes that prevent caching (e.g., |
ConversationMemoryMiddleware#
Injects semantically relevant past messages into the current context using Redis-backed session management.
from langgraph.middleware.redis import ConversationMemoryMiddleware, ConversationMemoryConfig
config = ConversationMemoryConfig(
redis_url="redis://localhost:6379",
name="conversation_memory",
session_tag="user-123",
top_k=5,
distance_threshold=0.7,
ttl_seconds=86400,
)
memory_middleware = ConversationMemoryMiddleware(config)
SemanticRouterMiddleware#
Routes requests based on semantic matching against predefined route categories. Useful for directing different types of user queries to specialized handlers.
from langgraph.middleware.redis import SemanticRouterMiddleware, SemanticRouterConfig
config = SemanticRouterConfig(
redis_url="redis://localhost:6379",
name="semantic_router",
routes=[
{
"name": "technical",
"references": [
"How do I fix this error?",
"What API should I use?",
"Debug this code",
],
},
{
"name": "general",
"references": [
"Tell me a joke",
"What's the weather like?",
"How are you?",
],
},
],
max_k=3,
aggregation_method="avg",
)
router_middleware = SemanticRouterMiddleware(config)
MiddlewareStack#
Compose multiple middleware into a single stack. Middleware are applied in order: the first wraps the second, which wraps the third, and so on.
from langgraph.middleware.redis import MiddlewareStack
stack = MiddlewareStack([
cache_middleware,
tool_middleware,
memory_middleware,
])
Use the stack with create_agent:
from langchain.agents import create_agent
agent = create_agent(
model="gpt-4o",
tools=[search, calculate],
middleware=[stack],
)
from_configs Factory#
Create a middleware stack from configuration objects, optionally sharing a single Redis connection:
from langgraph.middleware.redis import from_configs, SemanticCacheConfig, ToolCacheConfig
stack = from_configs(
redis_url="redis://localhost:6379",
configs=[
SemanticCacheConfig(ttl_seconds=3600),
ToolCacheConfig(cacheable_tools=["search"]),
],
)
create_caching_stack Convenience#
For the common pattern of combining semantic and tool caching:
from langgraph.middleware.redis import create_caching_stack
stack = create_caching_stack(
redis_url="redis://localhost:6379",
semantic_cache_ttl=3600,
tool_cache_ttl=7200,
cacheable_tools=["search", "calculate"],
excluded_tools=["send_email"],
distance_threshold=0.1,
)
Connection Sharing with IntegratedRedisMiddleware#
When you already have a RedisSaver or RedisStore, use
IntegratedRedisMiddleware to create middleware that connects to the same
Redis server:
from langgraph.checkpoint.redis import AsyncRedisSaver
from langgraph.middleware.redis import IntegratedRedisMiddleware, SemanticCacheConfig
async with AsyncRedisSaver.from_conn_string("redis://localhost:6379") as saver:
await saver.asetup()
stack = IntegratedRedisMiddleware.from_saver(
saver,
[
SemanticCacheConfig(ttl_seconds=3600),
ToolCacheConfig(cacheable_tools=["search"]),
],
)
graph = builder.compile(checkpointer=saver)
# Both the checkpointer and middleware use the same Redis server
You can also create middleware from a store:
from langgraph.store.redis import AsyncRedisStore
from langgraph.middleware.redis import IntegratedRedisMiddleware, SemanticCacheConfig
async with AsyncRedisStore.from_conn_string("redis://localhost:6379") as store:
await store.asetup()
stack = IntegratedRedisMiddleware.from_store(
store,
[SemanticCacheConfig(ttl_seconds=3600)],
)
Base Configuration#
All middleware configs inherit from MiddlewareConfig:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Redis connection URL |
|
|
|
Existing Redis client instance |
|
|
|
Extra connection arguments |
|
|
|
Pass through on Redis errors instead of failing |
When graceful_degradation is True (the default), middleware failures are
logged as warnings and the request passes through to the underlying handler
without caching.
Next Steps#
Checkpointers – persist graph state with Redis checkpointers
Stores – cross-thread key-value storage
Azure and Enterprise Deployment – deploy middleware with enterprise Redis