feat(workflow): add Dify workflow import adapter and related APIs

This commit is contained in:
Eternity
2026-02-28 10:29:14 +08:00
parent e9ff742162
commit 9916cf3265
25 changed files with 1625 additions and 124 deletions

View File

@@ -321,6 +321,26 @@ class AppService:
self.db.add(agent_cfg)
logger.debug("Agent 配置已创建", extra={"app_id": str(app_id)})
def _create_workflow_config(
self,
app_id: uuid.UUID,
data: app_schema.WorkflowConfigCreate,
now: datetime.datetime
):
workflow_cfg = WorkflowConfig(
id=uuid.uuid4(),
app_id=app_id,
nodes=[node.model_dump() for node in data.nodes] if data.nodes else [],
edges=[edge.model_dump() for edge in data.edges] if data.edges else [],
variables=[var.model_dump() for var in data.variables] if data.variables else [],
execution_config=data.execution_config.model_dump() if data.execution_config else {},
triggers=[trigger.model_dump() for trigger in data.triggers] if data.triggers else [],
is_active=True,
created_at=now,
updated_at=now
)
self.db.add(workflow_cfg)
def _create_multi_agent_config(
self,
app_id: uuid.UUID,
@@ -532,6 +552,9 @@ class AppService:
if app.type == "multi_agent" and data.multi_agent_config:
self._create_multi_agent_config(app.id, data.multi_agent_config, now)
if app.type == "workflow" and data.workflow_config:
self._create_workflow_config(app.id, data.workflow_config, now)
self.db.commit()
self.db.refresh(app)
@@ -968,7 +991,7 @@ class AppService:
config = self.db.scalars(stmt).first()
try:
config_memory=config.memory
config_memory = config.memory
if 'memory_content' in config_memory:
config.memory['memory_config_id'] = config.memory.pop('memory_content')
except:
@@ -1189,9 +1212,9 @@ class AppService:
# ==================== 记忆配置提取方法 ====================
def _extract_memory_config_id(
self,
app_type: str,
config: Dict[str, Any]
self,
app_type: str,
config: Dict[str, Any]
) -> Tuple[Optional[uuid.UUID], bool]:
"""从发布配置中提取 memory_config_id委托给 MemoryConfigService
@@ -1205,13 +1228,13 @@ class AppService:
- is_legacy_int: 是否检测到旧格式 int 数据,需要回退到工作空间默认配置
"""
from app.services.memory_config_service import MemoryConfigService
service = MemoryConfigService(self.db)
return service.extract_memory_config_id(app_type, config)
def _get_workspace_default_memory_config_id(
self,
workspace_id: uuid.UUID
self,
workspace_id: uuid.UUID
) -> Optional[uuid.UUID]:
"""获取工作空间的默认记忆配置ID
@@ -1222,22 +1245,22 @@ class AppService:
Optional[uuid.UUID]: 默认记忆配置ID如果不存在则返回 None
"""
from app.services.memory_config_service import MemoryConfigService
service = MemoryConfigService(self.db)
config = service.get_workspace_default_config(workspace_id)
if not config:
logger.warning(
f"工作空间没有可用的记忆配置: workspace_id={workspace_id}"
)
return None
return config.config_id
def _update_endusers_memory_config(
self,
app_id: uuid.UUID,
memory_config_id: uuid.UUID
self,
app_id: uuid.UUID,
memory_config_id: uuid.UUID
) -> int:
"""批量更新应用下所有终端用户的 memory_config_id
@@ -1249,13 +1272,13 @@ class AppService:
int: 更新的终端用户数量
"""
from app.repositories.end_user_repository import EndUserRepository
repo = EndUserRepository(self.db)
updated_count = repo.batch_update_memory_config_id(
app_id=app_id,
memory_config_id=memory_config_id
)
return updated_count
# ==================== 应用发布管理 ====================
@@ -1403,7 +1426,7 @@ class AppService:
# 提取记忆配置ID并更新终端用户
memory_config_id, is_legacy_int = self._extract_memory_config_id(app.type, config)
# 如果检测到旧格式 int 数据,回退到工作空间默认配置
if is_legacy_int and not memory_config_id:
memory_config_id = self._get_workspace_default_memory_config_id(app.workspace_id)
@@ -1412,7 +1435,7 @@ class AppService:
f"发布时使用工作空间默认记忆配置(旧数据兼容): app_id={app_id}, "
f"workspace_id={app.workspace_id}, memory_config_id={memory_config_id}"
)
if memory_config_id:
updated_count = self._update_endusers_memory_config(app_id, memory_config_id)
logger.info(
@@ -1537,7 +1560,7 @@ class AppService:
# 提取记忆配置ID并更新终端用户
memory_config_id, is_legacy_int = self._extract_memory_config_id(release.type, release.config)
# 如果检测到旧格式 int 数据,回退到工作空间默认配置
if is_legacy_int and not memory_config_id:
memory_config_id = self._get_workspace_default_memory_config_id(app.workspace_id)
@@ -1546,7 +1569,7 @@ class AppService:
f"回滚时使用工作空间默认记忆配置(旧数据兼容): app_id={app_id}, "
f"workspace_id={app.workspace_id}, memory_config_id={memory_config_id}"
)
if memory_config_id:
updated_count = self._update_endusers_memory_config(app_id, memory_config_id)
logger.info(

View File

@@ -0,0 +1,102 @@
# -*- coding: UTF-8 -*-
# Author: Eternity
# @Email: 1533512157@qq.com
# @Time : 2026/2/25 14:39
import json
import uuid
from typing import Any
from sqlalchemy.orm import Session
from app.aioRedis import aio_redis_set, aio_redis_get
from app.core.config import settings
from app.core.exceptions import BusinessException
from app.core.workflow.adapters.base_adapter import WorkflowImportResult, WorkflowParserResult
from app.core.workflow.adapters.errors import UnsupportPlatform, InvalidConfiguration
from app.core.workflow.adapters.registry import PlatformAdapterRegistry
from app.schemas import AppCreate
from app.schemas.workflow_schema import WorkflowConfigCreate
from app.services.app_service import AppService
from app.services.workflow_service import WorkflowService
class WorkflowImportService:
def __init__(self, db: Session):
self.db = db
self.registry = PlatformAdapterRegistry
self.cache_timeout = settings.WORKFLOW_IMPORT_CACHE_TIMEOUT
self.app_service = AppService(db)
self.workflow_service = WorkflowService(db)
async def flush_config(self, temp_id: str, config: WorkflowParserResult):
config_cache = await aio_redis_get(temp_id)
if not config_cache:
raise BusinessException("Workflow configuration has expired. Please re-upload it.")
await aio_redis_set(temp_id, config.model_dump_json(), expire=self.cache_timeout)
async def upload_config(
self,
platform: str,
config: dict[str, Any],
):
if not self.registry.is_supported(platform):
return WorkflowImportResult(
success=False,
temp_id=None,
workflow_id=None,
errors=[UnsupportPlatform(platform=platform)]
)
adapter = self.registry.get_adapter(platform, config)
if not adapter.validate_config():
return WorkflowImportResult(
success=False,
temp_id=None,
workflow_id=None,
errors=[InvalidConfiguration()]
)
workflow_config = adapter.parse_workflow()
temp_id = uuid.uuid4().hex
await aio_redis_set(temp_id, workflow_config.model_dump(), expire=self.cache_timeout)
return WorkflowImportResult(
success=True,
temp_id=temp_id,
workflow_id=None,
edges=workflow_config.edges,
nodes=workflow_config.nodes,
variables=workflow_config.variables,
warnings=workflow_config.warnings,
errors=workflow_config.errors
)
async def save_workflow(
self,
user_id: uuid.UUID,
workspace_id: uuid.UUID,
temp_id: str,
name: str,
description: str | None,
):
config = await aio_redis_get(temp_id)
if config is None:
raise BusinessException("Configuration import timed out. Please try again.")
config = json.loads(config)
app = self.app_service.create_app(
user_id=user_id,
workspace_id=workspace_id,
data=AppCreate(
name=name,
description=description,
type="workflow",
workflow_config=WorkflowConfigCreate(
nodes=config["nodes"],
edges=config["edges"],
variables=config["variables"]
)
)
)
return app

View File

@@ -6,13 +6,16 @@ import logging
import uuid
from typing import Any, Annotated, Optional
import yaml
from fastapi import Depends
from sqlalchemy.orm import Session
from app.core.error_codes import BizCode
from app.core.exceptions import BusinessException
from app.core.workflow.adapters.registry import PlatformAdapterRegistry
from app.core.workflow.validator import validate_workflow_config
from app.db import get_db
from app.models import App
from app.models.workflow_model import WorkflowConfig, WorkflowExecution
from app.repositories.workflow_repository import (
WorkflowConfigRepository,
@@ -38,6 +41,8 @@ class WorkflowService:
self.conversation_service = ConversationService(db)
self.multimodal_service = MultimodalService(db)
self.registry = PlatformAdapterRegistry
# ==================== 配置管理 ====================
def create_workflow_config(
@@ -200,6 +205,32 @@ class WorkflowService:
logger.info(f"删除工作流配置成功: app_id={app_id}, config_id={config.id}")
return True
def export_workflow_dsl(self, app_id: uuid.UUID):
config = self.get_workflow_config(app_id)
if not config:
raise BusinessException(
code=BizCode.NOT_FOUND,
message=f"工作流配置不存在: app_id={app_id}"
)
app: App = config.app
dsl_info = {
"app": {
"name": app.name,
"description": app.description,
"icon": app.icon,
"icon_type": app.icon_type
},
"workflow": {
"variables": config.variables,
"edges": config.edges,
"nodes": config.nodes,
"execution_config": config.execution_config,
"triggers": config.triggers
}
}
return yaml.dump(dsl_info, default_flow_style=False, allow_unicode=True)
def check_config(self, app_id: uuid.UUID) -> WorkflowConfig:
"""检查工作流配置的完整性