666ghj

Improve forum communication mechanism between agents.

@@ -131,7 +131,12 @@ class LogMonitor: @@ -131,7 +131,12 @@ class LogMonitor:
131 "已更新段落", 131 "已更新段落",
132 "正在生成", 132 "正在生成",
133 "开始处理", 133 "开始处理",
134 - "处理完成" 134 + "处理完成",
  135 + "已读取HOST发言",
  136 + "读取HOST发言失败",
  137 + "未找到HOST发言",
  138 + "调试输出",
  139 + "信息记录"
135 ] 140 ]
136 141
137 for pattern in exclude_patterns: 142 for pattern in exclude_patterns:
@@ -18,6 +18,17 @@ from ..utils.text_processing import ( @@ -18,6 +18,17 @@ from ..utils.text_processing import (
18 format_search_results_for_prompt 18 format_search_results_for_prompt
19 ) 19 )
20 20
  21 +# 导入论坛读取工具
  22 +import sys
  23 +import os
  24 +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
  25 +try:
  26 + from utils.forum_reader import get_latest_host_speech, format_host_speech_for_prompt
  27 + FORUM_READER_AVAILABLE = True
  28 +except ImportError:
  29 + FORUM_READER_AVAILABLE = False
  30 + print("警告: 无法导入forum_reader模块,将跳过HOST发言读取功能")
  31 +
21 32
22 class FirstSummaryNode(StateMutationNode): 33 class FirstSummaryNode(StateMutationNode):
23 """根据搜索结果生成段落首次总结的节点""" 34 """根据搜索结果生成段落首次总结的节点"""
@@ -62,9 +73,28 @@ class FirstSummaryNode(StateMutationNode): @@ -62,9 +73,28 @@ class FirstSummaryNode(StateMutationNode):
62 73
63 # 准备输入数据 74 # 准备输入数据
64 if isinstance(input_data, str): 75 if isinstance(input_data, str):
65 - message = input_data 76 + data = json.loads(input_data)
66 else: 77 else:
67 - message = json.dumps(input_data, ensure_ascii=False) 78 + data = input_data.copy() if isinstance(input_data, dict) else input_data
  79 +
  80 + # 读取最新的HOST发言(如果可用)
  81 + if FORUM_READER_AVAILABLE:
  82 + try:
  83 + host_speech = get_latest_host_speech()
  84 + if host_speech:
  85 + # 将HOST发言添加到输入数据中
  86 + data['host_speech'] = host_speech
  87 + self.log_info(f"已读取HOST发言,长度: {len(host_speech)}字符")
  88 + except Exception as e:
  89 + self.log_info(f"读取HOST发言失败: {str(e)}")
  90 +
  91 + # 转换为JSON字符串
  92 + message = json.dumps(data, ensure_ascii=False)
  93 +
  94 + # 如果有HOST发言,添加到消息前面作为参考
  95 + if FORUM_READER_AVAILABLE and 'host_speech' in data and data['host_speech']:
  96 + formatted_host = format_host_speech_for_prompt(data['host_speech'])
  97 + message = formatted_host + "\n" + message
68 98
69 self.log_info("正在生成首次段落总结") 99 self.log_info("正在生成首次段落总结")
70 100
@@ -208,9 +238,28 @@ class ReflectionSummaryNode(StateMutationNode): @@ -208,9 +238,28 @@ class ReflectionSummaryNode(StateMutationNode):
208 238
209 # 准备输入数据 239 # 准备输入数据
210 if isinstance(input_data, str): 240 if isinstance(input_data, str):
211 - message = input_data 241 + data = json.loads(input_data)
212 else: 242 else:
213 - message = json.dumps(input_data, ensure_ascii=False) 243 + data = input_data.copy() if isinstance(input_data, dict) else input_data
  244 +
  245 + # 读取最新的HOST发言(如果可用)
  246 + if FORUM_READER_AVAILABLE:
  247 + try:
  248 + host_speech = get_latest_host_speech()
  249 + if host_speech:
  250 + # 将HOST发言添加到输入数据中
  251 + data['host_speech'] = host_speech
  252 + self.log_info(f"已读取HOST发言,长度: {len(host_speech)}字符")
  253 + except Exception as e:
  254 + self.log_info(f"读取HOST发言失败: {str(e)}")
  255 +
  256 + # 转换为JSON字符串
  257 + message = json.dumps(data, ensure_ascii=False)
  258 +
  259 + # 如果有HOST发言,添加到消息前面作为参考
  260 + if FORUM_READER_AVAILABLE and 'host_speech' in data and data['host_speech']:
  261 + formatted_host = format_host_speech_for_prompt(data['host_speech'])
  262 + message = formatted_host + "\n" + message
214 263
215 self.log_info("正在生成反思总结") 264 self.log_info("正在生成反思总结")
216 265
@@ -18,6 +18,17 @@ from ..utils.text_processing import ( @@ -18,6 +18,17 @@ from ..utils.text_processing import (
18 format_search_results_for_prompt 18 format_search_results_for_prompt
19 ) 19 )
20 20
  21 +# 导入论坛读取工具
  22 +import sys
  23 +import os
  24 +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
  25 +try:
  26 + from utils.forum_reader import get_latest_host_speech, format_host_speech_for_prompt
  27 + FORUM_READER_AVAILABLE = True
  28 +except ImportError:
  29 + FORUM_READER_AVAILABLE = False
  30 + print("警告: 无法导入forum_reader模块,将跳过HOST发言读取功能")
  31 +
21 32
22 class FirstSummaryNode(StateMutationNode): 33 class FirstSummaryNode(StateMutationNode):
23 """根据搜索结果生成段落首次总结的节点""" 34 """根据搜索结果生成段落首次总结的节点"""
@@ -62,9 +73,28 @@ class FirstSummaryNode(StateMutationNode): @@ -62,9 +73,28 @@ class FirstSummaryNode(StateMutationNode):
62 73
63 # 准备输入数据 74 # 准备输入数据
64 if isinstance(input_data, str): 75 if isinstance(input_data, str):
65 - message = input_data 76 + data = json.loads(input_data)
66 else: 77 else:
67 - message = json.dumps(input_data, ensure_ascii=False) 78 + data = input_data.copy() if isinstance(input_data, dict) else input_data
  79 +
  80 + # 读取最新的HOST发言(如果可用)
  81 + if FORUM_READER_AVAILABLE:
  82 + try:
  83 + host_speech = get_latest_host_speech()
  84 + if host_speech:
  85 + # 将HOST发言添加到输入数据中
  86 + data['host_speech'] = host_speech
  87 + self.log_info(f"已读取HOST发言,长度: {len(host_speech)}字符")
  88 + except Exception as e:
  89 + self.log_info(f"读取HOST发言失败: {str(e)}")
  90 +
  91 + # 转换为JSON字符串
  92 + message = json.dumps(data, ensure_ascii=False)
  93 +
  94 + # 如果有HOST发言,添加到消息前面作为参考
  95 + if FORUM_READER_AVAILABLE and 'host_speech' in data and data['host_speech']:
  96 + formatted_host = format_host_speech_for_prompt(data['host_speech'])
  97 + message = formatted_host + "\n" + message
68 98
69 self.log_info("正在生成首次段落总结") 99 self.log_info("正在生成首次段落总结")
70 100
@@ -212,9 +242,28 @@ class ReflectionSummaryNode(StateMutationNode): @@ -212,9 +242,28 @@ class ReflectionSummaryNode(StateMutationNode):
212 242
213 # 准备输入数据 243 # 准备输入数据
214 if isinstance(input_data, str): 244 if isinstance(input_data, str):
215 - message = input_data 245 + data = json.loads(input_data)
216 else: 246 else:
217 - message = json.dumps(input_data, ensure_ascii=False) 247 + data = input_data.copy() if isinstance(input_data, dict) else input_data
  248 +
  249 + # 读取最新的HOST发言(如果可用)
  250 + if FORUM_READER_AVAILABLE:
  251 + try:
  252 + host_speech = get_latest_host_speech()
  253 + if host_speech:
  254 + # 将HOST发言添加到输入数据中
  255 + data['host_speech'] = host_speech
  256 + self.log_info(f"已读取HOST发言,长度: {len(host_speech)}字符")
  257 + except Exception as e:
  258 + self.log_info(f"读取HOST发言失败: {str(e)}")
  259 +
  260 + # 转换为JSON字符串
  261 + message = json.dumps(data, ensure_ascii=False)
  262 +
  263 + # 如果有HOST发言,添加到消息前面作为参考
  264 + if FORUM_READER_AVAILABLE and 'host_speech' in data and data['host_speech']:
  265 + formatted_host = format_host_speech_for_prompt(data['host_speech'])
  266 + message = formatted_host + "\n" + message
218 267
219 self.log_info("正在生成反思总结") 268 self.log_info("正在生成反思总结")
220 269
@@ -18,6 +18,17 @@ from ..utils.text_processing import ( @@ -18,6 +18,17 @@ from ..utils.text_processing import (
18 format_search_results_for_prompt 18 format_search_results_for_prompt
19 ) 19 )
20 20
  21 +# 导入论坛读取工具
  22 +import sys
  23 +import os
  24 +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
  25 +try:
  26 + from utils.forum_reader import get_latest_host_speech, format_host_speech_for_prompt
  27 + FORUM_READER_AVAILABLE = True
  28 +except ImportError:
  29 + FORUM_READER_AVAILABLE = False
  30 + print("警告: 无法导入forum_reader模块,将跳过HOST发言读取功能")
  31 +
21 32
22 class FirstSummaryNode(StateMutationNode): 33 class FirstSummaryNode(StateMutationNode):
23 """根据搜索结果生成段落首次总结的节点""" 34 """根据搜索结果生成段落首次总结的节点"""
@@ -62,9 +73,28 @@ class FirstSummaryNode(StateMutationNode): @@ -62,9 +73,28 @@ class FirstSummaryNode(StateMutationNode):
62 73
63 # 准备输入数据 74 # 准备输入数据
64 if isinstance(input_data, str): 75 if isinstance(input_data, str):
65 - message = input_data 76 + data = json.loads(input_data)
66 else: 77 else:
67 - message = json.dumps(input_data, ensure_ascii=False) 78 + data = input_data.copy() if isinstance(input_data, dict) else input_data
  79 +
  80 + # 读取最新的HOST发言(如果可用)
  81 + if FORUM_READER_AVAILABLE:
  82 + try:
  83 + host_speech = get_latest_host_speech()
  84 + if host_speech:
  85 + # 将HOST发言添加到输入数据中
  86 + data['host_speech'] = host_speech
  87 + self.log_info(f"已读取HOST发言,长度: {len(host_speech)}字符")
  88 + except Exception as e:
  89 + self.log_info(f"读取HOST发言失败: {str(e)}")
  90 +
  91 + # 转换为JSON字符串
  92 + message = json.dumps(data, ensure_ascii=False)
  93 +
  94 + # 如果有HOST发言,添加到消息前面作为参考
  95 + if FORUM_READER_AVAILABLE and 'host_speech' in data and data['host_speech']:
  96 + formatted_host = format_host_speech_for_prompt(data['host_speech'])
  97 + message = formatted_host + "\n" + message
68 98
69 self.log_info("正在生成首次段落总结") 99 self.log_info("正在生成首次段落总结")
70 100
@@ -212,9 +242,28 @@ class ReflectionSummaryNode(StateMutationNode): @@ -212,9 +242,28 @@ class ReflectionSummaryNode(StateMutationNode):
212 242
213 # 准备输入数据 243 # 准备输入数据
214 if isinstance(input_data, str): 244 if isinstance(input_data, str):
215 - message = input_data 245 + data = json.loads(input_data)
216 else: 246 else:
217 - message = json.dumps(input_data, ensure_ascii=False) 247 + data = input_data.copy() if isinstance(input_data, dict) else input_data
  248 +
  249 + # 读取最新的HOST发言(如果可用)
  250 + if FORUM_READER_AVAILABLE:
  251 + try:
  252 + host_speech = get_latest_host_speech()
  253 + if host_speech:
  254 + # 将HOST发言添加到输入数据中
  255 + data['host_speech'] = host_speech
  256 + self.log_info(f"已读取HOST发言,长度: {len(host_speech)}字符")
  257 + except Exception as e:
  258 + self.log_info(f"读取HOST发言失败: {str(e)}")
  259 +
  260 + # 转换为JSON字符串
  261 + message = json.dumps(data, ensure_ascii=False)
  262 +
  263 + # 如果有HOST发言,添加到消息前面作为参考
  264 + if FORUM_READER_AVAILABLE and 'host_speech' in data and data['host_speech']:
  265 + formatted_host = format_host_speech_for_prompt(data['host_speech'])
  266 + message = formatted_host + "\n" + message
218 267
219 self.log_info("正在生成反思总结") 268 self.log_info("正在生成反思总结")
220 269
@@ -58,16 +58,18 @@ Say goodbye to traditional data dashboards. In "WeiYu", everything starts with a @@ -58,16 +58,18 @@ Say goodbye to traditional data dashboards. In "WeiYu", everything starts with a
58 58
59 ### A Complete Analysis Workflow 59 ### A Complete Analysis Workflow
60 60
61 -| Step | Phase Name | Main Operations | Participating Components |  
62 -|------|------------|-----------------|-------------------------|  
63 -| 1 | User Query | Flask main application receives the query | Flask Main Application |  
64 -| 2 | Parallel Launch | Three Agents start working simultaneously | Query Agent, Media Agent, Insight Agent |  
65 -| 3 | Preliminary Analysis | Each Agent uses dedicated tools for overview search | Each Agent + Dedicated Toolsets |  
66 -| 4 | Strategy Formulation | Develop segmented research strategies based on preliminary results | Internal Decision Modules of Each Agent |  
67 -| 5 | In-depth Research | Multi-round search and reflection mechanisms calling respective tools | Each Agent + Reflection Mechanisms |  
68 -| 6 | Forum Collaboration | ForumEngine accepts key findings from each Agent and facilitates Agent communication | ForumEngine + All Agents |  
69 -| 7 | Result Integration | Report Agent collects all analysis results and forum content | Report Agent |  
70 -| 8 | Report Generation | Dynamically select templates and styles, generate final reports through multiple rounds | Report Agent + Template Engine | 61 +| Step | Phase Name | Main Operations | Participating Components | Cycle Nature |
  62 +|------|------------|-----------------|-------------------------|--------------|
  63 +| 1 | User Query | Flask main application receives the query | Flask Main Application | - |
  64 +| 2 | Parallel Launch | Three Agents start working simultaneously | Query Agent, Media Agent, Insight Agent | - |
  65 +| 3 | Preliminary Analysis | Each Agent uses dedicated tools for overview search | Each Agent + Dedicated Toolsets | - |
  66 +| 4 | Strategy Formulation | Develop segmented research strategies based on preliminary results | Internal Decision Modules of Each Agent | - |
  67 +| 5-N | **Iterative Phase** | **Forum Collaboration + In-depth Research** | **ForumEngine + All Agents** | **Multi-round cycles** |
  68 +| 5.1 | In-depth Research | Each Agent conducts specialized search guided by forum host | Each Agent + Reflection Mechanisms + Forum Guidance | Each cycle |
  69 +| 5.2 | Forum Collaboration | ForumEngine monitors Agent communications and generates host summaries | ForumEngine + LLM Host | Each cycle |
  70 +| 5.3 | Communication Integration | Each Agent adjusts research directions based on discussions | Each Agent + forum_reader tool | Each cycle |
  71 +| N+1 | Result Integration | Report Agent collects all analysis results and forum content | Report Agent | - |
  72 +| N+2 | Report Generation | Dynamically select templates and styles, generate final reports through multiple rounds | Report Agent + Template Engine | - |
71 73
72 ### Project Code Structure Tree 74 ### Project Code Structure Tree
73 75
@@ -161,6 +163,8 @@ Weibo_PublicOpinion_AnalysisSystem/ @@ -161,6 +163,8 @@ Weibo_PublicOpinion_AnalysisSystem/
161 ├── logs/ # Runtime log directory 163 ├── logs/ # Runtime log directory
162 ├── final_reports/ # Final generated HTML report files 164 ├── final_reports/ # Final generated HTML report files
163 ├── utils/ # Common utility functions 165 ├── utils/ # Common utility functions
  166 +│ ├── forum_reader.py # Agent forum communication
  167 +│ └── retry_helper.py # Network request retry mechanism tool
164 ├── app.py # Flask main application entry 168 ├── app.py # Flask main application entry
165 ├── config.py # Global configuration file 169 ├── config.py # Global configuration file
166 └── requirements.txt # Python dependency list 170 └── requirements.txt # Python dependency list
@@ -58,16 +58,18 @@ @@ -58,16 +58,18 @@
58 58
59 ### 一次完整分析流程 59 ### 一次完整分析流程
60 60
61 -| 步骤 | 阶段名称 | 主要操作 | 参与组件 |  
62 -|------|----------|----------|----------|  
63 -| 1 | 用户提问 | Flask主应用接收查询 | Flask主应用 |  
64 -| 2 | 并行启动 | 三个Agent同时开始工作 | Query Agent、Media Agent、Insight Agent |  
65 -| 3 | 初步分析 | 各Agent使用专属工具进行概览搜索 | 各Agent + 专属工具集 |  
66 -| 4 | 策略制定 | 基于初步结果制定分块研究策略 | 各Agent内部决策模块 |  
67 -| 5 | 深度研究 | 多轮搜索与反思机制调用各自工具 | 各Agent + 反思机制 |  
68 -| 6 | 论坛协作 | ForumEngine接受各Agent关键发现并促进Agent交流 | ForumEngine + 所有Agent |  
69 -| 7 | 结果整合 | Report Agent收集所有分析结果和论坛内容 | Report Agent |  
70 -| 8 | 报告生成 | 动态选择模板和样式,多轮生成最终报告 | Report Agent + 模板引擎 | 61 +| 步骤 | 阶段名称 | 主要操作 | 参与组件 | 循环特性 |
  62 +|------|----------|----------|----------|----------|
  63 +| 1 | 用户提问 | Flask主应用接收查询 | Flask主应用 | - |
  64 +| 2 | 并行启动 | 三个Agent同时开始工作 | Query Agent、Media Agent、Insight Agent | - |
  65 +| 3 | 初步分析 | 各Agent使用专属工具进行概览搜索 | 各Agent + 专属工具集 | - |
  66 +| 4 | 策略制定 | 基于初步结果制定分块研究策略 | 各Agent内部决策模块 | - |
  67 +| 5-N | **循环阶段** | **论坛协作 + 深度研究** | **ForumEngine + 所有Agent** | **多轮循环** |
  68 +| 5.1 | 深度研究 | 各Agent基于论坛主持人引导进行专项搜索 | 各Agent + 反思机制 + 论坛引导 | 每轮循环 |
  69 +| 5.2 | 论坛协作 | ForumEngine监控Agent发言并生成主持人总结 | ForumEngine + LLM主持人 | 每轮循环 |
  70 +| 5.3 | 交流融合 | 各Agent根据讨论调整研究方向 | 各Agent + forum_reader工具 | 每轮循环 |
  71 +| N+1 | 结果整合 | Report Agent收集所有分析结果和论坛内容 | Report Agent | - |
  72 +| N+2 | 报告生成 | 动态选择模板和样式,多轮生成最终报告 | Report Agent + 模板引擎 | - |
71 73
72 ### 项目代码结构树 74 ### 项目代码结构树
73 75
@@ -161,6 +163,8 @@ Weibo_PublicOpinion_AnalysisSystem/ @@ -161,6 +163,8 @@ Weibo_PublicOpinion_AnalysisSystem/
161 ├── logs/ # 运行日志目录 163 ├── logs/ # 运行日志目录
162 ├── final_reports/ # 最终生成的HTML报告文件 164 ├── final_reports/ # 最终生成的HTML报告文件
163 ├── utils/ # 通用工具函数 165 ├── utils/ # 通用工具函数
  166 +│ ├── forum_reader.py # Agent间论坛通信
  167 +│ └── retry_helper.py # 网络请求重试机制工具
164 ├── app.py # Flask主应用入口 168 ├── app.py # Flask主应用入口
165 ├── config.py # 全局配置文件 169 ├── config.py # 全局配置文件
166 └── requirements.txt # Python依赖包清单 170 └── requirements.txt # Python依赖包清单
@@ -26,7 +26,7 @@ TAVILY_API_KEY = "your_tavily_api_key" @@ -26,7 +26,7 @@ TAVILY_API_KEY = "your_tavily_api_key"
26 KIMI_API_KEY = "your_kimi_api_key" 26 KIMI_API_KEY = "your_kimi_api_key"
27 27
28 # Gemini API Key (via OpenAI format proxy) 28 # Gemini API Key (via OpenAI format proxy)
29 -# 申请地址https://api.chataiapi.com/ 29 +# 这里我用了一个中转api来接入Gemini,申请地址https://api.chataiapi.com/,你也可以使用其他
30 GEMINI_API_KEY = "your_gemini_api_key" 30 GEMINI_API_KEY = "your_gemini_api_key"
31 31
32 # Bocha Search API Key 32 # Bocha Search API Key
  1 +"""
  2 +Forum日志读取工具
  3 +用于读取forum.log中的最新HOST发言
  4 +"""
  5 +
  6 +import re
  7 +from pathlib import Path
  8 +from typing import Optional, List, Dict
  9 +import logging
  10 +
  11 +logger = logging.getLogger(__name__)
  12 +
  13 +
  14 +def get_latest_host_speech(log_dir: str = "logs") -> Optional[str]:
  15 + """
  16 + 获取forum.log中最新的HOST发言
  17 +
  18 + Args:
  19 + log_dir: 日志目录路径
  20 +
  21 + Returns:
  22 + 最新的HOST发言内容,如果没有则返回None
  23 + """
  24 + try:
  25 + forum_log_path = Path(log_dir) / "forum.log"
  26 +
  27 + if not forum_log_path.exists():
  28 + logger.debug("forum.log文件不存在")
  29 + return None
  30 +
  31 + with open(forum_log_path, 'r', encoding='utf-8', errors='ignore') as f:
  32 + lines = f.readlines()
  33 +
  34 + # 从后往前查找最新的HOST发言
  35 + host_speech = None
  36 + for line in reversed(lines):
  37 + # 匹配格式: [时间] [HOST] 内容
  38 + match = re.match(r'\[(\d{2}:\d{2}:\d{2})\]\s*\[HOST\]\s*(.+)', line)
  39 + if match:
  40 + _, content = match.groups()
  41 + # 处理转义的换行符,还原为实际换行
  42 + host_speech = content.replace('\\n', '\n').strip()
  43 + break
  44 +
  45 + if host_speech:
  46 + logger.info(f"找到最新的HOST发言,长度: {len(host_speech)}字符")
  47 + else:
  48 + logger.debug("未找到HOST发言")
  49 +
  50 + return host_speech
  51 +
  52 + except Exception as e:
  53 + logger.error(f"读取forum.log失败: {str(e)}")
  54 + return None
  55 +
  56 +
  57 +def get_all_host_speeches(log_dir: str = "logs") -> List[Dict[str, str]]:
  58 + """
  59 + 获取forum.log中所有的HOST发言
  60 +
  61 + Args:
  62 + log_dir: 日志目录路径
  63 +
  64 + Returns:
  65 + 包含所有HOST发言的列表,每个元素是包含timestamp和content的字典
  66 + """
  67 + try:
  68 + forum_log_path = Path(log_dir) / "forum.log"
  69 +
  70 + if not forum_log_path.exists():
  71 + logger.debug("forum.log文件不存在")
  72 + return []
  73 +
  74 + with open(forum_log_path, 'r', encoding='utf-8', errors='ignore') as f:
  75 + lines = f.readlines()
  76 +
  77 + host_speeches = []
  78 + for line in lines:
  79 + # 匹配格式: [时间] [HOST] 内容
  80 + match = re.match(r'\[(\d{2}:\d{2}:\d{2})\]\s*\[HOST\]\s*(.+)', line)
  81 + if match:
  82 + timestamp, content = match.groups()
  83 + # 处理转义的换行符
  84 + content = content.replace('\\n', '\n').strip()
  85 + host_speeches.append({
  86 + 'timestamp': timestamp,
  87 + 'content': content
  88 + })
  89 +
  90 + logger.info(f"找到{len(host_speeches)}条HOST发言")
  91 + return host_speeches
  92 +
  93 + except Exception as e:
  94 + logger.error(f"读取forum.log失败: {str(e)}")
  95 + return []
  96 +
  97 +
  98 +def get_recent_agent_speeches(log_dir: str = "logs", limit: int = 5) -> List[Dict[str, str]]:
  99 + """
  100 + 获取forum.log中最近的Agent发言(不包括HOST)
  101 +
  102 + Args:
  103 + log_dir: 日志目录路径
  104 + limit: 返回的最大发言数量
  105 +
  106 + Returns:
  107 + 包含最近Agent发言的列表
  108 + """
  109 + try:
  110 + forum_log_path = Path(log_dir) / "forum.log"
  111 +
  112 + if not forum_log_path.exists():
  113 + return []
  114 +
  115 + with open(forum_log_path, 'r', encoding='utf-8', errors='ignore') as f:
  116 + lines = f.readlines()
  117 +
  118 + agent_speeches = []
  119 + for line in reversed(lines): # 从后往前读取
  120 + # 匹配格式: [时间] [AGENT_NAME] 内容
  121 + match = re.match(r'\[(\d{2}:\d{2}:\d{2})\]\s*\[(INSIGHT|MEDIA|QUERY)\]\s*(.+)', line)
  122 + if match:
  123 + timestamp, agent, content = match.groups()
  124 + # 处理转义的换行符
  125 + content = content.replace('\\n', '\n').strip()
  126 + agent_speeches.append({
  127 + 'timestamp': timestamp,
  128 + 'agent': agent,
  129 + 'content': content
  130 + })
  131 + if len(agent_speeches) >= limit:
  132 + break
  133 +
  134 + agent_speeches.reverse() # 恢复时间顺序
  135 + return agent_speeches
  136 +
  137 + except Exception as e:
  138 + logger.error(f"读取forum.log失败: {str(e)}")
  139 + return []
  140 +
  141 +
  142 +def format_host_speech_for_prompt(host_speech: str) -> str:
  143 + """
  144 + 格式化HOST发言,用于添加到prompt中
  145 +
  146 + Args:
  147 + host_speech: HOST发言内容
  148 +
  149 + Returns:
  150 + 格式化后的内容
  151 + """
  152 + if not host_speech:
  153 + return ""
  154 +
  155 + return f"""
  156 +### 论坛主持人最新总结
  157 +以下是论坛主持人对各Agent讨论的最新总结和引导,请参考其中的观点和建议:
  158 +
  159 +{host_speech}
  160 +
  161 +---
  162 +"""