From 43a1486605f58c8ba248163d1ca020bf35e620f0 Mon Sep 17 00:00:00 2001 From: nicky <2284087296@qq.com> Date: Fri, 19 Dec 2025 15:11:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E4=BA=8E=E5=95=86=E6=B1=A4LazyLLM?= =?UTF-8?q?=E7=9A=84=E8=B4=A2=E5=8A=A1=E6=96=B0=E9=97=BB=E5=8A=A9=E6=89=8B?= =?UTF-8?q?Agent=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- news-agent/news_agent.py | 369 ++++++++++++++ news-agent/requirements.txt | 14 + ...71\347\233\256\350\257\264\346\230\216.md" | 479 ++++++++++++++++++ 3 files changed, 862 insertions(+) create mode 100644 news-agent/news_agent.py create mode 100644 news-agent/requirements.txt create mode 100644 "news-agent/\351\241\271\347\233\256\350\257\264\346\230\216.md" diff --git a/news-agent/news_agent.py b/news-agent/news_agent.py new file mode 100644 index 0000000..f9f6c3d --- /dev/null +++ b/news-agent/news_agent.py @@ -0,0 +1,369 @@ +# -*- coding: utf-8 -*- +""" +基于商汤LazyLLM开发的财务新闻助手Agent +功能:实时爬取财经新闻、RAG检索、结构化分析输出、Web交互 +""" +import os +import time +import json +import schedule +import threading +import requests +from datetime import datetime +from bs4 import BeautifulSoup +import lazyllm +from lazyllm import bind + +# ======================== 全局配置项(请根据实际情况修改)======================== +# 1. 基础路径配置 +NEWS_DATA_PATH = "./financial_news_data" # 新闻本地存储路径 +LOG_PATH = "./agent_logs" # 日志存储路径 +# 2. 服务配置 +PORT = 23467 # Web服务端口 +TOP_K = 5 # 检索返回的新闻数量 +MAX_CONTEXT_LEN = 500 # 新闻正文截取长度(避免LLM上下文过长) +NEWS_EXPIRE_DAYS = 30 # 仅保留30天内的新闻 +# 3. 模型配置 +MODEL_NAME = "SenseNova-V6-5-Turbo" # 商汤大模型名称 +TEMPERATURE = 0.3 # 生成温度(越低越稳定) +# 4. 爬虫配置 +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" +CRAWL_INTERVAL_HOURS = 2 # 新闻爬取间隔(小时) + +# ======================== 日志初始化 ========================= +os.makedirs(LOG_PATH, exist_ok=True) + + +def log_info(msg): + """日志打印函数""" + log_msg = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}" + print(log_msg) + with open(os.path.join(LOG_PATH, "agent.log"), "a", encoding="utf-8") as f: + f.write(log_msg + "\n") + + +# ======================== 1. 新闻爬虫模块 ========================= +def crawl_news_content(url): + """ + 爬取单篇新闻的正文内容 + :param url: 新闻详情页链接 + :return: 新闻正文(字符串) + """ + try: + headers = {"User-Agent": USER_AGENT} + response = requests.get(url, headers=headers, timeout=10) + response.encoding = response.apparent_encoding # 自动识别编码 + soup = BeautifulSoup(response.text, "html.parser") + + # 适配东方财富网正文结构(不同网站需调整selector) + content_tags = soup.select(".main-content p") or soup.select("#ContentBody p") or soup.select(".content p") + if not content_tags: + return "正文爬取失败:未匹配到正文标签" + + content = "\n".join([tag.text.strip() for tag in content_tags if tag.text.strip()]) + return content[:2000] # 限制正文长度,避免数据过大 + except Exception as e: + log_info(f"爬取正文失败(URL:{url}):{str(e)}") + return f"正文爬取失败:{str(e)[:50]}" + + +def crawl_financial_news(): + """ + 爬取东方财富网行业新闻列表 + :return: 结构化新闻列表(字典列表) + """ + news_list = [] + try: + # 东方财富网行业新闻入口 + base_url = "https://finance.eastmoney.com/industry/" + headers = {"User-Agent": USER_AGENT} + response = requests.get(base_url, headers=headers, timeout=10) + response.encoding = response.apparent_encoding + soup = BeautifulSoup(response.text, "html.parser") + + # 解析新闻列表(适配网页结构,若网页更新需调整selector) + news_items = soup.select(".news-item") or soup.select(".item") + if not news_items: + log_info("爬虫警告:未匹配到新闻列表标签,可能网页结构已更新") + return news_list + + for idx, item in enumerate(news_items[:20]): # 每次爬取前20条最新新闻 + try: + # 提取基础信息 + title_tag = item.select_one("a[title]") or item.select_one(".title a") + title = title_tag.text.strip() if title_tag else f"无标题_{idx}" + link = title_tag["href"] if (title_tag and "href" in title_tag.attrs) else "" + # 补全相对链接 + if link and not link.startswith("http"): + link = f"https:{link}" if link.startswith("//") else f"https://finance.eastmoney.com{link}" + + # 提取发布时间和来源 + time_tag = item.select_one(".time") or item.select_one(".publish-time") + publish_time = time_tag.text.strip() if time_tag else datetime.now().strftime("%Y-%m-%d %H:%M:%S") + source_tag = item.select_one(".source") or item.select_one(".author") + source = source_tag.text.strip() if source_tag else "未知来源" + + # 爬取正文 + content = crawl_news_content(link) + + # 结构化存储 + news_info = { + "title": title, + "link": link, + "publish_time": publish_time, + "source": source, + "content": content, + "industry": "综合财经", # 可扩展:根据网页分类自动识别行业 + "crawl_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + news_list.append(news_info) + log_info(f"成功爬取新闻:{title}") + except Exception as e: + log_info(f"解析单条新闻失败(序号:{idx}):{str(e)}") + continue + except Exception as e: + log_info(f"新闻爬取整体失败:{str(e)}") + + return news_list + + +def save_news_to_local(news_list, base_path): + """ + 将爬取的新闻保存到本地文件(按日期分类) + :param news_list: 结构化新闻列表 + :param base_path: 根存储路径 + """ + if not news_list: + log_info("无新闻数据可保存") + return + + # 按日期创建文件夹 + today = datetime.now().strftime("%Y-%m-%d") + save_path = os.path.join(base_path, today) + os.makedirs(save_path, exist_ok=True) + + # 去重:基于标题+发布时间去重 + unique_news = [] + news_keys = set() + for news in news_list: + key = f"{news['title']}_{news['publish_time']}" + if key not in news_keys: + news_keys.add(key) + unique_news.append(news) + + # 保存每条新闻为JSON文件(易解析) + for news in unique_news: + # 生成安全的文件名 + safe_title = news['title'].replace("/", "_").replace("\\", "_").replace(":", "_").replace("*", "_").replace("?", + "_").replace( + '"', "_").replace("<", "_").replace(">", "_").replace("|", "_") + filename = f"{news['publish_time'].replace(':', '-')}_{safe_title[:20]}.json" + file_path = os.path.join(save_path, filename) + + try: + with open(file_path, "w", encoding="utf-8") as f: + json.dump(news, f, ensure_ascii=False, indent=2) + except Exception as e: + log_info(f"保存新闻文件失败({filename}):{str(e)}") + + log_info(f"成功保存 {len(unique_news)} 条新闻到本地(去重后)") + + +# ======================== 2. 知识库定时更新模块 ========================= +def update_news_knowledge_base(): + """定时爬取新闻并更新LazyLLM知识库""" + log_info("开始更新财务新闻知识库...") + # 1. 爬取最新新闻 + crawled_news = crawl_financial_news() + # 2. 保存到本地 + save_news_to_local(crawled_news, NEWS_DATA_PATH) + # 3. 验证知识库文件数量 + total_files = 0 + for root, dirs, files in os.walk(NEWS_DATA_PATH): + total_files += len([f for f in files if f.endswith(".json")]) + log_info(f"知识库更新完成,本地累计新闻文件数:{total_files}") + + +def start_schedule_task(): + """启动定时任务(后台线程运行)""" + # 立即执行一次更新 + update_news_knowledge_base() + # 设定定时任务:每CRAWL_INTERVAL_HOURS小时更新一次 + schedule.every(CRAWL_INTERVAL_HOURS).hours.do(update_news_knowledge_base) + log_info(f"定时任务启动:每{CRAWL_INTERVAL_HOURS}小时更新一次知识库") + + # 后台循环执行定时任务 + while True: + schedule.run_pending() + time.sleep(60) # 每分钟检查一次任务 + + +# ======================== 3. LazyLLM工作流与Web服务 ========================= +def load_news_documents(data_path): + """ + 加载本地新闻文件到LazyLLM Document + :param data_path: 新闻存储路径 + :return: LazyLLM Document对象 + """ + # 遍历所有JSON新闻文件 + news_files = [] + for root, dirs, files in os.walk(data_path): + for file in files: + if file.endswith(".json"): + news_files.append(os.path.join(root, file)) + + if not news_files: + log_info("警告:知识库无新闻文件,先执行爬取任务") + # 创建空文档 + documents = lazyllm.Document() + return documents + + # 加载文件到Document + documents = lazyllm.Document(dataset_path=data_path) + # 为每个文档节点补充元数据(标题、来源、发布时间等) + for node in documents.nodes: + file_path = node.meta.get("filename", "") + if file_path and os.path.exists(file_path): + try: + with open(file_path, "r", encoding="utf-8") as f: + news_data = json.load(f) + # 补充元数据 + node.meta["title"] = news_data.get("title", "") + node.meta["source"] = news_data.get("source", "") + node.meta["publish_time"] = news_data.get("publish_time", "") + node.meta["link"] = news_data.get("link", "") + node.meta["industry"] = news_data.get("industry", "") + # 设置文档内容为新闻正文(用于检索) + node.set_content(news_data.get("content", "")) + except Exception as e: + log_info(f"加载新闻文件元数据失败({file_path}):{str(e)}") + + log_info(f"成功加载 {len(documents.nodes)} 条新闻到LazyLLM知识库") + return documents + + +def build_financial_agent(): + """构建财务新闻助手工作流""" + # 1. 加载知识库 + documents = load_news_documents(NEWS_DATA_PATH) + + # 2. 定义财务新闻分析Prompt(结构化输出模板) + news_prompt = """ +你是一名专业的财务新闻分析助手,专注于解读财经新闻的核心信息与财务影响。 +请基于用户查询和检索到的新闻内容,按以下框架生成结构化回答: + +## 一、相关新闻汇总(按发布时间倒序) +### 新闻1:{title}(来源:{source} | 发布时间:{publish_time}) +- 核心摘要:[提炼100字内核心内容] +- 关键信息:[时间、主体、事件、数据等关键点] + +### 新闻2:{title}(来源:{source} | 发布时间:{publish_time}) +...(最多展示5条相关新闻) + +## 二、财务影响分析 +### 1. 直接影响 +- 对相关行业/公司的短期影响(如营收、利润、政策支持等) +### 2. 潜在风险/机遇 +- 隐藏的风险点(如监管收紧、市场竞争加剧)或机遇(如政策红利、技术突破) +### 3. 投资参考 +- 简洁给出中性、专业的投资决策参考(不构成具体买卖建议) + +## 严格要求 +1. 仅基于检索到的新闻内容生成,绝不编造任何信息; +2. 优先展示最新发布的新闻,发布时间格式统一为YYYY-MM-DD HH:MM; +3. 避免专业术语堆砌,保持语言通俗易懂; +4. 若检索不到相关新闻,直接回复:"未查询到相关财务新闻,请尝试调整关键词或扩大时间范围"; +5. 投资参考部分必须中立,明确说明"不构成投资建议"。 + """ + + # 3. 编排LazyLLM工作流 + with lazyllm.pipeline() as ppl: + # 检索组件:中文优化的BM25检索,过滤过期新闻 + ppl.retriever = lazyllm.Retriever( + doc=documents, + group_name="FinancialNews", + similarity="bm25_chinese", + topk=TOP_K, + filter_fn=lambda node: _filter_expired_news(node) + ) + + # 上下文格式化:整合检索结果为LLM输入 + def format_context(nodes, query): + if not nodes: + return {"query": query, "context_str": ""} + + context_str = "\n=== 检索到的相关新闻 ===\n" + for i, node in enumerate(nodes, 1): + title = node.meta.get("title", "无标题") + source = node.meta.get("source", "未知来源") + publish_time = node.meta.get("publish_time", "未知时间") + content = node.get_content()[:MAX_CONTEXT_LEN] # 截取正文 + context_str += f""" +### 新闻{i} +标题:{title} +来源:{source} +发布时间:{publish_time} +正文摘要:{content} +链接:{node.meta.get('link', '无')} +""" + return {"query": query, "context_str": context_str} + + ppl.formatter = format_context | bind(query=ppl.input) + + # 生成组件:接入商汤SenseNova大模型 + ppl.llm = lazyllm.OnlineChatModule( + model=MODEL_NAME, + stream=True, # 流式输出,提升交互体验 + temperature=TEMPERATURE, + timeout=60 # 超时时间 + ).prompt(lazyllm.ChatPrompter( + instruction=news_prompt, + extra_keys=["context_str"] + )) + + return ppl + + +def _filter_expired_news(node): + """过滤过期新闻(仅保留NEWS_EXPIRE_DAYS天内的)""" + publish_time_str = node.meta.get("publish_time", "") + if not publish_time_str: + return False # 无发布时间的新闻过滤掉 + + # 适配常见的时间格式 + time_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d", "%Y/%m/%d %H:%M"] + publish_time = None + for fmt in time_formats: + try: + publish_time = datetime.strptime(publish_time_str, fmt) + break + except: + continue + + if not publish_time: + return False + + # 计算是否在有效期内 + days_diff = (datetime.now() - publish_time).days + return days_diff <= NEWS_EXPIRE_DAYS + + +# ======================== 4. 主函数:启动服务 ========================= +if __name__ == "__main__": + # 创建必要的目录 + os.makedirs(NEWS_DATA_PATH, exist_ok=True) + os.makedirs(LOG_PATH, exist_ok=True) + + # 1. 启动定时更新知识库的后台线程 + schedule_thread = threading.Thread(target=start_schedule_task, daemon=True) + schedule_thread.start() + log_info("后台定时更新线程已启动") + + # 2. 构建Agent工作流 + financial_agent = build_financial_agent() + + # 3. 启动Web服务 + log_info(f"\n财务新闻助手Agent启动成功!") + log_info(f"Web交互地址:http://127.0.0.1:{PORT}") + log_info(f"提示:首次使用请等待知识库更新完成(约1-2分钟)") + lazyllm.WebModule(financial_agent, port=PORT).start().wait() \ No newline at end of file diff --git a/news-agent/requirements.txt b/news-agent/requirements.txt new file mode 100644 index 0000000..290445e --- /dev/null +++ b/news-agent/requirements.txt @@ -0,0 +1,14 @@ +# 核心依赖(必须安装) +python>=3.10,<3.12 # LazyLLM最佳兼容Python版本 +lazyllm>=0.5.0 # 商汤LazyLLM核心框架 +requests>=2.31.0 # 新闻爬取HTTP请求 +beautifulsoup4>=4.12.2 # 网页解析 +schedule>=1.2.0 # 定时任务调度 +python-dotenv>=1.0.0 # 环境变量管理(可选但推荐) +charset-normalizer>=3.3.2 # 自动识别网页编码(解决乱码) + +# 可选优化依赖(提升体验) +jieba>=0.42.1 # 中文分词(优化BM25检索精度) +pytest>=7.4.0 # 单元测试(可选,用于代码验证) +loguru>=0.7.2 # 更美观的日志输出(替换自定义日志) +tqdm>=4.66.1 # 进度条(爬取/更新时展示进度) \ No newline at end of file diff --git "a/news-agent/\351\241\271\347\233\256\350\257\264\346\230\216.md" "b/news-agent/\351\241\271\347\233\256\350\257\264\346\230\216.md" new file mode 100644 index 0000000..736e2f8 --- /dev/null +++ "b/news-agent/\351\241\271\347\233\256\350\257\264\346\230\216.md" @@ -0,0 +1,479 @@ +# 基于商汤LazyLLM的财务新闻助手Agent项目文档(完整版) + +## 一、项目介绍 + +### 1. 项目背景 + +在信息爆炸的时代,财经新闻呈现碎片化、更新快、专业度高的特点,个人投资者、企业财务人员、行业研究员等用户面临三大核心痛点:① 新闻获取分散,需切换多个平台筛选信息;② 关键信息提取低效,难以快速抓取新闻核心价值;③ 专业解读不足,普通用户难以判断新闻对行业、公司的财务影响。 + +为解决上述问题,本项目基于商汤LazyLLM框架开发**财务新闻助手Agent**,整合权威财经数据源,通过RAG(检索增强生成)技术实现“实时新闻聚合-精准检索-专业解读-结构化输出”全流程自动化,为用户提供高效、专业、易用的财务新闻分析工具。 + +### 2. 项目定位 + +- **核心目标**:打造“实时性+专业性+易用性”兼具的财务新闻分析助手,降低用户获取财经信息的门槛,辅助投资决策与行业研究。 +- **目标用户**:个人投资者、企业财务分析师、行业研究员、金融从业者等关注财经动态的人群。 +- 核心价值 + - 实时性:每2小时同步权威财经新闻,确保信息时效性; + - 精准性:基于中文语义检索,过滤无关信息,聚焦用户关注的行业/公司/政策; + - 专业性:解析新闻背后的财务影响(利好/利空、风险/机遇),提供中立投资参考; + - 易用性:支持自然语言查询,结构化输出结果,无需专业知识即可快速理解。 + +### 3. 核心功能 + +| 功能模块 | 功能描述 | 应用场景 | +| -------------- | ------------------------------------------------------------ | ---------------------------------------- | +| 新闻聚合与更新 | 自动爬取东方财富网、证券时报网等权威平台新闻,按日期分类存储,支持定时更新 | 无需手动切换平台,获取最新财经动态 | +| 自然语言查询 | 支持行业、公司、政策、市场热点等多维度自然语言查询(如“新能源行业最新补贴政策”) | 快速定位目标领域新闻,替代关键词反复搜索 | +| 关键信息提取 | 自动提炼新闻核心要素(标题、来源、发布时间、核心事件、相关主体) | 节省阅读时间,快速掌握新闻核心 | +| 财务影响分析 | 基于新闻内容解读短期影响、潜在风险/机遇,提供中立投资参考 | 辅助用户判断新闻对投资、业务的实际价值 | +| 结构化输出 | 以“新闻汇总+影响分析+投资参考”的固定框架输出结果,格式清晰 | 便于用户快速浏览和二次使用(如报告撰写) | +| 新闻过滤机制 | 自动过滤30天以上过期新闻、重复新闻,确保信息有效性 | 避免冗余信息干扰,提升查询效率 | + +### 4. 项目创新点 + +- **轻量化架构**:基于商汤LazyLLM框架快速搭建,模块化设计降低开发复杂度,支持灵活扩展; +- **中文优化检索**:采用BM25中文语义检索算法,适配财经领域专业术语,提升检索精准度; +- **结构化生成**:通过自定义Prompt模板引导LLM输出固定格式结果,解决传统LLM回答杂乱的问题; +- **工程化落地**:集成日志记录、异常处理、新闻去重/过期过滤等功能,确保系统稳定运行。 + +## 二、成员详情 + +**马增群**(队长): + +* 技术探索:分析 LazyLLM 框架,接入大模型实现智能问答。 + +* 核心工具开发:独立设计并编写了财务新闻数据爬取功能。 + +* 应用代码实现:编写并迭代了主应用文件,整合了模型、工具和 Web 界面。 + +* 文档撰写:根据要求整理并撰写了本技术文档。 + +## 三、技术栈 + +### 1. 核心框架 + +- **LazyLLM**:商汤开源大模型应用开发框架,提供Pipeline编排、RAG检索、模型接入等核心能力,支撑Agent快速搭建; +- **SenseNova大模型**:商汤SenseNova-V6-5-Turbo,用于自然语言理解、新闻解读与结构化生成,兼顾速度与专业度。 + +### 2. 开发语言与工具 + +- **开发语言**:Python 3.10(LazyLLM最佳兼容版本); +- **数据爬取**:Requests(HTTP请求)、BeautifulSoup4(网页解析); +- **定时任务**:Schedule(定时新闻爬取与知识库更新); +- **存储方案**:本地文件存储(新闻数据、日志文件),支持扩展至Milvus向量数据库; +- **Web服务**:LazyLLM内置WebModule(快速搭建交互界面); +- **日志工具**:自定义日志模块(记录系统运行状态、异常信息)。 + +### 3. 关键技术 + +- **RAG检索增强生成**:结合BM25中文语义检索与LLM生成,确保回答的准确性与相关性; +- **数据预处理**:新闻去重(基于标题+发布时间)、过期过滤(30天有效期)、正文截取(控制上下文长度); +- **结构化Prompt工程**:设计固定输出模板,引导LLM生成“新闻汇总+影响分析+投资参考”的结构化结果; +- **多线程并发**:后台线程运行定时爬取任务,不影响前端Web交互性能。 + +## 四、系统架构设计 + +### 1. 整体架构图 + +``` +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ 用户层 │ │ 交互层 │ │ 核心层 │ │ 数据层 │ +│ (Web浏览器) │─────▶│ (LazyLLM Web)│─────▶│ (Agent工作流)│─────▶│ (新闻知识库)│ +└───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘ + ▲ + │ + ┌───────────────┐ + │ 数据源层 │ + │(权威财经平台)│ + └───────────────┘ +``` + +### 2. 核心层工作流程 + +``` +用户查询 → 意图识别 → RAG检索(知识库)→ 上下文格式化 → LLM结构化生成 → 结果输出 +``` + +- **意图识别**:解析用户查询关键词(如行业、公司、政策),优化检索方向; +- **RAG检索**:基于BM25中文算法从知识库中检索Top5相关、30天内的新闻; +- **上下文格式化**:整合检索结果,提取新闻标题、来源、正文等关键信息,生成LLM可理解的上下文; +- **LLM生成**:基于自定义Prompt模板,生成结构化回答(新闻汇总+影响分析+投资参考)。 + +### 3. 数据层设计 + +- **新闻存储结构**:按“日期文件夹”分类(如`./financial_news_data/2024-09-20`),单条新闻以JSON文件存储,包含字段:标题、链接、发布时间、来源、正文、行业、爬取时间; +- **日志存储**:`./agent_logs/agent.log`,记录系统启动、知识库更新、爬取结果、异常信息等,便于问题定位; +- **知识库更新**:每2小时自动爬取最新新闻,去重后更新本地存储,LazyLLM实时加载新数据。 + +## 五、核心代码说明 + +### 1. 代码整体结构 + +核心代码文件`news_agent.py`分为9个核心模块,各模块职责与关键代码说明如下: + +### 2. 关键模块代码解析 + +#### (1)全局配置模块 + +```python +# ======================== 全局配置项(请根据实际情况修改)======================== +# 1. 基础路径配置 +NEWS_DATA_PATH = "./financial_news_data" # 新闻本地存储路径 +LOG_PATH = "./agent_logs" # 日志存储路径 +# 2. 服务配置 +PORT = 23467 # Web服务端口 +TOP_K = 5 # 检索返回的新闻数量 +MAX_CONTEXT_LEN = 500 # 新闻正文截取长度(避免LLM上下文过长) +NEWS_EXPIRE_DAYS = 30 # 仅保留30天内的新闻 +# 3. 模型配置 +MODEL_NAME = "SenseNova-V6-5-Turbo" # 商汤大模型名称 +TEMPERATURE = 0.3 # 生成温度(越低越稳定) +# 4. 爬虫配置 +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..." +CRAWL_INTERVAL_HOURS = 2 # 新闻爬取间隔(小时) +``` + +**核心作用**:集中管理系统所有可配置参数,便于后续调整(如修改爬取频率、检索数量、模型参数等),提升代码可维护性。 + +#### (2)新闻爬虫模块(核心函数:crawl_financial_news) + +```python +def crawl_financial_news(): + """爬取东方财富网行业新闻列表""" + news_list = [] + try: + base_url = "https://finance.eastmoney.com/industry/" + headers = {"User-Agent": USER_AGENT} + response = requests.get(base_url, headers=headers, timeout=10) + response.encoding = response.apparent_encoding + soup = BeautifulSoup(response.text, "html.parser") + + # 解析新闻列表(适配网页结构) + news_items = soup.select(".news-item") or soup.select(".item") + for idx, item in enumerate(news_items[:20]): # 每次爬取前20条最新新闻 + try: + # 提取基础信息 + title_tag = item.select_one("a[title]") or item.select_one(".title a") + title = title_tag.text.strip() if title_tag else f"无标题_{idx}" + link = title_tag["href"] if (title_tag and "href" in title_tag.attrs) else "" + # 补全相对链接 + if link and not link.startswith("http"): + link = f"https:{link}" if link.startswith("//") else f"https://finance.eastmoney.com{link}" + + # 提取发布时间和来源 + time_tag = item.select_one(".time") or item.select_one(".publish-time") + publish_time = time_tag.text.strip() if time_tag else datetime.now().strftime("%Y-%m-%d %H:%M:%S") + source_tag = item.select_one(".source") or item.select_one(".author") + source = source_tag.text.strip() if source_tag else "未知来源" + + # 爬取正文 + content = crawl_news_content(link) + + # 结构化存储 + news_info = { + "title": title, "link": link, "publish_time": publish_time, + "source": source, "content": content, "industry": "综合财经", + "crawl_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + news_list.append(news_info) + except Exception as e: + log_info(f"解析单条新闻失败(序号:{idx}):{str(e)}") + continue + except Exception as e: + log_info(f"新闻爬取整体失败:{str(e)}") + return news_list +``` + +**核心作用**: + +- 从东方财富网爬取最新财经新闻列表,提取标题、链接、发布时间、来源等基础信息; +- 调用`crawl_news_content`函数爬取单篇新闻正文; +- 异常处理:单条新闻解析失败不影响整体爬取,提升鲁棒性; +- 链接补全:处理相对链接,确保后续可访问原文。 + +#### (3)知识库定时更新模块 + +```python +def update_news_knowledge_base(): + """定时爬取新闻并更新LazyLLM知识库""" + log_info("开始更新财务新闻知识库...") + # 1. 爬取最新新闻 + crawled_news = crawl_financial_news() + # 2. 保存到本地(自动去重) + save_news_to_local(crawled_news, NEWS_DATA_PATH) + # 3. 验证知识库文件数量 + total_files = 0 + for root, dirs, files in os.walk(NEWS_DATA_PATH): + total_files += len([f for f in files if f.endswith(".json")]) + log_info(f"知识库更新完成,本地累计新闻文件数:{total_files}") + +def start_schedule_task(): + """启动定时任务(后台线程运行)""" + # 立即执行一次更新 + update_news_knowledge_base() + # 设定定时任务:每CRAWL_INTERVAL_HOURS小时更新一次 + schedule.every(CRAWL_INTERVAL_HOURS).hours.do(update_news_knowledge_base) + log_info(f"定时任务启动:每{CRAWL_INTERVAL_HOURS}小时更新一次知识库") + + # 后台循环执行定时任务 + while True: + schedule.run_pending() + time.sleep(60) +``` + +**核心作用**: + +- `update_news_knowledge_base`:串联“爬取-保存-验证”全流程,确保知识库数据最新; +- `start_schedule_task`:后台线程运行定时任务,避免阻塞Web服务,实现“无需人工干预”的自动更新; +- 首次启动立即执行更新,保证用户首次使用时有数据可用。 + +#### (4)LazyLLM RAG检索模块 + +```python +def build_financial_agent(): + """构建财务新闻助手工作流""" + # 1. 加载知识库 + documents = load_news_documents(NEWS_DATA_PATH) + + # 2. 定义结构化Prompt模板(核心:引导LLM输出固定格式) + news_prompt = """ +你是一名专业的财务新闻分析助手,专注于解读财经新闻的核心信息与财务影响。 +请基于用户查询和检索到的新闻内容,按以下框架生成结构化回答: + +## 一、相关新闻汇总(按发布时间倒序) +### 新闻1:{title}(来源:{source} | 发布时间:{publish_time}) +- 核心摘要:[提炼100字内核心内容] +- 关键信息:[时间、主体、事件、数据等关键点] + +## 二、财务影响分析 +### 1. 直接影响 +- 对相关行业/公司的短期影响 +### 2. 潜在风险/机遇 +- 隐藏的风险点或机遇 +### 3. 投资参考 +- 中性、专业的投资决策参考(不构成具体买卖建议) + """ + + # 3. 编排LazyLLM工作流 + with lazyllm.pipeline() as ppl: + # 检索组件:中文优化的BM25检索,过滤过期新闻 + ppl.retriever = lazyllm.Retriever( + doc=documents, + group_name="FinancialNews", + similarity="bm25_chinese", # 适配中文的检索算法 + topk=TOP_K, + filter_fn=lambda node: _filter_expired_news(node) # 过滤过期新闻 + ) + + # 上下文格式化:整合检索结果为LLM输入 + def format_context(nodes, query): + if not nodes: + return {"query": query, "context_str": ""} + context_str = "\n=== 检索到的相关新闻 ===\n" + for i, node in enumerate(nodes, 1): + title = node.meta.get("title", "无标题") + source = node.meta.get("source", "未知来源") + publish_time = node.meta.get("publish_time", "未知时间") + content = node.get_content()[:MAX_CONTEXT_LEN] + context_str += f""" +### 新闻{i} +标题:{title} +来源:{source} +发布时间:{publish_time} +正文摘要:{content} +链接:{node.meta.get('link', '无')} +""" + return {"query": query, "context_str": context_str} + + ppl.formatter = format_context | bind(query=ppl.input) + + # 生成组件:接入商汤SenseNova大模型 + ppl.llm = lazyllm.OnlineChatModule( + model=MODEL_NAME, + stream=True, # 流式输出,提升交互体验 + temperature=TEMPERATURE, + timeout=60 + ).prompt(lazyllm.ChatPrompter( + instruction=news_prompt, + extra_keys=["context_str"] + )) + + return ppl +``` + +**核心作用**: + +- **Retriever组件**:基于BM25中文算法检索相关新闻,通过`filter_fn`过滤30天以上过期新闻,确保检索结果时效性; +- **format_context函数**:将检索到的新闻节点转换为LLM可理解的文本格式,控制正文长度避免上下文超限; +- **OnlineChatModule**:接入商汤SenseNova大模型,通过自定义Prompt模板强制LLM输出结构化结果,解决回答格式混乱问题; +- **stream=True**:开启流式输出,用户可实时看到回答生成过程,提升交互体验。 + +#### (5)主函数(服务启动) + +```python +if __name__ == "__main__": + # 创建必要的目录 + os.makedirs(NEWS_DATA_PATH, exist_ok=True) + os.makedirs(LOG_PATH, exist_ok=True) + + # 1. 启动定时更新知识库的后台线程 + schedule_thread = threading.Thread(target=start_schedule_task, daemon=True) + schedule_thread.start() + log_info("后台定时更新线程已启动") + + # 2. 构建Agent工作流 + financial_agent = build_financial_agent() + + # 3. 启动Web服务 + log_info(f"\n财务新闻助手Agent启动成功!") + log_info(f"Web交互地址:http://127.0.0.1:{PORT}") + log_info(f"提示:首次使用请等待知识库更新完成(约1-2分钟)") + lazyllm.WebModule(financial_agent, port=PORT).start().wait() +``` + +**核心作用**: + +- 初始化目录:自动创建新闻存储、日志目录,避免手动创建; +- 多线程:后台线程运行定时更新任务,不阻塞Web服务; +- WebModule:基于LazyLLM快速搭建Web交互界面,无需额外开发前端代码。 + +### 3. 核心代码亮点 + +| 亮点 | 代码体现 | 价值 | +| ------------- | ---------------------------------------- | ------------------------------------- | +| 模块化设计 | 按“配置-爬虫-知识库-工作流-启动”拆分模块 | 代码结构清晰,便于维护和扩展 | +| 异常容错 | 爬虫、文件保存、模型调用均加异常处理 | 单个环节失败不影响整体系统运行 | +| 数据去重/过滤 | 基于标题+发布时间去重、30天过期过滤 | 提升知识库数据质量,避免冗余信息 | +| 结构化生成 | 自定义Prompt模板+固定输出框架 | 解决LLM回答格式混乱问题,提升用户体验 | +| 工程化特性 | 日志记录、多线程、自动目录创建 | 符合生产级代码规范,支持长期稳定运行 | + +## 六、接口设计 + +### 1. 内部模块接口 + +#### (1)新闻爬取模块接口 + +| 接口名称 | 函数名 | 输入参数 | 输出参数 | 功能描述 | +| ------------ | -------------------- | ------------------------------------------------------------ | ---------------------------- | ---------------------------------------------------- | +| 单篇新闻爬取 | crawl_news_content | url: 新闻详情页链接(str) | 新闻正文(str) | 爬取指定链接的新闻正文,处理编码与超时异常 | +| 新闻列表爬取 | crawl_financial_news | 无 | 结构化新闻列表(list[dict]) | 爬取权威平台新闻列表,提取标题、来源、发布时间等信息 | +| 新闻本地存储 | save_news_to_local | news_list: 新闻列表(list[dict]), base_path: 存储根路径(str) | 无 | 按日期分类保存新闻到本地,自动去重 | + +#### (2)知识库模块接口 + +| 接口名称 | 函数名 | 输入参数 | 输出参数 | 功能描述 | +| ---------- | -------------------------- | ------------------------------ | -------------------- | ---------------------------------------------- | +| 知识库加载 | load_news_documents | data_path: 新闻存储路径(str) | LazyLLM Document对象 | 加载本地新闻文件,补充元数据,生成检索用文档 | +| 知识库更新 | update_news_knowledge_base | 无 | 无 | 调用爬取接口获取最新新闻,更新本地存储与知识库 | + +#### (3)Agent工作流接口 + +| 接口名称 | 组件 | 输入参数 | 输出参数 | 功能描述 | +| ------------ | ---------------- | -------------------------------------------------------- | -------------------------- | ---------------------------------------------- | +| 检索组件 | Retriever | query: 用户查询(str) | 相关新闻节点(list[Node]) | 基于BM25中文算法检索Top5相关新闻,过滤过期数据 | +| 上下文格式化 | format_context | nodes: 检索结果(list[Node]), query: 用户查询(str) | 格式化上下文(dict) | 整合检索结果,生成LLM输入上下文 | +| 生成组件 | OnlineChatModule | context_str: 格式化上下文(str), query: 用户查询(str) | 结构化回答(str) | 调用SenseNova大模型,按Prompt模板生成回答 | + +### 2. 外部交互接口 + +| 接口类型 | 访问方式 | 输入参数 | 输出结果 | 功能描述 | +| ----------- | --------------------------------- | ------------------------ | ---------------------- | ---------------------------------------------- | +| Web交互接口 | 浏览器访问 http://127.0.0.1:23467 | 用户查询(自然语言文本) | 结构化回答(HTML格式) | 提供可视化交互界面,用户输入查询后返回分析结果 | + +## 七、部署与运行说明 + +### 1. 环境准备 + +#### (1)硬件要求 + +- 最低配置:CPU ≥ 4核,内存 ≥ 8GB,硬盘 ≥ 10GB(用于存储新闻与日志); +- 推荐配置:CPU ≥ 8核,内存 ≥ 16GB(提升检索与生成速度)。 + +#### (2)软件环境 + +- 操作系统:Windows 10/11、MacOS 12+、Linux(Ubuntu 20.04+); + +- Python版本:3.10(需提前安装); + +- 依赖安装:执行以下命令安装项目所需依赖: + + ```bash + # 基础依赖 + pip install lazyllm requests beautifulsoup4 schedule python-dotenv + # 可选:中文分词优化(提升检索精度) + pip install jieba + ``` + +#### (3)密钥配置 + +1. 登录商汤大装置平台:https://console.sensecore.cn/aistudio/management/service-manage; + +2. 开通SenseNova-V6-5-Turbo模型服务,创建AccessKey与SecretKey; + +3. 配置环境变量(终端执行): + + ```bash + # Windows + set LAZYLLM_SENSENOVA_API_KEY="你的API_KEY" + set LAZYLLM_SENSENOVA_SECRET_KEY="你的SECRET_KEY" + + # Mac/Linux + export LAZYLLM_SENSENOVA_API_KEY="你的API_KEY" + export LAZYLLM_SENSENOVA_SECRET_KEY="你的SECRET_KEY" + ``` + +### 2. 运行步骤 + +1. 下载项目代码,将核心代码保存为`financial_news_agent.py`; + +2. 终端进入代码所在目录,执行启动命令: + + ```bash + python financial_news_agent.py + ``` + +3. 系统启动后,终端会输出以下信息(表示启动成功): + + ``` + [2024-09-20 10:00:00] 后台定时更新线程已启动 + [2024-09-20 10:00:01] 开始更新财务新闻知识库... + [2024-09-20 10:00:05] 成功爬取新闻:XXX行业迎政策红利... + [2024-09-20 10:00:08] 成功保存 18 条新闻到本地(去重后) + [2024-09-20 10:00:09] 知识库更新完成,本地累计新闻文件数:18 + [2024-09-20 10:00:10] 成功加载 18 条新闻到LazyLLM知识库 + [2024-09-20 10:00:10] 财务新闻助手Agent启动成功! + [2024-09-20 10:00:10] Web交互地址:http://127.0.0.1:23467 + [2024-09-20 10:00:10] 提示:首次使用请等待知识库更新完成(约1-2分钟) + ``` + +4. 打开浏览器,访问`http://127.0.0.1:23467`,进入交互界面,输入查询即可获取结果。 + + + +分析财务年报: + +![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/aeb4943e43374f62b58b1f6a23d5f98a.png) + +### 3. 注意事项 + +- 首次运行需等待1-2分钟,待知识库初始化完成后再进行查询; +- 爬虫模块适配东方财富网当前网页结构,若网页更新导致爬取失败,需调整`crawl_financial_news`中的选择器(如`.news-item`、`.main-content p`); +- 系统默认每2小时更新一次知识库,可通过修改`CRAWL_INTERVAL_HOURS`参数调整更新频率; +- 若出现模型调用失败,需检查API密钥是否有效、模型服务是否开通。 + + + +## 八、项目总结与展望 + +### 1. 项目总结 + +本项目基于商汤LazyLLM框架,成功实现了一款功能完整、可落地的财务新闻助手Agent。通过整合新闻爬取、定时更新、RAG检索、LLM生成等核心能力,解决了财经新闻获取碎片化、解读专业度不足的问题,为用户提供了高效、专业的信息分析工具。 + +项目亮点:① 轻量化架构,基于LazyLLM快速搭建,开发效率高;② 中文优化检索,适配财经领域场景,检索精度高;③ 结构化输出,用户体验佳;④ 工程化设计,支持稳定运行与后续扩展。 + +### 2. 未来展望 + +- **数据源扩展**:接入同花顺、证监会公告、Wind、Bloomberg等更多权威数据源,覆盖海外财经新闻与深度研报; +- **功能增强**:新增多维度筛选(时间、行业、新闻类型)、负面新闻预警、结果导出(PDF/Excel)、关联分析(如“特斯拉降价对国内车企的影响”); +- **性能优化**:引入Milvus向量数据库,提升百万级新闻数据的检索速度;优化Prompt工程,提升分析的深度与准确性; +- **交互升级**:开发移动端适配界面,支持语音查询、个性化推荐(基于用户查询历史)等功能。 -- Gitee