Skip to content

Commit 0de4cc2

Browse files
GWealecopybara-github
authored andcommitted
refactor(types): type the environment simulation tools for strict mypy
Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 970127626
1 parent 9657340 commit 0de4cc2

6 files changed

Lines changed: 39 additions & 17 deletions

File tree

src/google/adk/tools/environment/_read_file_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ async def run_async(
143143
numbered = ''.join(
144144
f'{start + i:6d}\t{line}' for i, line in enumerate(lines)
145145
)
146-
result = {
146+
result: dict[str, str | int] = {
147147
'status': 'ok',
148148
'content': _truncate(
149149
numbered,

src/google/adk/tools/environment_simulation/environment_simulation_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from pydantic import field_validator
2727
from pydantic import model_validator
2828
from pydantic import ValidationError # noqa: F401
29+
from typing_extensions import Self
2930

3031
from ...features import experimental
3132
from ...features import FeatureName
@@ -156,7 +157,9 @@ class EnvironmentSimulationConfig(BaseModel):
156157

157158
@field_validator("tool_simulation_configs")
158159
@classmethod
159-
def check_tool_simulation_configs(cls, v: List[ToolSimulationConfig]):
160+
def check_tool_simulation_configs(
161+
cls, v: List[ToolSimulationConfig]
162+
) -> List[ToolSimulationConfig]:
160163
"""Checks that tool_simulation_configs is not empty."""
161164
if not v:
162165
raise ValueError("tool_simulation_configs must be provided.")

src/google/adk/tools/environment_simulation/environment_simulation_engine.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from google.adk.tools.environment_simulation.strategies import tool_spec_mock_strategy
3434
from google.adk.tools.environment_simulation.tool_connection_analyzer import ToolConnectionAnalyzer
3535
from google.adk.tools.environment_simulation.tool_connection_map import ToolConnectionMap
36+
from google.genai import types as genai_types
3637

3738
from ...features import experimental
3839
from ...features import FeatureName
@@ -66,7 +67,7 @@ def __init__(self, config: EnvironmentSimulationConfig):
6667
llm_name=config.simulation_model,
6768
llm_config=config.simulation_model_configuration,
6869
)
69-
self._state_store = {}
70+
self._state_store: Dict[str, Any] = {}
7071
self._random_generator = random.Random()
7172
self._environment_data = config.environment_data
7273
self._tracing = config.tracing

src/google/adk/tools/environment_simulation/strategies/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from google.adk.features import experimental
2222
from google.adk.features import FeatureName
23+
from google.adk.tools.base_tool import BaseTool
2324
from google.adk.tools.environment_simulation.tool_connection_map import ToolConnectionMap
2425
from google.genai import types as genai_types
2526

src/google/adk/tools/environment_simulation/strategies/tool_spec_mock_strategy.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import json
1818
import re
1919
from typing import Any
20+
from typing import cast
2021
from typing import Dict
21-
from typing import Optional
2222

2323
from google.adk.features import experimental
2424
from google.adk.features import FeatureName
@@ -73,12 +73,13 @@
7373
"""
7474

7575

76-
def _find_value_by_key(data: Any, target_key: str) -> Optional[Any]:
76+
def _find_value_by_key(data: object, target_key: str) -> object | None:
7777
"""Recursively searches for a value by key in a nested structure."""
7878
if isinstance(data, dict):
7979
if target_key in data:
80-
return data[target_key]
81-
for key, value in data.items():
80+
result: object = data[target_key]
81+
return result
82+
for value in data.values():
8283
result = _find_value_by_key(value, target_key)
8384
if result is not None:
8485
return result
@@ -108,10 +109,10 @@ async def mock(
108109
tool: BaseTool,
109110
args: Dict[str, Any],
110111
tool_context: Any,
111-
tool_connection_map: Optional[ToolConnectionMap],
112+
tool_connection_map: ToolConnectionMap | None,
112113
state_store: Dict[str, Any],
113-
environment_data: Optional[str] = None,
114-
tracing: Optional[str] = None,
114+
environment_data: str | None = None,
115+
tracing: str | None = None,
115116
) -> Dict[str, Any]:
116117
declaration = tool._get_declaration()
117118
if not declaration:
@@ -178,7 +179,9 @@ async def mock(
178179
response_text = ""
179180
async with Aclosing(self._llm.generate_content_async(request)) as agen:
180181
async for llm_response in agen:
181-
generated_content: genai_types.Content = llm_response.content
182+
generated_content = llm_response.content
183+
if generated_content is None:
184+
continue
182185
if generated_content.parts:
183186
for part in generated_content.parts:
184187
if part.text:
@@ -187,7 +190,19 @@ async def mock(
187190
try:
188191
clean_json_text = re.sub(r"^```[a-zA-Z]*\n", "", response_text)
189192
clean_json_text = re.sub(r"\n```$", "", clean_json_text)
190-
mock_response = json.loads(clean_json_text.strip())
193+
parsed_response: object = json.loads(clean_json_text.strip())
194+
if not isinstance(parsed_response, dict) or not all(
195+
isinstance(key, str) for key in parsed_response
196+
):
197+
return {
198+
"status": "error",
199+
"error_message": "Generated mock response was not a JSON object.",
200+
"llm_output": response_text,
201+
}
202+
# Keys checked to be str by the guard above; isinstance only narrows the
203+
# value type as far as dict[Any, Any].
204+
mock_response = cast(dict[str, Any], parsed_response)
205+
191206
# Determine if the current tool is mutative by checking the connection map.
192207
is_mutative = False
193208
if tool_connection_map:
@@ -200,7 +215,7 @@ async def mock(
200215
is_mutative = True
201216

202217
# After getting the response, update the state if this was a mutative tool.
203-
if is_mutative:
218+
if is_mutative and tool_connection_map:
204219
for param_info in tool_connection_map.stateful_parameters:
205220
param_name = param_info.parameter_name
206221
# Only update the state for the specific parameter this tool

src/google/adk/tools/environment_simulation/tool_connection_analyzer.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ async def analyze(self, tools: List[BaseTool]) -> ToolConnectionMap:
9696
Analyzes a list of tools and returns a map of their connections.
9797
"""
9898
tool_schemas = [
99-
tool._get_declaration().model_dump(exclude_none=True)
99+
declaration.model_dump(exclude_none=True)
100100
for tool in tools
101-
if tool._get_declaration()
101+
if (declaration := tool._get_declaration()) is not None
102102
]
103103
tool_schemas_json = json.dumps(tool_schemas, indent=2)
104104
prompt = _TOOL_CONNECTION_ANALYSIS_PROMPT_TEMPLATE.format(
@@ -119,7 +119,9 @@ async def analyze(self, tools: List[BaseTool]) -> ToolConnectionMap:
119119
response_text = ""
120120
async with Aclosing(self._llm.generate_content_async(request)) as agen:
121121
async for llm_response in agen:
122-
generated_content: genai_types.Content = llm_response.content
122+
generated_content = llm_response.content
123+
if generated_content is None:
124+
continue
123125
if not generated_content.parts:
124126
continue
125127
for part in generated_content.parts:
@@ -129,7 +131,7 @@ async def analyze(self, tools: List[BaseTool]) -> ToolConnectionMap:
129131
try:
130132
clean_json_text = re.sub(r"^```[a-zA-Z]*\n", "", response_text)
131133
clean_json_text = re.sub(r"\n```$", "", clean_json_text)
132-
response_json = json.loads(clean_json_text.strip())
134+
response_json: object = json.loads(clean_json_text.strip())
133135
except json.JSONDecodeError as e:
134136
logging.warning(
135137
"Failed to parse tool connection analysis from LLM. Proceeding"

0 commit comments

Comments
 (0)