Skip to content

Commit d1ee6ab

Browse files
version2.0.5
2 parents af1016d + 3264b55 commit d1ee6ab

File tree

7 files changed

+192
-17
lines changed

7 files changed

+192
-17
lines changed

backend/agent_factory.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'''
22
Author: ai-business-hql [email protected]
33
Date: 2025-07-31 19:38:08
4-
LastEditors: ai-business-hql qingli.hql@alibaba-inc.com
5-
LastEditTime: 2025-08-14 06:14:22
4+
LastEditors: ai-business-hql ai.bussiness.hql@gmail.com
5+
LastEditTime: 2025-08-25 21:23:10
66
FilePath: /comfyui_copilot/backend/agent_factory.py
77
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
88
'''
9-
from agents import Agent, OpenAIChatCompletionsModel
9+
from agents import Agent, OpenAIChatCompletionsModel, ModelSettings
1010
from dotenv import dotenv_values
1111
from .utils.globals import LLM_DEFAULT_BASE_URL, LMSTUDIO_DEFAULT_BASE_URL, get_comfyui_copilot_api_key, is_lmstudio_url
1212
from openai import AsyncOpenAI
@@ -68,5 +68,7 @@ def create_agent(**kwargs) -> Agent:
6868
default_model_name = os.environ.get("OPENAI_MODEL", "gemini-2.5-flash")
6969
model_name = kwargs.pop("model") or default_model_name
7070
model = OpenAIChatCompletionsModel(model_name, openai_client=client)
71-
71+
72+
if config.get("max_tokens"):
73+
return Agent(model=model, model_settings=ModelSettings(max_tokens=config.get("max_tokens") or 8192), **kwargs)
7274
return Agent(model=model, **kwargs)

backend/service/mcp_client.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'''
22
Author: ai-business-hql [email protected]
33
Date: 2025-06-16 16:50:17
4-
LastEditors: ai-business-hql qingli.hql@alibaba-inc.com
5-
LastEditTime: 2025-08-20 11:57:58
4+
LastEditors: ai-business-hql ai.bussiness.hql@gmail.com
5+
LastEditTime: 2025-08-25 20:06:27
66
FilePath: /comfyui_copilot/backend/service/mcp-client.py
77
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
88
'''
@@ -19,13 +19,16 @@
1919
from agents.mcp import MCPServerSse
2020
from agents.run import Runner
2121
from agents.tracing import set_tracing_disabled
22+
from agents import handoff, RunContextWrapper
23+
from agents.extensions import handoff_filters
2224

2325
from ..agent_factory import create_agent
2426
from ..service.workflow_rewrite_agent import create_workflow_rewrite_agent
25-
from ..utils.request_context import get_session_id, get_config
27+
from ..utils.request_context import get_rewrite_context, get_session_id, get_config
2628
from ..utils.logger import log
2729
from openai.types.responses import ResponseTextDeltaEvent
2830
from openai import APIError, RateLimitError
31+
from pydantic import BaseModel
2932

3033

3134
class ImageData:
@@ -76,6 +79,19 @@ async def comfyui_agent_invoke(messages: List[Dict[str, Any]], images: List[Imag
7679
# 创建workflow_rewrite_agent实例 (session_id通过context获取)
7780
workflow_rewrite_agent_instance = create_workflow_rewrite_agent()
7881

82+
class HandoffRewriteData(BaseModel):
83+
rewrite_intent: str
84+
85+
async def on_handoff(ctx: RunContextWrapper[None], input_data: HandoffRewriteData):
86+
get_rewrite_context().rewrite_intent = input_data.rewrite_intent
87+
log.info(f"Rewrite agent called with intent: {input_data.rewrite_intent}")
88+
89+
handoff_rewrite = handoff(
90+
agent=workflow_rewrite_agent_instance,
91+
input_type=HandoffRewriteData,
92+
on_handoff=on_handoff,
93+
)
94+
7995
agent = create_agent(
8096
name="ComfyUI-Copilot",
8197
instructions=f"""You are a powerful AI assistant for designing image processing workflows, capable of automating problem-solving using tools and commands.
@@ -121,7 +137,7 @@ async def comfyui_agent_invoke(messages: List[Dict[str, Any]], images: List[Imag
121137
""",
122138
mcp_servers=[server],
123139
model=model_name,
124-
handoffs=[workflow_rewrite_agent_instance],
140+
handoffs=[handoff_rewrite],
125141
config=config
126142
)
127143

backend/service/workflow_rewrite_agent.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Author: ai-business-hql [email protected]
33
Date: 2025-07-24 17:10:23
44
LastEditors: ai-business-hql [email protected]
5-
LastEditTime: 2025-08-22 11:26:59
5+
LastEditTime: 2025-08-25 20:10:55
66
FilePath: /comfyui_copilot/backend/service/workflow_rewrite_agent.py
77
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
88
'''
@@ -30,6 +30,7 @@ def get_rewrite_expert_by_name(name_list: list[str]) -> str:
3030
result = get_rewrite_expert_by_name_list(name_list)
3131
temp = json.dumps(result, ensure_ascii=False)
3232
log.info(f"get_rewrite_expert_by_name, name_list: {name_list}, result: {temp}")
33+
get_rewrite_context().rewrite_expert += temp
3334
return temp
3435

3536
def get_rewrite_export_schema() -> dict:
@@ -104,6 +105,9 @@ def create_workflow_rewrite_agent():
104105
始终以用户的实际需求为导向,提供专业、准确、高效的工作流改写服务。
105106
""",
106107
tools=[get_rewrite_expert_by_name, get_current_workflow, get_node_info, update_workflow, remove_node],
108+
config={
109+
"max_tokens": 8192
110+
}
107111
)
108112

109113
# 注意:工作流改写代理现在需要在有session context的环境中创建
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
'''
2+
Author: ai-business-hql [email protected]
3+
Date: 2025-08-25 20:16:18
4+
LastEditors: ai-business-hql [email protected]
5+
LastEditTime: 2025-08-25 21:08:36
6+
FilePath: /ComfyUI-Copilot/backend/service/workflow_rewrite_agent_simple.py
7+
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8+
'''
9+
10+
import json
11+
from typing import Dict, Any, Optional
12+
import asyncio
13+
from pydantic import BaseModel
14+
from openai import OpenAI
15+
16+
from ..agent_factory import create_agent
17+
from ..utils.globals import WORKFLOW_MODEL_NAME, get_comfyui_copilot_api_key, LLM_DEFAULT_BASE_URL
18+
from ..utils.request_context import get_rewrite_context, RewriteContext
19+
from ..utils.logger import log
20+
21+
22+
class RewriteResponse(BaseModel):
23+
"""
24+
重写后的工作流数据,必须是严格的JSON字符串
25+
"""
26+
workflow_data: str
27+
28+
def rewrite_workflow_simple(rewrite_context: RewriteContext) -> str:
29+
"""
30+
使用简化的方式重写工作流,直接调用OpenAI API
31+
32+
Args:
33+
rewrite_context: 包含所有重写所需信息的上下文
34+
35+
Returns:
36+
改写后的API工作流(JSON字符串)
37+
"""
38+
try:
39+
# 构建给LLM的完整上下文信息
40+
context_info = f"""
41+
## 重写意图
42+
{rewrite_context.rewrite_intent}
43+
44+
## 当前工作流 (ComfyUI API格式)
45+
{rewrite_context.current_workflow}
46+
47+
## 节点信息
48+
{json.dumps(rewrite_context.node_infos or {}, ensure_ascii=False)}
49+
50+
## 专家经验
51+
{rewrite_context.rewrite_expert or "无特定专家经验"}
52+
"""
53+
54+
# 构建系统提示词
55+
system_prompt = """你是专业的ComfyUI工作流重写专家。你需要根据用户的重写意图,对现有的ComfyUI API格式工作流进行改写。
56+
57+
## ComfyUI工作流基础知识
58+
- ComfyUI工作流是基于节点的图形化系统,每个节点代表一个操作
59+
- 节点通过输入输出端口进行数据流转,形成有向无环图(DAG)
60+
- 节点的输入输出类型必须严格匹配(如image, latent, model, string, int, float等)
61+
- 每个节点的必需输入必须有有效连接,否则会导致工作流运行失败
62+
63+
## API格式结构
64+
ComfyUI API格式工作流是一个JSON对象,其中:
65+
- 键是节点ID (字符串形式的数字,如"1", "2", "3")
66+
- 值是节点对象,包含:
67+
- class_type: 节点类型
68+
- inputs: 输入参数对象,可以是:
69+
- 直接值(字符串、数字等)
70+
- 连接引用 [node_id, output_index] 形式的数组
71+
72+
## 重写原则
73+
1. **保持结构完整性**:确保所有节点的必需输入都有连接
74+
2. **类型匹配**:输入输出类型必须严格匹配
75+
3. **连接正确性**:确保节点间的连接关系正确
76+
4. **功能实现**:确保改写后的工作流能实现用户的重写意图
77+
78+
## 重要:输出格式要求
79+
你必须严格按照以下JSON格式返回结果,不要添加任何其他说明文字:
80+
{
81+
"workflow_data": "这里是完整的工作流JSON字符串"
82+
}
83+
"""
84+
85+
# 创建OpenAI客户端
86+
client = OpenAI(
87+
base_url = LLM_DEFAULT_BASE_URL,
88+
api_key = get_comfyui_copilot_api_key() or ""
89+
)
90+
91+
# 调用LLM
92+
completion = client.chat.completions.parse(
93+
model=WORKFLOW_MODEL_NAME,
94+
messages=[
95+
{"role": "system", "content": system_prompt},
96+
{"role": "user", "content": context_info}
97+
],
98+
max_tokens=8192,
99+
temperature=0.1, # 降低随机性,确保输出一致性
100+
response_format=RewriteResponse # 要求返回JSON格式
101+
)
102+
103+
result = completion.choices[0].message.parsed
104+
log.info(f"workflow simple rewrite LLM response: {result}")
105+
106+
# 解析返回的JSON
107+
# result = json.loads(result_text)
108+
109+
# 验证返回格式
110+
if result.workflow_data is None:
111+
return "{}"
112+
113+
# 验证workflow_data是有效的JSON字符串
114+
if isinstance(result.workflow_data, str):
115+
# 尝试解析以验证有效性
116+
json.loads(result.workflow_data)
117+
return result.workflow_data
118+
else:
119+
# 如果不是字符串,转换为字符串
120+
return json.dumps(result.workflow_data, ensure_ascii=False)
121+
122+
except Exception as e:
123+
log.error(f"简化工作流重写失败: {str(e)}")
124+
return f"工作流重写失败:{str(e)}"

backend/service/workflow_rewrite_tools.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
from agents import RunContextWrapper
88
from agents.tool import function_tool
9+
from .workflow_rewrite_agent_simple import rewrite_workflow_simple
910

1011
from ..dao.workflow_table import get_workflow_data, save_workflow_data, get_workflow_data_ui, get_workflow_data_by_id
1112
from ..utils.comfy_gateway import get_object_info
12-
from ..utils.request_context import get_session_id
13+
from ..utils.request_context import get_rewrite_context, get_session_id
1314
from ..utils.logger import log
1415

1516
def get_workflow_data_from_config(config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
@@ -58,15 +59,20 @@ def get_current_workflow() -> str:
5859
workflow_data = get_workflow_data(session_id)
5960
if not workflow_data:
6061
return json.dumps({"error": "No workflow data found for this session"})
61-
return json.dumps(workflow_data)
62+
63+
workflow_data_str = json.dumps(workflow_data, ensure_ascii=False)
64+
get_rewrite_context().current_workflow = workflow_data_str
65+
return workflow_data_str
6266

6367
@function_tool
6468
async def get_node_info(node_class: str) -> str:
6569
"""获取节点的详细信息,包括输入输出参数"""
6670
try:
6771
object_info = await get_object_info()
6872
if node_class in object_info:
69-
return json.dumps(object_info[node_class])
73+
node_info_str = json.dumps(object_info[node_class], ensure_ascii=False)
74+
get_rewrite_context().node_infos[node_class] = node_info_str
75+
return node_info_str
7076
else:
7177
# 搜索类似的节点类
7278
similar_nodes = [k for k in object_info.keys() if node_class.lower() in k.lower()]
@@ -140,10 +146,10 @@ def update_workflow(workflow_data: str = "") -> str:
140146
return json.dumps({"error": "No session_id found in context"})
141147

142148
if not workflow_data or not isinstance(workflow_data, str) or not workflow_data.strip():
143-
return json.dumps({
144-
"error": "Missing required argument: workflow_data",
145-
"hint": "Pass the full workflow JSON as a string."
146-
})
149+
rewrite_context = get_rewrite_context()
150+
log.info(f"[update_workflow] workflow_data: {workflow_data}, trigger simple rewrite, context: {rewrite_context}")
151+
workflow_data = rewrite_workflow_simple(rewrite_context)
152+
147153

148154
log.info(f"[update_workflow] workflow_data: {workflow_data}")
149155
# 在修改前保存checkpoint

backend/utils/request_context.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55

66
import contextvars
77
from typing import Optional, Dict, Any
8+
from pydantic import BaseModel
9+
10+
11+
class RewriteContext(BaseModel):
12+
rewrite_intent: str = ""
13+
current_workflow: str = ""
14+
node_infos: Optional[Dict[str, Any]] = None
15+
rewrite_expert: Optional[str] = ""
816

917
# Define context variables
1018
_session_id: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar('session_id', default=None)
1119
_workflow_checkpoint_id: contextvars.ContextVar[Optional[int]] = contextvars.ContextVar('workflow_checkpoint_id', default=None)
1220
_config: contextvars.ContextVar[Optional[Dict[str, Any]]] = contextvars.ContextVar('config', default=None)
21+
_rewrite_context: contextvars.ContextVar[Optional[RewriteContext]] = contextvars.ContextVar('rewrite_context', default=None)
22+
1323

1424
def set_session_id(session_id: str) -> None:
1525
"""Set the session ID for the current request context"""
@@ -48,3 +58,16 @@ def clear_request_context() -> None:
4858
_session_id.set(None)
4959
_workflow_checkpoint_id.set(None)
5060
_config.set(None)
61+
62+
def set_rewrite_context(rewrite_context: RewriteContext) -> None:
63+
"""Set the rewrite context for the current request context"""
64+
_rewrite_context.set(rewrite_context)
65+
66+
def get_rewrite_context() -> RewriteContext:
67+
"""Get the rewrite context from the current request context"""
68+
context = _rewrite_context.get()
69+
if context is None:
70+
# 初始化一个新的 RewriteContext
71+
context = RewriteContext()
72+
_rewrite_context.set(context)
73+
return context

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "ComfyUI-Copilot"
33
description = "Your Intelligent Assistant for Comfy-UI."
4-
version = "2.0.4"
4+
version = "2.0.5"
55
license = {file = "LICENSE"}
66

77
[project.urls]

0 commit comments

Comments
 (0)