From 19b96e2d8e06e3c01d7384db35f933736bf6d228 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:56:41 +0000 Subject: [PATCH 1/7] Initial plan From 9d33cd0982c9e963efd2459e77d01b26677e6cc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:04:17 +0000 Subject: [PATCH 2/7] Fix LangCache clear() to raise NotImplementedError instead of sending empty attributes - Changed delete() and adelete() to raise NotImplementedError - Added validation to delete_by_attributes() and adelete_by_attributes() to reject empty attributes - Updated all related tests to verify new behavior - Added new tests for delete_by_attributes with both valid and empty attributes Co-authored-by: abrookins <97182+abrookins@users.noreply.github.com> --- redisvl/extensions/cache/llm/langcache.py | 46 ++++++- tests/unit/test_langcache_semantic_cache.py | 125 ++++++++++++++++++-- 2 files changed, 152 insertions(+), 19 deletions(-) diff --git a/redisvl/extensions/cache/llm/langcache.py b/redisvl/extensions/cache/llm/langcache.py index a35b7f99..057e79a7 100644 --- a/redisvl/extensions/cache/llm/langcache.py +++ b/redisvl/extensions/cache/llm/langcache.py @@ -536,18 +536,34 @@ async def aupdate(self, key: str, **kwargs) -> None: def delete(self) -> None: """Delete the entire cache. - This deletes all entries in the cache by calling delete_query - with no attributes. + Note: LangCache API does not support deleting all entries without + specific attributes. Use delete_by_attributes() to delete entries + matching specific criteria, or delete_by_id() to delete individual entries. + + Raises: + NotImplementedError: LangCache does not support clearing all entries. """ - self._client.delete_query(attributes={}) + raise NotImplementedError( + "LangCache API does not support deleting all entries without attributes. " + "Use delete_by_attributes(attributes) to delete entries matching specific " + "criteria, or delete_by_id(entry_id) to delete individual entries." + ) async def adelete(self) -> None: """Async delete the entire cache. - This deletes all entries in the cache by calling delete_query - with no attributes. + Note: LangCache API does not support deleting all entries without + specific attributes. Use adelete_by_attributes() to delete entries + matching specific criteria, or adelete_by_id() to delete individual entries. + + Raises: + NotImplementedError: LangCache does not support clearing all entries. """ - await self._client.delete_query_async(attributes={}) + raise NotImplementedError( + "LangCache API does not support deleting all entries without attributes. " + "Use adelete_by_attributes(attributes) to delete entries matching specific " + "criteria, or adelete_by_id(entry_id) to delete individual entries." + ) def clear(self) -> None: """Clear the cache of all entries. @@ -584,10 +600,19 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]: Args: attributes (Dict[str, Any]): Attributes to match for deletion. + Cannot be empty. Returns: Dict[str, Any]: Result of the deletion operation. + + Raises: + ValueError: If attributes is empty. """ + if not attributes: + raise ValueError( + "attributes cannot be empty. Provide specific attributes to match, " + "or use delete_by_id(entry_id) to delete individual entries." + ) result = self._client.delete_query(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} @@ -597,10 +622,19 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A Args: attributes (Dict[str, Any]): Attributes to match for deletion. + Cannot be empty. Returns: Dict[str, Any]: Result of the deletion operation. + + Raises: + ValueError: If attributes is empty. """ + if not attributes: + raise ValueError( + "attributes cannot be empty. Provide specific attributes to match, " + "or use adelete_by_id(entry_id) to delete individual entries." + ) result = await self._client.delete_query_async(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} diff --git a/tests/unit/test_langcache_semantic_cache.py b/tests/unit/test_langcache_semantic_cache.py index 8aa884a4..066af0d5 100644 --- a/tests/unit/test_langcache_semantic_cache.py +++ b/tests/unit/test_langcache_semantic_cache.py @@ -354,10 +354,8 @@ def test_check_with_empty_attributes_does_not_send_attributes( _, call_kwargs = mock_client.search.call_args assert "attributes" not in call_kwargs - def test_delete(self, mock_langcache_client): - """Test deleting the entire cache.""" - _, mock_client = mock_langcache_client - + def test_delete_not_supported(self, mock_langcache_client): + """Test that delete() raises NotImplementedError.""" cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -365,17 +363,28 @@ def test_delete(self, mock_langcache_client): api_key="test-key", ) - cache.delete() - - mock_client.delete_query.assert_called_once_with(attributes={}) + with pytest.raises( + NotImplementedError, match="does not support deleting all entries" + ): + cache.delete() @pytest.mark.asyncio - async def test_adelete(self, mock_langcache_client): - """Test async deleting the entire cache.""" - _, mock_client = mock_langcache_client + async def test_adelete_not_supported(self, mock_langcache_client): + """Test that async delete() raises NotImplementedError.""" + cache = LangCacheSemanticCache( + name="test", + server_url="https://api.example.com", + cache_id="test-cache", + api_key="test-key", + ) - mock_client.delete_query_async = AsyncMock() + with pytest.raises( + NotImplementedError, match="does not support deleting all entries" + ): + await cache.adelete() + def test_clear_not_supported(self, mock_langcache_client): + """Test that clear() raises NotImplementedError.""" cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -383,9 +392,25 @@ async def test_adelete(self, mock_langcache_client): api_key="test-key", ) - await cache.adelete() + with pytest.raises( + NotImplementedError, match="does not support deleting all entries" + ): + cache.clear() + + @pytest.mark.asyncio + async def test_aclear_not_supported(self, mock_langcache_client): + """Test that async clear() raises NotImplementedError.""" + cache = LangCacheSemanticCache( + name="test", + server_url="https://api.example.com", + cache_id="test-cache", + api_key="test-key", + ) - mock_client.delete_query_async.assert_called_once_with(attributes={}) + with pytest.raises( + NotImplementedError, match="does not support deleting all entries" + ): + await cache.aclear() def test_delete_by_id(self, mock_langcache_client): """Test deleting a single entry by ID.""" @@ -402,6 +427,80 @@ def test_delete_by_id(self, mock_langcache_client): mock_client.delete_by_id.assert_called_once_with(entry_id="entry-123") + def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client): + """Test deleting entries by attributes with valid attributes.""" + _, mock_client = mock_langcache_client + + mock_response = MagicMock() + mock_response.model_dump.return_value = {"deleted_count": 5} + mock_client.delete_query.return_value = mock_response + + cache = LangCacheSemanticCache( + name="test", + server_url="https://api.example.com", + cache_id="test-cache", + api_key="test-key", + ) + + result = cache.delete_by_attributes({"topic": "python"}) + + assert result == {"deleted_count": 5} + mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"}) + + def test_delete_by_attributes_with_empty_attributes_raises_error( + self, mock_langcache_client + ): + """Test that delete_by_attributes raises ValueError with empty attributes.""" + cache = LangCacheSemanticCache( + name="test", + server_url="https://api.example.com", + cache_id="test-cache", + api_key="test-key", + ) + + with pytest.raises(ValueError, match="attributes cannot be empty"): + cache.delete_by_attributes({}) + + @pytest.mark.asyncio + async def test_adelete_by_attributes_with_valid_attributes( + self, mock_langcache_client + ): + """Test async deleting entries by attributes with valid attributes.""" + _, mock_client = mock_langcache_client + + mock_response = MagicMock() + mock_response.model_dump.return_value = {"deleted_count": 3} + mock_client.delete_query_async = AsyncMock(return_value=mock_response) + + cache = LangCacheSemanticCache( + name="test", + server_url="https://api.example.com", + cache_id="test-cache", + api_key="test-key", + ) + + result = await cache.adelete_by_attributes({"language": "python"}) + + assert result == {"deleted_count": 3} + mock_client.delete_query_async.assert_called_once_with( + attributes={"language": "python"} + ) + + @pytest.mark.asyncio + async def test_adelete_by_attributes_with_empty_attributes_raises_error( + self, mock_langcache_client + ): + """Test that async delete_by_attributes raises ValueError with empty attributes.""" + cache = LangCacheSemanticCache( + name="test", + server_url="https://api.example.com", + cache_id="test-cache", + api_key="test-key", + ) + + with pytest.raises(ValueError, match="attributes cannot be empty"): + await cache.adelete_by_attributes({}) + def test_update_not_supported(self, mock_langcache_client): """Test that update raises NotImplementedError.""" cache = LangCacheSemanticCache( From 8d2549e8d8033ad2ac6c845bd8d9748d9a2d00ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:06:14 +0000 Subject: [PATCH 3/7] Use flush() API to delete all entries instead of delete_query with empty attributes - Changed delete() to use self._client.flush() instead of raising NotImplementedError - Changed adelete() to use self._client.flush_async() - Updated error messages in delete_by_attributes to suggest using delete()/clear() - Updated all tests to verify flush() is called - This fixes the 400 error by using the proper API endpoint for clearing all entries Co-authored-by: abrookins <97182+abrookins@users.noreply.github.com> --- redisvl/extensions/cache/llm/langcache.py | 28 +++-------- tests/unit/test_langcache_semantic_cache.py | 56 ++++++++++++--------- 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/redisvl/extensions/cache/llm/langcache.py b/redisvl/extensions/cache/llm/langcache.py index 057e79a7..1a62f9bf 100644 --- a/redisvl/extensions/cache/llm/langcache.py +++ b/redisvl/extensions/cache/llm/langcache.py @@ -536,34 +536,16 @@ async def aupdate(self, key: str, **kwargs) -> None: def delete(self) -> None: """Delete the entire cache. - Note: LangCache API does not support deleting all entries without - specific attributes. Use delete_by_attributes() to delete entries - matching specific criteria, or delete_by_id() to delete individual entries. - - Raises: - NotImplementedError: LangCache does not support clearing all entries. + This deletes all entries in the cache by calling the flush API. """ - raise NotImplementedError( - "LangCache API does not support deleting all entries without attributes. " - "Use delete_by_attributes(attributes) to delete entries matching specific " - "criteria, or delete_by_id(entry_id) to delete individual entries." - ) + self._client.flush() async def adelete(self) -> None: """Async delete the entire cache. - Note: LangCache API does not support deleting all entries without - specific attributes. Use adelete_by_attributes() to delete entries - matching specific criteria, or adelete_by_id() to delete individual entries. - - Raises: - NotImplementedError: LangCache does not support clearing all entries. + This deletes all entries in the cache by calling the flush API. """ - raise NotImplementedError( - "LangCache API does not support deleting all entries without attributes. " - "Use adelete_by_attributes(attributes) to delete entries matching specific " - "criteria, or adelete_by_id(entry_id) to delete individual entries." - ) + await self._client.flush_async() def clear(self) -> None: """Clear the cache of all entries. @@ -611,6 +593,7 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]: if not attributes: raise ValueError( "attributes cannot be empty. Provide specific attributes to match, " + "use delete() or clear() to delete all entries, " "or use delete_by_id(entry_id) to delete individual entries." ) result = self._client.delete_query(attributes=attributes) @@ -633,6 +616,7 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A if not attributes: raise ValueError( "attributes cannot be empty. Provide specific attributes to match, " + "use adelete() or aclear() to delete all entries, " "or use adelete_by_id(entry_id) to delete individual entries." ) result = await self._client.delete_query_async(attributes=attributes) diff --git a/tests/unit/test_langcache_semantic_cache.py b/tests/unit/test_langcache_semantic_cache.py index 066af0d5..d5a88a00 100644 --- a/tests/unit/test_langcache_semantic_cache.py +++ b/tests/unit/test_langcache_semantic_cache.py @@ -354,8 +354,10 @@ def test_check_with_empty_attributes_does_not_send_attributes( _, call_kwargs = mock_client.search.call_args assert "attributes" not in call_kwargs - def test_delete_not_supported(self, mock_langcache_client): - """Test that delete() raises NotImplementedError.""" + def test_delete(self, mock_langcache_client): + """Test deleting the entire cache using flush().""" + _, mock_client = mock_langcache_client + cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -363,14 +365,17 @@ def test_delete_not_supported(self, mock_langcache_client): api_key="test-key", ) - with pytest.raises( - NotImplementedError, match="does not support deleting all entries" - ): - cache.delete() + cache.delete() + + mock_client.flush.assert_called_once() @pytest.mark.asyncio - async def test_adelete_not_supported(self, mock_langcache_client): - """Test that async delete() raises NotImplementedError.""" + async def test_adelete(self, mock_langcache_client): + """Test async deleting the entire cache using flush().""" + _, mock_client = mock_langcache_client + + mock_client.flush_async = AsyncMock() + cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -378,13 +383,14 @@ async def test_adelete_not_supported(self, mock_langcache_client): api_key="test-key", ) - with pytest.raises( - NotImplementedError, match="does not support deleting all entries" - ): - await cache.adelete() + await cache.adelete() + + mock_client.flush_async.assert_called_once() + + def test_clear(self, mock_langcache_client): + """Test that clear() calls delete() which uses flush().""" + _, mock_client = mock_langcache_client - def test_clear_not_supported(self, mock_langcache_client): - """Test that clear() raises NotImplementedError.""" cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -392,14 +398,17 @@ def test_clear_not_supported(self, mock_langcache_client): api_key="test-key", ) - with pytest.raises( - NotImplementedError, match="does not support deleting all entries" - ): - cache.clear() + cache.clear() + + mock_client.flush.assert_called_once() @pytest.mark.asyncio - async def test_aclear_not_supported(self, mock_langcache_client): - """Test that async clear() raises NotImplementedError.""" + async def test_aclear(self, mock_langcache_client): + """Test that async clear() calls adelete() which uses flush().""" + _, mock_client = mock_langcache_client + + mock_client.flush_async = AsyncMock() + cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -407,10 +416,9 @@ async def test_aclear_not_supported(self, mock_langcache_client): api_key="test-key", ) - with pytest.raises( - NotImplementedError, match="does not support deleting all entries" - ): - await cache.aclear() + await cache.aclear() + + mock_client.flush_async.assert_called_once() def test_delete_by_id(self, mock_langcache_client): """Test deleting a single entry by ID.""" From 5d02f58937897a7f750b848f4431f757563f3445 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:51:38 +0000 Subject: [PATCH 4/7] Ignore empty attributes in delete_by_attributes instead of raising error - Changed delete_by_attributes() to return {} when attributes is empty - Changed adelete_by_attributes() to return {} when attributes is empty - Updated tests to verify empty attributes are ignored without calling API - This prevents accidentally calling API with empty dict which would cause 400 error Co-authored-by: abrookins <97182+abrookins@users.noreply.github.com> --- redisvl/extensions/cache/llm/langcache.py | 24 +++++-------------- tests/unit/test_langcache_semantic_cache.py | 26 ++++++++++++++------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/redisvl/extensions/cache/llm/langcache.py b/redisvl/extensions/cache/llm/langcache.py index 1a62f9bf..b691e4b0 100644 --- a/redisvl/extensions/cache/llm/langcache.py +++ b/redisvl/extensions/cache/llm/langcache.py @@ -582,20 +582,14 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]: Args: attributes (Dict[str, Any]): Attributes to match for deletion. - Cannot be empty. + If empty, no deletion is performed. Returns: Dict[str, Any]: Result of the deletion operation. - - Raises: - ValueError: If attributes is empty. """ if not attributes: - raise ValueError( - "attributes cannot be empty. Provide specific attributes to match, " - "use delete() or clear() to delete all entries, " - "or use delete_by_id(entry_id) to delete individual entries." - ) + # No attributes provided, return empty result without calling API + return {} result = self._client.delete_query(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} @@ -605,20 +599,14 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A Args: attributes (Dict[str, Any]): Attributes to match for deletion. - Cannot be empty. + If empty, no deletion is performed. Returns: Dict[str, Any]: Result of the deletion operation. - - Raises: - ValueError: If attributes is empty. """ if not attributes: - raise ValueError( - "attributes cannot be empty. Provide specific attributes to match, " - "use adelete() or aclear() to delete all entries, " - "or use adelete_by_id(entry_id) to delete individual entries." - ) + # No attributes provided, return empty result without calling API + return {} result = await self._client.delete_query_async(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} diff --git a/tests/unit/test_langcache_semantic_cache.py b/tests/unit/test_langcache_semantic_cache.py index d5a88a00..1d534e77 100644 --- a/tests/unit/test_langcache_semantic_cache.py +++ b/tests/unit/test_langcache_semantic_cache.py @@ -455,10 +455,12 @@ def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client) assert result == {"deleted_count": 5} mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"}) - def test_delete_by_attributes_with_empty_attributes_raises_error( + def test_delete_by_attributes_with_empty_attributes_returns_empty( self, mock_langcache_client ): - """Test that delete_by_attributes raises ValueError with empty attributes.""" + """Test that delete_by_attributes returns empty dict with empty attributes.""" + _, mock_client = mock_langcache_client + cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -466,8 +468,11 @@ def test_delete_by_attributes_with_empty_attributes_raises_error( api_key="test-key", ) - with pytest.raises(ValueError, match="attributes cannot be empty"): - cache.delete_by_attributes({}) + result = cache.delete_by_attributes({}) + + # Should return empty dict without calling delete_query + assert result == {} + mock_client.delete_query.assert_not_called() @pytest.mark.asyncio async def test_adelete_by_attributes_with_valid_attributes( @@ -495,10 +500,12 @@ async def test_adelete_by_attributes_with_valid_attributes( ) @pytest.mark.asyncio - async def test_adelete_by_attributes_with_empty_attributes_raises_error( + async def test_adelete_by_attributes_with_empty_attributes_returns_empty( self, mock_langcache_client ): - """Test that async delete_by_attributes raises ValueError with empty attributes.""" + """Test that async delete_by_attributes returns empty dict with empty attributes.""" + _, mock_client = mock_langcache_client + cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -506,8 +513,11 @@ async def test_adelete_by_attributes_with_empty_attributes_raises_error( api_key="test-key", ) - with pytest.raises(ValueError, match="attributes cannot be empty"): - await cache.adelete_by_attributes({}) + result = await cache.adelete_by_attributes({}) + + # Should return empty dict without calling delete_query_async + assert result == {} + mock_client.delete_query_async.assert_not_called() def test_update_not_supported(self, mock_langcache_client): """Test that update raises NotImplementedError.""" From cd08209f7664173cb2b54da0ab4ee809ecd6fc8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 01:02:28 +0000 Subject: [PATCH 5/7] Return correct DeleteQueryResponse shape for empty attributes - Changed delete_by_attributes() to return {"deleted_entries_count": 0} instead of {} - Changed adelete_by_attributes() to return {"deleted_entries_count": 0} instead of {} - Updated tests to verify correct response shape - This matches the DeleteQueryResponse structure from LangCache SDK Co-authored-by: abrookins <97182+abrookins@users.noreply.github.com> --- redisvl/extensions/cache/llm/langcache.py | 8 ++++---- tests/unit/test_langcache_semantic_cache.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/redisvl/extensions/cache/llm/langcache.py b/redisvl/extensions/cache/llm/langcache.py index b691e4b0..4406083c 100644 --- a/redisvl/extensions/cache/llm/langcache.py +++ b/redisvl/extensions/cache/llm/langcache.py @@ -588,8 +588,8 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]: Dict[str, Any]: Result of the deletion operation. """ if not attributes: - # No attributes provided, return empty result without calling API - return {} + # No attributes provided, return result with zero deletions + return {"deleted_entries_count": 0} result = self._client.delete_query(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} @@ -605,8 +605,8 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A Dict[str, Any]: Result of the deletion operation. """ if not attributes: - # No attributes provided, return empty result without calling API - return {} + # No attributes provided, return result with zero deletions + return {"deleted_entries_count": 0} result = await self._client.delete_query_async(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} diff --git a/tests/unit/test_langcache_semantic_cache.py b/tests/unit/test_langcache_semantic_cache.py index 1d534e77..e0a270e9 100644 --- a/tests/unit/test_langcache_semantic_cache.py +++ b/tests/unit/test_langcache_semantic_cache.py @@ -458,7 +458,7 @@ def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client) def test_delete_by_attributes_with_empty_attributes_returns_empty( self, mock_langcache_client ): - """Test that delete_by_attributes returns empty dict with empty attributes.""" + """Test that delete_by_attributes returns correct shape with empty attributes.""" _, mock_client = mock_langcache_client cache = LangCacheSemanticCache( @@ -470,8 +470,8 @@ def test_delete_by_attributes_with_empty_attributes_returns_empty( result = cache.delete_by_attributes({}) - # Should return empty dict without calling delete_query - assert result == {} + # Should return dict with correct shape without calling delete_query + assert result == {"deleted_entries_count": 0} mock_client.delete_query.assert_not_called() @pytest.mark.asyncio @@ -503,7 +503,7 @@ async def test_adelete_by_attributes_with_valid_attributes( async def test_adelete_by_attributes_with_empty_attributes_returns_empty( self, mock_langcache_client ): - """Test that async delete_by_attributes returns empty dict with empty attributes.""" + """Test that async delete_by_attributes returns correct shape with empty attributes.""" _, mock_client = mock_langcache_client cache = LangCacheSemanticCache( @@ -515,8 +515,8 @@ async def test_adelete_by_attributes_with_empty_attributes_returns_empty( result = await cache.adelete_by_attributes({}) - # Should return empty dict without calling delete_query_async - assert result == {} + # Should return dict with correct shape without calling delete_query_async + assert result == {"deleted_entries_count": 0} mock_client.delete_query_async.assert_not_called() def test_update_not_supported(self, mock_langcache_client): From cf800f97caa3b82960118421849bd37e7a6ba93e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:32:46 +0000 Subject: [PATCH 6/7] Fix test mocks to use correct SDK key deleted_entries_count - Updated test mocks to use "deleted_entries_count" instead of "deleted_count" - This matches the actual DeleteQueryResponse from LangCache SDK - All tests now consistently use the correct key name Co-authored-by: abrookins <97182+abrookins@users.noreply.github.com> --- tests/unit/test_langcache_semantic_cache.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_langcache_semantic_cache.py b/tests/unit/test_langcache_semantic_cache.py index e0a270e9..8079565f 100644 --- a/tests/unit/test_langcache_semantic_cache.py +++ b/tests/unit/test_langcache_semantic_cache.py @@ -440,7 +440,7 @@ def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client) _, mock_client = mock_langcache_client mock_response = MagicMock() - mock_response.model_dump.return_value = {"deleted_count": 5} + mock_response.model_dump.return_value = {"deleted_entries_count": 5} mock_client.delete_query.return_value = mock_response cache = LangCacheSemanticCache( @@ -452,7 +452,7 @@ def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client) result = cache.delete_by_attributes({"topic": "python"}) - assert result == {"deleted_count": 5} + assert result == {"deleted_entries_count": 5} mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"}) def test_delete_by_attributes_with_empty_attributes_returns_empty( @@ -482,7 +482,7 @@ async def test_adelete_by_attributes_with_valid_attributes( _, mock_client = mock_langcache_client mock_response = MagicMock() - mock_response.model_dump.return_value = {"deleted_count": 3} + mock_response.model_dump.return_value = {"deleted_entries_count": 3} mock_client.delete_query_async = AsyncMock(return_value=mock_response) cache = LangCacheSemanticCache( @@ -494,7 +494,7 @@ async def test_adelete_by_attributes_with_valid_attributes( result = await cache.adelete_by_attributes({"language": "python"}) - assert result == {"deleted_count": 3} + assert result == {"deleted_entries_count": 3} mock_client.delete_query_async.assert_called_once_with( attributes={"language": "python"} ) From 414ad66e6de76643bf60d68074a364546362f028 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:21:13 +0000 Subject: [PATCH 7/7] Raise ValueError for empty attributes in delete_by_attributes - Changed delete_by_attributes() to raise ValueError instead of returning {"deleted_entries_count": 0} - Changed adelete_by_attributes() to raise ValueError instead of returning {"deleted_entries_count": 0} - Updated docstrings to indicate attributes cannot be empty and document the ValueError - Updated tests to verify ValueError is raised with empty attributes - Error message: "Cannot delete by attributes with an empty attributes dictionary." Co-authored-by: abrookins <97182+abrookins@users.noreply.github.com> --- redisvl/extensions/cache/llm/langcache.py | 20 +++++++++---- tests/unit/test_langcache_semantic_cache.py | 32 +++++++++------------ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/redisvl/extensions/cache/llm/langcache.py b/redisvl/extensions/cache/llm/langcache.py index 4406083c..bfefad63 100644 --- a/redisvl/extensions/cache/llm/langcache.py +++ b/redisvl/extensions/cache/llm/langcache.py @@ -582,14 +582,18 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]: Args: attributes (Dict[str, Any]): Attributes to match for deletion. - If empty, no deletion is performed. + Cannot be empty. Returns: Dict[str, Any]: Result of the deletion operation. + + Raises: + ValueError: If attributes is an empty dictionary. """ if not attributes: - # No attributes provided, return result with zero deletions - return {"deleted_entries_count": 0} + raise ValueError( + "Cannot delete by attributes with an empty attributes dictionary." + ) result = self._client.delete_query(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} @@ -599,14 +603,18 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A Args: attributes (Dict[str, Any]): Attributes to match for deletion. - If empty, no deletion is performed. + Cannot be empty. Returns: Dict[str, Any]: Result of the deletion operation. + + Raises: + ValueError: If attributes is an empty dictionary. """ if not attributes: - # No attributes provided, return result with zero deletions - return {"deleted_entries_count": 0} + raise ValueError( + "Cannot delete by attributes with an empty attributes dictionary." + ) result = await self._client.delete_query_async(attributes=attributes) # Convert DeleteQueryResponse to dict return result.model_dump() if hasattr(result, "model_dump") else {} diff --git a/tests/unit/test_langcache_semantic_cache.py b/tests/unit/test_langcache_semantic_cache.py index 8079565f..1307e9d9 100644 --- a/tests/unit/test_langcache_semantic_cache.py +++ b/tests/unit/test_langcache_semantic_cache.py @@ -455,12 +455,10 @@ def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client) assert result == {"deleted_entries_count": 5} mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"}) - def test_delete_by_attributes_with_empty_attributes_returns_empty( + def test_delete_by_attributes_with_empty_attributes_raises_error( self, mock_langcache_client ): - """Test that delete_by_attributes returns correct shape with empty attributes.""" - _, mock_client = mock_langcache_client - + """Test that delete_by_attributes raises ValueError with empty attributes.""" cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -468,11 +466,11 @@ def test_delete_by_attributes_with_empty_attributes_returns_empty( api_key="test-key", ) - result = cache.delete_by_attributes({}) - - # Should return dict with correct shape without calling delete_query - assert result == {"deleted_entries_count": 0} - mock_client.delete_query.assert_not_called() + with pytest.raises( + ValueError, + match="Cannot delete by attributes with an empty attributes dictionary", + ): + cache.delete_by_attributes({}) @pytest.mark.asyncio async def test_adelete_by_attributes_with_valid_attributes( @@ -500,12 +498,10 @@ async def test_adelete_by_attributes_with_valid_attributes( ) @pytest.mark.asyncio - async def test_adelete_by_attributes_with_empty_attributes_returns_empty( + async def test_adelete_by_attributes_with_empty_attributes_raises_error( self, mock_langcache_client ): - """Test that async delete_by_attributes returns correct shape with empty attributes.""" - _, mock_client = mock_langcache_client - + """Test that async delete_by_attributes raises ValueError with empty attributes.""" cache = LangCacheSemanticCache( name="test", server_url="https://api.example.com", @@ -513,11 +509,11 @@ async def test_adelete_by_attributes_with_empty_attributes_returns_empty( api_key="test-key", ) - result = await cache.adelete_by_attributes({}) - - # Should return dict with correct shape without calling delete_query_async - assert result == {"deleted_entries_count": 0} - mock_client.delete_query_async.assert_not_called() + with pytest.raises( + ValueError, + match="Cannot delete by attributes with an empty attributes dictionary", + ): + await cache.adelete_by_attributes({}) def test_update_not_supported(self, mock_langcache_client): """Test that update raises NotImplementedError."""