Spaces:
Runtime error
Runtime error
| from typing import Any, Dict, List, Optional | |
| from openai.types.chat.completion_create_params import Function | |
| from pydantic import BaseModel | |
| from api.utils.compat import model_dump | |
| def convert_data_type(param_type: str) -> str: | |
| """ convert data_type to typescript data type """ | |
| return "number" if param_type in {"integer", "float"} else param_type | |
| def get_param_type(param: Dict[str, Any]) -> str: | |
| """ get param_type of parameter """ | |
| param_type = "any" | |
| if "type" in param: | |
| raw_param_type = param["type"] | |
| param_type = ( | |
| " | ".join(raw_param_type) | |
| if type(raw_param_type) is list | |
| else raw_param_type | |
| ) | |
| elif "oneOf" in param: | |
| one_of_types = [ | |
| convert_data_type(item["type"]) | |
| for item in param["oneOf"] | |
| if "type" in item | |
| ] | |
| one_of_types = list(set(one_of_types)) | |
| param_type = " | ".join(one_of_types) | |
| return convert_data_type(param_type) | |
| def get_format_param(param: Dict[str, Any]) -> Optional[str]: | |
| """ Get "format" from param. There are cases where format is not directly in param but in oneOf """ | |
| if "format" in param: | |
| return param["format"] | |
| if "oneOf" in param: | |
| formats = [item["format"] for item in param["oneOf"] if "format" in item] | |
| if formats: | |
| return " or ".join(formats) | |
| return None | |
| def get_param_info(param: Dict[str, Any]) -> Optional[str]: | |
| """ get additional information about parameter such as: format, default value, min, max, ... """ | |
| param_type = param.get("type", "any") | |
| info_list = [] | |
| if "description" in param: | |
| desc = param["description"] | |
| if not desc.endswith("."): | |
| desc += "." | |
| info_list.append(desc) | |
| if "default" in param: | |
| default_value = param["default"] | |
| if param_type == "string": | |
| default_value = f'"{default_value}"' # if string --> add "" | |
| info_list.append(f"Default={default_value}.") | |
| format_param = get_format_param(param) | |
| if format_param is not None: | |
| info_list.append(f"Format={format_param}") | |
| info_list.extend( | |
| f"{field_name}={str(param[field])}" | |
| for field, field_name in [ | |
| ("maximum", "Maximum"), | |
| ("minimum", "Minimum"), | |
| ("maxLength", "Maximum length"), | |
| ("minLength", "Minimum length"), | |
| ] | |
| if field in param | |
| ) | |
| if info_list: | |
| result = "// " + " ".join(info_list) | |
| return result.replace("\n", " ") | |
| return None | |
| def append_new_param_info(info_list: List[str], param_declaration: str, comment_info: Optional[str], depth: int): | |
| """ Append a new parameter with comment to the info_list """ | |
| offset = "".join([" " for _ in range(depth)]) if depth >= 1 else "" | |
| if comment_info is not None: | |
| # if depth == 0: # format: //comment\nparam: type | |
| info_list.append(f"{offset}{comment_info}") | |
| info_list.append(f"{offset}{param_declaration}") | |
| def get_enum_option_str(enum_options: List) -> str: | |
| """get enum option separated by: "|" | |
| Args: | |
| enum_options (List): list of options | |
| Returns: | |
| _type_: concatenation of options separated by "|" | |
| """ | |
| # if each option is string --> add quote | |
| return " | ".join([f'"{v}"' if type(v) is str else str(v) for v in enum_options]) | |
| def get_array_typescript(param_name: Optional[str], param_dic: dict, depth: int = 0) -> str: | |
| """recursive implementation for generating type script of array | |
| Args: | |
| param_name (Optional[str]): name of param, optional | |
| param_dic (dict): param_dic | |
| depth (int, optional): nested level. Defaults to 0. | |
| Returns: | |
| _type_: typescript of array | |
| """ | |
| offset = "".join([" " for _ in range(depth)]) if depth >= 1 else "" | |
| items_info = param_dic.get("items", {}) | |
| if len(items_info) == 0: | |
| return f"{offset}{param_name}: []" if param_name is not None else "[]" | |
| array_type = get_param_type(items_info) | |
| if array_type == "object": | |
| info_lines = [] | |
| child_lines = get_parameter_typescript( | |
| items_info.get("properties", {}), items_info.get("required", []), depth + 1 | |
| ) | |
| # if comment_info is not None: | |
| # info_lines.append(f"{offset}{comment_info}") | |
| if param_name is not None: | |
| info_lines.append(f"{offset}{param_name}" + ": {") | |
| else: | |
| info_lines.append(f"{offset}" + "{") | |
| info_lines.extend(child_lines) | |
| info_lines.append(f"{offset}" + "}[]") | |
| return "\n".join(info_lines) | |
| elif array_type == "array": | |
| item_info = get_array_typescript(None, items_info, depth + 1) | |
| if param_name is None: | |
| return f"{item_info}[]" | |
| return f"{offset}{param_name}: {item_info.strip()}[]" | |
| else: | |
| if "enum" not in items_info: | |
| return ( | |
| f"{array_type}[]" | |
| if param_name is None | |
| else f"{offset}{param_name}: {array_type}[]," | |
| ) | |
| item_type = get_enum_option_str(items_info["enum"]) | |
| if param_name is None: | |
| return f"({item_type})[]" | |
| else: | |
| return f"{offset}{param_name}: ({item_type})[]" | |
| def get_parameter_typescript(properties, required_params, depth=0) -> List[str]: | |
| """Recursion, returning the information about parameters including data type, description and other information | |
| These kinds of information will be put into the prompt | |
| Args: | |
| properties (_type_): properties in parameters | |
| required_params (_type_): List of required parameters | |
| depth (int, optional): the depth of params (nested level). Defaults to 0. | |
| Returns: | |
| _type_: list of lines containing information about all parameters | |
| """ | |
| tp_lines = [] | |
| for param_name, param in properties.items(): | |
| # Sometimes properties have "required" field as a list of string. | |
| # Even though it is supposed to be not under properties. So we skip it | |
| if not isinstance(param, dict): | |
| continue | |
| # Param Description | |
| comment_info = get_param_info(param) | |
| # Param Name declaration | |
| param_declaration = f"{param_name}" | |
| if isinstance(required_params, list) and param_name not in required_params: | |
| param_declaration += "?" | |
| param_type = get_param_type(param) | |
| offset = "" | |
| if depth >= 1: | |
| offset = "".join([" " for _ in range(depth)]) | |
| if param_type == "object": # param_type is object | |
| child_lines = get_parameter_typescript(param.get("properties", {}), param.get("required", []), depth + 1) | |
| if comment_info is not None: | |
| tp_lines.append(f"{offset}{comment_info}") | |
| param_declaration += ": {" | |
| tp_lines.append(f"{offset}{param_declaration}") | |
| tp_lines.extend(child_lines) | |
| tp_lines.append(f"{offset}" + "},") | |
| elif param_type == "array": # param_type is an array | |
| item_info = param.get("items", {}) | |
| if "type" not in item_info: # don't know type of array | |
| param_declaration += ": []," | |
| append_new_param_info(tp_lines, param_declaration, comment_info, depth) | |
| else: | |
| array_declaration = get_array_typescript(param_declaration, param, depth) | |
| if not array_declaration.endswith(","): | |
| array_declaration += "," | |
| if comment_info is not None: | |
| tp_lines.append(f"{offset}{comment_info}") | |
| tp_lines.append(array_declaration) | |
| else: | |
| if "enum" in param: | |
| param_type = " | ".join([f'"{v}"' for v in param["enum"]]) | |
| param_declaration += f": {param_type}," | |
| append_new_param_info(tp_lines, param_declaration, comment_info, depth) | |
| return tp_lines | |
| def generate_schema_from_functions(functions: List[Function], namespace="functions") -> str: | |
| """ | |
| Convert functions schema to a schema that language models can understand. | |
| """ | |
| schema = "// Supported function definitions that should be called when necessary.\n" | |
| schema += f"namespace {namespace} {{\n\n" | |
| for function in functions: | |
| # Convert a Function object to dict, if necessary | |
| if isinstance(function, BaseModel): | |
| function = model_dump(function) | |
| function_name = function.get("name", None) | |
| if function_name is None: | |
| continue | |
| description = function.get("description", "") | |
| schema += f"// {description}\n" | |
| schema += f"type {function_name}" | |
| parameters = function.get("parameters", None) | |
| if parameters is not None and parameters.get("properties") is not None: | |
| schema += " = (_: {\n" | |
| required_params = parameters.get("required", []) | |
| tp_lines = get_parameter_typescript(parameters.get("properties"), required_params, 0) | |
| schema += "\n".join(tp_lines) | |
| schema += "\n}) => any;\n\n" | |
| else: | |
| # Doesn't have any parameters | |
| schema += " = () => any;\n\n" | |
| schema += f"}} // namespace {namespace}" | |
| return schema | |
| def generate_schema_from_openapi(specification: Dict[str, Any], description: str, namespace: str) -> str: | |
| """ | |
| Convert OpenAPI specification object to a schema that language models can understand. | |
| Input: | |
| specification: can be obtained by json. loads of any OpanAPI json spec, or yaml.safe_load for yaml OpenAPI specs | |
| Example output: | |
| // General Description | |
| namespace functions { | |
| // Simple GET endpoint | |
| type getEndpoint = (_: { | |
| // This is a string parameter | |
| param_string: string, | |
| param_integer: number, | |
| param_boolean?: boolean, | |
| param_enum: "value1" | "value2" | "value3", | |
| }) => any; | |
| } // namespace functions | |
| """ | |
| description_clean = description.replace("\n", "") | |
| schema = f"// {description_clean}\n" | |
| schema += f"namespace {namespace} {{\n\n" | |
| for path_name, paths in specification.get("paths", {}).items(): | |
| for method_name, method_info in paths.items(): | |
| operationId = method_info.get("operationId", None) | |
| if operationId is None: | |
| continue | |
| description = method_info.get("description", method_info.get("summary", "")) | |
| schema += f"// {description}\n" | |
| schema += f"type {operationId}" | |
| if ("requestBody" in method_info) or (method_info.get("parameters") is not None): | |
| schema += f" = (_: {{\n" | |
| # Body | |
| if "requestBody" in method_info: | |
| try: | |
| body_schema = ( | |
| method_info.get("requestBody", {}) | |
| .get("content", {}) | |
| .get("application/json", {}) | |
| .get("schema", {}) | |
| ) | |
| except AttributeError: | |
| body_schema = {} | |
| for param_name, param in body_schema.get("properties", {}).items(): | |
| # Param Description | |
| description = param.get("description") | |
| if description is not None: | |
| schema += f"// {description}\n" | |
| # Param Name | |
| schema += f"{param_name}" | |
| if ( | |
| (not param.get("required", False)) | |
| or (param.get("nullable", False)) | |
| or (param_name in body_schema.get("required", [])) | |
| ): | |
| schema += "?" | |
| # Param Type | |
| param_type = param.get("type", "any") | |
| if param_type == "integer": | |
| param_type = "number" | |
| if "enum" in param: | |
| param_type = " | ".join([f'"{v}"' for v in param["enum"]]) | |
| schema += f": {param_type},\n" | |
| # URL | |
| for param in method_info.get("parameters", []): | |
| # Param Description | |
| if description := param.get("description"): | |
| schema += f"// {description}\n" | |
| # Param Name | |
| schema += f"{param['name']}" | |
| if (not param.get("required", False)) or (param.get("nullable", False)): | |
| schema += "?" | |
| if param.get("schema") is None: | |
| continue | |
| # Param Type | |
| param_type = param["schema"].get("type", "any") | |
| if param_type == "integer": | |
| param_type = "number" | |
| if "enum" in param["schema"]: | |
| param_type = " | ".join([f'"{v}"' for v in param["schema"]["enum"]]) | |
| schema += f": {param_type},\n" | |
| schema += f"}}) => any;\n\n" | |
| else: | |
| # Doesn't have any parameters | |
| schema += f" = () => any;\n\n" | |
| schema += f"}} // namespace {namespace}" | |
| return schema | |
| if __name__ == "__main__": | |
| functions = [ | |
| { | |
| "name": "get_current_weather", | |
| "description": "Get the current weather in a given location", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "location": { | |
| "type": "string", | |
| "description": "The city and state, e.g. San Francisco, CA", | |
| }, | |
| "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, | |
| }, | |
| "required": ["location"], | |
| }, | |
| } | |
| ] | |
| print(generate_schema_from_functions(functions)) | |