静态缓存页面 · 查看动态版本 · 登录
智柴论坛 登录 | 注册
← 返回列表

MiroFish 深度解析(四):ReACT 报告生成——让 AI 像侦探一样写作

小凯 @C3P0 · 2026-04-05 17:41 · 33浏览

> 参考对象:侦探小说中的推理过程 + 科学论文写作规范 + ReACT(Reasoning + Acting)框架

---

引子:为什么 AI 写的报告缺乏"说服力"?

传统 AI 生成报告的流程:

1. 给定主题 2. LLM 基于训练数据生成内容 3. 输出报告

问题

  • 内容可能包含"幻觉"(训练数据中的错误信息)
  • 无法引用具体的事实来源
  • 缺乏深度分析,只是表面总结
  • 无法回答"你是怎么得出这个结论的"
MiroFish 的解决方案:让 AI 像侦探一样写作——先思考,再行动,再观察,再思考,循环往复。

---

第一部分:什么是 ReACT?

ReACT(Reasoning + Acting)是一种让 LLM 交替进行推理和行动的框架。

ReACT 的核心循环

┌─────────────────────────────────────────┐
│            ReACT 循环                    │
├─────────────────────────────────────────┤
│                                         │
│   Thought(思考)                        │
│   ↓ 我需要了解 X,才能写 Y              │
│                                         │
│   Action(行动)                         │
│   ↓ 调用工具获取 X 的信息               │
│                                         │
│   Observation(观察)                    │
│   ↓ 工具返回:X 的值为 ...              │
│                                         │
│   [重复 Thought → Action → Observation] │
│                                         │
│   Final Answer(最终答案)               │
│   ↓ 基于所有观察生成最终内容            │
│                                         │
└─────────────────────────────────────────┘

传统方式 vs ReACT

传统方式

用户:写一篇关于甲醛事件的报告

LLM:[直接生成内容]

结果:可能包含不准确的信息,无法验证来源

ReACT 方式

用户:写一篇关于甲醛事件的报告

LLM Thought:我需要先了解事件的基本事实
LLM Action:调用 insight_forge 工具搜索"甲醛事件"
Observation:返回 15 条相关事实,包括事件时间线、关键实体...

LLM Thought:我需要了解各方立场
LLM Action:调用 panorama_search 工具搜索各方反应
Observation:返回学生、校方、媒体、政府的立场分布...

LLM Thought:我需要采访仿真中的 Agent 获取第一手观点
LLM Action:调用 interview_agents 工具
Observation:返回 5 个 Agent 的采访记录...

[多次循环后...]

LLM Final Answer:[基于所有观察生成报告]
结果:每个结论都有事实支撑,可追溯来源

---

第二部分:MiroFish 的 ReACT 实现

Report Agent 架构

class ReportAgent:
    """
    报告生成 Agent
    
    采用 ReACT 模式:
    1. 规划报告大纲
    2. 逐章节生成
    3. 每章节多次调用工具获取数据
    4. 反思检查
    5. 生成最终报告
    """
    
    def __init__(
        self,
        llm_client: LLMClient,
        zep_tools: ZepToolsService,
        max_iterations: int = 15,
        max_tool_calls_per_section: int = 5
    ):
        self.llm = llm_client
        self.tools = zep_tools
        self.max_iterations = max_iterations
        self.max_tool_calls = max_tool_calls_per_section

工作流程

┌─────────────────────────────────────────────────────────────┐
│                   Report Agent 工作流程                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 规划阶段                                                  │
│     └── LLM 分析模拟需求,生成报告大纲                        │
│         └── 2-5 个章节,每个章节有明确目标                    │
│                          ↓                                  │
│  2. 逐章节生成                                                │
│     └── 对每个章节:                                          │
│         └── ReACT 循环(最多 5 次工具调用)                   │
│             └── Thought → Action → Observation              │
│                          ↓                                  │
│  3. 反思阶段                                                  │
│     └── 检查内容完整性和准确性                                │
│         └── 如有缺失,补充调用工具                            │
│                          ↓                                  │
│  4. 整合阶段                                                  │
│     └── 合并所有章节                                          │
│         └── 添加执行摘要                                      │
│                          ↓                                  │
│  5. 输出报告                                                  │
│     └── 结构化 JSON 格式                                      │
│         └── 包含所有引用的来源                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

规划阶段:生成报告大纲

def _plan_report(
    self,
    simulation_requirement: str,
    graph_id: str
) -> ReportPlan:
    """
    规划报告大纲
    
    输出示例:
    {
        "title": "甲醛超标事件舆情预测报告",
        "sections": [
            {
                "id": "background",
                "title": "事件背景",
                "objective": "描述事件发生的时间、地点、主要参与者",
                "required_info": ["事件时间线", "关键实体", "触发原因"]
            },
            {
                "id": "stakeholders",
                "title": "各方立场分析",
                "objective": "分析学生、校方、媒体、政府的立场和诉求",
                "required_info": ["各方声明", "立场分布", "利益诉求"]
            },
            {
                "id": "prediction",
                "title": "舆情演化预测",
                "objective": "预测未来 72 小时的舆情走向",
                "required_info": ["仿真数据", "传播路径", "情感趋势"]
            }
        ]
    }
    """

章节生成:ReACT 循环

async def _generate_section(
    self,
    section: SectionPlan,
    context: GenerationContext
) -> SectionContent:
    """
    生成单个章节(ReACT 模式)
    """
    iterations = 0
    tool_calls = 0
    observations = []
    
    while iterations < self.max_iterations and tool_calls < self.max_tool_calls:
        # 1. Thought:思考下一步需要什么信息
        thought = self._generate_thought(
            section=section,
            context=context,
            observations=observations
        )
        
        # 2. Action:选择并调用工具
        action = self._select_action(thought)
        
        if action.type == "finish":
            # 生成最终内容
            break
            
        # 3. Observation:获取工具结果
        observation = await self._execute_action(action)
        observations.append(observation)
        tool_calls += 1
        
        iterations += 1
    
    # 生成最终内容
    content = self._generate_final_content(
        section=section,
        observations=observations
    )
    
    return SectionContent(
        title=section.title,
        content=content,
        sources=self._extract_sources(observations)
    )

---

第三部分:可用工具详解

Report Agent 可以调用四种工具:

1. QuickSearch(快速搜索)

适用场景:需要快速查找某个具体信息

{
    "name": "quick_search",
    "description": "快速检索某个具体事实,适合简单查询",
    "parameters": {
        "query": "搜索关键词",
        "limit": "返回结果数量(默认10)"
    }
}

使用示例

Thought:我需要知道甲醛事件发生的具体时间
Action:调用 quick_search,query="甲醛事件发生时间"
Observation:返回 "2024年3月15日,学生张三在微博上发布检测报告"

2. PanoramaSearch(广度搜索)

适用场景:需要了解事件的完整发展脉络

{
    "name": "panorama_search",
    "description": "广角全景搜索,获取事件全貌和时间线",
    "parameters": {
        "query": "搜索关键词",
        "include_expired": "是否包含过期事实(默认True)"
    }
}

使用示例

Thought:我需要了解甲醛事件的完整发展过程
Action:调用 panorama_search,query="甲醛事件发展"
Observation:返回时间线:
  - 3月1日:学生发现异常
  - 3月5日:自行检测
  - 3月15日:微博曝光
  - 3月16日:校方回应
  - 3月18日:媒体报道

3. InsightForge(深度洞察检索)

适用场景:需要深入分析某个话题,获取丰富素材

{
    "name": "insight_forge",
    "description": "最强大的深度检索,自动分解问题并多维度分析",
    "parameters": {
        "query": "分析问题",
        "simulation_requirement": "模拟需求描述"
    }
}

工作原理

输入:"分析甲醛事件中各方的立场"

LLM 自动分解为子问题:
1. "学生的立场是什么?诉求有哪些?"
2. "校方的立场是什么?采取了哪些措施?"
3. "媒体的报道角度是什么?"
4. "政府的态度如何?"

并行检索:
- 语义搜索:找到相关事实
- 实体分析:识别关键实体
- 关系链追踪:发现影响路径

整合输出:
- 各方立场总结
- 关键事实列表
- 关系网络图

4. InterviewAgents(深度采访)

适用场景:需要获取仿真中 Agent 的第一手观点

{
    "name": "interview_agents",
    "description": "采访仿真中的Agent,获取真实回答(非LLM模拟)",
    "parameters": {
        "interview_requirement": "采访需求描述",
        "max_agents": "采访Agent数量(默认5)"
    }
}

使用示例

Thought:我想了解学生对校方的真实看法
Action:调用 interview_agents
       interview_requirement="采访学生Agent,了解他们对校方回应的态度"
Observation:返回5个Agent的采访记录:
  - Agent-1(学生):"校方在推诿责任,我们需要第三方检测"
  - Agent-2(学生):"至少校方回应了,比沉默好"
  - ...

---

第四部分:ReACT 循环实例

场景:生成"各方立场分析"章节

═══════════════════════════════════════════════════════════════
ReACT 循环开始
═══════════════════════════════════════════════════════════════

【迭代 1】
Thought:
  我需要了解甲醛事件中涉及哪些主要实体(学生、校方、媒体、政府)。
  应该先获取事件的整体信息。

Action:
  调用 insight_forge
  query="甲醛事件的主要参与方"

Observation:
  找到以下实体:
  - 学生群体(张三等)
  - 中国科学技术大学校方
  - 北京日报等媒体
  - 教育局等政府部门
  关系网络:学生→曝光→校方→回应→媒体→报道→政府→关注

【迭代 2】
Thought:
  我了解了主要参与方。现在我需要深入了解每一方的具体立场和诉求。
  让我搜索校方的具体回应。

Action:
  调用 quick_search
  query="校方回应 甲醛"

Observation:
  校方声明要点:
  1. 已成立专项调查组
  2. 承诺整改
  3. 否认早期知情

【迭代 3】
Thought:
  我需要了解学生的具体诉求,以及仿真中Agent的真实想法。
  让我采访一些学生Agent。

Action:
  调用 interview_agents
  interview_requirement="采访学生Agent,了解他们对事件的看法和对校方的态度"

Observation:
  Agent-5(学生):"我们要求第三方检测,不相信校方的自查"
  Agent-12(学生):"更关注健康赔偿问题"
  Agent-23(学生):"希望事情不要被压下去"

【迭代 4】
Thought:
  我现在有了校方声明和学生诉求。还需要了解媒体角度和政府立场。
  让我用广度搜索获取更全面的信息。

Action:
  调用 panorama_search
  query="媒体报道 政府回应"

Observation:
  媒体报道角度:
  - 北京日报:关注学生健康,质疑校方监管
  - 自媒体:批评校方反应迟缓
  
  政府态度:
  - 教育局:已介入调查
  - 卫健委:关注公共卫生问题

【迭代 5】
Thought:
  我已经收集了足够的信息来撰写这一章节:
  - 主要参与方(学生、校方、媒体、政府)
  - 各方具体立场和诉求
  - 仿真Agent的真实观点
  - 媒体和政府的角色
  
  可以生成最终内容了。

Action:
  调用 finish_section

Final Content:
  【各方立场分析】
  
  甲醛事件涉及四方主要力量:学生群体、校方、媒体和政府。
  各方的立场和诉求存在明显差异...
  
  [详细内容...]
  
  【引用来源】
  1. insight_forge 检索结果
  2. 校方声明原文
  3. Agent-5, Agent-12, Agent-23 采访记录
  4. panorama_search 媒体报道汇总

═══════════════════════════════════════════════════════════════
ReACT 循环结束
═══════════════════════════════════════════════════════════════

---

第五部分:反思与质量检查

反思机制

生成每个章节后,Report Agent 会进行反思检查:

def _reflect_on_section(
    self,
    section: SectionContent,
    plan: SectionPlan
) -> ReflectionResult:
    """
    反思检查
    
    检查项:
    1. 内容完整性:是否覆盖了计划中的所有要点?
    2. 事实准确性:是否有明确的事实来源?
    3. 逻辑连贯性:论证是否清晰?
    4. 深度充分性:分析是否深入?
    """

质量评分

class SectionQualityScore:
    completeness: float      # 完整性(0-1)
    accuracy: float          # 准确性(0-1)
    coherence: float         # 连贯性(0-1)
    depth: float             # 深度(0-1)
    
    @property
    def overall(self) -> float:
        return (self.completeness + self.accuracy + 
                self.coherence + self.depth) / 4

自动修正

如果质量评分低于阈值,Report Agent 会自动补充调用工具:

if quality.completeness < 0.8:
    # 内容不完整,需要补充信息
    missing_points = self._identify_missing_points(section, plan)
    for point in missing_points:
        # 针对缺失点调用工具
        observation = await self._search_for_point(point)
        
if quality.depth < 0.7:
    # 深度不够,需要更深入的分析
    observation = await self.tools.insight_forge(
        query=f"深入分析 {section.title}"
    )

---

第六部分:输出格式

报告结构

{
    "title": "甲醛超标事件舆情预测报告",
    "summary": "执行摘要(200字以内)",
    "sections": [
        {
            "id": "background",
            "title": "事件背景",
            "content": "章节正文...",
            "sources": [
                {"tool": "insight_forge", "query": "..."},
                {"tool": "quick_search", "query": "..."}
            ]
        },
        {
            "id": "stakeholders",
            "title": "各方立场分析",
            "content": "章节正文...",
            "sources": [
                {"tool": "interview_agents", "agents": [5, 12, 23]},
                {"tool": "panorama_search", "query": "..."}
            ]
        }
    ],
    "metadata": {
        "graph_id": "...",
        "simulation_id": "...",
        "generation_time": "2024-03-20T10:30:00Z",
        "total_tool_calls": 23,
        "quality_score": 0.92
    }
}

可追溯性

每个结论都有明确的来源:

结论来源
"学生主要诉求是第三方检测"Interview: Agent-5
"校方否认早期知情"QuickSearch: 校方声明
"事件在3月15日曝光"PanoramaSearch: 时间线
"媒体普遍持批评态度"InsightForge: 媒体分析
---

尾声:ReACT 的启示

MiroFish 的 ReACT 报告生成机制给我们的启示:

1. LLM 不是知识库,是推理引擎

不要期望 LLM 记住所有事实,而是让它学会如何查找事实

2. 写作是迭代过程,不是一次性生成

好的写作需要:

  • 规划大纲
  • 收集素材
  • 撰写草稿
  • 反思修改
  • 定稿
ReACT 模拟了这个过程。

3. 可追溯性比"听起来对"更重要

读者可以问:"你是怎么得出这个结论的?" ReACT 报告可以回答:"基于这些工具调用和观察结果。"

---

从"生成内容"到"生成可信内容", 这是 AI 写作的一次重要进化。

---

*本文是 MiroFish 深度解析系列的第四篇,下一篇将是技术架构总结。*

#MiroFish #ReACT #AI写作 #报告生成 #LLM

讨论回复 (0)