# Semantic caching

```json metadata
{
  "schema_version": 2,
  "title": "Semantic caching",
  "description": "Cache LLM responses and tool results using semantic similarity with Redis.",
  "categories": ["docs","integrate","oss","rs","rc"],
  "group": "ai",
  "tableOfContents": {"sections":[{"id":"how-it-works","title":"How it works"},{"children":[{"id":"redisvl-provider-local-embeddings","title":"RedisVL provider (local embeddings)"},{"id":"langcache-provider-managed","title":"LangCache provider (managed)"}],"id":"cache-providers","title":"Cache providers"},{"id":"cache-entry-ids-and-targeted-invalidation","title":"Cache entry IDs and targeted invalidation"},{"children":[{"id":"configuration-notes","title":"Configuration notes"}],"id":"llm-response-cache","title":"LLM response cache"},{"id":"tool-result-cache","title":"Tool result cache"},{"id":"more-info","title":"More info"}]}

,
  "codeExamples": []
}
```


adk-redis provides semantic caching at two levels: LLM response caching and tool result caching, both backed by Redis. Caching uses ADK's callback system, so enabling it requires no changes to your agent's core logic.

## How it works

Before each LLM call (or tool execution), the cache checks whether a semantically similar prompt already exists in Redis. If so, the cached response is returned immediately. If not, the call proceeds and the response is stored for future lookups.

## Cache providers

Two backends are available:

| Provider | Embeddings | Setup | Best for |
|----------|-----------|-------|----------|
| `RedisVLCacheProvider` | Local (you provide vectorizer) | Self-managed Redis | Full control |
| `LangCacheProvider` | Server-side (managed) | API key from Redis Cloud | Zero embedding overhead |

### RedisVL provider (local embeddings)

```python
from redisvl.utils.vectorize import HFTextVectorizer
from adk_redis.cache import RedisVLCacheProvider, RedisVLCacheProviderConfig

provider = RedisVLCacheProvider(
    config=RedisVLCacheProviderConfig(
        redis_url="redis://localhost:6379",
        name="my_cache",
        ttl=3600,
        distance_threshold=0.1,
    ),
    vectorizer=HFTextVectorizer(model="redis/langcache-embed-v2"),
)
```

### LangCache provider (managed)

No local vectorizer needed. Embeddings are generated server-side.

```python
import os

from adk_redis.cache import LangCacheProvider, LangCacheProviderConfig

provider = LangCacheProvider(
    config=LangCacheProviderConfig(
        cache_id=os.environ["LANGCACHE_CACHE_ID"],
        api_key=os.environ["LANGCACHE_API_KEY"],
        server_url="https://aws-us-east-1.langcache.redis.io",
        ttl=3600,
    )
)
```

Set `server_url` to the endpoint for your LangCache region.

## Cache entry IDs and targeted invalidation

Both providers return a `CacheEntry` from `check()` and an entry ID from
`store()`. When the backend exposes a stable identifier, `CacheEntry.entry_id`
carries it, and you can retire exactly that entry with `delete_by_id()` instead
of clearing the whole cache.

```python
entry = await provider.check(prompt="What is the return policy?")

if entry is not None and entry.entry_id is not None:
    # Retire one stale answer without touching unrelated entries
    await provider.delete_by_id(entry.entry_id)
```

`CacheEntry` also carries the matched `prompt`, the cached `response`, the match
`distance`, and any `metadata` stored alongside the entry. `entry_id` is `None`
when the backend does not expose an identifier.

## LLM response cache

Intercepts model calls through ADK's `before_model_callback` and `after_model_callback`.

```python
from adk_redis.cache import (
    LLMResponseCache,
    LLMResponseCacheConfig,
    create_llm_cache_callbacks,
)

llm_cache = LLMResponseCache(
    provider=provider,
    config=LLMResponseCacheConfig(
        first_message_only=True,
        include_app_name=True,
        include_user_id=True,
    ),
)

before_cb, after_cb = create_llm_cache_callbacks(llm_cache)

agent = Agent(
    name="cached_agent",
    model="gemini-2.5-flash",
    instruction="You are a helpful assistant.",
    before_model_callback=before_cb,
    after_model_callback=after_cb,
)
```

### Configuration notes

- **`first_message_only=True`** caches only the first message in a session. Later messages depend on conversation context, making cache hits unreliable.
- Function call responses and errors are automatically excluded from caching.
- **`distance_threshold`** (set on the provider) controls how similar two prompts need to be for a cache hit. `0.0` = exact match only. `0.1` = small phrasing variations. Higher values risk returning wrong cached responses.

## Tool result cache

Caches tool executions using `before_tool_callback` and `after_tool_callback`.

```python
from adk_redis.cache import (
    ToolCache,
    ToolCacheConfig,
    create_tool_cache_callbacks,
)

tool_cache = ToolCache(
    provider=provider,
    config=ToolCacheConfig(
        tool_names={"web_search", "get_weather"},
    ),
)

before_tool_cb, after_tool_cb = create_tool_cache_callbacks(tool_cache)
```

The `tool_names` set specifies which tools to cache. Not all tools are idempotent: cache `get_weather` but not `send_email`.

## More info

- [semantic_cache example](https://github.com/redis-developer/adk-redis/tree/main/examples/semantic_cache): Local caching with RedisVL
- [langcache_cache example](https://github.com/redis-developer/adk-redis/tree/main/examples/langcache_cache): Managed caching with LangCache

