1. 项目背景与需求拆解
1.1 为什么我决定用 Agent 做资讯整理
做技术这行,每天必须看行业动态,不然没过多久就跟不上节奏了。我之前每天早上常用的操作是:先刷一遍几个技术社区首页,再翻公众号、邮件订阅的周报,然后打开一堆浏览器标签页。整个过程少说四十分钟,看起来很勤奋,但真正有效的信息其实没几条。更难受的是,很多新闻在多个渠道会重复推送,我每天要花不少时间判断"这条昨天是不是看过了"。
后来我意识到,这件事天然适合让 Agent 去做。它的输入是几十个信息源,输出是一份精简的行业日报,中间还牵扯到过滤、去重、摘要、分类,这些步骤单靠固定的脚本规则很难写好,因为每天的内容千变万化,规则永远要在新样本上打补丁。而用大模型驱动的 Agent,可以把"看内容、做判断"这件事交给自然语言指令来完成,比一堆正则表达式可靠得多。
这个系列写到第四篇了,前几篇我分别分享了 Agent 的基础概念、常用框架以及工具调用的落地方式。这篇就沿着一个真实在跑的项目展开:每天定时自动整理行业资讯,生成日报推送到工作群。整个项目不算复杂,但涉及 Agent 开发中的不少典型问题:怎么设计架构、怎么处理数据源、要不要引入记忆、怎么保证输出稳定。我会把每一步的选择逻辑和踩过的坑都讲清楚。
1.2 需求边界:不是做一个通用机器人
动手之前,我把需求收敛得非常克制。很多人在这个环节容易犯的错误是,一上来就想做一个"智能资讯助手",既要做日报,又要做语音播报,还要支持对话问答,结果项目拖了很久都没上线。我的原则是先把最小闭环跑通:每天早上 8 点 30 分,Agent 自动抓取行业信息源,过滤掉低质量和重复内容,用大模型生成每条的摘要和分类标签,最后汇总成一篇日报推送到飞书群。
具体拆解下来,核心需求只有四块:采集、清洗、归纳、分发。采集是把分散在 RSS、官网、社区的信息拿回来;清洗是去掉广告、无关内容和重复文章;归纳是让 Agent 对每篇内容做摘要和打标签;分发是把整理好的结果推送到指定渠道。这四块正好对应了一个简单 AI Agent 的完整链路:感知输入、处理信息、调用工具、输出结果。
我特意把"能与 Agent 对话"这个需求砍掉了。因为一旦加入对话功能,就需要维护上下文窗口、控制回答风格、处理追问,复杂度会翻好几倍。而我每天真正需要的只是早晨那份日报而已。这个取舍很重要,Agent 项目最怕需求漫无边际,先做窄做深,再谈扩展。
2. Agent 整体架构与框架选型
2.1 我为什么没有一开始就套框架
最早我想得很简单,直接上一个现成的 Agent 框架,调度、工具调用、记忆管理全都帮你封装好了,我只要写插件就行。但实际用下来,我发现对"定时抓取资讯→总结推送"这种相对固定流程的项目,重型框架反而累赘:启动慢、配置多、排错时需要理解框架自己的封装逻辑,出了问题都不知道该查谁。
后来我重新理了一下思路。Agent 项目的核心价值应该放在"利用大模型做判断和生成"这件事上,至于调度、HTTP 请求、消息推送,这些用常规的 Python 代码反而更直接。所以最终我采用了一个很轻的架构:用 Python 脚本做流程控制,把大模型调用封装成一个独立的 Agent 服务,再通过函数调用(Function Calling)让模型在需要时调用工具。这个思路在行业里也常被叫作"Agent 编排",它的本质就是:大脑负责思考和决策,工具负责具体执行。
这样拆的好处很明显:每个环节都可以单独测试。采集逻辑挂了,不影响 Agent 服务;模型输出抽风了,我也能快速定位到是 Prompt 的问题还是上下文太长的问题。如果你的项目也只是固定流程的自动化任务,我建议别急着上全家桶框架,先理清哪些环节真的需要"智能决策",哪些环节只是"确定性的代码",后者完全不需要 Agent 参与。
2.2 轻量 Agent 服务的设计思路
在这个项目里,我定义的 Agent 不是传统意义上的全自主智能体,更准确说是一个 "ReAct 模式"(推理+行动循环)的工作单元。流程是这样的:先读取采集模块整理好的候选文章列表,然后根据当前批次的任务指令,决定是要继续抽摘要,还是先调用分类工具,等工具结果返回后再继续下一步。
我把这套 Agent 服务拆成了三个组件:指令层、执行层、工具层。指令层就是我写的 System Prompt 和 User Prompt,用来告诉模型当前的任务目标、输出格式、注意禁忌;执行层负责与大模型通信,管理请求重试和返回结果解析;工具层则是具体的函数集合,比如"内容清洗""摘要生成""标签分类""格式排版"。这个结构和很多人理解的"Agent = 大模型 + 工具"是一样的,只是我也挂了一个简易的记忆模块,用来记录历史处理过的文章指纹,避免重复推送。
我特别想聊聊 skill 和 agent 的区别,因为这也是我早期容易混淆的点。在这个项目里,Agent 更像一个调度大脑,它决定把一条资讯分配给哪个 skill 去处理;而 skill 是具体的能力包,比如"摘要生成"是一个 skill,"情感判断"是另一个 skill。Agent 负责组合和调度这些 skill,而不是自己亲自实现每一个动作。理解这点之后,我设计功能时思路就清晰多了:先把所有需要的能力拆成独立的 skill,再让 Agent 负责编排,而不是把所有逻辑都塞进 Prompt 里。
2.3 框架与自研代码的边界取舍
虽然我倾向轻量实现,但也不是说完全不用框架。在这个项目里我用了两个轻量库:一个是调度用的 APScheduler,负责定时触发;一个是 HTTP 客户端 httpx,负责抓取和推送。如果你想更省事,也可以用云函数自带的定时触发器,比如在云服务控制台上配置一条 Cron 表达式,函数到点自动执行,连常驻进程都省了。
真正值得纠结的是要不要引入像 LangChain 或 Dify 这一类的框架。我的建议是:如果你需要图形化编排、需要给非技术人员配置流程,Dify 这类平台会更合适;如果你像我一样需要灵活调试 Prompt、需要把自己的代码和模型逻辑深度融合,直接写 Python 会更顺手。这个项目最终选择了自写代码加轻量库的组合,因为资讯整理流程很短,框架带来的收益不明显,反而增加了依赖和学习成本。
我还测试过在本地跑一个开源模型来做分类,但效果不稳定,后来换成 API 调用大模型,成本和速度都可控。这一点后面会详细说。
3. 核心模块实现:采集、清洗、摘要与分类
3.1 数据源接入:先解决"在哪里拿资讯"的问题
资讯 Agent 的第一步是拿到原始数据,数据源的质量直接决定日报质量。我用到的信息源主要有三类:RSS 订阅源、官方网站的更新列表、行业社区的话题栏目。RSS 还是最推荐的,结构清晰、更新及时,而且大部分技术博客和媒体平台都支持。采集时直接用 feedparser 这个库解析,几行代码就能拿到标题、链接、发布时间、正文摘要。
如果你要抓的网站没有 RSS,就需要写 HTML 解析规则了。我的做法是先找到列表页的 DOM 结构,提取出文章链接和标题,再进入详情页抓正文。这一步要注意遵守网站的服务条款,控制抓取频率,尽量不要给目标服务器造成压力。我还会给每个数据源配一个抓取间隔,比如官方博客最长每 2 小时抓一次,行业社区每 30 分钟抓一次,避免频繁请求被封。
代码层面我封装了一个函数,输入是源配置,输出是标准化后的文章对象列表。所谓标准化,就是不管来源是 RSS 还是 HTML,最后都统一成包含 title、url、published、content 四个字段的结构。这样下游处理模块就不用关心数据来自哪个源了。这一步看似简单,但非常关键,它把采集层和处理层完全解耦,后面新增数据源只需要写一个适配器。
3.2 清洗去重:别让模型把时间浪费在垃圾内容上
原始数据拿到后,第一步是清洗。很多网站会把导航栏、页脚、推广信息一起抓进来,正文里也可能包含大量无关代码块或广告。我写了一套轻量级的清洗规则:先去掉标题和正文中的 HTML 标签,再按常见广告关键词过滤,最后用正则去掉重复的空行和奇怪的字符。这是唯一使用正则比较多的环节,因为这些规则非常确定,不适合交给模型。
去重这块我踩过不少坑。最开始我只用文章 URL 做去重,但很快发现同一个事件会被不同媒体转载,URL 完全不同,内容却高度相似。后来我改用"标题相似度 + 正文摘要指纹"结合的方式:先把标题做归一化处理,去掉标点符号和空格,计算两两之间的编辑距离相似度;再用 MinHash 粗略估算正文的相似度。两个相似度都超过阈值的文章,就认为是重复内容,只保留发布时间最早的那一篇。
这个去重逻辑最好放在模型调用之前,因为大模型按 token 计费,处理重复内容等于烧钱。我做过一个粗略统计,加入去重层之后,每天需要交给模型处理的文章数量大约减少了百分之三十。这意味着成本直接砍掉三成,效果非常明显。
3.3 摘要与分类:让 Agent 输出 JSON 而不是散文
清洗完的文章列表,接下来要交给 Agent 做摘要和分类。这一步是整个项目的核心,也是 Agent 能力体现最明显的地方。我会把文章正文截断到前 2000 字左右,和系统指令一起发给模型,让它生成三样东西:一句话摘要、标签列表、相关度评分。为了不让模型自由发挥,我要求它严格返回 JSON 格式,字段固定为 summary、tags、score。
为了让返回格式稳定,我做了两个优化:一是在 Prompt 里给出 JSON 示例,并明确要求不要输出任何解释性文字;二是开启模型的 JSON 输出模式,如果 API 支持就尽量开启。这两招下去之后,解析出错的概率大幅下降。如果解析失败,我会让函数自动重试一次,第二次只传摘要指令,不传完整的正文,成本更低,容错也更好。
分类标签我预设了一套固定的标签体系,比如"行业动态""产品更新""技术方案""数据报告""招聘",然后告诉模型只能从这些标签中选择一到三个。固定标签的好处是后续做统计和归档时非常方便,你不用每天面对一套全新技术词。如果你希望标签更开放,也可以让模型自由生成,但一定要告诉它"标签必须简短、名词化",否则会出现大量动宾短语,很难看。
3.4 记忆机制:让 Agent 不重复唠叨同一件事
记忆是这个项目里比较有意思的部分。我给它设计了一个很轻的"短期记忆 + 长期记忆"结构。短期记忆是当天的运行上下文,记录本次已经处理过哪些文章、哪些还在队列里;长期记忆则是去重指纹库,把每天处理过的文章标题和 URL 指纹持久化到本地 SQLite 数据库,跨天复用。
有人可能觉得,资讯去重不是上一节已经做过了吗?为什么记忆模块还要再做一次?因为清洗层的去重只处理"同一天内"的重复,跨天重复的情况它管不了。很多媒体的热门新闻会连推好几天,如果不去查历史记录,同一篇文章你会在日报里看见三次。我在记忆模块里设计了一个简单的逻辑:每处理完一篇文章,就计算标题和 URL 的哈希值,写入数据库;下一次遇到相同哈希就直接跳过。
这个实现其实很朴素,但效果很好。后来我在想能不能引入更复杂的向量记忆,让模型根据语义判断两篇文章是不是讲同一件事。试验下来,准确率确实有提升,但对一个日报项目来说,成本增加不少,而且偶尔出现"把所有相似新闻都当成重复"的误杀问题。我最后选择了折中方案:数据库指纹去重保底,向量相似度只用来做推荐排序,不参与过滤。
4. 定时调度与日报分发
4.1 让 Agent 跑得更从容:任务编排与优先级
每天定时任务最怕什么?怕采集还没完成,摘要已经开始调模型,最后日报里缺了一部分来源。我在最初版本就踩过这个坑:调度触发后直接开始处理,结果某个数据源响应慢,等它返回时后面的文章已经处理到一半了。所以后来我加了简单的任务编排逻辑,分两个阶段执行。
第一阶段是采集和清洗,所有数据源并发抓取,统一汇总成候选列表;第二阶段才是 Agent 处理,按时间倒序排列,逐条生成摘要和标签。为什么分开?因为采集是 IO 密集型操作,并发效率高,而且不消耗模型 token;处理是计算密集型操作,依赖大模型,如果混在一起跑,一条慢数据源会导致整个流程卡住。分开之后,任何一个阶段出问题,另一个阶段都能独立重试。
为了不让 Agent 处理时把流量全部打到一个模型接口,我还做了简单的限流:每处理五篇文章,线程暂停两秒。这个设计对个人项目来说已经够用,接口的限流阈值通常远高于我的需求。
4.2 定时触发的三种可行方案
定时调度我一开始用的是服务器上的 cron,写一条30 8 * * *表达式,每天八点半执行。后来项目搬到容器环境,又改用 APScheduler 在 Python 进程里管理,这样重试逻辑和异常上报都能写在同一个代码文件里,排查问题时不用东翻西找。
我整理一下三种方案的区别,你们可以按自己的环境选择:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 系统 cron | 简单可靠,不依赖应用代码 | 异常处理和重试逻辑要自己写 | 单机部署,流程简单 |
| APScheduler | 可编程,支持复杂调度,进程内管理 | 进程崩溃后会丢任务,需配合守护进程 | 常驻 Python 进程 |
| 云函数定时触发 | 免运维,按量付费,自带重试 | 运行时长受限,不适合超长任务 | 轻量任务,快速试错 |
我最终用了 APScheduler,因为项目里已经有常驻的 Python 进程,挂一个调度器成本很低。设了一个misfire_grace_time参数,正常运行不存在问题,但如果服务器在八点半恰好重启,任务错过触发时间后还能补一次,避免当天日报直接缺席。
4.3 日报格式化与多渠道分发
Agent 把每条文章的摘要和标签生成完之后,就到了分发环节。我先写了一个格式化函数,把结果拼成一个适合在 IM 工具里阅读的 Markdown 文本:顶部是日期和统计信息,下面按分类分组列出文章标题、摘要和原文链接。格式化函数看起来不起眼,但它是日报的"门面",如果排版混乱,读者体验会非常差。
推送渠道我接了两个:飞书群机器人和邮件。飞书群机器人其实就是一个 Webhook,往指定 URL POST 一段 JSON 就能发送消息,支持 Markdown 格式,日常使用很方便。邮件则用 SMTP 发送,适合我这种想把日报归档到邮箱反复查看的习惯。两个渠道互备,即使一个挂了还有另一个兜底。
推送失败的情况我也做了处理。Webhook 偶尔返回 429 或 5xx,我会用指数退避策略重试三次,第一次等 5 秒,第二次 20 秒,第三次 60 秒。三次都失败就发一封告警邮件给自己,说明哪一步失败、失败原因是什么。这比静默失败好得多,至少你不会等到晚上才发现早上没收到日报。
5. 常见问题与排查实录
5.1 模型输出不稳定,JSON 格式频繁翻车
这是我在 Agent 开发里遇到最频繁的问题。模型有时候会在 JSON 前后加解释性文字,有时候会把某个字段漏掉,还有时候返回的标签不在预设集合里。针对这个问题,我总结了一套组合拳:一是在 Prompt 里给出"只允许输出 JSON"的强约束;二是开启 JSON 模式;三是在代码里做解析容错,如果第一次解析失败,用正则提取大括号内容再解析一次;四是再失败就重启一次请求,但这次在 Prompt 里补充一句"上次返回格式错误,请重新输出规范的 JSON"。
这套方案看起来很土,但实测下来成功率能达到 98% 以上。剩下的 2% 也不会导致日报中断,最多是某一条资讯没有摘要,我依然会把标题和链接发出来。这里分享一个经验:不要追求 100% 的模型输出准确率,成本会指数级上升,99% 可用配合正常的失败降级策略,已经能保证业务稳定。
5.2 采集超时与网页结构变化,怎么优雅处理
网页结构变化是抓取类项目的老大难。某个网站改版,CSS 选择器失效,采集模块就会静默地返回空列表,日报里就会少一个信息源。我现在会在采集模块里加一个"心跳检查"机制:每次抓取都记录返回的文章数量,如果连续三天某个数据源返回数量为 0,就向自己的告警渠道发一条消息,提示我该检查了。这样虽然不能自动修复,但至少不会一挂挂一周才发现。
抓取超时也是常客。我用 httpx 设置了 10 秒超时,超过就放弃该源,并记录错误日志。如果某个源连续失败超过五次,我会临时将它从本轮抓取中摘除,避免整个流程被它拖垮。这个"熔断"思路是从微服务里借鉴过来的,在个人项目里依然管用。
5.3 成本控制:哪些钱该花,哪些钱不该花
每次跑日报的成本大头是大模型摘要调用。为了压成本,我用了几招。第一招,文章正文做截断,超过 2000 字的部分不送进模型,因为资讯摘要只需要看开头部分就足够。第二招,分类和评分用轻量模型完成,只有最终摘要生成才使用更强的模型。第三招,缓存结果,同一篇 URL 在一个月内只处理一次,即使它又出现在抓取列表里也不用再花 token。
有人会问,为什么不直接把摘要任务也交给轻量模型?我试过,效果差距比较大,尤其是技术类资讯,轻量模型常常丢关键细节。日报这类内容是给人看的,质量比成本更敏感,所以核心步骤坚决用强模型,边缘步骤用轻量模型,这个取舍大家可以根据自己的预算灵活调整。
5.4 跑了一百多天后,我总结出的 Agent 落地心得
这个项目上线到现在,稳定运行了一百多天,每天的日报基本没断过。如果让我重新做一次,我会在一开始就多看几个 Agent 框架,但最终还是会更早地回到轻量自研这条路。因为对固定流程的自动化任务来说,真正复杂的是对业务细节的理解和异常处理,而不是 Agent 本身。
日常使用中我发现,模型输出的"人工感"还是差一些,偶尔会把重要新闻放在很靠后的位置,或者对某条资讯的理解有点偏。不过这并不影响使用,日报的意义是帮我节省筛选时间,而不是替代我做判断。我每天花两分钟扫一眼日报,再挑有兴趣的原文深入阅读,这个工作流已经比过去高效太多。
最后再分享一个小技巧:我把每次运行的结构化中间数据全部存了下来,包括原始文章列表、摘要结果、分类统计。这些数据看起来只是日志,但实际上是非常宝贵的语料库。比如我后来想做一个"这个月行业热点趋势"的功能,直接把历史数据拿出来统计标签频次,不费吹灰之力就实现了。所以做 Agent 项目时,中间产物一定要留好,便宜又重要。