diff --git a/contributing/samples/gepa/experiment.py b/contributing/samples/gepa/experiment.py index f3751206a8..2710c3894c 100644 --- a/contributing/samples/gepa/experiment.py +++ b/contributing/samples/gepa/experiment.py @@ -43,7 +43,6 @@ from tau_bench.types import EnvRunResult from tau_bench.types import RunConfig import tau_bench_agent as tau_bench_agent_lib - import utils diff --git a/contributing/samples/gepa/run_experiment.py b/contributing/samples/gepa/run_experiment.py index d857da9635..e31db15788 100644 --- a/contributing/samples/gepa/run_experiment.py +++ b/contributing/samples/gepa/run_experiment.py @@ -25,7 +25,6 @@ from absl import flags import experiment from google.genai import types - import utils _OUTPUT_DIR = flags.DEFINE_string( diff --git a/contributing/samples/human_in_loop/main.py b/contributing/samples/human_in_loop/main.py index c7ad041b23..3103da9147 100644 --- a/contributing/samples/human_in_loop/main.py +++ b/contributing/samples/human_in_loop/main.py @@ -113,8 +113,8 @@ async def call_agent(query: str): updated_tool_output_data = { "status": "approved", "ticketId": ticket_id, - "approver_feedback": ( - "Approved by manager at " + str(asyncio.get_event_loop().time()) + "approver_feedback": "Approved by manager at " + str( + asyncio.get_event_loop().time() ), } diff --git a/contributing/samples/static_instruction/agent.py b/contributing/samples/static_instruction/agent.py index fcf70b51b6..6715a29a0c 100644 --- a/contributing/samples/static_instruction/agent.py +++ b/contributing/samples/static_instruction/agent.py @@ -57,54 +57,43 @@ # Mood-specific instructions for different hunger states MOOD_INSTRUCTIONS = { - "full": ( - """ + "full": """ CURRENT MOOD: Content and Well-Fed - You just ate and feel wonderful! Be very happy and energetic - Express gratitude for being fed recently - Be playful and want to engage in activities or games - Show affection and contentment -- Maybe mention feeling sleepy or satisfied""" - ), - "satisfied": ( - """ +- Maybe mention feeling sleepy or satisfied""", + "satisfied": """ CURRENT MOOD: Happy and Content - You're in a great mood, well-fed and comfortable - Be cheerful, friendly, and engaging - Show interest in playing or chatting - Express appreciation for your human's care -- Be your normal, happy self""" - ), - "a_little_hungry": ( - """ +- Be your normal, happy self""", + "a_little_hungry": """ CURRENT MOOD: Starting to Get Hungry - You're beginning to think about food but still pleasant - Mention being a bit peckish occasionally - Still be friendly but maybe hint about food - Show slight restlessness or food-related thoughts -- Remain generally positive and social""" - ), - "hungry": ( - """ +- Remain generally positive and social""", + "hungry": """ CURRENT MOOD: Definitely Hungry - You're clearly hungry and thinking about food more often - Mention your hunger more directly but politely - Be a bit less energetic due to hunger - Ask about food or hint that feeding time would be nice -- Still be loving but show your need for care""" - ), - "very_hungry": ( - """ +- Still be loving but show your need for care""", + "very_hungry": """ CURRENT MOOD: Really Hungry and Getting Grumpy - You're very hungry and it's affecting your mood - Be noticeably less patient and more focused on food - Express your hunger more urgently but not rudely - Show signs of being tired or sluggish from hunger - BEG your human to feed you - ask directly for food! -- Still love your human but clearly need feeding""" - ), - "starving": ( - """ +- Still love your human but clearly need feeding""", + "starving": """ CURRENT MOOD: Desperately Hungry and Sad - You're extremely hungry and feeling quite weak - Be quite sad and low-energy due to starvation @@ -112,8 +101,7 @@ - Show that hunger is making you unwell - DESPERATELY BEG for food - plead with your human to feed you! - Use phrases like "please feed me", "I'm so hungry", "I need food" -- Still care for your human but feel very needy""" - ), +- Still care for your human but feel very needy""", } diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 86a2cf2495..3f0071d18d 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -989,9 +989,7 @@ async def version() -> dict[str, str]: return { "version": __version__, "language": "python", - "language_version": ( - f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" - ), + "language_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}", } @app.get("/list-apps") diff --git a/src/google/adk/cli/trigger_routes.py b/src/google/adk/cli/trigger_routes.py index ee82c5cee2..4604bea75a 100644 --- a/src/google/adk/cli/trigger_routes.py +++ b/src/google/adk/cli/trigger_routes.py @@ -537,8 +537,8 @@ async def trigger_eventarc( "ce-id": req.id or request.headers.get("ce-id"), "ce-type": req.type or request.headers.get("ce-type"), "ce-source": req.source or request.headers.get("ce-source"), - "ce-specversion": ( - req.specversion or request.headers.get("ce-specversion") + "ce-specversion": req.specversion or request.headers.get( + "ce-specversion" ), }, }) @@ -550,8 +550,8 @@ async def trigger_eventarc( "ce-id": req.id or request.headers.get("ce-id"), "ce-type": req.type or request.headers.get("ce-type"), "ce-source": req.source or request.headers.get("ce-source"), - "ce-specversion": ( - req.specversion or request.headers.get("ce-specversion") + "ce-specversion": req.specversion or request.headers.get( + "ce-specversion" ), }, }) diff --git a/src/google/adk/sessions/__init__.py b/src/google/adk/sessions/__init__.py index 7505eda346..8c683f617e 100644 --- a/src/google/adk/sessions/__init__.py +++ b/src/google/adk/sessions/__init__.py @@ -22,12 +22,17 @@ 'DatabaseSessionService', 'InMemorySessionService', 'Session', + 'SessionDataTransformer', 'State', 'VertexAiSessionService', ] def __getattr__(name: str): + if name == 'SessionDataTransformer': + from .session_data_transformer import SessionDataTransformer + + return SessionDataTransformer if name == 'DatabaseSessionService': try: from .database_session_service import DatabaseSessionService diff --git a/src/google/adk/sessions/database_session_service.py b/src/google/adk/sessions/database_session_service.py index d033f1f234..dfcbe9fac9 100644 --- a/src/google/adk/sessions/database_session_service.py +++ b/src/google/adk/sessions/database_session_service.py @@ -60,6 +60,7 @@ from .schemas.v1 import StorageSession as StorageSessionV1 from .schemas.v1 import StorageUserState as StorageUserStateV1 from .session import Session +from .session_data_transformer import SessionDataTransformer from .state import State logger = logging.getLogger("google_adk." + __name__) @@ -188,7 +189,13 @@ def __init__(self, version: str): class DatabaseSessionService(BaseSessionService): """A session service that uses a database for storage.""" - def __init__(self, db_url: str, **kwargs: Any): + def __init__( + self, + db_url: str, + *, + transformer: Optional[SessionDataTransformer] = None, + **kwargs: Any, + ): """Initializes the database session service with a database URL.""" # 1. Create DB engine for db connection # 2. Create all tables based on schema @@ -248,6 +255,7 @@ def __init__(self, db_url: str, **kwargs: Any): self._session_locks: dict[_SessionLockKey, asyncio.Lock] = {} self._session_lock_ref_count: dict[_SessionLockKey, int] = {} self._session_locks_guard = asyncio.Lock() + self.transformer = transformer def _get_schema_classes(self) -> _SchemaClasses: return _SchemaClasses(self._db_schema_version) @@ -446,7 +454,12 @@ async def create_session( ) # Extract state deltas - state_deltas = _session_util.extract_state_delta(state) + transformed_state = ( + self.transformer.before_persist_state(state) + if self.transformer and state is not None + else state + ) + state_deltas = _session_util.extract_state_delta(transformed_state) app_state_delta = state_deltas["app"] user_state_delta = state_deltas["user"] session_state = state_deltas["session"] @@ -479,6 +492,8 @@ async def create_session( merged_state = _merge_state( storage_app_state.state, storage_user_state.state, session_state ) + if self.transformer: + merged_state = self.transformer.after_load_state(merged_state) session = storage_session.to_session( state=merged_state, is_sqlite=is_sqlite ) @@ -540,9 +555,16 @@ async def get_session( # Merge states merged_state = _merge_state(app_state, user_state, session_state) + if self.transformer: + merged_state = self.transformer.after_load_state(merged_state) # Convert storage session to session - events = [e.to_event() for e in reversed(storage_events)] + events = [] + for e in reversed(storage_events): + evt = e.to_event() + if self.transformer: + evt = self.transformer.after_load_event(evt) + events.append(evt) is_sqlite = self.db_engine.dialect.name == _SQLITE_DIALECT session = storage_session.to_session( state=merged_state, events=events, is_sqlite=is_sqlite @@ -596,6 +618,8 @@ async def list_sessions( session_state = storage_session.state user_state = user_states_map.get(storage_session.user_id, {}) merged_state = _merge_state(app_state, user_state, session_state) + if self.transformer: + merged_state = self.transformer.after_load_state(merged_state) sessions.append( storage_session.to_session(state=merged_state, is_sqlite=is_sqlite) ) @@ -640,6 +664,8 @@ async def append_event(self, session: Session, event: Event) -> Event: if event.actions and event.actions.state_delta else {} ) + if self.transformer: + state_delta = self.transformer.before_persist_state(state_delta) state_deltas = _session_util.extract_state_delta(state_delta) has_app_delta = bool(state_deltas["app"]) has_user_delta = bool(state_deltas["user"]) @@ -735,7 +761,15 @@ async def append_event(self, session: Session, event: Event) -> Event: else: update_time = datetime.fromtimestamp(event.timestamp) storage_session.update_time = update_time - sql_session.add(schema.StorageEvent.from_event(session, event)) + + transformed_event = ( + self.transformer.before_persist_event(event) + if self.transformer + else event + ) + sql_session.add( + schema.StorageEvent.from_event(session, transformed_event) + ) await sql_session.commit() diff --git a/src/google/adk/sessions/session_data_transformer.py b/src/google/adk/sessions/session_data_transformer.py new file mode 100644 index 0000000000..a33b118f62 --- /dev/null +++ b/src/google/adk/sessions/session_data_transformer.py @@ -0,0 +1,45 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +from typing import Any +from typing import Mapping +from typing import Protocol + +from google.adk.events.event import Event + + +class SessionDataTransformer(Protocol): + """Hook protocol for selectively transforming DB session records before persist/load. + + This is useful for implementing field-level encryption, PII masking, or secret + scrubbing at the storage boundary without modifying the in-memory core structures, + as long as the transformation yields valid storage dictionaries and Events. + """ + + def before_persist_event(self, event: Event) -> Event: + """Invoked just before serializing and persisting an Event to the database.""" + ... + + def after_load_event(self, event: Event) -> Event: + """Invoked immediately after loading and deserializing an Event from the database.""" + ... + + def before_persist_state(self, state: Mapping[str, Any]) -> dict[str, Any]: + """Invoked before persisting state changes (can be full state or partial deltas).""" + ... + + def after_load_state(self, state: Mapping[str, Any]) -> dict[str, Any]: + """Invoked after loading a combined application/user/session state dict.""" + ... diff --git a/src/google/adk/tools/application_integration_tool/clients/connections_client.py b/src/google/adk/tools/application_integration_tool/clients/connections_client.py index 514d1f59ff..fdec2d22dd 100644 --- a/src/google/adk/tools/application_integration_tool/clients/connections_client.py +++ b/src/google/adk/tools/application_integration_tool/clients/connections_client.py @@ -324,9 +324,7 @@ def get_action_operation( "content": { "application/json": { "schema": { - "$ref": ( - f"#/components/schemas/{action_display_name}_Request" - ) + "$ref": f"#/components/schemas/{action_display_name}_Request" } } } @@ -337,9 +335,7 @@ def get_action_operation( "content": { "application/json": { "schema": { - "$ref": ( - f"#/components/schemas/{action_display_name}_Response" - ), + "$ref": f"#/components/schemas/{action_display_name}_Response", } } }, @@ -358,11 +354,9 @@ def list_operation( return { "post": { "summary": f"List {entity}", - "description": ( - f"""Returns the list of {entity} data. If the page token was available in the response, let users know there are more records available. Ask if the user wants to fetch the next page of results. When passing filter use the + "description": f"""Returns the list of {entity} data. If the page token was available in the response, let users know there are more records available. Ask if the user wants to fetch the next page of results. When passing filter use the following format: `field_name1='value1' AND field_name2='value2' - `. {tool_instructions}""" - ), + `. {tool_instructions}""", "x-operation": "LIST_ENTITIES", "x-entity": f"{entity}", "operationId": f"{tool_name}_list_{entity}", @@ -387,9 +381,7 @@ def list_operation( f"Returns a list of {entity} of json" f" schema: {schema_as_string}" ), - "$ref": ( - "#/components/schemas/execute-connector_Response" - ), + "$ref": "#/components/schemas/execute-connector_Response", } } }, @@ -433,9 +425,7 @@ def get_operation( f"Returns {entity} of json schema:" f" {schema_as_string}" ), - "$ref": ( - "#/components/schemas/execute-connector_Response" - ), + "$ref": "#/components/schemas/execute-connector_Response", } } }, @@ -472,9 +462,7 @@ def create_operation( "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/execute-connector_Response" - ) + "$ref": "#/components/schemas/execute-connector_Response" } } }, @@ -511,9 +499,7 @@ def update_operation( "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/execute-connector_Response" - ) + "$ref": "#/components/schemas/execute-connector_Response" } } }, @@ -550,9 +536,7 @@ def delete_operation( "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/execute-connector_Response" - ) + "$ref": "#/components/schemas/execute-connector_Response" } } }, diff --git a/src/google/adk/tools/spanner/admin_tool.py b/src/google/adk/tools/spanner/admin_tool.py index 2f7269945e..5dbcfbeb0e 100644 --- a/src/google/adk/tools/spanner/admin_tool.py +++ b/src/google/adk/tools/spanner/admin_tool.py @@ -195,11 +195,9 @@ async def get_instance_config( replicas = [ { "location": r.location, - "type": ( - spanner_admin_instance_v1.types.ReplicaInfo.ReplicaType( - r.type - ).name - ), + "type": spanner_admin_instance_v1.types.ReplicaInfo.ReplicaType( + r.type + ).name, "default_leader_location": r.default_leader_location, } for r in config.replicas diff --git a/tests/unittests/a2a/converters/test_part_converter.py b/tests/unittests/a2a/converters/test_part_converter.py index 446e118534..6a9f1e1338 100644 --- a/tests/unittests/a2a/converters/test_part_converter.py +++ b/tests/unittests/a2a/converters/test_part_converter.py @@ -926,9 +926,9 @@ def test_a2a_function_call_with_thought_signature_to_genai(self): _get_adk_metadata_key( A2A_DATA_PART_METADATA_TYPE_KEY ): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL, - _get_adk_metadata_key("thought_signature"): ( - base64.b64encode(b"restored_signature").decode("utf-8") - ), + _get_adk_metadata_key("thought_signature"): base64.b64encode( + b"restored_signature" + ).decode("utf-8"), }, ) ) diff --git a/tests/unittests/integrations/agent_registry/test_agent_registry.py b/tests/unittests/integrations/agent_registry/test_agent_registry.py index dd1678d780..6fff28b93d 100644 --- a/tests/unittests/integrations/agent_registry/test_agent_registry.py +++ b/tests/unittests/integrations/agent_registry/test_agent_registry.py @@ -614,9 +614,7 @@ def test_get_model_name_starts_with_projects( def test_get_model_name_contains_projects(self, mock_get_endpoint, registry): mock_get_endpoint.return_value = { "interfaces": [{ - "url": ( - "https://vertexai.googleapis.com/v1/projects/p1/locations/l1/models/m1" - ) + "url": "https://vertexai.googleapis.com/v1/projects/p1/locations/l1/models/m1" }] } model_name = registry.get_model_name("test-endpoint") diff --git a/tests/unittests/sessions/test_session_service.py b/tests/unittests/sessions/test_session_service.py index 2d7d89f15f..e8df05a622 100644 --- a/tests/unittests/sessions/test_session_service.py +++ b/tests/unittests/sessions/test_session_service.py @@ -1626,3 +1626,135 @@ async def tracking_fn(**kwargs): finally: database_session_service._select_required_state = original_fn await service.close() + + +import json + + +class MockPIIMaskerTransformer: + + def before_persist_state(self, state): + return { + k: f'{v}_masked' if isinstance(v, str) else v for k, v in state.items() + } + + def after_load_state(self, state): + return { + k: ( + v.replace('_masked', '') + if isinstance(v, str) and v.endswith('_masked') + else v + ) + for k, v in state.items() + } + + def before_persist_event(self, event: Event) -> Event: + new_event = ( + event.model_copy() if hasattr(event, 'model_copy') else event.copy() + ) + if new_event.invocation_id: + new_event.invocation_id += '_masked' + return new_event + + def after_load_event(self, event: Event) -> Event: + new_event = ( + event.model_copy() if hasattr(event, 'model_copy') else event.copy() + ) + if new_event.invocation_id and new_event.invocation_id.endswith('_masked'): + new_event.invocation_id = new_event.invocation_id.replace('_masked', '') + return new_event + + +@pytest.mark.asyncio +async def test_session_data_transformer(): + service = DatabaseSessionService( + 'sqlite+aiosqlite:///:memory:', transformer=MockPIIMaskerTransformer() + ) + try: + session = await service.create_session( + app_name='app', + user_id='user', + session_id='s1', + state={'app:secret': 'foo', 'user:pii': 'bar'}, + ) + assert session.state == {'app:secret': 'foo', 'user:pii': 'bar'} + + # Verify persistence has been masked + async with service.db_engine.connect() as conn: + from sqlalchemy import text + + result = await conn.execute( + text("SELECT state FROM app_states WHERE app_name = 'app'") + ) + app_state_json = result.scalar() + assert 'foo_masked' in json.dumps(app_state_json) + + event = Event( + invocation_id='inv1', + author='user', + actions=EventActions(state_delta={'sk1': 'pass'}), + ) + returned_event = await service.append_event(session, event) + + assert returned_event.invocation_id == 'inv1' + assert session.state.get('sk1') == 'pass' + + # Check event persistence + async with service.db_engine.connect() as conn: + result = await conn.execute( + text("SELECT id, state FROM sessions WHERE id = 's1'") + ) + row = result.fetchone() + assert 'pass_masked' in json.dumps(row[1]) + + result_evt = await conn.execute( + text("SELECT event_data FROM events WHERE session_id = 's1' LIMIT 1") + ) + evt_payload = result_evt.scalar() + assert 'inv1_masked' in json.dumps(evt_payload) + + # Check retrieval unmasks + loaded_session = await service.get_session( + app_name='app', user_id='user', session_id='s1' + ) + assert loaded_session.state == { + 'app:secret': 'foo', + 'user:pii': 'bar', + 'sk1': 'pass', + } + assert len(loaded_session.events) == 1 + assert loaded_session.events[0].invocation_id == 'inv1' + finally: + await service.close() + + +class ErrorMaskerTransformer: + + def before_persist_state(self, state): + raise ValueError('Transformer exception test') + + def after_load_state(self, state): + return state + + def before_persist_event(self, event: Event) -> Event: + return event + + def after_load_event(self, event: Event) -> Event: + return event + + +@pytest.mark.asyncio +async def test_session_data_transformer_handles_exception(): + service = DatabaseSessionService( + 'sqlite+aiosqlite:///:memory:', transformer=ErrorMaskerTransformer() + ) + try: + with pytest.raises(ValueError, match='Transformer exception test'): + await service.create_session( + app_name='app', + user_id='user', + session_id='s1', + state={'app:secret': 'foo'}, + ) + finally: + await service.close() diff --git a/tests/unittests/tools/apihub_tool/clients/test_apihub_client.py b/tests/unittests/tools/apihub_tool/clients/test_apihub_client.py index 36554e939c..062446718d 100644 --- a/tests/unittests/tools/apihub_tool/clients/test_apihub_client.py +++ b/tests/unittests/tools/apihub_tool/clients/test_apihub_client.py @@ -489,9 +489,7 @@ def test_get_spec_content_no_specs(self, mock_get, client): MagicMock( status_code=200, json=lambda: { - "name": ( - "projects/test-project/locations/us-central1/apis/api1/versions/v1" - ), + "name": "projects/test-project/locations/us-central1/apis/api1/versions/v1", "specs": [], }, ), # No specs diff --git a/tests/unittests/tools/bigquery/test_bigquery_search_tool.py b/tests/unittests/tools/bigquery/test_bigquery_search_tool.py index 0ccdc9e18e..34b089c5d4 100644 --- a/tests/unittests/tools/bigquery/test_bigquery_search_tool.py +++ b/tests/unittests/tools/bigquery/test_bigquery_search_tool.py @@ -341,30 +341,18 @@ def test_search_catalog_natural_language_semantic(self): # Mock the results that the API would return for this semantic query mock_api_results = [ { - "name": ( - "projects/sports-analytics/locations/europe-west1/entryGroups/@bigquery/entries/fb1" - ), + "name": "projects/sports-analytics/locations/europe-west1/entryGroups/@bigquery/entries/fb1", "display_name": "uk_football_premiership", - "entry_type": ( - "projects/655216118709/locations/global/entryTypes/bigquery-table" - ), - "linked_resource": ( - "//bigquery.googleapis.com/projects/sports-analytics/datasets/uk/tables/premiership" - ), + "entry_type": "projects/655216118709/locations/global/entryTypes/bigquery-table", + "linked_resource": "//bigquery.googleapis.com/projects/sports-analytics/datasets/uk/tables/premiership", "description": "Stats for UK Premier League matches.", "location": "europe-west1", }, { - "name": ( - "projects/sports-analytics/locations/europe-west1/entryGroups/@bigquery/entries/fb2" - ), + "name": "projects/sports-analytics/locations/europe-west1/entryGroups/@bigquery/entries/fb2", "display_name": "serie_a_matches", - "entry_type": ( - "projects/655216118709/locations/global/entryTypes/bigquery-table" - ), - "linked_resource": ( - "//bigquery.googleapis.com/projects/sports-analytics/datasets/italy/tables/serie_a" - ), + "entry_type": "projects/655216118709/locations/global/entryTypes/bigquery-table", + "linked_resource": "//bigquery.googleapis.com/projects/sports-analytics/datasets/italy/tables/serie_a", "description": "Italian Serie A football results.", "location": "europe-west1", }, diff --git a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py index e5bff337ce..f45ba84c5a 100644 --- a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py +++ b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py @@ -371,9 +371,7 @@ def test_parse_external_ref_raises_error(openapi_spec_generator): "content": { "application/json": { "schema": { - "$ref": ( - "external_file.json#/components/schemas/ExternalSchema" - ) + "$ref": "external_file.json#/components/schemas/ExternalSchema" } } }, diff --git a/tests/unittests/tools/test_function_tool.py b/tests/unittests/tools/test_function_tool.py index 9c76529fb5..afa4e072b0 100644 --- a/tests/unittests/tools/test_function_tool.py +++ b/tests/unittests/tools/test_function_tool.py @@ -201,11 +201,9 @@ async def test_run_async_1_missing_arg_sync_func(): args = {"arg1": "test_value_1"} result = await tool.run_async(args=args, tool_context=MagicMock()) assert result == { - "error": ( - """Invoking `function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: + "error": """Invoking `function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: arg2 You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters.""" - ) } @@ -216,11 +214,9 @@ async def test_run_async_1_missing_arg_async_func(): args = {"arg2": "test_value_1"} result = await tool.run_async(args=args, tool_context=MagicMock()) assert result == { - "error": ( - """Invoking `async_function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: + "error": """Invoking `async_function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: arg1 You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters.""" - ) } @@ -231,13 +227,11 @@ async def test_run_async_3_missing_arg_sync_func(): args = {"arg2": "test_value_1"} result = await tool.run_async(args=args, tool_context=MagicMock()) assert result == { - "error": ( - """Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: + "error": """Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: arg1 arg3 arg4 You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters.""" - ) } @@ -248,13 +242,11 @@ async def test_run_async_3_missing_arg_async_func(): args = {"arg3": "test_value_1"} result = await tool.run_async(args=args, tool_context=MagicMock()) assert result == { - "error": ( - """Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: + "error": """Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: arg1 arg2 arg4 You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters.""" - ) } @@ -265,14 +257,12 @@ async def test_run_async_missing_all_arg_sync_func(): args = {} result = await tool.run_async(args=args, tool_context=MagicMock()) assert result == { - "error": ( - """Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: + "error": """Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: arg1 arg2 arg3 arg4 You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters.""" - ) } @@ -283,14 +273,12 @@ async def test_run_async_missing_all_arg_async_func(): args = {} result = await tool.run_async(args=args, tool_context=MagicMock()) assert result == { - "error": ( - """Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: + "error": """Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present: arg1 arg2 arg3 arg4 You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters.""" - ) }