diff --git a/backend/app/services/report_agent.py b/backend/app/services/report_agent.py index 3a93cb9..dfafeb5 100644 --- a/backend/app/services/report_agent.py +++ b/backend/app/services/report_agent.py @@ -466,21 +466,398 @@ class Report: } +# ═══════════════════════════════════════════════════════════════ +# Prompt 模板常量 +# ═══════════════════════════════════════════════════════════════ + +# ── 工具描述 ── + +TOOL_DESC_INSIGHT_FORGE = """\ +【深度洞察检索 - 强大的检索工具】 +这是我们强大的检索函数,专为深度分析设计。它会: +1. 自动将你的问题分解为多个子问题 +2. 从多个维度检索模拟图谱中的信息 +3. 整合语义搜索、实体分析、关系链追踪的结果 +4. 返回最全面、最深度的检索内容 + +【使用场景】 +- 需要深入分析某个话题 +- 需要了解事件的多个方面 +- 需要获取支撑报告章节的丰富素材 + +【返回内容】 +- 相关事实原文(可直接引用) +- 核心实体洞察 +- 关系链分析""" + +TOOL_DESC_PANORAMA_SEARCH = """\ +【广度搜索 - 获取全貌视图】 +这个工具用于获取模拟结果的完整全貌,特别适合了解事件演变过程。它会: +1. 获取所有相关节点和关系 +2. 区分当前有效的事实和历史/过期的事实 +3. 帮助你了解舆情是如何演变的 + +【使用场景】 +- 需要了解事件的完整发展脉络 +- 需要对比不同阶段的舆情变化 +- 需要获取全面的实体和关系信息 + +【返回内容】 +- 当前有效事实(模拟最新结果) +- 历史/过期事实(演变记录) +- 所有涉及的实体""" + +TOOL_DESC_QUICK_SEARCH = """\ +【简单搜索 - 快速检索】 +轻量级的快速检索工具,适合简单、直接的信息查询。 + +【使用场景】 +- 需要快速查找某个具体信息 +- 需要验证某个事实 +- 简单的信息检索 + +【返回内容】 +- 与查询最相关的事实列表""" + +TOOL_DESC_INTERVIEW_AGENTS = """\ +【深度采访 - 真实Agent采访(双平台)】 +调用OASIS模拟环境的采访API,对正在运行的模拟Agent进行真实采访! +这不是LLM模拟,而是调用真实的采访接口获取模拟Agent的原始回答。 +默认在Twitter和Reddit两个平台同时采访,获取更全面的观点。 + +功能流程: +1. 自动读取人设文件,了解所有模拟Agent +2. 智能选择与采访主题最相关的Agent(如学生、媒体、官方等) +3. 自动生成采访问题 +4. 调用 /api/simulation/interview/batch 接口在双平台进行真实采访 +5. 整合所有采访结果,提供多视角分析 + +【使用场景】 +- 需要从不同角色视角了解事件看法(学生怎么看?媒体怎么看?官方怎么说?) +- 需要收集多方意见和立场 +- 需要获取模拟Agent的真实回答(来自OASIS模拟环境) +- 想让报告更生动,包含"采访实录" + +【返回内容】 +- 被采访Agent的身份信息 +- 各Agent在Twitter和Reddit两个平台的采访回答 +- 关键引言(可直接引用) +- 采访摘要和观点对比 + +【重要】需要OASIS模拟环境正在运行才能使用此功能!""" + +# ── 大纲规划 prompt ── + +PLAN_SYSTEM_PROMPT = """\ +你是一个「未来预测报告」的撰写专家,拥有对模拟世界的「上帝视角」——你可以洞察模拟中每一位Agent的行为、言论和互动。 + +【核心理念】 +我们构建了一个模拟世界,并向其中注入了特定的「模拟需求」作为变量。模拟世界的演化结果,就是对未来可能发生情况的预测。你正在观察的不是"实验数据",而是"未来的预演"。 + +【你的任务】 +撰写一份「未来预测报告」,回答: +1. 在我们设定的条件下,未来发生了什么? +2. 各类Agent(人群)是如何反应和行动? +3. 这个模拟揭示了哪些值得关注的未来趋势和风险? + +【报告定位】 +- ✅ 这是一份基于模拟的未来预测报告,揭示"如果这样,未来会怎样" +- ✅ 聚焦于预测结果:事件走向、群体反应、涌现现象、潜在风险 +- ✅ 模拟世界中的Agent言行就是对未来人群行为的预测 +- ❌ 不是对现实世界现状的分析 +- ❌ 不是泛泛而谈的舆情综述 + +【章节数量限制】 +- 最少2个章节,最多5个章节 +- 不需要子章节,每个章节直接撰写完整内容 +- 内容要精炼,聚焦于核心预测发现 +- 章节结构由你根据预测结果自主设计 + +请输出JSON格式的报告大纲,格式如下: +{ + "title": "报告标题", + "summary": "报告摘要(一句话概括核心预测发现)", + "sections": [ + { + "title": "章节标题", + "description": "章节内容描述" + } + ] +} + +注意:sections数组最少2个,最多5个元素!""" + +PLAN_USER_PROMPT_TEMPLATE = """\ +【预测场景设定】 +我们向模拟世界注入的变量(模拟需求):{simulation_requirement} + +【模拟世界规模】 +- 参与模拟的实体数量: {total_nodes} +- 实体间产生的关系数量: {total_edges} +- 实体类型分布: {entity_types} +- 活跃Agent数量: {total_entities} + +【模拟预测到的部分未来事实样本】 +{related_facts_json} + +请以「上帝视角」审视这个未来预演: +1. 在我们设定的条件下,未来呈现出了什么样的状态? +2. 各类人群(Agent)是如何反应和行动的? +3. 这个模拟揭示了哪些值得关注的未来趋势? + +根据预测结果,设计最合适的报告章节结构。 + +【再次提醒】报告章节数量:最少2个,最多5个,内容要精炼聚焦于核心预测发现。""" + +# ── 章节生成 prompt ── + +SECTION_SYSTEM_PROMPT_TEMPLATE = """\ +你是一个「未来预测报告」的撰写专家,正在撰写报告的一个章节。 + +报告标题: {report_title} +报告摘要: {report_summary} +预测场景(模拟需求): {simulation_requirement} + +当前要撰写的章节: {section_title} + +═══════════════════════════════════════════════════════════════ +【核心理念】 +═══════════════════════════════════════════════════════════════ + +模拟世界是对未来的预演。我们向模拟世界注入了特定条件(模拟需求), +模拟中Agent的行为和互动,就是对未来人群行为的预测。 + +你的任务是: +- 揭示在设定条件下,未来发生了什么 +- 预测各类人群(Agent)是如何反应和行动的 +- 发现值得关注的未来趋势、风险和机会 + +❌ 不要写成对现实世界现状的分析 +✅ 要聚焦于"未来会怎样"——模拟结果就是预测的未来 + +═══════════════════════════════════════════════════════════════ +【最重要的规则 - 必须遵守】 +═══════════════════════════════════════════════════════════════ + +1. 【必须调用工具观察模拟世界】 + - 你正在以「上帝视角」观察未来的预演 + - 所有内容必须来自模拟世界中发生的事件和Agent言行 + - 禁止使用你自己的知识来编写报告内容 + - 每个章节至少调用3次工具(最多5次)来观察模拟的世界,它代表了未来 + +2. 【必须引用Agent的原始言行】 + - Agent的发言和行为是对未来人群行为的预测 + - 在报告中使用引用格式展示这些预测,例如: + > "某类人群会表示:原文内容..." + - 这些引用是模拟预测的核心证据 + +3. 【忠实呈现预测结果】 + - 报告内容必须反映模拟世界中的代表未来的模拟结果 + - 不要添加模拟中不存在的信息 + - 如果某方面信息不足,如实说明 + +═══════════════════════════════════════════════════════════════ +【⚠️ 格式规范 - 极其重要!】 +═══════════════════════════════════════════════════════════════ + +【一个章节 = 最小内容单位】 +- 每个章节是报告的最小分块单位 +- ❌ 禁止在章节内使用任何 Markdown 标题(#、##、###、#### 等) +- ❌ 禁止在内容开头添加章节主标题 +- ✅ 章节标题由系统自动添加,你只需撰写纯正文内容 +- ✅ 使用**粗体**、段落分隔、引用、列表来组织内容,但不要用标题 + +【正确示例】 +``` +本章节分析了事件的舆论传播态势。通过对模拟数据的深入分析,我们发现... + +**首发引爆阶段** + +微博作为舆情的第一现场,承担了信息首发的核心功能: + +> "微博贡献了68%的首发声量..." + +**情绪放大阶段** + +抖音平台进一步放大了事件影响力: + +- 视觉冲击力强 +- 情绪共鸣度高 +``` + +【错误示例】 +``` +## 执行摘要 ← 错误!不要添加任何标题 +### 一、首发阶段 ← 错误!不要用###分小节 +#### 1.1 详细分析 ← 错误!不要用####细分 + +本章节分析了... +``` + +═══════════════════════════════════════════════════════════════ +【可用检索工具】(每章节调用3-5次) +═══════════════════════════════════════════════════════════════ + +{tools_description} + +【工具使用建议 - 请混合使用不同工具,不要只用一种】 +- insight_forge: 深度洞察分析,自动分解问题并多维度检索事实和关系 +- panorama_search: 广角全景搜索,了解事件全貌、时间线和演变过程 +- quick_search: 快速验证某个具体信息点 +- interview_agents: 采访模拟Agent,获取不同角色的第一人称观点和真实反应 + +═══════════════════════════════════════════════════════════════ +【ReACT工作流程】 +═══════════════════════════════════════════════════════════════ + +1. Thought: [分析需要什么信息,规划检索策略] +2. Action: [调用一个工具获取信息](每轮只能调用一个工具!) + + {{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}} + +3. Observation: [系统返回工具结果] +4. 重复步骤1-3,直到收集到足够信息 +5. Final Answer: [基于检索结果撰写章节内容] + +⚠️ 重要规则: +- 每轮只能调用一个工具,不要在一次回复中放多个 +- 当你认为信息足够时,必须以 "Final Answer:" 开头输出最终内容 + +═══════════════════════════════════════════════════════════════ +【章节内容要求】 +═══════════════════════════════════════════════════════════════ + +1. 内容必须基于工具检索到的模拟数据 +2. 大量引用原文来展示模拟效果 +3. 使用Markdown格式(但禁止使用标题): + - 使用 **粗体文字** 标记重点(代替子标题) + - 使用列表(-或1.2.3.)组织要点 + - 使用空行分隔不同段落 + - ❌ 禁止使用 #、##、###、#### 等任何标题语法 +4. 【引用格式规范 - 必须单独成段】 + 引用必须独立成段,前后各有一个空行,不能混在段落中: + + ✅ 正确格式: + ``` + 校方的回应被认为缺乏实质内容。 + + > "校方的应对模式在瞬息万变的社交媒体环境中显得僵化和迟缓。" + + 这一评价反映了公众的普遍不满。 + ``` + + ❌ 错误格式: + ``` + 校方的回应被认为缺乏实质内容。> "校方的应对模式..." 这一评价反映了... + ``` +5. 保持与其他章节的逻辑连贯性 +6. 【避免重复】仔细阅读下方已完成的章节内容,不要重复描述相同的信息 +7. 【再次强调】不要添加任何标题!用**粗体**代替小节标题""" + +SECTION_USER_PROMPT_TEMPLATE = """\ +已完成的章节内容(请仔细阅读,避免重复): +{previous_content} + +═══════════════════════════════════════════════════════════════ +【当前任务】撰写章节: {section_title} +═══════════════════════════════════════════════════════════════ + +【重要提醒】 +1. 仔细阅读上方已完成的章节,避免重复相同的内容! +2. 开始前必须先调用工具获取模拟数据 +3. 请混合使用不同工具,不要只用一种 +4. 报告内容必须来自检索结果,不要使用自己的知识 + +【⚠️ 格式警告 - 必须遵守】 +- ❌ 不要写任何标题(#、##、###、####都不行) +- ❌ 不要写"{section_title}"作为开头 +- ✅ 章节标题由系统自动添加 +- ✅ 直接写正文,用**粗体**代替小节标题 + +请开始: +1. 首先思考(Thought)这个章节需要什么信息 +2. 然后调用工具(Action)获取模拟数据 +3. 收集足够信息后输出 Final Answer(纯正文,无任何标题)""" + +# ── ReACT 循环内消息模板 ── + +REACT_OBSERVATION_TEMPLATE = """\ +Observation(检索结果): + +═══ 工具 {tool_name} 返回 ═══ +{result} + +═══════════════════════════════════════════════════════════════ +已调用工具 {tool_calls_count}/{max_tool_calls} 次(已用: {used_tools_str}){unused_hint} +- 如果信息充分:以 "Final Answer:" 开头输出章节内容(必须引用上述原文) +- 如果需要更多信息:调用一个工具继续检索 +═══════════════════════════════════════════════════════════════""" + +REACT_INSUFFICIENT_TOOLS_MSG = ( + "【注意】你只调用了{tool_calls_count}次工具,至少需要{min_tool_calls}次。" + "请再调用工具获取更多模拟数据,然后再输出 Final Answer。{unused_hint}" +) + +REACT_INSUFFICIENT_TOOLS_MSG_ALT = ( + "当前只调用了 {tool_calls_count} 次工具,至少需要 {min_tool_calls} 次。" + "请调用工具获取模拟数据。{unused_hint}" +) + +REACT_TOOL_LIMIT_MSG = ( + "工具调用次数已达上限({tool_calls_count}/{max_tool_calls}),不能再调用工具。" + '请立即基于已获取的信息,以 "Final Answer:" 开头输出章节内容。' +) + +REACT_UNUSED_TOOLS_HINT = "\n💡 你还没有使用过: {unused_list},建议尝试不同工具获取多角度信息" + +REACT_FORCE_FINAL_MSG = "已达到工具调用限制,请直接输出 Final Answer: 并生成章节内容。" + +# ── Chat prompt ── + +CHAT_SYSTEM_PROMPT_TEMPLATE = """\ +你是一个简洁高效的模拟预测助手。 + +【背景】 +预测条件: {simulation_requirement} + +【已生成的分析报告】 +{report_content} + +【规则】 +1. 优先基于上述报告内容回答问题 +2. 直接回答问题,避免冗长的思考论述 +3. 仅在报告内容不足以回答时,才调用工具检索更多数据 +4. 回答要简洁、清晰、有条理 + +【可用工具】(仅在需要时使用,最多调用1-2次) +{tools_description} + +【工具调用格式】 + +{{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}} + + +【回答风格】 +- 简洁直接,不要长篇大论 +- 使用 > 格式引用关键内容 +- 优先给出结论,再解释原因""" + +CHAT_OBSERVATION_SUFFIX = "\n\n请简洁回答问题。" + + +# ═══════════════════════════════════════════════════════════════ +# ReportAgent 主类 +# ═══════════════════════════════════════════════════════════════ + + class ReportAgent: """ Report Agent - 模拟报告生成Agent - + 采用ReACT(Reasoning + Acting)模式: 1. 规划阶段:分析模拟需求,规划报告目录结构 2. 生成阶段:逐章节生成内容,每章节可多次调用工具获取信息 3. 反思阶段:检查内容完整性和准确性 - - 【核心检索工具 - 优化后】 - - insight_forge: 深度洞察检索(强大,自动分解问题,多维度检索) - - panorama_search: 广度搜索(获取全貌,包括历史/过期内容) - - quick_search: 简单搜索(快速检索) - - 【重要】Report Agent必须优先调用工具获取模拟数据,而非使用自身知识! """ # 最大工具调用次数(每个章节) @@ -528,31 +905,11 @@ class ReportAgent: logger.info(f"ReportAgent 初始化完成: graph_id={graph_id}, simulation_id={simulation_id}") def _define_tools(self) -> Dict[str, Dict[str, Any]]: - """ - 定义可用工具 - - 【重要】这三个工具是专门为从模拟图谱中检索信息设计的, - 必须优先使用这些工具获取数据,而不是使用LLM自身的知识! - """ + """定义可用工具""" return { "insight_forge": { "name": "insight_forge", - "description": """【深度洞察检索 - 强大的检索工具】 -这是我们强大的检索函数,专为深度分析设计。它会: -1. 自动将你的问题分解为多个子问题 -2. 从多个维度检索模拟图谱中的信息 -3. 整合语义搜索、实体分析、关系链追踪的结果 -4. 返回最全面、最深度的检索内容 - -【使用场景】 -- 需要深入分析某个话题 -- 需要了解事件的多个方面 -- 需要获取支撑报告章节的丰富素材 - -【返回内容】 -- 相关事实原文(可直接引用) -- 核心实体洞察 -- 关系链分析""", + "description": TOOL_DESC_INSIGHT_FORGE, "parameters": { "query": "你想深入分析的问题或话题", "report_context": "当前报告章节的上下文(可选,有助于生成更精准的子问题)" @@ -560,21 +917,7 @@ class ReportAgent: }, "panorama_search": { "name": "panorama_search", - "description": """【广度搜索 - 获取全貌视图】 -这个工具用于获取模拟结果的完整全貌,特别适合了解事件演变过程。它会: -1. 获取所有相关节点和关系 -2. 区分当前有效的事实和历史/过期的事实 -3. 帮助你了解舆情是如何演变的 - -【使用场景】 -- 需要了解事件的完整发展脉络 -- 需要对比不同阶段的舆情变化 -- 需要获取全面的实体和关系信息 - -【返回内容】 -- 当前有效事实(模拟最新结果) -- 历史/过期事实(演变记录) -- 所有涉及的实体""", + "description": TOOL_DESC_PANORAMA_SEARCH, "parameters": { "query": "搜索查询,用于相关性排序", "include_expired": "是否包含过期/历史内容(默认True)" @@ -582,16 +925,7 @@ class ReportAgent: }, "quick_search": { "name": "quick_search", - "description": """【简单搜索 - 快速检索】 -轻量级的快速检索工具,适合简单、直接的信息查询。 - -【使用场景】 -- 需要快速查找某个具体信息 -- 需要验证某个事实 -- 简单的信息检索 - -【返回内容】 -- 与查询最相关的事实列表""", + "description": TOOL_DESC_QUICK_SEARCH, "parameters": { "query": "搜索查询字符串", "limit": "返回结果数量(可选,默认10)" @@ -599,31 +933,7 @@ class ReportAgent: }, "interview_agents": { "name": "interview_agents", - "description": """【深度采访 - 真实Agent采访(双平台)】 -调用OASIS模拟环境的采访API,对正在运行的模拟Agent进行真实采访! -这不是LLM模拟,而是调用真实的采访接口获取模拟Agent的原始回答。 -默认在Twitter和Reddit两个平台同时采访,获取更全面的观点。 - -功能流程: -1. 自动读取人设文件,了解所有模拟Agent -2. 智能选择与采访主题最相关的Agent(如学生、媒体、官方等) -3. 自动生成采访问题 -4. 调用 /api/simulation/interview/batch 接口在双平台进行真实采访 -5. 整合所有采访结果,提供多视角分析 - -【使用场景】 -- 需要从不同角色视角了解事件看法(学生怎么看?媒体怎么看?官方怎么说?) -- 需要收集多方意见和立场 -- 需要获取模拟Agent的真实回答(来自OASIS模拟环境) -- 想让报告更生动,包含"采访实录" - -【返回内容】 -- 被采访Agent的身份信息 -- 各Agent在Twitter和Reddit两个平台的采访回答 -- 关键引言(可直接引用) -- 采访摘要和观点对比 - -【重要】需要OASIS模拟环境正在运行才能使用此功能!""", + "description": TOOL_DESC_INTERVIEW_AGENTS, "parameters": { "interview_topic": "采访主题或需求描述(如:'了解学生对宿舍甲醛事件的看法')", "max_agents": "最多采访的Agent数量(可选,默认5)" @@ -646,10 +956,7 @@ class ReportAgent: logger.info(f"执行工具: {tool_name}, 参数: {parameters}") try: - # ========== 核心检索工具(优化后) ========== - if tool_name == "insight_forge": - # 深度洞察检索 - 强大的工具 query = parameters.get("query", "") ctx = parameters.get("report_context", "") or report_context result = self.zep_tools.insight_forge( @@ -821,65 +1128,15 @@ class ReportAgent: if progress_callback: progress_callback("planning", 30, "正在生成报告大纲...") - # 构建规划prompt - system_prompt = """你是一个「未来预测报告」的撰写专家,拥有对模拟世界的「上帝视角」——你可以洞察模拟中每一位Agent的行为、言论和互动。 - -【核心理念】 -我们构建了一个模拟世界,并向其中注入了特定的「模拟需求」作为变量。模拟世界的演化结果,就是对未来可能发生情况的预测。你正在观察的不是"实验数据",而是"未来的预演"。 - -【你的任务】 -撰写一份「未来预测报告」,回答: -1. 在我们设定的条件下,未来发生了什么? -2. 各类Agent(人群)是如何反应和行动? -3. 这个模拟揭示了哪些值得关注的未来趋势和风险? - -【报告定位】 -- ✅ 这是一份基于模拟的未来预测报告,揭示"如果这样,未来会怎样" -- ✅ 聚焦于预测结果:事件走向、群体反应、涌现现象、潜在风险 -- ✅ 模拟世界中的Agent言行就是对未来人群行为的预测 -- ❌ 不是对现实世界现状的分析 -- ❌ 不是泛泛而谈的舆情综述 - -【章节数量限制】 -- 最少2个章节,最多5个章节 -- 不需要子章节,每个章节直接撰写完整内容 -- 内容要精炼,聚焦于核心预测发现 -- 章节结构由你根据预测结果自主设计 - -请输出JSON格式的报告大纲,格式如下: -{ - "title": "报告标题", - "summary": "报告摘要(一句话概括核心预测发现)", - "sections": [ - { - "title": "章节标题", - "description": "章节内容描述" - } - ] -} - -注意:sections数组最少2个,最多5个元素!""" - - user_prompt = f"""【预测场景设定】 -我们向模拟世界注入的变量(模拟需求):{self.simulation_requirement} - -【模拟世界规模】 -- 参与模拟的实体数量: {context.get('graph_statistics', {}).get('total_nodes', 0)} -- 实体间产生的关系数量: {context.get('graph_statistics', {}).get('total_edges', 0)} -- 实体类型分布: {list(context.get('graph_statistics', {}).get('entity_types', {}).keys())} -- 活跃Agent数量: {context.get('total_entities', 0)} - -【模拟预测到的部分未来事实样本】 -{json.dumps(context.get('related_facts', [])[:10], ensure_ascii=False, indent=2)} - -请以「上帝视角」审视这个未来预演: -1. 在我们设定的条件下,未来呈现出了什么样的状态? -2. 各类人群(Agent)是如何反应和行动的? -3. 这个模拟揭示了哪些值得关注的未来趋势? - -根据预测结果,设计最合适的报告章节结构。 - -【再次提醒】报告章节数量:最少2个,最多5个,内容要精炼聚焦于核心预测发现。""" + system_prompt = PLAN_SYSTEM_PROMPT + 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), + ) try: response = self.llm.chat_json( @@ -960,153 +1217,13 @@ class ReportAgent: if self.report_logger: self.report_logger.log_section_start(section.title, section_index) - # 构建系统prompt - 优化后强调工具使用和引用原文 - # 确定当前章节的标题级别 - section_level = 2 # 默认为二级标题(##) - sub_heading_level = 3 # 子标题使用三级(###) - sub_sub_heading_level = 4 # 更小的子标题使用四级(####) - - system_prompt = f"""你是一个「未来预测报告」的撰写专家,正在撰写报告的一个章节。 - -报告标题: {outline.title} -报告摘要: {outline.summary} -预测场景(模拟需求): {self.simulation_requirement} - -当前要撰写的章节: {section.title} - -═══════════════════════════════════════════════════════════════ -【核心理念】 -═══════════════════════════════════════════════════════════════ - -模拟世界是对未来的预演。我们向模拟世界注入了特定条件(模拟需求), -模拟中Agent的行为和互动,就是对未来人群行为的预测。 - -你的任务是: -- 揭示在设定条件下,未来发生了什么 -- 预测各类人群(Agent)是如何反应和行动的 -- 发现值得关注的未来趋势、风险和机会 - -❌ 不要写成对现实世界现状的分析 -✅ 要聚焦于"未来会怎样"——模拟结果就是预测的未来 - -═══════════════════════════════════════════════════════════════ -【最重要的规则 - 必须遵守】 -═══════════════════════════════════════════════════════════════ - -1. 【必须调用工具观察模拟世界】 - - 你正在以「上帝视角」观察未来的预演 - - 所有内容必须来自模拟世界中发生的事件和Agent言行 - - 禁止使用你自己的知识来编写报告内容 - - 每个章节至少调用3次工具(最多5次)来观察模拟的世界,它代表了未来 - -2. 【必须引用Agent的原始言行】 - - Agent的发言和行为是对未来人群行为的预测 - - 在报告中使用引用格式展示这些预测,例如: - > "某类人群会表示:原文内容..." - - 这些引用是模拟预测的核心证据 - -3. 【忠实呈现预测结果】 - - 报告内容必须反映模拟世界中的代表未来的模拟结果 - - 不要添加模拟中不存在的信息 - - 如果某方面信息不足,如实说明 - -═══════════════════════════════════════════════════════════════ -【⚠️ 格式规范 - 极其重要!】 -═══════════════════════════════════════════════════════════════ - -【一个章节 = 最小内容单位】 -- 每个章节是报告的最小分块单位 -- ❌ 禁止在章节内使用任何 Markdown 标题(#、##、###、#### 等) -- ❌ 禁止在内容开头添加章节主标题 -- ✅ 章节标题由系统自动添加,你只需撰写纯正文内容 -- ✅ 使用**粗体**、段落分隔、引用、列表来组织内容,但不要用标题 - -【正确示例】 -``` -本章节分析了事件的舆论传播态势。通过对模拟数据的深入分析,我们发现... - -**首发引爆阶段** - -微博作为舆情的第一现场,承担了信息首发的核心功能: - -> "微博贡献了68%的首发声量..." - -**情绪放大阶段** - -抖音平台进一步放大了事件影响力: - -- 视觉冲击力强 -- 情绪共鸣度高 -``` - -【错误示例】 -``` -## 执行摘要 ← 错误!不要添加任何标题 -### 一、首发阶段 ← 错误!不要用###分小节 -#### 1.1 详细分析 ← 错误!不要用####细分 - -本章节分析了... -``` - -═══════════════════════════════════════════════════════════════ -【可用检索工具】(每章节调用3-5次) -═══════════════════════════════════════════════════════════════ - -{self._get_tools_description()} - -【工具使用建议 - 请混合使用不同工具,不要只用一种】 -- insight_forge: 深度洞察分析,自动分解问题并多维度检索事实和关系 -- panorama_search: 广角全景搜索,了解事件全貌、时间线和演变过程 -- quick_search: 快速验证某个具体信息点 -- interview_agents: 采访模拟Agent,获取不同角色的第一人称观点和真实反应 - -═══════════════════════════════════════════════════════════════ -【ReACT工作流程】 -═══════════════════════════════════════════════════════════════ - -1. Thought: [分析需要什么信息,规划检索策略] -2. Action: [调用一个工具获取信息](每轮只能调用一个工具!) - - {{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}} - -3. Observation: [系统返回工具结果] -4. 重复步骤1-3,直到收集到足够信息 -5. Final Answer: [基于检索结果撰写章节内容] - -⚠️ 重要规则: -- 每轮只能调用一个工具,不要在一次回复中放多个 -- 当你认为信息足够时,必须以 "Final Answer:" 开头输出最终内容 - -═══════════════════════════════════════════════════════════════ -【章节内容要求】 -═══════════════════════════════════════════════════════════════ - -1. 内容必须基于工具检索到的模拟数据 -2. 大量引用原文来展示模拟效果 -3. 使用Markdown格式(但禁止使用标题): - - 使用 **粗体文字** 标记重点(代替子标题) - - 使用列表(-或1.2.3.)组织要点 - - 使用空行分隔不同段落 - - ❌ 禁止使用 #、##、###、#### 等任何标题语法 -4. 【引用格式规范 - 必须单独成段】 - 引用必须独立成段,前后各有一个空行,不能混在段落中: - - ✅ 正确格式: - ``` - 校方的回应被认为缺乏实质内容。 - - > "校方的应对模式在瞬息万变的社交媒体环境中显得僵化和迟缓。" - - 这一评价反映了公众的普遍不满。 - ``` - - ❌ 错误格式: - ``` - 校方的回应被认为缺乏实质内容。> "校方的应对模式..." 这一评价反映了... - ``` -5. 保持与其他章节的逻辑连贯性 -6. 【避免重复】仔细阅读下方已完成的章节内容,不要重复描述相同的信息 -7. 【再次强调】不要添加任何标题!用**粗体**代替小节标题""" + system_prompt = SECTION_SYSTEM_PROMPT_TEMPLATE.format( + report_title=outline.title, + report_summary=outline.summary, + simulation_requirement=self.simulation_requirement, + section_title=section.title, + tools_description=self._get_tools_description(), + ) # 构建用户prompt - 每个已完成章节各传入最大4000字 if previous_sections: @@ -1119,29 +1236,10 @@ class ReportAgent: else: previous_content = "(这是第一个章节)" - user_prompt = f"""已完成的章节内容(请仔细阅读,避免重复): -{previous_content} - -═══════════════════════════════════════════════════════════════ -【当前任务】撰写章节: {section.title} -═══════════════════════════════════════════════════════════════ - -【重要提醒】 -1. 仔细阅读上方已完成的章节,避免重复相同的内容! -2. 开始前必须先调用工具获取模拟数据 -3. 请混合使用不同工具,不要只用一种 -4. 报告内容必须来自检索结果,不要使用自己的知识 - -【⚠️ 格式警告 - 必须遵守】 -- ❌ 不要写任何标题(#、##、###、####都不行) -- ❌ 不要写"{section.title}"作为开头 -- ✅ 章节标题由系统自动添加 -- ✅ 直接写正文,用**粗体**代替小节标题 - -请开始: -1. 首先思考(Thought)这个章节需要什么信息 -2. 然后调用工具(Action)获取模拟数据 -3. 收集足够信息后输出 Final Answer(纯正文,无任何标题)""" + user_prompt = SECTION_USER_PROMPT_TEMPLATE.format( + previous_content=previous_content, + section_title=section.title, + ) messages = [ {"role": "system", "content": system_prompt}, @@ -1211,7 +1309,11 @@ class ReportAgent: unused_hint = f"(这些工具还未使用,推荐用一下他们: {', '.join(unused_tools)})" if unused_tools else "" messages.append({ "role": "user", - "content": f"【注意】你只调用了{tool_calls_count}次工具,至少需要{min_tool_calls}次。请再调用工具获取更多模拟数据,然后再输出 Final Answer。{unused_hint}" + "content": REACT_INSUFFICIENT_TOOLS_MSG.format( + tool_calls_count=tool_calls_count, + min_tool_calls=min_tool_calls, + unused_hint=unused_hint, + ), }) continue @@ -1235,7 +1337,10 @@ class ReportAgent: messages.append({"role": "assistant", "content": response}) messages.append({ "role": "user", - "content": f"工具调用次数已达上限({tool_calls_count}/{self.MAX_TOOL_CALLS_PER_SECTION}),不能再调用工具。请立即基于已获取的信息,以 \"Final Answer:\" 开头输出章节内容。" + "content": REACT_TOOL_LIMIT_MSG.format( + tool_calls_count=tool_calls_count, + max_tool_calls=self.MAX_TOOL_CALLS_PER_SECTION, + ), }) continue @@ -1275,22 +1380,19 @@ class ReportAgent: unused_tools = all_tools - used_tools unused_hint = "" if unused_tools and tool_calls_count < self.MAX_TOOL_CALLS_PER_SECTION: - unused_list = "、".join(unused_tools) - unused_hint = f"\n💡 你还没有使用过: {unused_list},建议尝试不同工具获取多角度信息" + unused_hint = REACT_UNUSED_TOOLS_HINT.format(unused_list="、".join(unused_tools)) messages.append({"role": "assistant", "content": response}) messages.append({ "role": "user", - "content": f"""Observation(检索结果): - -═══ 工具 {call['name']} 返回 ═══ -{result} - -═══════════════════════════════════════════════════════════════ -已调用工具 {tool_calls_count}/{self.MAX_TOOL_CALLS_PER_SECTION} 次(已用: {', '.join(used_tools)}){unused_hint} -- 如果信息充分:以 "Final Answer:" 开头输出章节内容(必须引用上述原文) -- 如果需要更多信息:调用一个工具继续检索 -═══════════════════════════════════════════════════════════════""" + "content": REACT_OBSERVATION_TEMPLATE.format( + tool_name=call["name"], + result=result, + tool_calls_count=tool_calls_count, + max_tool_calls=self.MAX_TOOL_CALLS_PER_SECTION, + used_tools_str=", ".join(used_tools), + unused_hint=unused_hint, + ), }) continue @@ -1304,7 +1406,11 @@ class ReportAgent: messages.append({ "role": "user", - "content": f"当前只调用了 {tool_calls_count} 次工具,至少需要 {min_tool_calls} 次。请调用工具获取模拟数据。{unused_hint}" + "content": REACT_INSUFFICIENT_TOOLS_MSG_ALT.format( + tool_calls_count=tool_calls_count, + min_tool_calls=min_tool_calls, + unused_hint=unused_hint, + ), }) continue @@ -1324,10 +1430,7 @@ class ReportAgent: # 达到最大迭代次数,强制生成内容 logger.warning(f"章节 {section.title} 达到最大迭代次数,强制生成") - messages.append({ - "role": "user", - "content": "已达到工具调用限制,请直接输出 Final Answer: 并生成章节内容。" - }) + messages.append({"role": "user", "content": REACT_FORCE_FINAL_MSG}) response = self.llm.chat( messages=messages, @@ -1626,33 +1729,11 @@ class ReportAgent: except Exception as e: logger.warning(f"获取报告内容失败: {e}") - # 构建系统提示 - system_prompt = f"""你是一个简洁高效的模拟预测助手。 - -【背景】 -预测条件: {self.simulation_requirement} - -【已生成的分析报告】 -{report_content if report_content else "(暂无报告)"} - -【规则】 -1. 优先基于上述报告内容回答问题 -2. 直接回答问题,避免冗长的思考论述 -3. 仅在报告内容不足以回答时,才调用工具检索更多数据 -4. 回答要简洁、清晰、有条理 - -【可用工具】(仅在需要时使用,最多调用1-2次) -{self._get_tools_description()} - -【工具调用格式】 - -{{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}} - - -【回答风格】 -- 简洁直接,不要长篇大论 -- 使用 > 格式引用关键内容 -- 优先给出结论,再解释原因""" + system_prompt = CHAT_SYSTEM_PROMPT_TEMPLATE.format( + simulation_requirement=self.simulation_requirement, + report_content=report_content if report_content else "(暂无报告)", + tools_description=self._get_tools_description(), + ) # 构建消息 messages = [{"role": "system", "content": system_prompt}] @@ -1707,8 +1788,8 @@ class ReportAgent: messages.append({"role": "assistant", "content": response}) observation = "\n".join([f"[{r['tool']}结果]\n{r['result']}" for r in tool_results]) messages.append({ - "role": "user", - "content": observation + "\n\n请简洁回答问题。" + "role": "user", + "content": observation + CHAT_OBSERVATION_SUFFIX }) # 达到最大迭代,获取最终响应