Skip to main content

Semantic caching wrapper for Redis — stop paying for duplicate LLM calls

Project description

semantic-redis-cache

Semantic caching for Redis. Stop paying for duplicate LLM calls.

PyPI version Python License


The Problem

Traditional caching misses similar queries:

"what is monthly revenue?"  → cache miss ❌
"show me monthly revenue"   → cache miss ❌
"monthly revenue figures?"  → cache miss ❌

All three mean the same thing. You pay for 3 LLM calls instead of 1.


The Solution

semantic-redis-cache uses sentence embeddings to find semantically similar cached responses. Zero cost embeddings — runs locally on your machine.

"what is monthly revenue?"  → cache miss  → calls LLM → stores result
"show me monthly revenue"   → cache HIT ✅ → returns cached result
"monthly revenue figures?"  → cache HIT ✅ → returns cached result

Install

pip install semantic-redis-cache

Requirements

  • Redis running locally or remotely
  • Python 3.8+
# Run Redis locally with Docker
docker run -d -p 6379:6379 redis

Quick Start

from semcache import SemanticRedisCache

# initialize cache
cache = SemanticRedisCache(threshold=0.85)

# define your LLM function
def call_llm(question: str) -> str:
    # replace with your actual LLM call
    # works with OpenAI, Claude, Groq, Ollama — anything
    return "Your LLM response here"

# get_or_set — checks cache first, calls LLM only on miss
result = cache.get_or_set(
    query="what is monthly revenue?",
    func=call_llm,
    question="what is monthly revenue?"
)

print(result.value)      # LLM response
print(result.hit)        # False (first call — cache miss)
print(result.similarity) # 0.0

# second call — similar question
result = cache.get_or_set(
    query="show me monthly revenue",
    func=call_llm,
    question="show me monthly revenue"
)

print(result.value)      # same cached response
print(result.hit)        # True  ✅ cache hit
print(result.similarity) # 0.91  ← similarity score

Full Example — With OpenAI

from semcache import SemanticRedisCache
from openai import OpenAI

client = OpenAI()
cache = SemanticRedisCache(threshold=0.85)

def call_openai(question: str) -> str:
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": question}]
    )
    return response.choices[0].message.content

questions = [
    "what is monthly revenue?",
    "show me the monthly revenue",   # similar → cache hit
    "monthly revenue figures?",      # similar → cache hit
    "what is the weather today?",    # different → cache miss
]

for question in questions:
    result = cache.get_or_set(
        query=question,
        func=call_openai,
        question=question
    )
    status = "HIT" if result.hit else "MISS"
    print(f"[{status}] ({result.similarity:.2f}) {question}")

print(cache.metrics)

Output:

[MISS] (0.00) what is monthly revenue?
[HIT]  (0.94) show me the monthly revenue
[HIT]  (0.91) monthly revenue figures?
[MISS] (0.12) what is the weather today?

{
  'hits': 2,
  'misses': 2,
  'hit_rate': '50.0%',
  'total_cached': 2
}

Full Example — With Claude

from semcache import SemanticRedisCache
import anthropic

client = anthropic.Anthropic()
cache = SemanticRedisCache(threshold=0.85)

def call_claude(question: str) -> str:
    message = client.messages.create(
        model="claude-opus-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": question}]
    )
    return message.content[0].text

result = cache.get_or_set(
    query="explain semantic caching",
    func=call_claude,
    question="explain semantic caching"
)

print(result.value)
print(result.hit)

Full Example — With Ollama (Free, Local)

from semcache import SemanticRedisCache
import ollama

cache = SemanticRedisCache(threshold=0.85)

def call_ollama(question: str) -> str:
    response = ollama.chat(
        model="llama3",
        messages=[{"role": "user", "content": question}]
    )
    return response["message"]["content"]

result = cache.get_or_set(
    query="what is machine learning?",
    func=call_ollama,
    question="what is machine learning?"
)

print(result.value)
print(result.hit)

Test Script — No LLM Required

Run this to verify everything is working:

from semcache import SemanticRedisCache

# initialize
cache = SemanticRedisCache(
    host="localhost",
    port=6379,
    threshold=0.85,
    namespace="test"
)

# clear any existing test data
cache.flush()

# mock LLM function — no API key needed
call_count = 0
def mock_llm(question: str) -> str:
    global call_count
    call_count += 1
    return f"Answer to: {question}"

print("=" * 50)
print("semantic-redis-cache test")
print("=" * 50)

# test 1 — cache miss (first call)
result = cache.get_or_set(
    "what is monthly revenue?",
    mock_llm,
    question="what is monthly revenue?"
)
print(f"\nTest 1 — First call:")
print(f"  Hit:        {result.hit}")        # False
print(f"  LLM calls:  {call_count}")        # 1
print(f"  Value:      {result.value}")

# test 2 — semantic hit (similar question)
result = cache.get_or_set(
    "show me the monthly revenue",
    mock_llm,
    question="show me the monthly revenue"
)
print(f"\nTest 2 — Similar question:")
print(f"  Hit:        {result.hit}")        # True
print(f"  Similarity: {result.similarity}") # ~0.90
print(f"  LLM calls:  {call_count}")        # still 1 — not called again

# test 3 — cache miss (unrelated question)
result = cache.get_or_set(
    "what is the weather today?",
    mock_llm,
    question="what is the weather today?"
)
print(f"\nTest 3 — Unrelated question:")
print(f"  Hit:        {result.hit}")        # False
print(f"  LLM calls:  {call_count}")        # 2

# test 4 — metrics
print(f"\nMetrics:")
print(f"  {cache.metrics}")

# test 5 — manual get and set
cache.set("capital of France?", "Paris")
result = cache.get("what is the capital of France?")
print(f"\nTest 4 — Manual get/set:")
print(f"  Hit:        {result.hit}")        # True
print(f"  Value:      {result.value}")      # Paris
print(f"  Similarity: {result.similarity}")

print("\n" + "=" * 50)
print("All tests passed ✅")
print("=" * 50)

Expected output:

==================================================
semantic-redis-cache test
==================================================

Test 1 — First call:
  Hit:        False
  LLM calls:  1
  Value:      Answer to: what is monthly revenue?

Test 2 — Similar question:
  Hit:        True
  Similarity: 0.9034
  LLM calls:  1

Test 3 — Unrelated question:
  Hit:        False
  LLM calls:  2

Metrics:
  {'hits': 1, 'misses': 2, 'hit_rate': '33.33%', 'total_cached': 2}

Test 4 — Manual get/set:
  Hit:        True
  Value:      Paris
  Similarity: 0.9187

==================================================
All tests passed ✅
==================================================

Configuration

cache = SemanticRedisCache(
    host="localhost",                # Redis host
    port=6379,                       # Redis port
    threshold=0.85,                  # similarity threshold (0.0 - 1.0)
    ttl=86400,                       # cache TTL in seconds (24 hours)
    model_name="all-MiniLM-L6-v2",  # sentence transformer model
    namespace="semcache"             # Redis key namespace
)

Threshold Guide

Threshold Behaviour
0.70 Aggressive — high hit rate, risk of wrong answers
0.85 Balanced — recommended starting point ✅
0.90 Conservative — safe, fewer hits
0.95 Strict — almost like exact matching

API Reference

SemanticRedisCache

cache = SemanticRedisCache(...)
Method Description
cache.get(query) Search cache for similar query
cache.set(query, response) Store query and response
cache.get_or_set(query, func, *args, **kwargs) Check cache, call func on miss
cache.delete(query) Delete a cached entry
cache.flush() Clear all cached entries
cache.metrics Get hit rate and counts

CacheResult

result.value       # cached or fresh response
result.hit         # True if cache hit
result.similarity  # cosine similarity score
result.cached_at   # when it was cached

How It Works

1. Query arrives
         ↓
2. Convert to embedding (local model — free)
         ↓
3. Search Redis for similar embeddings
         ↓
4. Similarity score > threshold?
         ↓ yes              ↓ no
5. Return cached      Call your LLM
   response           Store result
                      Return response

Embeddings are generated locally using sentence-transformers — zero API cost.


Results

  • 65-70% cache hit rate on real LLM workloads
  • Zero cost embeddings — runs on your machine
  • Works with any LLM — OpenAI, Claude, Groq, Ollama
  • Drop-in Redis wrapper — no infrastructure changes

Contributing

PRs welcome. Open an issue first for major changes.

git clone https://github.com/YOUR_USERNAME/semcache
cd semcache
pip install -e ".[dev]"
pytest tests/

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

semantic_redis_cache-0.1.5.tar.gz (7.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

semantic_redis_cache-0.1.5-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

Details for the file semantic_redis_cache-0.1.5.tar.gz.

File metadata

  • Download URL: semantic_redis_cache-0.1.5.tar.gz
  • Upload date:
  • Size: 7.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.20

File hashes

Hashes for semantic_redis_cache-0.1.5.tar.gz
Algorithm Hash digest
SHA256 f0db186a6a727a9a241c0c57cab8b665b5d15d4e6ba6fd60b6f1cf14757c4859
MD5 379a6080f4d4822c9a2e113fb4f064c8
BLAKE2b-256 e14d6bf8d828c53f935f8ca6f2f0761befc7b5062bc4a8617436b8cfd744d50c

See more details on using hashes here.

File details

Details for the file semantic_redis_cache-0.1.5-py3-none-any.whl.

File metadata

File hashes

Hashes for semantic_redis_cache-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 bda536cbd8caa358d417cebc1067d6c59e4ed660ef7311a08a93904afdd69fb6
MD5 4407cc42b5fe450f469cbbd2e49a575f
BLAKE2b-256 2ffbf73b8947be89d2e674374bc24d3ccb5239d9eca404f7db89ea265e7c21b5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page