Skip to content

Commit 26ff591

Browse files
yinxulaiAlainCopilotautofix-ci[bot]
authored
fix: fix OpenAPI Schema Import Pydantic Validation Errors for Complex Default Values (langgenius#27159)
Co-authored-by: Alain <yinxulai@hoymail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent bebb4ff commit 26ff591

File tree

3 files changed

+114
-7
lines changed

3 files changed

+114
-7
lines changed

‎api/core/plugin/entities/parameters.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class PluginParameter(BaseModel):
7676
auto_generate: PluginParameterAutoGenerate | None = None
7777
template: PluginParameterTemplate | None = None
7878
required: bool = False
79-
default: Union[float, int, str] | None = None
79+
default: Union[float, int, str, bool] | None = None
8080
min: Union[float, int] | None = None
8181
max: Union[float, int] | None = None
8282
precision: int | None = None

‎api/core/tools/utils/parser.py‎

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ def parse_openapi_to_tool_bundle(
6262
root = root[ref]
6363
interface["operation"]["parameters"][i] = root
6464
for parameter in interface["operation"]["parameters"]:
65+
# Handle complex type defaults that are not supported by PluginParameter
66+
default_value = None
67+
if "schema" in parameter and "default" in parameter["schema"]:
68+
default_value = ApiBasedToolSchemaParser._sanitize_default_value(parameter["schema"]["default"])
69+
6570
tool_parameter = ToolParameter(
6671
name=parameter["name"],
6772
label=I18nObject(en_US=parameter["name"], zh_Hans=parameter["name"]),
@@ -72,9 +77,7 @@ def parse_openapi_to_tool_bundle(
7277
required=parameter.get("required", False),
7378
form=ToolParameter.ToolParameterForm.LLM,
7479
llm_description=parameter.get("description"),
75-
default=parameter["schema"]["default"]
76-
if "schema" in parameter and "default" in parameter["schema"]
77-
else None,
80+
default=default_value,
7881
placeholder=I18nObject(
7982
en_US=parameter.get("description", ""), zh_Hans=parameter.get("description", "")
8083
),
@@ -134,6 +137,11 @@ def parse_openapi_to_tool_bundle(
134137
required = body_schema.get("required", [])
135138
properties = body_schema.get("properties", {})
136139
for name, property in properties.items():
140+
# Handle complex type defaults that are not supported by PluginParameter
141+
default_value = ApiBasedToolSchemaParser._sanitize_default_value(
142+
property.get("default", None)
143+
)
144+
137145
tool = ToolParameter(
138146
name=name,
139147
label=I18nObject(en_US=name, zh_Hans=name),
@@ -144,12 +152,11 @@ def parse_openapi_to_tool_bundle(
144152
required=name in required,
145153
form=ToolParameter.ToolParameterForm.LLM,
146154
llm_description=property.get("description", ""),
147-
default=property.get("default", None),
155+
default=default_value,
148156
placeholder=I18nObject(
149157
en_US=property.get("description", ""), zh_Hans=property.get("description", "")
150158
),
151159
)
152-
153160
# check if there is a type
154161
typ = ApiBasedToolSchemaParser._get_tool_parameter_type(property)
155162
if typ:
@@ -197,6 +204,22 @@ def parse_openapi_to_tool_bundle(
197204

198205
return bundles
199206

207+
@staticmethod
208+
def _sanitize_default_value(value):
209+
"""
210+
Sanitize default values for PluginParameter compatibility.
211+
Complex types (list, dict) are converted to None to avoid validation errors.
212+
213+
Args:
214+
value: The default value from OpenAPI schema
215+
216+
Returns:
217+
None for complex types (list, dict), otherwise the original value
218+
"""
219+
if isinstance(value, (list, dict)):
220+
return None
221+
return value
222+
200223
@staticmethod
201224
def _get_tool_parameter_type(parameter: dict) -> ToolParameter.ToolParameterType | None:
202225
parameter = parameter or {}
@@ -217,7 +240,11 @@ def _get_tool_parameter_type(parameter: dict) -> ToolParameter.ToolParameterType
217240
return ToolParameter.ToolParameterType.STRING
218241
elif typ == "array":
219242
items = parameter.get("items") or parameter.get("schema", {}).get("items")
220-
return ToolParameter.ToolParameterType.FILES if items and items.get("format") == "binary" else None
243+
if items and items.get("format") == "binary":
244+
return ToolParameter.ToolParameterType.FILES
245+
else:
246+
# For regular arrays, return ARRAY type instead of None
247+
return ToolParameter.ToolParameterType.ARRAY
221248
else:
222249
return None
223250

‎api/tests/unit_tests/core/tools/utils/test_parser.py‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,83 @@ def test_parse_openapi_to_tool_bundle_properties_all_of(app):
109109
assert tool_bundles[0].parameters[0].llm_description == "desc prop1"
110110
# TODO: support enum in OpenAPI
111111
# assert set(tool_bundles[0].parameters[0].options) == {"option1", "option2", "option3"}
112+
113+
114+
def test_parse_openapi_to_tool_bundle_default_value_type_casting(app):
115+
"""
116+
Test that default values are properly cast to match parameter types.
117+
This addresses the issue where array default values like [] cause validation errors
118+
when parameter type is inferred as string/number/boolean.
119+
"""
120+
openapi = {
121+
"openapi": "3.0.0",
122+
"info": {"title": "Test API", "version": "1.0.0"},
123+
"servers": [{"url": "https://example.com"}],
124+
"paths": {
125+
"/product/create": {
126+
"post": {
127+
"operationId": "createProduct",
128+
"summary": "Create a product",
129+
"requestBody": {
130+
"content": {
131+
"application/json": {
132+
"schema": {
133+
"type": "object",
134+
"properties": {
135+
"categories": {
136+
"description": "List of category identifiers",
137+
"default": [],
138+
"type": "array",
139+
"items": {"type": "string"},
140+
},
141+
"name": {
142+
"description": "Product name",
143+
"default": "Default Product",
144+
"type": "string",
145+
},
146+
"price": {"description": "Product price", "default": 0.0, "type": "number"},
147+
"available": {
148+
"description": "Product availability",
149+
"default": True,
150+
"type": "boolean",
151+
},
152+
},
153+
}
154+
}
155+
}
156+
},
157+
"responses": {"200": {"description": "Default Response"}},
158+
}
159+
}
160+
},
161+
}
162+
163+
with app.test_request_context():
164+
tool_bundles = ApiBasedToolSchemaParser.parse_openapi_to_tool_bundle(openapi)
165+
166+
assert len(tool_bundles) == 1
167+
bundle = tool_bundles[0]
168+
assert len(bundle.parameters) == 4
169+
170+
# Find parameters by name
171+
params_by_name = {param.name: param for param in bundle.parameters}
172+
173+
# Check categories parameter (array type with [] default)
174+
categories_param = params_by_name["categories"]
175+
assert categories_param.type == "array" # Will be detected by _get_tool_parameter_type
176+
assert categories_param.default is None # Array default [] is converted to None
177+
178+
# Check name parameter (string type with string default)
179+
name_param = params_by_name["name"]
180+
assert name_param.type == "string"
181+
assert name_param.default == "Default Product"
182+
183+
# Check price parameter (number type with number default)
184+
price_param = params_by_name["price"]
185+
assert price_param.type == "number"
186+
assert price_param.default == 0.0
187+
188+
# Check available parameter (boolean type with boolean default)
189+
available_param = params_by_name["available"]
190+
assert available_param.type == "boolean"
191+
assert available_param.default is True

0 commit comments

Comments
 (0)