feat: 完善企业场景多线路由与执行稳定性
补齐平台模板与场景 DSL、预算控制、执行看板和企业场景脚本,增强 Windows 启动/迁移与前端代理和聊天会话记忆,修复执行创建阶段 500 与异步链路排障体验。 Made-with: Cursor
This commit is contained in:
320
backend/scripts/create_complex_enterprise_agent.py
Normal file
320
backend/scripts/create_complex_enterprise_agent.py
Normal file
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
创建「企业复杂编排」示例 Agent(单 DAG、无环):
|
||||
|
||||
Start → Condition(用户 query 是否含触发标记)
|
||||
├─ True: Code(预检/标注) → LLM(多内置工具)
|
||||
└─ False: LLM(轻量直连)
|
||||
→ Merge → End
|
||||
|
||||
触发深度链路:在用户问题中任意位置包含 **[[深度]]**(不含引号),例如:
|
||||
「[[深度]]请用 http_request 拉取 https://httpbin.org/get 的 JSON 并摘要」
|
||||
未包含标记则走轻量分支(仍可调 LLM,但默认关闭工具)。
|
||||
|
||||
用法:
|
||||
cd backend && .\\venv\\Scripts\\python.exe scripts/create_complex_enterprise_agent.py
|
||||
USE_TESTCLIENT=1 # 不经 TCP,直接内存请求 FastAPI(推荐本机多实例抢端口时)
|
||||
|
||||
环境变量:
|
||||
PLATFORM_BASE_URL, PLATFORM_USERNAME, PLATFORM_PASSWORD
|
||||
COMPLEX_AGENT_NAME(默认 企业复杂编排_深度分流)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List
|
||||
|
||||
DEEP_MARKER = "[[深度]]"
|
||||
DEFAULT_NAME = os.getenv("COMPLEX_AGENT_NAME", "企业复杂编排_深度分流")
|
||||
|
||||
TOOLS_DEEP = [
|
||||
"http_request",
|
||||
"text_analyze",
|
||||
"json_process",
|
||||
"datetime",
|
||||
"math_calculate",
|
||||
"file_read",
|
||||
"system_info",
|
||||
]
|
||||
|
||||
PROMPT_DEEP = f"""你是企业级「深度分析」助手。用户消息中可能含有触发标记「{DEEP_MARKER}」,回答时请忽略该标记本身,专注实质需求。
|
||||
可调用工具完成:HTTP 请求、文本分析、JSON 处理、时间、数学、读文件、系统信息。按需调用,勿编造工具结果。
|
||||
最后用简洁中文总结结论。"""
|
||||
|
||||
PROMPT_FAST = """你是企业助手「快速通道」。用户未走深度编排;请直接、简洁地回答,避免冗长。若信息不足先追问一句。"""
|
||||
|
||||
CODE_PREFLIGHT = """
|
||||
d = input_data if isinstance(input_data, dict) else {}
|
||||
q = str(d.get("query", "") or "")
|
||||
result = dict(d)
|
||||
result["_deep_preflight"] = True
|
||||
result["_query_chars"] = len(q)
|
||||
result["_marker_detected"] = "[[深度]]" in q
|
||||
""".strip()
|
||||
|
||||
|
||||
def _sanitize_edges(edges: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
seen: set = set()
|
||||
out: List[Dict[str, Any]] = []
|
||||
for e in edges or []:
|
||||
s, t = e.get("source"), e.get("target")
|
||||
if not s or not t or s == t:
|
||||
continue
|
||||
key = (s, t, e.get("sourceHandle") or "")
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
ne = dict(e)
|
||||
if not ne.get("targetHandle"):
|
||||
ne["targetHandle"] = "left"
|
||||
if not ne.get("id"):
|
||||
sh = ne.get("sourceHandle") or "r"
|
||||
ne["id"] = f"e_{s}_{t}_{sh}"
|
||||
out.append(ne)
|
||||
return out
|
||||
|
||||
|
||||
def build_complex_workflow() -> Dict[str, Any]:
|
||||
"""Condition 表达式与引擎一致:{{query}} contains \"...\" """
|
||||
cond_expr = f'{{query}} contains "{DEEP_MARKER}"'
|
||||
nodes: List[Dict[str, Any]] = [
|
||||
{"id": "start-1", "type": "start", "position": {"x": 40, "y": 260}, "data": {"label": "开始"}},
|
||||
{
|
||||
"id": "cond-1",
|
||||
"type": "condition",
|
||||
"position": {"x": 280, "y": 260},
|
||||
"data": {"label": "深度分流", "condition": cond_expr},
|
||||
},
|
||||
{
|
||||
"id": "code-pf",
|
||||
"type": "code",
|
||||
"position": {"x": 520, "y": 120},
|
||||
"data": {
|
||||
"label": "预检/标注",
|
||||
"language": "python",
|
||||
"code": CODE_PREFLIGHT,
|
||||
"timeout": 15,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "llm-deep",
|
||||
"type": "llm",
|
||||
"position": {"x": 760, "y": 120},
|
||||
"data": {
|
||||
"label": "深度 LLM+工具",
|
||||
"prompt": PROMPT_DEEP,
|
||||
"temperature": 0.25,
|
||||
"enable_tools": True,
|
||||
"tools": TOOLS_DEEP,
|
||||
"selected_tools": TOOLS_DEEP,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "llm-fast",
|
||||
"type": "llm",
|
||||
"position": {"x": 520, "y": 400},
|
||||
"data": {
|
||||
"label": "快速 LLM",
|
||||
"prompt": PROMPT_FAST,
|
||||
"temperature": 0.35,
|
||||
"enable_tools": False,
|
||||
"tools": [],
|
||||
"selected_tools": [],
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "merge-1",
|
||||
"type": "merge",
|
||||
"position": {"x": 1000, "y": 260},
|
||||
"data": {"label": "合并输出", "mode": "merge_all", "strategy": "object"},
|
||||
},
|
||||
{"id": "end-1", "type": "end", "position": {"x": 1240, "y": 260}, "data": {"label": "结束"}},
|
||||
]
|
||||
edges = _sanitize_edges(
|
||||
[
|
||||
{"source": "start-1", "target": "cond-1", "sourceHandle": "right", "targetHandle": "left"},
|
||||
{"source": "cond-1", "target": "code-pf", "sourceHandle": "true", "targetHandle": "left"},
|
||||
{"source": "cond-1", "target": "llm-fast", "sourceHandle": "false", "targetHandle": "left"},
|
||||
{"source": "code-pf", "target": "llm-deep", "sourceHandle": "right", "targetHandle": "left"},
|
||||
{"source": "llm-deep", "target": "merge-1", "sourceHandle": "right", "targetHandle": "left"},
|
||||
{"source": "llm-fast", "target": "merge-1", "sourceHandle": "right", "targetHandle": "left"},
|
||||
{"source": "merge-1", "target": "end-1", "sourceHandle": "right", "targetHandle": "left"},
|
||||
]
|
||||
)
|
||||
return {"nodes": nodes, "edges": edges}
|
||||
|
||||
|
||||
def _validate_local(wf: Dict[str, Any]) -> None:
|
||||
from app.services.workflow_validator import validate_workflow
|
||||
|
||||
r = validate_workflow(wf.get("nodes") or [], wf.get("edges") or [])
|
||||
if not r.get("valid"):
|
||||
errs = r.get("errors") or []
|
||||
raise ValueError("工作流校验失败: " + "; ".join(errs))
|
||||
|
||||
|
||||
def _via_requests() -> int:
|
||||
import requests
|
||||
|
||||
base = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||||
user = os.getenv("PLATFORM_USERNAME", "admin")
|
||||
pwd = os.getenv("PLATFORM_PASSWORD", "123456")
|
||||
wf = build_complex_workflow()
|
||||
_validate_local(wf)
|
||||
|
||||
lr = requests.post(
|
||||
f"{base}/api/v1/auth/login",
|
||||
data={"username": user, "password": pwd},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
timeout=20,
|
||||
)
|
||||
if lr.status_code != 200:
|
||||
print("登录失败:", lr.status_code, lr.text[:500], file=sys.stderr)
|
||||
return 1
|
||||
token = lr.json().get("access_token")
|
||||
if not token:
|
||||
print("无 access_token", file=sys.stderr)
|
||||
return 1
|
||||
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
def find_id() -> str | None:
|
||||
rr = requests.get(f"{base}/api/v1/agents", params={"search": DEFAULT_NAME, "limit": 50}, headers=h, timeout=30)
|
||||
if rr.status_code != 200:
|
||||
return None
|
||||
for a in rr.json() or []:
|
||||
if a.get("name") == DEFAULT_NAME:
|
||||
return a.get("id")
|
||||
return None
|
||||
|
||||
desc = (
|
||||
f"复杂编排示例:条件分流(query 含「{DEEP_MARKER}」→ 代码预检 + 多工具 LLM),否则快速 LLM;Merge 汇总后结束。"
|
||||
" 适合演示画布、引擎分支与预算。"
|
||||
)
|
||||
budget = {"max_steps": 120, "max_llm_invocations": 6, "max_tool_calls": 48}
|
||||
existing = find_id()
|
||||
if existing:
|
||||
ur = requests.put(
|
||||
f"{base}/api/v1/agents/{existing}",
|
||||
headers=h,
|
||||
json={"description": desc, "workflow_config": wf, "budget_config": budget},
|
||||
timeout=120,
|
||||
)
|
||||
if ur.status_code != 200:
|
||||
print("更新失败:", ur.status_code, ur.text[:800], file=sys.stderr)
|
||||
return 1
|
||||
aid = existing
|
||||
print("已更新:", DEFAULT_NAME, aid)
|
||||
else:
|
||||
cr = requests.post(
|
||||
f"{base}/api/v1/agents",
|
||||
headers=h,
|
||||
json={
|
||||
"name": DEFAULT_NAME,
|
||||
"description": desc,
|
||||
"workflow_config": wf,
|
||||
"budget_config": budget,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
if cr.status_code != 201:
|
||||
print("创建失败:", cr.status_code, cr.text[:800], file=sys.stderr)
|
||||
return 1
|
||||
aid = cr.json()["id"]
|
||||
print("已创建:", DEFAULT_NAME, aid)
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"id": aid,
|
||||
"name": DEFAULT_NAME,
|
||||
"deep_marker": DEEP_MARKER,
|
||||
"hint": f"执行时在 query 中加入 {DEEP_MARKER} 可走深度分支",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _via_testclient() -> int:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
|
||||
wf = build_complex_workflow()
|
||||
_validate_local(wf)
|
||||
|
||||
c = TestClient(app)
|
||||
lr = c.post(
|
||||
"/api/v1/auth/login",
|
||||
data={
|
||||
"username": os.getenv("PLATFORM_USERNAME", "admin"),
|
||||
"password": os.getenv("PLATFORM_PASSWORD", "123456"),
|
||||
},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
if lr.status_code != 200:
|
||||
print("login:", lr.status_code, lr.text[:400], file=sys.stderr)
|
||||
return 1
|
||||
token = lr.json().get("access_token")
|
||||
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
gr = c.get("/api/v1/agents", params={"search": DEFAULT_NAME, "limit": 50}, headers=h)
|
||||
existing = None
|
||||
if gr.status_code == 200:
|
||||
for a in gr.json() or []:
|
||||
if a.get("name") == DEFAULT_NAME:
|
||||
existing = a.get("id")
|
||||
break
|
||||
|
||||
desc = (
|
||||
f"复杂编排示例:条件分流(query 含「{DEEP_MARKER}」→ 代码预检 + 多工具 LLM),否则快速 LLM;Merge 汇总后结束。"
|
||||
)
|
||||
budget = {"max_steps": 120, "max_llm_invocations": 6, "max_tool_calls": 48}
|
||||
if existing:
|
||||
ur = c.put(
|
||||
f"/api/v1/agents/{existing}",
|
||||
headers=h,
|
||||
json={"description": desc, "workflow_config": wf, "budget_config": budget},
|
||||
)
|
||||
if ur.status_code != 200:
|
||||
print("put:", ur.status_code, ur.text[:800], file=sys.stderr)
|
||||
return 1
|
||||
aid = existing
|
||||
print("已更新(TestClient):", DEFAULT_NAME, aid)
|
||||
else:
|
||||
cr = c.post(
|
||||
"/api/v1/agents",
|
||||
headers=h,
|
||||
json={
|
||||
"name": DEFAULT_NAME,
|
||||
"description": desc,
|
||||
"workflow_config": wf,
|
||||
"budget_config": budget,
|
||||
},
|
||||
)
|
||||
if cr.status_code != 201:
|
||||
print("post:", cr.status_code, cr.text[:800], file=sys.stderr)
|
||||
return 1
|
||||
aid = cr.json()["id"]
|
||||
print("已创建(TestClient):", DEFAULT_NAME, aid)
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{"id": aid, "name": DEFAULT_NAME, "deep_marker": DEEP_MARKER},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if os.getenv("USE_TESTCLIENT", "").strip() in ("1", "true", "yes"):
|
||||
return _via_testclient()
|
||||
return _via_requests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user