mirror of https://github.com/langgenius/dify.git
fix: fix OpenAPI Schema Import Pydantic Validation Errors for Complex Default Values (#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>
This commit is contained in:
parent
bebb4ffbaa
commit
26ff59172e
|
|
@ -76,7 +76,7 @@ class PluginParameter(BaseModel):
|
|||
auto_generate: PluginParameterAutoGenerate | None = None
|
||||
template: PluginParameterTemplate | None = None
|
||||
required: bool = False
|
||||
default: Union[float, int, str] | None = None
|
||||
default: Union[float, int, str, bool] | None = None
|
||||
min: Union[float, int] | None = None
|
||||
max: Union[float, int] | None = None
|
||||
precision: int | None = None
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ class ApiBasedToolSchemaParser:
|
|||
root = root[ref]
|
||||
interface["operation"]["parameters"][i] = root
|
||||
for parameter in interface["operation"]["parameters"]:
|
||||
# Handle complex type defaults that are not supported by PluginParameter
|
||||
default_value = None
|
||||
if "schema" in parameter and "default" in parameter["schema"]:
|
||||
default_value = ApiBasedToolSchemaParser._sanitize_default_value(parameter["schema"]["default"])
|
||||
|
||||
tool_parameter = ToolParameter(
|
||||
name=parameter["name"],
|
||||
label=I18nObject(en_US=parameter["name"], zh_Hans=parameter["name"]),
|
||||
|
|
@ -72,9 +77,7 @@ class ApiBasedToolSchemaParser:
|
|||
required=parameter.get("required", False),
|
||||
form=ToolParameter.ToolParameterForm.LLM,
|
||||
llm_description=parameter.get("description"),
|
||||
default=parameter["schema"]["default"]
|
||||
if "schema" in parameter and "default" in parameter["schema"]
|
||||
else None,
|
||||
default=default_value,
|
||||
placeholder=I18nObject(
|
||||
en_US=parameter.get("description", ""), zh_Hans=parameter.get("description", "")
|
||||
),
|
||||
|
|
@ -134,6 +137,11 @@ class ApiBasedToolSchemaParser:
|
|||
required = body_schema.get("required", [])
|
||||
properties = body_schema.get("properties", {})
|
||||
for name, property in properties.items():
|
||||
# Handle complex type defaults that are not supported by PluginParameter
|
||||
default_value = ApiBasedToolSchemaParser._sanitize_default_value(
|
||||
property.get("default", None)
|
||||
)
|
||||
|
||||
tool = ToolParameter(
|
||||
name=name,
|
||||
label=I18nObject(en_US=name, zh_Hans=name),
|
||||
|
|
@ -144,12 +152,11 @@ class ApiBasedToolSchemaParser:
|
|||
required=name in required,
|
||||
form=ToolParameter.ToolParameterForm.LLM,
|
||||
llm_description=property.get("description", ""),
|
||||
default=property.get("default", None),
|
||||
default=default_value,
|
||||
placeholder=I18nObject(
|
||||
en_US=property.get("description", ""), zh_Hans=property.get("description", "")
|
||||
),
|
||||
)
|
||||
|
||||
# check if there is a type
|
||||
typ = ApiBasedToolSchemaParser._get_tool_parameter_type(property)
|
||||
if typ:
|
||||
|
|
@ -197,6 +204,22 @@ class ApiBasedToolSchemaParser:
|
|||
|
||||
return bundles
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_default_value(value):
|
||||
"""
|
||||
Sanitize default values for PluginParameter compatibility.
|
||||
Complex types (list, dict) are converted to None to avoid validation errors.
|
||||
|
||||
Args:
|
||||
value: The default value from OpenAPI schema
|
||||
|
||||
Returns:
|
||||
None for complex types (list, dict), otherwise the original value
|
||||
"""
|
||||
if isinstance(value, (list, dict)):
|
||||
return None
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _get_tool_parameter_type(parameter: dict) -> ToolParameter.ToolParameterType | None:
|
||||
parameter = parameter or {}
|
||||
|
|
@ -217,7 +240,11 @@ class ApiBasedToolSchemaParser:
|
|||
return ToolParameter.ToolParameterType.STRING
|
||||
elif typ == "array":
|
||||
items = parameter.get("items") or parameter.get("schema", {}).get("items")
|
||||
return ToolParameter.ToolParameterType.FILES if items and items.get("format") == "binary" else None
|
||||
if items and items.get("format") == "binary":
|
||||
return ToolParameter.ToolParameterType.FILES
|
||||
else:
|
||||
# For regular arrays, return ARRAY type instead of None
|
||||
return ToolParameter.ToolParameterType.ARRAY
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -109,3 +109,83 @@ def test_parse_openapi_to_tool_bundle_properties_all_of(app):
|
|||
assert tool_bundles[0].parameters[0].llm_description == "desc prop1"
|
||||
# TODO: support enum in OpenAPI
|
||||
# assert set(tool_bundles[0].parameters[0].options) == {"option1", "option2", "option3"}
|
||||
|
||||
|
||||
def test_parse_openapi_to_tool_bundle_default_value_type_casting(app):
|
||||
"""
|
||||
Test that default values are properly cast to match parameter types.
|
||||
This addresses the issue where array default values like [] cause validation errors
|
||||
when parameter type is inferred as string/number/boolean.
|
||||
"""
|
||||
openapi = {
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Test API", "version": "1.0.0"},
|
||||
"servers": [{"url": "https://example.com"}],
|
||||
"paths": {
|
||||
"/product/create": {
|
||||
"post": {
|
||||
"operationId": "createProduct",
|
||||
"summary": "Create a product",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"categories": {
|
||||
"description": "List of category identifiers",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"name": {
|
||||
"description": "Product name",
|
||||
"default": "Default Product",
|
||||
"type": "string",
|
||||
},
|
||||
"price": {"description": "Product price", "default": 0.0, "type": "number"},
|
||||
"available": {
|
||||
"description": "Product availability",
|
||||
"default": True,
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {"200": {"description": "Default Response"}},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
with app.test_request_context():
|
||||
tool_bundles = ApiBasedToolSchemaParser.parse_openapi_to_tool_bundle(openapi)
|
||||
|
||||
assert len(tool_bundles) == 1
|
||||
bundle = tool_bundles[0]
|
||||
assert len(bundle.parameters) == 4
|
||||
|
||||
# Find parameters by name
|
||||
params_by_name = {param.name: param for param in bundle.parameters}
|
||||
|
||||
# Check categories parameter (array type with [] default)
|
||||
categories_param = params_by_name["categories"]
|
||||
assert categories_param.type == "array" # Will be detected by _get_tool_parameter_type
|
||||
assert categories_param.default is None # Array default [] is converted to None
|
||||
|
||||
# Check name parameter (string type with string default)
|
||||
name_param = params_by_name["name"]
|
||||
assert name_param.type == "string"
|
||||
assert name_param.default == "Default Product"
|
||||
|
||||
# Check price parameter (number type with number default)
|
||||
price_param = params_by_name["price"]
|
||||
assert price_param.type == "number"
|
||||
assert price_param.default == 0.0
|
||||
|
||||
# Check available parameter (boolean type with boolean default)
|
||||
available_param = params_by_name["available"]
|
||||
assert available_param.type == "boolean"
|
||||
assert available_param.default is True
|
||||
|
|
|
|||
Loading…
Reference in New Issue