MiroFish · 专题 03
← MiroFish 项目分析 专题 03 / 03
Technical deep-dive · 03

报告证据链与交互机制

报告生成器真实存在,并具有大纲规划、ReACT 工具调用、逐章落盘和前端渐进展示能力;但它的默认证据入口是 Zep 图谱上下文,不是平台行动日志或 SQLite 数据库。标准网页虽开启模拟记忆写回,使报告可能间接检索到行动,但结论没有强制携带行动编号、轮次、平台或原文引用。更严重的是:报告接口只接受已完成/已停止模拟,而报告代理提供的 Agent 采访又要求 OASIS 环境仍然存活;当前第三步页面的报告按钮需等到终态,却没有绑定关闭环境按钮。静态源码因此显示一条需要端到端复现的流程矛盾。

版本 0.1.0 证据基线 117ed3775 分析日期 2026-08-24
Key finding
静态源码因此显示一条需要端到端复现的流程矛盾。

专题结论

报告生成器真实存在,并具有大纲规划、ReACT 工具调用、逐章落盘和前端渐进展示能力;但它的默认证据入口是 Zep 图谱上下文,不是平台行动日志或 SQLite 数据库。标准网页虽开启模拟记忆写回,使报告可能间接检索到行动,但结论没有强制携带行动编号、轮次、平台或原文引用。更严重的是:报告接口只接受已完成/已停止模拟,而报告代理提供的 Agent 采访又要求 OASIS 环境仍然存活;当前第三步页面的报告按钮需等到终态,却没有绑定关闭环境按钮。静态源码因此显示一条需要端到端复现的流程矛盾。

原始类与方法

原始符号 真实职责 源码定义 固定提交行级证据
generate_report()(检查模拟、图谱和写入终态后启动报告任务的接口函数,约第 33 行) 报告阶段的状态门槛与图谱 reader 租约入口 报告接口文件 固定版本第 33-286 行
ReportAgent(规划、检索、反思和逐章生成报告的代理类,约第 871 行) 管理报告工具和生成生命周期 报告代理文件 固定版本第 871-924 行
ReportAgent._define_tools()(注册深度洞察、全景、快速搜索和采访工具的方法,约第 925 行) 定义报告代理能够访问的数据入口 报告代理文件 固定版本第 925-960 行
ReportAgent.plan_outline()(根据图谱统计和相关事实生成大纲的方法,约第 1176 行) 报告规划阶段的数据入口 报告代理文件 固定版本第 1176-1258 行
ReportAgent._generate_section_react()(按节执行思考、工具、观察和回答循环的方法,约第 1260 行) 逐章检索与内容生成 报告代理文件 固定版本第 1260-1574 行
ReportAgent.generate_report()(规划、逐章保存并组装完整报告的方法,约第 1576 行) 管理报告文件与进度 报告代理文件 固定版本第 1576-1809 行
ZepToolsService.get_simulation_context()(返回图谱相关事实、统计和实体的方法,约第 888 行) 大纲规划所称“模拟上下文”的实际数据源 图谱工具文件 固定版本第 888-939 行
ZepToolsService.interview_agents()(选人、生成问题并访问存活 OASIS 环境的方法,约第 1270 行) 报告代理和深度交互的 Agent 采访入口 图谱工具文件 固定版本第 1270-1435 行
SimulationRunner.get_all_actions()(从两个平台行动日志读取完整行动历史的方法,约第 1156 行) 已存在但未被报告代理调用的直接模拟证据接口 模拟运行文件 固定版本第 1156-1206 行
handleNextStep()(向报告接口发请求并跳转报告页的前端函数,约第 645 行) 第三步进入第四步的唯一可见主路径 第三步组件 固定版本第 645-679 行
renderMarkdown()(正则转换 Markdown 并返回 HTML 的报告前端函数,约第 1874 行) 把模型生成内容交给富文本指令显示 报告组件 固定版本第 1874-1930 行
sendMessage()(向报告代理或选定 Agent 发送消息的前端函数,约第 645 行) 深度交互页的单体对话入口 深度交互组件 固定版本第 645-755 行
submitSurvey()(批量采访选定 Agent 的问卷函数,约第 805 行) 群体问卷入口 深度交互组件 固定版本第 805-894 行

原始调用链

  1. handleNextStep()(第三步请求生成报告的方法,约第 645 行)调用后端 generate_report()(报告状态门槛与后台任务入口,约第 33 行)。
  2. 后端通过终态和图谱 reader 检查后创建 ReportAgent(持有报告生成状态和工具的代理类,约第 871 行),进入 ReportAgent.generate_report()(组织规划与逐章生成的方法,约第 1576 行)。
  3. ReportAgent.generate_report()(保存大纲、章节和完整报告的方法,约第 1576 行)先调用 ReportAgent.plan_outline()(规划大纲的方法,约第 1176 行),后者只通过 ZepToolsService.get_simulation_context()(读取图谱事实、统计和实体的方法,约第 888 行)取得上下文。
  4. 每节内容进入 ReportAgent._generate_section_react()(执行 ReACT 工具循环的方法,约第 1260 行),可使用图谱检索或 ZepToolsService.interview_agents()(访问存活模拟环境进行采访的方法,约第 1270 行)。
  5. 第四步页面用 renderMarkdown()(正则生成 HTML 的渲染函数,约第 1874 行)展示章节;第五步通过 sendMessage()(单 Agent/报告代理对话函数,约第 645 行)和 submitSurvey()(多 Agent 问卷函数,约第 805 行)继续交互。

源码片段一:报告只接受终态

原项目关键片段。来源:generate_report()(拒绝活动态并只接受完成/已停止状态的接口函数,约第 84-117 行);固定提交第 84-117 行

原项目关键片段:

python
run_state = SimulationRunner.get_run_state(simulation_id)
updater = ZepGraphMemoryManager.get_updater(simulation_id)
active_statuses = {
    RunnerStatus.STARTING,
    RunnerStatus.RUNNING,
    RunnerStatus.PAUSED,
    RunnerStatus.STOPPING,
}
if updater is not None or (
    run_state is not None and run_state.runner_status in active_statuses
):
    return jsonify({
        "success": False,
        "error": (
            "Simulation or Zep graph ingestion is still active; "
            "wait for a terminal run status before generating a report"
        ),
    }), 409

successful_terminal_statuses = {
    RunnerStatus.COMPLETED,
    RunnerStatus.STOPPED,
}

这个屏障本身是正确的:报告不应读取仍在变化或尚未完成图谱写入的数据。问题来自它与“保持环境以便采访”的运行模式组合,而不是终态检查单独存在。

源码片段二:大纲上下文来自图谱,不来自行动日志

原项目关键片段。来源:ReportAgent.plan_outline()(取得图谱上下文并拼接大纲提示的方法,约第 1191-1213 行);固定提交第 1191-1213 行

原项目关键片段:

python
context = self.zep_tools.get_simulation_context(
    graph_id=self.graph_id,
    simulation_requirement=self.simulation_requirement
)

user_prompt = PLAN_USER_PROMPT_TEMPLATE.format(
    simulation_requirement=self.simulation_requirement,
    total_nodes=context.get('graph_statistics', {}).get('total_nodes', 0),
    total_edges=context.get('graph_statistics', {}).get('total_edges', 0),
    entity_types=list(
        context.get('graph_statistics', {}).get('entity_types', {}).keys()
    ),
    total_entities=context.get('total_entities', 0),
    related_facts_json=json.dumps(
        context.get('related_facts', [])[:10],
        ensure_ascii=False,
        indent=2
    ),
)

全仓库静态调用检索显示,报告代理及其图谱工具没有调用 SimulationRunner.get_all_actions()(读取完整平台行动历史的方法,约第 1156 行),也没有读取平台 SQLite 数据库。标准前端开启图谱写回后,报告可能间接检索到自然语言化的模拟活动;但这一链路仍缺少行动编号、轮次、平台和原文引用。

源码片段三:采访要求存活环境

原项目关键片段。来源:ZepToolsService.interview_agents()(声明采访前置状态并调用 OASIS 的方法,约第 1278-1305 行);固定提交第 1278-1305 行

原项目关键片段:

python
"""
【InterviewAgents - 深度采访】

调用真实的OASIS采访API,采访模拟中正在运行的Agent:
1. 自动读取人设文件,了解所有模拟Agent
2. 使用LLM分析采访需求,智能选择最相关的Agent
3. 使用LLM生成采访问题
4. 调用 /api/simulation/interview/batch 接口进行真实采访
5. 整合所有采访结果,生成采访报告

【重要】此功能需要模拟环境处于运行状态(OASIS环境未关闭)
"""
from .simulation_runner import SimulationRunner

结合上一段终态门槛和模拟专题中的等待循环,状态矛盾可以精确描述为:报告入口要求环境所属子进程已退出并排空写入,报告工具却可能选择一个只有子进程仍在等待命令时才可用的采访工具。应该把“交互态”和“可报告态”设计成显式状态,或把采访结果在关闭环境前固化为报告输入。

源码片段四:报告内容未经清洗进入富文本指令

原项目关键片段 A。来源:报告章节模板(把渲染函数返回值交给 Vue 富文本指令的位置,约第 49-52 行);固定提交第 49-52 行

原项目关键片段:

vue
<div class="section-body" v-show="!collapsedSections.has(idx)">
  <div
    v-if="generatedSections[idx + 1]"
    class="generated-content"
    v-html="renderMarkdown(generatedSections[idx + 1])">
  </div>
</div>

原项目关键片段 B。来源:renderMarkdown()(用字符串替换生成 HTML 的函数,约第 1874-1887 行);固定提交第 1874-1887 行

原项目关键片段:

javascript
const renderMarkdown = (content) => {
  if (!content) return ''

  let processedContent = content.replace(/^##\s+.+\n+/, '')
  let html = processedContent.replace(
    /```(\w*)\n([\s\S]*?)```/g,
    '<pre class="code-block"><code>$2</code></pre>'
  )
  html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
  html = html.replace(/^#### (.+)$/gm, '<h5 class="md-h5">$1</h5>')

没有看到输入 HTML 转义、可信 Markdown 解析器或白名单清洗。由于内容来自材料、LLM 和 Agent,这不是单纯排版问题,而是潜在存储型/反射型 XSS。安全实现应默认禁用原始 HTML、对链接协议和标签/属性做白名单,并对第三步、第四步和第五步使用同一渲染组件。

产品表达与实际证据

产品或界面表达 实际实现证据 风险判断
“预测报告” ReportAgent.plan_outline()(从图谱事实生成大纲的方法,约第 1176 行)和 ReportAgent._generate_section_react()(通过检索与 LLM 逐章生成的方法,约第 1260 行)没有统计校准或行动级强制引用 更接近情景叙事报告,不是概率预测
“模拟后深度采访” ZepToolsService.interview_agents()(要求环境仍存活的方法,约第 1270 行)与 generate_report()(要求模拟终态的接口函数,约第 33 行)状态前提相反 主流程需要重设状态机
“开始生成报告”按钮 第三步模板约第 93-102 行要求页面完成阶段,但停止方法没有绑定到模板按钮 用户可能无法自然满足报告前置条件
“实时安全展示模型结果” renderMarkdown()(无白名单清洗的正则渲染函数,约第 1874 行)直接进入富文本指令 存在 XSS 风险

对当前需求的取舍

建议仅作设计参照逐章报告、工具调用可视化、图谱/工作台分屏、单 Agent 对话和群体问卷;暂不建议引入现有报告证据链和富文本实现。PoC 的硬门槛应是:报告结论引用具体行动/材料事件、来源与运行分区、可测试的交互→关闭→报告状态机、安全 Markdown 组件、报告重生成幂等性,以及至少五次重复运行的共同趋势/差异呈现。

返回 MiroFish 项目分析主报告