Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Sep 16, 2025

The Redis caching implementation was experiencing cache misses even when data existed in Redis, causing unnecessary LLM API calls and always returning cached=False. This occurred because the _check_cache method in ChatCompletionCache didn't handle string values returned by the Redis store.

Problem

When using Redis with decode_responses=True or when Redis returns string data due to JSON decode errors, the RedisStore.get() method would return string values instead of dictionaries or CreateResult objects. The _check_cache method only handled:

  1. dict objects (converted to CreateResult)
  2. list objects (processed for streaming scenarios)
  3. Already correct types (CreateResult or list)

When a string was returned, the method would fall through and return None, cache_key, causing a cache miss even though valid data existed in Redis.

Reproduction

import asyncio
from autogen_core.models import UserMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_ext.models.cache import ChatCompletionCache, CHAT_CACHE_VALUE_TYPE
from autogen_ext.cache_store.redis import RedisStore
import redis

async def main():
    openai_model_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
    redis_instance = redis.Redis(decode_responses=True)
    cache_store = RedisStore[CHAT_CACHE_VALUE_TYPE](redis_instance)
    cache_client = ChatCompletionCache(openai_model_client, cache_store)

    # First call - stores in cache
    response1 = await cache_client.create([UserMessage(content="what is the capital of france", source="user")])
    print(f"First response cached: {response1.cached}")  # False (expected)
    
    # Second call - should use cache but doesn't due to bug
    response2 = await cache_client.create([UserMessage(content="what is the capital of france", source="user")])  
    print(f"Second response cached: {response2.cached}")  # False (BUG - should be True)

Solution

Enhanced the _check_cache method to handle string values by:

  1. String detection: Added isinstance(cached_result, str) check
  2. JSON parsing: Attempt to parse string as JSON using json.loads()
  3. Object reconstruction: Convert parsed JSON back to CreateResult objects
  4. Streaming support: Handle both single results and streaming lists stored as JSON strings
  5. Error handling: Gracefully handle invalid JSON or malformed data by treating as cache miss

The fix ensures that when Redis returns string representations of cached data, they are properly deserialized back into CreateResult objects, allowing cache hits to work correctly.

Testing

Added comprehensive test coverage including:

  • String JSON deserialization for single CreateResult objects
  • String JSON deserialization for streaming result lists
  • Graceful handling of invalid JSON strings
  • Graceful handling of valid JSON with invalid data structure
  • Verification that existing functionality remains intact

After the fix, the reproduction case now correctly shows:

  • First response: cached=False (LLM called)
  • Second response: cached=True (cache used)

Fixes #6984.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@Copilot Copilot AI assigned Copilot and ekzhu Sep 16, 2025
Copy link
Contributor Author

Copilot AI commented Sep 16, 2025

@ekzhu 👋 This repository doesn't have Copilot instructions. With Copilot instructions, I can understand the repository better, work faster and produce higher quality PRs.

I can generate a .github/copilot-instructions.md file for you automatically. Click here to open a pre-filled issue and assign it to me. I'll write the instructions, and then tag you for review.

@Copilot Copilot AI changed the title [WIP] Redis caching always return False due to unmet condition Fix Redis caching always returning False due to unhandled string values Sep 16, 2025
@Copilot Copilot AI requested a review from ekzhu September 16, 2025 08:13
Copilot finished work on behalf of ekzhu September 16, 2025 08:13
@ekzhu ekzhu marked this pull request as ready for review September 16, 2025 08:34
Copy link

codecov bot commented Sep 16, 2025

Codecov Report

❌ Patch coverage is 88.23529% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.93%. Comparing base (79d5d6a) to head (830c104).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...autogen_ext/models/cache/_chat_completion_cache.py 88.23% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7022   +/-   ##
=======================================
  Coverage   80.93%   80.93%           
=======================================
  Files         237      237           
  Lines       18240    18258   +18     
=======================================
+ Hits        14762    14778   +16     
- Misses       3478     3480    +2     
Flag Coverage Δ
unittests 80.93% <88.23%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ekzhu ekzhu merged commit e0e39e4 into main Sep 16, 2025
64 checks passed
@ekzhu ekzhu deleted the copilot/fix-6984 branch September 16, 2025 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Redis caching always return False due to unmet condition
2 participants