Showing
8 changed files
with
356 additions
and
34 deletions
| @@ -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 |
utils/forum_reader.py
0 → 100644
| 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 | +""" |
-
Please register or login to post a comment