diff --git a/backend/app/services/report_agent.py b/backend/app/services/report_agent.py index 2a71c16..536f9ab 100644 --- a/backend/app/services/report_agent.py +++ b/backend/app/services/report_agent.py @@ -1001,7 +1001,7 @@ class ReportAgent: - 你正在以「上帝视角」观察未来的预演 - 所有内容必须来自模拟世界中发生的事件和Agent言行 - 禁止使用你自己的知识来编写报告内容 - - 每个章节至少调用2次工具(最多4次)来观察模拟的世界,它代表了未来 + - 每个章节至少调用2次工具(最多5次)来观察模拟的世界,它代表了未来 2. 【必须引用Agent的原始言行】 - Agent的发言和行为是对未来人群行为的预测 @@ -1053,7 +1053,7 @@ class ReportAgent: ``` ═══════════════════════════════════════════════════════════════ -【可用检索工具】(每章节调用2-4次) +【可用检索工具】(每章节调用2-5次) ═══════════════════════════════════════════════════════════════ {self._get_tools_description()} @@ -1069,14 +1069,18 @@ class ReportAgent: ═══════════════════════════════════════════════════════════════ 1. Thought: [分析需要什么信息,规划检索策略] -2. Action: [调用工具获取信息] +2. Action: [调用一个工具获取信息](每轮只能调用一个工具!) {{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}} -3. Observation: [分析工具返回的结果] -4. 重复步骤1-3,直到收集到足够信息(最多5轮) +3. Observation: [系统返回工具结果] +4. 重复步骤1-3,直到收集到足够信息 5. Final Answer: [基于检索结果撰写章节内容] +⚠️ 重要规则: +- 每轮只能调用一个工具,不要在一次回复中放多个 +- 当你认为信息足够时,必须以 "Final Answer:" 开头输出最终内容 + ═══════════════════════════════════════════════════════════════ 【章节内容要求】 ═══════════════════════════════════════════════════════════════ @@ -1184,10 +1188,11 @@ class ReportAgent: logger.debug(f"LLM响应: {response[:200]}...") - # 检查是否有工具调用和最终答案 - has_tool_calls = bool(self._parse_tool_calls(response)) + # 解析一次,复用结果 + tool_calls = self._parse_tool_calls(response) + has_tool_calls = bool(tool_calls) has_final_answer = "Final Answer:" in response - + # 记录 LLM 响应日志 if self.report_logger: self.report_logger.log_llm_response( @@ -1198,30 +1203,22 @@ class ReportAgent: has_tool_calls=has_tool_calls, has_final_answer=has_final_answer ) - - # 检查是否有最终答案 + + # ── 情况1:LLM 输出了 Final Answer ── if has_final_answer: - # 如果工具调用次数不足,提醒需要更多检索 + # 工具调用次数不足,拒绝并要求继续调工具 if tool_calls_count < min_tool_calls: messages.append({"role": "assistant", "content": response}) messages.append({ - "role": "user", - "content": f"""【注意】你只调用了{tool_calls_count}次工具,信息可能不够充分。 - -请再调用1-2次工具来获取更多模拟数据,然后再输出 Final Answer。 -建议: -- 使用 insight_forge 深度检索更多细节 -- 使用 panorama_search 了解事件全貌 - -记住:报告内容必须来自模拟结果,而不是你的知识!""" + "role": "user", + "content": f"【注意】你只调用了{tool_calls_count}次工具,信息可能不够充分。请再调用工具获取更多模拟数据,然后再输出 Final Answer。" }) continue - - # 提取最终答案 + + # 正常结束 final_answer = response.split("Final Answer:")[-1].strip() logger.info(f"章节 {section.title} 生成完成(工具调用: {tool_calls_count}次)") - - # 记录章节内容生成完成日志 + if self.report_logger: self.report_logger.log_section_content( section_title=section.title, @@ -1229,46 +1226,24 @@ class ReportAgent: content=final_answer, tool_calls_count=tool_calls_count ) - return final_answer - # 解析工具调用 - tool_calls = self._parse_tool_calls(response) - - if not tool_calls: - # 没有工具调用也没有最终答案 - messages.append({"role": "assistant", "content": response}) - - if tool_calls_count < min_tool_calls: - # 还没有足够的工具调用,强烈提示需要调用工具 - messages.append({ - "role": "user", - "content": f"""【重要】你还没有调用足够的工具来获取模拟数据! - -当前只调用了 {tool_calls_count} 次工具,至少需要 {min_tool_calls} 次。 - -请立即调用工具获取信息: - -{{"name": "insight_forge", "parameters": {{"query": "{section.title}相关的模拟结果和分析"}}}} - - -【记住】报告内容必须100%来自模拟结果,不能使用你自己的知识!""" - }) - else: - # 已有足够调用,可以生成最终答案 - messages.append({ - "role": "user", - "content": "你已经获取了足够的模拟数据。请基于检索到的信息,输出 Final Answer: 并撰写章节内容。\n\n【重要】内容必须大量引用检索到的原文,使用 > 格式引用。" - }) - continue - - # 执行工具调用 - tool_results = [] - for call in tool_calls: + # ── 情况2:LLM 尝试调用工具 ── + if has_tool_calls: + # 工具额度已耗尽 → 明确告知,要求输出 Final Answer if tool_calls_count >= self.MAX_TOOL_CALLS_PER_SECTION: - break - - # 记录工具调用日志 + messages.append({"role": "assistant", "content": response}) + messages.append({ + "role": "user", + "content": f"工具调用次数已达上限({tool_calls_count}/{self.MAX_TOOL_CALLS_PER_SECTION}),不能再调用工具。请立即基于已获取的信息,以 \"Final Answer:\" 开头输出章节内容。" + }) + continue + + # 只执行第一个工具调用 + call = tool_calls[0] + if len(tool_calls) > 1: + logger.info(f"LLM 尝试调用 {len(tool_calls)} 个工具,只执行第一个: {call['name']}") + if self.report_logger: self.report_logger.log_tool_call( section_title=section.title, @@ -1277,14 +1252,13 @@ class ReportAgent: parameters=call.get("parameters", {}), iteration=iteration + 1 ) - + result = self._execute_tool( - call["name"], + call["name"], call.get("parameters", {}), report_context=report_context ) - - # 记录工具结果日志 + if self.report_logger: self.report_logger.log_tool_result( section_title=section.title, @@ -1293,26 +1267,52 @@ class ReportAgent: result=result, iteration=iteration + 1 ) - - tool_results.append(f"═══ 工具 {call['name']} 返回 ═══\n{result}") - tool_calls_count += 1 - - # 将结果添加到消息 - messages.append({"role": "assistant", "content": response}) - messages.append({ - "role": "user", - "content": f"""Observation(检索结果): -{"".join(tool_results)} + tool_calls_count += 1 + + messages.append({"role": "assistant", "content": response}) + messages.append({ + "role": "user", + "content": f"""Observation(检索结果): + +═══ 工具 {call['name']} 返回 ═══ +{result} ═══════════════════════════════════════════════════════════════ -【下一步行动】 -- 如果信息充分:输出 Final Answer 并撰写章节内容(必须引用上述原文) -- 如果需要更多信息:继续调用工具检索 - 已调用工具 {tool_calls_count}/{self.MAX_TOOL_CALLS_PER_SECTION} 次 +- 如果信息充分:以 "Final Answer:" 开头输出章节内容(必须引用上述原文) +- 如果需要更多信息:调用一个工具继续检索 ═══════════════════════════════════════════════════════════════""" - }) + }) + continue + + # ── 情况3:既没有工具调用,也没有 Final Answer ── + messages.append({"role": "assistant", "content": response}) + + if tool_calls_count < min_tool_calls: + # 工具调用次数不足,催促调工具 + messages.append({ + "role": "user", + "content": f"""当前只调用了 {tool_calls_count} 次工具,至少需要 {min_tool_calls} 次。请调用工具获取模拟数据: + +{{"name": "insight_forge", "parameters": {{"query": "{section.title}相关的模拟结果和分析"}}}} +""" + }) + continue + + # 工具调用已足够,LLM 输出了内容但没带 "Final Answer:" 前缀 + # 直接将这段内容作为最终答案,不再空转 + logger.info(f"章节 {section.title} 未检测到 'Final Answer:' 前缀,直接采纳LLM输出作为最终内容(工具调用: {tool_calls_count}次)") + final_answer = response.strip() + + if self.report_logger: + self.report_logger.log_section_content( + section_title=section.title, + section_index=section_index, + content=final_answer, + tool_calls_count=tool_calls_count + ) + return final_answer # 达到最大迭代次数,强制生成内容 logger.warning(f"章节 {section.title} 达到最大迭代次数,强制生成")