Phase G3: ACP Gateway + DAG Multi-Agent - acp_gateway.py (Agent communication layer, 350L), workflow_admin.py upgraded (async parallel execution + agent_node), Workflow.tsx updated (agent node + real-time streaming + WebSocket)
This commit is contained in:
1 parent
9b3e15ace6
commit
d2e3147fb8
3 files changed
+1073
-88
No files matched your search
@@ -2,13 +2,14 @@
|
||||
Agent 可视化编排 API(管理员)
|
||||
Phase 3.6: Agent Visual Orchestration - DAG Workflow Editor
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body, WebSocket, WebSocketDisconnect
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_, func
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Any
|
||||
import json
|
||||
import uuid
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
from ...database import get_db, SessionLocal
|
||||
@@ -25,7 +26,7 @@ router = APIRouter()
|
||||
|
||||
class WorkflowNode(BaseModel):
|
||||
id: str
|
||||
type: str # start / llm / knowledge / skill / condition / http / code / end
|
||||
type: str # start / llm / knowledge / skill / condition / http / code / agent / end
|
||||
position: dict # {x, y}
|
||||
data: dict # {label, config: {...}}
|
||||
|
||||
@@ -147,6 +148,21 @@ NODE_TEMPLATES = {
|
||||
"code": {"type": "textarea", "label": "Python 代码", "default": "result = input\noutput = result", "help": "可用变量: input, prev_output, context. 必须设置 output 变量"}
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"type": "agent",
|
||||
"label": "Agent 节点",
|
||||
"icon": "bot",
|
||||
"color": "#7C3AED",
|
||||
"description": "调用平台 AI Agent — 支持并行多 Agent 协作",
|
||||
"config_schema": {
|
||||
"agent_id": {"type": "select", "label": "选择 Agent", "options_from": "agents"},
|
||||
"prompt": {"type": "textarea", "label": "提示词模板", "default": "{input}", "help": "可用变量: {input}, {prev_output}, {context} 中的变量"},
|
||||
"system_prompt": {"type": "textarea", "label": "系统提示词 (覆盖Agent默认)", "default": "", "help": "留空则使用Agent默认系统提示词"},
|
||||
"temperature": {"type": "number", "label": "温度 (覆盖默认)", "default": 0.7, "min": 0, "max": 2},
|
||||
"max_tokens": {"type": "number", "label": "最大Token", "default": 4096},
|
||||
"output_variable": {"type": "string", "label": "输出变量名", "default": "", "help": "将Agent输出存入context.{变量名},供后续节点使用"}
|
||||
}
|
||||
},
|
||||
"end": {
|
||||
"type": "end",
|
||||
"label": "结束",
|
||||
@@ -216,13 +232,17 @@ async def get_node_templates(
|
||||
options["models"] = [{"value": m.id, "label": f"{getattr(m, 'display_name', None) or m.name}"} for m in models]
|
||||
|
||||
# Knowledge bases
|
||||
kbs = db.query(KnowledgeBase).all()
|
||||
kbs = db.query(KnowledgeBase).filter(KnowledgeBase.status == "active").all()
|
||||
options["knowledge_bases"] = [{"value": kb.id, "label": kb.name} for kb in kbs]
|
||||
|
||||
# Skills
|
||||
skills = db.query(EvolutionSkill).filter(EvolutionSkill.state == "active").all()
|
||||
options["skills"] = [{"value": s.id, "label": s.skill_name} for s in skills]
|
||||
|
||||
# Agents (for multi-agent orchestration - Phase G3)
|
||||
agents = db.query(MarketApp).filter(MarketApp.type == "agent", MarketApp.status == "published").all()
|
||||
options["agents"] = [{"value": a.id, "label": a.name} for a in agents]
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
@@ -496,7 +516,7 @@ async def execute_workflow(
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""执行工作流 — 按 DAG 拓扑顺序处理节点"""
|
||||
"""执行工作流 — 按 DAG 拓扑顺序处理节点,同层节点并行执行 (Phase G3)"""
|
||||
rows = _execute_raw(db, "SELECT * FROM agent_workflows WHERE id = :id", {"id": workflow_id})
|
||||
if not rows:
|
||||
raise HTTPException(status_code=404, detail="工作流不存在")
|
||||
@@ -508,28 +528,55 @@ async def execute_workflow(
|
||||
if not nodes:
|
||||
raise HTTPException(status_code=400, detail="工作流没有节点")
|
||||
|
||||
# Build adjacency map
|
||||
# Build structures
|
||||
node_map = {n["id"]: n for n in nodes}
|
||||
edge_map = {} # source_id -> [{target, handle}]
|
||||
edge_map = {}
|
||||
reverse_edge_map = {} # target -> [sources]
|
||||
for e in edges:
|
||||
src = e["source"]
|
||||
tgt = e["target"]
|
||||
if src not in edge_map:
|
||||
edge_map[src] = []
|
||||
edge_map[src].append({
|
||||
"target": e["target"],
|
||||
"sourceHandle": e.get("sourceHandle")
|
||||
})
|
||||
edge_map[src].append({"target": tgt, "sourceHandle": e.get("sourceHandle")})
|
||||
if tgt not in reverse_edge_map:
|
||||
reverse_edge_map[tgt] = []
|
||||
reverse_edge_map[tgt].append(src)
|
||||
|
||||
# Find start node
|
||||
# Find start nodes
|
||||
start_node = None
|
||||
for n in nodes:
|
||||
if n["type"] == "start":
|
||||
start_node = n
|
||||
start_node = n["id"]
|
||||
break
|
||||
if not start_node:
|
||||
start_node = nodes[0] # fallback to first node
|
||||
if not start_node and nodes:
|
||||
start_node = nodes[0]["id"]
|
||||
|
||||
# Execute
|
||||
# Topological sort to assign levels
|
||||
in_degree = {n["id"]: len(reverse_edge_map.get(n["id"], [])) for n in nodes}
|
||||
levels = {}
|
||||
queue = [start_node]
|
||||
levels[start_node] = 0
|
||||
|
||||
while queue:
|
||||
nid = queue.pop(0)
|
||||
level = levels[nid]
|
||||
for edge in edge_map.get(nid, []):
|
||||
tgt = edge["target"]
|
||||
if tgt in node_map:
|
||||
new_level = level + 1
|
||||
if tgt not in levels or new_level > levels[tgt]:
|
||||
levels[tgt] = new_level
|
||||
in_degree[tgt] -= 1
|
||||
if in_degree[tgt] <= 0:
|
||||
queue.append(tgt)
|
||||
|
||||
# Group nodes by level
|
||||
max_level = max(levels.values()) if levels else 0
|
||||
level_groups = {i: [] for i in range(max_level + 1)}
|
||||
for nid, lvl in levels.items():
|
||||
level_groups[lvl].append(nid)
|
||||
|
||||
# Context
|
||||
context = {
|
||||
"input": req.input_text,
|
||||
"variables": req.variables or {},
|
||||
@@ -538,74 +585,78 @@ async def execute_workflow(
|
||||
"log": [],
|
||||
}
|
||||
|
||||
visited = set()
|
||||
queue = [start_node["id"]]
|
||||
final_output = ""
|
||||
abort = False
|
||||
|
||||
while queue:
|
||||
node_id = queue.pop(0)
|
||||
if node_id in visited:
|
||||
continue
|
||||
visited.add(node_id)
|
||||
|
||||
node = node_map.get(node_id)
|
||||
if not node:
|
||||
continue
|
||||
|
||||
node_type = node["type"]
|
||||
node_config = node.get("data", {}).get("config", {})
|
||||
node_label = node.get("data", {}).get("label", node_type)
|
||||
|
||||
try:
|
||||
result = _execute_node(node_type, node_config, context, db)
|
||||
context["results"][node_id] = result
|
||||
context["prev_output"] = result.get("output", "")
|
||||
context["log"].append({
|
||||
"node_id": node_id,
|
||||
"node_label": node_label,
|
||||
"node_type": node_type,
|
||||
"success": True,
|
||||
"output_preview": str(result.get("output", ""))[:200],
|
||||
})
|
||||
|
||||
if node_type == "end":
|
||||
final_output = result.get("output", "")
|
||||
break
|
||||
|
||||
# Determine next nodes
|
||||
next_edges = edge_map.get(node_id, [])
|
||||
if node_type == "condition":
|
||||
# Evaluate condition
|
||||
cond_expr = node_config.get("condition_expr", "True")
|
||||
try:
|
||||
cond_result = eval(cond_expr, {"__builtins__": {}}, {
|
||||
"input": context["input"],
|
||||
"prev_output": context["prev_output"],
|
||||
"len": len,
|
||||
"str": str,
|
||||
})
|
||||
except Exception:
|
||||
cond_result = True
|
||||
|
||||
for edge in next_edges:
|
||||
handle = edge["sourceHandle"]
|
||||
if (cond_result and handle == "true") or (not cond_result and handle == "false") or handle is None:
|
||||
queue.append(edge["target"])
|
||||
else:
|
||||
for edge in next_edges:
|
||||
queue.append(edge["target"])
|
||||
|
||||
except Exception as e:
|
||||
context["log"].append({
|
||||
"node_id": node_id,
|
||||
"node_label": node_label,
|
||||
"node_type": node_type,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
})
|
||||
# Stop execution on error
|
||||
# Execute level by level
|
||||
for lvl in range(max_level + 1):
|
||||
if abort:
|
||||
break
|
||||
|
||||
level_nodes = level_groups[lvl]
|
||||
if not level_nodes:
|
||||
continue
|
||||
|
||||
# Execute nodes in this level
|
||||
tasks = []
|
||||
for nid in level_nodes:
|
||||
node = node_map.get(nid)
|
||||
if not node:
|
||||
continue
|
||||
task = _execute_node_async(node, context, db, edge_map, node_map, workflow_id)
|
||||
tasks.append(task)
|
||||
|
||||
# Parallel execution for same-level nodes
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
for res in results:
|
||||
if isinstance(res, Exception):
|
||||
context["log"].append({
|
||||
"node_id": "unknown",
|
||||
"node_label": "unknown",
|
||||
"node_type": "unknown",
|
||||
"success": False,
|
||||
"error": str(res),
|
||||
})
|
||||
abort = True
|
||||
continue
|
||||
|
||||
if res is None:
|
||||
continue
|
||||
|
||||
nid = res["node_id"]
|
||||
context["results"][nid] = res["result"]
|
||||
|
||||
if res["success"]:
|
||||
context["prev_output"] = res["result"].get("output", "")
|
||||
context["log"].append({
|
||||
"node_id": nid,
|
||||
"node_label": res["node_label"],
|
||||
"node_type": res["node_type"],
|
||||
"success": True,
|
||||
"output_preview": str(res["result"].get("output", ""))[:200],
|
||||
})
|
||||
|
||||
if res["node_type"] == "end":
|
||||
final_output = res["result"].get("output", "")
|
||||
abort = True
|
||||
break
|
||||
|
||||
# Handle condition node branching — update context for next level
|
||||
if res["node_type"] == "condition" and "next_nodes" in res:
|
||||
# The condition routing is applied by _execute_node_async
|
||||
pass
|
||||
else:
|
||||
context["log"].append({
|
||||
"node_id": nid,
|
||||
"node_label": res["node_label"],
|
||||
"node_type": res["node_type"],
|
||||
"success": False,
|
||||
"error": res.get("error", "未知错误"),
|
||||
})
|
||||
abort = True
|
||||
break
|
||||
|
||||
if not final_output:
|
||||
final_output = context["prev_output"]
|
||||
|
||||
@@ -619,6 +670,195 @@ async def execute_workflow(
|
||||
}
|
||||
|
||||
|
||||
@router.websocket("/{workflow_id}/execute-stream")
|
||||
async def execute_workflow_stream(
|
||||
websocket: WebSocket,
|
||||
workflow_id: str,
|
||||
):
|
||||
"""WebSocket 端点 — 实时流式推送工作流执行进度 (Phase G3)"""
|
||||
await websocket.accept()
|
||||
|
||||
try:
|
||||
# Receive execution params
|
||||
data = await websocket.receive_json()
|
||||
input_text = data.get("input_text", "")
|
||||
|
||||
# Create DB session
|
||||
db = SessionLocal()
|
||||
try:
|
||||
rows = _execute_raw(db, "SELECT * FROM agent_workflows WHERE id = :id", {"id": workflow_id})
|
||||
if not rows:
|
||||
await websocket.send_json({"type": "workflow_error", "data": {"output": "工作流不存在"}})
|
||||
await websocket.close()
|
||||
return
|
||||
|
||||
wf = workflow_to_dict(rows[0])
|
||||
nodes = wf["nodes"]
|
||||
edges = wf["edges"]
|
||||
|
||||
if not nodes:
|
||||
await websocket.send_json({"type": "workflow_error", "data": {"output": "工作流没有节点"}})
|
||||
await websocket.close()
|
||||
return
|
||||
|
||||
# Build structures
|
||||
node_map = {n["id"]: n for n in nodes}
|
||||
edge_map = {}
|
||||
reverse_edge_map = {}
|
||||
for e in edges:
|
||||
src = e["source"]
|
||||
tgt = e["target"]
|
||||
if src not in edge_map:
|
||||
edge_map[src] = []
|
||||
edge_map[src].append({"target": tgt, "sourceHandle": e.get("sourceHandle")})
|
||||
if tgt not in reverse_edge_map:
|
||||
reverse_edge_map[tgt] = []
|
||||
reverse_edge_map[tgt].append(src)
|
||||
|
||||
# Find start
|
||||
start_node = None
|
||||
for n in nodes:
|
||||
if n["type"] == "start":
|
||||
start_node = n["id"]
|
||||
break
|
||||
if not start_node and nodes:
|
||||
start_node = nodes[0]["id"]
|
||||
|
||||
# Topo sort
|
||||
in_degree = {n["id"]: len(reverse_edge_map.get(n["id"], [])) for n in nodes}
|
||||
levels = {start_node: 0}
|
||||
queue = [start_node]
|
||||
while queue:
|
||||
nid = queue.pop(0)
|
||||
lvl = levels[nid]
|
||||
for edge in edge_map.get(nid, []):
|
||||
tgt = edge["target"]
|
||||
if tgt in node_map:
|
||||
nl = lvl + 1
|
||||
if tgt not in levels or nl > levels[tgt]:
|
||||
levels[tgt] = nl
|
||||
in_degree[tgt] -= 1
|
||||
if in_degree[tgt] <= 0:
|
||||
queue.append(tgt)
|
||||
|
||||
max_level = max(levels.values()) if levels else 0
|
||||
level_groups = {i: [] for i in range(max_level + 1)}
|
||||
for nid, lvl in levels.items():
|
||||
level_groups[lvl].append(nid)
|
||||
|
||||
context = {
|
||||
"input": input_text,
|
||||
"variables": {},
|
||||
"prev_output": "",
|
||||
"results": {},
|
||||
"log": [],
|
||||
}
|
||||
|
||||
final_output = ""
|
||||
abort = False
|
||||
|
||||
for lvl in range(max_level + 1):
|
||||
if abort:
|
||||
break
|
||||
|
||||
level_nodes = level_groups[lvl]
|
||||
if not level_nodes:
|
||||
continue
|
||||
|
||||
tasks_list = []
|
||||
for nid in level_nodes:
|
||||
node = node_map.get(nid)
|
||||
if not node:
|
||||
continue
|
||||
|
||||
# Send node_start
|
||||
await websocket.send_json({
|
||||
"type": "node_start",
|
||||
"node_id": nid,
|
||||
"node_type": node["type"],
|
||||
"node_label": node.get("data", {}).get("label", node["type"]),
|
||||
})
|
||||
|
||||
task = _execute_node_async(node, context, db, edge_map, node_map, workflow_id)
|
||||
tasks_list.append(task)
|
||||
|
||||
results = await asyncio.gather(*tasks_list, return_exceptions=True)
|
||||
|
||||
for res in results:
|
||||
if isinstance(res, Exception):
|
||||
await websocket.send_json({
|
||||
"type": "node_error",
|
||||
"node_id": "unknown",
|
||||
"node_label": "unknown",
|
||||
"node_type": "unknown",
|
||||
"error": str(res),
|
||||
})
|
||||
abort = True
|
||||
continue
|
||||
|
||||
if res is None:
|
||||
continue
|
||||
|
||||
nid = res["node_id"]
|
||||
context["results"][nid] = res["result"]
|
||||
|
||||
if res["success"]:
|
||||
context["prev_output"] = res["result"].get("output", "")
|
||||
|
||||
await websocket.send_json({
|
||||
"type": "node_complete",
|
||||
"node_id": nid,
|
||||
"node_label": res["node_label"],
|
||||
"node_type": res["node_type"],
|
||||
"output_preview": str(res["result"].get("output", ""))[:200],
|
||||
})
|
||||
|
||||
if res["node_type"] == "end":
|
||||
final_output = res["result"].get("output", "")
|
||||
abort = True
|
||||
break
|
||||
else:
|
||||
await websocket.send_json({
|
||||
"type": "node_error",
|
||||
"node_id": nid,
|
||||
"node_label": res["node_label"],
|
||||
"node_type": res["node_type"],
|
||||
"error": res.get("error", "未知错误"),
|
||||
})
|
||||
abort = True
|
||||
break
|
||||
|
||||
if not final_output:
|
||||
final_output = context["prev_output"]
|
||||
|
||||
await websocket.send_json({
|
||||
"type": "workflow_complete",
|
||||
"data": {
|
||||
"output": final_output,
|
||||
"log": context["log"],
|
||||
}
|
||||
})
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
except Exception as e:
|
||||
try:
|
||||
await websocket.send_json({
|
||||
"type": "workflow_error",
|
||||
"data": {"output": f"执行失败: {str(e)}"}
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
await websocket.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _execute_node(node_type: str, config: dict, context: dict, db: Session) -> dict:
|
||||
"""Execute a single node and return result"""
|
||||
|
||||
@@ -643,6 +883,9 @@ def _execute_node(node_type: str, config: dict, context: dict, db: Session) -> d
|
||||
elif node_type == "code":
|
||||
return _execute_code_node(config, context)
|
||||
|
||||
elif node_type == "agent":
|
||||
return _execute_agent_node(config, context, db)
|
||||
|
||||
elif node_type == "end":
|
||||
var_name = config.get("output_variable", "output")
|
||||
return {"output": context.get(var_name, context.get("prev_output", ""))}
|
||||
@@ -650,6 +893,114 @@ def _execute_node(node_type: str, config: dict, context: dict, db: Session) -> d
|
||||
return {"output": ""}
|
||||
|
||||
|
||||
async def _execute_node_async(node: dict, context: dict, db: Session, edge_map: dict, node_map: dict, workflow_id: str) -> Optional[dict]:
|
||||
"""Execute a single node async, returning structured result for parallel orchestration"""
|
||||
nid = node["id"]
|
||||
node_type = node["type"]
|
||||
node_config = node.get("data", {}).get("config", {})
|
||||
node_label = node.get("data", {}).get("label", node_type)
|
||||
|
||||
try:
|
||||
# Publish node start event
|
||||
from app.api.admin.acp_gateway import publish_workflow_event
|
||||
publish_workflow_event(workflow_id, nid, {
|
||||
"event": "node_start",
|
||||
"node_type": node_type,
|
||||
"node_label": node_label,
|
||||
})
|
||||
|
||||
if node_type == "agent":
|
||||
# Async agent invocation — may take time
|
||||
result = await _execute_agent_node_async(node_config, context, db, workflow_id, nid)
|
||||
|
||||
# Store output in context variable if specified
|
||||
output_var = node_config.get("output_variable", "")
|
||||
if output_var and result.get("output"):
|
||||
context[output_var] = result["output"]
|
||||
|
||||
publish_workflow_event(workflow_id, nid, {
|
||||
"event": "node_complete",
|
||||
"node_type": node_type,
|
||||
"node_label": node_label,
|
||||
"output_preview": str(result.get("output", ""))[:200],
|
||||
})
|
||||
else:
|
||||
# Sync node execution (run in thread)
|
||||
result = await asyncio.to_thread(_execute_node, node_type, node_config, context, db)
|
||||
publish_workflow_event(workflow_id, nid, {
|
||||
"event": "node_complete",
|
||||
"node_type": node_type,
|
||||
"node_label": node_label,
|
||||
"output_preview": str(result.get("output", ""))[:200],
|
||||
})
|
||||
|
||||
return {
|
||||
"node_id": nid,
|
||||
"node_label": node_label,
|
||||
"node_type": node_type,
|
||||
"success": True,
|
||||
"result": result,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
from app.api.admin.acp_gateway import publish_workflow_event
|
||||
publish_workflow_event(workflow_id, nid, {
|
||||
"event": "node_error",
|
||||
"node_type": node_type,
|
||||
"node_label": node_label,
|
||||
"error": str(e),
|
||||
})
|
||||
return {
|
||||
"node_id": nid,
|
||||
"node_label": node_label,
|
||||
"node_type": node_type,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
|
||||
async def _execute_agent_node_async(config: dict, context: dict, db: Session, workflow_id: str, node_id: str) -> dict:
|
||||
"""Execute agent node via ACP Gateway — async"""
|
||||
from app.api.admin.acp_gateway import get_acp_gateway
|
||||
|
||||
agent_id = config.get("agent_id")
|
||||
if not agent_id:
|
||||
return {"output": "[Agent] 未选择 Agent", "error": "no agent_id"}
|
||||
|
||||
# Build prompt from template
|
||||
prompt = config.get("prompt", "{input}")
|
||||
prompt = prompt.replace("{input}", str(context.get("input", "")))
|
||||
prompt = prompt.replace("{prev_output}", str(context.get("prev_output", "")))
|
||||
# Substitute context variables
|
||||
for key, val in context.items():
|
||||
if isinstance(val, str):
|
||||
prompt = prompt.replace("{" + key + "}", val)
|
||||
|
||||
system_prompt = config.get("system_prompt", "")
|
||||
temperature = float(config.get("temperature", 0.7))
|
||||
max_tokens = int(config.get("max_tokens", 4096))
|
||||
|
||||
gateway = get_acp_gateway()
|
||||
|
||||
# Set notification handler for Redis EventBus
|
||||
from app.api.admin.acp_gateway import publish_workflow_event
|
||||
gateway.set_notification_handler(
|
||||
lambda data: publish_workflow_event(workflow_id, node_id, data)
|
||||
)
|
||||
|
||||
result = await gateway.invoke_agent(
|
||||
agent_id=agent_id,
|
||||
prompt=prompt,
|
||||
system_prompt=system_prompt,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
context=context,
|
||||
db_session=db,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _execute_llm_node(config: dict, context: dict, db: Session) -> dict:
|
||||
"""Execute LLM call"""
|
||||
import httpx
|
||||
@@ -759,6 +1110,59 @@ def _execute_skill_node(config: dict, context: dict, db: Session) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _execute_agent_node(config: dict, context: dict, db: Session) -> dict:
|
||||
"""Execute agent node (sync wrapper) — calls the async version via asyncio.run"""
|
||||
agent_id = config.get("agent_id")
|
||||
if not agent_id:
|
||||
return {"output": "[Agent] 未选择 Agent", "error": "no agent_id"}
|
||||
|
||||
prompt = config.get("prompt", "{input}")
|
||||
prompt = prompt.replace("{input}", str(context.get("input", "")))
|
||||
prompt = prompt.replace("{prev_output}", str(context.get("prev_output", "")))
|
||||
|
||||
system_prompt = config.get("system_prompt", "")
|
||||
temperature = float(config.get("temperature", 0.7))
|
||||
max_tokens = int(config.get("max_tokens", 4096))
|
||||
|
||||
from .acp_gateway import get_acp_gateway
|
||||
|
||||
gateway = get_acp_gateway()
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
# In async context — create new loop
|
||||
new_loop = asyncio.new_event_loop()
|
||||
result = new_loop.run_until_complete(
|
||||
gateway.invoke_agent(
|
||||
agent_id=agent_id,
|
||||
prompt=prompt,
|
||||
system_prompt=system_prompt,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
context=context,
|
||||
db_session=db,
|
||||
)
|
||||
)
|
||||
new_loop.close()
|
||||
else:
|
||||
result = loop.run_until_complete(
|
||||
gateway.invoke_agent(
|
||||
agent_id=agent_id,
|
||||
prompt=prompt,
|
||||
system_prompt=system_prompt,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
context=context,
|
||||
db_session=db,
|
||||
)
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
return {"output": f"[Agent] 调用失败: {str(e)}", "error": str(e)}
|
||||
|
||||
|
||||
def _execute_http_node(config: dict, context: dict) -> dict:
|
||||
"""Execute HTTP request"""
|
||||
import httpx
|
||||
|
||||
Reference in new issue
Block a user