From ac96d192a65e5e8b537b1eb7d65a4750f6dfed16 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:59:11 +0800 Subject: [PATCH] fix: parameter type handling in API tool and parser (#2574) --- api/core/tools/tool/api_tool.py | 2 +- api/core/tools/utils/parser.py | 47 +++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/api/core/tools/tool/api_tool.py b/api/core/tools/tool/api_tool.py index f6914d3473..2a1ee92e78 100644 --- a/api/core/tools/tool/api_tool.py +++ b/api/core/tools/tool/api_tool.py @@ -200,7 +200,7 @@ class ApiTool(Tool): # replace path parameters for name, value in path_params.items(): - url = url.replace(f'{{{name}}}', value) + url = url.replace(f'{{{name}}}', f'{value}') # parse http body data if needed, for GET/HEAD/OPTIONS/TRACE, the body is ignored if 'Content-Type' in headers: diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index 91c18be3f5..889316c235 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -1,4 +1,6 @@ +import re +import uuid from json import loads as json_loads from requests import get @@ -46,7 +48,7 @@ class ApiBasedToolSchemaParser: parameters = [] if 'parameters' in interface['operation']: for parameter in interface['operation']['parameters']: - parameters.append(ToolParameter( + tool_parameter = ToolParameter( name=parameter['name'], label=I18nObject( en_US=parameter['name'], @@ -61,7 +63,14 @@ class ApiBasedToolSchemaParser: form=ToolParameter.ToolParameterForm.LLM, llm_description=parameter.get('description'), default=parameter['schema']['default'] if 'schema' in parameter and 'default' in parameter['schema'] else None, - )) + ) + + # check if there is a type + typ = ApiBasedToolSchemaParser._get_tool_parameter_type(parameter) + if typ: + tool_parameter.type = typ + + parameters.append(tool_parameter) # create tool bundle # check if there is a request body if 'requestBody' in interface['operation']: @@ -80,13 +89,14 @@ class ApiBasedToolSchemaParser: root = root[ref] # overwrite the content interface['operation']['requestBody']['content'][content_type]['schema'] = root + # parse body parameters if 'schema' in interface['operation']['requestBody']['content'][content_type]: body_schema = interface['operation']['requestBody']['content'][content_type]['schema'] required = body_schema['required'] if 'required' in body_schema else [] properties = body_schema['properties'] if 'properties' in body_schema else {} for name, property in properties.items(): - parameters.append(ToolParameter( + tool = ToolParameter( name=name, label=I18nObject( en_US=name, @@ -101,7 +111,14 @@ class ApiBasedToolSchemaParser: form=ToolParameter.ToolParameterForm.LLM, llm_description=property['description'] if 'description' in property else '', default=property['default'] if 'default' in property else None, - )) + ) + + # check if there is a type + typ = ApiBasedToolSchemaParser._get_tool_parameter_type(property) + if typ: + tool.type = typ + + parameters.append(tool) # check if parameters is duplicated parameters_count = {} @@ -119,7 +136,11 @@ class ApiBasedToolSchemaParser: path = interface['path'] if interface['path'].startswith('/'): path = interface['path'][1:] - path = path.replace('/', '_') + # remove special characters like / to ensure the operation id is valid ^[a-zA-Z0-9_-]{1,64}$ + path = re.sub(r'[^a-zA-Z0-9_-]', '', path) + if not path: + path = str(uuid.uuid4()) + interface['operation']['operationId'] = f'{path}_{interface["method"]}' bundles.append(ApiBasedToolBundle( @@ -134,7 +155,23 @@ class ApiBasedToolSchemaParser: )) return bundles + + @staticmethod + def _get_tool_parameter_type(parameter: dict) -> ToolParameter.ToolParameterType: + parameter = parameter or {} + typ = None + if 'type' in parameter: + typ = parameter['type'] + elif 'schema' in parameter and 'type' in parameter['schema']: + typ = parameter['schema']['type'] + if typ == 'integer' or typ == 'number': + return ToolParameter.ToolParameterType.NUMBER + elif typ == 'boolean': + return ToolParameter.ToolParameterType.BOOLEAN + elif typ == 'string': + return ToolParameter.ToolParameterType.STRING + @staticmethod def parse_openapi_yaml_to_tool_bundle(yaml: str, extra_info: dict = None, warning: dict = None) -> list[ApiBasedToolBundle]: """