"""圆桌后台作业真网关 —— headless 验证脚本(Phase 3b)。 用法(在能起完整后端栈 + 配好真模型的环境): 1) 用真网关启动后端: ROUNDTABLE_JOB_GATEWAY=inprocess make gateway (或 docker 里给 gateway 容器加该环境变量) 2) 跑本脚本,它会:POST 启动一个作业 → 轮询 GET 打印进度 → 跑到 done 打印报告长度。 PYTHONPATH=. uv run python scripts/probe_roundtable_job.py 环境变量: GATEWAY_URL 默认 http://127.0.0.1:8001 AUTH_COOKIE 若后端开启鉴权,传 "session=xxx" 之类的 Cookie 头 PROBE_INTENT 自定义任务意图 PROBE_MODE recommend(默认)| chain 说明:鉴权关闭的 dev 模式无需 AUTH_COOKIE。真网关会进程内驱动 roundtable-coordinator / 各席位 / roundtable-report 真模型,耗时取决于模型,可能数分钟。 """ from __future__ import annotations import asyncio import os import time import httpx GATEWAY_URL = os.getenv("GATEWAY_URL", "http://127.0.0.1:8001").rstrip("/") AUTH_COOKIE = os.getenv("AUTH_COOKIE", "") INTENT = os.getenv("PROBE_INTENT", "为一家中型制造企业设计一套数字化转型的整体方案") MODE = os.getenv("PROBE_MODE", "recommend") # 用 3 个内置圆桌席位(确保可见、已 seed)。 AGENTS = [ {"agentId": "roundtable-intelligence", "name": "情报收集"}, {"agentId": "roundtable-solution-design", "name": "方案设计"}, {"agentId": "roundtable-summary", "name": "总结陈述"}, ] TERMINAL = {"done", "error", "cancelled"} def _headers() -> dict[str, str]: h = {"Content-Type": "application/json"} if AUTH_COOKIE: h["Cookie"] = AUTH_COOKIE return h async def main() -> None: async with httpx.AsyncClient(timeout=30.0, trust_env=False) as client: print(f"[probe] POST {GATEWAY_URL}/api/roundtable-jobs mode={MODE}") resp = await client.post( f"{GATEWAY_URL}/api/roundtable-jobs", headers=_headers(), json={"intent": INTENT, "agents": AGENTS, "orchestrationMode": MODE}, ) if resp.status_code != 201: print(f"[probe] start failed: HTTP {resp.status_code}: {resp.text[:500]}") return job = resp.json() job_id = job["id"] print(f"[probe] started job {job_id}") last = "" t0 = time.perf_counter() while True: await asyncio.sleep(2.0) r = await client.get(f"{GATEWAY_URL}/api/roundtable-jobs/{job_id}", headers=_headers()) if r.status_code != 200: print(f"[probe] get failed: HTTP {r.status_code}: {r.text[:300]}") return j = r.json() line = ( f"status={j['status']} phase={j['phase']} cycle={j['cycle']} " f"active={j.get('activeAgentId')} consensus={j['consensusPercentage']}%" ) if line != last: print(f"[probe] +{time.perf_counter() - t0:5.0f}s {line}") last = line if j["status"] in TERMINAL: print(f"[probe] terminal: {j['status']}") if j["status"] == "error": print(f"[probe] error detail: {j.get('error')}") # 读草稿 step3 验证报告已落库 if j.get("draftId"): dr = await client.get( f"{GATEWAY_URL}/api/roundtable-drafts/{j['draftId']}", headers=_headers() ) if dr.status_code == 200: step3 = dr.json().get("step3") html_len = len((step3 or {}).get("html") or "") print(f"[probe] draft.step3 html length = {html_len}") return if __name__ == "__main__": asyncio.run(main())