|
3 | 3 | import datetime |
4 | 4 | import os |
5 | 5 | import re |
| 6 | +from collections.abc import AsyncIterator |
6 | 7 | from typing import Any |
7 | 8 |
|
8 | 9 | import pytest |
|
47 | 48 | BuiltinToolCallEvent, # pyright: ignore[reportDeprecated] |
48 | 49 | BuiltinToolResultEvent, # pyright: ignore[reportDeprecated] |
49 | 50 | ) |
| 51 | +from pydantic_ai.models import ModelRequestParameters |
50 | 52 | from pydantic_ai.output import NativeOutput, PromptedOutput, TextOutput, ToolOutput |
51 | 53 | from pydantic_ai.settings import ModelSettings |
52 | 54 | from pydantic_ai.usage import RequestUsage, RunUsage, UsageLimits |
|
56 | 58 |
|
57 | 59 | with try_import() as imports_successful: |
58 | 60 | from google.genai.types import ( |
| 61 | + FinishReason as GoogleFinishReason, |
59 | 62 | GenerateContentResponse, |
60 | 63 | GenerateContentResponseUsageMetadata, |
61 | 64 | HarmBlockThreshold, |
|
64 | 67 | ModalityTokenCount, |
65 | 68 | ) |
66 | 69 |
|
67 | | - from pydantic_ai.models.google import GoogleModel, GoogleModelSettings, _metadata_as_usage # type: ignore |
| 70 | + from pydantic_ai.models.google import ( |
| 71 | + GeminiStreamedResponse, |
| 72 | + GoogleModel, |
| 73 | + GoogleModelSettings, |
| 74 | + _metadata_as_usage, # pyright: ignore[reportPrivateUsage] |
| 75 | + ) |
68 | 76 | from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings |
69 | 77 | from pydantic_ai.providers.google import GoogleProvider |
70 | 78 | from pydantic_ai.providers.openai import OpenAIProvider |
@@ -3063,3 +3071,52 @@ async def test_google_httpx_client_is_not_closed(allow_model_requests: None, gem |
3063 | 3071 | agent = Agent(GoogleModel('gemini-2.5-flash-lite', provider=GoogleProvider(api_key=gemini_api_key))) |
3064 | 3072 | result = await agent.run('What is the capital of Mexico?') |
3065 | 3073 | assert result.output == snapshot('The capital of Mexico is **Mexico City**.') |
| 3074 | + |
| 3075 | + |
| 3076 | +def test_google_process_response_filters_empty_text_parts(google_provider: GoogleProvider): |
| 3077 | + model = GoogleModel('gemini-2.5-pro', provider=google_provider) |
| 3078 | + response = _generate_response_with_texts(response_id='resp-123', texts=['', 'first', '', 'second']) |
| 3079 | + |
| 3080 | + result = model._process_response(response) # pyright: ignore[reportPrivateUsage] |
| 3081 | + |
| 3082 | + assert result.parts == snapshot([TextPart(content='first'), TextPart(content='second')]) |
| 3083 | + |
| 3084 | + |
| 3085 | +async def test_gemini_streamed_response_emits_text_events_for_non_empty_parts(): |
| 3086 | + chunk = _generate_response_with_texts('stream-1', ['', 'streamed text']) |
| 3087 | + |
| 3088 | + async def response_iterator() -> AsyncIterator[GenerateContentResponse]: |
| 3089 | + yield chunk |
| 3090 | + |
| 3091 | + streamed_response = GeminiStreamedResponse( |
| 3092 | + model_request_parameters=ModelRequestParameters(), |
| 3093 | + _model_name='gemini-test', |
| 3094 | + _response=response_iterator(), |
| 3095 | + _timestamp=datetime.datetime.now(datetime.timezone.utc), |
| 3096 | + _provider_name='test-provider', |
| 3097 | + ) |
| 3098 | + |
| 3099 | + events = [event async for event in streamed_response._get_event_iterator()] # pyright: ignore[reportPrivateUsage] |
| 3100 | + assert events == snapshot([PartStartEvent(index=0, part=TextPart(content='streamed text'))]) |
| 3101 | + |
| 3102 | + |
| 3103 | +def _generate_response_with_texts(response_id: str, texts: list[str]) -> GenerateContentResponse: |
| 3104 | + return GenerateContentResponse.model_validate( |
| 3105 | + { |
| 3106 | + 'response_id': response_id, |
| 3107 | + 'model_version': 'gemini-test', |
| 3108 | + 'usage_metadata': GenerateContentResponseUsageMetadata( |
| 3109 | + prompt_token_count=0, |
| 3110 | + candidates_token_count=0, |
| 3111 | + ), |
| 3112 | + 'candidates': [ |
| 3113 | + { |
| 3114 | + 'finish_reason': GoogleFinishReason.STOP, |
| 3115 | + 'content': { |
| 3116 | + 'role': 'model', |
| 3117 | + 'parts': [{'text': text} for text in texts], |
| 3118 | + }, |
| 3119 | + } |
| 3120 | + ], |
| 3121 | + } |
| 3122 | + ) |
0 commit comments