Skip to content

Commit e3c9c81

Browse files
authored
Merge branch 'main' into feat/expose-mcps-streamable-http-custom-httpx-factory-parameter
2 parents 59e93cf + b2c45f8 commit e3c9c81

25 files changed

Lines changed: 1507 additions & 214 deletions

AGENTS.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ app = App(
119119
### Requirements
120120

121121
**Minimum requirements:**
122-
- Python 3.9+ (**Python 3.11+ strongly recommended** for best performance)
122+
123+
- Python 3.10+ (**Python 3.11+ strongly recommended** for best performance)
123124
- `uv` package manager (**required** - faster than pip/venv)
124125

125126
**Install uv if not already installed:**
@@ -326,11 +327,6 @@ pytest tests/unittests -n auto
326327
# Run a specific test file during development
327328
pytest tests/unittests/agents/test_llm_agent.py
328329

329-
# Python 3.9 compatibility mode (excludes features requiring 3.10+)
330-
pytest tests/unittests \
331-
--ignore=tests/unittests/a2a \
332-
--ignore=tests/unittests/tools/mcp_tool \
333-
--ignore=tests/unittests/artifacts/test_artifact_service.py
334330
```
335331

336332
### Testing Philosophy

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ part before or alongside your code PR.
145145

146146
3. **Create and activate a virtual environment:**
147147

148-
**NOTE**: ADK supports Python 3.9+. Python 3.11 and above is strongly
148+
**NOTE**: ADK supports Python 3.10+. Python 3.11 and above is strongly
149149
recommended.
150150

151151
Create a workspace venv using uv.

pyproject.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
name = "google-adk"
66
description = "Agent Development Kit"
77
readme = "README.md"
8-
requires-python = ">=3.9"
8+
requires-python = ">=3.10"
99
license = { file = "LICENSE" }
1010
authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
1111
classifiers = [ # List of https://pypi.org/classifiers/
@@ -27,7 +27,7 @@ dependencies = [
2727
# go/keep-sorted start
2828
"PyYAML>=6.0.2, <7.0.0", # For APIHubToolset.
2929
"aiosqlite>=0.21.0", # For SQLite database
30-
"anyio>=4.9.0, <5.0.0;python_version>='3.10'", # For MCP Session Manager
30+
"anyio>=4.9.0, <5.0.0", # For MCP Session Manager
3131
"authlib>=1.5.1, <2.0.0", # For RestAPI Tool
3232
"click>=8.1.8, <9.0.0", # For CLI tools
3333
"fastapi>=0.115.0, <0.119.0", # FastAPI framework
@@ -44,7 +44,7 @@ dependencies = [
4444
"google-genai>=1.45.0, <2.0.0", # Google GenAI SDK
4545
"graphviz>=0.20.2, <1.0.0", # Graphviz for graph rendering
4646
"jsonschema>=4.23.0, <5.0.0", # Agent Builder config validation
47-
"mcp>=1.8.0, <2.0.0;python_version>='3.10'", # For MCP Toolset
47+
"mcp>=1.8.0, <2.0.0", # For MCP Toolset
4848
"opentelemetry-api>=1.37.0, <=1.37.0", # OpenTelemetry - limit upper version for sdk and api to not risk breaking changes from unstable _logs package.
4949
"opentelemetry-exporter-gcp-logging>=1.9.0a0, <2.0.0",
5050
"opentelemetry-exporter-gcp-monitoring>=1.9.0a0, <2.0.0",
@@ -93,7 +93,7 @@ dev = [
9393

9494
a2a = [
9595
# go/keep-sorted start
96-
"a2a-sdk>=0.3.11,<0.4.0;python_version>='3.10'",
96+
"a2a-sdk>=0.3.4,<0.4.0",
9797
# go/keep-sorted end
9898
]
9999

@@ -114,9 +114,9 @@ eval = [
114114

115115
test = [
116116
# go/keep-sorted start
117-
"a2a-sdk>=0.3.0,<0.4.0;python_version>='3.10'",
117+
"a2a-sdk>=0.3.0,<0.4.0",
118118
"anthropic>=0.43.0", # For anthropic model tests
119-
"crewai[tools];python_version>='3.10' and python_version<'3.14'", # For CrewaiTool tests
119+
"crewai[tools];python_version>='3.10' and python_version<'3.12'", # For CrewaiTool tests; chromadb/pypika fail on 3.12+
120120
"kubernetes>=29.0.0", # For GkeCodeExecutor
121121
"langchain-community>=0.3.17",
122122
"langgraph>=0.2.60, <0.4.8", # For LangGraphAgent
@@ -126,7 +126,7 @@ test = [
126126
"pytest-asyncio>=0.25.0",
127127
"pytest-mock>=3.14.0",
128128
"pytest-xdist>=3.6.1",
129-
"pytest>=8.3.4",
129+
"pytest>=9.0.0,<10.0.0",
130130
"python-multipart>=0.0.9",
131131
"rouge-score>=0.1.2",
132132
"tabulate>=0.9.0",
@@ -146,7 +146,7 @@ docs = [
146146
extensions = [
147147
"anthropic>=0.43.0", # For anthropic model support
148148
"beautifulsoup4>=3.2.2", # For load_web_page tool.
149-
"crewai[tools];python_version>='3.10' and python_version<'3.14'", # For CrewaiTool
149+
"crewai[tools];python_version>='3.10' and python_version<'3.12'", # For CrewaiTool; chromadb/pypika fail on 3.12+
150150
"docker>=7.0.0", # For ContainerCodeExecutor
151151
"kubernetes>=29.0.0", # For GkeCodeExecutor
152152
"langgraph>=0.2.60, <0.4.8", # For LangGraphAgent
@@ -208,7 +208,7 @@ asyncio_mode = "auto"
208208

209209

210210
[tool.mypy]
211-
python_version = "3.9"
211+
python_version = "3.10"
212212
exclude = "tests/"
213213
plugins = ["pydantic.mypy"]
214214
# Start with non-strict mode, and swtich to strict mode later.

src/google/adk/features/_feature_registry.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
class FeatureName(str, Enum):
2525
"""Feature names."""
2626

27-
JSON_SCHEMA_FOR_FUNC_DECL = "JSON_SCHEMA_FOR_FUNC_DECL"
2827
COMPUTER_USE = "COMPUTER_USE"
28+
JSON_SCHEMA_FOR_FUNC_DECL = "JSON_SCHEMA_FOR_FUNC_DECL"
29+
PROGRESSIVE_SSE_STREAMING = "PROGRESSIVE_SSE_STREAMING"
2930

3031

3132
class FeatureStage(Enum):
@@ -58,11 +59,14 @@ class FeatureConfig:
5859

5960
# Central registry: FeatureName -> FeatureConfig
6061
_FEATURE_REGISTRY: dict[FeatureName, FeatureConfig] = {
62+
FeatureName.COMPUTER_USE: FeatureConfig(
63+
FeatureStage.EXPERIMENTAL, default_on=True
64+
),
6165
FeatureName.JSON_SCHEMA_FOR_FUNC_DECL: FeatureConfig(
6266
FeatureStage.WIP, default_on=False
6367
),
64-
FeatureName.COMPUTER_USE: FeatureConfig(
65-
FeatureStage.EXPERIMENTAL, default_on=True
68+
FeatureName.PROGRESSIVE_SSE_STREAMING: FeatureConfig(
69+
FeatureStage.WIP, default_on=False
6670
),
6771
}
6872

src/google/adk/flows/llm_flows/base_llm_flow.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
from ...agents.run_config import StreamingMode
3939
from ...agents.transcription_entry import TranscriptionEntry
4040
from ...events.event import Event
41+
from ...features import FeatureName
42+
from ...features import is_feature_enabled
4143
from ...models.base_llm_connection import BaseLlmConnection
4244
from ...models.llm_request import LlmRequest
4345
from ...models.llm_response import LlmResponse
@@ -525,6 +527,16 @@ async def _postprocess_async(
525527

526528
# Handles function calls.
527529
if model_response_event.get_function_calls():
530+
531+
if is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING):
532+
# In progressive SSE streaming mode stage 1, we skip partial FC events
533+
# Only execute FCs in the final aggregated event (partial=False)
534+
if (
535+
invocation_context.run_config.streaming_mode == StreamingMode.SSE
536+
and model_response_event.partial
537+
):
538+
return
539+
528540
async with Aclosing(
529541
self._postprocess_handle_function_calls_async(
530542
invocation_context, model_response_event, llm_request

src/google/adk/models/anthropic_llm.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import os
2323
from typing import Any
2424
from typing import AsyncGenerator
25-
from typing import Generator
2625
from typing import Iterable
2726
from typing import Literal
2827
from typing import Optional
@@ -98,14 +97,29 @@ def part_to_message_block(
9897
)
9998
elif part.function_response:
10099
content = ""
101-
if (
102-
"result" in part.function_response.response
103-
and part.function_response.response["result"]
104-
):
100+
response_data = part.function_response.response
101+
102+
# Handle response with content array
103+
if "content" in response_data and response_data["content"]:
104+
content_items = []
105+
for item in response_data["content"]:
106+
if isinstance(item, dict):
107+
# Handle text content blocks
108+
if item.get("type") == "text" and "text" in item:
109+
content_items.append(item["text"])
110+
else:
111+
# Handle other structured content
112+
content_items.append(str(item))
113+
else:
114+
content_items.append(str(item))
115+
content = "\n".join(content_items) if content_items else ""
116+
# Handle traditional result format
117+
elif "result" in response_data and response_data["result"]:
105118
# Transformation is required because the content is a list of dict.
106119
# ToolResultBlockParam content doesn't support list of dict. Converting
107120
# to str to prevent anthropic.BadRequestError from being thrown.
108-
content = str(part.function_response.response["result"])
121+
content = str(response_data["result"])
122+
109123
return anthropic_types.ToolResultBlockParam(
110124
tool_use_id=part.function_response.id or "",
111125
type="tool_result",
@@ -219,23 +233,27 @@ def function_declaration_to_tool_param(
219233
"""Converts a function declaration to an Anthropic tool param."""
220234
assert function_declaration.name
221235

222-
properties = {}
223-
required_params = []
224-
if function_declaration.parameters:
225-
if function_declaration.parameters.properties:
226-
for key, value in function_declaration.parameters.properties.items():
227-
value_dict = value.model_dump(exclude_none=True)
228-
_update_type_string(value_dict)
229-
properties[key] = value_dict
230-
if function_declaration.parameters.required:
231-
required_params = function_declaration.parameters.required
232-
233-
input_schema = {
234-
"type": "object",
235-
"properties": properties,
236-
}
237-
if required_params:
238-
input_schema["required"] = required_params
236+
# Use parameters_json_schema if available, otherwise convert from parameters
237+
if function_declaration.parameters_json_schema:
238+
input_schema = function_declaration.parameters_json_schema
239+
else:
240+
properties = {}
241+
required_params = []
242+
if function_declaration.parameters:
243+
if function_declaration.parameters.properties:
244+
for key, value in function_declaration.parameters.properties.items():
245+
value_dict = value.model_dump(exclude_none=True)
246+
_update_type_string(value_dict)
247+
properties[key] = value_dict
248+
if function_declaration.parameters.required:
249+
required_params = function_declaration.parameters.required
250+
251+
input_schema = {
252+
"type": "object",
253+
"properties": properties,
254+
}
255+
if required_params:
256+
input_schema["required"] = required_params
239257

240258
return anthropic_types.ToolParam(
241259
name=function_declaration.name,

0 commit comments

Comments
 (0)