1. 项目概述:一个能自动追踪AI新闻的智能体
最近在GitHub上看到一个挺有意思的项目,叫ai-news-weekly-agent。光看名字,你大概能猜到它是个和AI新闻相关的自动化工具。没错,它的核心目标就是扮演一个“AI新闻周刊编辑”的角色,自动从互联网上抓取、筛选、整理并生成一份关于人工智能领域最新动态的周报。
我自己也经常需要关注AI领域的前沿进展,无论是为了技术选型、寻找灵感,还是单纯保持对行业的敏感度。但信息爆炸的时代,每天都有海量的论文、产品发布、技术博客和行业新闻涌现,手动去追踪和筛选,不仅耗时耗力,还容易遗漏关键信息。这个项目正好切中了这个痛点——它试图用代码和智能体(Agent)技术,将“信息收集-处理-输出”这个繁琐的流程自动化。
简单来说,ai-news-weekly-agent是一个基于大语言模型(LLM)驱动的智能体系统。它每周会自动运行,通过预设的规则或指令,去指定的信息源(如技术社区、新闻网站、论文预印本平台、知名博客等)爬取内容。然后,利用LLM的理解和总结能力,对这些原始信息进行重要性排序、去重、分类,并最终生成一份结构清晰、重点突出的中文周报,可能以Markdown、PDF或邮件订阅的形式交付给用户。
这个项目的价值在于,它将一个信息工作者的核心工作流(信息感知、筛选、整合、报告)进行了程序化封装。对于开发者、研究者、产品经理,甚至是投资分析师来说,拥有这样一个“永不疲倦的AI信息助理”,能极大地提升信息获取的效率和质量,让你把精力更集中在深度思考和决策上,而不是淹没在信息的海洋里。
2. 核心架构与工作流拆解
要理解ai-news-weekly-agent是如何工作的,我们需要深入其内部,拆解它的核心架构。一个典型的自动化新闻聚合智能体,其工作流可以抽象为四个核心阶段:信息采集、内容处理、报告生成与任务调度。
2.1 信息采集层:数据源的规划与获取
这是整个流程的起点,决定了周报内容的广度和质量。一个设计良好的采集层需要兼顾覆盖面和精准度。
2.1.1 核心数据源选择
项目通常会预设一个高质量的信息源列表。这些源站的选择至关重要,它们必须是AI领域信息的一手或高质量聚合地。常见的来源包括:
- 学术前沿:arXiv(cs.AI, cs.CL, cs.CV, cs.LG等子版块)、Papers with Code。这里能获取最新的研究论文。
- 技术社区与博客:Hacker News的AI板块、Reddit的r/MachineLearning、知名公司的技术博客(如OpenAI Blog, DeepMind Blog, Anthropic Blog)、以及一些顶级研究者的个人博客。
- 行业新闻与资讯:TechCrunch, The Verge的AI板块,以及一些垂直媒体如MIT Technology Review的相关报道。
- 开源项目动态:GitHub Trending中与AI相关的仓库,或者关注特定组织(如huggingface, langchain)的更新。
注意:数据源的配置需要谨慎。过多的源站会导致信息过载和运行缓慢,过少则可能遗漏重要新闻。一个好的实践是建立一个“核心源”(必读)和“扩展源”(可选扫描)的层级列表,并在运行中根据历史反馈动态调整。
2.1.2 采集技术实现
采集通常通过API和网络爬虫结合的方式实现。
- API优先:对于提供开放API的平台(如GitHub API, arXiv API, Hacker News Firebase API),应优先使用。这更稳定、更友好,且通常有速率限制,需要代码中做好请求间隔和错误处理。
- 爬虫作为补充:对于没有API或API信息不全的网站,则需要使用轻量级爬虫。常用的工具包括
requests+BeautifulSoup(静态页面)或Playwright/Selenium(动态渲染页面)。这里必须遵守网站的robots.txt协议,并设置合理的请求头(User-Agent)和请求间隔,避免对目标服务器造成压力。 - RSS订阅:许多博客和新闻网站仍提供RSS源,使用
feedparser这样的库来解析RSS是最简单高效的方式之一。
在ai-news-weekly-agent的实现中,可能会为每一类数据源编写一个独立的“采集器”(Collector)模块,每个模块负责处理特定站点的数据获取和初步解析(如提取标题、链接、发布时间、摘要等),并输出结构化的数据条目。
2.2 内容处理层:从数据到信息的提炼
采集到的原始数据条目是杂乱无章的。内容处理层的任务就是利用LLM的智能,将这些数据转化为有价值的信息。
2.2.1 去重与聚类
同一事件可能被多个源站报道。首先需要进行去重。简单的基于标题或链接的精确匹配效果有限,更智能的方法是使用文本嵌入(Embedding)。例如,使用OpenAI的text-embedding-3-small或开源的BGE模型,为每个条目的标题和摘要生成向量,然后计算向量之间的余弦相似度。相似度超过某个阈值(如0.85)的条目可以被视为同一事件,进行合并,并保留最早或最权威的来源信息。
2.2.2 重要性评估与过滤
不是所有抓取到的内容都值得进入周报。这里需要LLM出场。我们可以设计一个提示词(Prompt),让LLM扮演“资深AI编辑”,对每个条目进行打分和分类。
一个典型的Prompt可能是:
你是一位专注于人工智能领域的资深技术编辑。请评估以下新闻/论文/博客的重要性,并给出理由。 评估维度: 1. 技术突破性(0-10分):是否提出了颠覆性的新方法、模型或理论? 2. 行业影响力(0-10分):是否会对业界产品、投资或就业市场产生显著影响? 3. 社区关注度(0-10分):是否在开发者/研究社区引发了广泛讨论? 4. 可访问性(0-10分):其代码、模型或服务是否已开源或可用? 请根据以上维度,对以下内容进行综合评分(0-40分),并判断其所属类别(如“大模型技术”、“多模态”、“AI基础设施”、“伦理与治理”、“产品发布”、“融资动态”等)。 内容标题:[标题] 内容摘要:[摘要] 来源:[来源]LLM会返回一个结构化的JSON,包含分数和类别。我们可以设定一个阈值(比如总分高于20分),只保留高分条目进入下一阶段。这个过程也完成了初步的分类。
2.2.3 关键信息提取与摘要生成
对于通过筛选的条目,需要进一步加工。我们可以再次调用LLM,为每个条目生成一个更精炼的摘要,并提取关键信息点,例如:
- 论文:核心创新点、性能提升(SOTA)、代码/模型地址。
- 产品发布:主要功能、定价变化、目标用户。
- 行业新闻:涉及公司、交易金额、潜在影响。
这个摘要将直接用于周报的撰写,因此要求准确、简洁、包含干货。
2.3 报告生成层:从信息到知识的结构化呈现
经过处理的信息是零散的“珍珠”,报告生成层负责将它们串成一条“项链”。
2.3.1 内容组织与大纲生成
首先,LLM需要根据所有条目的类别和重要性,生成本周报的目录大纲。例如:
# AI Weekly Digest (YYYY-MM-DD) ## 一、 大模型前沿 ### 1.1 模型架构新进展 ### 1.2 高效训练与推理 ## 二、 多模态与具身智能 ## 三、 AI基础设施与工具 ## 四、 行业动态与商业应用 ## 五、 开源项目推荐这个大纲为后续的内容填充提供了骨架。
2.3.2 章节内容撰写
接着,LLM会以“编辑”的身份,根据大纲和每个分类下的条目列表,撰写完整的章节内容。这里不再是简单的条目罗列,而是需要LLM进行连贯的叙述,可能包括背景介绍、事件串联、简要评论等,使周报读起来像是由人撰写的。
2.3.3 格式渲染与输出
最后,将LLM生成的Markdown文本进行最终渲染。这可能包括:
- 添加固定的页眉页脚(如项目说明、订阅方式)。
- 确保所有链接格式正确。
- 将Markdown转换为更美观的格式,如通过
WeasyPrint生成PDF,或直接发布到静态网站(如GitHub Pages)。 - 集成邮件发送功能(使用
smtplib或邮件服务商API),将周报推送给订阅者。
2.4 任务调度与运维层:让智能体自主运行
一个真正的“智能体”需要能定期、无人值守地运行。这一层关注系统的可持续性。
2.4.1 调度系统
最简单的方式是使用服务器的Cron Job(Linux)或Task Scheduler(Windows),定时(如每周一凌晨2点)执行主程序脚本。更云原生的做法是使用GitHub Actions的schedule事件,这样无需自有服务器,且能与代码仓库集成,运行日志清晰可见。在GitHub Actions的.yml配置文件中,可以这样设置:
on: schedule: - cron: '0 2 * * 1' # 每周一UTC时间2点(北京时间10点)运行 workflow_dispatch: # 允许手动触发2.4.2 状态管理与错误处理
智能体运行中可能遇到各种问题:网络超时、API额度用尽、网站改版导致爬虫失效、LLM调用失败等。健壮的系统需要:
- 日志记录:详细记录每个阶段的运行状态、获取的条目数、处理结果、遇到的错误,便于后期排查。
- 检查点(Checkpoint):在关键步骤后保存中间状态(如采集到的原始数据、处理后的条目列表)。如果后续步骤失败,可以从检查点恢复,避免重复工作(特别是昂贵的LLM调用)。
- 失败重试与降级策略:对网络请求和API调用设置重试机制。如果某个数据源完全失效,系统应能跳过它并继续运行,同时在日志中告警,而不是整体崩溃。
- 通知机制:当运行失败或生成的内容异常(如条目数过少)时,通过邮件、Slack或钉钉机器人通知维护者。
2.4.3 成本控制与优化
LLM API调用是主要成本。需要优化:
- 批量处理:将多个条目的评估或摘要任务合并到一个Prompt中调用,减少请求次数。
- 模型选型:对于重要性评估、分类等任务,可以使用更便宜、更快的模型(如GPT-3.5-Turbo);对于最终的报告撰写,再使用能力更强的模型(如GPT-4)。
- 缓存策略:对于短期内重复出现或相似的新闻,可以使用缓存,避免重复调用LLM进行分析。
3. 关键技术点与工具选型解析
构建ai-news-weekly-agent这样的项目,技术选型直接决定了开发效率和最终效果。下面我们来拆解几个关键的技术组件及其常见的选型方案。
3.1 大语言模型(LLM)接入与提示工程
LLM是整个系统的“大脑”,其选型和Prompt设计是核心。
3.1.1 模型服务选型
- 闭源API(易用、能力强):
- OpenAI GPT系列:生态最成熟,API稳定,文档齐全。
gpt-4o或gpt-4-turbo在理解和生成能力上表现优异,但成本较高。gpt-3.5-turbo性价比高,适合处理大量文本分类和摘要任务。 - Anthropic Claude系列:以长上下文和强指令跟随著称,适合处理需要整合大量信息的报告生成任务。
- 国内大厂API:如百度文心、阿里通义、智谱GLM等,访问速度和合规性有优势。
- OpenAI GPT系列:生态最成熟,API稳定,文档齐全。
- 开源模型自部署(可控、成本固定):
- 轻量级模型:如
Qwen2.5-7B-Instruct,Llama-3.2-3B-Instruct,它们参数量小,可以在消费级显卡(甚至CPU)上运行,适合对实时性要求不高或希望完全控制数据的场景。可以使用vLLM,TGI(Text Generation Inference) 等框架进行高效部署。 - 重量级模型:如
Qwen2.5-72B-Instruct,Llama-3.1-70B,能力接近第一梯队闭源模型,但需要强大的GPU服务器,运维成本高。
- 轻量级模型:如
实操心得:对于个人或小团队项目,初期强烈建议从OpenAI或Claude的API开始。这能让你快速验证想法和流程,把精力集中在业务逻辑而非模型部署上。当流程跑通且成本成为主要考量时,再考虑将部分任务(如文本分类、摘要)迁移到本地部署的轻量开源模型上,形成混合架构。
3.1.2 提示工程(Prompt Engineering)实践
Prompt是驱动LLM工作的“指令集”,设计好坏直接影响结果质量。
- 角色扮演(Role Playing):如“你是一位资深的AI科技媒体编辑”,这能有效引导模型输出符合特定风格和深度的内容。
- 结构化输出(Structured Output):明确要求模型以JSON、XML或特定Markdown格式返回结果,便于后续程序化处理。例如,在评估重要性时,直接要求返回
{"score": 25, "category": "大模型技术", "reason": "..."}。 - 少样本学习(Few-shot Learning):在Prompt中提供1-3个高质量的例子(输入和期望的输出),能显著提升模型在特定任务上的表现。例如,给一个新闻标题和摘要,然后展示一个理想的重要性评估结果。
- 链式思考(Chain-of-Thought):对于复杂任务,鼓励模型“一步一步思考”,可以提高推理的准确性和可靠性。
一个综合性的Prompt模板可能长这样:
你是一位专注于人工智能领域的资深技术编辑,负责为专业读者筛选和总结本周重要动态。 ## 你的任务 对给定的内容条目进行重要性评估和摘要。 ## 输出格式 你必须严格按照以下JSON格式输出,不要有任何其他文字: { “overall_score”: 一个0到40的整数, “category”: “分类名称”, “key_points”: [“要点1”, “要点2”, “要点3”], “concise_summary”: “一段不超过100字的中文摘要” } ## 评估标准 - 技术突破性 (0-10): ... - 行业影响力 (0-10): ... - ... ## 示例 输入: {“title”: “...”, “abstract”: “...”} 输出: {“overall_score”: 32, “category”: “...”, ...} ## 现在开始处理 输入: {“title”: “{{title}}”, “abstract”: “{{abstract}}”}3.2 数据采集与处理的工程实现
3.2.1 异步并发采集
为了提高采集效率,必须使用异步IO。Python的asyncio库配合aiohttp是标准选择。可以构建一个异步的采集器池,同时并发抓取多个数据源。
import aiohttp import asyncio from bs4 import BeautifulSoup async def fetch_url(session, url): try: async with session.get(url, headers=headers, timeout=10) as response: html = await response.text() # 使用BeautifulSoup解析 soup = BeautifulSoup(html, 'html.parser') # ... 提取逻辑 ... return extracted_data except Exception as e: logging.error(f“Failed to fetch {url}: {e}”) return None async def main(urls): async with aiohttp.ClientSession() as session: tasks = [fetch_url(session, url) for url in urls] results = await asyncio.gather(*tasks, return_exceptions=True) # 处理结果3.2.2 向量数据库用于智能去重
如前所述,基于语义的去重需要计算文本嵌入的相似度。虽然可以实时计算,但对于历史数据比对,使用向量数据库(Vector Database)更高效。你可以将过去几周处理过的条目向量存入数据库,当新条目到来时,进行相似度查询。
- 轻量级选择:
ChromaDB或FAISS。它们易于集成,特别是ChromaDB,API简单,支持持久化。 - 生产级选择:
Qdrant,Weaviate,Milvus。它们功能更强大,支持分布式、更丰富的查询条件。
基本流程:1) 用嵌入模型将新条目文本转换为向量;2) 在向量库中搜索Top-K个最相似的向量;3) 如果相似度超过阈值,则视为重复或高度相关,进行合并处理。
3.3 系统架构与代码组织
一个清晰的项目结构有助于长期维护。
ai-news-weekly-agent/ ├── config.yaml # 配置文件:API密钥、数据源列表、阈值参数等 ├── main.py # 主程序入口,协调整个流程 ├── src/ │ ├── collectors/ # 采集器模块 │ │ ├── __init__.py │ │ ├── arxiv_collector.py │ │ ├── github_collector.py │ │ └── rss_collector.py │ ├── processors/ # 处理器模块 │ │ ├── __init__.py │ │ ├── deduplicator.py # 去重 │ │ ├── classifier.py # 分类与评分(调用LLM) │ │ └── summarizer.py # 摘要生成(调用LLM) │ ├── generators/ # 生成器模块 │ │ ├── __init__.py │ │ └── report_generator.py # 报告撰写(调用LLM) │ ├── utils/ │ │ ├── llm_client.py # 封装LLM API调用 │ │ ├── vector_db.py # 向量数据库操作 │ │ └── logger.py │ └── models.py # 数据模型定义(如NewsItem类) ├── outputs/ │ ├── raw_data/ # 原始采集数据 │ ├── processed/ # 处理后的数据 │ └── reports/ # 生成的周报 ├── tests/ # 单元测试 └── requirements.txt # 依赖列表这种模块化设计使得每个功能职责单一,易于测试和替换。例如,要增加一个新的数据源,只需在collectors目录下添加一个新的类。
4. 从零搭建的实操步骤与核心配置
假设我们现在要从零开始实现一个简化版的ai-news-weekly-agent,以下是核心的实操步骤。我们将以Python为主要语言,使用OpenAI API和GitHub Actions。
4.1 环境准备与基础依赖
首先,创建一个新的项目目录并初始化虚拟环境。
mkdir ai-news-weekly-agent && cd ai-news-weekly-agent python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows创建requirements.txt文件,包含基础依赖:
# 核心依赖 openai>=1.0.0 aiohttp>=3.9.0 beautifulsoup4>=4.12.0 feedparser>=6.0.0 python-dotenv>=1.0.0 pyyaml>=6.0 chromadb>=0.4.0 # 向量数据库 sentence-transformers>=2.2.0 # 用于生成文本嵌入 # 报告生成与格式化 markdown>=3.5.0 pdfkit>=1.0.0 # 需要额外安装wkhtmltopdf # 调度与部署(可选) schedule>=1.2.0安装依赖:pip install -r requirements.txt。
接下来,创建配置文件.env用于存储敏感信息(切勿提交到Git):
OPENAI_API_KEY=sk-your-openai-key-here OPENAI_BASE_URL=https://api.openai.com/v1 # 如果使用其他兼容API,可修改 DATA_SOURCES_PATH=./config/data_sources.yaml VECTOR_DB_PATH=./data/chroma_db LOG_LEVEL=INFO创建config/data_sources.yaml定义数据源:
sources: - type: “arxiv” category: “cs.AI” max_results: 50 - type: “rss” url: “https://news.ycombinator.com/rss" tags: [“hackernews”] - type: “github_trending” language: “python” since: “weekly”4.2 实现核心数据模型与采集器
在src/models.py中定义核心数据类:
from pydantic import BaseModel from datetime import datetime from typing import List, Optional class NewsItem(BaseModel): id: str # 唯一标识,可以是URL的hash title: str url: str source: str # 来源网站,如 “arxiv”, “hackernews” published_at: Optional[datetime] content: Optional[str] # 原始内容或摘要 embedding: Optional[List[float]] = None # 文本向量 category: Optional[str] = None score: Optional[int] = None processed_summary: Optional[str] = None在src/collectors/arxiv_collector.py中实现一个简单的ArXiv采集器:
import arxiv import asyncio from ..models import NewsItem from datetime import datetime class ArXivCollector: def __init__(self, category=“cs.AI”, max_results=50): self.category = category self.max_results = max_results self.client = arxiv.Client() async def fetch(self) -> List[NewsItem]: search = arxiv.Search( query=f“cat:{self.category}”, max_results=self.max_results, sort_by=arxiv.SortCriterion.SubmittedDate ) items = [] for result in self.client.results(search): # 构造唯一ID,例如使用entry_id的MD5 import hashlib item_id = hashlib.md5(result.entry_id.encode()).hexdigest() news_item = NewsItem( id=item_id, title=result.title, url=result.entry_id, source=“arxiv”, published_at=result.published, content=result.summary, # 使用摘要作为初始内容 ) items.append(news_item) return items类似地,可以实现RSS采集器(使用feedparser)和GitHub Trending采集器(使用GitHub API或爬虫)。
4.3 构建处理流水线:去重、评分与摘要
在src/processors/deduplicator.py中,实现基于向量相似度的去重:
import chromadb from sentence_transformers import SentenceTransformer from ..models import NewsItem class Deduplicator: def __init__(self, persist_path=“./data/chroma_db”): self.client = chromadb.PersistentClient(path=persist_path) # 创建一个集合(collection)来存储历史新闻 self.collection = self.client.get_or_create_collection(name=“news_items”) self.embedding_model = SentenceTransformer(‘all-MiniLM-L6-v2’) # 轻量级嵌入模型 def find_similar(self, news_item: NewsItem, top_k=3, threshold=0.85): # 为新闻项生成嵌入向量 text_to_embed = f“{news_item.title} {news_item.content[:500]}” # 取部分内容 embedding = self.embedding_model.encode(text_to_embed).tolist() news_item.embedding = embedding # 在向量数据库中查询相似项 results = self.collection.query( query_embeddings=[embedding], n_results=top_k ) # results包含ids, distances, metadatas等 similar_items = [] if results[‘distances’][0]: # 检查是否有结果 for dist, meta in zip(results[‘distances’][0], results[‘metadatas’][0]): if 1 - dist > threshold: # chromadb使用余弦距离,越小越相似 similar_items.append(meta) return similar_items def add_item(self, news_item: NewsItem): # 将新项目添加到向量数据库,以备未来查重 if news_item.embedding: self.collection.add( embeddings=[news_item.embedding], metadatas=[{“id”: news_item.id, “title”: news_item.title, “source”: news_item.source}], ids=[news_item.id] )在src/processors/classifier.py中,实现调用LLM进行评分和分类:
from openai import OpenAI import json from ..models import NewsItem import logging class NewsClassifier: def __init__(self, model=“gpt-3.5-turbo”): self.client = OpenAI() # 会自动从环境变量读取OPENAI_API_KEY self.model = model self.prompt_template = “””你是一位AI领域编辑...(此处填入之前设计好的Prompt)...””” def classify_and_score(self, news_item: NewsItem) -> NewsItem: prompt = self.prompt_template.format(title=news_item.title, abstract=news_item.content[:1000]) try: response = self.client.chat.completions.create( model=self.model, messages=[{“role”: “user”, “content”: prompt}], temperature=0.2, # 低温度保证输出稳定 response_format={“type”: “json_object”} # 要求返回JSON ) result = json.loads(response.choices[0].message.content) news_item.category = result.get(“category”) news_item.score = result.get(“overall_score”) # 可以保存其他信息到news_item的额外字段中 return news_item except Exception as e: logging.error(f“Classification failed for {news_item.id}: {e}”) return news_item # 返回原item,分数为NoneSummarizer的实现类似,设计一个专注于生成简洁摘要的Prompt来调用LLM。
4.4 组装主流程与生成报告
在src/generators/report_generator.py中,实现报告生成:
class ReportGenerator: def __init__(self, llm_client, model=“gpt-4o”): self.llm_client = llm_client self.model = model def generate_outline(self, categorized_items: dict) -> str: # categorized_items 是一个字典,key是类别,value是该类下的NewsItem列表 prompt = f“””基于以下分类的AI新闻条目,生成一份中文周报的目录大纲。大纲应清晰有逻辑,包含主要章节和可能的子章节。 分类数据:{json.dumps(categorized_items, default=str, indent=2)} 只输出大纲内容,用Markdown格式。””” # 调用LLM生成大纲... return outline_markdown def generate_section(self, section_title: str, items: List[NewsItem]) -> str: prompt = f“””你是一位AI科技专栏作者。请根据以下条目,撰写‘{section_title}’部分的周报内容。 要求:内容连贯,有引言和简要评论,不要简单罗列。使用中文。 条目信息:{json.dumps([item.dict() for item in items], default=str, indent=2)}””” # 调用LLM生成章节内容... return section_content def assemble_report(self, outline: str, sections: dict) -> str: # sections: {‘章节名’: ‘内容’} report = “# AI Weekly Digest\n\n” report += f“*本期周报生成于 {datetime.now().strftime(‘%Y-%m-%d %H:%M’)}*\n\n” report += outline + “\n\n” for sec_title, sec_content in sections.items(): report += f“## {sec_title}\n\n{sec_content}\n\n” report += “---\n*本报告由AI自动生成,仅供参考。*” return report最后,在main.py中组装整个流水线:
import asyncio from src.collectors import ArXivCollector, RssCollector from src.processors import Deduplicator, NewsClassifier, Summarizer from src.generators import ReportGenerator from src.utils.llm_client import get_llm_client import logging async def main(): logging.basicConfig(level=logging.INFO) # 1. 采集 collectors = [ArXivCollector(), RssCollector(“https://example.com/feed")] all_items = [] for collector in collectors: items = await collector.fetch() all_items.extend(items) logging.info(f“Collected {len(all_items)} raw items.”) # 2. 去重 deduplicator = Deduplicator() unique_items = [] for item in all_items: similar = deduplicator.find_similar(item) if not similar: # 没有高度相似的,认为是新内容 unique_items.append(item) deduplicator.add_item(item) # 存入数据库 logging.info(f“After deduplication: {len(unique_items)} items.”) # 3. 分类与评分 classifier = NewsClassifier(model=“gpt-3.5-turbo”) scored_items = [] for item in unique_items: processed_item = classifier.classify_and_score(item) if processed_item.score and processed_item.score > 20: # 阈值过滤 scored_items.append(processed_item) logging.info(f“After scoring: {len(scored_items)} high-quality items.”) # 4. 按类别分组 from collections import defaultdict categorized = defaultdict(list) for item in scored_items: categorized[item.category].append(item) # 5. 生成报告 llm_client = get_llm_client() generator = ReportGenerator(llm_client, model=“gpt-4”) outline = generator.generate_outline(categorized) sections = {} for category, items in categorized.items(): sections[category] = generator.generate_section(category, items) final_report = generator.assemble_report(outline, sections) # 6. 保存输出 import os os.makedirs(‘./outputs/reports’, exist_ok=True) report_path = f‘./outputs/reports/weekly_{datetime.now().strftime(“%Y%m%d”)}.md’ with open(report_path, ‘w’, encoding=‘utf-8’) as f: f.write(final_report) logging.info(f“Report saved to {report_path}”) if __name__ == “__main__”: asyncio.run(main())4.5 配置自动化部署与调度
为了让项目每周自动运行,我们使用GitHub Actions。
在项目根目录创建.github/workflows/weekly-report.yml:
name: Generate AI Weekly Report on: schedule: - cron: ‘0 10 * * 1’ # 每周一UTC时间10点(北京时间周一18点)运行 workflow_dispatch: # 允许手动触发 jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ‘3.11’ - name: Install dependencies run: | pip install -r requirements.txt # 如果需要wkhtmltopdf for PDF generation sudo apt-get update sudo apt-get install -y wkhtmltopdf - name: Run weekly agent env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} # 其他环境变量... run: python main.py - name: Commit and push if report changed run: | git config --local user.email “action@github.com” git config --local user.name “GitHub Action” git add ./outputs/reports/ git commit -m “chore: update weekly report [skip ci]” || echo “No changes to commit” git push你需要将OPENAI_API_KEY添加到GitHub仓库的Settings -> Secrets and variables -> Actions中。
5. 常见问题、优化方向与避坑指南
在实际搭建和运行过程中,你一定会遇到各种问题。以下是我在类似项目中积累的一些经验和避坑点。
5.1 典型问题与排查
1. 采集失败或数据为空
- 表现:某个采集器没有获取到任何数据。
- 排查:
- 网络问题:检查目标网站是否可访问,是否被屏蔽。考虑在代码中添加重试机制和更友好的User-Agent。
- 网站改版:网站结构变化导致CSS选择器或XPath失效。需要定期维护采集器代码。可以为关键采集器编写简单的单元测试,定期运行以检测是否失效。
- API限制:检查是否触发了目标API的速率限制(Rate Limit)。需要在代码中实现请求间隔(如
time.sleep)和使用API Key轮询。
- 解决:增加详细的日志记录,记录每个请求的URL和响应状态。使用
try...except包裹采集逻辑,捕获异常并记录,不影响其他采集器运行。
2. LLM调用超时或返回非预期格式
- 表现:OpenAI API调用失败,或者返回的内容不是预期的JSON。
- 排查:
- 网络或服务不稳定:API服务临时故障。
- Prompt设计问题:Prompt指令不够清晰,导致模型“放飞自我”。特别是没有强制要求JSON输出时。
- Token超限:输入文本过长,超过了模型上下文窗口。
- 解决:
- 实现指数退避的重试逻辑。
- 在Prompt中明确使用
response_format={“type”: “json_object”}并要求模型输出指定键名的JSON。在代码中解析JSON前,加入json.loads()的异常捕获,并准备一个降级处理方案(如标记该条目为处理失败)。 - 对过长的输入文本进行智能截断,保留开头、结尾和可能的关键中间部分。
3. 生成的内容质量不稳定
- 表现:周报内容有时啰嗦,有时遗漏重点,风格不一致。
- 排查:
- Temperature参数:在生成创造性内容(如报告撰写)时,
temperature可以稍高(如0.7);在进行分类、摘要等确定性任务时,temperature应调低(如0.2)。 - System Prompt缺失:没有给模型设定明确的角色和风格。
- 输入信息质量差:如果给LLM的原始条目摘要本身就模糊不清,输出自然不佳。
- Temperature参数:在生成创造性内容(如报告撰写)时,
- 解决:
- 为不同的LLM调用任务(分类、摘要、撰写)设置不同的
temperature和max_tokens参数。 - 精心设计System Prompt和User Prompt,使用少样本示例(Few-shot)来锚定输出风格和质量。
- 在前期的摘要生成阶段把好关,确保输入给报告生成器的“素材”是高质量的。
- 为不同的LLM调用任务(分类、摘要、撰写)设置不同的
4. 运行成本失控
- 表现:API调用费用超出预期。
- 排查:
- 数据源过多:每周抓取上千条新闻,每条都调用LLM处理。
- 模型使用不当:所有任务都用最贵的
gpt-4模型。 - 重复处理:没有做好去重,同一新闻被多次处理。
- 解决:
- 严格筛选数据源,只保留最高质量的。
- 采用混合模型策略:分类、去重用便宜的
gpt-3.5-turbo或本地小模型;最终报告润色用gpt-4。 - 强化去重模块,确保每条信息只被处理一次。实现一个缓存层,对近期已处理过的新闻标题/内容哈希值进行缓存。
5.2 性能与效果优化方向
当基础版本跑通后,可以考虑以下优化来提升系统的智能性和实用性:
1. 个性化推荐
- 让系统学习用户的兴趣偏好。例如,用户可以标记对某些条目“感兴趣”或“不感兴趣”。系统可以记录这些条目的关键词、类别或嵌入向量,在后续的评分环节中引入“个性化分数”加权,让周报更贴合用户口味。
2. 多模态信息整合
- 目前的处理主要基于文本。可以扩展采集器,抓取一些关键图表、模型性能对比图等。在报告中,可以引用这些图片的链接,甚至尝试用多模态模型(如GPT-4V)对图表进行简要描述,整合进摘要中。
3. 溯源与可解释性
- 在生成的周报中,为每个观点或结论注明来源(超链接到原始文章)。这不仅增加可信度,也方便读者深入阅读。可以在
NewsItem中保留原始URL,并在报告生成时,要求LLM在叙述中自然地附上引用标记。
4. 交互与反馈闭环
- 将生成的周报发布到一个带有简单交互功能的页面(如静态博客)。读者可以对某条新闻点击“有用”或“无用”,或者提交评论。收集这些反馈数据,可以用于优化LLM的Prompt,或调整数据源的权重,形成一个持续改进的闭环。
5. 部署与监控升级
- 将项目容器化(Docker),便于在不同环境部署。
- 引入更完善的监控,如使用
Prometheus和Grafana监控API调用次数、耗时、费用消耗、各阶段处理条目数等关键指标,并设置告警。
5.3 新手避坑指南
- 不要一开始就追求大而全:先从1-2个核心数据源(如Hacker News和ArXiv)和最简单的流程(采集->LLM摘要->输出列表)开始。快速跑通一个端到端的MVP(最小可行产品),验证想法是否可行。
- API密钥安全第一:永远不要将API密钥硬编码在代码中或提交到公开仓库。始终使用环境变量或密钥管理服务。
- 成本预估与控制:在运行前,粗略估算一下每次运行的成本。例如,处理100条新闻,每条调用2次LLM(分类+摘要),使用
gpt-3.5-turbo,大约花费在0.1-0.2美元。可以先设置一个消费额度告警。 - LLM调用要加“护栏”:LLM的输出是不可控的。一定要对输出进行格式验证和内容过滤(防止生成不当内容)。设置合理的超时和重试。
- 日志是你的好朋友:在项目初期就建立完善的日志系统,记录每个步骤的输入输出。当出现问题时,详细的日志是唯一的排查线索。
- 尊重数据源:遵守网站的
robots.txt,设置合理的爬取间隔,不要用你的脚本对一个小网站进行高频请求。可以考虑使用公共API或RSS订阅,这是更友好的方式。
构建一个ai-news-weekly-agent是一次非常有益的实践,它串联起了爬虫、数据处理、LLM应用、提示工程、自动化部署等多个技能点。最终产出的不仅是一个工具,更是一个能够持续为你提供价值的“数字员工”。