补齐平台模板与场景 DSL、预算控制、执行看板和企业场景脚本,增强 Windows 启动/迁移与前端代理和聊天会话记忆,修复执行创建阶段 500 与异步链路排障体验。 Made-with: Cursor
321 lines
11 KiB
Python
321 lines
11 KiB
Python
#!/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())
|