627 lines
27 KiB
Python
627 lines
27 KiB
Python
"""创建「任务指挥」6 块数据交付链的 9 个席位智能体 + 业务链 + 一次性任务 002。
|
||
|
||
一次性联调脚本(可重复运行):
|
||
1. POST/PUT /api/agents ×9 —— 用户级智能体,绑 task-data-submit 技能;
|
||
2. POST/PUT /api/roundtable-chains —— 业务链 mc-command-chain(三阶段);
|
||
3. PUT 链 is_public=True —— 发布链(自动发布席位智能体);
|
||
4. POST /api/task-analysis/tasks —— 若不存在则创建 source_task_id=002(只执行一次);
|
||
5. PUT 块标题 block_1~block_6;绑定链;默认立刻触发一轮。
|
||
|
||
用法(backend 目录):PYTHONPATH=. uv run python scripts/setup_mission_command_chain.py
|
||
PYTHONPATH=. uv run python scripts/setup_mission_command_chain.py --skip-trigger
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import sys
|
||
import urllib.error
|
||
import urllib.parse
|
||
import urllib.request
|
||
|
||
BASE = "http://localhost:8001"
|
||
USERNAME = "admin"
|
||
PASSWORD = "123ewq"
|
||
SOURCE_TASK_ID = "002"
|
||
CHAIN_ID = "mc-command-chain"
|
||
TASK_NAME = "任务指挥商业情报采集"
|
||
|
||
SCENE = (
|
||
"场景设定:我方是消费电子品牌「北辰智联」,正在推进 2026 Q3 区域市场扩张。"
|
||
"G01=核心大客户续约,G02=华东重点区域铺货,G03=经销商网络巩固。"
|
||
"竞对与挑战方用商业主体(品牌/渠道/舆情工作室),禁止写真实战事、部队、武器。"
|
||
"字段名、id、tone/level/icon 枚举必须与示例同构;x/y 为 8~94 的数字。"
|
||
)
|
||
|
||
|
||
def submit_rules(block_key: str) -> str:
|
||
return f"""## 数据提交(核心交付方式)
|
||
|
||
你唯一的正式交付方式是调用技能 `task-data-submit` 入库(bash):
|
||
|
||
```bash
|
||
python /mnt/skills/custom/task-data-submit/scripts/task_data_submit.py \\
|
||
--task-id <任务号> --block {block_key} --data '@/mnt/user-data/outputs/my-part.json'
|
||
```
|
||
|
||
- `--task-id` 取任务上下文里「数据交付 task_id(调用 task-data-submit 技能入库时的 --task-id 参数):」一行给出的值;
|
||
- 固定提交到 `{block_key}`;
|
||
- **只提交你负责的顶层 key**,绝不把其他席位负责的 key 抄回来(同 key 后写覆盖会冲掉别人的数据);
|
||
- payload 必须是 JSON 对象;完成一部分就先提交一部分;
|
||
- 提交成功(stdout `"success": true`)即交付完成;失败时把 errors 原样写进回复并重试一次。
|
||
"""
|
||
|
||
|
||
NO_ASK_RULES = """## 工作纪律
|
||
|
||
- 无人值守定时任务:**禁止向用户提问或等待确认**;信息不足时基于合理假设生成当期商业情报,并在文本中注明「(基于假设的演示数据)」。
|
||
- 检索工具不可用或无结果时不要卡住:直接用合理的模拟商业数据完成交付。
|
||
- 内容必须是商业化情报(竞对、渠道、客户、舆情、政策、经营窗口),不要写成军事作战。
|
||
- 字段名一个都不能改;数组条数与示例接近(可 ±1)。
|
||
"""
|
||
|
||
AGENTS = [
|
||
{
|
||
"id": "mc-overview-facts",
|
||
"name": "任务总览事实席位",
|
||
"description": "任务指挥链·阶段1:产出总览页事实层,提交 block_1 的 coreIssues/situationBriefs/map*。",
|
||
"soul": f"""# 任务总览事实席位(mc-overview-facts)
|
||
|
||
你是任务指挥「任务研判总览」的事实采集席位,负责 block_1 的事实层顶层 key。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(严格遵守)
|
||
|
||
`coreIssues` —— 4 条:
|
||
```json
|
||
[{{"id":"P1","text":"商业问题一句","level":"high","label":"高"}}]
|
||
```
|
||
level ∈ high/medium,label ∈ 高/中。围绕客户续约、渠道铺货、竞对促销、经营窗口。
|
||
|
||
`situationBriefs` —— 3 条,id 固定 enemy/friendly/environment:
|
||
```json
|
||
[{{"id":"enemy","icon":"enemy","title":"敌情摘要","text":"竞对动态一句","tone":"danger"}}]
|
||
```
|
||
title 保持「敌情摘要/我情摘要/环境摘要」;text 写成商业情报。tone ∈ danger/friendly/opportunity。
|
||
|
||
`mapLayers` —— 保持 id:all/target/friendly/enemy/event/risk/opportunity/resource。
|
||
`mapNodes` —— 3 条 G01/G02/G03,name 用商业目标名,tone ∈ friendly/critical,layers 含 target。
|
||
`mapUnits` —— 6 条左右,label 用商业力量名(渠道军团、竞对促销队等),tone ∈ friendly/enemy/muted。
|
||
`mapAnnotations` —— 4 条,title/detail 为商业风险与窗口。
|
||
|
||
""" + submit_rules("block_1") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-overview-synthesis",
|
||
"name": "任务总览研判席位",
|
||
"description": "任务指挥链·阶段2:基于前序交付做总览综合研判,提交 block_1 的 assessmentSummary/assessmentProgress。",
|
||
"soul": f"""# 任务总览研判席位(mc-overview-synthesis)
|
||
|
||
你运行在阶段 2,必须基于前序各席位交付做综合研判,不要写出与前序矛盾的结论。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(严格遵守)
|
||
|
||
`assessmentSummary`:
|
||
```json
|
||
{{
|
||
"paragraphs": ["商业研判段1","段2","段3"],
|
||
"tags": [
|
||
{{"icon":"opportunity","title":"机会","code":"O1","text":"……","tone":"opportunity"}},
|
||
{{"icon":"risk","title":"风险","code":"R1 / R2","text":"……","tone":"danger"}},
|
||
{{"icon":"constraint","title":"约束","code":"C1","text":"……","tone":"amber"}}
|
||
]
|
||
}}
|
||
```
|
||
|
||
`assessmentProgress`:
|
||
```json
|
||
{{
|
||
"value": 82,
|
||
"items": [
|
||
{{"icon":"brief","label":"已核实事实","value":28,"tone":"cyan"}},
|
||
{{"icon":"event","label":"待核实信息","value":7,"tone":"amber"}},
|
||
{{"icon":"risk","label":"待决策问题","value":4,"tone":"orange"}}
|
||
]
|
||
}}
|
||
```
|
||
value 为 70~90 的整数;items 的 label 保持中文,value 为整数。
|
||
|
||
""" + submit_rules("block_1") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-enemy-entities",
|
||
"name": "竞对主体能力席位",
|
||
"description": "任务指挥链·阶段1:产出敌情页主体/能力/地图,提交 block_2 的 enemyEntities/enemyCapabilities/enemyMap*。",
|
||
"soul": f"""# 竞对主体能力席位(mc-enemy-entities)
|
||
|
||
你负责「敌情」页的主体与能力层,写成商业竞对情报。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(严格遵守)
|
||
|
||
`enemyEntities` —— 5 条:
|
||
```json
|
||
[{{"id":"E1","icon":"enemy-formation","name":"岚海科技","description":"主力竞对,价格与渠道下沉","risk":"高","level":"high"}}]
|
||
```
|
||
icon 从 enemy-formation/enemy-network/mercenary-group/cyber-unit/support-convoy 取;level ∈ high/medium;risk ∈ 高/中。
|
||
|
||
`enemyCapabilities` —— 6 条,id 保持 mobility/organization/capability/influence/support/procurement,label 可写成商业能力名(渠道下沉、组织协同、舆论声量等),score 6.0~9.0,level ∈ 高/中/中高,tone ∈ high/medium。
|
||
|
||
`enemyMapLayers` —— id:all/entity/event/narrative/platform/area/support。
|
||
`enemyMapNodes` —— G01/G02/G03,name 商业化,tone ∈ friendly/critical。
|
||
`enemyMapSubjects` —— 5 条与 entities 对应,icon/code 一致,x/y 数字。
|
||
`enemyMapAnnotations` —— 5 条,title/detail 为商业事件(促销窗口、渠道截流、舆情渗透)。
|
||
|
||
""" + submit_rules("block_2") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-enemy-narrative",
|
||
"name": "竞对叙事影响席位",
|
||
"description": "任务指挥链·阶段1:产出敌情页叙事/趋势/风险,提交 block_2 的 enemyNarratives/enemyTrend/enemyImpactMatrix/enemyRiskAlerts。",
|
||
"soul": f"""# 竞对叙事影响席位(mc-enemy-narrative)
|
||
|
||
你负责「敌情」页的叙事、趋势与影响,写成商业舆情与市场动作。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(严格遵守)
|
||
|
||
`enemyNarratives` —— 3 条,id 固定 operation/narrative/broadcast:
|
||
```json
|
||
[{{"id":"operation","icon":"enemy-operation","title":"当前主要行动","description":"……","tone":"danger"}}]
|
||
```
|
||
tone ∈ danger/blue/green。
|
||
|
||
`enemyTrend`:
|
||
```json
|
||
{{
|
||
"labels": ["06-08","06-09","06-10","06-11","06-12","06-13","06-14"],
|
||
"series": [
|
||
{{"id":"activity","label":"竞对促销强度","color":"#ff667a","values":[33,42,37,49,58,63,74]}},
|
||
{{"id":"opinion","label":"舆论热度","color":"#ffbc65","values":[18,26,23,29,33,45,61]}},
|
||
{{"id":"support","label":"渠道截流迹象","color":"#a785ff","values":[8,11,10,14,17,20,27]}}
|
||
]
|
||
}}
|
||
```
|
||
|
||
`enemyImpactMatrix` —— 5 条,id E1~E5,impacts 长度 3,value ∈ 高/中/低,tone ∈ high/medium/low。
|
||
|
||
`enemyRiskAlerts` —— 3 条:`{{"id","title","text","level"}}`,level ∈ 高/中高/中。
|
||
|
||
""" + submit_rules("block_2") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-friendly-seat",
|
||
"name": "我方经营能力席位",
|
||
"description": "任务指挥链·阶段1:产出我情页全部 JSON,提交 block_3。",
|
||
"soul": f"""# 我方经营能力席位(mc-friendly-seat)
|
||
|
||
你负责「我情」整页 JSON,写成我方品牌/渠道/内容/客户触达能力。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(该块全部由你写)
|
||
|
||
`friendlyCapabilityScores` —— 8 条,id 保持 monitor/verify/calibrate/produce/group/reach/publish/evaluate;
|
||
label 可商业化(态势监测→市场监测等);value 50~95 整数;status ∈ 充足/基本满足/存在缺口;tone ∈ sufficient/basic/gap。
|
||
|
||
`friendlyResources` —— 5 条,id 保持 accounts/media/channel/experts/emergency;amount 为带单位字符串;status 短中文;tone sufficient。
|
||
|
||
`friendlyGaps` —— 3 条:`{{"id","rank","title","text","level","tone"}}`,level ∈ 高/中,tone ∈ high/medium。
|
||
|
||
`friendlyTopologyLayers` —— 保持 id:all/g01/g02/g03/capability/force/account/media/channel/time。
|
||
|
||
`friendlyTopologyNodes` —— 约 24 条,type ∈ target/capability/force/resource/time;含 G01/G02/G03 与能力/资源/时间窗节点;title/detail 商业化。
|
||
|
||
`friendlyResourceMatrix`:
|
||
```json
|
||
{{
|
||
"columns": [{{"id":"monitor","label":"市场监测"}}, …共8列],
|
||
"rows": [
|
||
{{"id":"G01","label":"核心客户续约","cells":["sufficient","sufficient","basic","basic","sufficient","basic","sufficient","basic"],"overall":"充足","tone":"sufficient"}}
|
||
]
|
||
}}
|
||
```
|
||
cells 每行 8 个,∈ sufficient/basic/gap。
|
||
|
||
`friendlyResourceSchedule` —— 5 条:`{{"id","label","start","span","text","tone"}}`,start/span 为数字,tone ∈ sufficient/basic/gap。
|
||
|
||
""" + submit_rules("block_3") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-environment-seat",
|
||
"name": "市场环境席位",
|
||
"description": "任务指挥链·阶段1:产出战场环境页全部 JSON,提交 block_4。",
|
||
"soul": f"""# 市场环境席位(mc-environment-seat)
|
||
|
||
你负责「战场环境」整页 JSON,写成政策/舆情/平台/时间窗口等经营环境。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(该块全部由你写)
|
||
|
||
`environmentFactors` —— 6 条,id 保持 information/social/opinion/platform/timeline/policy;
|
||
title 可保持「信息环境」等原标题,items 的 text 必须商业化。
|
||
每条 3 个 items,id 固定 advantage/adverse/trigger,state ∈ 有利/不利/触发,tone ∈ favorable/adverse/trigger。
|
||
overall 短中文,tone ∈ favorable/adverse。
|
||
|
||
`environmentMapLayers` —— id:all/information/social/opinion/platform/timeline/policy。
|
||
`environmentMapNodes` —— 约 6 条,tone ∈ friendly/target/danger。
|
||
`environmentMapAnnotations` —— 约 6 条,tone ∈ opinion/danger/favorable/constraint/neutral。
|
||
|
||
`environmentConclusionGroups` —— 3 条,id 固定 favorable/adverse/trigger,items 为字符串数组。
|
||
|
||
`environmentTrend`:
|
||
```json
|
||
{{
|
||
"labels": ["现在","+4h","+8h","+12h","+16h","+20h","+24h"],
|
||
"series": [
|
||
{{"id":"opinion","label":"舆论热度","color":"#ff657a","values":[34,39,46,58,65,72,78]}},
|
||
{{"id":"warning","label":"竞对加码概率","color":"#ffcc5d","values":[19,22,29,36,48,62,75]}},
|
||
{{"id":"window","label":"窗口可用性","color":"#43d5ff","values":[94,89,82,69,48,30,18]}}
|
||
]
|
||
}}
|
||
```
|
||
|
||
`environmentWindow`:`{{"label":"6–12 h","detail":"……","notes":["……","……","……"]}}`
|
||
|
||
""" + submit_rules("block_4") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-assessment-factors",
|
||
"name": "情况研判要素席位",
|
||
"description": "任务指挥链·阶段2:产出情况研判 SWOT 要素,提交 block_5 的 assessmentFactors。",
|
||
"soul": f"""# 情况研判要素席位(mc-assessment-factors)
|
||
|
||
你运行在阶段 2,基于前序敌情/我情/环境交付产出 SWOT 要素。内容商业化。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key
|
||
|
||
`assessmentFactors` —— 5 条,id 固定 strengths/weaknesses/risks/opportunities/constraints:
|
||
```json
|
||
[{{
|
||
"id":"strengths","title":"我方优势","icon":"thumb-up","tone":"opportunity",
|
||
"items":[{{"code":"S1","text":"……","level":"高","tone":"high"}}]
|
||
}}]
|
||
```
|
||
每组 3 条 items;level ∈ 高/中;tone ∈ high/medium。text 写经营优劣势、市场风险、窗口机会、政策/预算约束。
|
||
|
||
""" + submit_rules("block_5") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-assessment-synthesis",
|
||
"name": "情况研判综合席位",
|
||
"description": "任务指挥链·阶段2:产出情况研判地图与结论,提交 block_5 的 assessmentMap*/assessmentJudgements/assessmentConclusion。",
|
||
"soul": f"""# 情况研判综合席位(mc-assessment-synthesis)
|
||
|
||
你运行在阶段 2,基于前序交付做综合研判地图与结论。内容商业化。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key
|
||
|
||
`assessmentMapLayers` —— id:all/enemy/friendly/environment/deduction/conclusion。
|
||
|
||
`assessmentMapCards` —— 3 条,id 固定 enemy/environment/friendly;lines 为 `[["标签:","商业内容"],…]` 长度 4。
|
||
|
||
`assessmentMapNodes` —— G01/G02/G03,title 商业目标名,tone ∈ friendly/target。
|
||
|
||
`assessmentMapUnits` —— 约 6 条,tone ∈ enemy/friendly,layers 含 enemy 或 friendly。
|
||
|
||
`assessmentJudgements` —— 3 条:`{{"id","code","title","text","icon","tone","layers","x"}}`,code J-01/J-02/J-03。
|
||
|
||
`assessmentConclusion`:
|
||
```json
|
||
{{
|
||
"points": ["商业结论1","结论2","结论3"],
|
||
"groups": [
|
||
{{"id":"risk","title":"风险","icon":"risk","tone":"danger","items":[["R1","……"],["R2","……"],["R3","……"]]}},
|
||
{{"id":"opportunity","title":"机会","icon":"opportunity","tone":"opportunity","items":[["O1","……"],["O2","……"],["O3","……"]]}},
|
||
{{"id":"constraint","title":"约束","icon":"constraint","tone":"amber","items":[["C1","……"],["C2","……"],["C3","……"]]}}
|
||
],
|
||
"feasibility": {{"title":"任务可行性评估","value":68,"label":"总体可行","detail":"但风险较高"}}
|
||
}}
|
||
```
|
||
value 55~85 整数。
|
||
|
||
""" + submit_rules("block_5") + NO_ASK_RULES,
|
||
},
|
||
{
|
||
"id": "mc-recommendation-seat",
|
||
"name": "决心建议席位",
|
||
"description": "任务指挥链·阶段3:产出决心建议整页 JSON(recommendationModes 必须整包提交),提交 block_6。",
|
||
"soul": f"""# 决心建议席位(mc-recommendation-seat)
|
||
|
||
你运行在阶段 3,必须基于前序全部交付给出决策建议。`recommendationModes` 是嵌套大数组,**必须由你整包提交**,不要拆开。
|
||
进攻型 = 市场扩张方案;防御型 = 风险/舆情处置。正文商业化,字段名保持原样。
|
||
{SCENE}
|
||
|
||
## 你负责的顶层 key(该块全部由你写)
|
||
|
||
`recommendationMaturity`:
|
||
```json
|
||
{{
|
||
"value": 82, "label": "研判基本成熟", "detail": "可支撑决心建议",
|
||
"metrics": [["document-stack","已核实事实","28"],["assessment-lens","待核实信息","7"],["risk","存疑 / 风险项","4"]]
|
||
}}
|
||
```
|
||
|
||
`recommendationModes` —— 长度必须为 2,id 固定 offensive / defensive。结构要点:
|
||
|
||
- `label` 保持「进攻型任务 · 决心建议」「防御型任务 · 处置建议」
|
||
- `options` 3 条,id coa-a/coa-b/coa-c,title 用商业方案名(如「渠道闪电铺货」「重点区域深耕」「防守型控盘」)
|
||
- `metrics` 每条 5 组 `["指标名","值"]`
|
||
- `evidence[0].items` 6 条,形如 `["J-01","标题","商业依据","高"]`,第四段 ∈ 高/中/低
|
||
- `map.cards` / `map.side` / `map.nodes` / `map.legend` / `map.process` / `map.recommendation` 字段齐全
|
||
- `map.background` 保持:
|
||
- offensive: `/assets/mission-command/offensive-decision-background.png`
|
||
- defensive: `/assets/mission-command/defensive-response-background.png`
|
||
- `summary.primary` 与 `summary.companion` 的 rows 为 `["字段","商业内容"]`
|
||
- `defaultOption` 用 `coa-b`
|
||
|
||
节点 G01/G02/G03 的 label 写成核心客户/重点区域/关键渠道。
|
||
|
||
""" + submit_rules("block_6") + NO_ASK_RULES,
|
||
},
|
||
]
|
||
|
||
CHAIN = {
|
||
"id": CHAIN_ID,
|
||
"title": "任务指挥商业情报采集链",
|
||
"description": (
|
||
"任务指挥 6 页数据交付:阶段1 并行采集总览事实/竞对/我情/环境;"
|
||
"阶段2 总览研判与情况研判;阶段3 决心建议。各席位经 task-data-submit 写入 block_1~block_6。"
|
||
),
|
||
"seats": [
|
||
{"agent_id": "mc-overview-facts", "name": "总览事实", "description": "block_1 事实层:议题/摘要/地图"},
|
||
{"agent_id": "mc-overview-synthesis", "name": "总览研判", "description": "block_1 研判层:综合结论/进度"},
|
||
{"agent_id": "mc-enemy-entities", "name": "竞对主体", "description": "block_2 主体/能力/地图"},
|
||
{"agent_id": "mc-enemy-narrative", "name": "竞对叙事", "description": "block_2 叙事/趋势/风险"},
|
||
{"agent_id": "mc-friendly-seat", "name": "我方能力", "description": "block_3 整页"},
|
||
{"agent_id": "mc-environment-seat", "name": "市场环境", "description": "block_4 整页"},
|
||
{"agent_id": "mc-assessment-factors", "name": "研判要素", "description": "block_5 SWOT 要素"},
|
||
{"agent_id": "mc-assessment-synthesis", "name": "研判综合", "description": "block_5 地图与结论"},
|
||
{"agent_id": "mc-recommendation-seat", "name": "决心建议", "description": "block_6 整页"},
|
||
],
|
||
"stages": [
|
||
[
|
||
"mc-overview-facts",
|
||
"mc-enemy-entities",
|
||
"mc-enemy-narrative",
|
||
"mc-friendly-seat",
|
||
"mc-environment-seat",
|
||
],
|
||
[
|
||
"mc-overview-synthesis",
|
||
"mc-assessment-factors",
|
||
"mc-assessment-synthesis",
|
||
],
|
||
["mc-recommendation-seat"],
|
||
],
|
||
"stage_goals": [
|
||
"并行采集:总览事实、竞对两席、我情、环境,分别提交 block_1~block_4",
|
||
"并行研判:总览综合 + 情况研判两席,提交 block_1 研判层与 block_5",
|
||
"决策建议:决心建议席位整包提交 block_6",
|
||
],
|
||
"coordinator_prompt": (
|
||
"派活时明确块与顶层 key 边界:总览事实只交 coreIssues/situationBriefs/map* 到 block_1;"
|
||
"总览研判只交 assessmentSummary/assessmentProgress 到 block_1;"
|
||
"竞对主体交 enemyEntities/enemyCapabilities/enemyMap* 到 block_2;"
|
||
"竞对叙事交 enemyNarratives/enemyTrend/enemyImpactMatrix/enemyRiskAlerts 到 block_2;"
|
||
"我情整页交 block_3;环境整页交 block_4;"
|
||
"研判要素只交 assessmentFactors 到 block_5;研判综合交其余 block_5 key;"
|
||
"决心建议整包交 recommendationModes/recommendationMaturity 到 block_6。"
|
||
"所有席位用 task-data-submit,--task-id 取任务上下文里的「数据交付 task_id」。"
|
||
"正文必须商业化。无人值守,禁止澄清。"
|
||
),
|
||
"enabled": True,
|
||
}
|
||
|
||
BLOCK_TITLES = [
|
||
{"block_key": "block_1", "title": "任务研判总览"},
|
||
{"block_key": "block_2", "title": "敌情"},
|
||
{"block_key": "block_3", "title": "我情"},
|
||
{"block_key": "block_4", "title": "战场环境"},
|
||
{"block_key": "block_5", "title": "情况研判"},
|
||
{"block_key": "block_6", "title": "决心建议"},
|
||
]
|
||
|
||
|
||
def call(method: str, path: str, body: dict | None, token: str):
|
||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||
if token:
|
||
headers["Authorization"] = f"Bearer {token}"
|
||
request = urllib.request.Request(
|
||
BASE + path,
|
||
data=json.dumps(body).encode() if body is not None else None,
|
||
headers=headers,
|
||
method=method,
|
||
)
|
||
try:
|
||
with urllib.request.urlopen(request, timeout=60) as response:
|
||
return response.status, json.loads(response.read() or b"{}")
|
||
except urllib.error.HTTPError as error:
|
||
return error.code, json.loads(error.read() or b"{}")
|
||
|
||
|
||
def upsert_agent(token: str, agent: dict) -> int:
|
||
status, existing = call("GET", f"/api/agents/{agent['id']}", None, token)
|
||
body = {
|
||
**agent,
|
||
"published": True,
|
||
"skills": ["task-data-submit"],
|
||
"tool_groups": ["web", "file:read", "file:write", "bash"],
|
||
}
|
||
if status == 200:
|
||
status, updated = call(
|
||
"PUT",
|
||
f"/api/agents/{agent['id']}",
|
||
{
|
||
"name": agent["name"],
|
||
"description": agent["description"],
|
||
"soul": agent["soul"],
|
||
"published": True,
|
||
"skills": ["task-data-submit"],
|
||
"tool_groups": ["web", "file:read", "file:write", "bash"],
|
||
},
|
||
token,
|
||
)
|
||
print(f"更新智能体 {agent['id']}: HTTP {status}", updated.get("id") or updated)
|
||
return 0 if status in (200, 201) else 1
|
||
status, created = call("POST", "/api/agents", body, token)
|
||
print(f"创建智能体 {agent['id']}: HTTP {status}", created.get("id") or created)
|
||
return 0 if status in (200, 201) else 1
|
||
|
||
|
||
def upsert_chain(token: str) -> int:
|
||
status, existing = call("GET", f"/api/roundtable-chains/{CHAIN_ID}", None, token)
|
||
if status == 200:
|
||
status, updated = call(
|
||
"PUT",
|
||
f"/api/roundtable-chains/{CHAIN_ID}",
|
||
{
|
||
"title": CHAIN["title"],
|
||
"description": CHAIN["description"],
|
||
"seats": CHAIN["seats"],
|
||
"stages": CHAIN["stages"],
|
||
"stage_goals": CHAIN["stage_goals"],
|
||
"coordinator_prompt": CHAIN["coordinator_prompt"],
|
||
"enabled": True,
|
||
},
|
||
token,
|
||
)
|
||
print(f"更新业务链: HTTP {status}", updated.get("id") or CHAIN_ID)
|
||
if status not in (200, 201):
|
||
print(updated)
|
||
return 1
|
||
else:
|
||
status, created = call("POST", "/api/roundtable-chains", CHAIN, token)
|
||
print(f"创建业务链: HTTP {status}", created.get("id") or created)
|
||
if status not in (200, 201):
|
||
print(created)
|
||
return 1
|
||
status, chain = call("GET", f"/api/roundtable-chains/{CHAIN_ID}", None, token)
|
||
if status == 200 and not chain.get("is_public"):
|
||
status, updated = call("PUT", f"/api/roundtable-chains/{CHAIN_ID}", {"is_public": True}, token)
|
||
print(
|
||
f"发布业务链: HTTP {status}, is_public={updated.get('is_public')}, "
|
||
f"自动发布席位={updated.get('published_seat_agents')}"
|
||
)
|
||
if status not in (200, 201):
|
||
print(updated)
|
||
return 1
|
||
else:
|
||
print("业务链已是公开状态")
|
||
return 0
|
||
|
||
|
||
def ensure_task(token: str, *, skip_trigger: bool) -> tuple[int, str | None]:
|
||
status, detail = call("GET", f"/api/task-analysis/external/tasks/{SOURCE_TASK_ID}/detail", None, token)
|
||
binding_body = {
|
||
"chain_id": CHAIN_ID,
|
||
"cron": "0 9 * * *",
|
||
"timezone": "Asia/Shanghai",
|
||
"schedule_type": "once",
|
||
"enabled": not skip_trigger,
|
||
"model": None,
|
||
"coordinator_prompt": None,
|
||
}
|
||
if status == 200:
|
||
internal_id = detail["task"]["task_id"]
|
||
print(f"任务已存在: {SOURCE_TASK_ID} -> {internal_id}")
|
||
status, bound = call("PUT", f"/api/task-analysis/tasks/{internal_id}/binding", binding_body, token)
|
||
print(f"绑定一次性任务: HTTP {status}", bound.get("binding", {}).get("schedule_type") or bound)
|
||
if status not in (200, 201):
|
||
print(bound)
|
||
return 1, None
|
||
return 0, internal_id
|
||
|
||
status, created = call(
|
||
"POST",
|
||
"/api/task-analysis/tasks",
|
||
{
|
||
"name": TASK_NAME,
|
||
"source_task_id": SOURCE_TASK_ID,
|
||
"description": "任务指挥页 6 块商业情报采集(只执行一次)",
|
||
"binding": binding_body,
|
||
},
|
||
token,
|
||
)
|
||
print(f"创建任务 {SOURCE_TASK_ID}: HTTP {status}", created.get("task", {}).get("id") or created)
|
||
if status not in (200, 201):
|
||
print(created)
|
||
return 1, None
|
||
internal_id = created["task"]["id"]
|
||
run = created.get("run") or {}
|
||
if run:
|
||
print(f"一次性任务已启动: run_id={run.get('id')}, status={run.get('status')}")
|
||
return 0, internal_id
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--skip-trigger", action="store_true", help="只建智能体/链/任务绑定,不触发执行")
|
||
args = parser.parse_args()
|
||
|
||
query = urllib.parse.urlencode({"password": PASSWORD})
|
||
status, login = call("POST", f"/api/v1/auth/login/username?{query}", {"username": USERNAME}, None)
|
||
if status != 200:
|
||
print("登录失败", status, login)
|
||
return 1
|
||
token = login["access_token"]
|
||
print("登录 OK")
|
||
|
||
for agent in AGENTS:
|
||
if upsert_agent(token, agent) != 0:
|
||
return 1
|
||
|
||
if upsert_chain(token) != 0:
|
||
return 1
|
||
|
||
code, internal_id = ensure_task(token, skip_trigger=args.skip_trigger)
|
||
if code != 0 or not internal_id:
|
||
return 1
|
||
|
||
# 新任务 ensure_block_defs 会种 6 条;存量 5 块任务在读 blocks 时懒补 block_6。
|
||
status, _defs = call("GET", f"/api/task-analysis/external/tasks/{SOURCE_TASK_ID}/blocks", None, token)
|
||
print(f"读取块定义: HTTP {status}")
|
||
status, renamed = call(
|
||
"PUT",
|
||
f"/api/task-analysis/tasks/{internal_id}/blocks",
|
||
{"blocks": BLOCK_TITLES},
|
||
token,
|
||
)
|
||
titles = [item.get("title") for item in renamed.get("blocks", [])] if isinstance(renamed, dict) else renamed
|
||
print(f"更新块标题: HTTP {status}", titles)
|
||
if status not in (200, 201):
|
||
print(renamed)
|
||
return 1
|
||
|
||
if args.skip_trigger:
|
||
print("已跳过触发。可在任务分析页手动「立即执行」。")
|
||
else:
|
||
# 新建 once+enabled 会在 POST /tasks 时启动;存量任务需要手动 trigger。
|
||
status, runs = call("GET", f"/api/task-analysis/tasks/{internal_id}/runs", None, token)
|
||
run_rows = runs if isinstance(runs, list) else (runs.get("runs") or [])
|
||
live = [item for item in run_rows if item.get("status") in ("running", "pending")]
|
||
if live:
|
||
print(f"已有执行中的运行: {live[0].get('id')} status={live[0].get('status')}")
|
||
else:
|
||
status, run = call("POST", f"/api/task-analysis/tasks/{internal_id}/trigger", {}, token)
|
||
print(
|
||
f"手动触发: HTTP {status}, run_id={(run.get('run') or {}).get('id')}, "
|
||
f"status={(run.get('run') or {}).get('status')}"
|
||
)
|
||
if status not in (200, 201):
|
||
print(run)
|
||
return 1
|
||
|
||
print(f"\n内部任务 id: {internal_id}")
|
||
print(f"外部任务号: {SOURCE_TASK_ID}")
|
||
print(f"查询执行: GET /api/task-analysis/tasks/{internal_id}/runs")
|
||
print(f"查询数据: GET /api/task-analysis/external/tasks/{SOURCE_TASK_ID}/periods/latest")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|