- 扩展 test_agent_execution(--homework、UTF-8 控制台) - 后端:uploads 预览、file_read、工作流与对话落盘等 - 前端:AgentChatPreview 与设计器相关调整 - 忽略 redis二进制、agent_workspaces、uploads、tessdata 等本机产物 Made-with: Cursor
570 lines
21 KiB
Python
570 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
批量创建多个「企业场景」示例 Agent(画布均为无环 DAG,可通过平台校验)。
|
||
|
||
场景一览:
|
||
1. 企业场景_电商售后 — LLM + http / 文本分析 / 时间
|
||
2. 企业场景_研发联调 — LLM + http / JSON / 读文件 / 数学
|
||
3. 企业场景_数据分析 — LLM + 只读库查询 / JSON / 文本 / 数学(需数据源与权限)
|
||
4. 企业场景_移动运维 — LLM + adb / 读文件 / 文本 / 系统信息
|
||
5. 企业场景_合规纪要 — 纯 LLM,偏合规与留痕表述
|
||
6. 企业场景_销售助理 — LLM + http / text_analyze / datetime
|
||
7. 企业场景_多线路由 — Code 解析 query 中的线路标记 → Switch → 三条专精 LLM → Merge
|
||
8. 企业场景_审批流样例 — Approval → 通过则 LLM 生成说明 / 拒绝则直接结束(首次执行会待审批)
|
||
|
||
多线路由标记(写在用户 query 里,任选其一,无标记走默认):
|
||
「【客服】」「【研发】」「【运维】」或同义 「[[客服]]」「[[研发]]」「[[运维]]」
|
||
|
||
用法:
|
||
cd backend && .\\venv\\Scripts\\python.exe scripts/create_enterprise_scenario_agents.py
|
||
USE_TESTCLIENT=1 # 推荐:不经 TCP
|
||
|
||
环境变量:
|
||
PLATFORM_BASE_URL, PLATFORM_USERNAME, PLATFORM_PASSWORD
|
||
ENTERPRISE_SCENARIO_NAMES 可选,逗号分隔,只创建/更新列出的 Agent 名称(完整名)
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 图构建辅助
|
||
# ---------------------------------------------------------------------------
|
||
|
||
DEFAULT_LLM_PROVIDER = os.getenv("ENTERPRISE_LLM_PROVIDER", "deepseek")
|
||
DEFAULT_LLM_MODEL = os.getenv("ENTERPRISE_LLM_MODEL", "deepseek-chat")
|
||
DEFAULT_LLM_TIMEOUT = max(30, int(os.getenv("ENTERPRISE_LLM_TIMEOUT", "180")))
|
||
|
||
|
||
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 _llm(
|
||
nid: str,
|
||
pos: Tuple[int, int],
|
||
label: str,
|
||
prompt: str,
|
||
*,
|
||
temperature: float = 0.3,
|
||
tools: Optional[List[str]] = None,
|
||
enable_tools: bool = False,
|
||
) -> Dict[str, Any]:
|
||
tlist = list(tools or [])
|
||
en = bool(enable_tools and tlist)
|
||
return {
|
||
"id": nid,
|
||
"type": "llm",
|
||
"position": {"x": pos[0], "y": pos[1]},
|
||
"data": {
|
||
"label": label,
|
||
"prompt": prompt,
|
||
"provider": DEFAULT_LLM_PROVIDER,
|
||
"model": DEFAULT_LLM_MODEL,
|
||
"temperature": float(temperature),
|
||
"request_timeout": DEFAULT_LLM_TIMEOUT,
|
||
"enable_tools": en,
|
||
"tools": tlist if en else [],
|
||
"selected_tools": tlist if en else [],
|
||
},
|
||
}
|
||
|
||
|
||
def _linear_start_llm_end(
|
||
llm_id: str,
|
||
llm_pos: Tuple[int, int],
|
||
llm_label: str,
|
||
prompt: str,
|
||
*,
|
||
temperature: float = 0.3,
|
||
tools: Optional[List[str]] = None,
|
||
enable_tools: bool = False,
|
||
) -> Dict[str, Any]:
|
||
nodes = [
|
||
{"id": "start-1", "type": "start", "position": {"x": 80, "y": 200}, "data": {"label": "开始"}},
|
||
_llm(llm_id, llm_pos, llm_label, prompt, temperature=temperature, tools=tools, enable_tools=enable_tools),
|
||
{"id": "end-1", "type": "end", "position": {"x": llm_pos[0] + 240, "y": 200}, "data": {"label": "结束"}},
|
||
]
|
||
edges = _sanitize_edges(
|
||
[
|
||
{"source": "start-1", "target": llm_id, "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": llm_id, "target": "end-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
]
|
||
)
|
||
return {"nodes": nodes, "edges": edges}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 各场景 workflow
|
||
# ---------------------------------------------------------------------------
|
||
|
||
WF_ECOM = _linear_start_llm_end(
|
||
"llm-1",
|
||
(360, 200),
|
||
"售后处理",
|
||
"""你是电商售后场景助手。处理订单状态咨询、物流、退换货政策说明;不要编造订单号与物流单号。
|
||
可调用工具辅助:HTTP(查公开接口时)、文本分析、时间。回答简洁、分条,必要时先澄清一项关键信息。""",
|
||
temperature=0.35,
|
||
tools=["http_request", "text_analyze", "datetime"],
|
||
enable_tools=True,
|
||
)
|
||
|
||
WF_DEV = _linear_start_llm_end(
|
||
"llm-1",
|
||
(360, 200),
|
||
"联调助手",
|
||
"""你是研发联调助手:帮助解析 API 返回、构造示例请求体、阅读本地说明文件(路径由用户提供)、简单计算。
|
||
务必以工具返回为准,勿编造响应体。优先给出可复制的 curl 或 JSON 片段。""",
|
||
temperature=0.25,
|
||
tools=["http_request", "json_process", "file_read", "math_calculate"],
|
||
enable_tools=True,
|
||
)
|
||
|
||
WF_DATA = _linear_start_llm_end(
|
||
"llm-1",
|
||
(360, 200),
|
||
"数据问答",
|
||
"""你是企业数据问答助手:在用户提供明确需求时,可用只读 SQL(SELECT)分析业务表;可解析 JSON、做简单统计与文本摘要。
|
||
不得执行删改;无数据源或查询失败时如实说明。回答用中文,结论在前。""",
|
||
temperature=0.28,
|
||
tools=["database_query", "json_process", "text_analyze", "math_calculate"],
|
||
enable_tools=True,
|
||
)
|
||
|
||
WF_MOBILE = _linear_start_llm_end(
|
||
"llm-1",
|
||
(360, 200),
|
||
"端侧运维",
|
||
"""你是移动端/设备侧运维助手:在用户需要时协助解读 adb 日志、读取用户指定的日志文件、结合系统信息排查。
|
||
若环境无 adb 或权限不足,根据工具错误如实说明。不要编造日志内容。""",
|
||
temperature=0.3,
|
||
tools=["adb_log", "file_read", "text_analyze", "system_info"],
|
||
enable_tools=True,
|
||
)
|
||
|
||
WF_COMPLIANCE = _linear_start_llm_end(
|
||
"llm-1",
|
||
(360, 200),
|
||
"合规纪要",
|
||
"""你是合规与对内纪要助手。输出应:条理清晰、避免绝对化承诺、敏感处提示「需法务/合规复核」;不编造内部制度条文。
|
||
不使用外部工具,基于用户给定材料与问题作答。""",
|
||
temperature=0.2,
|
||
tools=[],
|
||
enable_tools=False,
|
||
)
|
||
|
||
WF_SALES = _linear_start_llm_end(
|
||
"llm-1",
|
||
(360, 200),
|
||
"销售助理",
|
||
"""你是 B 端销售助理:协助整理客户痛点、撰写简短跟进话术、摘要公开产品资料(若用户提供 URL 可用 HTTP 拉取)。
|
||
保持专业、不夸大效果;数字与条款以工具或用户提供的原文为准。""",
|
||
temperature=0.35,
|
||
tools=["http_request", "text_analyze", "datetime"],
|
||
enable_tools=True,
|
||
)
|
||
|
||
CODE_LANE = """
|
||
d = input_data if isinstance(input_data, dict) else {}
|
||
q = str(d.get("query", "") or "")
|
||
lane = "default"
|
||
if "【研发】" in q or "[[研发]]" in q:
|
||
lane = "dev"
|
||
elif "【运维】" in q or "[[运维]]" in q:
|
||
lane = "ops"
|
||
elif "【客服】" in q or "[[客服]]" in q:
|
||
lane = "cs"
|
||
result = dict(d)
|
||
result["lane"] = lane
|
||
""".strip()
|
||
|
||
|
||
def build_switch_multilane_workflow() -> Dict[str, Any]:
|
||
nodes: List[Dict[str, Any]] = [
|
||
{"id": "start-1", "type": "start", "position": {"x": 60, "y": 300}, "data": {"label": "开始"}},
|
||
{
|
||
"id": "code-lane",
|
||
"type": "code",
|
||
"position": {"x": 260, "y": 300},
|
||
"data": {"label": "解析线路", "language": "python", "code": CODE_LANE, "timeout": 15},
|
||
},
|
||
{
|
||
"id": "sw-1",
|
||
"type": "switch",
|
||
"position": {"x": 500, "y": 300},
|
||
"data": {
|
||
"label": "业务线路",
|
||
"field": "lane",
|
||
"cases": {"cs": "br_cs", "dev": "br_dev", "ops": "br_ops"},
|
||
"default": "br_default",
|
||
},
|
||
},
|
||
_llm(
|
||
"llm-cs",
|
||
(760, 120),
|
||
"客服线",
|
||
"你是企业客服专家。用户已从多线路由进入本分支;忽略线路标记,专注解决问题。简洁、礼貌。",
|
||
temperature=0.35,
|
||
),
|
||
_llm(
|
||
"llm-def",
|
||
(760, 260),
|
||
"默认线",
|
||
"你是通用企业助手。用户未指定【客服】/【研发】/【运维】线路;先简要澄清所属场景再回答。",
|
||
temperature=0.35,
|
||
),
|
||
_llm(
|
||
"llm-dev",
|
||
(760, 400),
|
||
"研发线",
|
||
"你是研发支持专家。用户已进入研发分支;给出可执行步骤与示例,避免空泛。",
|
||
temperature=0.28,
|
||
),
|
||
_llm(
|
||
"llm-ops",
|
||
(760, 540),
|
||
"运维线",
|
||
"你是运维专家。用户已进入运维分支;优先给排查顺序与注意事项,不编造监控数据。",
|
||
temperature=0.3,
|
||
),
|
||
{
|
||
"id": "merge-1",
|
||
"type": "merge",
|
||
"position": {"x": 1060, "y": 330},
|
||
"data": {"label": "合并", "mode": "merge_all", "strategy": "object"},
|
||
},
|
||
{"id": "end-1", "type": "end", "position": {"x": 1300, "y": 330}, "data": {"label": "结束"}},
|
||
]
|
||
edges = _sanitize_edges(
|
||
[
|
||
{"source": "start-1", "target": "code-lane", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "code-lane", "target": "sw-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "sw-1", "target": "llm-cs", "sourceHandle": "br_cs", "targetHandle": "left"},
|
||
{"source": "sw-1", "target": "llm-dev", "sourceHandle": "br_dev", "targetHandle": "left"},
|
||
{"source": "sw-1", "target": "llm-ops", "sourceHandle": "br_ops", "targetHandle": "left"},
|
||
{"source": "sw-1", "target": "llm-def", "sourceHandle": "br_default", "targetHandle": "left"},
|
||
{"source": "llm-cs", "target": "merge-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "llm-dev", "target": "merge-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "llm-ops", "target": "merge-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "llm-def", "target": "merge-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "merge-1", "target": "end-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
]
|
||
)
|
||
return {"nodes": nodes, "edges": edges}
|
||
|
||
|
||
def build_hitl_approval_workflow() -> Dict[str, Any]:
|
||
nodes: List[Dict[str, Any]] = [
|
||
{"id": "start-1", "type": "start", "position": {"x": 80, "y": 220}, "data": {"label": "开始"}},
|
||
{
|
||
"id": "appr-1",
|
||
"type": "approval",
|
||
"position": {"x": 320, "y": 220},
|
||
"data": {
|
||
"label": "人工审批",
|
||
"message": "请确认是否允许本助手根据用户 query 生成对外说明草稿(示例 HITL)。",
|
||
"approved_branch": "approved",
|
||
"rejected_branch": "rejected",
|
||
},
|
||
},
|
||
_llm(
|
||
"llm-ok",
|
||
(580, 120),
|
||
"通过后生成",
|
||
"审批已通过。请根据用户问题写一段简短、可发送给业务方的说明草稿(中文)。",
|
||
temperature=0.35,
|
||
),
|
||
{
|
||
"id": "end-ok",
|
||
"type": "end",
|
||
"position": {"x": 860, "y": 120},
|
||
"data": {"label": "结束(通过)"},
|
||
},
|
||
{
|
||
"id": "end-rej",
|
||
"type": "end",
|
||
"position": {"x": 580, "y": 320},
|
||
"data": {"label": "结束(拒绝)"},
|
||
},
|
||
]
|
||
edges = _sanitize_edges(
|
||
[
|
||
{"source": "start-1", "target": "appr-1", "sourceHandle": "right", "targetHandle": "left"},
|
||
{"source": "appr-1", "target": "llm-ok", "sourceHandle": "approved", "targetHandle": "left"},
|
||
{"source": "appr-1", "target": "end-rej", "sourceHandle": "rejected", "targetHandle": "left"},
|
||
{"source": "llm-ok", "target": "end-ok", "sourceHandle": "right", "targetHandle": "left"},
|
||
]
|
||
)
|
||
return {"nodes": nodes, "edges": edges}
|
||
|
||
|
||
ScenarioSpec = Tuple[str, str, Dict[str, Any], Dict[str, Any]]
|
||
|
||
DEFAULT_BUDGET = {"max_steps": 100, "max_llm_invocations": 8, "max_tool_calls": 40}
|
||
SWITCH_BUDGET = {"max_steps": 160, "max_llm_invocations": 12, "max_tool_calls": 48}
|
||
HITL_BUDGET = {"max_steps": 80, "max_llm_invocations": 6, "max_tool_calls": 20}
|
||
|
||
|
||
def all_scenario_specs() -> List[ScenarioSpec]:
|
||
return [
|
||
(
|
||
"企业场景_电商售后",
|
||
"电商售后:订单/物流/退换货咨询,带 HTTP·文本·时间工具。",
|
||
WF_ECOM,
|
||
DEFAULT_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_研发联调",
|
||
"研发联调:API/JSON/读文件/计算,适合排障与样例构造。",
|
||
WF_DEV,
|
||
DEFAULT_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_数据分析",
|
||
"数据分析:只读 SQL + JSON/文本/数学(依赖平台数据源)。",
|
||
WF_DATA,
|
||
DEFAULT_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_移动运维",
|
||
"移动运维:adb、读文件、文本与系统信息(依赖运行环境)。",
|
||
WF_MOBILE,
|
||
DEFAULT_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_合规纪要",
|
||
"合规纪要:无工具,偏对内记录与风险提示。",
|
||
WF_COMPLIANCE,
|
||
HITL_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_销售助理",
|
||
"销售助理:话术与资料摘要,带 HTTP·文本·时间。",
|
||
WF_SALES,
|
||
DEFAULT_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_多线路由",
|
||
"多线路由:query 含【客服】/【研发】/【运维】→ Switch → 专精 LLM → Merge。",
|
||
build_switch_multilane_workflow(),
|
||
SWITCH_BUDGET,
|
||
),
|
||
(
|
||
"企业场景_审批流样例",
|
||
"审批样例:首跑 awaiting_approval;通过后 LLM 写说明,拒绝直接结束。",
|
||
build_hitl_approval_workflow(),
|
||
HITL_BUDGET,
|
||
),
|
||
]
|
||
|
||
|
||
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 _filter_specs(
|
||
specs: List[ScenarioSpec], only_names: Optional[List[str]]
|
||
) -> List[ScenarioSpec]:
|
||
if not only_names:
|
||
return specs
|
||
want = {n.strip() for n in only_names if n.strip()}
|
||
return [s for s in specs if s[0] in want]
|
||
|
||
|
||
def _upsert_agent_requests(
|
||
base: str,
|
||
headers: Dict[str, str],
|
||
name: str,
|
||
description: str,
|
||
wf: Dict[str, Any],
|
||
budget: Dict[str, Any],
|
||
) -> Tuple[str, bool]:
|
||
import requests
|
||
|
||
def find_id() -> Optional[str]:
|
||
rr = requests.get(
|
||
f"{base}/api/v1/agents", params={"search": name, "limit": 80}, headers=headers, timeout=45
|
||
)
|
||
if rr.status_code != 200:
|
||
return None
|
||
for a in rr.json() or []:
|
||
if a.get("name") == name:
|
||
return a.get("id")
|
||
return None
|
||
|
||
existing = find_id()
|
||
if existing:
|
||
ur = requests.put(
|
||
f"{base}/api/v1/agents/{existing}",
|
||
headers=headers,
|
||
json={"description": description, "workflow_config": wf, "budget_config": budget},
|
||
timeout=120,
|
||
)
|
||
if ur.status_code != 200:
|
||
raise RuntimeError(f"更新 {name} 失败 {ur.status_code}: {ur.text[:600]}")
|
||
return existing, False
|
||
cr = requests.post(
|
||
f"{base}/api/v1/agents",
|
||
headers=headers,
|
||
json={"name": name, "description": description, "workflow_config": wf, "budget_config": budget},
|
||
timeout=120,
|
||
)
|
||
if cr.status_code != 201:
|
||
raise RuntimeError(f"创建 {name} 失败 {cr.status_code}: {cr.text[:600]}")
|
||
return cr.json()["id"], True
|
||
|
||
|
||
def _upsert_agent_testclient(
|
||
c: Any,
|
||
headers: Dict[str, str],
|
||
name: str,
|
||
description: str,
|
||
wf: Dict[str, Any],
|
||
budget: Dict[str, Any],
|
||
) -> Tuple[str, bool]:
|
||
existing = None
|
||
gr = c.get("/api/v1/agents", params={"search": name, "limit": 80}, headers=headers)
|
||
if gr.status_code == 200:
|
||
for a in gr.json() or []:
|
||
if a.get("name") == name:
|
||
existing = a.get("id")
|
||
break
|
||
if existing:
|
||
ur = c.put(
|
||
f"/api/v1/agents/{existing}",
|
||
headers=headers,
|
||
json={"description": description, "workflow_config": wf, "budget_config": budget},
|
||
)
|
||
if ur.status_code != 200:
|
||
raise RuntimeError(f"更新 {name} 失败 {ur.status_code}: {ur.text[:600]}")
|
||
return existing, False
|
||
cr = c.post(
|
||
"/api/v1/agents",
|
||
headers=headers,
|
||
json={"name": name, "description": description, "workflow_config": wf, "budget_config": budget},
|
||
)
|
||
if cr.status_code != 201:
|
||
raise RuntimeError(f"创建 {name} 失败 {cr.status_code}: {cr.text[:600]}")
|
||
return cr.json()["id"], True
|
||
|
||
|
||
def main(argv: Optional[List[str]] = None) -> int:
|
||
p = argparse.ArgumentParser(description="批量创建企业场景 Agent")
|
||
p.add_argument(
|
||
"--names",
|
||
nargs="*",
|
||
help="仅处理这些完整 Agent 名称(可替代环境变量 ENTERPRISE_SCENARIO_NAMES)",
|
||
)
|
||
args = p.parse_args(argv)
|
||
|
||
env_filter = os.getenv("ENTERPRISE_SCENARIO_NAMES", "").strip()
|
||
only_list: Optional[List[str]] = None
|
||
if args.names:
|
||
only_list = list(args.names)
|
||
elif env_filter:
|
||
only_list = [x.strip() for x in env_filter.split(",") if x.strip()]
|
||
|
||
specs = _filter_specs(all_scenario_specs(), only_list)
|
||
|
||
results: List[Dict[str, Any]] = []
|
||
errors: List[str] = []
|
||
|
||
use_tc = os.getenv("USE_TESTCLIENT", "").strip().lower() in ("1", "true", "yes")
|
||
|
||
if use_tc:
|
||
from fastapi.testclient import TestClient
|
||
|
||
from app.main import app
|
||
|
||
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")
|
||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||
|
||
for name, desc, wf, budget in specs:
|
||
try:
|
||
_validate_local(wf)
|
||
aid, created = _upsert_agent_testclient(c, headers, name, desc, wf, budget)
|
||
results.append({"name": name, "id": aid, "created": created})
|
||
tag = "创建" if created else "更新"
|
||
print(f"[{tag}] {name} {aid}")
|
||
except Exception as e:
|
||
errors.append(f"{name}: {e}")
|
||
print(f"[失败] {name}: {e}", file=sys.stderr)
|
||
else:
|
||
import requests
|
||
|
||
base = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||
lr = requests.post(
|
||
f"{base}/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"},
|
||
timeout=20,
|
||
)
|
||
if lr.status_code != 200:
|
||
print("登录失败:", lr.status_code, lr.text[:400], file=sys.stderr)
|
||
return 1
|
||
token = lr.json().get("access_token")
|
||
if not token:
|
||
print("无 access_token", file=sys.stderr)
|
||
return 1
|
||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||
|
||
for name, desc, wf, budget in specs:
|
||
try:
|
||
_validate_local(wf)
|
||
aid, created = _upsert_agent_requests(base, headers, name, desc, wf, budget)
|
||
results.append({"name": name, "id": aid, "created": created})
|
||
tag = "创建" if created else "更新"
|
||
print(f"[{tag}] {name} {aid}")
|
||
except Exception as e:
|
||
errors.append(f"{name}: {e}")
|
||
print(f"[失败] {name}: {e}", file=sys.stderr)
|
||
|
||
summary = {"ok": len(results), "fail": len(errors), "agents": results, "errors": errors}
|
||
print(json.dumps(summary, ensure_ascii=False))
|
||
return 0 if not errors else 2
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|