From f4a8c7ea56c84b2517e06b11c30c7d4120ca0582 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 Date: Wed, 11 Mar 2026 10:08:31 +0800 Subject: [PATCH 01/12] feat: tool_workflow init --- apps/tools/api/tool_workflow.py | 54 ++ ...er_tool_tool_type_toolworkflow_and_more.py | 53 ++ apps/tools/models/__init__.py | 1 + apps/tools/models/tool.py | 2 +- apps/tools/models/tool_workflow.py | 46 ++ apps/tools/serializers/tool.py | 13 +- apps/tools/serializers/tool_workflow.py | 273 ++++++++ apps/tools/urls.py | 3 + apps/tools/views/__init__.py | 3 +- apps/tools/views/tool_workflow.py | 200 ++++++ ui/src/api/tool/tool.ts | 97 ++- .../workflow-dropdown-menu/index.vue | 3 + .../tool/NodeContent.vue | 90 +++ .../workflow-dropdown-menu/tool/index.vue | 254 +++++++ ui/src/enums/application.ts | 6 + ui/src/router/routes.ts | 6 + ui/src/views/tool-workflow/index.vue | 648 ++++++++++++++++++ ui/src/views/tool/WorkflowFormDialog.vue | 166 +++++ .../tool/component/ToolListContainer.vue | 59 +- ui/src/views/tool/index.vue | 1 + ui/src/workflow/common/NodeCascader.vue | 7 +- ui/src/workflow/common/NodeContainer.vue | 3 +- ui/src/workflow/common/app-node.ts | 14 +- ui/src/workflow/common/data.ts | 125 +++- ui/src/workflow/common/validate.ts | 45 +- ui/src/workflow/icons/output-icon.vue | 4 + ui/src/workflow/icons/tool-base-node-icon.vue | 6 + .../workflow/icons/tool-start-node-icon.vue | 6 + .../component/input/InputFieldFormDialog.vue | 119 ++++ .../component/input/InputFieldTable.vue | 140 ++++ .../component/input/InputTitleDialog.vue | 83 +++ .../output/OutputFieldFormDialog.vue | 114 +++ .../component/output/OutputFieldTable.vue | 128 ++++ .../component/output/OutputTitleDialog.vue | 83 +++ ui/src/workflow/nodes/tool-base-node/index.ts | 12 + .../workflow/nodes/tool-base-node/index.vue | 14 + .../workflow/nodes/tool-start-node/index.ts | 36 + .../workflow/nodes/tool-start-node/index.vue | 44 ++ 38 files changed, 2927 insertions(+), 34 deletions(-) create mode 100644 apps/tools/api/tool_workflow.py create mode 100644 apps/tools/migrations/0007_alter_tool_tool_type_toolworkflow_and_more.py create mode 100644 apps/tools/models/tool_workflow.py create mode 100644 apps/tools/serializers/tool_workflow.py create mode 100644 apps/tools/views/tool_workflow.py create mode 100644 ui/src/components/workflow-dropdown-menu/tool/NodeContent.vue create mode 100644 ui/src/components/workflow-dropdown-menu/tool/index.vue create mode 100644 ui/src/views/tool-workflow/index.vue create mode 100644 ui/src/views/tool/WorkflowFormDialog.vue create mode 100644 ui/src/workflow/icons/output-icon.vue create mode 100644 ui/src/workflow/icons/tool-base-node-icon.vue create mode 100644 ui/src/workflow/icons/tool-start-node-icon.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/component/input/InputFieldFormDialog.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/component/input/InputTitleDialog.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldFormDialog.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/component/output/OutputTitleDialog.vue create mode 100644 ui/src/workflow/nodes/tool-base-node/index.ts create mode 100644 ui/src/workflow/nodes/tool-base-node/index.vue create mode 100644 ui/src/workflow/nodes/tool-start-node/index.ts create mode 100644 ui/src/workflow/nodes/tool-start-node/index.vue diff --git a/apps/tools/api/tool_workflow.py b/apps/tools/api/tool_workflow.py new file mode 100644 index 00000000000..a9cf179377d --- /dev/null +++ b/apps/tools/api/tool_workflow.py @@ -0,0 +1,54 @@ +# coding=utf-8 +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter + +from common.mixins.api_mixin import APIMixin +from common.result import DefaultResultSerializer +from tools.serializers.tool_workflow import ToolWorkflowImportRequest + + +class ToolWorkflowApi(APIMixin): + pass + + +class ToolWorkflowVersionApi(APIMixin): + pass + + +class ToolWorkflowExportApi(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="tool_id", + description="工具id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + ] + + @staticmethod + def get_response(): + return DefaultResultSerializer + + +class ToolWorkflowImportApi(APIMixin): + @staticmethod + def get_parameters(): + return ToolWorkflowExportApi.get_parameters() + + @staticmethod + def get_request(): + return ToolWorkflowImportRequest + + @staticmethod + def get_response(): + return DefaultResultSerializer diff --git a/apps/tools/migrations/0007_alter_tool_tool_type_toolworkflow_and_more.py b/apps/tools/migrations/0007_alter_tool_tool_type_toolworkflow_and_more.py new file mode 100644 index 00000000000..9e0b7fa3507 --- /dev/null +++ b/apps/tools/migrations/0007_alter_tool_tool_type_toolworkflow_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.2.11 on 2026-03-10 10:46 + +import django.db.models.deletion +import uuid_utils.compat +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tools', '0006_alter_tool_tool_type'), + ] + + operations = [ + migrations.AlterField( + model_name='tool', + name='tool_type', + field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('SKILL', '技能'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源'), ('WORKFLOW', 'Workflow')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'), + ), + migrations.CreateModel( + name='ToolWorkflow', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), + ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), + ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), + ('is_publish', models.BooleanField(db_index=True, default=False, verbose_name='是否发布')), + ('publish_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')), + ('tool', models.OneToOneField(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='workflow', to='tools.tool', verbose_name='工具')), + ], + options={ + 'db_table': 'tool_workflow', + }, + ), + migrations.CreateModel( + name='ToolWorkflowVersion', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), + ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), + ('name', models.CharField(default='', max_length=128, verbose_name='版本名称')), + ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), + ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')), + ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')), + ('tool', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='tools.tool', verbose_name='工具')), + ], + options={ + 'db_table': 'tool_workflow_version', + }, + ), + ] diff --git a/apps/tools/models/__init__.py b/apps/tools/models/__init__.py index 4d360990db7..0e5f75ce59d 100644 --- a/apps/tools/models/__init__.py +++ b/apps/tools/models/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from .tool import * +from .tool_workflow import * \ No newline at end of file diff --git a/apps/tools/models/tool.py b/apps/tools/models/tool.py index 0e27ff6e946..99ecfb08069 100644 --- a/apps/tools/models/tool.py +++ b/apps/tools/models/tool.py @@ -36,6 +36,7 @@ class ToolType(models.TextChoices): SKILL = "SKILL", "技能" MCP = "MCP", "MCP工具" DATA_SOURCE = "DATA_SOURCE", "数据源" + WORKFLOW = "WORKFLOW" class ToolTaskTypeChoices(models.TextChoices): @@ -69,7 +70,6 @@ class Meta: db_table = "tool" - class ToolRecord(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") tool = models.ForeignKey(Tool, on_delete=models.SET_NULL, null=True) diff --git a/apps/tools/models/tool_workflow.py b/apps/tools/models/tool_workflow.py new file mode 100644 index 00000000000..4f070cebd6c --- /dev/null +++ b/apps/tools/models/tool_workflow.py @@ -0,0 +1,46 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: tool_workflow.py + @date:2026/3/3 13:59 + @desc: +""" +from django.db import models + +from common.mixins.app_model_mixin import AppModelMixin +import uuid_utils.compat as uuid + +from tools.models import Tool + + +class ToolWorkflow(AppModelMixin): + """ + 知识库工作流表 + """ + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + tool = models.OneToOneField(Tool, on_delete=models.CASCADE, verbose_name="工具", + db_constraint=False, related_name='workflow') + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) + work_flow = models.JSONField(verbose_name="工作流数据", default=dict) + is_publish = models.BooleanField(verbose_name="是否发布", default=False, db_index=True) + publish_time = models.DateTimeField(verbose_name="发布时间", null=True, blank=True) + + class Meta: + db_table = "tool_workflow" + + +class ToolWorkflowVersion(AppModelMixin): + """ + 知识库工作流版本表 - 记录工作流历史版本 + """ + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + tool = models.ForeignKey(Tool, on_delete=models.CASCADE, verbose_name="工具", db_constraint=False) + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) + name = models.CharField(verbose_name="版本名称", max_length=128, default="") + work_flow = models.JSONField(verbose_name="工作流数据", default=dict) + publish_user_id = models.UUIDField(verbose_name="发布者id", max_length=128, default=None, null=True) + publish_user_name = models.CharField(verbose_name="发布者名称", max_length=128, default="") + + class Meta: + db_table = "tool_workflow_version" diff --git a/apps/tools/serializers/tool.py b/apps/tools/serializers/tool.py index 8dcba7507fb..8fe4ff36e72 100644 --- a/apps/tools/serializers/tool.py +++ b/apps/tools/serializers/tool.py @@ -40,6 +40,7 @@ from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from tools.models import Tool, ToolScope, ToolFolder, ToolType, ToolRecord +from tools.models.tool_workflow import ToolWorkflow from trigger.models import TriggerTask, Trigger from users.serializers.user import is_workspace_manage @@ -391,7 +392,8 @@ def insert(self, instance, with_valid=True): 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource(str(tool_id)) - + if instance.get('tool_type') == ToolType.WORKFLOW: + ToolWorkflow(id=uuid.uuid7(), tool_id=tool_id, work_flow=instance.get('work_flow', {})).save() # 如果是SKILL类型的工具,修改file表中对应的记录 if instance.get('tool_type') == ToolType.SKILL: file_id = instance.get('code') @@ -609,11 +611,18 @@ def one(self): 'name': skill_file.file_name, 'size': skill_file.file_size, } if skill_file else None + work_flow = {} + if tool.tool_type == 'WORKFLOW': + tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=tool.id).first() + if tool_workflow: + work_flow = tool_workflow.work_flow + return { **ToolModelSerializer(tool).data, 'init_params': tool.init_params if tool.init_params else {}, 'nick_name': nick_name, - 'fileList': [skill_file_dict] if tool.tool_type == 'SKILL' else [] + 'fileList': [skill_file_dict] if tool.tool_type == 'SKILL' else [], + 'work_flow': work_flow } def export(self): diff --git a/apps/tools/serializers/tool_workflow.py b/apps/tools/serializers/tool_workflow.py new file mode 100644 index 00000000000..5d02c6fea02 --- /dev/null +++ b/apps/tools/serializers/tool_workflow.py @@ -0,0 +1,273 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: tool_workflow.py + @date:2026/3/6 13:59 + @desc: +""" +# coding=utf-8 +import pickle +from functools import reduce +from typing import Dict, List + +import requests +import uuid_utils.compat as uuid +from django.db import transaction +from django.db.models import QuerySet +from django.http import HttpResponse +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers, status + +from common.exception.app_exception import AppApiException +from common.field.common import UploadedFileField +from common.result import result +from common.utils.common import bytes_to_uploaded_file +from common.utils.common import restricted_loads, generate_uuid +from common.utils.logger import maxkb_logger +from common.utils.tool_code import ToolExecutor +from knowledge.models import KnowledgeWorkflow +from system_manage.models import AuthTargetType +from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer +from tools.models import Tool, ToolScope, ToolWorkflow, ToolWorkflowVersion +from tools.serializers.tool import ToolExportModelSerializer +from users.models import User + +tool_executor = ToolExecutor() + + +def hand_node(node, update_tool_map): + if node.get('type') == 'tool-lib-node': + tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '') + node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id) + + if node.get('type') == 'search-knowledge-node': + node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = [] + if node.get('type') == 'ai-chat-node': + node_data = node.get('properties', {}).get('node_data', {}) + mcp_tool_ids = node_data.get('mcp_tool_ids') or [] + node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id, + tool_id) for tool_id in mcp_tool_ids] + tool_ids = node_data.get('tool_ids') or [] + node_data['tool_ids'] = [update_tool_map.get(tool_id, + tool_id) for tool_id in tool_ids] + if node.get('type') == 'mcp-node': + mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '') + node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id, + mcp_tool_id) + + +class ToolWorkflowModelSerializer(serializers.ModelSerializer): + class Meta: + model = ToolWorkflow + fields = '__all__' + + +class ToolWorkflowImportRequest(serializers.Serializer): + file = UploadedFileField(required=True, label=_("file")) + + +class ToolWorkflowActionListQuerySerializer(serializers.Serializer): + user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True) + state = serializers.CharField(required=False, label=_("State"), allow_blank=True, allow_null=True) + + +class ToolWorkflowInstance: + + def __init__(self, knowledge_workflow: dict, version: str, tool_list: List[dict]): + self.knowledge_workflow = knowledge_workflow + self.version = version + self.tool_list = tool_list + + def get_tool_list(self): + return self.tool_list or [] + + +class ToolWorkflowSerializer(serializers.Serializer): + class Import(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.CharField(required=False, label=_('workspace id')) + knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) + + @transaction.atomic + def import_(self, instance: dict, is_import_tool, with_valid=True): + if with_valid: + self.is_valid() + ToolWorkflowSerializer(data=instance).is_valid(raise_exception=True) + user_id = self.data.get('user_id') + workspace_id = self.data.get('workspace_id') + tool_id = self.data.get('tool_id') + tool_instance_bytes = instance.get('file').read() + try: + tool_instance = restricted_loads(tool_instance_bytes) + except Exception as e: + raise AppApiException(1001, _("Unsupported file format")) + tool_workflow = tool_instance.work_flow + tool_list = tool_instance.get_tool_list() + update_tool_map = {} + if len(tool_list) > 0: + tool_id_list = reduce(lambda x, y: [*x, *y], + [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))] + for tool + in + tool_list], []) + # 存在的工具列表 + exits_tool_id_list = [str(tool.id) for tool in + QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)] + # 需要更新的工具集合 + update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool + in + tool_list if + not exits_tool_id_list.__contains__( + tool.get('id'))} + + tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if + not exits_tool_id_list.__contains__( + tool.get('id')) and not exits_tool_id_list.__contains__( + generate_uuid((tool.get('id') + workspace_id or '')))] + + work_flow = self.to_tool_workflow( + tool_workflow, + update_tool_map, + ) + tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list] + QuerySet(ToolWorkflow).filter(workspace_id=workspace_id, tool_id=tool_id).update_or_create( + tool_id=tool_id, + workspace_id=workspace_id, + defaults={'work_flow': work_flow} + ) + + if is_import_tool: + if len(tool_model_list) > 0: + QuerySet(Tool).bulk_create(tool_model_list) + UserResourcePermissionSerializer(data={ + 'workspace_id': self.data.get('workspace_id'), + 'user_id': self.data.get('user_id'), + 'auth_target_type': AuthTargetType.TOOL.value + }).auth_resource_batch([t.id for t in tool_model_list]) + + @staticmethod + def to_tool_workflow(knowledge_workflow, update_tool_map): + work_flow = knowledge_workflow.get("work_flow") + for node in work_flow.get('nodes', []): + hand_node(node, update_tool_map) + if node.get('type') == 'loop_node': + for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []): + hand_node(n, update_tool_map) + return work_flow + + @staticmethod + def to_tool(tool, workspace_id, user_id): + return Tool(id=tool.get('id'), + user_id=user_id, + name=tool.get('name'), + code=tool.get('code'), + template_id=tool.get('template_id'), + input_field_list=tool.get('input_field_list'), + init_field_list=tool.get('init_field_list'), + is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'), + tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM', + scope=ToolScope.SHARED if workspace_id == 'None' else ToolScope.WORKSPACE, + folder_id='default' if workspace_id == 'None' else workspace_id, + workspace_id=workspace_id) + + class Export(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.CharField(required=False, label=_('workspace id')) + tool_id = serializers.UUIDField(required=True, label=_('knowledge id')) + + def export(self, with_valid=True): + try: + if with_valid: + self.is_valid() + tool_id = self.data.get('tool_id') + tool_workflow = QuerySet(ToolWorkflow).filter(knowledge_id=tool_id).first() + tool = QuerySet(Tool).filter(id=tool_id).first() + from application.flow.tools import get_tool_id_list + tool_id_list = get_tool_id_list(tool.work_flow) + tool_list = [] + if len(tool_id_list) > 0: + tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED) + tool_workflow_dict = {'id': tool.id, + 'work_flow': tool_workflow.work_flow, + 'workspace_id': tool.workspace_id, + 'name': tool.name, + 'desc': tool.desc, + 'tool_type': tool.tool_type} + + tool_workflow_instance = ToolWorkflowInstance( + tool_workflow_dict, + 'v2', + [ToolExportModelSerializer(tool).data for tool in tool_list] + ) + tool_workflow_pickle = pickle.dumps(tool_workflow_instance) + response = HttpResponse(content_type='text/plain', content=tool_workflow_pickle) + response['Content-Disposition'] = f'attachment; filename="{tool.name}.tool"' + return response + except Exception as e: + return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + class Operate(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.CharField(required=True, label=_('workspace id')) + tool_id = serializers.UUIDField(required=True, label=_('tool id')) + + def publish(self, with_valid=True): + if with_valid: + self.is_valid() + user_id = self.data.get('user_id') + workspace_id = self.data.get("workspace_id") + user = QuerySet(User).filter(id=user_id).first() + tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get("tool_id"), + workspace_id=workspace_id).first() + work_flow_version = ToolWorkflowVersion(work_flow=tool_workflow.work_flow, + tool_id=self.data.get("tool_id"), + name=timezone.localtime(timezone.now()).strftime( + '%Y-%m-%d %H:%M:%S'), + publish_user_id=user_id, + publish_user_name=user.username, + workspace_id=workspace_id) + work_flow_version.save() + QuerySet(ToolWorkflow).filter( + tool_id=self.data.get("tool_id") + ).update(is_publish=True, publish_time=timezone.now()) + return True + + def edit(self, instance: Dict): + self.is_valid(raise_exception=True) + if instance.get("work_flow"): + QuerySet(ToolWorkflow).update_or_create(tool_id=self.data.get("tool_id"), + create_defaults={'id': uuid.uuid7(), + 'tool_id': self.data.get( + "tool_id"), + "workspace_id": self.data.get( + 'workspace_id'), + 'work_flow': instance.get('work_flow', + {}), }, + defaults={ + 'work_flow': instance.get('work_flow') + }) + return self.one() + if instance.get("work_flow_template"): + template_instance = instance.get('work_flow_template') + download_url = template_instance.get('downloadUrl') + # 查找匹配的版本名称 + res = requests.get(download_url, timeout=5) + ToolWorkflowSerializer.Import(data={ + 'user_id': self.data.get('user_id'), + 'workspace_id': self.data.get('workspace_id'), + 'tool_id': str(self.data.get('tool_id')), + }).import_({'file': bytes_to_uploaded_file(res.content, 'file.tool')}, is_import_tool=False) + + try: + requests.get(template_instance.get('downloadCallbackUrl'), timeout=5) + except Exception as e: + maxkb_logger.error(f"callback appstore tool download error: {e}") + + return self.one() + + def one(self): + self.is_valid(raise_exception=True) + workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first() + return {**ToolWorkflowModelSerializer(workflow).data} diff --git a/apps/tools/urls.py b/apps/tools/urls.py index dd505bdb5e8..e608debc5bf 100644 --- a/apps/tools/urls.py +++ b/apps/tools/urls.py @@ -8,6 +8,7 @@ path('workspace/internal/tool', views.ToolView.InternalTool.as_view()), path('workspace/store/tool', views.ToolView.StoreTool.as_view()), path('workspace//tool', views.ToolView.as_view()), + path('workspace//tool/workflow', views.ToolWorkflowView.as_view()), path('workspace//tool/import', views.ToolView.Import.as_view()), path('workspace//tool/pylint', views.ToolView.Pylint.as_view()), path('workspace//tool/debug', views.ToolView.Debug.as_view()), @@ -15,6 +16,8 @@ path('workspace//tool/test_connection', views.ToolView.TestConnection.as_view()), path('workspace//tool/upload_skill_file', views.ToolView.UploadSkillFile.as_view()), path('workspace//tool/', views.ToolView.Operate.as_view()), + path('workspace//tool//publish', views.ToolWorkflowView.Publish.as_view()), + path('workspace//tool//workflow', views.ToolWorkflowView.Operate.as_view()), path('workspace//tool//edit_icon', views.ToolView.EditIcon.as_view()), path('workspace//tool//export', views.ToolView.Export.as_view()), path('workspace//tool//add_internal_tool', views.ToolView.AddInternalTool.as_view()), diff --git a/apps/tools/views/__init__.py b/apps/tools/views/__init__.py index d3bf330deeb..54997153a23 100644 --- a/apps/tools/views/__init__.py +++ b/apps/tools/views/__init__.py @@ -1 +1,2 @@ -from .tool import * \ No newline at end of file +from .tool import * +from .tool_workflow import * diff --git a/apps/tools/views/tool_workflow.py b/apps/tools/views/tool_workflow.py new file mode 100644 index 00000000000..541cde012f5 --- /dev/null +++ b/apps/tools/views/tool_workflow.py @@ -0,0 +1,200 @@ +# coding=utf-8 + +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema +from rest_framework.request import Request +from rest_framework.views import APIView + +from common.auth import TokenAuth +from common.auth.authentication import has_permissions, get_is_permissions +from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants +from common.log.log import log +from common.result import result, DefaultResultSerializer +from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi +from knowledge.serializers.common import get_knowledge_operation_object +from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer +from tools.api.tool_workflow import ToolWorkflowApi, ToolWorkflowExportApi, ToolWorkflowImportApi +from tools.serializers.tool_workflow import ToolWorkflowSerializer +from tools.views import get_tool_operation_object + + +class ToolWorkflowView(APIView): + authentication_classes = [TokenAuth] + + class Publish(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['PUT'], + description=_("Publishing an tool"), + summary=_("Publishing an tool"), + operation_id=_("Publishing an tool"), # type: ignore + parameters=ToolWorkflowApi.get_parameters(), + request=None, + responses=DefaultResultSerializer, + tags=[_('Tool')] # type: ignore + ) + @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), + PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), + ViewPermission([RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_knowledge_permission()], + CompareConstants.AND), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) + @log(menu='Tool', operate='Publishing an tool', + get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id'))) + def put(self, request: Request, workspace_id: str, tool_id: str): + return result.success( + ToolWorkflowSerializer.Operate( + data={'tool_id': tool_id, 'user_id': request.user.id, + 'workspace_id': workspace_id, }).publish()) + + class Export(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_('Export tool workflow'), + summary=_('Export tool workflow'), + operation_id=_('Export tool workflow'), # type: ignore + parameters=ToolWorkflowExportApi.get_parameters(), + request=None, + responses=ToolWorkflowExportApi.get_response(), + tags=[_('Tool')] # type: ignore + ) + @has_permissions( + PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(), + PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.KNOWLEDGE.get_workspace_tool_permission()], + CompareConstants.AND + ) + ) + @log(menu='Tool', operate="Export tool workflow", + get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), + ) + def get(self, request: Request, workspace_id: str, tool_id: str): + return KnowledgeWorkflowSerializer.Export( + data={'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id} + ).export() + + class Import(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['POST'], + description=_('Import tool workflow'), + summary=_('Import tool workflow'), + operation_id=_('Import tool workflow'), # type: ignore + parameters=ToolWorkflowImportApi.get_parameters(), + request=ToolWorkflowImportApi.get_request(), + responses=ToolWorkflowImportApi.get_response(), + tags=[_('Tool')] # type: ignore + ) + @has_permissions( + PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(), + PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.KNOWLEDGE.get_workspace_tool_permission()], + CompareConstants.AND + ) + ) + @log(menu='Tool', operate="Import tool workflow", + get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool')), + ) + def post(self, request: Request, workspace_id: str, tool_id: str): + is_import_tool = get_is_permissions(request, workspace_id=workspace_id)( + PermissionConstants.TOOL_IMPORT.get_workspace_permission(), + PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() + ) + return result.success(ToolWorkflowSerializer.Import(data={ + 'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id + }).import_({'file': request.FILES.get('file')}, is_import_tool)) + + class Operate(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['PUT'], + description=_('Edit tool workflow'), + summary=_('Edit tool workflow'), + operation_id=_('Edit tool workflow'), # type: ignore + parameters=ToolWorkflowApi.get_parameters(), + request=ToolWorkflowApi.get_request(), + responses=ToolWorkflowApi.get_response(), + tags=[_('Tool')] # type: ignore + ) + @has_permissions( + PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), + PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_tool_permission()], + CompareConstants.AND + ) + ) + @log( + menu='Tool', operate="Modify tool workflow", + get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), + ) + def put(self, request: Request, workspace_id: str, tool_id: str): + return result.success(ToolWorkflowSerializer.Operate( + data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id} + ).edit(request.data)) + + @extend_schema( + methods=['GET'], + description=_('Get tool workflow'), + summary=_('Get tool workflow'), + operation_id=_('Get tool workflow'), # type: ignore + parameters=KnowledgeWorkflowApi.get_parameters(), + responses=KnowledgeWorkflowApi.get_response(), + tags=[_('Tool')] # type: ignore + ) + @has_permissions( + PermissionConstants.TOOL_READ.get_workspace_tool_permission(), + PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_tool_permission()], + CompareConstants.AND + ), + ) + def get(self, request: Request, workspace_id: str, tool_id: str): + return result.success(ToolWorkflowSerializer.Operate( + data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id} + ).one()) + + +class KnowledgeWorkflowVersionView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_('Get tool workflow version list'), + summary=_('Get tool workflow version list'), + operation_id=_('Get tool workflow version list'), # type: ignore + parameters=ToolWorkflowApi.get_parameters(), + responses=ToolWorkflowApi.get_response(), + tags=[_('Tool')] # type: ignore + ) + @has_permissions( + PermissionConstants.TOOL_READ.get_workspace_tool_permission(), + PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_tool_permission()], + CompareConstants.AND + ), + ) + def get(self, request: Request, workspace_id: str, tool_id: str): + return result.success(KnowledgeWorkflowSerializer.Operate( + data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id} + ).one()) diff --git a/ui/src/api/tool/tool.ts b/ui/src/api/tool/tool.ts index 9506dfeb175..356b8702700 100644 --- a/ui/src/api/tool/tool.ts +++ b/ui/src/api/tool/tool.ts @@ -85,7 +85,6 @@ const postToolTestConnection: (data: toolData, loading?: Ref) => Promis return post(`${prefix.value}/test_connection`, data, undefined, loading) } - /** * 获取工具详情 * @param tool_id 工具id @@ -159,7 +158,6 @@ const addInternalTool: ( return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading) } - /** * 工具商店-添加 */ @@ -179,12 +177,7 @@ const updateStoreTool: ( return post(`${prefix.value}/${tool_id}/update_store_tool`, param, undefined, loading) } -const pageToolRecord = ( - tool_id: string, - page: pageRequest, - param: any, - loading?: Ref, -) => { +const pageToolRecord = (tool_id: string, page: pageRequest, param: any, loading?: Ref) => { return get( `${prefix.value}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`, param, @@ -192,10 +185,7 @@ const pageToolRecord = ( ) } -const getToolRecordDetail = ( - tool_id: string, - record_id: string -) => { +const getToolRecordDetail = (tool_id: string, record_id: string) => { return get(`${prefix.value}/${tool_id}/tool_record/${record_id}`) } @@ -205,8 +195,84 @@ const uploadSkillFile: (data: toolData, loading?: Ref) => Promise { return put(`${prefix.value}/upload_skill_file`, data, undefined, loading) } +/** + * 保存工具工作流 + * @param tool_id + * @param data + * @param loading + * @returns + */ +const putToolWorkflow: ( + tool_id: string, + data: any, + loading?: Ref, +) => Promise> = (tool_id, data, loading) => { + return put(`${prefix.value}/${tool_id}/workflow`, data, undefined, loading) +} - +/** + * 导出知识库工作流 + * @param knowledge_id + * @param knowledge_name + * @param loading + * @returns + */ +const exportKnowledgeWorkflow = ( + knowledge_id: string, + knowledge_name: string, + loading?: Ref, +) => { + return exportFile( + knowledge_name + '.kbwf', + `${prefix.value}/${knowledge_id}/workflow/export`, + undefined, + loading, + ) +} +/** + * 导入工具工作流 + */ +const importToolWorkflow: ( + tool_id: string, + data: any, + loading?: Ref, +) => Promise> = (tool_id, data, loading) => { + return post(`${prefix.value}/${tool_id}/workflow/import`, data, undefined, loading) +} +/** + * 获取工具工作流版本列表 + * @param tool_id + * @param loading + * @returns + */ +const listToolWorkflowVersion: (tool_id: string, loading?: Ref) => Promise> = ( + tool_id: string, + loading, +) => { + return get(`${prefix.value}/${tool_id}/tool_version`, {}, loading) +} +/** + * + * @param tool_id 工具id + * @param tool_version_id 工具版本id + * @param data 数据 + * @param loading + * @returns + */ +const updateToolWorkflowVersion: ( + tool_id: string, + tool_version_id: string, + data: any, + loading?: Ref, +) => Promise> = (tool_id: string, tool_version_id, data, loading) => { + return put(`${prefix.value}/${tool_id}/tool_version/${tool_version_id}`, data, {}, loading) +} +const publish: (tool_id: string, loading?: Ref) => Promise> = ( + tool_id: string, + loading, +) => { + return put(`${prefix.value}/${tool_id}/publish`, {}, {}, loading) +} export default { getToolList, getAllToolList, @@ -227,4 +293,9 @@ export default { pageToolRecord, getToolRecordDetail, uploadSkillFile, + putToolWorkflow, + importToolWorkflow, + listToolWorkflowVersion, + updateToolWorkflowVersion, + publish, } diff --git a/ui/src/components/workflow-dropdown-menu/index.vue b/ui/src/components/workflow-dropdown-menu/index.vue index eaac237d748..0b1a1a83b73 100644 --- a/ui/src/components/workflow-dropdown-menu/index.vue +++ b/ui/src/components/workflow-dropdown-menu/index.vue @@ -7,6 +7,7 @@ import { WorkflowMode } from '@/enums/application' import ApplicationDropdownMenu from '@/components/workflow-dropdown-menu/application/index.vue' import KnowledgeDropdownMenu from '@/components/workflow-dropdown-menu/knowledge/index.vue' import KnowledgeDropdownInnerMenu from '@/components/workflow-dropdown-menu/knowledge-inner/index.vue' +import ToolDropdownMenu from '@/components/workflow-dropdown-menu/tool/index.vue' const workflow_mode: WorkflowMode = inject('workflowMode') || WorkflowMode.Application const props = defineProps({ show: { @@ -28,6 +29,8 @@ const kw: any = { [WorkflowMode.ApplicationLoop]: ApplicationDropdownMenu, [WorkflowMode.Knowledge]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu, [WorkflowMode.KnowledgeLoop]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu, + [WorkflowMode.Tool]: ToolDropdownMenu, + [WorkflowMode.ToolLoop]: ToolDropdownMenu, } diff --git a/ui/src/components/workflow-dropdown-menu/tool/index.vue b/ui/src/components/workflow-dropdown-menu/tool/index.vue new file mode 100644 index 00000000000..360dd2d602b --- /dev/null +++ b/ui/src/components/workflow-dropdown-menu/tool/index.vue @@ -0,0 +1,254 @@ + + + diff --git a/ui/src/enums/application.ts b/ui/src/enums/application.ts index d0781273eb0..719890d87ff 100644 --- a/ui/src/enums/application.ts +++ b/ui/src/enums/application.ts @@ -42,6 +42,8 @@ export enum WorkflowType { DataSourceLocalNode = 'data-source-local-node', DataSourceWebNode = 'data-source-web-node', KnowledgeWriteNode = 'knowledge-write-node', + ToolStartNode = 'tool-start-node', + ToolBaseNode = 'tool-base-node', } export enum WorkflowKind { DataSource = 'data-source', @@ -53,6 +55,10 @@ export enum WorkflowMode { ApplicationLoop = 'application-loop', // 知识库工作流 Knowledge = 'knowledge', + // 工具 + Tool = 'tool', + // 工具循环体 + ToolLoop = 'tool-loop', // 知识库工作流循环体 KnowledgeLoop = 'knowledge-loop', } diff --git a/ui/src/router/routes.ts b/ui/src/router/routes.ts index e5b52ddb388..b0a08e9f182 100644 --- a/ui/src/router/routes.ts +++ b/ui/src/router/routes.ts @@ -43,6 +43,12 @@ export const routes: Array = [ meta: { activeMenu: '/knowledge' }, component: () => import('@/views/knowledge-workflow/index.vue'), }, + { + path: '/tool/:id/:folderId/workflow', + name: 'ToolWorkflow', + meta: { activeMenu: '/tool' }, + component: () => import('@/views/tool-workflow/index.vue'), + }, // 对话 { path: '/chat/:accessToken', diff --git a/ui/src/views/tool-workflow/index.vue b/ui/src/views/tool-workflow/index.vue new file mode 100644 index 00000000000..ec1dc33a301 --- /dev/null +++ b/ui/src/views/tool-workflow/index.vue @@ -0,0 +1,648 @@ + + + diff --git a/ui/src/views/tool/WorkflowFormDialog.vue b/ui/src/views/tool/WorkflowFormDialog.vue new file mode 100644 index 00000000000..e21332ce5f4 --- /dev/null +++ b/ui/src/views/tool/WorkflowFormDialog.vue @@ -0,0 +1,166 @@ + + + diff --git a/ui/src/views/tool/component/ToolListContainer.vue b/ui/src/views/tool/component/ToolListContainer.vue index 014b59f0be5..0da6fb73786 100644 --- a/ui/src/views/tool/component/ToolListContainer.vue +++ b/ui/src/views/tool/component/ToolListContainer.vue @@ -82,6 +82,16 @@ + +
+ + + +
+
创建工作流
+
+
+
@@ -353,7 +363,11 @@ - + + diff --git a/ui/src/workflow/icons/tool-base-node-icon.vue b/ui/src/workflow/icons/tool-base-node-icon.vue new file mode 100644 index 00000000000..cf9ad11e91c --- /dev/null +++ b/ui/src/workflow/icons/tool-base-node-icon.vue @@ -0,0 +1,6 @@ + + diff --git a/ui/src/workflow/icons/tool-start-node-icon.vue b/ui/src/workflow/icons/tool-start-node-icon.vue new file mode 100644 index 00000000000..da762ada8df --- /dev/null +++ b/ui/src/workflow/icons/tool-start-node-icon.vue @@ -0,0 +1,6 @@ + + diff --git a/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldFormDialog.vue b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldFormDialog.vue new file mode 100644 index 00000000000..ed59fd24f75 --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldFormDialog.vue @@ -0,0 +1,119 @@ + + + diff --git a/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue new file mode 100644 index 00000000000..7b059f0548a --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/ui/src/workflow/nodes/tool-base-node/component/input/InputTitleDialog.vue b/ui/src/workflow/nodes/tool-base-node/component/input/InputTitleDialog.vue new file mode 100644 index 00000000000..08c2f48c32f --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/component/input/InputTitleDialog.vue @@ -0,0 +1,83 @@ + + + diff --git a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldFormDialog.vue b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldFormDialog.vue new file mode 100644 index 00000000000..ee48511e0c1 --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldFormDialog.vue @@ -0,0 +1,114 @@ + + + diff --git a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue new file mode 100644 index 00000000000..bd6c1869493 --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/ui/src/workflow/nodes/tool-base-node/component/output/OutputTitleDialog.vue b/ui/src/workflow/nodes/tool-base-node/component/output/OutputTitleDialog.vue new file mode 100644 index 00000000000..1768f9fa940 --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/component/output/OutputTitleDialog.vue @@ -0,0 +1,83 @@ + + + diff --git a/ui/src/workflow/nodes/tool-base-node/index.ts b/ui/src/workflow/nodes/tool-base-node/index.ts new file mode 100644 index 00000000000..93b5edffd6c --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/index.ts @@ -0,0 +1,12 @@ +import ToolBaseNodeVue from './index.vue' +import { AppNode, AppNodeModel } from '@/workflow/common/app-node' +class ToolBaseNode extends AppNode { + constructor(props: any) { + super(props, ToolBaseNodeVue) + } +} +export default { + type: 'tool-base-node', + model: AppNodeModel, + view: ToolBaseNode, +} diff --git a/ui/src/workflow/nodes/tool-base-node/index.vue b/ui/src/workflow/nodes/tool-base-node/index.vue new file mode 100644 index 00000000000..2ec970bd37e --- /dev/null +++ b/ui/src/workflow/nodes/tool-base-node/index.vue @@ -0,0 +1,14 @@ + + + diff --git a/ui/src/workflow/nodes/tool-start-node/index.ts b/ui/src/workflow/nodes/tool-start-node/index.ts new file mode 100644 index 00000000000..2ff937d0699 --- /dev/null +++ b/ui/src/workflow/nodes/tool-start-node/index.ts @@ -0,0 +1,36 @@ +import ToolBaseNodeVue from './index.vue' +import { AppNode, AppNodeModel } from '@/workflow/common/app-node' +import { t } from '@/locales' +class ToolBaseNode extends AppNode { + constructor(props: any) { + super(props, ToolBaseNodeVue) + } + get_node_field_list() { + const result = [] + result.push({ + value: 'global', + label: t('workflow.variable.global'), + type: 'global', + children: this.props.model.properties?.config?.globalFields || [], + }) + const tbn = this.props.graphModel.getNodeModelById('tool-base-node') + console.log(tbn) + const output = tbn.properties.user_output_field_list.map((i: any) => { + return { label: i.label || i.name, value: i.field } + }) + + result.push({ + value: 'output', + label: '参数输出', + type: 'output', + children: output || [], + }) + console.log(result) + return result + } +} +export default { + type: 'tool-start-node', + model: AppNodeModel, + view: ToolBaseNode, +} diff --git a/ui/src/workflow/nodes/tool-start-node/index.vue b/ui/src/workflow/nodes/tool-start-node/index.vue new file mode 100644 index 00000000000..b966ba9f865 --- /dev/null +++ b/ui/src/workflow/nodes/tool-start-node/index.vue @@ -0,0 +1,44 @@ + + + From fb9dc21af1344a649a5fb93f48c1294e59ca7098 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 Date: Thu, 12 Mar 2026 13:45:41 +0800 Subject: [PATCH 02/12] feat: tool_workflow init --- apps/tools/serializers/tool_version.py | 107 +++++++++++ apps/tools/serializers/tool_workflow.py | 4 +- apps/tools/urls.py | 5 + apps/tools/views/__init__.py | 1 + apps/tools/views/tool_workflow.py | 2 +- apps/tools/views/tool_workflow_version.py | 134 +++++++++++++ ui/src/api/tool/tool.ts | 16 ++ .../component/PublishHistory.vue | 177 ++++++++++++++++++ ui/src/views/tool-workflow/index.vue | 44 +---- 9 files changed, 450 insertions(+), 40 deletions(-) create mode 100644 apps/tools/serializers/tool_version.py create mode 100644 apps/tools/views/tool_workflow_version.py create mode 100644 ui/src/views/tool-workflow/component/PublishHistory.vue diff --git a/apps/tools/serializers/tool_version.py b/apps/tools/serializers/tool_version.py new file mode 100644 index 00000000000..9eba191375d --- /dev/null +++ b/apps/tools/serializers/tool_version.py @@ -0,0 +1,107 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: KnowledgeVersionSerializer.py + @date:2025/11/28 18:00 + @desc: +""" +from typing import Dict + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from common.db.search import page_search +from common.exception.app_exception import AppApiException +from tools.models import ToolWorkflowVersion, Tool + + +class ToolWorkflowVersionEditSerializer(serializers.Serializer): + name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True, + label=_("Version Name")) + + +class ToolVersionModelSerializer(serializers.ModelSerializer): + class Meta: + model = ToolWorkflowVersion + fields = ['id', 'name', 'workspace_id', 'tool_id', 'work_flow', 'publish_user_id', 'publish_user_name', + 'create_time', + 'update_time'] + + +class ToolWorkflowVersionQuerySerializer(serializers.Serializer): + tool_id = serializers.UUIDField(required=True, label=_("Tool ID")) + name = serializers.CharField(required=False, allow_null=True, allow_blank=True, + label=_("summary")) + + +class ToolWorkflowVersionSerializer(serializers.Serializer): + workspace_id = serializers.CharField(required=False, label=_("Workspace ID")) + + class Query(serializers.Serializer): + workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) + + def get_query_set(self, query): + query_set = QuerySet(ToolWorkflowVersion).filter(tool_id=query.get('tool_id')) + if 'name' in query and query.get('name') is not None: + query_set = query_set.filter(name__contains=query.get('name')) + if 'workspace_id' in self.data and self.data.get('workspace_id') is not None: + query_set = query_set.filter(workspace_id=self.data.get('workspace_id')) + return query_set.order_by("-create_time") + + def list(self, query, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + ToolWorkflowVersionQuerySerializer(data=query).is_valid(raise_exception=True) + query_set = self.get_query_set(query) + return [ToolVersionModelSerializer(v).data for v in query_set] + + def page(self, query, current_page, page_size, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + return page_search(current_page, page_size, + self.get_query_set(query), + post_records_handler=lambda v: ToolVersionModelSerializer(v).data) + + class Operate(serializers.Serializer): + workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) + tool_id = serializers.UUIDField(required=True, label=_("Tool ID")) + tool_version_id = serializers.UUIDField(required=True, + label=_("Tool version ID")) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + workspace_id = self.data.get('workspace_id') + query_set = QuerySet(Tool).filter(id=self.data.get('tool_id')) + if workspace_id: + query_set = query_set.filter(workspace_id=workspace_id) + if not query_set.exists(): + raise AppApiException(500, _('Tool id does not exist')) + + def one(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + tool_version = QuerySet(ToolWorkflowVersion).filter(tool_id=self.data.get('tool_id'), + id=self.data.get( + 'tool_version_id')).first() + if tool_version is not None: + return ToolVersionModelSerializer(tool_version).data + else: + raise AppApiException(500, _('Workflow version does not exist')) + + def edit(self, instance: Dict, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + ToolWorkflowVersionEditSerializer(data=instance).is_valid(raise_exception=True) + tool_version = QuerySet(ToolWorkflowVersion).filter(tool_id=self.data.get('tool_id'), + id=self.data.get( + 'knowledge_version_id')).first() + if tool_version is not None: + name = instance.get('name', None) + if name is not None and len(name) > 0: + tool_version.name = name + tool_version.save() + return ToolVersionModelSerializer(tool_version).data + else: + raise AppApiException(500, _('Workflow version does not exist')) diff --git a/apps/tools/serializers/tool_workflow.py b/apps/tools/serializers/tool_workflow.py index 5d02c6fea02..ef1600ab4e2 100644 --- a/apps/tools/serializers/tool_workflow.py +++ b/apps/tools/serializers/tool_workflow.py @@ -182,10 +182,10 @@ def export(self, with_valid=True): if with_valid: self.is_valid() tool_id = self.data.get('tool_id') - tool_workflow = QuerySet(ToolWorkflow).filter(knowledge_id=tool_id).first() + tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=tool_id).first() tool = QuerySet(Tool).filter(id=tool_id).first() from application.flow.tools import get_tool_id_list - tool_id_list = get_tool_id_list(tool.work_flow) + tool_id_list = get_tool_id_list(tool_workflow.work_flow) tool_list = [] if len(tool_id_list) > 0: tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED) diff --git a/apps/tools/urls.py b/apps/tools/urls.py index e608debc5bf..aceff0b22cc 100644 --- a/apps/tools/urls.py +++ b/apps/tools/urls.py @@ -18,6 +18,7 @@ path('workspace//tool/', views.ToolView.Operate.as_view()), path('workspace//tool//publish', views.ToolWorkflowView.Publish.as_view()), path('workspace//tool//workflow', views.ToolWorkflowView.Operate.as_view()), + path('workspace//tool//workflow/export', views.ToolWorkflowView.Export.as_view()), path('workspace//tool//edit_icon', views.ToolView.EditIcon.as_view()), path('workspace//tool//export', views.ToolView.Export.as_view()), path('workspace//tool//add_internal_tool', views.ToolView.AddInternalTool.as_view()), @@ -26,4 +27,8 @@ path('workspace//tool//tool_record/', views.ToolView.ToolRecord.as_view()), path('workspace//tool//tool_record//', views.ToolView.PageToolRecord.as_view()), path('workspace//tool//', views.ToolView.Page.as_view()), + path('workspace//tool//tool_version', views.ToolWorkflowVersionView.as_view()), + path('workspace//tool//tool_version//', views.ToolWorkflowVersionView.Page.as_view()), + path('workspace//tool//tool_version/', views.ToolWorkflowVersionView.Operate.as_view()), + ] diff --git a/apps/tools/views/__init__.py b/apps/tools/views/__init__.py index 54997153a23..94c01cadef1 100644 --- a/apps/tools/views/__init__.py +++ b/apps/tools/views/__init__.py @@ -1,2 +1,3 @@ from .tool import * from .tool_workflow import * +from .tool_workflow_version import * diff --git a/apps/tools/views/tool_workflow.py b/apps/tools/views/tool_workflow.py index 541cde012f5..1513f4b7830 100644 --- a/apps/tools/views/tool_workflow.py +++ b/apps/tools/views/tool_workflow.py @@ -75,7 +75,7 @@ class Export(APIView): get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def get(self, request: Request, workspace_id: str, tool_id: str): - return KnowledgeWorkflowSerializer.Export( + return ToolWorkflowSerializer.Export( data={'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id} ).export() diff --git a/apps/tools/views/tool_workflow_version.py b/apps/tools/views/tool_workflow_version.py new file mode 100644 index 00000000000..7eea50a9077 --- /dev/null +++ b/apps/tools/views/tool_workflow_version.py @@ -0,0 +1,134 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: application_version.py.py + @date:2025/6/3 15:46 + @desc: +""" +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema +from rest_framework.request import Request +from rest_framework.views import APIView + +from common import result +from common.auth import TokenAuth +from common.auth.authentication import has_permissions +from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants +from common.log.log import log +from knowledge.api.knowledge_version import KnowledgeVersionListAPI, KnowledgeVersionPageAPI, \ + KnowledgeVersionOperateAPI +from knowledge.models import Knowledge +from tools.serializers.tool_version import ToolWorkflowVersionSerializer +from tools.views import get_tool_operation_object + + +def get_knowledge_operation_object(knowledge_id): + knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first() + if knowledge_model is not None: + return { + 'name': knowledge_model.name + } + return {} + + +class ToolWorkflowVersionView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_("Get the tool version list"), + summary=_("Get the tool version list"), + operation_id=_("Get the tool version list"), # type: ignore + parameters=KnowledgeVersionListAPI.get_parameters(), + responses=KnowledgeVersionListAPI.get_response(), + tags=[_('Tool/Version')] # type: ignore + ) + @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(), + PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), + ViewPermission([RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_knowledge_permission()], + CompareConstants.AND), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) + def get(self, request: Request, workspace_id, tool_id: str): + return result.success( + ToolWorkflowVersionSerializer.Query( + data={'workspace_id': workspace_id}).list( + {'name': request.query_params.get("name"), 'tool_id': tool_id})) + + class Page(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_("Get the list of tool versions by page"), + summary=_("Get the list of tool versions by page"), + operation_id=_("Get the list of tool versions by page"), # type: ignore + parameters=KnowledgeVersionPageAPI.get_parameters(), + responses=KnowledgeVersionPageAPI.get_response(), + tags=[_('Tool/Version')] # type: ignore + ) + @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(), + PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), + ViewPermission([RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_knowledge_permission()], + CompareConstants.AND), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) + def get(self, request: Request, workspace_id: str, tool_id: str, current_page: int, page_size: int): + return result.success( + ToolWorkflowVersionSerializer.Query( + data={'workspace_id': workspace_id}).page( + {'name': request.query_params.get("name"), 'tool_id': tool_id}, + current_page, page_size)) + + class Operate(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_("Get tool version details"), + summary=_("Get tool version details"), + operation_id=_("Get tool version details"), # type: ignore + parameters=KnowledgeVersionOperateAPI.get_parameters(), + responses=KnowledgeVersionOperateAPI.get_response(), + tags=[_('Tool/Version')] # type: ignore + ) + @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(), + PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), + ViewPermission([RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_knowledge_permission()], + CompareConstants.AND), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) + def get(self, request: Request, workspace_id: str, tool_id: str, tool_version_id: str): + return result.success( + ToolWorkflowVersionSerializer.Operate( + data={'user_id': request.user, 'workspace_id': workspace_id, + 'tool_id': tool_id, 'tool_version_id': tool_version_id}).one()) + + @extend_schema( + methods=['PUT'], + description=_("Modify tool version information"), + summary=_("Modify tool version information"), + operation_id=_("Modify tool version information"), # type: ignore + parameters=KnowledgeVersionOperateAPI.get_parameters(), + request=None, + responses=KnowledgeVersionOperateAPI.get_response(), + tags=[_('Tool/Version')] # type: ignore + ) + @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_knowledge_permission(), + PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), + ViewPermission([RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_knowledge_permission()], + CompareConstants.AND), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) + @log(menu='Tool', operate="Modify tool version information", + get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), + ) + def put(self, request: Request, workspace_id: str, tool_id: str, tool_version_id: str): + return result.success( + ToolWorkflowVersionSerializer.Operate( + data={'tool_id': tool_id, 'workspace_id': workspace_id, + 'tool_version_id': tool_version_id, + 'user_id': request.user.id}).edit( + request.data)) diff --git a/ui/src/api/tool/tool.ts b/ui/src/api/tool/tool.ts index 356b8702700..bfec9f715cf 100644 --- a/ui/src/api/tool/tool.ts +++ b/ui/src/api/tool/tool.ts @@ -229,6 +229,21 @@ const exportKnowledgeWorkflow = ( loading, ) } +/** + * 导出知识库工作流 + * @param knowledge_id + * @param knowledge_name + * @param loading + * @returns + */ +const exportToolWorkflow = (tool_id: string, tool_name: string, loading?: Ref) => { + return exportFile( + tool_name + '.tool', + `${prefix.value}/${tool_id}/workflow/export`, + undefined, + loading, + ) +} /** * 导入工具工作流 */ @@ -298,4 +313,5 @@ export default { listToolWorkflowVersion, updateToolWorkflowVersion, publish, + exportToolWorkflow, } diff --git a/ui/src/views/tool-workflow/component/PublishHistory.vue b/ui/src/views/tool-workflow/component/PublishHistory.vue new file mode 100644 index 00000000000..b8f5797981e --- /dev/null +++ b/ui/src/views/tool-workflow/component/PublishHistory.vue @@ -0,0 +1,177 @@ + + + diff --git a/ui/src/views/tool-workflow/index.vue b/ui/src/views/tool-workflow/index.vue index ec1dc33a301..8e11b52a0e0 100644 --- a/ui/src/views/tool-workflow/index.vue +++ b/ui/src/views/tool-workflow/index.vue @@ -56,44 +56,14 @@ diff --git a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue index bd6c1869493..c8e141a56fb 100644 --- a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue +++ b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue @@ -116,10 +116,13 @@ function onDragHandle() { } onMounted(() => { - set(props.nodeModel.properties, 'user_output_field_list', inputFieldList) if (props.nodeModel.properties.user_output_config) { outputFieldConfig.value = props.nodeModel.properties.user_output_config } + if (props.nodeModel.properties.user_output_field_list) { + inputFieldList.value = props.nodeModel.properties.user_output_field_list + } + set(props.nodeModel.properties, 'user_output_field_list', inputFieldList.value) set(props.nodeModel.properties, 'user_output_config', outputFieldConfig) onDragHandle() }) From 2130ff4776d89bb6906a52ab98d4df242e2cee00 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 Date: Fri, 13 Mar 2026 16:27:15 +0800 Subject: [PATCH 04/12] feat: tool_workflow init --- apps/application/flow/common.py | 4 + apps/application/flow/i_step_node.py | 5 + apps/application/flow/step_node/__init__.py | 4 +- .../ai_chat_step_node/i_chat_node.py | 9 +- .../condition_node/i_condition_node.py | 3 +- .../direct_reply_node/i_reply_node.py | 5 +- .../i_document_extract_node.py | 3 +- .../i_document_split_node.py | 3 +- .../flow/step_node/form_node/i_form_node.py | 2 +- .../i_image_generate_node.py | 7 +- .../i_image_to_video_node.py | 8 +- .../i_image_understand_node.py | 9 +- .../step_node/intent_node/i_intent_node.py | 7 +- .../loop_break_node/i_loop_break_node.py | 2 +- .../i_loop_continue_node.py | 2 +- .../flow/step_node/loop_node/i_loop_node.py | 2 +- .../loop_start_node/i_loop_start_node.py | 2 +- .../flow/step_node/mcp_node/i_mcp_node.py | 2 +- .../i_parameter_extraction_node.py | 2 +- .../question_node/i_question_node.py | 2 +- .../reranker_node/i_reranker_node.py | 2 +- .../i_search_document_node.py | 2 +- .../i_search_knowledge_node.py | 2 +- .../i_speech_to_text_node.py | 2 +- .../i_text_to_speech_node.py | 2 +- .../i_text_to_video_node.py | 7 +- .../tool_lib_node/i_tool_lib_node.py | 2 +- .../flow/step_node/tool_node/i_tool_node.py | 2 +- .../step_node/tool_start_node/__init__.py | 9 + .../tool_start_node/i_tool_start_node.py | 21 ++ .../tool_start_node/impl/__init__.py | 9 + .../impl/base_tool_start_node.py | 66 +++++++ .../i_variable_aggregation_node.py | 3 +- .../i_variable_assign_node.py | 2 +- .../impl/base_variable_assign_node.py | 11 +- .../i_variable_splitting_node.py | 2 +- .../i_video_understand_node.py | 7 +- apps/application/flow/tool_workflow_manage.py | 13 +- apps/tools/serializers/tool_workflow.py | 16 +- apps/tools/urls.py | 1 + apps/tools/views/tool_workflow.py | 19 +- ui/src/api/tool/tool.ts | 14 +- .../workflow-dropdown-menu/tool/index.vue | 4 +- .../component/debug-drawer/index.vue | 31 ++- .../component/debug/result/index.vue | 186 +++++++++++++++++- ui/src/workflow/common/data.ts | 1 - .../component/input/InputFieldTable.vue | 4 +- .../component/output/OutputFieldTable.vue | 11 +- 48 files changed, 451 insertions(+), 83 deletions(-) create mode 100644 apps/application/flow/step_node/tool_start_node/__init__.py create mode 100644 apps/application/flow/step_node/tool_start_node/i_tool_start_node.py create mode 100644 apps/application/flow/step_node/tool_start_node/impl/__init__.py create mode 100644 apps/application/flow/step_node/tool_start_node/impl/base_tool_start_node.py diff --git a/apps/application/flow/common.py b/apps/application/flow/common.py index 675cc4a6387..abb439a7faf 100644 --- a/apps/application/flow/common.py +++ b/apps/application/flow/common.py @@ -99,6 +99,10 @@ class WorkflowMode(Enum): KNOWLEDGE_LOOP = "knowledge-loop" + TOOL = "tool" + + TOOL_LOOP = "tool-loop" + class Workflow: """ diff --git a/apps/application/flow/i_step_node.py b/apps/application/flow/i_step_node.py index e1227b8599e..aa1c5266ec1 100644 --- a/apps/application/flow/i_step_node.py +++ b/apps/application/flow/i_step_node.py @@ -215,6 +215,11 @@ class KnowledgeFlowParamsSerializer(serializers.Serializer): knowledge_base = serializers.DictField(required=False, label="知识库设置") +class ToolFlowParamsSerializer(serializers.Serializer): + tool_id = serializers.UUIDField(required=True, label="工具id") + workspace_id = serializers.CharField(required=True, label="工作空间id") + + class INode: view_type = 'many_view' diff --git a/apps/application/flow/step_node/__init__.py b/apps/application/flow/step_node/__init__.py index 52379486872..01b9a83570a 100644 --- a/apps/application/flow/step_node/__init__.py +++ b/apps/application/flow/step_node/__init__.py @@ -40,6 +40,7 @@ from .variable_splitting_node import BaseVariableSplittingNode from .video_understand_step_node import BaseVideoUnderstandNode from .document_split_node import BaseDocumentSplitNode +from .tool_start_node import BaseToolStartStepNode node_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseSearchDocumentNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode, @@ -51,7 +52,8 @@ BaseIntentNode, BaseLoopNode, BaseLoopStartStepNode, BaseLoopContinueNode, BaseLoopBreakNode, BaseVariableSplittingNode, BaseParameterExtractionNode, BaseVariableAggregationNode, - BaseDataSourceLocalNode, BaseDataSourceWebNode, BaseKnowledgeWriteNode, BaseDocumentSplitNode] + BaseDataSourceLocalNode, BaseDataSourceWebNode, BaseKnowledgeWriteNode, BaseDocumentSplitNode, + BaseToolStartStepNode] node_map = {n.type: {w: n for w in n.support} for n in node_list} diff --git a/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py b/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py index 77e53cd4a44..c9a348c6bd0 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py +++ b/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py @@ -41,22 +41,23 @@ class ChatNodeSerializer(serializers.Serializer): tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, label=_("Tool IDs"), ) application_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, - label=_("App IDs"), ) + label=_("App IDs"), ) skill_tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, - label=_("Skill IDs"), ) + label=_("Skill IDs"), ) mcp_output_enable = serializers.BooleanField(required=False, default=True, label=_("Whether to enable MCP output")) class IChatNode(INode): type = 'ai-chat-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, - WorkflowMode.KNOWLEDGE] + WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ChatNodeSerializer def _run(self): - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) diff --git a/apps/application/flow/step_node/condition_node/i_condition_node.py b/apps/application/flow/step_node/condition_node/i_condition_node.py index 9dec6b0c649..664ee91baff 100644 --- a/apps/application/flow/step_node/condition_node/i_condition_node.py +++ b/apps/application/flow/step_node/condition_node/i_condition_node.py @@ -38,4 +38,5 @@ def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: type = 'condition-node' - support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP] + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] diff --git a/apps/application/flow/step_node/direct_reply_node/i_reply_node.py b/apps/application/flow/step_node/direct_reply_node/i_reply_node.py index c1646a05494..9526d3c046a 100644 --- a/apps/application/flow/step_node/direct_reply_node/i_reply_node.py +++ b/apps/application/flow/step_node/direct_reply_node/i_reply_node.py @@ -40,13 +40,14 @@ def is_valid(self, *, raise_exception=False): class IReplyNode(INode): type = 'reply-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, - WorkflowMode.KNOWLEDGE] + WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ReplyNodeParamsSerializer def _run(self): - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'stream': True}) diff --git a/apps/application/flow/step_node/document_extract_node/i_document_extract_node.py b/apps/application/flow/step_node/document_extract_node/i_document_extract_node.py index eca4856ddb9..d2cf43e0238 100644 --- a/apps/application/flow/step_node/document_extract_node/i_document_extract_node.py +++ b/apps/application/flow/step_node/document_extract_node/i_document_extract_node.py @@ -16,7 +16,8 @@ class DocumentExtractNodeSerializer(serializers.Serializer): class IDocumentExtractNode(INode): type = 'document-extract-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, - WorkflowMode.KNOWLEDGE] + WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return DocumentExtractNodeSerializer diff --git a/apps/application/flow/step_node/document_split_node/i_document_split_node.py b/apps/application/flow/step_node/document_split_node/i_document_split_node.py index 6834238d3d0..e4a1c809fb1 100644 --- a/apps/application/flow/step_node/document_split_node/i_document_split_node.py +++ b/apps/application/flow/step_node/document_split_node/i_document_split_node.py @@ -72,7 +72,8 @@ class DocumentSplitNodeSerializer(serializers.Serializer): class IDocumentSplitNode(INode): type = 'document-split-node' support = [ - WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE + WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE, + WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP ] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: diff --git a/apps/application/flow/step_node/form_node/i_form_node.py b/apps/application/flow/step_node/form_node/i_form_node.py index a47a75ddcac..9be117f857f 100644 --- a/apps/application/flow/step_node/form_node/i_form_node.py +++ b/apps/application/flow/step_node/form_node/i_form_node.py @@ -25,7 +25,7 @@ class FormNodeParamsSerializer(serializers.Serializer): class IFormNode(INode): type = 'form-node' view_type = 'single_view' - support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP] + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FormNodeParamsSerializer diff --git a/apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py b/apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py index a3528d5fb13..13d227bc87f 100644 --- a/apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py +++ b/apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py @@ -33,14 +33,15 @@ class ImageGenerateNodeSerializer(serializers.Serializer): class IImageGenerateNode(INode): type = 'image-generate-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ImageGenerateNodeSerializer def _run(self): - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( - self.workflow_manage.flow.workflow_mode): + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( + self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: diff --git a/apps/application/flow/step_node/image_to_video_step_node/i_image_to_video_node.py b/apps/application/flow/step_node/image_to_video_step_node/i_image_to_video_node.py index 8e4aa9f0d33..adc6731ee0e 100644 --- a/apps/application/flow/step_node/image_to_video_step_node/i_image_to_video_node.py +++ b/apps/application/flow/step_node/image_to_video_step_node/i_image_to_video_node.py @@ -36,7 +36,8 @@ class ImageToVideoNodeSerializer(serializers.Serializer): class IImageToVideoNode(INode): type = 'image-to-video-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ImageToVideoNodeSerializer @@ -57,11 +58,12 @@ def _run(self): if k not in ['first_frame_url', 'last_frame_url']} if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): - return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url, **node_params_data, **self.flow_params_serializer.data, + return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url, **node_params_data, + **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url, - **node_params_data, **self.flow_params_serializer.data) + **node_params_data, **self.flow_params_serializer.data) def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, diff --git a/apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py b/apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py index 325ef935fe8..aa2932689a1 100644 --- a/apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py +++ b/apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py @@ -32,7 +32,7 @@ class ImageUnderstandNodeSerializer(serializers.Serializer): class IImageUnderstandNode(INode): type = 'image-understand-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ImageUnderstandNodeSerializer @@ -40,10 +40,11 @@ def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: def _run(self): res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('image_list')[0], self.node_params_serializer.data.get('image_list')[1:]) - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( - self.workflow_manage.flow.workflow_mode): + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( + self.workflow_manage.flow.workflow_mode): return self.execute(image=res, **self.node_params_serializer.data, **self.flow_params_serializer.data, - **{'history_chat_record': [], 'stream': True, 'chat_record_id': None}) + **{'history_chat_record': [], 'stream': True, 'chat_record_id': None}) else: return self.execute(image=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) diff --git a/apps/application/flow/step_node/intent_node/i_intent_node.py b/apps/application/flow/step_node/intent_node/i_intent_node.py index d609359d471..de09826b392 100644 --- a/apps/application/flow/step_node/intent_node/i_intent_node.py +++ b/apps/application/flow/step_node/intent_node/i_intent_node.py @@ -28,7 +28,7 @@ class IntentNodeSerializer(serializers.Serializer): class IIntentNode(INode): type = 'intent-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def save_context(self, details, workflow_manage): pass @@ -41,8 +41,9 @@ def _run(self): self.node_params_serializer.data.get('content_list')[0], self.node_params_serializer.data.get('content_list')[1:], ) - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( - self.workflow_manage.flow.workflow_mode): + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( + self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None, 'user_input': str(question)}) diff --git a/apps/application/flow/step_node/loop_break_node/i_loop_break_node.py b/apps/application/flow/step_node/loop_break_node/i_loop_break_node.py index bc3bf739a35..07edf227b53 100644 --- a/apps/application/flow/step_node/loop_break_node/i_loop_break_node.py +++ b/apps/application/flow/step_node/loop_break_node/i_loop_break_node.py @@ -29,7 +29,7 @@ class LoopBreakNodeSerializer(serializers.Serializer): class ILoopBreakNode(INode): type = 'loop-break-node' - support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP] + support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return LoopBreakNodeSerializer diff --git a/apps/application/flow/step_node/loop_continue_node/i_loop_continue_node.py b/apps/application/flow/step_node/loop_continue_node/i_loop_continue_node.py index efb21437d46..00b6aa04c39 100644 --- a/apps/application/flow/step_node/loop_continue_node/i_loop_continue_node.py +++ b/apps/application/flow/step_node/loop_continue_node/i_loop_continue_node.py @@ -28,7 +28,7 @@ class LoopContinueNodeSerializer(serializers.Serializer): class ILoopContinueNode(INode): type = 'loop-continue-node' - support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP] + support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return LoopContinueNodeSerializer diff --git a/apps/application/flow/step_node/loop_node/i_loop_node.py b/apps/application/flow/step_node/loop_node/i_loop_node.py index fb0fd914778..e16dbebc059 100644 --- a/apps/application/flow/step_node/loop_node/i_loop_node.py +++ b/apps/application/flow/step_node/loop_node/i_loop_node.py @@ -41,7 +41,7 @@ def is_valid(self, *, raise_exception=False): class ILoopNode(INode): type = 'loop-node' - support = [WorkflowMode.APPLICATION, WorkflowMode.KNOWLEDGE] + support = [WorkflowMode.APPLICATION, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ILoopNodeSerializer diff --git a/apps/application/flow/step_node/loop_start_node/i_loop_start_node.py b/apps/application/flow/step_node/loop_start_node/i_loop_start_node.py index 3c50b33b2df..7c3ffa31413 100644 --- a/apps/application/flow/step_node/loop_start_node/i_loop_start_node.py +++ b/apps/application/flow/step_node/loop_start_node/i_loop_start_node.py @@ -12,7 +12,7 @@ class ILoopStarNode(INode): type = 'loop-start-node' - support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP] + support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP] def _run(self): return self.execute(**self.flow_params_serializer.data) diff --git a/apps/application/flow/step_node/mcp_node/i_mcp_node.py b/apps/application/flow/step_node/mcp_node/i_mcp_node.py index d0a7be13322..6dd3827d640 100644 --- a/apps/application/flow/step_node/mcp_node/i_mcp_node.py +++ b/apps/application/flow/step_node/mcp_node/i_mcp_node.py @@ -21,7 +21,7 @@ class McpNodeSerializer(serializers.Serializer): class IMcpNode(INode): type = 'mcp-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return McpNodeSerializer diff --git a/apps/application/flow/step_node/parameter_extraction_node/i_parameter_extraction_node.py b/apps/application/flow/step_node/parameter_extraction_node/i_parameter_extraction_node.py index a0a9bc5cb59..f36cd4c26ce 100644 --- a/apps/application/flow/step_node/parameter_extraction_node/i_parameter_extraction_node.py +++ b/apps/application/flow/step_node/parameter_extraction_node/i_parameter_extraction_node.py @@ -25,7 +25,7 @@ class VariableSplittingNodeParamsSerializer(serializers.Serializer): class IParameterExtractionNode(INode): type = 'parameter-extraction-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableSplittingNodeParamsSerializer diff --git a/apps/application/flow/step_node/question_node/i_question_node.py b/apps/application/flow/step_node/question_node/i_question_node.py index 19399976054..63d5454a4bd 100644 --- a/apps/application/flow/step_node/question_node/i_question_node.py +++ b/apps/application/flow/step_node/question_node/i_question_node.py @@ -33,7 +33,7 @@ class QuestionNodeSerializer(serializers.Serializer): class IQuestionNode(INode): type = 'question-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return QuestionNodeSerializer diff --git a/apps/application/flow/step_node/reranker_node/i_reranker_node.py b/apps/application/flow/step_node/reranker_node/i_reranker_node.py index 0421c55b00a..2829eb50e5f 100644 --- a/apps/application/flow/step_node/reranker_node/i_reranker_node.py +++ b/apps/application/flow/step_node/reranker_node/i_reranker_node.py @@ -42,7 +42,7 @@ def is_valid(self, *, raise_exception=False): class IRerankerNode(INode): type = 'reranker-node' - support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP] + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return RerankerStepNodeSerializer diff --git a/apps/application/flow/step_node/search_document_node/i_search_document_node.py b/apps/application/flow/step_node/search_document_node/i_search_document_node.py index 2d55ef64bae..0a2c99a1e71 100644 --- a/apps/application/flow/step_node/search_document_node/i_search_document_node.py +++ b/apps/application/flow/step_node/search_document_node/i_search_document_node.py @@ -43,7 +43,7 @@ def is_valid(self, *, raise_exception=False): class ISearchDocumentStepNode(INode): type = 'search-document-node' - support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP] + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return SearchDocumentStepNodeSerializer diff --git a/apps/application/flow/step_node/search_knowledge_node/i_search_knowledge_node.py b/apps/application/flow/step_node/search_knowledge_node/i_search_knowledge_node.py index 17da82a4a27..0cf23cb5e5d 100644 --- a/apps/application/flow/step_node/search_knowledge_node/i_search_knowledge_node.py +++ b/apps/application/flow/step_node/search_knowledge_node/i_search_knowledge_node.py @@ -68,7 +68,7 @@ def get_paragraph_list(chat_record, node_id): class ISearchKnowledgeStepNode(INode): type = 'search-knowledge-node' - support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP] + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return SearchDatasetStepNodeSerializer diff --git a/apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py b/apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py index a071b46d1cb..acb82affa90 100644 --- a/apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py +++ b/apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py @@ -24,7 +24,7 @@ class SpeechToTextNodeSerializer(serializers.Serializer): class ISpeechToTextNode(INode): type = 'speech-to-text-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP,WorkflowMode.TOOL,WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return SpeechToTextNodeSerializer diff --git a/apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py b/apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py index 975a6e98166..a1047dc71c1 100644 --- a/apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py +++ b/apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py @@ -24,7 +24,7 @@ class TextToSpeechNodeSerializer(serializers.Serializer): class ITextToSpeechNode(INode): type = 'text-to-speech-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return TextToSpeechNodeSerializer diff --git a/apps/application/flow/step_node/text_to_video_step_node/i_text_to_video_node.py b/apps/application/flow/step_node/text_to_video_step_node/i_text_to_video_node.py index e596f00d1cb..66d57e74c78 100644 --- a/apps/application/flow/step_node/text_to_video_step_node/i_text_to_video_node.py +++ b/apps/application/flow/step_node/text_to_video_step_node/i_text_to_video_node.py @@ -33,14 +33,15 @@ class TextToVideoNodeSerializer(serializers.Serializer): class ITextToVideoNode(INode): type = 'text-to-video-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return TextToVideoNodeSerializer def _run(self): - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( - self.workflow_manage.flow.workflow_mode): + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( + self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: diff --git a/apps/application/flow/step_node/tool_lib_node/i_tool_lib_node.py b/apps/application/flow/step_node/tool_lib_node/i_tool_lib_node.py index e1d9335ce3d..08f3e3a845d 100644 --- a/apps/application/flow/step_node/tool_lib_node/i_tool_lib_node.py +++ b/apps/application/flow/step_node/tool_lib_node/i_tool_lib_node.py @@ -42,7 +42,7 @@ def is_valid(self, *, raise_exception=False): class IToolLibNode(INode): type = 'tool-lib-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FunctionLibNodeParamsSerializer diff --git a/apps/application/flow/step_node/tool_node/i_tool_node.py b/apps/application/flow/step_node/tool_node/i_tool_node.py index 1b34e96fee9..4f8343a67db 100644 --- a/apps/application/flow/step_node/tool_node/i_tool_node.py +++ b/apps/application/flow/step_node/tool_node/i_tool_node.py @@ -54,7 +54,7 @@ def is_valid(self, *, raise_exception=False): class IToolNode(INode): type = 'tool-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FunctionNodeParamsSerializer diff --git a/apps/application/flow/step_node/tool_start_node/__init__.py b/apps/application/flow/step_node/tool_start_node/__init__.py new file mode 100644 index 00000000000..98a1afcd904 --- /dev/null +++ b/apps/application/flow/step_node/tool_start_node/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: __init__.py + @date:2024/6/11 15:30 + @desc: +""" +from .impl import * diff --git a/apps/application/flow/step_node/tool_start_node/i_tool_start_node.py b/apps/application/flow/step_node/tool_start_node/i_tool_start_node.py new file mode 100644 index 00000000000..ca313277376 --- /dev/null +++ b/apps/application/flow/step_node/tool_start_node/i_tool_start_node.py @@ -0,0 +1,21 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: i_start_node.py + @date:2024/6/3 16:54 + @desc: +""" +from application.flow.common import WorkflowMode +from application.flow.i_step_node import INode, NodeResult + + +class IToolStartNode(INode): + type = 'tool-start-node' + support = [WorkflowMode.TOOL] + + def _run(self): + return self.execute(**self.flow_params_serializer.data) + + def execute(self, **kwargs) -> NodeResult: + pass diff --git a/apps/application/flow/step_node/tool_start_node/impl/__init__.py b/apps/application/flow/step_node/tool_start_node/impl/__init__.py new file mode 100644 index 00000000000..6fcd243dc5c --- /dev/null +++ b/apps/application/flow/step_node/tool_start_node/impl/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: __init__.py + @date:2024/6/11 15:36 + @desc: +""" +from .base_tool_start_node import BaseToolStartStepNode diff --git a/apps/application/flow/step_node/tool_start_node/impl/base_tool_start_node.py b/apps/application/flow/step_node/tool_start_node/impl/base_tool_start_node.py new file mode 100644 index 00000000000..05a2675eb68 --- /dev/null +++ b/apps/application/flow/step_node/tool_start_node/impl/base_tool_start_node.py @@ -0,0 +1,66 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: base_start_node.py + @date:2024/6/3 17:17 + @desc: +""" +from typing import Type + +from rest_framework import serializers + +from application.flow.i_step_node import NodeResult +from application.flow.step_node.tool_start_node.i_tool_start_node import IToolStartNode + + +class BaseToolStartStepNode(IToolStartNode): + def save_context(self, details, workflow_manage): + base_node = self.workflow_manage.get_base_node() + workflow_variable = {} + self.context['exception_message'] = details.get('err_message') + self.status = details.get('status') + self.err_message = details.get('err_message') + for key, value in workflow_variable.items(): + workflow_manage.context[key] = value + for item in details.get('global_fields', []): + workflow_manage.context[item.get('key')] = item.get('value') + + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: + pass + + def execute(self, **kwargs) -> NodeResult: + base_node = self.workflow_manage.get_base_node() + global_value = {} + params = self.workflow_manage.get_body() + for item in base_node.properties.get('user_input_field_list', []): + global_value[item.get('field')] = params[item.get('field')] + + self.workflow_manage.out_context = { + item.get('field'): None + for item in base_node.properties.get('user_output_field_list', []) + if item.get('default_value', None) is not None + } + return NodeResult({}, global_value) + + def get_details(self, index: int, **kwargs): + global_fields = [] + for field in self.node.properties.get('config')['globalFields']: + key = field['value'] + global_fields.append({ + 'label': field['label'], + 'key': key, + 'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else '' + }) + return { + 'name': self.node.properties.get('stepName'), + "index": index, + "question": self.context.get('question'), + 'run_time': self.context.get('run_time'), + 'type': self.node.type, + 'status': self.status, + 'err_message': self.err_message, + 'global_fields': global_fields, + '': '', + 'enableException': self.node.properties.get('enableException'), + } diff --git a/apps/application/flow/step_node/variable_aggregation_node/i_variable_aggregation_node.py b/apps/application/flow/step_node/variable_aggregation_node/i_variable_aggregation_node.py index 4878f0c4c0c..0e000e3d5ec 100644 --- a/apps/application/flow/step_node/variable_aggregation_node/i_variable_aggregation_node.py +++ b/apps/application/flow/step_node/variable_aggregation_node/i_variable_aggregation_node.py @@ -28,7 +28,8 @@ class VariableAggregationNodeSerializer(serializers.Serializer): class IVariableAggregation(INode): type = 'variable-aggregation-node' - support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP] + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableAggregationNodeSerializer diff --git a/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py index b65d8c81274..6652cbe9e9a 100644 --- a/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py @@ -17,7 +17,7 @@ class VariableAssignNodeParamsSerializer(serializers.Serializer): class IVariableAssignNode(INode): type = 'variable-assign-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableAssignNodeParamsSerializer diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 3ff1982f573..f1711009a47 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -31,6 +31,13 @@ def chat_evaluation(self, variable, value): else: self.workflow_manage.chat_context[variable['fields'][1]] = value + def out_evaluation(self, variable, value): + from application.flow.loop_workflow_manage import LoopWorkflowManage + if isinstance(self.workflow_manage, LoopWorkflowManage): + self.workflow_manage.parentWorkflowManage.out_context[variable['fields'][1]] = value + else: + self.workflow_manage.out_context[variable['fields'][1]] = value + def handle(self, variable, evaluation): result = { 'name': variable['name'], @@ -76,7 +83,9 @@ def execute(self, variable_list, **kwargs) -> NodeResult: if 'loop' == variable['fields'][0]: result = self.handle(variable, self.loop_evaluation) result_list.append(result) - + if 'output' == variable['fields'][0]: + result = self.handle(variable, self.out_evaluation) + result_list.append(result) if is_chat: from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): diff --git a/apps/application/flow/step_node/variable_splitting_node/i_variable_splitting_node.py b/apps/application/flow/step_node/variable_splitting_node/i_variable_splitting_node.py index 78983ca0a70..39c48f817be 100644 --- a/apps/application/flow/step_node/variable_splitting_node/i_variable_splitting_node.py +++ b/apps/application/flow/step_node/variable_splitting_node/i_variable_splitting_node.py @@ -20,7 +20,7 @@ class VariableSplittingNodeParamsSerializer(serializers.Serializer): class IVariableSplittingNode(INode): type = 'variable-splitting-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableSplittingNodeParamsSerializer diff --git a/apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py b/apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py index 0b362415d36..25d2971959c 100644 --- a/apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py +++ b/apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py @@ -31,7 +31,7 @@ class VideoUnderstandNodeSerializer(serializers.Serializer): class IVideoUnderstandNode(INode): type = 'video-understand-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, - WorkflowMode.KNOWLEDGE_LOOP] + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VideoUnderstandNodeSerializer @@ -40,10 +40,11 @@ def _run(self): res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('video_list')[0], self.node_params_serializer.data.get('video_list')[1:]) - if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( + if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, + WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data, - **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) + **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) diff --git a/apps/application/flow/tool_workflow_manage.py b/apps/application/flow/tool_workflow_manage.py index c7c2da4cf7c..431045d716a 100644 --- a/apps/application/flow/tool_workflow_manage.py +++ b/apps/application/flow/tool_workflow_manage.py @@ -9,7 +9,7 @@ from concurrent.futures import ThreadPoolExecutor from application.flow.common import Workflow -from application.flow.i_step_node import WorkFlowPostHandler +from application.flow.i_step_node import WorkFlowPostHandler, ToolFlowParamsSerializer from application.flow.workflow_manage import WorkflowManage from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse @@ -24,6 +24,17 @@ def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostH start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False): super().__init__(flow, params, work_flow_post_handler, base_to_response, form_data, None, None, None, None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted) + self.out_context = {} + + def get_params_serializer_class(self): + return ToolFlowParamsSerializer def get_start_node(self): return self.flow.get_node('tool-start-node') + + def get_base_node(self): + """ + 获取基础节点 + @return: + """ + return self.flow.get_node('tool-base-node') diff --git a/apps/tools/serializers/tool_workflow.py b/apps/tools/serializers/tool_workflow.py index 61fdfd18991..674cb375983 100644 --- a/apps/tools/serializers/tool_workflow.py +++ b/apps/tools/serializers/tool_workflow.py @@ -20,6 +20,8 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers, status +from application.flow.common import Workflow, WorkflowMode +from application.flow.i_step_node import ToolWorkflowPostHandler from application.flow.tool_workflow_manage import ToolWorkflowManage from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField @@ -217,7 +219,19 @@ class Operate(serializers.Serializer): def debug(self, instance: Dict, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - ToolWorkflowManage() + tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get("tool_id")).first() + work_flow_manage = ToolWorkflowManage( + Workflow.new_instance(tool_workflow.work_flow, WorkflowMode.TOOL), + { + 'tool_id': self.data.get("tool_id"), + 'stream': True, + 'workspace_id': self.data.get("workspace_id"), + **instance}, + ToolWorkflowPostHandler(None, self.data.get("tool_id")), + is_the_task_interrupted=lambda: False) + + r = work_flow_manage.run() + return r def publish(self, with_valid=True): if with_valid: diff --git a/apps/tools/urls.py b/apps/tools/urls.py index aceff0b22cc..68445f0a8cf 100644 --- a/apps/tools/urls.py +++ b/apps/tools/urls.py @@ -17,6 +17,7 @@ path('workspace//tool/upload_skill_file', views.ToolView.UploadSkillFile.as_view()), path('workspace//tool/', views.ToolView.Operate.as_view()), path('workspace//tool//publish', views.ToolWorkflowView.Publish.as_view()), + path('workspace//tool//debug', views.ToolWorkflowDebugView.as_view()), path('workspace//tool//workflow', views.ToolWorkflowView.Operate.as_view()), path('workspace//tool//workflow/export', views.ToolWorkflowView.Export.as_view()), path('workspace//tool//edit_icon', views.ToolView.EditIcon.as_view()), diff --git a/apps/tools/views/tool_workflow.py b/apps/tools/views/tool_workflow.py index dcf9c8f806c..3d2a08b8604 100644 --- a/apps/tools/views/tool_workflow.py +++ b/apps/tools/views/tool_workflow.py @@ -11,7 +11,6 @@ from common.log.log import log from common.result import result, DefaultResultSerializer from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi -from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer from tools.api.tool_workflow import ToolWorkflowApi, ToolWorkflowExportApi, ToolWorkflowImportApi from tools.serializers.tool_workflow import ToolWorkflowSerializer @@ -200,7 +199,7 @@ def get(self, request: Request, workspace_id: str, tool_id: str): ).one()) -class KnowledgeWorkflowActionView(APIView): +class ToolWorkflowDebugView(APIView): authentication_classes = [TokenAuth] @extend_schema( @@ -213,18 +212,18 @@ class KnowledgeWorkflowActionView(APIView): tags=[_('Tool')] # type: ignore ) @has_permissions( - PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), - PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), + PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), + PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], - [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], + [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND ), ) def post(self, request: Request, workspace_id: str, tool_id: str): - return result.success( - ToolWorkflowSerializer.Operate(data={'workspace_id': workspace_id, 'tool_id': tool_id}) - .debug(request.data, - request.user, - True)) + return ToolWorkflowSerializer.Operate( + data={'workspace_id': workspace_id, 'tool_id': tool_id, 'user_id': request.user.id}).debug( + request.data, + request.user, + True) diff --git a/ui/src/api/tool/tool.ts b/ui/src/api/tool/tool.ts index bfec9f715cf..177561f09c6 100644 --- a/ui/src/api/tool/tool.ts +++ b/ui/src/api/tool/tool.ts @@ -1,5 +1,5 @@ import { Result } from '@/request/Result' -import { get, post, del, put, exportFile } from '@/request/index' +import { get, post, del, put, exportFile, postStream } from '@/request/index' import { type Ref } from 'vue' import type { pageRequest } from '@/api/type/common' import type { AddInternalToolParam, toolData } from '@/api/type/tool' @@ -288,6 +288,17 @@ const publish: (tool_id: string, loading?: Ref) => Promise> ) => { return put(`${prefix.value}/${tool_id}/publish`, {}, {}, loading) } + +/** + * 调试工作流 + * @param 参数 + * chat_id: string + * data + */ +const debugToolWorkflow: (tool_id: string, data: any) => Promise = (tool_id, data) => { + const p = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' + return postStream(`${p}${prefix.value}/${tool_id}/debug`, data) +} export default { getToolList, getAllToolList, @@ -314,4 +325,5 @@ export default { updateToolWorkflowVersion, publish, exportToolWorkflow, + debugToolWorkflow, } diff --git a/ui/src/components/workflow-dropdown-menu/tool/index.vue b/ui/src/components/workflow-dropdown-menu/tool/index.vue index 360dd2d602b..ec75a3e750a 100644 --- a/ui/src/components/workflow-dropdown-menu/tool/index.vue +++ b/ui/src/components/workflow-dropdown-menu/tool/index.vue @@ -113,9 +113,7 @@ const workflowModel = inject('workflowMode') as WorkflowMode const route = useRoute() const { user, folder } = useStore() -const menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)?.filter( - (item, index) => index > 0, -) +const menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application) const search_text = ref('') const props = defineProps({ show: { diff --git a/ui/src/views/tool-workflow/component/debug-drawer/index.vue b/ui/src/views/tool-workflow/component/debug-drawer/index.vue index 53a2df82aba..83f81e1879c 100644 --- a/ui/src/views/tool-workflow/component/debug-drawer/index.vue +++ b/ui/src/views/tool-workflow/component/debug-drawer/index.vue @@ -1,18 +1,30 @@ + diff --git a/ui/src/workflow/common/data.ts b/ui/src/workflow/common/data.ts index 3ff690312cf..60ba0f65398 100644 --- a/ui/src/workflow/common/data.ts +++ b/ui/src/workflow/common/data.ts @@ -997,7 +997,6 @@ const toolMenuNodes = [ }, ] export const getMenuNodes = (workflowMode: WorkflowMode) => { - console.log(workflowMode) if (workflowMode == WorkflowMode.Application) { return menuNodes } diff --git a/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue index 1b1bd26140a..84f6ca46483 100644 --- a/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue +++ b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue @@ -129,10 +129,10 @@ function onDragHandle() { onMounted(() => { if (props.nodeModel.properties.user_input_config) { - inputFieldConfig.value = props.nodeModel.properties.user_input_config + inputFieldConfig.value = cloneDeep(props.nodeModel.properties.user_input_config) } if (props.nodeModel.properties.user_input_field_list) { - inputFieldList.value = props.nodeModel.properties.user_input_field_list + inputFieldList.value = cloneDeep(props.nodeModel.properties.user_input_field_list) } props.nodeModel.graphModel.eventCenter.emit('refreshFieldList') onDragHandle() diff --git a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue index c8e141a56fb..f05b4038602 100644 --- a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue +++ b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue @@ -67,9 +67,8 @@ function openChangeTitleDialog() { } function deleteField(index: any) { - inputFieldList.value.splice(index, 1) - props.nodeModel.graphModel.eventCenter.emit('refreshFieldList') - onDragHandle() + inputFieldList.value = inputFieldList.value.filter((item, i) => i !== index) + set(props.nodeModel.properties, 'user_output_field_list', inputFieldList.value) } const currentIndex = ref(null) function refreshFieldList(data: any) { @@ -117,13 +116,11 @@ function onDragHandle() { onMounted(() => { if (props.nodeModel.properties.user_output_config) { - outputFieldConfig.value = props.nodeModel.properties.user_output_config + outputFieldConfig.value = cloneDeep(props.nodeModel.properties.user_output_config) } if (props.nodeModel.properties.user_output_field_list) { - inputFieldList.value = props.nodeModel.properties.user_output_field_list + inputFieldList.value = cloneDeep(props.nodeModel.properties.user_output_field_list) } - set(props.nodeModel.properties, 'user_output_field_list', inputFieldList.value) - set(props.nodeModel.properties, 'user_output_config', outputFieldConfig) onDragHandle() }) From 906629694af1c660db3cd6f12200970ea7b25089 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 Date: Fri, 13 Mar 2026 16:48:19 +0800 Subject: [PATCH 05/12] feat: tool_workflow init --- .../nodes/tool-base-node/component/input/InputFieldTable.vue | 1 + .../nodes/tool-base-node/component/output/OutputFieldTable.vue | 1 + 2 files changed, 2 insertions(+) diff --git a/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue index 84f6ca46483..fe48f37c81e 100644 --- a/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue +++ b/ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue @@ -93,6 +93,7 @@ function refreshFieldList(data: any) { } inputFieldList.value?.push(data) } + set(props.nodeModel.properties, 'user_input_field_list', cloneDeep(inputFieldList.value)) props.nodeModel.graphModel.eventCenter.emit('refreshFieldList') props.nodeModel.graphModel.getNodeModelById('tool-start-node').clear_next_node_field(true) inputFieldFormDialogRef.value?.close() diff --git a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue index f05b4038602..16da75aa779 100644 --- a/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue +++ b/ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue @@ -81,6 +81,7 @@ function refreshFieldList(data: any) { } inputFieldList.value?.push(data) } + set(props.nodeModel.properties, 'user_output_field_list', cloneDeep(inputFieldList.value)) inputFieldFormDialogRef.value?.close() props.nodeModel.graphModel.getNodeModelById('tool-start-node').clear_next_node_field(true) currentIndex.value = null From b90a1c90361cc3b7c6c46f35b2dc2ec90ca166ce Mon Sep 17 00:00:00 2001 From: zhangzhanwei Date: Mon, 16 Mar 2026 11:28:36 +0800 Subject: [PATCH 06/12] feat: Tool workflow node --- apps/tools/serializers/tool.py | 7 +- apps/tools/views/tool.py | 1 + ui/src/assets/tool/icon_tool_workflow.svg | 15 ++ .../application/index.vue | 29 ++- .../knowledge-inner/index.vue | 16 +- .../knowledge/index.vue | 16 +- .../workflow-dropdown-menu/tool/index.vue | 15 +- ui/src/enums/application.ts | 1 + ui/src/workflow/common/NodeContainer.vue | 7 +- ui/src/workflow/common/data.ts | 22 ++ .../icons/tool-workflow-lib-node-icon.vue | 6 + .../nodes/tool-workflow-lib-node/index.ts | 12 + .../nodes/tool-workflow-lib-node/index.vue | 209 ++++++++++++++++++ 13 files changed, 338 insertions(+), 18 deletions(-) create mode 100644 ui/src/assets/tool/icon_tool_workflow.svg create mode 100644 ui/src/workflow/icons/tool-workflow-lib-node-icon.vue create mode 100644 ui/src/workflow/nodes/tool-workflow-lib-node/index.ts create mode 100644 ui/src/workflow/nodes/tool-workflow-lib-node/index.vue diff --git a/apps/tools/serializers/tool.py b/apps/tools/serializers/tool.py index 8fe4ff36e72..7117fd9e68e 100644 --- a/apps/tools/serializers/tool.py +++ b/apps/tools/serializers/tool.py @@ -1066,6 +1066,7 @@ class Query(serializers.Serializer): user_id = serializers.UUIDField(required=False, allow_null=True, label=_('user id')) scope = serializers.CharField(required=True, label=_('scope')) tool_type = serializers.CharField(required=False, label=_('tool type'), allow_null=True, allow_blank=True) + tool_type_list = serializers.ListField(child=serializers.CharField(),required=False, label=_('tool type list'), allow_null=True, allow_empty=True) create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True) def page_tool(self, current_page: int, page_size: int): @@ -1127,7 +1128,11 @@ def get_query_set(self, workspace_manage, is_x_pack_ee): if scope is not None: tool_query_set = tool_query_set.filter(scope=scope) - if tool_type: + + tool_type_list = self.data.get('tool_type_list') + if tool_type_list: + tool_query_set = tool_query_set.filter(tool_type__in=tool_type_list) + elif tool_type: tool_query_set = tool_query_set.filter(tool_type=tool_type) query_set_dict = { diff --git a/apps/tools/views/tool.py b/apps/tools/views/tool.py index ec08b0e4fc1..0fe2dc616f6 100644 --- a/apps/tools/views/tool.py +++ b/apps/tools/views/tool.py @@ -74,6 +74,7 @@ def get(self, request: Request, workspace_id: str): 'name': request.query_params.get('name'), 'scope': request.query_params.get('scope', ToolScope.WORKSPACE), 'tool_type': request.query_params.get('tool_type'), + 'tool_type_list': request.query_params.getlist('tool_type_list[]'), 'user_id': request.user.id, 'create_user': request.query_params.get('create_user'), } diff --git a/ui/src/assets/tool/icon_tool_workflow.svg b/ui/src/assets/tool/icon_tool_workflow.svg new file mode 100644 index 00000000000..1e53f7b475e --- /dev/null +++ b/ui/src/assets/tool/icon_tool_workflow.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/ui/src/components/workflow-dropdown-menu/application/index.vue b/ui/src/components/workflow-dropdown-menu/application/index.vue index 001d75c42d7..e2752e63abc 100644 --- a/ui/src/components/workflow-dropdown-menu/application/index.vue +++ b/ui/src/components/workflow-dropdown-menu/application/index.vue @@ -89,8 +89,22 @@ @@ -122,7 +136,12 @@ diff --git a/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts b/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts new file mode 100644 index 00000000000..fea8d415d94 --- /dev/null +++ b/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts @@ -0,0 +1,12 @@ +import ToolWorkflowLibNodeVue from './index.vue' +import { AppNode, AppNodeModel } from '@/workflow/common/app-node' +class ToolWorkflowLibNode extends AppNode { + constructor(props: any) { + super(props, ToolWorkflowLibNodeVue) + } +} +export default { + type: 'tool-workflow-lib-node', + model: AppNodeModel, + view: ToolWorkflowLibNode, +} diff --git a/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue b/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue new file mode 100644 index 00000000000..a04796c15a9 --- /dev/null +++ b/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue @@ -0,0 +1,209 @@ + + + From 33a119cd9e84f1d0832752cc81cf1cd6f79c6d94 Mon Sep 17 00:00:00 2001 From: zhangzhanwei Date: Mon, 16 Mar 2026 15:46:37 +0800 Subject: [PATCH 07/12] fix: Tool workflow node result --- ui/src/workflow/common/data.ts | 4 ---- .../nodes/tool-workflow-lib-node/index.ts | 3 +++ .../nodes/tool-workflow-lib-node/index.vue | 22 +++++++++---------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ui/src/workflow/common/data.ts b/ui/src/workflow/common/data.ts index e8365e2b825..ea80cb8860a 100644 --- a/ui/src/workflow/common/data.ts +++ b/ui/src/workflow/common/data.ts @@ -1050,10 +1050,6 @@ export const toolWorkflowLibNode = { stepName: t('workflow.nodes.toolWorlflowNode.label','工作流工具'), config: { fields: [ - { - label: t('common.result'), - value: 'result', - }, ], }, }, diff --git a/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts b/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts index fea8d415d94..bb896f90846 100644 --- a/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts +++ b/ui/src/workflow/nodes/tool-workflow-lib-node/index.ts @@ -4,6 +4,9 @@ class ToolWorkflowLibNode extends AppNode { constructor(props: any) { super(props, ToolWorkflowLibNodeVue) } + getConfig(props: any) { + return props.model.properties.config + } } export default { type: 'tool-workflow-lib-node', diff --git a/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue b/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue index a04796c15a9..91933a99807 100644 --- a/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue +++ b/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue @@ -150,8 +150,6 @@ const update_field = () => { loadSharedApi({ type: 'tool', systemType: apiType.value }) .getToolById(props.nodeModel.properties.node_data.tool_lib_id) .then((ok: any) => { - console.log('ssss', ok.data) - const workflowNodes = ok.data?.work_flow?.nodes || [] const baseNode = workflowNodes.find((n: any) => n.type === 'tool-base-node') @@ -159,17 +157,14 @@ const update_field = () => { const new_input_list = baseNode.properties.user_input_field_list || [] const new_output_list = baseNode.properties.user_output_field_list || [] - let config_field_list: any[] = [] - if (new_output_list.length > 0) { - config_field_list = new_output_list.map((item: any) => ({ - label: item.label, - value: item.field, - })) - } + const old_config_fields = props.nodeModel.properties.config?.fields || [] + const config_field_list = new_output_list.map((item: any) => { + const old = old_config_fields.find((o: any) => o.value === item.field) + return old ? JSON.parse(JSON.stringify(old)) : { label: item.label, value: item.field } + }) const input_title = baseNode.properties.user_input_config?.title const output_title = baseNode.properties.user_output_config?.title - const old_input_list = props.nodeModel.properties.node_data.input_field_list || [] const merged_input_list = new_input_list.map((item: any) => { const find_field = old_input_list.find((old_item: any) => old_item.field === item.field) @@ -185,11 +180,14 @@ const update_field = () => { }) set(props.nodeModel.properties.node_data, 'input_field_list', merged_input_list) - set(props.nodeModel.properties.config, 'fields', config_field_list) + set(props.nodeModel.properties, 'config', { + fields: config_field_list, + output_title: output_title, + }) set(props.nodeModel.properties.node_data, 'input_title', input_title) - set(props.nodeModel.properties.config, 'output_title', output_title) } set(props.nodeModel.properties, 'status', ok.data.is_active ? 200 : 500) + props.nodeModel.clear_next_node_field(true) }) .catch(() => { set(props.nodeModel.properties, 'status', 500) From 2fe7b3b04f6a869608c4ab944112ab53f4fae3eb Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:29:44 +0800 Subject: [PATCH 08/12] feat: Tool workflow node execution (#4895) --- apps/application/flow/i_step_node.py | 14 +- .../flow/knowledge_loop_workflow_manage.py | 6 + .../flow/knowledge_workflow_manage.py | 6 + apps/application/flow/loop_workflow_manage.py | 6 + apps/application/flow/step_node/__init__.py | 3 +- .../form_node/impl/base_form_node.py | 5 +- .../loop_node/impl/base_loop_node.py | 10 +- .../tool_workflow_lib_node/__init__.py | 9 + .../i_tool_workflow_lib_node.py | 57 +++++ .../tool_workflow_lib_node/impl/__init__.py | 9 + .../impl/base_tool_workflow_lib_node.py | 214 ++++++++++++++++++ .../flow/tool_loop_workflow_manage.py | 21 ++ apps/application/flow/tool_workflow_manage.py | 15 ++ apps/application/flow/workflow_manage.py | 10 +- apps/application/serializers/common.py | 69 ++++++ apps/common/constants/cache_version.py | 2 + apps/tools/models/tool.py | 1 + apps/tools/serializers/tool_workflow.py | 30 ++- .../component/debug/result/index.vue | 52 +++-- .../workflow/nodes/tool-start-node/index.ts | 2 +- .../nodes/tool-workflow-lib-node/index.vue | 3 + 21 files changed, 513 insertions(+), 31 deletions(-) create mode 100644 apps/application/flow/step_node/tool_workflow_lib_node/__init__.py create mode 100644 apps/application/flow/step_node/tool_workflow_lib_node/i_tool_workflow_lib_node.py create mode 100644 apps/application/flow/step_node/tool_workflow_lib_node/impl/__init__.py create mode 100644 apps/application/flow/step_node/tool_workflow_lib_node/impl/base_tool_workflow_lib_node.py create mode 100644 apps/application/flow/tool_loop_workflow_manage.py diff --git a/apps/application/flow/i_step_node.py b/apps/application/flow/i_step_node.py index aa1c5266ec1..cb13aa1769a 100644 --- a/apps/application/flow/i_step_node.py +++ b/apps/application/flow/i_step_node.py @@ -123,7 +123,19 @@ def __init__(self, chat_info, tool_id): def handler(self, workflow): state = get_workflow_state(workflow) - ToolRecord(tool_id=self.tool_id) + record = ToolRecord(id=self.chat_info.tool_record_id, tool_id=self.tool_id, + workspace_id=self.chat_info.workspace_id, + source_type=self.chat_info.source_type, + source_id=self.chat_info.source_id, + state=state, + meta={ + 'output': workflow.out_context, + 'details': workflow.get_runtime_details(), + 'answer_text_list': workflow.get_answer_text_list() + }) + self.chat_info.set_record(record) + self.chat_info = None + self.tool_id = None def get_loop_workflow_node(node_list): diff --git a/apps/application/flow/knowledge_loop_workflow_manage.py b/apps/application/flow/knowledge_loop_workflow_manage.py index fbda319c1ad..31d3ab4df25 100644 --- a/apps/application/flow/knowledge_loop_workflow_manage.py +++ b/apps/application/flow/knowledge_loop_workflow_manage.py @@ -13,3 +13,9 @@ class KnowledgeLoopWorkflowManage(LoopWorkflowManage): def get_params_serializer_class(self): return KnowledgeFlowParamsSerializer + + def get_source_type(self): + return "KNOWLEDGE" + + def get_source_id(self): + return self.params.get('knowledge_id') diff --git a/apps/application/flow/knowledge_workflow_manage.py b/apps/application/flow/knowledge_workflow_manage.py index 4a19803a367..98212c9ee5a 100644 --- a/apps/application/flow/knowledge_workflow_manage.py +++ b/apps/application/flow/knowledge_workflow_manage.py @@ -122,3 +122,9 @@ def hand_node_result(self, current_node, node_result_future): current_node.node_chunk.end() QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update( details=self.get_runtime_details()) + + def get_source_type(self): + return "KNOWLEDGE" + + def get_source_id(self): + return self.params.get('knowledge_id') diff --git a/apps/application/flow/loop_workflow_manage.py b/apps/application/flow/loop_workflow_manage.py index 27c84f4dc66..c236b15dcc5 100644 --- a/apps/application/flow/loop_workflow_manage.py +++ b/apps/application/flow/loop_workflow_manage.py @@ -191,3 +191,9 @@ def generate_prompt(self, prompt: str): prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2') value = prompt_template.format(context=context) return value + + def get_source_type(self): + return "APPLICATION" + + def get_source_id(self): + return self.params.get('application_id') diff --git a/apps/application/flow/step_node/__init__.py b/apps/application/flow/step_node/__init__.py index 01b9a83570a..4c38020771e 100644 --- a/apps/application/flow/step_node/__init__.py +++ b/apps/application/flow/step_node/__init__.py @@ -35,6 +35,7 @@ from .text_to_video_step_node.impl.base_text_to_video_node import BaseTextToVideoNode from .tool_lib_node import * from .tool_node import * +from .tool_workflow_lib_node import BaseToolWorkflowLibNodeNode from .variable_aggregation_node.impl.base_variable_aggregation_node import BaseVariableAggregationNode from .variable_assign_node import BaseVariableAssignNode from .variable_splitting_node import BaseVariableSplittingNode @@ -53,7 +54,7 @@ BaseLoopContinueNode, BaseLoopBreakNode, BaseVariableSplittingNode, BaseParameterExtractionNode, BaseVariableAggregationNode, BaseDataSourceLocalNode, BaseDataSourceWebNode, BaseKnowledgeWriteNode, BaseDocumentSplitNode, - BaseToolStartStepNode] + BaseToolStartStepNode, BaseToolWorkflowLibNodeNode] node_map = {n.type: {w: n for w in n.support} for n in node_list} diff --git a/apps/application/flow/step_node/form_node/impl/base_form_node.py b/apps/application/flow/step_node/form_node/impl/base_form_node.py index 930125124fa..30dfa97229b 100644 --- a/apps/application/flow/step_node/form_node/impl/base_form_node.py +++ b/apps/application/flow/step_node/form_node/impl/base_form_node.py @@ -144,8 +144,9 @@ def get_answer_list(self) -> List[Answer] | None: value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id, chat_record_id=self.flow_params_serializer.data.get("chat_record_id"), form_field_list=form_field_list) - return [Answer(value, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], None, - self.runtime_node_id, '')] + return [ + Answer(value, self.view_type, self.runtime_node_id, self.workflow_params.get('chat_record_id') or '', None, + self.runtime_node_id, '')] def get_details(self, index: int, **kwargs): form_content_format = self.context.get('form_content_format') diff --git a/apps/application/flow/step_node/loop_node/impl/base_loop_node.py b/apps/application/flow/step_node/loop_node/impl/base_loop_node.py index 7ffbaf26d58..6b03d628664 100644 --- a/apps/application/flow/step_node/loop_node/impl/base_loop_node.py +++ b/apps/application/flow/step_node/loop_node/impl/base_loop_node.py @@ -268,10 +268,16 @@ def get_loop_context(self): def execute(self, loop_type, array, number, loop_body, **kwargs) -> NodeResult: from application.flow.loop_workflow_manage import LoopWorkflowManage, Workflow from application.flow.knowledge_loop_workflow_manage import KnowledgeLoopWorkflowManage + from application.flow.tool_loop_workflow_manage import ToolLoopWorkflowManage def workflow_manage_new_instance(loop_data, global_data, start_node_id=None, start_node_data=None, chat_record=None, child_node=None): - workflow_mode = WorkflowMode.KNOWLEDGE_LOOP if WorkflowMode.KNOWLEDGE == self.workflow_manage.flow.workflow_mode else WorkflowMode.APPLICATION_LOOP - c = KnowledgeLoopWorkflowManage if workflow_mode == WorkflowMode.KNOWLEDGE_LOOP else LoopWorkflowManage + workflow_mode = {WorkflowMode.APPLICATION: WorkflowMode.APPLICATION_LOOP, + WorkflowMode.KNOWLEDGE: WorkflowMode.KNOWLEDGE_LOOP, + WorkflowMode.TOOL: WorkflowMode.TOOL_LOOP}.get( + self.workflow_manage.flow.workflow_mode) or WorkflowMode.APPLICATION + c = {WorkflowMode.APPLICATION_LOOP: LoopWorkflowManage, + WorkflowMode.KNOWLEDGE_LOOP: KnowledgeLoopWorkflowManage, + WorkflowMode.TOOL_LOOP: ToolLoopWorkflowManage}.get(workflow_mode) or LoopWorkflowManage workflow_manage = c(Workflow.new_instance(loop_body, workflow_mode), self.workflow_manage.params, LoopWorkFlowPostHandler( diff --git a/apps/application/flow/step_node/tool_workflow_lib_node/__init__.py b/apps/application/flow/step_node/tool_workflow_lib_node/__init__.py new file mode 100644 index 00000000000..d417d531251 --- /dev/null +++ b/apps/application/flow/step_node/tool_workflow_lib_node/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2026/3/16 13:53 + @desc: +""" +from .impl import * \ No newline at end of file diff --git a/apps/application/flow/step_node/tool_workflow_lib_node/i_tool_workflow_lib_node.py b/apps/application/flow/step_node/tool_workflow_lib_node/i_tool_workflow_lib_node.py new file mode 100644 index 00000000000..82b73d0904b --- /dev/null +++ b/apps/application/flow/step_node/tool_workflow_lib_node/i_tool_workflow_lib_node.py @@ -0,0 +1,57 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: i_function_lib_node.py + @date:2024/8/8 16:21 + @desc: +""" +from typing import Type + +from django.db import connection +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from application.flow.common import WorkflowMode +from application.flow.i_step_node import INode, NodeResult +from common.field.common import ObjectField +from tools.models.tool import Tool, ToolType + + +class InputField(serializers.Serializer): + field = serializers.CharField(required=True, label=_('Variable Name')) + label = serializers.CharField(required=True, label=_('Variable Label')) + source = serializers.CharField(required=True, label=_('Variable Source')) + type = serializers.CharField(required=True, label=_('Variable Type')) + value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list, bool, dict, int, float]) + + +class FunctionLibNodeParamsSerializer(serializers.Serializer): + tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID')) + input_field_list = InputField(required=True, many=True) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + f_lib = QuerySet(Tool).filter(id=self.data.get('tool_lib_id'), tool_type=ToolType.WORKFLOW).first() + # 归还链接到连接池 + connection.close() + if f_lib is None: + raise Exception(_('The function has been deleted')) + + +class IToolWorkflowLibNode(INode): + type = 'tool-workflow-lib-node' + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, + WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] + + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: + return FunctionLibNodeParamsSerializer + + def _run(self): + return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) + + def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: + pass diff --git a/apps/application/flow/step_node/tool_workflow_lib_node/impl/__init__.py b/apps/application/flow/step_node/tool_workflow_lib_node/impl/__init__.py new file mode 100644 index 00000000000..0b593554784 --- /dev/null +++ b/apps/application/flow/step_node/tool_workflow_lib_node/impl/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2026/3/16 13:53 + @desc: +""" +from .base_tool_workflow_lib_node import * diff --git a/apps/application/flow/step_node/tool_workflow_lib_node/impl/base_tool_workflow_lib_node.py b/apps/application/flow/step_node/tool_workflow_lib_node/impl/base_tool_workflow_lib_node.py new file mode 100644 index 00000000000..49b1501ffef --- /dev/null +++ b/apps/application/flow/step_node/tool_workflow_lib_node/impl/base_tool_workflow_lib_node.py @@ -0,0 +1,214 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: base_tool_workflow_lib_node.py.py + @date:2026/3/16 13:55 + @desc: +""" + +import time +from typing import Dict + +import uuid_utils.compat as uuid +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from application.flow.common import WorkflowMode, Workflow +from application.flow.i_step_node import NodeResult, ToolWorkflowPostHandler, INode +from application.flow.step_node.tool_workflow_lib_node.i_tool_workflow_lib_node import IToolWorkflowLibNode +from application.models import ChatRecord +from application.serializers.common import ToolExecute +from common.exception.app_exception import ChatException +from common.handle.impl.response.loop_to_response import LoopToResponse +from tools.models import ToolWorkflowVersion + + +def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str, + reasoning_content: str): + result = node_variable.get('result') + node.context['application_node_dict'] = node_variable.get('application_node_dict') + node.context['node_dict'] = node_variable.get('node_dict', {}) + node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec') + node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0) + node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0) + node.context['answer'] = answer + node.context['result'] = answer + node.context['reasoning_content'] = reasoning_content + node.context['run_time'] = time.time() - node.context['start_time'] + if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): + node.answer_text = answer + + +def get_answer_list(instance, child_node_node_dict, runtime_node_id): + answer_list = instance.get_record_answer_list() + for a in answer_list: + _v = child_node_node_dict.get(a.get('runtime_node_id')) + if _v: + a['runtime_node_id'] = runtime_node_id + a['child_node'] = _v + return answer_list + + +def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): + """ + 写入上下文数据 (流式) + @param node_variable: 节点数据 + @param workflow_variable: 全局数据 + @param node: 节点 + @param workflow: 工作流管理器 + """ + workflow_manage_new_instance = node_variable.get('workflow_manage_new_instance') + node_params = node.node_params + start_node_id = node_params.get('child_node', {}).get('runtime_node_id') + child_node_data = node.context.get('child_node_data') or [] + start_node_data = None + chat_record = None + child_node = None + if start_node_id: + chat_record_id = node_params.get('child_node', {}).get('chat_record_id') + child_node = node_params.get('child_node', {}).get('child_node') + start_node_data = node_params.get('node_data') + chat_record = ChatRecord(id=chat_record_id, answer_text_list=[], answer_text='', + details=child_node_data) + instance = workflow_manage_new_instance(start_node_id, + start_node_data, chat_record, child_node) + answer = '' + reasoning_content = '' + usage = {} + node_child_node = {} + is_interrupt_exec = False + response = instance.stream() + child_node_node_dict = {} + for chunk in response: + response_content = chunk + content = (response_content.get('content', '') or '') + runtime_node_id = response_content.get('runtime_node_id', '') + chat_record_id = response_content.get('chat_record_id', '') + child_node = response_content.get('child_node') + node_type = response_content.get('node_type') + _reasoning_content = (response_content.get('reasoning_content', '') or '') + if node_type == 'form-node': + is_interrupt_exec = True + answer += content + reasoning_content += _reasoning_content + node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, + 'child_node': child_node} + + child_node = chunk.get('child_node') + runtime_node_id = chunk.get('runtime_node_id', '') + chat_record_id = chunk.get('chat_record_id', '') + child_node_node_dict[runtime_node_id] = { + 'runtime_node_id': runtime_node_id, + 'chat_record_id': chat_record_id, + 'child_node': child_node} + content_chunk = (chunk.get('content', '') or '') + reasoning_content_chunk = (chunk.get('reasoning_content', '') or '') + reasoning_content += reasoning_content_chunk + answer += content_chunk + yield chunk + usage = response_content.get('usage', {}) + child_answer_data = get_answer_list(instance, child_node_node_dict, node.runtime_node_id) + node.context['usage'] = {'usage': usage} + node.context['is_interrupt_exec'] = is_interrupt_exec + node.context['child_node'] = node_child_node + node.context['child_node_data'] = instance.get_runtime_details() + node.context['is_interrupt_exec'] = is_interrupt_exec + node.context['child_node_data'] = instance.get_runtime_details() + node.context['child_answer_data'] = child_answer_data + node.context['run_time'] = time.time() - node.context.get("start_time") + for key, value in instance.out_context.items(): + node.context[key] = value + + +def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict): + return node.context.get('is_interrupt_exec', False) + + +class BaseToolWorkflowLibNodeNode(IToolWorkflowLibNode): + def get_parameters(self, input_field_list): + result = {} + for input in input_field_list: + source = input.get('source') + value = input.get('value') + if source == 'reference': + value = self.workflow_manage.get_reference_field( + value[0], + value[1:]) + result[input.get('field')] = value + + return result + + def save_context(self, details, workflow_manage): + self.context['child_answer_data'] = details.get('child_answer_data') + self.context['child_node_data'] = details.get('child_node_data') + self.context['result'] = details.get('result') + self.context['exception_message'] = details.get('err_message') + if self.node_params.get('is_result'): + self.answer_text = str(details.get('result')) + + @staticmethod + def to_chat_record(record): + if record is None: + return None + return ChatRecord( + answer_text_list=record.meta.get('answer_text_list'), + details=record.meta.get('details'), + answer_text='', + ) + + def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: + from application.flow.tool_workflow_manage import ToolWorkflowManage + workspace_id = self.workflow_manage.get_body().get('workspace_id') + tool_workflow_version = QuerySet(ToolWorkflowVersion).filter(tool_id=tool_lib_id).order_by( + '-create_time')[0:1].first() + if tool_workflow_version is None: + raise ChatException(500, _("The tool has not been published. Please use it after publishing.")) + parameters = self.get_parameters(input_field_list) + tool_record_id = (self.node_params.get('child_node') or {}).get('chat_record_id') or str(uuid.uuid7()) + took_execute = ToolExecute(tool_lib_id, tool_record_id, + workspace_id, + self.workflow_manage.get_source_type(), + self.workflow_manage.get_source_id(), + False) + + def workflow_manage_new_instance(start_node_id=None, + start_node_data=None, chat_record=None, child_node=None): + work_flow_manage = ToolWorkflowManage( + Workflow.new_instance(tool_workflow_version.work_flow, WorkflowMode.TOOL), + { + 'chat_record_id': tool_record_id, + 'tool_id': tool_lib_id, + 'stream': True, + 'workspace_id': workspace_id, + **parameters}, + ToolWorkflowPostHandler(took_execute, tool_lib_id), + base_to_response=LoopToResponse(), + start_node_id=start_node_id, + start_node_data=start_node_data, + child_node=child_node, + chat_record=self.to_chat_record(took_execute.get_record()), + is_the_task_interrupted=lambda: False) + + return work_flow_manage + + return NodeResult({'workflow_manage_new_instance': workflow_manage_new_instance}, + {}, _write_context=write_context_stream, + _is_interrupt=_is_interrupt_exec) + + def get_details(self, index: int, **kwargs): + result = self.context.get('result') + + return { + 'name': self.node.properties.get('stepName'), + "index": index, + "result": result, + "params": self.context.get('params'), + 'run_time': self.context.get('run_time'), + 'type': self.node.type, + 'status': self.status, + 'child_node_data': self.context.get("child_node_data"), + 'child_answer_data': self.context.get("child_answer_data"), + 'err_message': self.err_message, + 'enableException': self.node.properties.get('enableException'), + } diff --git a/apps/application/flow/tool_loop_workflow_manage.py b/apps/application/flow/tool_loop_workflow_manage.py new file mode 100644 index 00000000000..9fc2425f014 --- /dev/null +++ b/apps/application/flow/tool_loop_workflow_manage.py @@ -0,0 +1,21 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: workflow_manage.py + @date:2024/1/9 17:40 + @desc: +""" +from application.flow.i_step_node import ToolFlowParamsSerializer +from application.flow.loop_workflow_manage import LoopWorkflowManage + + +class ToolLoopWorkflowManage(LoopWorkflowManage): + def get_params_serializer_class(self): + return ToolFlowParamsSerializer + + def get_source_type(self): + return "TOOL" + + def get_source_id(self): + return self.params.get('tool_id') diff --git a/apps/application/flow/tool_workflow_manage.py b/apps/application/flow/tool_workflow_manage.py index 431045d716a..4b42983e592 100644 --- a/apps/application/flow/tool_workflow_manage.py +++ b/apps/application/flow/tool_workflow_manage.py @@ -8,6 +8,9 @@ """ from concurrent.futures import ThreadPoolExecutor +from django.db import close_old_connections +from django.utils.translation import get_language + from application.flow.common import Workflow from application.flow.i_step_node import WorkFlowPostHandler, ToolFlowParamsSerializer from application.flow.workflow_manage import WorkflowManage @@ -29,6 +32,12 @@ def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostH def get_params_serializer_class(self): return ToolFlowParamsSerializer + def stream(self): + close_old_connections() + language = get_language() + self.run_chain_async(self.start_node, None, language) + return self.await_result(is_cleanup=False) + def get_start_node(self): return self.flow.get_node('tool-start-node') @@ -38,3 +47,9 @@ def get_base_node(self): @return: """ return self.flow.get_node('tool-base-node') + + def get_source_type(self): + return "TOOL" + + def get_source_id(self): + return self.params.get('tool_id') diff --git a/apps/application/flow/workflow_manage.py b/apps/application/flow/workflow_manage.py index 0bffd0c5b3d..3d5ebdd7ec6 100644 --- a/apps/application/flow/workflow_manage.py +++ b/apps/application/flow/workflow_manage.py @@ -192,9 +192,7 @@ def load_node(self, chat_record, start_node_id, start_node_data): if node_details.get('runtime_node_id') == start_node_id: def get_node_params(n): is_result = False - if n.type == 'application-node': - is_result = True - if n.type == 'loop-node': + if ['application-node', 'loop-node', 'tool-workflow-lib-node'].__contains__(n.type): is_result = True return {**n.properties.get('node_data'), 'form_data': start_node_data, 'node_data': start_node_data, 'child_node': self.child_node, 'is_result': is_result} @@ -798,3 +796,9 @@ def get_node_reference(self, reference_address: Dict): def get_params_serializer_class(self): return FlowParamsSerializer + + def get_source_type(self): + return "APPLICATION" + + def get_source_id(self): + return self.params.get('application_id') diff --git a/apps/application/serializers/common.py b/apps/application/serializers/common.py index 68f1a9e7de0..a4d193cdacc 100644 --- a/apps/application/serializers/common.py +++ b/apps/application/serializers/common.py @@ -22,6 +22,75 @@ from models_provider.models import Model from models_provider.tools import get_model_credential from system_manage.models.resource_mapping import ResourceMapping +from tools.models import ToolRecord + + +class ToolExecute: + def __init__(self, tool_id: str, + tool_record_id: str, + workspace_id: str, + source_type, + source_id, + debug=False): + self.tool_id = tool_id + self.workspace_id = workspace_id + self.source_type = source_type + self.source_id = source_id + self.tool_record_id = tool_record_id + self.debug = debug + + def get_record(self): + if self.tool_record_id: + if self.debug: + return self.to_record(cache.get(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.tool_record_id), + version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version())) + else: + return QuerySet(ToolRecord).filter(tool_id=self.tool_id, id=self.tool_record_id).first() + return None + + def to_record(self, tool_record_dict): + if tool_record_dict is None: + return None + return ToolRecord(id=tool_record_dict.get('id'), + tool_id=tool_record_dict.get('tool_id'), + workspace_id=tool_record_dict.get('workspace_id'), + source_type=tool_record_dict.get('source_type'), + source_id=tool_record_dict.get('source_id'), + meta=tool_record_dict.get('meta'), + state=tool_record_dict.get('state'), + run_time=tool_record_dict.get('run_time')) + + def to_dict(self, tool_record): + return {'id': tool_record.id, + 'tool_id': tool_record.tool_id, + 'workspace_id': tool_record.workspace_id, + 'source_type': tool_record.source_type, + 'source_id': tool_record.source_id, + 'meta': tool_record.meta, + 'state': tool_record.state, + 'run_time': tool_record.run_time} + + def set_record(self, tool_record): + cache.set(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.tool_record_id), self.to_dict(tool_record), + version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version(), + timeout=60 * 30) + if not self.debug: + QuerySet(ToolRecord).update_or_create(id=tool_record.id, + create_defaults={'id': tool_record.id, + 'tool_id': tool_record.tool_id, + 'workspace_id': tool_record.workspace_id, + "source_type": tool_record.source_type, + 'source_id': tool_record.source_id, + 'meta': tool_record.meta, + 'run_time': tool_record.run_time}, + defaults={ + 'workspace_id': tool_record.workspace_id, + 'tool_id': tool_record.tool_id, + "source_type": tool_record.source_type, + 'source_id': tool_record.source_id, + 'meta': tool_record.meta, + 'run_time': tool_record.run_time + }) class ChatInfo: diff --git a/apps/common/constants/cache_version.py b/apps/common/constants/cache_version.py index 1b202dc8e07..0aed4715e2e 100644 --- a/apps/common/constants/cache_version.py +++ b/apps/common/constants/cache_version.py @@ -39,6 +39,8 @@ class Cache_Version(Enum): CHAT_USER_TOKEN = "CHAT_USER_TOKEN", lambda token: token + TOOL_WORKFLOW_EXECUTE = "TOOL_WORKFLOW_EXECUTE", lambda key: key + def get_version(self): return self.value[0] diff --git a/apps/tools/models/tool.py b/apps/tools/models/tool.py index 99ecfb08069..5f4d5bac334 100644 --- a/apps/tools/models/tool.py +++ b/apps/tools/models/tool.py @@ -42,6 +42,7 @@ class ToolType(models.TextChoices): class ToolTaskTypeChoices(models.TextChoices): APPLICATION = 'APPLICATION' KNOWLEDGE = 'KNOWLEDGE' + TOOL = 'TOOL' TRIGGER = 'TRIGGER' diff --git a/apps/tools/serializers/tool_workflow.py b/apps/tools/serializers/tool_workflow.py index 674cb375983..cffe8c2a6db 100644 --- a/apps/tools/serializers/tool_workflow.py +++ b/apps/tools/serializers/tool_workflow.py @@ -23,6 +23,8 @@ from application.flow.common import Workflow, WorkflowMode from application.flow.i_step_node import ToolWorkflowPostHandler from application.flow.tool_workflow_manage import ToolWorkflowManage +from application.models import ChatRecord +from application.serializers.common import ToolExecute from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField from common.result import result @@ -220,19 +222,43 @@ def debug(self, instance: Dict, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get("tool_id")).first() + tool_record_id = instance.get('chat_record_id') or str(uuid.uuid7()) + took_execute = ToolExecute(self.data.get("tool_id"), tool_record_id, + self.data.get("workspace_id"), + None, + None, + True) + record = took_execute.get_record() work_flow_manage = ToolWorkflowManage( Workflow.new_instance(tool_workflow.work_flow, WorkflowMode.TOOL), { + 'chat_record_id': tool_record_id, 'tool_id': self.data.get("tool_id"), 'stream': True, 'workspace_id': self.data.get("workspace_id"), **instance}, - ToolWorkflowPostHandler(None, self.data.get("tool_id")), - is_the_task_interrupted=lambda: False) + + ToolWorkflowPostHandler(took_execute, self.data.get("tool_id")), + is_the_task_interrupted=lambda: False, + child_node=instance.get('child_node'), + start_node_id=instance.get('runtime_node_id'), + start_node_data=instance.get('node_data'), + chat_record=self.to_chat_record(record) + ) r = work_flow_manage.run() return r + @staticmethod + def to_chat_record(record): + if record is None: + return None + return ChatRecord( + answer_text_list=record.meta.get('answer_text_list'), + details=record.meta.get('details'), + answer_text='', + ) + def publish(self, with_valid=True): if with_valid: self.is_valid() diff --git a/ui/src/views/tool-workflow/component/debug/result/index.vue b/ui/src/views/tool-workflow/component/debug/result/index.vue index 7a556be3a6e..891e20568e8 100644 --- a/ui/src/views/tool-workflow/component/debug/result/index.vue +++ b/ui/src/views/tool-workflow/component/debug/result/index.vue @@ -35,7 +35,12 @@ const details = { show_avatar: false, show_user_avatar: false, } +const currentToolId = ref() +const currentData = ref({}) const execute = (toolId: string, data: any) => { + console.log('execute') + currentToolId.value = toolId + currentData.value = data ChatManagement.addChatRecord(currentChat, 50, loading) ChatManagement.write(currentChat.id) return loadSharedApi({ type: 'tool', isShared: props.isShared, systemType: props.apiType }) @@ -158,25 +163,34 @@ const getWrite = (chat: any, reader: any, stream: boolean) => { return stream ? write_stream : write_json } -function chatMessage(toolId: string, chat?: any, other_params_data?: any) { - if (!chat) { - chat = reactive({}) - chatList.value.push(chat) - ChatManagement.addChatRecord(chat, 50, loading) - ChatManagement.write(chat.id) - } - if (chat.run_time) { - ChatManagement.addChatRecord(chat, 50, loading) - ChatManagement.write(chat.id) - } - const obj = { - ...other_params_data, - } - // 对话 - execute(toolId, obj) -} -const sendMessage = () => { - console.log('ss') + +const sendMessage = (val: string, other_params_data?: any, chat?: chatType) => { + loadSharedApi({ type: 'tool', isShared: props.isShared, systemType: props.apiType }) + .debugToolWorkflow(currentToolId.value, { ...other_params_data, ...currentData.value }) + .then((response: any) => { + if (response.status === 460) { + return Promise.reject(t('chat.tip.errorIdentifyMessage')) + } else if (response.status === 461) { + return Promise.reject(t('chat.tip.errorLimitMessage')) + } else { + const reader = response.body.getReader() + // 处理流数据 + const write = getWrite( + currentChat, + reader, + response.headers.get('Content-Type') !== 'application/json', + ) + return write() + } + }) + .finally(() => { + console.log('close') + ChatManagement.close(currentChat.id) + }) + .catch((e: any) => { + console.log(e) + }) + return Promise.resolve(true) } defineExpose({ execute, diff --git a/ui/src/workflow/nodes/tool-start-node/index.ts b/ui/src/workflow/nodes/tool-start-node/index.ts index 2ff937d0699..5191bf4ec34 100644 --- a/ui/src/workflow/nodes/tool-start-node/index.ts +++ b/ui/src/workflow/nodes/tool-start-node/index.ts @@ -15,7 +15,7 @@ class ToolBaseNode extends AppNode { }) const tbn = this.props.graphModel.getNodeModelById('tool-base-node') console.log(tbn) - const output = tbn.properties.user_output_field_list.map((i: any) => { + const output = tbn.properties?.user_output_field_list?.map((i: any) => { return { label: i.label || i.name, value: i.field } }) diff --git a/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue b/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue index 91933a99807..ad579d50461 100644 --- a/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue +++ b/ui/src/workflow/nodes/tool-workflow-lib-node/index.vue @@ -195,6 +195,9 @@ const update_field = () => { } onMounted(() => { + if (props.nodeModel.properties.config?.fields?.length) { + set(props.nodeModel.properties.config, 'fields', props.nodeModel.properties.config.fields) + } if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') { if (isLastNode(props.nodeModel)) { set(props.nodeModel.properties.node_data, 'is_result', true) From 2b94faa413c8b1ccff655699877fad6ffb51ffe5 Mon Sep 17 00:00:00 2001 From: zhangzhanwei Date: Wed, 18 Mar 2026 10:29:42 +0800 Subject: [PATCH 09/12] feat: Support reference model in ai chat node --- .../ai_chat_step_node/i_chat_node.py | 9 ++- .../ai_chat_step_node/impl/base_chat_node.py | 16 ++++- ui/src/workflow/nodes/ai-chat-node/index.vue | 59 ++++++++++++++----- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py b/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py index c9a348c6bd0..994fda75df6 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py +++ b/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py @@ -16,7 +16,10 @@ class ChatNodeSerializer(serializers.Serializer): - model_id = serializers.CharField(required=True, label=_("Model id")) + model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model id")) + model_id_type = serializers.CharField(required=False, default='custom', label=_("Model id type")) + model_id_reference = serializers.ListField(required=False, child=serializers.CharField(), allow_empty=True, + label=_("Reference Field")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Role Setting")) prompt = serializers.CharField(required=True, label=_("Prompt word")) @@ -58,7 +61,7 @@ def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: def _run(self): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( - self.workflow_manage.flow.workflow_mode): + self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: @@ -67,6 +70,8 @@ def _run(self): def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, model_params_setting=None, + model_id_type=None, + model_id_reference=None, dialogue_type=None, model_setting=None, mcp_servers=None, diff --git a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py b/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py index 4fdd9680194..ec42376e91e 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py +++ b/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py @@ -151,6 +151,8 @@ def save_context(self, details, workflow_manage): def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, model_params_setting=None, + model_id_type=None, + model_id_reference=None, dialogue_type=None, model_setting=None, mcp_servers=None, @@ -165,8 +167,20 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record if dialogue_type is None: dialogue_type = 'WORKFLOW' - if model_params_setting is None: + if model_id_type == 'reference' and model_id_reference: + + reference_data = self.workflow_manage.get_reference_field( + model_id_reference[0], + model_id_reference[1:], + ) + + if reference_data and isinstance(reference_data, dict): + model_id = reference_data.get('model_id', model_id) + model_params_setting = reference_data.get('model_params_setting') + + if model_params_setting is None and model_id: model_params_setting = get_default_model_params_setting(model_id) + if model_setting is None: model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '', 'reasoning_content_start': ''} diff --git a/ui/src/workflow/nodes/ai-chat-node/index.vue b/ui/src/workflow/nodes/ai-chat-node/index.vue index 460b5f8e8ee..90effb0a944 100644 --- a/ui/src/workflow/nodes/ai-chat-node/index.vue +++ b/ui/src/workflow/nodes/ai-chat-node/index.vue @@ -13,10 +13,10 @@ > @@ -28,7 +28,27 @@ }}*
- + + + + + + +
+
+ +
+
- - +
+ @@ -71,7 +88,7 @@ type="primary" link @click="openGeneratePromptDialog(chat_data.model_id)" - :disabled="!chat_data.model_id" + :disabled="chat_data.model_id_type === 'reference' || !chat_data.model_id" > @@ -458,6 +475,7 @@ + diff --git a/ui/src/components/dynamics-form/index.vue b/ui/src/components/dynamics-form/index.vue index 8a41683ea9b..473f4c08de8 100644 --- a/ui/src/components/dynamics-form/index.vue +++ b/ui/src/components/dynamics-form/index.vue @@ -215,6 +215,7 @@ const render = ( | (() => Promise>>), data?: Dict, ) => { + console.log(data, '-----') formFieldList.value = [] nextTick(() => { if (typeof render_data == 'string') { @@ -246,6 +247,7 @@ const render = ( } const getFormDefaultValue = (fieldList: Array, form_data?: any) => { form_data = form_data ? form_data : {} + console.log(form_data) const value = fieldList .map((item) => { if (form_data[item.field] !== undefined) { @@ -274,6 +276,7 @@ const getFormDefaultValue = (fieldList: Array, form_data?: any) => { return {} }) .reduce((x, y) => ({ ...x, ...y }), {}) + console.log(value) return value } /** diff --git a/ui/src/components/dynamics-form/items/model/Model.vue b/ui/src/components/dynamics-form/items/model/Model.vue new file mode 100644 index 00000000000..f87010f12f5 --- /dev/null +++ b/ui/src/components/dynamics-form/items/model/Model.vue @@ -0,0 +1,128 @@ + + + diff --git a/ui/src/components/dynamics-form/items/model/provider-data.ts b/ui/src/components/dynamics-form/items/model/provider-data.ts new file mode 100644 index 00000000000..398d07fa0b8 --- /dev/null +++ b/ui/src/components/dynamics-form/items/model/provider-data.ts @@ -0,0 +1,107 @@ +export const providerList = [ + { + "provider": "model_azure_provider", + "name": "Azure OpenAI", + "icon": "" + }, + { + "provider": "model_wenxin_provider", + "name": "千帆大模型", + "icon": "\n\n\n\n" + }, + { + "provider": "model_ollama_provider", + "name": "Ollama", + "icon": " \n\n" + }, + { + "provider": "model_openai_provider", + "name": "OpenAI", + "icon": "" + }, + { + "provider": "model_docker_ai_provider", + "name": "Docker AI", + "icon": "\n\n\n" + }, + { + "provider": "model_kimi_provider", + "name": "Kimi", + "icon": "" + }, + { + "provider": "model_zhipu_provider", + "name": "智谱 AI", + "icon": "" + }, + { + "provider": "model_xf_provider", + "name": "讯飞星火", + "icon": "" + }, + { + "provider": "model_deepseek_provider", + "name": "DeepSeek", + "icon": "\n\t\n" + }, + { + "provider": "model_gemini_provider", + "name": "Gemini", + "icon": "" + }, + { + "provider": "model_volcanic_engine_provider", + "name": "火山引擎", + "icon": "\n\n \n\n" + }, + { + "provider": "model_tencent_provider", + "name": "腾讯混元", + "icon": "\n\n \n\n" + }, + { + "provider": "model_tencent_cloud_provider", + "name": "腾讯云", + "icon": "\n\n\n \n \n \n" + }, + { + "provider": "model_aws_bedrock_provider", + "name": "Amazon Bedrock", + "icon": "" + }, + { + "provider": "model_local_provider", + "name": "本地模型", + "icon": "\n\n\n" + }, + { + "provider": "model_xinference_provider", + "name": "Xorbits Inference", + "icon": "\n\n \n\n" + }, + { + "provider": "model_vllm_provider", + "name": "vLLM", + "icon": "\n\n \n\n" + }, + { + "provider": "aliyun_bai_lian_model_provider", + "name": "阿里云百炼", + "icon": "【icon】阿里百炼大模型" + }, + { + "provider": "model_anthropic_provider", + "name": "Anthropic", + "icon": "" + }, + { + "provider": "model_siliconCloud_provider", + "name": "SILICONFLOW", + "icon": "\n\n\n\n\n" + }, + { + "provider": "model_regolo_provider", + "name": "Regolo", + "icon": "\n\n \n \n \n .cls-1 {\n fill: #303030;\n }\n\n .cls-2 {\n fill: #59e389;\n }\n \n \n \n \n \n \n \n \n \n\n" + } +] \ No newline at end of file diff --git a/ui/src/components/model-select/index.vue b/ui/src/components/model-select/index.vue index 34b3661b466..91120eed1f6 100644 --- a/ui/src/components/model-select/index.vue +++ b/ui/src/components/model-select/index.vue @@ -1,6 +1,12 @@