
我每天早晨最早做的事情之一是花四十分钟快速扫一遍行业资讯。这个习惯坚持了快两年直到最近终于忍无可忍——信息来源实在太多了今天想读的东西没读完明天又堆上来光“筛选哪些值得读”这个动作每天都要消耗大量精力。所以就有了这个“每天自动整理行业资讯”的Agent项目也是我Agent系列里最贴近日常、跑得最稳的一个。这个Agent的定位很明确每天固定时间抓取多个信息源把几百条原始内容过滤成十几条真正值得看的精华自动生成摘要、打分类标签、给出热度排序再推送到飞书群或者邮件里。整个过程不需要人盯我不再是“逐条刷信息”而是“每天读一份已经整理好的日报”。这套框架不仅适用于科技资讯换一下源和关键词就能变成竞品监控、政策动态跟踪、学术论文摘要这类场景。如果你是正在学Agent开发的Python开发者或者被信息过载折磨的从业者这篇可以给你一条完整可复现的动手路径。1. 资讯整理这件事真的有必要上Agent吗1.1 手动整理资讯的真实痛点很多人一开始会觉得整理资讯不就是把几个网站看一遍、复制粘贴到文档里吗但实际做起来之后问题一个接一个。第一个痛点是信源分散。微信公众号、行业论坛、博客RSS、邮件订阅、社交媒体热搜各有各的更新节奏你要在不同平台之间来回切换。第二个痛点是重复内容极多。同一篇技术发布会被四五家媒体各写一遍手动甄别的时候很容易被一长串标题干扰判断。第三个痛点是“值得看”的判断标准很难稳定。上午觉得重要的内容下午可能就觉得一般情绪和精力直接影响筛选质量。我最早用普通脚本解决这个问题。写一个Python脚本定时抓RSS把标题拼成一个列表发到邮箱。可跑了两三天就发现这只是把“打开网页”换成了“打开邮件”该看的我还是得看而且很多标题根本看不出内容价值还是要点进去才知道。这个阶段我意识到整理资讯的核心难点不在“抓取”而在“理解和取舍”。抓取是规则活理解和取舍是语义活脚本处理不了后者。1.2 为什么“Agent”而不是普通脚本Agent和普通脚本最大的区别在于它内部有一个能理解上下文、做出判断的模型环节。还是拿资讯整理来说普通脚本只能按照“标题包含大模型就保留”这种规则过滤但一条标题叫“新框架发布训练成本下降40%”没有直接出现“大模型”三个字脚本就会漏掉。而Agent可以读懂这句话讲的是大模型训练把它保留下来甚至能判断这是一条值得放头条的行业新闻。这个项目里的Agent本质上是一个workflow型Agent确定性流程做骨架模型做语义决策。采集、清洗、推送这些环节是确定的代码逻辑稳定、可控、出错容易排查筛选、摘要、分类这些环节由LLM承担灵活、能泛化。这也是我在这个系列中反复强调的观点——不需要什么任务都做成完全自主决策的Agent。资讯整理这件事你要的不是“它今天自己决定去搜点什么”而是“它按我设定的节奏、按我关心的方向把结果稳定地送到我面前”。所以这个Agent的架构核心是“可控的自动化”不是“自由的智能化”。很多初学者一开始就把Agent设计得很复杂给模型十几个工具让它自由调用跑起来之后连它下一步要干什么都猜不到。我踩过一次这样的坑之后就把设计原则改成了“流程是你定的模型只做它擅长的那一步”。2. 整体架构设计与技术选型2.1 五个核心环节这个资讯Agent从拆解需求到落地我看下来只需要五个环节少了任何一个日报质量都会明显打折。第一个环节是采集。这一步负责按配置定时去抓取各个信息源的更新内容。我这里主要用RSS feed好处是格式标准、正文干净、更新频率可控比直接爬网页省事太多。第二个环节是清洗。RSS里经常混着广告、推广、无关话题需要把HTML标签去掉、截取正文片段、过滤掉明显无效的条目。第三个环节是筛选与摘要。这是LLM发挥价值的地方对每一篇文章判断相关度、提炼出一句话摘要、打上分类标签。第四个环节是聚合排序。把所有文章按时间和热度排序挤掉重复内容形成最终日报。第五个环节是推送与归档。通过飞书机器人或邮件把日报发出去同时把原文链接和摘要存进数据库方便日后检索。把这五个环节按顺序串成一个流水线前一步的输出是后一步的输入。有人可能要问为什么不用一个大Prompt把所有环节交给LLM一次完成我也试过结果非常不稳定。让模型一口气完成“抓取”这件事根本不现实它没有实时访问网络的能力即便有工具调用面对几百条内容也会超出上下文限制。把抓取和清洗交给代码把理解和生成交给模型各干各擅长的这是这个架构能跑稳的根本原因。2.2 为什么选LangGraph而不是直接裸写Python说到编排多个环节有人会直接用纯Python写一个函数调另一个函数循环跑完完事。这在环节少的时候没有问题但一旦加了重试、降级、条件分支纯顺序代码很快就会变成一团乱麻。我这次用了LangGraph。图和节点的方式和这个任务天然契合每个环节都是图里的一个节点节点之间有明确的边数据沿着边流动。调试的时候可以单独跑某一个节点不依赖整条链路。比如我怀疑摘要模块有问题我可以只调用summarize节点给它传入测试数据直接看输出。这在纯函数调用的写法里得改代码才能做到。LangGraph还支持节点的并发执行和条件跳转。比如采集环节可以并发抓多个RSS源哪个源失败了不影响其他源筛选环节可以设计一个条件边如果有效内容太少就直接跳过推送环节改成发送一条告警消息。这种控制力度用裸脚本写会非常费劲。另外我还想说明一下并不是非得用LangGraph才能做这个项目LangChain自带的链式调用也能实现类似效果。只不过LangGraph把状态管理、节点复用、条件分支这些能力做成了原生设计代码结构更清楚后续扩展更方便。我现在的处理方式是模型调用用LangChain的接口流程控制用LangGraph的图各取所长。2.3 输出效果从“信息列表”到“每日日报”Agent跑出来的最终产物不是一长串带链接的标题列表那样和RSS订阅器没有区别。有价值的日报应该包含几个固定要素文章标题、一句话摘要、所属分类、相关度评分、原文链接。我会在推送模块里把日报格式化成下面这个样子【今日AI头条】新框架发布推理速度提升3倍 一句话摘要某团队开源了新的推理加速框架在保持精度的同时显著提升了速度相关代码已上线。 分类AI/技术 热度9/10 原文链接https://... 【行业动态】某头部公司公布季度财报 一句话摘要云业务收入增速回升AI相关订单成为主要增长引擎。 分类企业/商业 热度7/10 原文链接https://...这个格式是我实践下来最舒服的。摘要控制在80字以内一眼能判断要不要点开分类标签方便快速跳过不关心的板块热度评分帮我决定先读哪几条。整个日报控制在十五到二十条既能保证覆盖面又不会让人产生“今天又欠了一堆没读”的焦虑感。为了达到这个效果我调试prompt花了两天时间后面我会详细说摘要模块的思路和参数这块是整个项目最值得抠细节的地方。3. 核心实现步骤与关键参数3.1 前置准备与安装开发环境用的是Python 3.10以上依赖库也不复杂核心就这几个langgraph、langchain-openai、feedparser、pydantic、pyyaml、httpx。安装命令很简单pip install langgraph langchain-openai feedparser pydantic pyyaml httpx模型这块我默认接的是OpenAI风格接口。但你完全可以用其他兼容OpenAI接口的模型服务比如国产的DeepSeek、通义千问、智谱等等只要改一下base_url和api_key就行。这也意味着你不需要在模型供应商上绑定太死哪个性价比高就换哪个。配置文件我用的是yaml把信源、关键词、推送渠道全部集中管理方便调整不用改代码sources: tech: - name: 示例科技媒体 url: https://example.com/rss.xml weight: 1.5 ai: - name: 示例AI资讯 url: https://example.com/ai.xml weight: 2.0 filter: blacklist: - 抽奖 - 广告 keywords: ai: [大模型, Agent, 多模态, 训练, 推理] business: [财报, 融资, 收购] schedule: time: 07:30 timezone: Asia/Shanghai publish: type: feishu webhook: https://open.feishu.cn/open-apis/bot/v2/hook/your_webhookweight字段很关键这是给信源一个基础权重。我在实际使用中会给权威源权重调高一些比如1.8到2.0普通转载源权重调低到1.0以下。这个权重会和内容相关度评分叠加直接影响排序。关于为什么用yaml而不是直接放Python字典原因也很简单改信源是日常操作你用配置文件可以随时改随时生效不用动代码也不用考虑“改完字典后忘了哪里还有一份拷贝”。3.2 采集模块用feedparser读RSS采集模块是整条链路的起点代码不长但有一点值得特别注意——抓取的超时时间。有些RSS源响应很慢甚至偶尔超时如果不设超时时间整个Agent会被一个源卡死。import feedparser import httpx def fetch_feed(url: str, timeout: float 10.0) - list[dict]: try: with httpx.Client(timeouttimeout, follow_redirectsTrue) as client: resp client.get(url) resp.raise_for_status() feed feedparser.parse(resp.content) except Exception as e: # 记录日志返回空列表不影响其他源 return [] items [] for entry in feed.entries[:20]: items.append({ title: entry.get(title, ), link: entry.get(link, ), summary: entry.get(summary, )[:500], published: entry.get(published, ), }) return items每个源最多取20条新条目这个限制很重要。如果直接把一个源的几百条历史内容全部扔进去不仅浪费token还会稀释当天新闻的浓度。实际跑起来绝大多数源每天新增内容不会超过20条所以20这个数字足够覆盖又能控制成本。另外抓RSS的时候带上User-Agent请求头会友好很多部分源没有UA会直接把请求拦掉。我把UA统一设成“Mozilla/5.0 (compatible; DailyBot/1.0)”就能避开大部分拦截。做好事要说清楚抓取任何RSS源之前先看一眼源里的copyright说明或者robots.txt尊重内容提供方的规则。这个项目用的是公开RSS订阅接口正常频率下不会给源站造成压力但要是你加了几百个源还并发抓取那就有点不厚道了。我是把并发数控制在5个以内每次采集间隔至少10分钟这样既保证时效性又不会干扰目标站点运行。3.3 清洗模块去掉杂质RSS里的“摘要”字段其实质量参差不齐有的只有一句话有的塞了一堆HTML标签有的还带着“继续阅读”这种站内文案。清洗模块要处理三件事去HTML、截断、过滤黑名单关键词。HTML解析我用的是Python标准库里的html.parser根本不需要上BeautifulSoup因为RSS摘要里的结构标签就那几种简单的解析器足够用。去完HTML之后我会把连续空白字符压缩成单个空格避免正文里出现一堆换行和缩进。黑名单过滤是这个Agent的规则兜底机制。LLM能处理语义层面的判断但在“这条内容是广告”这种事情上便宜的规则比模型更高效、更便宜。我会维护一个黑名单列表命中就直接丢弃不会浪费后续环节的token。常见的黑名单词包括“抽奖”“限时优惠”“点击领取”“广告”这类。3.4 筛选与摘要模块Prompt是核心这个模块是整个Agent的重头戏LLM在这里做三件事给文章打分类标签、提取一句话摘要、评估相关度和价值分。为了保证输出格式稳定我定义了一个Pydantic模型from pydantic import BaseModel, Field from typing import Literal class ArticleMeta(BaseModel): category: Literal[ai, tech, business, product, other] summary: str Field(description不超过80字的一句话摘要) relevance: int Field(description相关度评分1到10分, ge1, le10) worth: int Field(description对读者的价值评分1到10分, ge1, le10)用Pydantic的好处是模型输出的JSON一旦出现字段缺失或类型错误程序会立刻报错而不会把脏数据带到后面的环节。我在LangChain里用的with_structured_output方法直接传入这个模型定义接口返回的就是对象实例不用自己手动解析JSON。Prompt我调了很多版最终稳定下来是这样一段你是一位资深行业编辑负责从一批原始资讯中筛选出值得阅读的内容。 给定文章标题和正文片段完成以下任务 1. 判断文章分类只能是 ai/tech/business/product/other 中的一类。 2. 用不超过80字概括文章核心信息要具体不要写空话套话。 3. 评估相关度(relevance)代表这条内容与AI/科技行业从业者的相关程度。 4. 评估价值(worth)代表这条内容对读者的思想和行动有多少启发。 注意如果标题是广告、标题党、无实质信息请将 relevance 设为1。在调用时我把温度设置为0.2max_tokens限制在200以内让模型尽量保持确定性输出。你是做摘要不是写散文温度太高会出现同一篇内容每天摘要风格完全不一致的情况。这里有个小技巧在处理一批文章时不要一条一条调模型API而是把5到10条打包成一个请求让模型一次性输出多个JSON对象。这样能把请求数降一个量级省时间也省钱。但打包数量不要超过10条超过之后输出的稳定性会明显下降。3.5 排序评分热度不能只看“相关度”有了模型给的相关度评分和价值评分排序逻辑就可以做了。我最终用的是这样一个公式final_score (relevance * 0.4 worth * 0.4) * source_weight time_decay其中source_weight来自配置文件里的weight字段time_decay是一个基于发布时间的衰减项。同一篇内容如果有多家媒体报道模型评分可能都很高但我不希望旧闻排在新瓜前面。所以搞了一个简单的时间衰减函数import math from datetime import datetime, timezone def time_decay(published_at: str, nowNone) - float: if not published_at: return 0.0 try: pub_time datetime.fromisoformat(published_at.replace(Z, 00:00)) except ValueError: return 0.0 now now or datetime.now(timezone.utc) hours (now - pub_time).total_seconds() / 3600 return 2.0 * math.exp(-hours / 24.0)这个公式的含义是刚发布的文章拿到2分的时间奖励过了24小时掉到差不多0.7分72小时后就趋近于0。这样能让日报保持“当日热点”的属性不至于把上周的旧闻翻出来排第一。实际用下来时间衰减项对日报体验的提升非常明显。3.6 节点编排与容错降级前面这些模块我用LangGraph串起来。整体流程可以概括为几条边采集节点连接清洗节点清洗节点连接摘要节点摘要节点连接聚合排序节点排序节点连接推送节点。但里面有几个关键设计值得写一下。第一个设计是节点级重试。摘要节点调用模型接口时偶尔会遇到网络波动或者限流我给这个节点加了两次重试重试间隔分别是3秒和10秒。这个重试是局部重试不会把整条链路从头跑一遍很大程度上节省了等待时间。第二个设计是降级路径。有一次模型服务商出了故障整条链路当时直接报错日报也没发出来。后来我加了一个条件边如果摘要模块连续失败三次就自动切换到“降级模式”把原文标题和链接直接推出来不生成摘要。这样即使模型不可用用户至少还能看到“今天有哪些文章”不至于完全断粮。这个降级逻辑虽然简单但价值非常高稳定性的权重应该排在质量前面。第三个设计是空结果告警。如果某个环节输出的有效文章数量为零触发告警节点往邮箱发一封“今日资讯为空”的邮件。这个场景出现的概率不大但一旦出现大概率是上游源集体变更了路径早发现早处理能避免连续几天收到空日报。4. 定时调度与推送部署4.1 跑在服务器上cron和APScheduler选哪个这个Agent不需要常驻服务每天定时跑一次就够。在Linux服务器上最简单的方案是crontab把执行命令加到系统计划任务里。不过cron有个问题不同服务器的时区不一定一样你要在配置里显式写明时区不然定时时间和你的预期可能差好几个小时。我这次用的是Python里面的APScheduler因为它可以精确到“每天早上7点30分运行”并且直接支持Asia/Shanghai这种时区写法不需要依赖系统的时区设置。核心代码就这么短from apscheduler.schedulers.blocking import BlockingScheduler scheduler BlockingScheduler(timezoneAsia/Shanghai) scheduler.add_job( run_daily_digest, triggercron, hour7, minute30, iddaily_digest ) scheduler.start()有人说APScheduler不如cron可靠我实际跑下来没有任何问题。这个选择对你的项目形态来说不一定适用如果你的服务器上已经有现成的调度平台直接用平台的定时任务也行。重点是你得保证“到点能跑、跑了能出结果、出事能告警”。4.2 推送渠道飞书机器人最方便日报推送到哪里直接决定了这份产物的可用性。我最早用邮件发现邮件容易被邮件客户端折叠而且手机上点开还要等加载。后来换成飞书群机器人体验提升了一大截——每天早晨群里自动出现一份消息卡片手机上直接看点链接就跳转原文几乎零摩擦。飞书群机器人的接入方式很简单在群里添加一个自定义机器人拿到webhook地址然后用httpx发一个POST请求就行import httpx def push_to_feishu(webhook: str, content: str): payload { msg_type: interactive, card: { header: {title: {tag: plain_text, content: 每日行业资讯日报}}, elements: [{tag: markdown, content: content}] } } httpx.post(webhook, jsonpayload, timeout10)同样的思路换成钉钉或者企业微信机器人只是payload格式略有差异。如果你没有飞书或者钉钉直接退回到邮件发送用smtplib就能搞定只是体验会差一些。4.3 日志与运行状态观测这个Agent虽然简单但每天定时跑总会有出问题的时候。为了快速定位问题我在每个节点开始和结束时各记一条日志包含节点名、输入数量、消耗时间和是否成功2025-01-15 07:30:01 [collect] 开始抓取共12个源 2025-01-15 07:30:12 [collect] 完成获取到86条原始内容 2025-01-15 07:30:13 [clean] 清洗完成剩余61条 2025-01-15 07:30:42 [summarize] 摘要完成59条有效 2025-01-15 07:30:43 [rank] 排序完成 2025-01-15 07:30:44 [publish] 推送成功日志文件名按日期切分比如agent_20250115.log这样复盘的时候按天翻日志非常方便。我在开发调试阶段踩过一个坑一开始乐观地认为节点不会失败结果日志只记录成功状态一旦真的失败整条链路在哪里断的都不知道。加日志这件事一开始就要做不是出了问题再补的东西。5. 常见问题与排查技巧实录5.1 日报里重复内容很多怎么办第一个常见问题是内容重复。同一个热点四五家媒体各写一篇虽然标题写法不同但核心信息高度重叠。初期日报里经常出现好几条“同一件事”阅读体验很差。解决方案分两步。第一步是URL去重这是最基础的同一篇文章被多个源转载时链接通常能对上。第二步是标题相似度去重我用的是简单的Levenshtein距离归一化两条标题相似度超过0.75就只保留评分更高那一条。如果内容量特别大还可以用embedding去重把所有标题向量化后算相似度效果更好但成本也更高。我对目前的资讯量级来说Levenshtein距离已经够用。5.2 摘要“一本正经地胡编”怎么办第二个问题是摘要内容不忠实原文。有次模型在摘要里写“该框架支持分布式多节点部署”但原文明明没有提分布式多节点这是典型的模型“脑补”现象。排查之后发现两个原因。一是摘要输入包含的正文信息太少模型只能靠推理补全自然容易补错。二是没有在Prompt里显式限制“只能基于给定内容总结不能补充原文没有的信息”。我后来做两处调整输入正文时截取尽可能完整的段落不只取RSS里的summary字段Prompt里加了一句强约束“摘要必须严格基于原文信息若原文没有相关内容不要补充”。调整之后胡编的问题基本不再出现。5.3 Token成本失控怎么控制有朋友看到这个项目第一反应是“每天几百条内容调用模型会不会很贵”。我实测下来让人大模型API处理几百条RSS内容每天消耗大概几十万token。如果用的是便宜的模型一天的模型费用大概几毛钱到几块钱人民币完全在可接受范围内。但省钱的技巧还是有几条。一是“先规则后模型”黑名单过滤、URL去重、相似度去重全部在模型调用之前做好减少模型处理的无效输入。二是摘要输入不传完整正文只传“标题前200字正文”这个长度已经足够生成靠谱摘要。三是分类标签、打分这种简单任务优先用便宜的小模型只有生成摘要这种对语言质量要求高的任务才用大模型。这样组合下来成本能比全用大模型低大约60%。5.4 某一天日报突然为空第三个问题是有天起来发现当天日报没推送查日志发现采集节点返回了86条内容但摘要环节输出了0条有效内容。细查才知道那天有一个科技大厂的RSS结构突然升级了字段名变化导致内容解析全为空加上新的格式恰好全部命中黑名单词。这个问题促成了我“空结果告警”功能的落地。现在当有效结果低于阈值时会强制发一封告警消息给管理员内容包含各环节的处理数量。这样即使出现问题我也能在当天早上知道“今天的日报没成功生成”而不是等到晚上才发现。自动化的项目出问题不可怕可怕的是出问题了你不知道。6. 这个框架还能拓展成什么这套资讯整理Agent跑稳之后我先后改造出了两个变体。第一个是竞品监控日报把信息源换成了目标公司官网、官方博客、招聘页面的RSS加上产品发现类媒体的更新每天出一份竞品动态摘要。第二个是论文摘要助手把信息源换成arXiv的CS分类RSSPrompt改成“用学术规范概括研究问题、方法和结论”每天早晨一份最新论文精选。这两个变体都只改了配置和Prompt代码复用率超过80%。所以这套思路的核心价值不在“整理资讯”这个具体任务而在于它提供了一种范式用规则处理确定性部分用模型处理语义判断用工作流把这些环节编排成稳定的自动化流水线。任何“每天/每周需要从多个来源收集信息、筛选重点、生成摘要”的需求都可以套用这个模板。我对这个项目最大的感想是做自动化整理目标不是为了替代人阅读而是为了把宝贵的注意力留给真正值得读的内容。模型把机械筛选的活干掉了我每天只需要专注于那十来条经过筛选的内容这个时间投入产出比远高于在几百条标题里自己划拉三十分钟。如果你也想动手做类似的东西我的建议是先不要追求一步到位从最简单的单源RSS加摘要跑通等稳定了再加多源、加排序、加告警。这比我一开始就想做一个“全自动智能体”结果被各种边界情况和幻觉输出折磨了三个星期靠谱得多。