memory-reuse
An execution cache layer for AI agents that cuts LLM and tool call costs by avoiding redundant computation. Drop it into any Python agent or LangGraph workflow with a single decorator.
- Framework-agnostic — LangGraph, LiteLLM, or any plain Python function.
- Zero required dependencies — the core runs on the standard library alone.
- Safe by default — per-user / per-session scoping prevents cross-user cache leaks.
- Typed — ships with
py.typed, fully type-hinted.
How it works
When your agent calls an LLM or a tool, memory-reuse hashes the inputs and
checks the cache first. On a hit it returns the stored result instantly — no
tokens spent, no API call made. On a miss it runs the real call and stores the
result for next time.
request ──► hash inputs ──► cache lookup
├── HIT ──► return cached result (0 cost)
└── MISS ──► run LLM/tool ──► store ──► return
Current scope (v0.1): exact-match caching — identical inputs hit the cache. Semantic caching (similar-but-not-identical inputs) is on the roadmap.
Install
Works with both pip and uv — pick whichever you use.
pip
pip install memory-reuse
uv
uv add memory-reuse
Optional extras
| Extra | What it adds | pip | uv |
|---|---|---|---|
redis |
Redis backend support | pip install memory-reuse[redis] |
uv add memory-reuse[redis] |
litellm |
LiteLLM cached wrappers | pip install memory-reuse[litellm] |
uv add memory-reuse[litellm] |
all |
Everything above | pip install memory-reuse[all] |
uv add memory-reuse[all] |
Note:
uvis a fast Python package manager. If you don't have it yet:pip install uvor see docs.astral.sh/uv
Quick start
from memory_reuse import MemoryCache, CacheConfig
from memory_reuse.integrations import cached_tool
cache = MemoryCache() # in-memory backend, 1-hour TTL
@cached_tool(cache, scope="global", ttl=300) # cache for 5 minutes
async def search_web(query: str) -> list[str]:
return await my_search_api(query) # only called on cache miss
Usage patterns
1 — Basic exact cache (LLM responses)
from memory_reuse import MemoryCache
cache = MemoryCache()
# Manual get/set
result = await cache.exact.get(["gpt-4", prompt], scope="global", scope_id=None)
if result is None:
result = await llm.ainvoke(prompt)
await cache.exact.set(["gpt-4", prompt], result, scope="global",
scope_id=None, ttl=3600)
2 — LangGraph node caching
from memory_reuse.integrations import cached_node
@cached_node(cache, scope="user", key_fields=["messages"])
async def summarise(state: dict) -> dict:
summary = await llm.ainvoke(state["messages"])
return {"summary": summary}
The decorator reads user_id from the state dict automatically, or from
cache.set_context(user_id=...).
3 — LangGraph tool caching
from memory_reuse.integrations import cached_tool
@cached_tool(cache, scope="session", ttl=120)
async def fetch_user_profile(user_id: str) -> dict:
return await db.get_user(user_id)
4 — LiteLLM (works with OpenAI, Claude, Bedrock, Groq, Ollama, and 100+ more)
from memory_reuse.integrations import cached_litellm_completion, cached_litellm_embedding
# Completion — same prompt + model = cache hit, 0 tokens used
response = await cached_litellm_completion(
cache,
model="gpt-4o-mini", # swap for any LiteLLM model string
messages=[{"role": "user", "content": "What is the capital of France?"}],
ttl=3600,
scope="global",
)
# Embeddings — deterministic, safe to cache for 24 hours
embeddings = await cached_litellm_embedding(
cache,
model="text-embedding-3-small",
input=["What is machine learning?"],
)
Backend options
| Backend | Extra required | Persistence | Notes |
|---|---|---|---|
memory |
none | in-process only | LRU eviction, TTL support |
redis |
[redis] |
yes | connection pool, lazy connect |
Configure via code or environment variables:
export MEMORY_REUSE_BACKEND=redis
export MEMORY_REUSE_REDIS_URL=redis://localhost:6379/0
export MEMORY_REUSE_DEFAULT_TTL=600
export MEMORY_REUSE_DEFAULT_SCOPE=user
cache = MemoryCache.from_env()
Multi-scope support
cache.set_context(user_id="alice", session_id="sess-001")
# User-scoped: alice cannot see bob's cache
await cache.exact.get(["key"], scope="user", scope_id="alice")
# Session-scoped: isolated per conversation
await cache.tool.get("search", args, scope="session", scope_id="sess-001")
# Global: shared across all users — safe for public, stateless data
await cache.exact.get(["key"], scope="global", scope_id=None)
Using scope="user" without a user_id raises ScopeViolationError to
prevent accidental cross-user data leaks.
Cache statistics
stats = cache.stats
print(f"Hit rate: {stats.hit_rate:.1%}")
print(f"Hits: {stats.hits} Misses: {stats.misses}")
print(stats.to_dict())
Examples
Runnable examples live in examples/:
basic_exact_cache.py— the cache primitives with no framework.langgraph_agent_example.py— cached nodes and tools in a LangGraph-style flow.langgraph_math_agent.py— a real ReAct agent with a calculator and a web-search tool, calling an LLM via LiteLLM.
export API_KEY="your-groq-key" # example uses Groq via LiteLLM
python examples/langgraph_math_agent.py
Roadmap
| Phase | Feature | Status |
|---|---|---|
| 1 | Exact cache (LLM + tool), Redis backend, LangGraph + LiteLLM | ✅ Shipped in v0.1 |
| 2 | Semantic cache (embedding similarity, configurable threshold) | Planned |
| 3 | Graph-level and node-level execution reuse | Planned |
| 4 | Analytics dashboard, more framework integrations | Planned |
Contributing
Contributions are welcome. See CONTRIBUTING.md for setup, tests, and code-style guidelines, and CONTRIBUTORS.md for the list of people who have helped build this project.
License
MIT © Pranit Pawar
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memory_reuse-0.1.0.tar.gz.
File metadata
- Download URL: memory_reuse-0.1.0.tar.gz
- Upload date:
- Size: 34.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7edcb0e0c996153a11bb72bb2dc615dd3df2da1bfd1f3595e0d07e6ac9ffb162
|
|
| MD5 |
426b7cee3a383a4bc8d796fef561d34e
|
|
| BLAKE2b-256 |
d2ce303202a9f366392aeaf94af7fdc62ba413f73f89d3f1cd2851742af81705
|
File details
Details for the file memory_reuse-0.1.0-py3-none-any.whl.
File metadata
- Download URL: memory_reuse-0.1.0-py3-none-any.whl
- Upload date:
- Size: 29.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac9cbd9a6c504d8f84797c416982e495d1d9ba635efd0dadfb6b85255e95034c
|
|
| MD5 |
44d8b5029920e3056ef8aa00d8013ae8
|
|
| BLAKE2b-256 |
9e88483746490db911f4bab5da56a2eb051f9bc3b65ab4d5e7f95f53123bc556
|