feat: 圆桌第3步大屏多页改用专职智能体 + 健壮 report-json 抽取

修复「大屏多页抽取 json 又失败」:旧实现复用画 HTML 的 roundtable-report
智能体 + rfind 围栏 + 单次 json.loads 的简单粗暴抽取,弱模型跑偏/格式偏差即失败。

- 新内置专职单例 roundtable-dashboard「大屏多页数据智能体」:SOUL 专讲逐席位
  分析 → report-json 契约,禁工具/禁文件/禁 HTML;新 run policy dashboard 禁用一切工具
- harness 新增 report_json.py:多策略抽取(report-json/json/裸 JSON/截断)+ schema
  归一化(移植前端 extractReportData/coerceReportData),返回可直接渲染的归一化 JSON
- 后台 run_dashboard_data 改用专职 agent + dashboard policy + 健壮抽取
- 新增 /api/multi-agent/dashboard/init;前端 initDashboardThread + useStep3Dashboard 改用之

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-17 21:47:21 +08:00
1 parent bb05f7b1de
commit ed887f762e
9 files changed
+452 -29

No files matched your search

@@ -835,6 +835,32 @@ export async function initReportThread(
return { agentName: json.agent_name, threadId: json.thread_id };
}
/**
* Create a thread for the Step 3「大屏多页」dashboard-data agent
* (`roundtable-dashboard`, a built-in singleton — distinct from `roundtable-report`;
* its run policy disables ALL tools so it only emits a ```report-json block). Run it
* with {@link streamMultiAgent} (`agentType: "special"`, `agentName: "roundtable-dashboard"`,
* `dAgentThreadId: { [agentName]: threadId }`); the frontend then `extractReportData`s
* the streamed text. Follow-up Q&A / refinements reuse the SAME thread (do not re-init)
* so the agent keeps the prior dashboard context.
*/
export async function initDashboardThread(
options: { signal?: AbortSignal } = {},
): Promise<ReportThreadResponse> {
const res = await apiFetch(`${getBackendBaseURL()}/api/multi-agent/dashboard/init`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: "{}",
signal: options.signal,
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`init dashboard thread failed (${res.status}): ${text}`);
}
const json = (await res.json()) as { agent_name: string; thread_id: string };
return { agentName: json.agent_name, threadId: json.thread_id };
}
/**
* Create a thread for the Step 3「总结报告」summary agent (`roundtable-summary`,
* a built-in singleton — distinct from `roundtable-report`; this only spins up a
@@ -15,7 +15,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { useArtifacts } from "@/components/workspace/artifacts";
import type { DraftStep3Snapshot } from "@/roundtable-planning/api/drafts";
import {
initReportThread,
initDashboardThread,
streamMultiAgent,
type StreamPhase,
} from "@/roundtable-planning/api/multi-agent";
@@ -35,7 +35,7 @@ import {
} from "@/roundtable-planning/lib/report-template";
import { nowTime } from "@/roundtable-planning/lib/roundtable-constants";
const DASH_AGENT_FALLBACK = "roundtable-report";
const DASH_AGENT_FALLBACK = "roundtable-dashboard";
export type DashboardPhase = "idle" | "creating" | "streaming" | "done" | "error";
@@ -269,7 +269,7 @@ export function useStep3Dashboard(options: UseStep3DashboardOptions): UseStep3Da
const { bubbleId, message, aborter } = args;
let threadId = liveThreadRef.current;
if (!threadId) {
const init = await initReportThread({ signal: aborter.signal });
const init = await initDashboardThread({ signal: aborter.signal });
if (aborter.signal.aborted) return;
threadId = init.threadId;
agentNameRef.current = init.agentName || DASH_AGENT_FALLBACK;