浏览代码

fix(api/services/workflow/workflow_converter.py): Add NoneType checkers & format file. (#7446)

tags/0.7.2
-LAN- 1年前
父节点
当前提交
5e42e90abc
没有帐户链接到提交者的电子邮件
共有 1 个文件被更改,包括 127 次插入192 次删除
  1. 127
    192
      api/services/workflow/workflow_converter.py

+ 127
- 192
api/services/workflow/workflow_converter.py 查看文件

App Convert to Workflow Mode App Convert to Workflow Mode
""" """


def convert_to_workflow(self, app_model: App,
account: Account,
name: str,
icon_type: str,
icon: str,
icon_background: str) -> App:
def convert_to_workflow(
self, app_model: App, account: Account, name: str, icon_type: str, icon: str, icon_background: str
):
""" """
Convert app to workflow Convert app to workflow


:return: new App instance :return: new App instance
""" """
# convert app model config # convert app model config
if not app_model.app_model_config:
raise ValueError("App model config is required")

workflow = self.convert_app_model_config_to_workflow( workflow = self.convert_app_model_config_to_workflow(
app_model=app_model,
app_model_config=app_model.app_model_config,
account_id=account.id
app_model=app_model, app_model_config=app_model.app_model_config, account_id=account.id
) )


# create new app # create new app
new_app = App() new_app = App()
new_app.tenant_id = app_model.tenant_id new_app.tenant_id = app_model.tenant_id
new_app.name = name if name else app_model.name + '(workflow)'
new_app.mode = AppMode.ADVANCED_CHAT.value \
if app_model.mode == AppMode.CHAT.value else AppMode.WORKFLOW.value
new_app.name = name if name else app_model.name + "(workflow)"
new_app.mode = AppMode.ADVANCED_CHAT.value if app_model.mode == AppMode.CHAT.value else AppMode.WORKFLOW.value
new_app.icon_type = icon_type if icon_type else app_model.icon_type new_app.icon_type = icon_type if icon_type else app_model.icon_type
new_app.icon = icon if icon else app_model.icon new_app.icon = icon if icon else app_model.icon
new_app.icon_background = icon_background if icon_background else app_model.icon_background new_app.icon_background = icon_background if icon_background else app_model.icon_background


return new_app return new_app


def convert_app_model_config_to_workflow(self, app_model: App,
app_model_config: AppModelConfig,
account_id: str) -> Workflow:
def convert_app_model_config_to_workflow(self, app_model: App, app_model_config: AppModelConfig, account_id: str):
""" """
Convert app model config to workflow mode Convert app model config to workflow mode
:param app_model: App instance :param app_model: App instance
:param app_model_config: AppModelConfig instance :param app_model_config: AppModelConfig instance
:param account_id: Account ID :param account_id: Account ID
:return:
""" """
# get new app mode # get new app mode
new_app_mode = self._get_new_app_mode(app_model) new_app_mode = self._get_new_app_mode(app_model)


# convert app model config # convert app model config
app_config = self._convert_to_app_config(
app_model=app_model,
app_model_config=app_model_config
)
app_config = self._convert_to_app_config(app_model=app_model, app_model_config=app_model_config)


# init workflow graph # init workflow graph
graph = {
"nodes": [],
"edges": []
}
graph = {"nodes": [], "edges": []}


# Convert list: # Convert list:
# - variables -> start # - variables -> start
# - show_retrieve_source -> knowledge-retrieval # - show_retrieve_source -> knowledge-retrieval


# convert to start node # convert to start node
start_node = self._convert_to_start_node(
variables=app_config.variables
)
start_node = self._convert_to_start_node(variables=app_config.variables)


graph['nodes'].append(start_node)
graph["nodes"].append(start_node)


# convert to http request node # convert to http request node
external_data_variable_node_mapping = {} external_data_variable_node_mapping = {}
http_request_nodes, external_data_variable_node_mapping = self._convert_to_http_request_node( http_request_nodes, external_data_variable_node_mapping = self._convert_to_http_request_node(
app_model=app_model, app_model=app_model,
variables=app_config.variables, variables=app_config.variables,
external_data_variables=app_config.external_data_variables
external_data_variables=app_config.external_data_variables,
) )


for http_request_node in http_request_nodes: for http_request_node in http_request_nodes:
# convert to knowledge retrieval node # convert to knowledge retrieval node
if app_config.dataset: if app_config.dataset:
knowledge_retrieval_node = self._convert_to_knowledge_retrieval_node( knowledge_retrieval_node = self._convert_to_knowledge_retrieval_node(
new_app_mode=new_app_mode,
dataset_config=app_config.dataset,
model_config=app_config.model
new_app_mode=new_app_mode, dataset_config=app_config.dataset, model_config=app_config.model
) )


if knowledge_retrieval_node: if knowledge_retrieval_node:
model_config=app_config.model, model_config=app_config.model,
prompt_template=app_config.prompt_template, prompt_template=app_config.prompt_template,
file_upload=app_config.additional_features.file_upload, file_upload=app_config.additional_features.file_upload,
external_data_variable_node_mapping=external_data_variable_node_mapping
external_data_variable_node_mapping=external_data_variable_node_mapping,
) )


graph = self._append_node(graph, llm_node) graph = self._append_node(graph, llm_node)
tenant_id=app_model.tenant_id, tenant_id=app_model.tenant_id,
app_id=app_model.id, app_id=app_model.id,
type=WorkflowType.from_app_mode(new_app_mode).value, type=WorkflowType.from_app_mode(new_app_mode).value,
version='draft',
version="draft",
graph=json.dumps(graph), graph=json.dumps(graph),
features=json.dumps(features), features=json.dumps(features),
created_by=account_id, created_by=account_id,


return workflow return workflow


def _convert_to_app_config(self, app_model: App,
app_model_config: AppModelConfig) -> EasyUIBasedAppConfig:
def _convert_to_app_config(self, app_model: App, app_model_config: AppModelConfig) -> EasyUIBasedAppConfig:
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
if app_mode == AppMode.AGENT_CHAT or app_model.is_agent: if app_mode == AppMode.AGENT_CHAT or app_model.is_agent:
app_model.mode = AppMode.AGENT_CHAT.value app_model.mode = AppMode.AGENT_CHAT.value
app_config = AgentChatAppConfigManager.get_app_config( app_config = AgentChatAppConfigManager.get_app_config(
app_model=app_model,
app_model_config=app_model_config
app_model=app_model, app_model_config=app_model_config
) )
elif app_mode == AppMode.CHAT: elif app_mode == AppMode.CHAT:
app_config = ChatAppConfigManager.get_app_config(
app_model=app_model,
app_model_config=app_model_config
)
app_config = ChatAppConfigManager.get_app_config(app_model=app_model, app_model_config=app_model_config)
elif app_mode == AppMode.COMPLETION: elif app_mode == AppMode.COMPLETION:
app_config = CompletionAppConfigManager.get_app_config( app_config = CompletionAppConfigManager.get_app_config(
app_model=app_model,
app_model_config=app_model_config
app_model=app_model, app_model_config=app_model_config
) )
else: else:
raise ValueError("Invalid app mode") raise ValueError("Invalid app mode")
"data": { "data": {
"title": "START", "title": "START",
"type": NodeType.START.value, "type": NodeType.START.value,
"variables": [jsonable_encoder(v) for v in variables]
}
"variables": [jsonable_encoder(v) for v in variables],
},
} }


def _convert_to_http_request_node(self, app_model: App,
variables: list[VariableEntity],
external_data_variables: list[ExternalDataVariableEntity]) \
-> tuple[list[dict], dict[str, str]]:
def _convert_to_http_request_node(
self, app_model: App, variables: list[VariableEntity], external_data_variables: list[ExternalDataVariableEntity]
) -> tuple[list[dict], dict[str, str]]:
""" """
Convert API Based Extension to HTTP Request Node Convert API Based Extension to HTTP Request Node
:param app_model: App instance :param app_model: App instance


# get params from config # get params from config
api_based_extension_id = tool_config.get("api_based_extension_id") api_based_extension_id = tool_config.get("api_based_extension_id")
if not api_based_extension_id:
continue


# get api_based_extension # get api_based_extension
api_based_extension = self._get_api_based_extension( api_based_extension = self._get_api_based_extension(
tenant_id=tenant_id,
api_based_extension_id=api_based_extension_id
tenant_id=tenant_id, api_based_extension_id=api_based_extension_id
) )


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 # decrypt api_key
api_key = encrypter.decrypt_token(
tenant_id=tenant_id,
token=api_based_extension.api_key
)
api_key = encrypter.decrypt_token(tenant_id=tenant_id, token=api_based_extension.api_key)


inputs = {} inputs = {}
for v in variables: for v in variables:
inputs[v.variable] = '{{#start.' + v.variable + '#}}'
inputs[v.variable] = "{{#start." + v.variable + "#}}"


request_body = { request_body = {
'point': APIBasedExtensionPoint.APP_EXTERNAL_DATA_TOOL_QUERY.value,
'params': {
'app_id': app_model.id,
'tool_variable': tool_variable,
'inputs': inputs,
'query': '{{#sys.query#}}' if app_model.mode == AppMode.CHAT.value else ''
}
"point": APIBasedExtensionPoint.APP_EXTERNAL_DATA_TOOL_QUERY.value,
"params": {
"app_id": app_model.id,
"tool_variable": tool_variable,
"inputs": inputs,
"query": "{{#sys.query#}}" if app_model.mode == AppMode.CHAT.value else "",
},
} }


request_body_json = json.dumps(request_body) request_body_json = json.dumps(request_body)
request_body_json = request_body_json.replace(r'\{\{', '{{').replace(r'\}\}', '}}')
request_body_json = request_body_json.replace(r"\{\{", "{{").replace(r"\}\}", "}}")


http_request_node = { http_request_node = {
"id": f"http_request_{index}", "id": f"http_request_{index}",
"type": NodeType.HTTP_REQUEST.value, "type": NodeType.HTTP_REQUEST.value,
"method": "post", "method": "post",
"url": api_based_extension.api_endpoint, "url": api_based_extension.api_endpoint,
"authorization": {
"type": "api-key",
"config": {
"type": "bearer",
"api_key": api_key
}
},
"authorization": {"type": "api-key", "config": {"type": "bearer", "api_key": api_key}},
"headers": "", "headers": "",
"params": "", "params": "",
"body": {
"type": "json",
"data": request_body_json
}
}
"body": {"type": "json", "data": request_body_json},
},
} }


nodes.append(http_request_node) nodes.append(http_request_node)
"data": { "data": {
"title": f"Parse {api_based_extension.name} Response", "title": f"Parse {api_based_extension.name} Response",
"type": NodeType.CODE.value, "type": NodeType.CODE.value,
"variables": [{
"variable": "response_json",
"value_selector": [http_request_node['id'], "body"]
}],
"variables": [{"variable": "response_json", "value_selector": [http_request_node["id"], "body"]}],
"code_language": "python3", "code_language": "python3",
"code": "import json\n\ndef main(response_json: str) -> str:\n response_body = json.loads(" "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": {
"result": {
"type": "string"
}
}
}
'response_json)\n return {\n "result": response_body["result"]\n }',
"outputs": {"result": {"type": "string"}},
},
} }


nodes.append(code_node) nodes.append(code_node)


external_data_variable_node_mapping[external_data_variable.variable] = code_node['id']
external_data_variable_node_mapping[external_data_variable.variable] = code_node["id"]
index += 1 index += 1


return nodes, external_data_variable_node_mapping return nodes, external_data_variable_node_mapping


def _convert_to_knowledge_retrieval_node(self, new_app_mode: AppMode,
dataset_config: DatasetEntity,
model_config: ModelConfigEntity) \
-> Optional[dict]:
def _convert_to_knowledge_retrieval_node(
self, new_app_mode: AppMode, dataset_config: DatasetEntity, model_config: ModelConfigEntity
) -> Optional[dict]:
""" """
Convert datasets to Knowledge Retrieval Node Convert datasets to Knowledge Retrieval Node
:param new_app_mode: new app mode :param new_app_mode: new app mode
"completion_params": { "completion_params": {
**model_config.parameters, **model_config.parameters,
"stop": model_config.stop, "stop": model_config.stop,
}
},
} }
} }
if retrieve_config.retrieve_strategy == DatasetRetrieveConfigEntity.RetrieveStrategy.SINGLE if retrieve_config.retrieve_strategy == DatasetRetrieveConfigEntity.RetrieveStrategy.SINGLE
"multiple_retrieval_config": { "multiple_retrieval_config": {
"top_k": retrieve_config.top_k, "top_k": retrieve_config.top_k,
"score_threshold": retrieve_config.score_threshold, "score_threshold": retrieve_config.score_threshold,
"reranking_model": retrieve_config.reranking_model
"reranking_model": retrieve_config.reranking_model,
} }
if retrieve_config.retrieve_strategy == DatasetRetrieveConfigEntity.RetrieveStrategy.MULTIPLE if retrieve_config.retrieve_strategy == DatasetRetrieveConfigEntity.RetrieveStrategy.MULTIPLE
else None, else None,
}
},
} }


def _convert_to_llm_node(self, original_app_mode: AppMode,
new_app_mode: AppMode,
graph: dict,
model_config: ModelConfigEntity,
prompt_template: PromptTemplateEntity,
file_upload: Optional[FileExtraConfig] = None,
external_data_variable_node_mapping: dict[str, str] = None) -> dict:
def _convert_to_llm_node(
self,
original_app_mode: AppMode,
new_app_mode: AppMode,
graph: dict,
model_config: ModelConfigEntity,
prompt_template: PromptTemplateEntity,
file_upload: Optional[FileExtraConfig] = None,
external_data_variable_node_mapping: dict[str, str] | None = None,
) -> dict:
""" """
Convert to LLM Node Convert to LLM Node
:param original_app_mode: original app mode :param original_app_mode: original app mode
:param external_data_variable_node_mapping: external data variable node mapping :param external_data_variable_node_mapping: external data variable node mapping
""" """
# fetch start and knowledge retrieval node # fetch start and knowledge retrieval node
start_node = next(filter(lambda n: n['data']['type'] == NodeType.START.value, graph['nodes']))
knowledge_retrieval_node = next(filter(
lambda n: n['data']['type'] == NodeType.KNOWLEDGE_RETRIEVAL.value,
graph['nodes']
), None)
start_node = next(filter(lambda n: n["data"]["type"] == NodeType.START.value, graph["nodes"]))
knowledge_retrieval_node = next(
filter(lambda n: n["data"]["type"] == NodeType.KNOWLEDGE_RETRIEVAL.value, graph["nodes"]), None
)


role_prefix = None role_prefix = None


# Chat Model # Chat Model
if model_config.mode == LLMMode.CHAT.value: if model_config.mode == LLMMode.CHAT.value:
if prompt_template.prompt_type == PromptTemplateEntity.PromptType.SIMPLE: if prompt_template.prompt_type == PromptTemplateEntity.PromptType.SIMPLE:
if not prompt_template.simple_prompt_template:
raise ValueError("Simple prompt template is required")
# get prompt template # get prompt template
prompt_transform = SimplePromptTransform() prompt_transform = SimplePromptTransform()
prompt_template_config = prompt_transform.get_prompt_template( prompt_template_config = prompt_transform.get_prompt_template(
model=model_config.model, model=model_config.model,
pre_prompt=prompt_template.simple_prompt_template, pre_prompt=prompt_template.simple_prompt_template,
has_context=knowledge_retrieval_node is not None, has_context=knowledge_retrieval_node is not None,
query_in_prompt=False
query_in_prompt=False,
) )


template = prompt_template_config['prompt_template'].template
template = prompt_template_config["prompt_template"].template
if not template: if not template:
prompts = [] prompts = []
else: else:
template = self._replace_template_variables( template = self._replace_template_variables(
template,
start_node['data']['variables'],
external_data_variable_node_mapping
template, start_node["data"]["variables"], external_data_variable_node_mapping
) )


prompts = [
{
"role": 'user',
"text": template
}
]
prompts = [{"role": "user", "text": template}]
else: else:
advanced_chat_prompt_template = prompt_template.advanced_chat_prompt_template advanced_chat_prompt_template = prompt_template.advanced_chat_prompt_template


prompts = [] prompts = []
for m in advanced_chat_prompt_template.messages:
if advanced_chat_prompt_template:
if advanced_chat_prompt_template:
for m in advanced_chat_prompt_template.messages:
text = m.text text = m.text
text = self._replace_template_variables( text = self._replace_template_variables(
text,
start_node['data']['variables'],
external_data_variable_node_mapping
text, start_node["data"]["variables"], external_data_variable_node_mapping
) )


prompts.append({
"role": m.role.value,
"text": text
})
prompts.append({"role": m.role.value, "text": text})
# Completion Model # Completion Model
else: else:
if prompt_template.prompt_type == PromptTemplateEntity.PromptType.SIMPLE: if prompt_template.prompt_type == PromptTemplateEntity.PromptType.SIMPLE:
if not prompt_template.simple_prompt_template:
raise ValueError("Simple prompt template is required")
# get prompt template # get prompt template
prompt_transform = SimplePromptTransform() prompt_transform = SimplePromptTransform()
prompt_template_config = prompt_transform.get_prompt_template( prompt_template_config = prompt_transform.get_prompt_template(
model=model_config.model, model=model_config.model,
pre_prompt=prompt_template.simple_prompt_template, pre_prompt=prompt_template.simple_prompt_template,
has_context=knowledge_retrieval_node is not None, has_context=knowledge_retrieval_node is not None,
query_in_prompt=False
query_in_prompt=False,
) )


template = prompt_template_config['prompt_template'].template
template = prompt_template_config["prompt_template"].template
template = self._replace_template_variables( template = self._replace_template_variables(
template,
start_node['data']['variables'],
external_data_variable_node_mapping
template=template,
variables=start_node["data"]["variables"],
external_data_variable_node_mapping=external_data_variable_node_mapping,
) )


prompts = {
"text": template
}
prompts = {"text": template}


prompt_rules = prompt_template_config['prompt_rules']
prompt_rules = prompt_template_config["prompt_rules"]
role_prefix = { role_prefix = {
"user": prompt_rules.get('human_prefix', 'Human'),
"assistant": prompt_rules.get('assistant_prefix', 'Assistant')
"user": prompt_rules.get("human_prefix", "Human"),
"assistant": prompt_rules.get("assistant_prefix", "Assistant"),
} }
else: else:
advanced_completion_prompt_template = prompt_template.advanced_completion_prompt_template advanced_completion_prompt_template = prompt_template.advanced_completion_prompt_template
if advanced_completion_prompt_template: if advanced_completion_prompt_template:
text = advanced_completion_prompt_template.prompt text = advanced_completion_prompt_template.prompt
text = self._replace_template_variables( text = self._replace_template_variables(
text,
start_node['data']['variables'],
external_data_variable_node_mapping
template=text,
variables=start_node["data"]["variables"],
external_data_variable_node_mapping=external_data_variable_node_mapping,
) )
else: else:
text = "" text = ""


text = text.replace('{{#query#}}', '{{#sys.query#}}')
text = text.replace("{{#query#}}", "{{#sys.query#}}")


prompts = { prompts = {
"text": text, "text": text,
} }


if advanced_completion_prompt_template.role_prefix:
if advanced_completion_prompt_template and advanced_completion_prompt_template.role_prefix:
role_prefix = { role_prefix = {
"user": advanced_completion_prompt_template.role_prefix.user, "user": advanced_completion_prompt_template.role_prefix.user,
"assistant": advanced_completion_prompt_template.role_prefix.assistant
"assistant": advanced_completion_prompt_template.role_prefix.assistant,
} }


memory = None memory = None
if new_app_mode == AppMode.ADVANCED_CHAT: if new_app_mode == AppMode.ADVANCED_CHAT:
memory = {
"role_prefix": role_prefix,
"window": {
"enabled": False
}
}
memory = {"role_prefix": role_prefix, "window": {"enabled": False}}


completion_params = model_config.parameters completion_params = model_config.parameters
completion_params.update({"stop": model_config.stop}) completion_params.update({"stop": model_config.stop})
"provider": model_config.provider, "provider": model_config.provider,
"name": model_config.model, "name": model_config.model,
"mode": model_config.mode, "mode": model_config.mode,
"completion_params": completion_params
"completion_params": completion_params,
}, },
"prompt_template": prompts, "prompt_template": prompts,
"memory": memory, "memory": memory,
"context": { "context": {
"enabled": knowledge_retrieval_node is not None, "enabled": knowledge_retrieval_node is not None,
"variable_selector": ["knowledge_retrieval", "result"] "variable_selector": ["knowledge_retrieval", "result"]
if knowledge_retrieval_node is not None else None
if knowledge_retrieval_node is not None
else None,
}, },
"vision": { "vision": {
"enabled": file_upload is not None, "enabled": file_upload is not None,
"variable_selector": ["sys", "files"] if file_upload is not None else None, "variable_selector": ["sys", "files"] if file_upload is not None else None,
"configs": {
"detail": file_upload.image_config['detail']
} if file_upload is not None else None
}
}
"configs": {"detail": file_upload.image_config["detail"]}
if file_upload is not None and file_upload.image_config is not None
else None,
},
},
} }


def _replace_template_variables(self, template: str,
variables: list[dict],
external_data_variable_node_mapping: dict[str, str] = None) -> str:
def _replace_template_variables(
self, template: str, variables: list[dict], external_data_variable_node_mapping: dict[str, str] | None = None
) -> str:
""" """
Replace Template Variables Replace Template Variables
:param template: template :param template: template
:return: :return:
""" """
for v in variables: for v in variables:
template = template.replace('{{' + v['variable'] + '}}', '{{#start.' + v['variable'] + '#}}')
template = template.replace("{{" + v["variable"] + "}}", "{{#start." + v["variable"] + "#}}")


if external_data_variable_node_mapping: if external_data_variable_node_mapping:
for variable, code_node_id in external_data_variable_node_mapping.items(): for variable, code_node_id in external_data_variable_node_mapping.items():
template = template.replace('{{' + variable + '}}',
'{{#' + code_node_id + '.result#}}')
template = template.replace("{{" + variable + "}}", "{{#" + code_node_id + ".result#}}")


return template return template


"data": { "data": {
"title": "END", "title": "END",
"type": NodeType.END.value, "type": NodeType.END.value,
"outputs": [{
"variable": "result",
"value_selector": ["llm", "text"]
}]
}
"outputs": [{"variable": "result", "value_selector": ["llm", "text"]}],
},
} }


def _convert_to_answer_node(self) -> dict: def _convert_to_answer_node(self) -> dict:
return { return {
"id": "answer", "id": "answer",
"position": None, "position": None,
"data": {
"title": "ANSWER",
"type": NodeType.ANSWER.value,
"answer": "{{#llm.text#}}"
}
"data": {"title": "ANSWER", "type": NodeType.ANSWER.value, "answer": "{{#llm.text#}}"},
} }


def _create_edge(self, source: str, target: str) -> dict: def _create_edge(self, source: str, target: str) -> dict:
:param target: target node id :param target: target node id
:return: :return:
""" """
return {
"id": f"{source}-{target}",
"source": source,
"target": target
}
return {"id": f"{source}-{target}", "source": source, "target": target}


def _append_node(self, graph: dict, node: dict) -> dict: def _append_node(self, graph: dict, node: dict) -> dict:
""" """
:param node: Node to append :param node: Node to append
:return: :return:
""" """
previous_node = graph['nodes'][-1]
graph['nodes'].append(node)
graph['edges'].append(self._create_edge(previous_node['id'], node['id']))
previous_node = graph["nodes"][-1]
graph["nodes"].append(node)
graph["edges"].append(self._create_edge(previous_node["id"], node["id"]))
return graph return graph


def _get_new_app_mode(self, app_model: App) -> AppMode: def _get_new_app_mode(self, app_model: App) -> AppMode:
else: else:
return AppMode.ADVANCED_CHAT return AppMode.ADVANCED_CHAT


def _get_api_based_extension(self, tenant_id: str, api_based_extension_id: str) -> APIBasedExtension:
def _get_api_based_extension(self, tenant_id: str, api_based_extension_id: str):
""" """
Get API Based Extension Get API Based Extension
:param tenant_id: tenant id :param tenant_id: tenant id
:param api_based_extension_id: api based extension id :param api_based_extension_id: api based extension id
:return: :return:
""" """
return db.session.query(APIBasedExtension).filter(
APIBasedExtension.tenant_id == tenant_id,
APIBasedExtension.id == api_based_extension_id
).first()
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(f"API Based Extension not found, id: {api_based_extension_id}")

return api_based_extension

正在加载...
取消
保存