From cce900557b56a9eeaa3f6cc0725d56399d0f9a3e Mon Sep 17 00:00:00 2001 From: Ishan Raj Singh Date: Wed, 8 Oct 2025 13:39:53 +0530 Subject: [PATCH 1/6] fix: Handle 'Any' type in OpenAPI Schema for Pydantic 2.11+ compatibility - Modified Schema model to use 'object' type instead of 'Any' - Added _sanitize_schema() method to convert 'Any' to valid types - Added _create_default_schema() method for consistent defaults - Integrated sanitization in parameter, request body, and return value processing - Ensures compatibility with Pydantic >=2.11 while maintaining backward compatibility The OpenApiSpecParser was creating Schema(type='Any'), which is invalid in Pydantic 2.11+ that only accepts: 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string'. This fix applies defense-in-depth by handling the conversion at multiple levels: 1. Default schema creation uses 'object' type 2. Schema sanitization method converts any 'Any' types 3. Applied to parameters, request bodies, and return values Fixes #3108 --- .../openapi_spec_parser/operation_parser.py | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py index f7a577afb2..636286265a 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py @@ -77,6 +77,36 @@ def load( parser._return_value = return_value return parser + @staticmethod + def _sanitize_schema(schema: Union[Schema, Dict[str, Any]]) -> Schema: + """Sanitizes schema to ensure Pydantic 2.11+ compatibility. + + Converts 'Any' type to 'object' type since Pydantic 2.11+ only accepts: + 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' + + Args: + schema: Schema object or dict to sanitize + + Returns: + Sanitized Schema object + """ + if isinstance(schema, dict): + schema = Schema(**schema) + + # If schema has 'Any' type, convert to 'object' + if hasattr(schema, 'type') and schema.type in ('Any', 'any'): + schema.type = 'object' + + return schema + + @staticmethod + def _create_default_schema() -> Schema: + """Creates a default schema that's compatible with Pydantic 2.11+. + + Returns 'object' type instead of 'Any' to ensure Pydantic validation passes. + """ + return Schema(type='object') + def _process_operation_parameters(self): """Processes parameters from the OpenAPI operation.""" parameters = self._operation.parameters or [] @@ -86,6 +116,11 @@ def _process_operation_parameters(self): description = param.description or '' location = param.in_ or '' schema = param.schema_ or {} # Use schema_ instead of .schema + + # Sanitize schema to handle 'Any' type + if isinstance(schema, Schema): + schema = self._sanitize_schema(schema) + schema.description = ( description if not schema.description else schema.description ) @@ -115,11 +150,20 @@ def _process_request_body(self): # If request body is an object, expand the properties as parameters for _, media_type_object in content.items(): schema = media_type_object.schema_ or {} + + # Sanitize schema to handle 'Any' type + if isinstance(schema, Schema): + schema = self._sanitize_schema(schema) + description = request_body.description or '' if schema and schema.type == 'object': properties = schema.properties or {} for prop_name, prop_details in properties.items(): + # Sanitize nested schema + if isinstance(prop_details, Schema): + prop_details = self._sanitize_schema(prop_details) + self._params.append( ApiParameter( original_name=prop_name, @@ -164,8 +208,8 @@ def _dedupe_param_names(self): def _process_return_value(self) -> Parameter: """Returns a Parameter object representing the return type.""" responses = self._operation.responses or {} - # Default to Any if no 2xx response or if schema is missing - return_schema = Schema(type='Any') + # Default to object type if no 2xx response or if schema is missing + return_schema = self._create_default_schema() # Take the 20x response with the smallest response code. valid_codes = list( @@ -178,6 +222,11 @@ def _process_return_value(self) -> Parameter: for mime_type in content: if content[mime_type].schema_: return_schema = content[mime_type].schema_ + + # Sanitize return schema + if isinstance(return_schema, Schema): + return_schema = self._sanitize_schema(return_schema) + break self._return_value = ApiParameter( From f745d6d9a1cc283ca23af2b982dba9481441e6fb Mon Sep 17 00:00:00 2001 From: ISHAN RAJ SINGH Date: Wed, 8 Oct 2025 13:48:46 +0530 Subject: [PATCH 2/6] Update src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../tools/openapi_tool/openapi_spec_parser/operation_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py index 8a21d60ab6..5fe58cb7b3 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py @@ -94,7 +94,7 @@ def _sanitize_schema(schema: Union[Schema, Dict[str, Any]]) -> Schema: schema = Schema(**schema) # If schema has 'Any' type, convert to 'object' - if hasattr(schema, 'type') and schema.type in ('Any', 'any'): + if hasattr(schema, 'type') and schema.type and schema.type.lower() == 'any': schema.type = 'object' return schema From ea4c73e5717be22fb1caa5b93d4606e9c56b2bc5 Mon Sep 17 00:00:00 2001 From: ISHAN RAJ SINGH Date: Wed, 8 Oct 2025 13:48:57 +0530 Subject: [PATCH 3/6] Update src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../tools/openapi_tool/openapi_spec_parser/operation_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py index 5fe58cb7b3..c172098bfb 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py @@ -118,7 +118,7 @@ def _process_operation_parameters(self): schema = param.schema_ or {} # Use schema_ instead of .schema # Sanitize schema to handle 'Any' type - if isinstance(schema, Schema): + if isinstance(schema, (Schema, dict)): schema = self._sanitize_schema(schema) schema.description = ( From ed3f9abc047f92aae0faa4a1674e11ab326373ed Mon Sep 17 00:00:00 2001 From: ISHAN RAJ SINGH Date: Wed, 8 Oct 2025 13:49:04 +0530 Subject: [PATCH 4/6] Update src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../tools/openapi_tool/openapi_spec_parser/operation_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py index c172098bfb..40272ce1e2 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py @@ -152,7 +152,7 @@ def _process_request_body(self): schema = media_type_object.schema_ or {} # Sanitize schema to handle 'Any' type - if isinstance(schema, Schema): + if isinstance(schema, (Schema, dict)): schema = self._sanitize_schema(schema) description = request_body.description or '' From b4072158a5f76f7924b9ada2538c89a631266ba5 Mon Sep 17 00:00:00 2001 From: ISHAN RAJ SINGH Date: Wed, 8 Oct 2025 13:56:43 +0530 Subject: [PATCH 5/6] Update operation_parser.py From c21a09530d3e1358b0b4f57cac532ad7c64fd106 Mon Sep 17 00:00:00 2001 From: ISHAN RAJ SINGH Date: Wed, 8 Oct 2025 13:58:53 +0530 Subject: [PATCH 6/6] Update operation_parser.py --- .../tools/openapi_tool/openapi_spec_parser/operation_parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py index 40272ce1e2..4a6f757624 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py @@ -210,8 +210,6 @@ def _process_return_value(self) -> Parameter: responses = self._operation.responses or {} # Default to object type if no 2xx response or if schema is missing return_schema = self._create_default_schema() - # Default to empty schema if no 2xx response or if schema is missing - return_schema = Schema() # Take the 20x response with the smallest response code. valid_codes = list(