@@ -34,6 +34,7 @@ class TestGeminiContextCacheManager:
3434 def setup_method (self ):
3535 """Set up test fixtures."""
3636 mock_client = AsyncMock (spec = Client )
37+ mock_client .vertexai = False
3738 self .manager = GeminiContextCacheManager (mock_client )
3839 self .cache_config = ContextCacheConfig (
3940 cache_intervals = 10 ,
@@ -202,6 +203,68 @@ async def test_handle_context_caching_invalid_cache_fingerprint_match(self):
202203 mock_cleanup .assert_called_once_with (existing_cache .cache_name )
203204 self .manager .genai_client .aio .caches .create .assert_called_once ()
204205
206+ async def test_model_change_invalidates_active_cache (self ):
207+ """A cache created for one model is not reused by another model."""
208+ flash_request = self .create_llm_request (contents_count = 0 )
209+ flash_metadata = await self .manager .handle_context_caching (flash_request )
210+ assert flash_metadata is not None
211+ active_metadata = CacheMetadata (
212+ cache_name = "cachedContents/flash-cache" ,
213+ expire_time = time .time () + 1_800 ,
214+ fingerprint = flash_metadata .fingerprint ,
215+ invocations_used = 1 ,
216+ contents_count = flash_metadata .contents_count ,
217+ created_at = time .time (),
218+ )
219+ pro_request = self .create_llm_request (
220+ cache_metadata = active_metadata , contents_count = 0
221+ )
222+ pro_request .model = "gemini-2.5-pro"
223+ self .manager .genai_client .aio .caches .delete = AsyncMock ()
224+
225+ pro_metadata = await self .manager .handle_context_caching (pro_request )
226+
227+ assert pro_metadata is not None
228+ assert pro_metadata .cache_name is None
229+ assert pro_metadata .fingerprint != active_metadata .fingerprint
230+ self .manager .genai_client .aio .caches .delete .assert_awaited_once_with (
231+ name = "cachedContents/flash-cache"
232+ )
233+
234+ async def test_backend_change_invalidates_active_cache (self ):
235+ """A Developer API cache is not reused by a Vertex client."""
236+ developer_request = self .create_llm_request (contents_count = 0 )
237+ developer_metadata = await self .manager .handle_context_caching (
238+ developer_request
239+ )
240+ assert developer_metadata is not None
241+ active_metadata = CacheMetadata (
242+ cache_name = "cachedContents/developer-cache" ,
243+ expire_time = time .time () + 1_800 ,
244+ fingerprint = developer_metadata .fingerprint ,
245+ invocations_used = 1 ,
246+ contents_count = developer_metadata .contents_count ,
247+ created_at = time .time (),
248+ )
249+ vertex_client = AsyncMock (spec = Client )
250+ vertex_client .vertexai = True
251+ vertex_client .aio .caches .delete = AsyncMock ()
252+ vertex_manager = GeminiContextCacheManager (vertex_client )
253+ vertex_request = self .create_llm_request (
254+ cache_metadata = active_metadata , contents_count = 0
255+ )
256+
257+ vertex_metadata = await vertex_manager .handle_context_caching (
258+ vertex_request
259+ )
260+
261+ assert vertex_metadata is not None
262+ assert vertex_metadata .cache_name is None
263+ assert vertex_metadata .fingerprint != active_metadata .fingerprint
264+ vertex_client .aio .caches .delete .assert_awaited_once_with (
265+ name = "cachedContents/developer-cache"
266+ )
267+
205268 async def test_create_cache_gates_on_prefix_not_full_prompt (self ):
206269 """Cache creation is gated on the cacheable prefix, not the full prompt.
207270
@@ -421,6 +484,38 @@ def test_generate_cache_fingerprint_different_requests(self):
421484
422485 assert fingerprint1 != fingerprint2
423486
487+ def test_generate_cache_fingerprint_canonicalizes_mapping_order (self ):
488+ """Equivalent argument mappings do not cause an avoidable cache miss."""
489+ first_request = self .create_llm_request (contents_count = 0 )
490+ second_request = self .create_llm_request (contents_count = 0 )
491+ first_request .contents = [
492+ types .ModelContent (
493+ types .Part (
494+ function_call = types .FunctionCall (
495+ name = "lookup" , args = {"first" : 1 , "second" : 2 }
496+ )
497+ )
498+ )
499+ ]
500+ second_request .contents = [
501+ types .ModelContent (
502+ types .Part (
503+ function_call = types .FunctionCall (
504+ name = "lookup" , args = {"second" : 2 , "first" : 1 }
505+ )
506+ )
507+ )
508+ ]
509+
510+ first_fingerprint = self .manager ._generate_cache_fingerprint (
511+ first_request , 1
512+ )
513+ second_fingerprint = self .manager ._generate_cache_fingerprint (
514+ second_request , 1
515+ )
516+
517+ assert first_fingerprint == second_fingerprint
518+
424519 def test_generate_cache_fingerprint_tool_config_variations (self ):
425520 """Test that different tool configs generate different fingerprints."""
426521 # Request with AUTO mode
0 commit comments