@@ -349,3 +349,120 @@ async def mock_create(*args, **kwargs):
349349 assert content [0 ]["text" ] == "Analyze this"
350350 assert content [1 ]["type" ] == "image_url"
351351 assert content [1 ]["image_url" ]["url" ].startswith ("data:image/png;base64," )
352+
353+
354+ def _completion_with_cached_tokens (cached_tokens ):
355+ """Builds a mock ChatCompletion whose usage carries prompt_tokens_details."""
356+ mock_response = mock .MagicMock ()
357+ mock_choice = mock .MagicMock ()
358+ mock_message = mock .MagicMock ()
359+ mock_message .content = "Hello there!"
360+ mock_message .tool_calls = None
361+ mock_choice .message = mock_message
362+ mock_response .choices = [mock_choice ]
363+ mock_response .usage .prompt_tokens = 100
364+ mock_response .usage .completion_tokens = 5
365+ mock_response .usage .total_tokens = 105
366+ if cached_tokens is None :
367+ mock_response .usage .prompt_tokens_details = None
368+ else :
369+ mock_response .usage .prompt_tokens_details .cached_tokens = cached_tokens
370+ return mock_response
371+
372+
373+ @pytest .mark .asyncio
374+ async def test_generate_content_async_reports_cached_tokens ():
375+ """prompt_tokens_details.cached_tokens populates cached_content_token_count."""
376+ with mock .patch .dict (os .environ , {"OPENAI_API_KEY" : "test_key" }):
377+ openai_llm = OpenAILlm (model = "gpt-4o" )
378+ llm_request = LlmRequest (
379+ model = "gpt-4o" ,
380+ contents = [Content (role = "user" , parts = [Part .from_text (text = "Hello" )])],
381+ )
382+
383+ mock_response = _completion_with_cached_tokens (64 )
384+
385+ async def mock_create (* args , ** kwargs ):
386+ return mock_response
387+
388+ with mock .patch (
389+ "google.adk.labs.openai._openai_llm.AsyncOpenAI"
390+ ) as mock_client_class :
391+ mock_client = mock .MagicMock ()
392+ mock_client_class .return_value = mock_client
393+ mock_client .chat .completions .create = mock_create
394+
395+ responses = [
396+ resp
397+ async for resp in openai_llm .generate_content_async (
398+ llm_request , stream = False
399+ )
400+ ]
401+
402+ assert len (responses ) == 1
403+ assert responses [0 ].usage_metadata .cached_content_token_count == 64
404+ assert responses [0 ].usage_metadata .prompt_token_count == 100
405+
406+
407+ @pytest .mark .asyncio
408+ async def test_generate_content_async_zero_cached_tokens ():
409+ """No cache hit (cached_tokens=0) reports 0, not a regression."""
410+ with mock .patch .dict (os .environ , {"OPENAI_API_KEY" : "test_key" }):
411+ openai_llm = OpenAILlm (model = "gpt-4o" )
412+ llm_request = LlmRequest (
413+ model = "gpt-4o" ,
414+ contents = [Content (role = "user" , parts = [Part .from_text (text = "Hello" )])],
415+ )
416+
417+ mock_response = _completion_with_cached_tokens (0 )
418+
419+ async def mock_create (* args , ** kwargs ):
420+ return mock_response
421+
422+ with mock .patch (
423+ "google.adk.labs.openai._openai_llm.AsyncOpenAI"
424+ ) as mock_client_class :
425+ mock_client = mock .MagicMock ()
426+ mock_client_class .return_value = mock_client
427+ mock_client .chat .completions .create = mock_create
428+
429+ responses = [
430+ resp
431+ async for resp in openai_llm .generate_content_async (
432+ llm_request , stream = False
433+ )
434+ ]
435+
436+ assert responses [0 ].usage_metadata .cached_content_token_count == 0
437+
438+
439+ @pytest .mark .asyncio
440+ async def test_generate_content_async_absent_prompt_tokens_details ():
441+ """Missing prompt_tokens_details maps to None (no cached count reported)."""
442+ with mock .patch .dict (os .environ , {"OPENAI_API_KEY" : "test_key" }):
443+ openai_llm = OpenAILlm (model = "gpt-4o" )
444+ llm_request = LlmRequest (
445+ model = "gpt-4o" ,
446+ contents = [Content (role = "user" , parts = [Part .from_text (text = "Hello" )])],
447+ )
448+
449+ mock_response = _completion_with_cached_tokens (None )
450+
451+ async def mock_create (* args , ** kwargs ):
452+ return mock_response
453+
454+ with mock .patch (
455+ "google.adk.labs.openai._openai_llm.AsyncOpenAI"
456+ ) as mock_client_class :
457+ mock_client = mock .MagicMock ()
458+ mock_client_class .return_value = mock_client
459+ mock_client .chat .completions .create = mock_create
460+
461+ responses = [
462+ resp
463+ async for resp in openai_llm .generate_content_async (
464+ llm_request , stream = False
465+ )
466+ ]
467+
468+ assert responses [0 ].usage_metadata .cached_content_token_count is None
0 commit comments