From fc243982e56b2600f89918bffa9dfa3cd435fcd5 Mon Sep 17 00:00:00 2001 From: takatost Date: Fri, 23 Feb 2024 14:58:03 +0800 Subject: [PATCH] add api extension to http request node convert --- api/core/features/external_data_fetch.py | 7 - api/services/workflow/workflow_converter.py | 149 ++++++++++++++++++-- 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/api/core/features/external_data_fetch.py b/api/core/features/external_data_fetch.py index 7f23c8ed72..ef37f05528 100644 --- a/api/core/features/external_data_fetch.py +++ b/api/core/features/external_data_fetch.py @@ -1,5 +1,4 @@ import concurrent -import json import logging from concurrent.futures import ThreadPoolExecutor from typing import Optional @@ -28,12 +27,6 @@ class ExternalDataFetchFeature: :param query: the query :return: the filled inputs """ - # Group tools by type and config - grouped_tools = {} - for tool in external_data_tools: - tool_key = (tool.type, json.dumps(tool.config, sort_keys=True)) - grouped_tools.setdefault(tool_key, []).append(tool) - results = {} with ThreadPoolExecutor() as executor: futures = {} diff --git a/api/services/workflow/workflow_converter.py b/api/services/workflow/workflow_converter.py index 647713b404..1fb37afe01 100644 --- a/api/services/workflow/workflow_converter.py +++ b/api/services/workflow/workflow_converter.py @@ -11,6 +11,7 @@ from core.entities.application_entities import ( PromptTemplateEntity, VariableEntity, ) +from core.helper import encrypter from core.model_runtime.entities.llm_entities import LLMMode from core.model_runtime.utils import helper from core.prompt.simple_prompt_transform import SimplePromptTransform @@ -18,6 +19,7 @@ from core.workflow.entities.NodeEntities import NodeType from core.workflow.nodes.end.entities import EndNodeOutputType from extensions.ext_database import db from models.account import Account +from models.api_based_extension import APIBasedExtension, APIBasedExtensionPoint from models.model import App, AppMode, ChatbotAppEngine from models.workflow import Workflow, WorkflowType @@ -49,7 +51,7 @@ class WorkflowConverter: # convert app model config application_manager = ApplicationManager() - application_manager.convert_from_app_model_config_dict( + app_orchestration_config_entity = application_manager.convert_from_app_model_config_dict( tenant_id=app_model.tenant_id, app_model_config_dict=app_model_config.to_dict() ) @@ -71,24 +73,27 @@ class WorkflowConverter: # convert to start node start_node = self._convert_to_start_node( - variables=app_model_config.variables + variables=app_orchestration_config_entity.variables ) graph['nodes'].append(start_node) # convert to http request node - if app_model_config.external_data_variables: - http_request_node = self._convert_to_http_request_node( - external_data_variables=app_model_config.external_data_variables + if app_orchestration_config_entity.external_data_variables: + http_request_nodes = self._convert_to_http_request_node( + app_model=app_model, + variables=app_orchestration_config_entity.variables, + external_data_variables=app_orchestration_config_entity.external_data_variables ) - graph = self._append_node(graph, http_request_node) + for http_request_node in http_request_nodes: + graph = self._append_node(graph, http_request_node) # convert to knowledge retrieval node - if app_model_config.dataset: + if app_orchestration_config_entity.dataset: knowledge_retrieval_node = self._convert_to_knowledge_retrieval_node( new_app_mode=new_app_mode, - dataset_config=app_model_config.dataset + dataset_config=app_orchestration_config_entity.dataset ) if knowledge_retrieval_node: @@ -98,9 +103,9 @@ class WorkflowConverter: llm_node = self._convert_to_llm_node( new_app_mode=new_app_mode, graph=graph, - model_config=app_model_config.model_config, - prompt_template=app_model_config.prompt_template, - file_upload=app_model_config.file_upload + model_config=app_orchestration_config_entity.model_config, + prompt_template=app_orchestration_config_entity.prompt_template, + file_upload=app_orchestration_config_entity.file_upload ) graph = self._append_node(graph, llm_node) @@ -160,14 +165,130 @@ class WorkflowConverter: } } - def _convert_to_http_request_node(self, external_data_variables: list[ExternalDataVariableEntity]) -> dict: + def _convert_to_http_request_node(self, app_model: App, + variables: list[VariableEntity], + external_data_variables: list[ExternalDataVariableEntity]) -> list[dict]: """ Convert API Based Extension to HTTP Request Node + :param app_model: App instance + :param variables: list of variables :param external_data_variables: list of external data variables :return: """ - # TODO: implement - pass + index = 1 + nodes = [] + tenant_id = app_model.tenant_id + for external_data_variable in external_data_variables: + tool_type = external_data_variable.type + if tool_type != "api": + continue + + tool_variable = external_data_variable.variable + tool_config = external_data_variable.config + + # get params from config + api_based_extension_id = tool_config.get("api_based_extension_id") + + # get api_based_extension + api_based_extension = db.session.query(APIBasedExtension).filter( + APIBasedExtension.tenant_id == tenant_id, + APIBasedExtension.id == api_based_extension_id + ).first() + + if not api_based_extension: + raise ValueError("[External data tool] API query failed, variable: {}, " + "error: api_based_extension_id is invalid" + .format(tool_variable)) + + # decrypt api_key + api_key = encrypter.decrypt_token( + tenant_id=tenant_id, + token=api_based_extension.api_key + ) + + http_request_variables = [] + inputs = {} + for v in variables: + http_request_variables.append({ + "variable": v.variable, + "value_selector": ["start", v.variable] + }) + + inputs[v.variable] = '{{' + v.variable + '}}' + + if app_model.mode == AppMode.CHAT.value: + http_request_variables.append({ + "variable": "_query", + "value_selector": ["start", "sys.query"] + }) + + request_body = { + 'point': APIBasedExtensionPoint.APP_EXTERNAL_DATA_TOOL_QUERY.value, + 'params': { + 'app_id': app_model.id, + 'tool_variable': tool_variable, + 'inputs': inputs, + 'query': '{{_query}}' if app_model.mode == AppMode.CHAT.value else '' + } + } + + request_body_json = json.dumps(request_body) + request_body_json = request_body_json.replace('\{\{', '{{').replace('\}\}', '}}') + + http_request_node = { + "id": f"http-request-{index}", + "position": None, + "data": { + "title": f"HTTP REQUEST {api_based_extension.name}", + "type": NodeType.HTTP_REQUEST.value, + "variables": http_request_variables, + "method": "post", + "url": api_based_extension.api_endpoint, + "authorization": { + "type": "api-key", + "config": { + "type": "bearer", + "api_key": api_key + } + }, + "headers": "", + "params": "", + "body": { + "type": "json", + "data": request_body_json + } + } + } + index += 1 + + nodes.append(http_request_node) + + # append code node for response body parsing + code_node = { + "id": f"code-{index}", + "position": None, + "data": { + "title": f"Parse {api_based_extension.name} response", + "type": NodeType.CODE.value, + "variables": [{ + "variable": "response_json", + "value_selector": [http_request_node['id'], "body"] + }], + "code_language": "python3", + "code": "import json\n\ndef main(response_json: str) -> str:\n response_body = json.loads(" + "response_json)\n return {\n \"result\": response_body[\"result\"]\n }", + "outputs": [ + { + "variable": "result", + "variable_type": "string" + } + ] + } + } + + nodes.append(code_node) + + return nodes def _convert_to_knowledge_retrieval_node(self, new_app_mode: AppMode, dataset_config: DatasetEntity) \ -> Optional[dict]: