Skip to content

Commit 8546bb0

Browse files
collindutterclaude
andcommitted
feat: add allow_any_type parameter to JSON schema resolver (#2030)
Add opt-in allow_any_type parameter to JSON schema type resolution. When enabled, schemas without explicit types infer Any type instead of raising TypeError. - Add allow_any_type parameter to ITypeResolver.resolve_type() interface - Update TypeResolver to use existing anyType logic when allow_any_type=True - Thread parameter through all recursive calls and combiner handlers - Update error message to inform users about the new parameter - Add allow_any_type parameter to create_model() public API - Enable allow_any_type in MCPTool for flexible MCP tool schemas 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent cf92b0c commit 8546bb0

File tree

6 files changed

+37
-16
lines changed

6 files changed

+37
-16
lines changed

griptape/tools/mcp/tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def _create_activity_handler(self, tool: types.Tool) -> Callable:
175175
config={
176176
"name": tool.name,
177177
"description": tool.description or tool.title or tool.name,
178-
"schema": create_model(tool.inputSchema),
178+
"schema": create_model(tool.inputSchema, allow_undefined_array_items=True, allow_any_type=True),
179179
}
180180
)
181181
def activity_handler(self: MCPTool, values: dict) -> Any:

griptape/utils/json_schema_to_pydantic/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def create_model(
3333
base_model_type: Type[T] = BaseModel,
3434
root_schema: Optional[Dict[str, Any]] = None,
3535
allow_undefined_array_items: bool = False,
36+
allow_any_type: bool = False,
3637
) -> Type[T]:
3738
"""
3839
Create a Pydantic model from a JSON Schema.
@@ -41,6 +42,8 @@ def create_model(
4142
schema: The JSON Schema to convert
4243
root_schema: The root schema containing definitions.
4344
Defaults to schema if not provided.
45+
allow_undefined_array_items: If True, allows arrays without items schema
46+
allow_any_type: If True, infers Any type for schemas without explicit types
4447
4548
Returns:
4649
A Pydantic model class
@@ -52,7 +55,7 @@ def create_model(
5255
ReferenceError: If there's an error resolving references
5356
"""
5457
builder = PydanticModelBuilder(base_model_type=base_model_type)
55-
return builder.create_pydantic_model(schema, root_schema, allow_undefined_array_items)
58+
return builder.create_pydantic_model(schema, root_schema, allow_undefined_array_items, allow_any_type)
5659

5760

5861
__all__ = [

griptape/utils/json_schema_to_pydantic/handlers.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def handle_all_of(
4242
schemas: List[Dict[str, Any]],
4343
root_schema: Dict[str, Any],
4444
allow_undefined_array_items: bool = False,
45+
allow_any_type: bool = False,
4546
) -> Type[BaseModel]:
4647
"""Combines multiple schemas with AND logic."""
4748
if not schemas:
@@ -80,7 +81,7 @@ def handle_all_of(
8081
field_definitions = {}
8182
for name, prop_schema in merged_properties.items():
8283
field_type = self.recursive_field_builder(
83-
prop_schema, root_schema, allow_undefined_array_items
84+
prop_schema, root_schema, allow_undefined_array_items, allow_any_type
8485
)
8586
field_info = self.field_info_builder(prop_schema, name in required_fields)
8687
field_definitions[name] = (field_type, field_info)
@@ -96,6 +97,7 @@ def handle_any_of(
9697
schemas: List[Dict[str, Any]],
9798
root_schema: Dict[str, Any],
9899
allow_undefined_array_items: bool = False,
100+
allow_any_type: bool = False,
99101
) -> Any:
100102
"""Allows validation against any of the given schemas."""
101103
if not schemas:
@@ -114,7 +116,7 @@ def handle_any_of(
114116

115117
# Use the recursive_field_builder callback to resolve the type
116118
resolved_type = self.recursive_field_builder(
117-
schema, root_schema, allow_undefined_array_items
119+
schema, root_schema, allow_undefined_array_items, allow_any_type
118120
)
119121
possible_types.append(resolved_type)
120122

@@ -125,6 +127,7 @@ def handle_one_of(
125127
schema: Dict[str, Any],
126128
root_schema: Dict[str, Any],
127129
allow_undefined_array_items: bool = False,
130+
allow_any_type: bool = False,
128131
) -> Type[BaseModel]:
129132
"""Implements discriminated unions using a type field."""
130133
schemas = schema.get("oneOf", [])
@@ -165,15 +168,15 @@ def handle_one_of(
165168
elif "oneOf" in prop_schema:
166169
# Handle nested oneOf using the callback
167170
field_type = self.recursive_field_builder(
168-
prop_schema, root_schema, allow_undefined_array_items
171+
prop_schema, root_schema, allow_undefined_array_items, allow_any_type
169172
)
170173
# Use field_info_builder for nested oneOf field info
171174
field_info = self.field_info_builder(prop_schema, name in required)
172175
fields[name] = (field_type, field_info)
173176
# Add other properties to the fields dictionary
174177
elif name != "type": # Skip the type field as it's handled above
175178
field_type = self.recursive_field_builder(
176-
prop_schema, root_schema, allow_undefined_array_items
179+
prop_schema, root_schema, allow_undefined_array_items, allow_any_type
177180
)
178181
field_info = self.field_info_builder(prop_schema, name in required)
179182
fields[name] = (field_type, field_info)

griptape/utils/json_schema_to_pydantic/interfaces.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def resolve_type(
1313
schema: Dict[str, Any],
1414
root_schema: Dict[str, Any],
1515
allow_undefined_array_items: bool = False,
16+
allow_any_type: bool = False,
1617
) -> Any:
1718
"""Resolves JSON Schema types to Pydantic types"""
1819
pass
@@ -32,6 +33,7 @@ def handle_all_of(
3233
schemas: List[Dict[str, Any]],
3334
root_schema: Dict[str, Any],
3435
allow_undefined_array_items: bool = False,
36+
allow_any_type: bool = False,
3537
) -> Any:
3638
"""Handles allOf combiner"""
3739
pass
@@ -42,6 +44,7 @@ def handle_any_of(
4244
schemas: List[Dict[str, Any]],
4345
root_schema: Dict[str, Any],
4446
allow_undefined_array_items: bool = False,
47+
allow_any_type: bool = False,
4548
) -> Any:
4649
"""Handles anyOf combiner"""
4750
pass
@@ -52,6 +55,7 @@ def handle_one_of(
5255
schema: Dict[str, Any],
5356
root_schema: Dict[str, Any],
5457
allow_undefined_array_items: bool = False,
58+
allow_any_type: bool = False,
5559
) -> Any:
5660
"""Handles oneOf combiner"""
5761
pass
@@ -73,6 +77,7 @@ def create_pydantic_model(
7377
schema: Dict[str, Any],
7478
root_schema: Optional[Dict[str, Any]] = None,
7579
allow_undefined_array_items: bool = False,
80+
allow_any_type: bool = False,
7681
) -> Type[T]:
7782
"""Creates a Pydantic model from JSON Schema"""
7883
pass

griptape/utils/json_schema_to_pydantic/model_builder.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def create_pydantic_model(
5656
schema: Dict[str, Any],
5757
root_schema: Optional[Dict[str, Any]] = None,
5858
allow_undefined_array_items: bool = False,
59+
allow_any_type: bool = False,
5960
_schema_ref: Optional[str] = None,
6061
) -> Type[T]:
6162
"""
@@ -94,15 +95,15 @@ def create_pydantic_model(
9495
# Handle combiners
9596
if "allOf" in schema:
9697
return self.combiner_handler.handle_all_of(
97-
schema["allOf"], root_schema, allow_undefined_array_items
98+
schema["allOf"], root_schema, allow_undefined_array_items, allow_any_type
9899
)
99100
if "anyOf" in schema:
100101
return self.combiner_handler.handle_any_of(
101-
schema["anyOf"], root_schema, allow_undefined_array_items
102+
schema["anyOf"], root_schema, allow_undefined_array_items, allow_any_type
102103
)
103104
if "oneOf" in schema:
104105
return self.combiner_handler.handle_one_of(
105-
schema["oneOf"], root_schema, allow_undefined_array_items
106+
schema["oneOf"], root_schema, allow_undefined_array_items, allow_any_type
106107
)
107108

108109
# Get model properties
@@ -126,7 +127,7 @@ def create_pydantic_model(
126127
fields = {}
127128
for field_name, field_schema in properties.items():
128129
field_type = self._get_field_type(
129-
field_schema, root_schema, allow_undefined_array_items
130+
field_schema, root_schema, allow_undefined_array_items, allow_any_type
130131
)
131132
field_info = self._build_field_info(field_schema, field_name in required)
132133
fields[field_name] = (field_type, field_info)
@@ -169,6 +170,7 @@ def _get_field_type(
169170
field_schema: Dict[str, Any],
170171
root_schema: Dict[str, Any],
171172
allow_undefined_array_items: bool = False,
173+
allow_any_type: bool = False,
172174
) -> Any:
173175
"""Resolves the Python type for a field schema."""
174176
# Store the original ref if present
@@ -200,15 +202,15 @@ def _get_field_type(
200202
# Handle combiners
201203
if "allOf" in field_schema:
202204
return self.combiner_handler.handle_all_of(
203-
field_schema["allOf"], root_schema, allow_undefined_array_items
205+
field_schema["allOf"], root_schema, allow_undefined_array_items, allow_any_type
204206
)
205207
if "anyOf" in field_schema:
206208
return self.combiner_handler.handle_any_of(
207-
field_schema["anyOf"], root_schema, allow_undefined_array_items
209+
field_schema["anyOf"], root_schema, allow_undefined_array_items, allow_any_type
208210
)
209211
if "oneOf" in field_schema:
210212
return self.combiner_handler.handle_one_of(
211-
field_schema, root_schema, allow_undefined_array_items
213+
field_schema, root_schema, allow_undefined_array_items, allow_any_type
212214
)
213215

214216
# Handle arrays by recursively processing items
@@ -224,7 +226,7 @@ def _get_field_type(
224226
# Recursively process the items schema through the model builder
225227
# This ensures that object types get proper models created
226228
item_type = self._get_field_type(
227-
items_schema, root_schema, allow_undefined_array_items
229+
items_schema, root_schema, allow_undefined_array_items, allow_any_type
228230
)
229231

230232
if field_schema.get("uniqueItems", False):
@@ -252,6 +254,7 @@ def _get_field_type(
252254
schema=field_schema,
253255
root_schema=root_schema,
254256
allow_undefined_array_items=allow_undefined_array_items,
257+
allow_any_type=allow_any_type,
255258
)
256259

257260
def _build_field_info(self, field_schema: Dict[str, Any], required: bool) -> Field:

griptape/utils/json_schema_to_pydantic/resolvers.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class TypeResolver(ITypeResolver):
1212
"""Resolves JSON Schema types to Pydantic types"""
1313

1414
def resolve_type(
15-
self, schema: dict, root_schema: dict, allow_undefined_array_items: bool = False
15+
self, schema: dict, root_schema: dict, allow_undefined_array_items: bool = False, allow_any_type: bool = False
1616
) -> Any:
1717
"""Get the Pydantic field type for a JSON schema field."""
1818
if not isinstance(schema, dict):
@@ -45,6 +45,7 @@ def resolve_type(
4545
schema={**schema, **{"type": other_types[0]}},
4646
root_schema=root_schema,
4747
allow_undefined_array_items=allow_undefined_array_items,
48+
allow_any_type=allow_any_type,
4849
)
4950
]
5051
else:
@@ -54,6 +55,7 @@ def resolve_type(
5455
schema={**schema, **{"type": t}},
5556
root_schema=root_schema,
5657
allow_undefined_array_items=allow_undefined_array_items,
58+
allow_any_type=allow_any_type,
5759
)
5860
for t in other_types
5961
]
@@ -66,6 +68,7 @@ def resolve_type(
6668
schema={**schema, **{"type": types[0]}},
6769
root_schema=root_schema,
6870
allow_undefined_array_items=allow_undefined_array_items,
71+
allow_any_type=allow_any_type,
6972
)
7073
else:
7174
# Multiple types without null: Union[type1, type2, ...]
@@ -74,6 +77,7 @@ def resolve_type(
7477
schema={**schema, **{"type": t}},
7578
root_schema=root_schema,
7679
allow_undefined_array_items=allow_undefined_array_items,
80+
allow_any_type=allow_any_type,
7781
)
7882
for t in types
7983
]
@@ -92,8 +96,10 @@ def resolve_type(
9296
schema_type = "object"
9397
elif "items" in schema:
9498
schema_type = "array"
99+
elif allow_any_type:
100+
schema_type = "anyType"
95101
else:
96-
raise TypeError("Schema must specify a type")
102+
raise TypeError("Schema must specify a type. Set allow_any_type=True to infer Any type for schemas without explicit types.")
97103

98104
if schema_type == "array":
99105
items_schema = schema.get("items")
@@ -115,6 +121,7 @@ def resolve_type(
115121
schema=items_schema,
116122
root_schema=root_schema,
117123
allow_undefined_array_items=allow_undefined_array_items,
124+
allow_any_type=allow_any_type,
118125
)
119126
if schema.get("uniqueItems", False):
120127
return Set[item_type]

0 commit comments

Comments
 (0)