Skip to content

Commit 60234b4

Browse files
authored
Merge branch 'main' into codex/provider-prefixed-gemini-models
2 parents 55dd83f + 8788d1c commit 60234b4

4 files changed

Lines changed: 105 additions & 37 deletions

File tree

pyproject.toml

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,51 +33,51 @@ classifiers = [
3333
dynamic = [ "version" ]
3434

3535
dependencies = [
36-
"aiosqlite>=0.21", # For SQLite database
37-
"anyio>=4.9,<5", # For MCP Session Manager
38-
"authlib>=1.6.6,<2", # For RestAPI Tool
39-
"click>=8.1.8,<9", # For CLI tools
40-
"fastapi>=0.124.1,<1", # FastAPI framework
41-
"google-api-python-client>=2.157,<3", # Google API client discovery
42-
"google-auth[pyopenssl]>=2.47", # Google Auth library
43-
"google-cloud-aiplatform[agent-engines]>=1.148.1,<2", # For VertexAI integrations, e.g. example store.
36+
"aiosqlite>=0.21", # For SQLite database
37+
"anyio>=4.9,<5", # For MCP Session Manager
38+
"authlib>=1.6.6,<2", # For RestAPI Tool
39+
"click>=8.1.8,<9", # For CLI tools
40+
"fastapi>=0.124.1,<1", # FastAPI framework
41+
"google-api-python-client>=2.157,<3", # Google API client discovery
42+
"google-auth[pyopenssl]>=2.47", # Google Auth library
43+
"google-cloud-aiplatform[agent-engines]>=1.148.1,<2", # For VertexAI integrations, e.g. example store.
4444
"google-cloud-bigquery>=2.2",
4545
"google-cloud-bigquery-storage>=2",
46-
"google-cloud-bigtable>=2.32", # For Bigtable database
47-
"google-cloud-dataplex>=1.7,<3", # For Dataplex Catalog Search tool
48-
"google-cloud-discoveryengine>=0.13.12,<0.14", # For Discovery Engine Search Tool
49-
"google-cloud-pubsub>=2,<3", # For Pub/Sub Tool
50-
"google-cloud-secret-manager>=2.22,<3", # Fetching secrets in RestAPI Tool
51-
"google-cloud-spanner>=3.56,<4", # For Spanner database
52-
"google-cloud-speech>=2.30,<3", # For Audio Transcription
53-
"google-cloud-storage>=2.18,<4", # For GCS Artifact service
54-
"google-genai>=1.72,<2", # Google GenAI SDK
55-
"graphviz>=0.20.2,<1", # Graphviz for graph rendering
56-
"httpx>=0.27,<1", # HTTP client library
57-
"jsonschema>=4.23,<5", # Agent Builder config validation
58-
"mcp>=1.24,<2", # For MCP Toolset
59-
"opentelemetry-api>=1.36,<1.39", # OpenTelemetry - keep below 1.39.0 due to current agent_engines exporter constraints.
60-
"opentelemetry-exporter-gcp-logging>=1.9.0a0,<2",
46+
"google-cloud-bigtable>=2.32", # For Bigtable database
47+
"google-cloud-dataplex>=1.7,<3", # For Dataplex Catalog Search tool
48+
"google-cloud-discoveryengine>=0.13.12,<0.14", # For Discovery Engine Search Tool
49+
"google-cloud-pubsub>=2,<3", # For Pub/Sub Tool
50+
"google-cloud-secret-manager>=2.22,<3", # Fetching secrets in RestAPI Tool
51+
"google-cloud-spanner>=3.56,<4", # For Spanner database
52+
"google-cloud-speech>=2.30,<3", # For Audio Transcription
53+
"google-cloud-storage>=2.18,<4", # For GCS Artifact service
54+
"google-genai>=1.72,<2", # Google GenAI SDK
55+
"graphviz>=0.20.2,<1", # Graphviz for graph rendering
56+
"httpx>=0.27,<1", # HTTP client library
57+
"jsonschema>=4.23,<5", # Agent Builder config validation
58+
"mcp>=1.24,<2", # For MCP Toolset
59+
"opentelemetry-api>=1.36,<=1.41.1", # OpenTelemetry - allow 1.39+ up to latest published at time of resolution.
60+
"opentelemetry-exporter-gcp-logging>=1.9.0a0,<=1.12.0a0",
6161
"opentelemetry-exporter-gcp-monitoring>=1.9.0a0,<2",
6262
"opentelemetry-exporter-gcp-trace>=1.9,<2",
6363
"opentelemetry-exporter-otlp-proto-http>=1.36",
6464
"opentelemetry-resourcedetector-gcp>=1.9.0a0,<2",
65-
"opentelemetry-sdk>=1.36,<1.39",
65+
"opentelemetry-sdk>=1.36,<=1.41.1",
6666
"pyarrow>=14",
67-
"pydantic>=2.12,<3", # For data validation/models
68-
"python-dateutil>=2.9.0.post0,<3", # For Vertext AI Session Service
69-
"python-dotenv>=1,<2", # To manage environment variables
70-
"pyyaml>=6.0.2,<7", # For APIHubToolset.
67+
"pydantic>=2.12,<3", # For data validation/models
68+
"python-dateutil>=2.9.0.post0,<3", # For Vertext AI Session Service
69+
"python-dotenv>=1,<2", # To manage environment variables
70+
"pyyaml>=6.0.2,<7", # For APIHubToolset.
7171
"requests>=2.32.4,<3",
72-
"sqlalchemy>=2,<3", # SQL database ORM
73-
"sqlalchemy-spanner>=1.14", # Spanner database session service
74-
"starlette>=0.49.1,<1", # For FastAPI CLI
75-
"tenacity>=9,<10", # For Retry management
72+
"sqlalchemy>=2,<3", # SQL database ORM
73+
"sqlalchemy-spanner>=1.14", # Spanner database session service
74+
"starlette>=0.49.1,<1", # For FastAPI CLI
75+
"tenacity>=9,<10", # For Retry management
7676
"typing-extensions>=4.5,<5",
77-
"tzlocal>=5.3,<6", # Time zone utilities
78-
"uvicorn>=0.34,<1", # ASGI server for FastAPI
79-
"watchdog>=6,<7", # For file change detection and hot reload
80-
"websockets>=15.0.1,<16", # For BaseLlmFlow
77+
"tzlocal>=5.3,<6", # Time zone utilities
78+
"uvicorn>=0.34,<1", # ASGI server for FastAPI
79+
"watchdog>=6,<7", # For file change detection and hot reload
80+
"websockets>=15.0.1,<16", # For BaseLlmFlow
8181
]
8282
optional-dependencies.a2a = [
8383
"a2a-sdk>=0.3.4,<0.4",

src/google/adk/evaluation/agent_evaluator.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class _EvalMetricResultWithInvocation(BaseModel):
9090
"""
9191

9292
actual_invocation: Invocation
93-
expected_invocation: Invocation
93+
expected_invocation: Invocation | None = None
9494
eval_metric_result: EvalMetricResult
9595

9696

@@ -438,15 +438,21 @@ def _print_details(
438438
"threshold": threshold,
439439
"prompt": AgentEvaluator._convert_content_to_text(
440440
per_invocation_result.expected_invocation.user_content
441+
if per_invocation_result.expected_invocation
442+
else per_invocation_result.actual_invocation.user_content
441443
),
442444
"expected_response": AgentEvaluator._convert_content_to_text(
443445
per_invocation_result.expected_invocation.final_response
446+
if per_invocation_result.expected_invocation
447+
else None
444448
),
445449
"actual_response": AgentEvaluator._convert_content_to_text(
446450
per_invocation_result.actual_invocation.final_response
447451
),
448452
"expected_tool_calls": AgentEvaluator._convert_tool_calls_to_text(
449453
per_invocation_result.expected_invocation.intermediate_data
454+
if per_invocation_result.expected_invocation
455+
else None
450456
),
451457
"actual_tool_calls": AgentEvaluator._convert_tool_calls_to_text(
452458
per_invocation_result.actual_invocation.intermediate_data

src/google/adk/models/gemini_llm_connection.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,25 @@ async def receive(self) -> AsyncGenerator[LlmResponse, None]:
366366
types.Part(function_call=function_call)
367367
for function_call in message.tool_call.function_calls
368368
])
369+
# Gemini 3.1 does not emit turn_complete until it receives the
370+
# tool response, so yield tool calls immediately to avoid
371+
# deadlocking the conversation. Other models (e.g. 2.5-pro,
372+
# native-audio) send turn_complete after tool calls, so buffer
373+
# and merge them into a single response at turn_complete.
374+
if (
375+
model_name_utils.is_gemini_3_1_flash_live(self._model_version)
376+
and tool_call_parts
377+
):
378+
logger.debug(
379+
'Yielding tool_call_parts immediately for Gemini 3.1 live tool'
380+
' call'
381+
)
382+
yield LlmResponse(
383+
content=types.Content(role='model', parts=tool_call_parts),
384+
model_version=self._model_version,
385+
live_session_id=live_session_id,
386+
)
387+
tool_call_parts = []
369388
if message.session_resumption_update:
370389
logger.debug('Received session resumption message: %s', message)
371390
yield (

tests/unittests/models/test_gemini_llm_connection.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,49 @@ async def mock_receive_generator():
11361136
assert responses[1].turn_complete is True
11371137

11381138

1139+
@pytest.mark.asyncio
1140+
async def test_receive_tool_calls_yielded_immediately_for_gemini_3_1(
1141+
mock_gemini_session,
1142+
):
1143+
"""Test that tool calls are yielded immediately for Gemini 3.1."""
1144+
connection = GeminiLlmConnection(
1145+
mock_gemini_session,
1146+
api_backend=GoogleLLMVariant.VERTEX_AI,
1147+
model_version='gemini-3.1-flash-live-preview',
1148+
)
1149+
1150+
mock_tool_call_msg = mock.create_autospec(
1151+
types.LiveServerMessage, instance=True
1152+
)
1153+
mock_tool_call_msg.usage_metadata = None
1154+
mock_tool_call_msg.server_content = None
1155+
mock_tool_call_msg.session_resumption_update = None
1156+
mock_tool_call_msg.go_away = None
1157+
1158+
function_call = types.FunctionCall(
1159+
name='test_tool',
1160+
args={'arg': 'value'},
1161+
)
1162+
mock_tool_call = mock.create_autospec(types.LiveServerToolCall, instance=True)
1163+
mock_tool_call.function_calls = [function_call]
1164+
mock_tool_call_msg.tool_call = mock_tool_call
1165+
1166+
async def mock_receive_generator():
1167+
yield mock_tool_call_msg
1168+
1169+
receive_mock = mock.Mock(return_value=mock_receive_generator())
1170+
mock_gemini_session.receive = receive_mock
1171+
1172+
responses = []
1173+
async for resp in connection.receive():
1174+
responses.append(resp)
1175+
break
1176+
1177+
assert len(responses) == 1
1178+
assert responses[0].content is not None
1179+
assert responses[0].content.parts[0].function_call.name == 'test_tool'
1180+
1181+
11391182
@pytest.mark.asyncio
11401183
async def test_receive_go_away(gemini_connection, mock_gemini_session):
11411184
"""Test receive yields go_away message."""

0 commit comments

Comments
 (0)