864 lines
28 KiB
Python
864 lines
28 KiB
Python
"""
|
||
工作流服务层
|
||
"""
|
||
import datetime
|
||
import json
|
||
import logging
|
||
import uuid
|
||
from typing import Any, Annotated
|
||
|
||
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.validator import validate_workflow_config
|
||
from app.db import get_db
|
||
from app.models.workflow_model import WorkflowConfig, WorkflowExecution
|
||
from app.repositories.workflow_repository import (
|
||
WorkflowConfigRepository,
|
||
WorkflowExecutionRepository,
|
||
WorkflowNodeExecutionRepository
|
||
)
|
||
from app.schemas import DraftRunRequest
|
||
from app.utils.sse_utils import format_sse_message
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class WorkflowService:
|
||
"""工作流服务"""
|
||
|
||
def __init__(self, db: Session):
|
||
self.db = db
|
||
self.config_repo = WorkflowConfigRepository(db)
|
||
self.execution_repo = WorkflowExecutionRepository(db)
|
||
self.node_execution_repo = WorkflowNodeExecutionRepository(db)
|
||
|
||
# ==================== 配置管理 ====================
|
||
|
||
def create_workflow_config(
|
||
self,
|
||
app_id: uuid.UUID,
|
||
nodes: list[dict[str, Any]],
|
||
edges: list[dict[str, Any]],
|
||
variables: list[dict[str, Any]] | None = None,
|
||
execution_config: dict[str, Any] | None = None,
|
||
triggers: list[dict[str, Any]] | None = None,
|
||
validate: bool = True
|
||
) -> WorkflowConfig:
|
||
"""创建工作流配置
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
nodes: 节点列表
|
||
edges: 边列表
|
||
variables: 变量列表
|
||
execution_config: 执行配置
|
||
triggers: 触发器列表
|
||
validate: 是否验证配置
|
||
|
||
Returns:
|
||
工作流配置
|
||
|
||
Raises:
|
||
BusinessException: 配置无效时抛出
|
||
"""
|
||
# 构建配置字典
|
||
config_dict = {
|
||
"nodes": nodes,
|
||
"edges": edges,
|
||
"variables": variables or [],
|
||
"execution_config": execution_config or {},
|
||
"triggers": triggers or []
|
||
}
|
||
|
||
# 验证配置
|
||
if validate:
|
||
is_valid, errors = validate_workflow_config(config_dict, for_publish=False)
|
||
if not is_valid:
|
||
logger.warning(f"工作流配置验证失败: {errors}")
|
||
raise BusinessException(
|
||
code=BizCode.INVALID_PARAMETER,
|
||
message=f"工作流配置无效: {'; '.join(errors)}"
|
||
)
|
||
|
||
# 创建或更新配置
|
||
config = self.config_repo.create_or_update(
|
||
app_id=app_id,
|
||
nodes=nodes,
|
||
edges=edges,
|
||
variables=variables,
|
||
execution_config=execution_config,
|
||
triggers=triggers
|
||
)
|
||
|
||
logger.info(f"创建工作流配置成功: app_id={app_id}, config_id={config.id}")
|
||
return config
|
||
|
||
def get_workflow_config(self, app_id: uuid.UUID) -> WorkflowConfig | None:
|
||
"""获取工作流配置
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
|
||
Returns:
|
||
工作流配置或 None
|
||
"""
|
||
return self.config_repo.get_by_app_id(app_id)
|
||
|
||
def update_workflow_config(
|
||
self,
|
||
app_id: uuid.UUID,
|
||
nodes: list[dict[str, Any]] | None = None,
|
||
edges: list[dict[str, Any]] | None = None,
|
||
variables: list[dict[str, Any]] | None = None,
|
||
execution_config: dict[str, Any] | None = None,
|
||
triggers: list[dict[str, Any]] | None = None,
|
||
validate: bool = True
|
||
) -> WorkflowConfig:
|
||
"""更新工作流配置
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
nodes: 节点列表
|
||
edges: 边列表
|
||
variables: 变量列表
|
||
execution_config: 执行配置
|
||
triggers: 触发器列表
|
||
validate: 是否验证配置
|
||
|
||
Returns:
|
||
工作流配置
|
||
|
||
Raises:
|
||
BusinessException: 配置不存在或无效时抛出
|
||
"""
|
||
# 获取现有配置
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
raise BusinessException(
|
||
code=BizCode.NOT_FOUND,
|
||
message=f"工作流配置不存在: app_id={app_id}"
|
||
)
|
||
|
||
# 合并配置
|
||
updated_nodes = nodes if nodes is not None else config.nodes
|
||
updated_edges = edges if edges is not None else config.edges
|
||
updated_variables = variables if variables is not None else config.variables
|
||
updated_execution_config = execution_config if execution_config is not None else config.execution_config
|
||
updated_triggers = triggers if triggers is not None else config.triggers
|
||
|
||
# 构建配置字典
|
||
config_dict = {
|
||
"nodes": updated_nodes,
|
||
"edges": updated_edges,
|
||
"variables": updated_variables,
|
||
"execution_config": updated_execution_config,
|
||
"triggers": updated_triggers
|
||
}
|
||
|
||
# 验证配置
|
||
if validate:
|
||
is_valid, errors = validate_workflow_config(config_dict, for_publish=False)
|
||
if not is_valid:
|
||
logger.warning(f"工作流配置验证失败: {errors}")
|
||
raise BusinessException(
|
||
code=BizCode.INVALID_PARAMETER,
|
||
message=f"工作流配置无效: {'; '.join(errors)}"
|
||
)
|
||
|
||
# 更新配置
|
||
config = self.config_repo.create_or_update(
|
||
app_id=app_id,
|
||
nodes=updated_nodes,
|
||
edges=updated_edges,
|
||
variables=updated_variables,
|
||
execution_config=updated_execution_config,
|
||
triggers=updated_triggers
|
||
)
|
||
|
||
logger.info(f"更新工作流配置成功: app_id={app_id}, config_id={config.id}")
|
||
return config
|
||
|
||
def delete_workflow_config(self, app_id: uuid.UUID) -> bool:
|
||
"""删除工作流配置
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
|
||
Returns:
|
||
是否删除成功
|
||
"""
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
return False
|
||
config.is_active = False
|
||
logger.info(f"删除工作流配置成功: app_id={app_id}, config_id={config.id}")
|
||
return True
|
||
|
||
def check_config(self, app_id: uuid.UUID) -> WorkflowConfig:
|
||
"""检查工作流配置的完整性
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
|
||
Raises:
|
||
BusinessException: 配置不完整或不存在时抛出
|
||
"""
|
||
|
||
# 1. 检查多智能体配置是否存在
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
raise BusinessException(
|
||
"工作流配置不存在,无法运行",
|
||
BizCode.CONFIG_MISSING
|
||
)
|
||
# validator 现在支持直接接受 Pydantic 模型
|
||
is_valid, errors = validate_workflow_config(config, for_publish=False)
|
||
if not is_valid:
|
||
logger.warning(f"工作流配置验证失败: {errors}")
|
||
raise BusinessException(
|
||
code=BizCode.INVALID_PARAMETER,
|
||
message=f"工作流配置无效: {'; '.join(errors)}"
|
||
)
|
||
return config
|
||
|
||
def validate_workflow_config_for_publish(
|
||
self,
|
||
app_id: uuid.UUID
|
||
) -> tuple[bool, list[str]]:
|
||
"""验证工作流配置是否可以发布
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
|
||
Returns:
|
||
(is_valid, errors): 是否有效和错误列表
|
||
|
||
Raises:
|
||
BusinessException: 配置不存在时抛出
|
||
"""
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
raise BusinessException(
|
||
code=BizCode.NOT_FOUND,
|
||
message=f"工作流配置不存在: app_id={app_id}"
|
||
)
|
||
|
||
config_dict = {
|
||
"nodes": config.nodes,
|
||
"edges": config.edges,
|
||
"variables": config.variables,
|
||
"execution_config": config.execution_config,
|
||
"triggers": config.triggers
|
||
}
|
||
|
||
return validate_workflow_config(config_dict, for_publish=True)
|
||
|
||
# ==================== 执行管理 ====================
|
||
|
||
def create_execution(
|
||
self,
|
||
workflow_config_id: uuid.UUID,
|
||
app_id: uuid.UUID,
|
||
trigger_type: str,
|
||
triggered_by: uuid.UUID | None = None,
|
||
conversation_id: uuid.UUID | None = None,
|
||
input_data: dict[str, Any] | None = None
|
||
) -> WorkflowExecution:
|
||
"""创建工作流执行记录
|
||
|
||
Args:
|
||
workflow_config_id: 工作流配置 ID
|
||
app_id: 应用 ID
|
||
trigger_type: 触发类型
|
||
triggered_by: 触发用户 ID
|
||
conversation_id: 会话 ID
|
||
input_data: 输入数据
|
||
|
||
Returns:
|
||
执行记录
|
||
"""
|
||
# 生成执行 ID
|
||
execution_id = f"exec_{uuid.uuid4().hex[:16]}"
|
||
|
||
execution = WorkflowExecution(
|
||
workflow_config_id=workflow_config_id,
|
||
app_id=app_id,
|
||
conversation_id=conversation_id,
|
||
execution_id=execution_id,
|
||
trigger_type=trigger_type,
|
||
triggered_by=triggered_by,
|
||
input_data=input_data or {},
|
||
status="pending"
|
||
)
|
||
|
||
self.db.add(execution)
|
||
self.db.commit()
|
||
self.db.refresh(execution)
|
||
|
||
logger.info(f"创建工作流执行记录: execution_id={execution_id}")
|
||
return execution
|
||
|
||
def get_execution(self, execution_id: str) -> WorkflowExecution | None:
|
||
"""获取执行记录
|
||
|
||
Args:
|
||
execution_id: 执行 ID
|
||
|
||
Returns:
|
||
执行记录或 None
|
||
"""
|
||
return self.execution_repo.get_by_execution_id(execution_id)
|
||
|
||
def get_executions_by_app(
|
||
self,
|
||
app_id: uuid.UUID,
|
||
limit: int = 50,
|
||
offset: int = 0
|
||
) -> list[WorkflowExecution]:
|
||
"""获取应用的执行记录列表
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
limit: 返回数量限制
|
||
offset: 偏移量
|
||
|
||
Returns:
|
||
执行记录列表
|
||
"""
|
||
return self.execution_repo.get_by_app_id(app_id, limit, offset)
|
||
|
||
def update_execution_status(
|
||
self,
|
||
execution_id: str,
|
||
status: str,
|
||
output_data: dict[str, Any] | None = None,
|
||
error_message: str | None = None,
|
||
error_node_id: str | None = None
|
||
) -> WorkflowExecution:
|
||
"""更新执行状态
|
||
|
||
Args:
|
||
execution_id: 执行 ID
|
||
status: 状态
|
||
output_data: 输出数据
|
||
error_message: 错误信息
|
||
error_node_id: 出错节点 ID
|
||
|
||
Returns:
|
||
执行记录
|
||
|
||
Raises:
|
||
BusinessException: 执行记录不存在时抛出
|
||
"""
|
||
execution = self.get_execution(execution_id)
|
||
if not execution:
|
||
raise BusinessException(
|
||
code=BizCode.NOT_FOUND,
|
||
message=f"执行记录不存在: execution_id={execution_id}"
|
||
)
|
||
|
||
execution.status = status
|
||
if output_data is not None:
|
||
execution.output_data = output_data
|
||
if error_message is not None:
|
||
execution.error_message = error_message
|
||
if error_node_id is not None:
|
||
execution.error_node_id = error_node_id
|
||
|
||
# 如果是完成状态,计算耗时
|
||
if status in ["completed", "failed", "cancelled", "timeout"]:
|
||
if not execution.completed_at:
|
||
execution.completed_at = datetime.datetime.now()
|
||
elapsed = (execution.completed_at - execution.started_at).total_seconds()
|
||
execution.elapsed_time = elapsed
|
||
|
||
self.db.commit()
|
||
self.db.refresh(execution)
|
||
|
||
logger.info(f"更新执行状态: execution_id={execution_id}, status={status}")
|
||
return execution
|
||
|
||
def get_execution_statistics(self, app_id: uuid.UUID) -> dict[str, Any]:
|
||
"""获取执行统计信息
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
|
||
Returns:
|
||
统计信息
|
||
"""
|
||
total = self.execution_repo.count_by_app_id(app_id)
|
||
completed = self.execution_repo.count_by_status(app_id, "completed")
|
||
failed = self.execution_repo.count_by_status(app_id, "failed")
|
||
running = self.execution_repo.count_by_status(app_id, "running")
|
||
|
||
return {
|
||
"total": total,
|
||
"completed": completed,
|
||
"failed": failed,
|
||
"running": running,
|
||
"success_rate": completed / total if total > 0 else 0
|
||
}
|
||
|
||
# ==================== 工作流执行 ====================
|
||
|
||
async def run(
|
||
self,
|
||
app_id: uuid.UUID,
|
||
payload: DraftRunRequest,
|
||
config: WorkflowConfig
|
||
):
|
||
"""运行工作流
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
input_data: 输入数据(包含 message 和 variables)
|
||
triggered_by: 触发用户 ID
|
||
conversation_id: 会话 ID(可选)
|
||
stream: 是否流式返回
|
||
|
||
Returns:
|
||
执行结果(非流式)或生成器(流式)
|
||
|
||
Raises:
|
||
BusinessException: 配置不存在或执行失败时抛出
|
||
"""
|
||
# 1. 获取工作流配置
|
||
if not config:
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
raise BusinessException(
|
||
code=BizCode.CONFIG_MISSING,
|
||
message=f"工作流配置不存在: app_id={app_id}"
|
||
)
|
||
input_data = {"message": payload.message, "variables": payload.variables, "conversation_id": payload.conversation_id}
|
||
|
||
# 转换 user_id 为 UUID
|
||
triggered_by_uuid = None
|
||
if payload.user_id:
|
||
try:
|
||
triggered_by_uuid = uuid.UUID(payload.user_id)
|
||
except (ValueError, AttributeError):
|
||
logger.warning(f"无效的 user_id 格式: {payload.user_id}")
|
||
|
||
# 转换 conversation_id 为 UUID
|
||
conversation_id_uuid = None
|
||
if payload.conversation_id:
|
||
try:
|
||
conversation_id_uuid = uuid.UUID(payload.conversation_id)
|
||
except (ValueError, AttributeError):
|
||
logger.warning(f"无效的 conversation_id 格式: {payload.conversation_id}")
|
||
|
||
# 2. 创建执行记录
|
||
execution = self.create_execution(
|
||
workflow_config_id=config.id,
|
||
app_id=app_id,
|
||
trigger_type="manual",
|
||
triggered_by=triggered_by_uuid,
|
||
conversation_id=conversation_id_uuid,
|
||
input_data=input_data
|
||
)
|
||
|
||
# 3. 构建工作流配置字典
|
||
workflow_config_dict = {
|
||
"nodes": config.nodes,
|
||
"edges": config.edges,
|
||
"variables": config.variables,
|
||
"execution_config": config.execution_config
|
||
}
|
||
|
||
# 4. 获取工作空间 ID(从 app 获取)
|
||
|
||
# 5. 执行工作流
|
||
from app.core.workflow.executor import execute_workflow
|
||
|
||
try:
|
||
# 更新状态为运行中
|
||
self.update_execution_status(execution.execution_id, "running")
|
||
|
||
result = await execute_workflow(
|
||
workflow_config=workflow_config_dict,
|
||
input_data=input_data,
|
||
execution_id=execution.execution_id,
|
||
workspace_id="",
|
||
user_id=payload.user_id
|
||
)
|
||
|
||
# 更新执行结果
|
||
if result.get("status") == "completed":
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"completed",
|
||
output_data=result.get("node_outputs", {})
|
||
)
|
||
else:
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"failed",
|
||
error_message=result.get("error")
|
||
)
|
||
|
||
# 返回增强的响应结构
|
||
return {
|
||
"execution_id": execution.execution_id,
|
||
"status": result.get("status"),
|
||
"output": result.get("output"), # 最终输出(字符串)
|
||
"output_data": result.get("node_outputs", {}), # 所有节点输出(详细数据)
|
||
"conversation_id": result.get("conversation_id"), # 所有节点输出(详细数据)payload., # 会话 ID
|
||
"error_message": result.get("error"),
|
||
"elapsed_time": result.get("elapsed_time"),
|
||
"token_usage": result.get("token_usage")
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"工作流执行失败: execution_id={execution.execution_id}, error={e}", exc_info=True)
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"failed",
|
||
error_message=str(e)
|
||
)
|
||
raise BusinessException(
|
||
code=BizCode.INTERNAL_ERROR,
|
||
message=f"工作流执行失败: {str(e)}"
|
||
)
|
||
|
||
async def run_stream(
|
||
self,
|
||
app_id: uuid.UUID,
|
||
payload: DraftRunRequest,
|
||
config: WorkflowConfig
|
||
):
|
||
"""运行工作流(流式)
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
payload: 请求对象(包含 message, variables, conversation_id 等)
|
||
config: 存储类型(可选)
|
||
|
||
Yields:
|
||
SSE 格式的流式事件
|
||
|
||
Raises:
|
||
BusinessException: 配置不存在或执行失败时抛出
|
||
"""
|
||
# 1. 获取工作流配置
|
||
if not config:
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
raise BusinessException(
|
||
code=BizCode.CONFIG_MISSING,
|
||
message=f"工作流配置不存在: app_id={app_id}"
|
||
)
|
||
input_data = {"message": payload.message, "variables": payload.variables,
|
||
"conversation_id": payload.conversation_id}
|
||
|
||
# 转换 user_id 为 UUID
|
||
triggered_by_uuid = None
|
||
if payload.user_id:
|
||
try:
|
||
triggered_by_uuid = uuid.UUID(payload.user_id)
|
||
except (ValueError, AttributeError):
|
||
logger.warning(f"无效的 user_id 格式: {payload.user_id}")
|
||
|
||
# 转换 conversation_id 为 UUID
|
||
conversation_id_uuid = None
|
||
if payload.conversation_id:
|
||
try:
|
||
conversation_id_uuid = uuid.UUID(payload.conversation_id)
|
||
except (ValueError, AttributeError):
|
||
logger.warning(f"无效的 conversation_id 格式: {payload.conversation_id}")
|
||
|
||
# 2. 创建执行记录
|
||
execution = self.create_execution(
|
||
workflow_config_id=config.id,
|
||
app_id=app_id,
|
||
trigger_type="manual",
|
||
triggered_by=triggered_by_uuid,
|
||
conversation_id=conversation_id_uuid,
|
||
input_data=input_data
|
||
)
|
||
|
||
# 3. 构建工作流配置字典
|
||
workflow_config_dict = {
|
||
"nodes": config.nodes,
|
||
"edges": config.edges,
|
||
"variables": config.variables,
|
||
"execution_config": config.execution_config
|
||
}
|
||
|
||
# 4. 获取工作空间 ID(从 app 获取)
|
||
|
||
# 5. 流式执行工作流
|
||
|
||
try:
|
||
# 更新状态为运行中
|
||
self.update_execution_status(execution.execution_id, "running")
|
||
|
||
# 发送开始事件
|
||
yield format_sse_message("workflow_start", {
|
||
"execution_id": execution.execution_id,
|
||
"conversation_id_uuid": str(conversation_id_uuid),
|
||
})
|
||
|
||
# 调用流式执行
|
||
async for event in self._run_workflow_stream(
|
||
workflow_config=workflow_config_dict,
|
||
input_data=input_data,
|
||
execution_id=execution.execution_id,
|
||
workspace_id="",
|
||
user_id=payload.user_id
|
||
):
|
||
# 清理事件数据,移除不可序列化的对象
|
||
cleaned_event = self._clean_event_for_json(event)
|
||
# 转换为 SSE 格式
|
||
yield f"data: {json.dumps(cleaned_event)}\n\n"
|
||
|
||
# 发送完成事件
|
||
yield format_sse_message("workflow_end", {
|
||
"execution_id": execution.execution_id,
|
||
"conversation_id_uuid": str(conversation_id_uuid),
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"工作流流式执行失败: execution_id={execution.execution_id}, error={e}", exc_info=True)
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"failed",
|
||
error_message=str(e)
|
||
)
|
||
# 发送错误事件
|
||
yield f"data: {json.dumps({'type': 'error', 'execution_id': execution.execution_id, 'error': str(e)})}\n\n"
|
||
|
||
async def run_workflow(
|
||
self,
|
||
app_id: uuid.UUID,
|
||
input_data: dict[str, Any],
|
||
triggered_by: uuid.UUID,
|
||
conversation_id: uuid.UUID | None = None,
|
||
stream: bool = False
|
||
):
|
||
"""运行工作流
|
||
|
||
Args:
|
||
app_id: 应用 ID
|
||
input_data: 输入数据(包含 message 和 variables)
|
||
triggered_by: 触发用户 ID
|
||
conversation_id: 会话 ID(可选)
|
||
stream: 是否流式返回
|
||
|
||
Returns:
|
||
执行结果(非流式)或生成器(流式)
|
||
|
||
Raises:
|
||
BusinessException: 配置不存在或执行失败时抛出
|
||
"""
|
||
# 1. 获取工作流配置
|
||
config = self.get_workflow_config(app_id)
|
||
if not config:
|
||
raise BusinessException(
|
||
code=BizCode.NOT_FOUND,
|
||
message=f"工作流配置不存在: app_id={app_id}"
|
||
)
|
||
|
||
# 2. 创建执行记录
|
||
execution = self.create_execution(
|
||
workflow_config_id=config.id,
|
||
app_id=app_id,
|
||
trigger_type="manual",
|
||
triggered_by=triggered_by,
|
||
conversation_id=conversation_id,
|
||
input_data=input_data
|
||
)
|
||
|
||
# 3. 构建工作流配置字典
|
||
workflow_config_dict = {
|
||
"nodes": config.nodes,
|
||
"edges": config.edges,
|
||
"variables": config.variables,
|
||
"execution_config": config.execution_config
|
||
}
|
||
|
||
# 4. 获取工作空间 ID(从 app 获取)
|
||
from app.models import App
|
||
app = self.db.query(App).filter(App.id == app_id).first()
|
||
if not app:
|
||
raise BusinessException(
|
||
code=BizCode.NOT_FOUND,
|
||
message=f"应用不存在: app_id={app_id}"
|
||
)
|
||
|
||
# 5. 执行工作流
|
||
from app.core.workflow.executor import execute_workflow
|
||
|
||
try:
|
||
# 更新状态为运行中
|
||
self.update_execution_status(execution.execution_id, "running")
|
||
|
||
if stream:
|
||
# 流式执行
|
||
return self._run_workflow_stream(
|
||
workflow_config_dict,
|
||
input_data,
|
||
execution.execution_id,
|
||
str(app.workspace_id),
|
||
str(triggered_by)
|
||
)
|
||
else:
|
||
# 非流式执行
|
||
result = await execute_workflow(
|
||
workflow_config=workflow_config_dict,
|
||
input_data=input_data,
|
||
execution_id=execution.execution_id,
|
||
workspace_id=str(app.workspace_id),
|
||
user_id=str(triggered_by)
|
||
)
|
||
|
||
# 更新执行结果
|
||
if result.get("status") == "completed":
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"completed",
|
||
output_data=result.get("node_outputs", {})
|
||
)
|
||
else:
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"failed",
|
||
error_message=result.get("error")
|
||
)
|
||
|
||
# 返回增强的响应结构
|
||
return {
|
||
"execution_id": execution.execution_id,
|
||
"status": result.get("status"),
|
||
"output": result.get("output"), # 最终输出(字符串)
|
||
"output_data": result.get("node_outputs", {}), # 所有节点输出(详细数据)
|
||
"error_message": result.get("error"),
|
||
"elapsed_time": result.get("elapsed_time"),
|
||
"token_usage": result.get("token_usage")
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"工作流执行失败: execution_id={execution.execution_id}, error={e}", exc_info=True)
|
||
self.update_execution_status(
|
||
execution.execution_id,
|
||
"failed",
|
||
error_message=str(e)
|
||
)
|
||
raise BusinessException(
|
||
code=BizCode.INTERNAL_ERROR,
|
||
message=f"工作流执行失败: {str(e)}"
|
||
)
|
||
|
||
def _clean_event_for_json(self, event: dict[str, Any]) -> dict[str, Any]:
|
||
"""清理事件数据,移除不可序列化的对象
|
||
|
||
Args:
|
||
event: 原始事件数据
|
||
|
||
Returns:
|
||
可序列化的事件数据
|
||
"""
|
||
from langchain_core.messages import BaseMessage
|
||
|
||
def clean_value(value):
|
||
"""递归清理值"""
|
||
if isinstance(value, BaseMessage):
|
||
# 将 Message 对象转换为字典
|
||
return {
|
||
"type": value.__class__.__name__,
|
||
"content": value.content,
|
||
}
|
||
elif isinstance(value, dict):
|
||
return {k: clean_value(v) for k, v in value.items()}
|
||
elif isinstance(value, list):
|
||
return [clean_value(item) for item in value]
|
||
elif isinstance(value, (str, int, float, bool, type(None))):
|
||
return value
|
||
else:
|
||
# 其他不可序列化的对象转换为字符串
|
||
return str(value)
|
||
|
||
return clean_value(event)
|
||
|
||
async def _run_workflow_stream(
|
||
self,
|
||
workflow_config: dict[str, Any],
|
||
input_data: dict[str, Any],
|
||
execution_id: str,
|
||
workspace_id: str,
|
||
user_id: str):
|
||
"""运行工作流(流式,内部方法)
|
||
|
||
Args:
|
||
workflow_config: 工作流配置
|
||
input_data: 输入数据
|
||
execution_id: 执行 ID
|
||
workspace_id: 工作空间 ID
|
||
user_id: 用户 ID
|
||
|
||
Yields:
|
||
流式事件
|
||
"""
|
||
from app.core.workflow.executor import execute_workflow_stream
|
||
|
||
try:
|
||
output_data = {}
|
||
|
||
async for event in execute_workflow_stream(
|
||
workflow_config=workflow_config,
|
||
input_data=input_data,
|
||
execution_id=execution_id,
|
||
workspace_id=workspace_id,
|
||
user_id=user_id
|
||
):
|
||
# 转发事件
|
||
yield event
|
||
|
||
# 收集输出数据
|
||
# if event.get("type") == "node_complete":
|
||
# node_data = event.get("data", {})
|
||
# node_outputs = node_data.get("node_outputs", {})
|
||
# output_data.update(node_outputs)
|
||
#
|
||
# # 处理完成事件
|
||
# if event.get("type") == "workflow_complete":
|
||
# self.update_execution_status(
|
||
# execution_id,
|
||
# "completed",
|
||
# output_data=output_data
|
||
# )
|
||
#
|
||
# # 处理错误事件
|
||
# if event.get("type") == "workflow_error":
|
||
# self.update_execution_status(
|
||
# execution_id,
|
||
# "failed",
|
||
# error_message=event.get("error")
|
||
# )
|
||
|
||
except Exception as e:
|
||
logger.error(f"工作流流式执行失败: execution_id={execution_id}, error={e}", exc_info=True)
|
||
self.update_execution_status(
|
||
execution_id,
|
||
"failed",
|
||
error_message=str(e)
|
||
)
|
||
yield {
|
||
"type": "workflow_error",
|
||
"execution_id": execution_id,
|
||
"error": str(e)
|
||
}
|
||
|
||
|
||
# ==================== 依赖注入函数 ====================
|
||
|
||
def get_workflow_service(
|
||
db: Annotated[Session, Depends(get_db)]
|
||
) -> WorkflowService:
|
||
"""获取工作流服务(依赖注入)"""
|
||
return WorkflowService(db)
|