Files
2025-09-15 10:22:45 +08:00

289 lines
11 KiB
Python

# Workflow Rewrite Agent for ComfyUI Workflow Structure Fixes
import json
import time
from typing import Dict, Any, Optional
from agents import RunContextWrapper
from agents.tool import function_tool
from .workflow_rewrite_agent_simple import rewrite_workflow_simple
from ..dao.workflow_table import get_workflow_data, save_workflow_data, get_workflow_data_ui, get_workflow_data_by_id
from ..utils.comfy_gateway import get_object_info, get_object_info_by_class_list
from ..utils.request_context import get_rewrite_context, get_session_id
from ..utils.logger import log
def get_workflow_data_from_config(config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""获取工作流数据,优先使用checkpoint_id,如果没有则使用session_id"""
workflow_checkpoint_id = config.get('workflow_checkpoint_id')
session_id = config.get('session_id')
if workflow_checkpoint_id:
try:
checkpoint_data = get_workflow_data_by_id(workflow_checkpoint_id)
if checkpoint_data and checkpoint_data.get('workflow_data'):
return checkpoint_data['workflow_data']
except Exception as e:
log.error(f"Failed to get workflow data from checkpoint {workflow_checkpoint_id}: {str(e)}")
if session_id:
return get_workflow_data(session_id)
return None
def get_workflow_data_ui_from_config(config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""获取工作流UI数据,优先使用checkpoint_id,如果没有则使用session_id"""
workflow_checkpoint_id = config.get('workflow_checkpoint_id')
session_id = config.get('session_id')
if workflow_checkpoint_id:
try:
checkpoint_data = get_workflow_data_by_id(workflow_checkpoint_id)
if checkpoint_data and checkpoint_data.get('workflow_data_ui'):
return checkpoint_data['workflow_data_ui']
except Exception as e:
log.error(f"Failed to get workflow UI data from checkpoint {workflow_checkpoint_id}: {str(e)}")
if session_id:
return get_workflow_data_ui(session_id)
return None
@function_tool
def get_current_workflow() -> str:
"""获取当前session的工作流数据"""
session_id = get_session_id()
if not session_id:
return json.dumps({"error": "No session_id found in context"})
workflow_data = get_workflow_data(session_id)
if not workflow_data:
return json.dumps({"error": "No workflow data found for this session"})
workflow_data_str = json.dumps(workflow_data, ensure_ascii=False)
get_rewrite_context().current_workflow = workflow_data_str
return workflow_data_str
@function_tool
async def get_node_info(node_class: str) -> str:
"""获取节点的详细信息,包括输入输出参数"""
try:
object_info = await get_object_info_by_class_list([node_class])
if node_class in object_info:
node_info_str = json.dumps(object_info[node_class], ensure_ascii=False)
get_rewrite_context().node_infos[node_class] = node_info_str
return node_info_str
else:
# 搜索类似的节点类
similar_nodes = [k for k in object_info.keys() if node_class.lower() in k.lower()]
if similar_nodes:
return json.dumps({
"error": f"Node class '{node_class}' not found",
"suggestions": similar_nodes[:5]
})
return json.dumps({"error": f"Node class '{node_class}' not found"})
except Exception as e:
return json.dumps({"error": f"Failed to get node info: {str(e)}"})
@function_tool
async def get_node_infos(node_class_list: list[str]) -> str:
"""获取多个节点的详细信息,包括输入输出参数。只做最小化有必要的查询,不要查询所有节点。尽量不要超过5个"""
try:
object_info = await get_object_info_by_class_list(node_class_list)
node_infos = {}
for node_class in node_class_list:
if node_class in object_info:
node_infos[node_class] = object_info[node_class]
return json.dumps(node_infos)
except Exception as e:
return json.dumps({"error": f"Failed to get node infos of {','.join(node_class_list)}: {str(e)}"})
def save_checkpoint_before_modification(session_id: str, action_description: str) -> Optional[int]:
"""在修改工作流前保存checkpoint,返回checkpoint_id"""
try:
current_workflow = get_workflow_data(session_id)
if not current_workflow:
return None
checkpoint_id = save_workflow_data(
session_id,
current_workflow,
workflow_data_ui=get_workflow_data_ui(session_id),
attributes={
"checkpoint_type": "workflow_rewrite_start",
"description": f"Checkpoint before {action_description}",
"action": "workflow_rewrite_checkpoint",
"timestamp": time.time()
}
)
log.info(f"Saved workflow rewrite checkpoint with ID: {checkpoint_id}")
return checkpoint_id
except Exception as e:
log.error(f"Failed to save checkpoint before modification: {str(e)}")
return None
def tool_error_function(ctx: RunContextWrapper[Any], error: Exception) -> str:
"""The default tool error function, which just returns a generic error message."""
# return f"An error occurred while running the tool. Please try again. Error: {str(error)}"
return json.dumps({"error": str(error)}, ensure_ascii= False)
# def update_workflow(session_id: str, workflow_data: Union[Dict[str, Any], str]) -> str:
@function_tool
def update_workflow(workflow_data: str = "") -> str:
"""
更新当前session的工作流数据
Args:
workflow_data: 工作流数据,必须是严格的json格式字符串
Returns:
str: 更新后的工作流数据
"""
try:
session_id = get_session_id()
if not session_id:
return json.dumps({"error": "No session_id found in context"})
if not workflow_data or not isinstance(workflow_data, str) or not workflow_data.strip():
rewrite_context = get_rewrite_context()
log.info(f"[update_workflow] workflow_data: {workflow_data}, trigger simple rewrite, context: {rewrite_context}")
workflow_data = rewrite_workflow_simple(rewrite_context)
log.info(f"[update_workflow] workflow_data: {workflow_data}")
# 在修改前保存checkpoint
checkpoint_id = save_checkpoint_before_modification(session_id, "workflow update")
# 解析JSON字符串
workflow_dict = json.loads(workflow_data) if isinstance(workflow_data, str) else workflow_data
version_id = save_workflow_data(
session_id,
workflow_dict,
attributes={"action": "workflow_rewrite", "description": "Workflow structure fixed by rewrite agent"}
)
# 构建返回数据,包含checkpoint信息
ext_data = [{
"type": "workflow_update",
"data": {
"workflow_data": workflow_dict
}
}]
# 如果成功保存了checkpoint,添加修改前的checkpoint信息(给用户消息)
if checkpoint_id:
ext_data.append({
"type": "workflow_rewrite_checkpoint",
"data": {
"checkpoint_id": checkpoint_id,
"checkpoint_type": "workflow_rewrite_start"
}
})
if version_id:
ext_data.append({
"type": "workflow_rewrite_complete",
"data": {
"version_id": version_id,
"checkpoint_type": "workflow_rewrite_complete"
}
})
return json.dumps({
"success": True,
"version_id": version_id,
"message": f"Workflow updated successfully with version ID: {version_id}",
"ext": ext_data
})
except Exception as e:
log.error(f"Failed to update workflow: {str(e)}")
return json.dumps({"error": f"Failed to update workflow: {str(e)}. Please try regenerating the workflow and then update again."})
@function_tool
def remove_node(node_id: str) -> str:
"""从工作流中移除节点"""
try:
session_id = get_session_id()
if not session_id:
return json.dumps({"error": "No session_id found in context"})
# 在修改前保存checkpoint
checkpoint_id = save_checkpoint_before_modification(session_id, f"remove node {node_id}")
workflow_data = get_workflow_data(session_id)
if not workflow_data:
return json.dumps({"error": "No workflow data found"})
if node_id not in workflow_data:
return json.dumps({"error": f"Node {node_id} not found"})
# 移除节点
removed_node = workflow_data.pop(node_id)
# 移除所有指向该节点的连接
for other_node_id, node_data in workflow_data.items():
inputs = node_data.get("inputs", {})
for input_name, input_value in list(inputs.items()):
if isinstance(input_value, list) and len(input_value) == 2:
if str(input_value[0]) == node_id:
# 移除这个连接
del inputs[input_name]
# 保存更新
version_id = save_workflow_data(
session_id,
workflow_data,
attributes={
"action": "remove_node",
"description": f"Removed node {node_id}",
"changes": {
"node_id": node_id,
"removed_node": removed_node
}
}
)
# 构建返回数据,包含checkpoint信息
ext_data = [{
"type": "workflow_update",
"data": {
"workflow_data": workflow_data,
"changes": {
"action": "remove_node",
"node_id": node_id,
"removed_node": removed_node
}
}
}]
# 如果成功保存了checkpoint,添加修改前的checkpoint信息(给用户消息)
if checkpoint_id:
ext_data.append({
"type": "workflow_rewrite_checkpoint",
"data": {
"checkpoint_id": checkpoint_id,
"checkpoint_type": "workflow_rewrite_start"
}
})
# 添加修改后的版本信息(给AI响应)
ext_data.append({
"type": "workflow_rewrite_complete",
"data": {
"version_id": version_id,
"checkpoint_type": "workflow_rewrite_complete"
}
})
return json.dumps({
"success": True,
"version_id": version_id,
"message": f"Removed node {node_id} and cleaned up connections",
"ext": ext_data
})
except Exception as e:
return json.dumps({"error": f"Failed to remove node: {str(e)}"})