Agent开发实战:用 Spring AI 2.0.0 手搓一个基于 RSS 的自主编排的可溯源搜索 Agent——RssAgent
RssAgent 不是一个「AI 搜索」的缝合怪。它是一个Agent——LLM 通过 4 个Tool方法自主编排全链路searchPlatforms → listRoutes → fetchRss → readSummaries每一步都是模型实时决策的结果而非预先写死的流水线。每个搜索结果锚定一个可溯源 URL从根本上解决 LLM 生成内容不可查证的问题。项目基于 Spring Boot 4.1 Spring AI 2.0 DeepSeek Chat53 个测试全绿Docker 一键部署。一、什么是一个 AgentRssAgent 凭什么算 Agent在深入架构之前先厘清一个根本问题RssAgent 凭什么叫 Agent要回答这个问题得先有一个可操作的 Agent 定义。业界对「AI Agent」的边界众说纷纭但剥掉营销话术一个真正的 Agent 必须同时满足四个必要条件条件含义反例感知Perceive从外部环境获取结构化信息只接收用户输入字符串不做结构化解析决策Decide根据当前状态自主选择下一步动作代码写死 if-else 分支LLM 只负责填空执行Act通过工具对外部环境产生实际影响只输出文本不触发任何外部系统观察 迭代Observe Loop根据执行结果调整策略可能多轮循环一次调用就返回结果不管结果好坏对照上述四条RssAgent 的行为如下① 感知—RouteCatalog维护了一个从 RSSHub 同步的结构化路由索引~80 个平台、2000 路由readSummaries返回每篇文章的 title、summary、score、url、publisher、publishTime。LLM 看到的不是原始网页 HTML而是结构化后的信息资产。② 决策— 核心机制。RssAgent 的流程是不确定的——取决于 LLM 在每个节点的实时判断用户输入 → LLM 分析意图 → LLM 决定: 先 searchPlatforms(AI) → LLM 看结果后决定: listRoutes(机器之心) → LLM 看路由后决定: fetchRss([/jiqizhixin/latest]) → LLM 拿到数据后决定: readSummaries(...) → LLM 聚合输出最终结论全程没有一行代码规定「先做什么后做什么」。LLM 自主判断何时调用哪个Tool、传什么参数、结果不满意要不要换关键词重试。③ 执行— 4 个Tool方法对应 4 种对外动作搜索平台、列出路由、发起 HTTP 抓取、读取存储。LLM 的决策不是纸上谈兵——fetchRss会真的发出 HTTP 请求readSummaries会真的从 EclipseStore 中读取持久化数据。④ 观察 迭代— Spring AI 2.0 的 Tool Calling 循环 自动完成这一步1LLM 输出 tool_call → 框架执行 → 结果注入上下文 → LLM 观察结果 → 决定下一步 → 重复直到输出最终回答。一次chatClient.prompt().user(question).stream().content()可能触发 5-10 次内部的「决策→执行→观察→再决策」循环。一句话总结RssAgent 不是 Agent 因为标题里带了「Agent」三个字母——而是因为它的控制流由 LLM 实时决策驱动而非预先写死的流程图。Spring AI 2.0 的 Tool Calling 给了 LLM 一组可以组合使用的原语让模型自己编排检索策略——这才是 Agent 的实质。二、为什么 RSS Agent 可溯源搜索的最优解2.1 可溯源危机AI 搜索的阿喀琉斯之踵2026 年ChatGPT 周活用户突破 9 亿Google AI Overviews 覆盖 16% 的搜索查询。但一个根本问题始终悬而未决LLM 生成的内容你信还是不信传统搜索引擎返回链接列表——用户可以自行判断来源可信度。而 AI 搜索直接给出「答案」用户无从验证这个答案从哪来、是否准确。根据 Princeton Georgia Tech Allen AI 在 2024 年联合发布的 GEO 研究论文「GEO: Generative Engine Optimization」发表于 KDD 2024引用权威来源可使 AI 引用率提升 30-40%加入统计数据再提升 30-40%引入专家引语再提升 30-40%——三者叠加后 AI 引用率整体提升 41%2。这意味着内容可溯源不仅是用户信任问题更是 AI 是否愿意引用你的技术前提。2.2 现有方案的局限方案核心思路致命缺陷搜索引擎Google/Bing倒排索引 PageRank返回链接列表AI Overviews 同样不可溯源向量 RAGLangChain Pinecone向量相似度检索语义匹配 ≠ 事实准确embedding 质量决定天花板AI 搜索Perplexity/Kimi/豆包LLM 网络爬虫爬虫盲扫时效性差来源不可控RssAgentRSSHub 结构化路由 LLM Agent 编排每个结果锚定可溯源 URLAgent 自主决策检索路径2.3 RSS 为什么被低估了RSS 有三个长期被忽视的特性结构化—— 每篇内容天然带有 title、link、pubDate、publisher 元数据无需爬虫解析定点抓取—— 不是你搜「AI」而是你搜「机器之心 最新文章」可溯源—— 每个条目锚定一个 URL不存在「AI 编了一个结论」的可能而 RSSHub 将 RSS 的覆盖范围从「原生支持 RSS 的网站」扩展到「整个互联网」——通过社区维护的2000 路由规则任何网站都可以被 RSS 化。关键洞察RSSHub 的路由命名空间platform/channel/keyword本质上是一个结构化的、可编程的、实时更新的信息索引——它天然比倒排索引更适合 AI Agent 使用。更关键的是AI 现在可以直接生成 RSS 路由了2025-2026 年一个突破性变化正在发生AI 可以自动为任意网站生成 RSS 路由了。这意味着 RSSHub 的覆盖范围从「社区手工维护的 2000」变成了「AI 能理解的任意网页」工具方式亮点OpenRSSnpm:openrss3Claude Code / Codex / Gemini CLI 等 AI Agent 驱动将任意网站转为 RSS支持登录页面、SPA 渲染FeedHubGitHub:fillpit/FeedHub4沙箱化 JS 6 种 LLMDeepSeek / 通义千问 / 豆包 / OpenAI / Gemini / Ollama安全运行自定义解析脚本AI 内容分析 翻译InsCode快马5Kimi-K2 模型自动分析 HTML DOM零代码粘贴 URL → 自动识别字段 → 生成 RSSHub 规则GitHub Copilot Agent RSSHub6Claude 3.7 Sonnet 自动编写 RSSHub 路由脚本VS Code 内一键生成lib/routes/namespace/route.ts这彻底改变了游戏规则。以前「这个网站没有 RSS 路由」是一个阻塞问题现在你只需要把 URL 丢给 AI它就能自动分析网页 DOM、提取结构化字段、生成可用的 RSS 路由——耗时从手工的数小时降至数分钟甚至零代码即可完成5。三、四个 Tool一个 Agent3.1 RssAgent 的 4 个工具RssAgent 暴露给 LLM 的工具只有 4 个RssTools.java#Tool 方法作用LLM 什么时候调用1searchPlatforms(keyword)在 ~80 个平台中按关键词搜索分析用户意图后需要定位相关平台2listRoutes(platform)列出某平台的可用 RSS 路由选定平台后需要知道有哪些具体频道3fetchRss(routes)对指定路由发起实时 RSS 抓取确定路由后发起真正的数据获取4readSummaries(routes)读取已存储的 AI 摘要抓取完成后获取文章内容用于最终回答这四个工具通过 Spring AI 2.0 的Tool注解 注册到 ChatClient1ConfigurationpublicclassAiConfig{BeanPrimarypublicChatClientchatClient(ChatModelchatModel,RssToolsrssTools,ChatMemorychatMemory){returnChatClient.builder(chatModel).defaultTools(rssTools)// ← 4 个 Tool 注入 LLM.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(),// 多轮对话记忆newSimpleLoggerAdvisor()).defaultSystem( You are an RSS aggregation engine. Your output is always based on actual data retrieved, never on speculation. Every step must adhere to structured route definitions; fuzzy searches or guessing routes are prohibited. ).build();}}Spring AI 2.0 的 Tool Calling 循环自动处理 LLM 与工具之间的交互LLM 输出 tool_call → Spring AI 执行 → 结果注入上下文 → LLM 决定下一步 → 重复直到输出最终回答。整个过程开发者只需要声明工具——框架负责编排。3.2 为什么是「一次 ChatClient 调用」这是 RssAgent 最核心的设计决策。很多项目手动拆分「选平台 → 选路由 → 抓取」三次独立的 LLM 调用代码显式管理每一层的输入输出。RssAgent 的做法相反——一次chatClient.prompt().user(question).stream().content()剩下的全部交给 Tool Calling 循环// ConversationService.java — 只有一次 chatClient 调用publicListFetchResponsesearchStreaming(StringsessionId,Stringquestion,SearchCallbackcb){chatClient.prompt().user(question).stream().content().doOnNext(cb::onResponseToken)// 打字机流式输出.blockLast();returnrssTools.getLastResults();}为什么这样设计多层调用会把中间结果锁在每一层的局部上下文中LLM 在下一次调用时看不到上一次的完整推理链。单次调用 Tool Calling 循环让 LLM 在全过程中保持一致的决策视野如果searchPlatforms(AI)返回空结果LLM 可以自行换关键词重试如果fetchRss失败了一个路由LLM 可以判断是跳过还是找替代路由。四、架构深潜三个域的协作RssAgent 采用三域架构遵循 DDD领域驱动设计的边界划分CLI (CliRunner) ← 交互式 REPL/sync /routes /new │ AI Domain (ai/) ← Agent 大脑LLM 决策 Tool Calling ├─ ConversationService ← 唯一一次 ChatClient 调用 │ ├─ RssTools ← 4 个 ToolLLM 的「手脚」 │ ├─ RouteCatalog ← 内存路由索引本地 JSON 持久化 │ └─ RouteSyncTask ← DOMXPath 从 RSSHub 同步路由 │ RSS Domain (rss/) ← 执行层异步管道 ├─ RssController ← 内部 Bean 入口 ├─ RouteFetchService ← async allOf 扇出编排 │ ├─ ArticlesFetchService ← Async tryMarkRefresh 原子 CAS │ ├─ RssFetcher ← 多实例容错 中文 URL 编码 │ │ └─ RssInstanceManager ← 滑动窗口健康评分 │ ├─ AiSummaryService ← 每篇文章 LLM 摘要Lazy 解环依赖 │ └─ SummaryStorageService ← 适配层 → 存储域 │ Storage Domain (storage/) ← DDD 持久化 ├─ DataRoot ← EclipseStore 聚合根 ├─ DataViewFactory ← fromRoute() 工厂 └─ SummaryView ← CQS 读写视图4.1 AI 域Agent 的「大脑」AI 域的核心是让 LLM 拥有「在 RSSHub 路由宇宙中导航」的能力。RouteCatalog是一个内存路由索引通过解析 RSSHub 的/rsshub/routes/zh端点构建持久化到data/routes.json。设计决策为什么用 DOMXPath 而不是 JSON APIRSSHub 的 RSS 输出中每个item的guid就是路由路径本身。DOM 解析绕过 Rome 库的 GUID 处理污染直接提取原始路径。这是典型的「框架能做的 vs. 我们需要的」之间的权衡——Rome 的 GUID 规范化对 RSS 阅读器有用但对路由解析反而是干扰。多轮对话由 Spring AI 的MessageChatMemoryAdvisor自动管理开发者不需要手动维护会话历史。4.2 RSS 域多实例容错的异步管道滑动窗口健康评分多个 RSSHub 实例的负载均衡不能用简单轮询——故障实例每轮都会被选到浪费 HTTP 超时等待。设计决策维护一个DequeBoolean最近 10 次请求的成功/失败按成功率排序。高成功率实例优先尝试故障实例自动下沉到队列尾部。// RssInstanceManager 的核心逻辑// DequeBoolean — 最近 10 次越新的在越前面// 排序成功率高的实例优先// 失败 → 自动切换到下一个实例tryMarkRefresh 原子 CAS在Async并发环境下多个线程可能同时尝试刷新同一个路由产生 TOCTOUTime-of-check to Time-of-use竞态。设计决策用ConcurrentHashMap.compute()将「检查是否已在刷新 标记为刷新中」合并为单个原子操作消除竞态窗口。booleanalreadyRefreshingrefreshMarks.compute(route,(k,v)-{if(v!nullv)returntrue;// 已在刷新中跳过returntrue;// 标记为刷新中});降级保护 Lazy 解环AI 摘要调用 DeepSeek Chat API 失败时自动回退到占位摘要保留 title URL publishTime——核心数据永不丢失7。AiSummaryService → ChatClient → RssTools → RssController → RouteFetchService → AiSummaryService形成循环依赖。Spring 的Lazy注解以最小侵入打破这个环。4.3 存储域EclipseStore 零配置持久化没有 MySQL没有 Redis没有 ORM 配置。EclipseStore 将 Java 对象图直接序列化到磁盘StoragepublicclassDataRoot{privateMapString,ListSummaryrouteSummariesListPair;privateListStringroutePlatforms;privatelonglastRouteSync;}对于「单机部署、按路由分区存储」的场景EclipseStore 比关系型数据库简洁得多——对象图即数据库重启即恢复。五、技术栈一览层级技术版本选型理由运行时Java21Virtual Threads Pattern Matching框架Spring Boot4.1.0最新稳定版Spring AI 2.0 原生集成AI 编排Spring AI2.0.0原生 Tool Calling无需手动封装 Function Calling模型DeepSeek Chat-中文理解强成本约为 GPT-4 的 1/207RSS 解析Rome2.1.0业界标准 RSS/Atom 解析库HTTP 客户端Apache HttpClient5.6.1HTTP/2 支持连接池管理持久化EclipseStore4.1.0零配置嵌入式对象图存储JSONJackson2.22.0Spring 默认容器化Docker docker-compose-一键部署含 RSSHub测试JUnit 5 Mockito AssertJ-53 测试0 失败六、Quick Start30 秒跑起来Docker推荐# 全栈部署RssAgent RSSHubexportDEEPSEEK_API_KEYsk-your-keydocker-composeup-d# 或直接拉取预构建镜像dockerpull makeiny/rss-agent:latestdockerrun-eDEEPSEEK_API_KEYsk-your-key makeiny/rss-agent:latest本地运行gitclone https://github.com/Nexknit/rssgse.gitcdrssagentexportDEEPSEEK_API_KEYsk-your-key ./mvnw package-DskipTestsjava-jartarget/rssagent-0.0.1-SNAPSHOT.jarCLI 交互实录下面是一次真实的交互过程——用户问了一个模糊的问题Agent 自主完成全链路编排RssAgent 最近AI领域有什么新进展 ⚙ searchPlatforms → AI → 5 platforms matched ⚙ listRoutes → 机器之心 (8 routes) ⚙ listRoutes → 量子位 (6 routes) ⚙ listRoutes → 36氪 (12 routes) ⚙ fetchRss → [/jiqizhixin/latest, /wukong/AI, /36kr/latest] ✓ /jiqizhixin/latest · SUCCESS · 20 articles · 1.2s ✓ /wukong/AI · SUCCESS · 15 articles · 0.9s ✓ /36kr/latest · SUCCESS · 18 articles · 1.5s 3/3 routes fetched · 3 success ⚙ readSummaries → 53 article summaries loaded [核心结论] 过去一周 AI 领域有三个值得关注的方向(1) 多模态模型军备竞赛加剧 (2) AI Agent 从概念走向生产部署(3) 开源模型在中文场景首次逼近 GPT-4 水平。 [支撑信息] 1. GPT-5 多模态能力全面开放支持实时视频理解 — /jiqizhixin/latest 2. Spring AI 2.0 GA 发布企业级 Agent 框架成熟 — /36kr/latest 3. DeepSeek-V3 在 C-Eval 上追平 GPT-4 — /wukong/AI RssAgent /new ← 开始新会话 RssAgent /exitAgent 自主完成了分析意图 → 搜索平台 → 选择路由 → 并发抓取 → 读取摘要 → 聚合输出全程无需用户干预。其他命令RssAgent /sync ← 从 RSSHub 同步最新路由表 RssAgent /routes ← 查看所有平台 (~80个) RssAgent /routes github ← 查看 GitHub 的路由 RssAgent /new ← 开始新会话 RssAgent /help七、FAQQ1: RssAgent 和 Perplexity / Kimi 有什么本质区别Perplexity 和 Kimi 是「AI 搜索引擎」——爬虫盲扫全网网页LLM 做概括。你无法控制它们搜什么、从哪里搜。RssAgent 是「AI 搜索代理」——你或者 Agent 自己指定 RSSHub 路由 指定具体的网站和频道。结果是定点抓取的每个条目锚定一个 URL。类比Perplexity 是让 AI 去图书馆随便翻书找答案RssAgent 是让 AI 去指定的书架、指定的分类、指定的期刊里找——来源可控结果可溯源。Q2: 为什么叫 Agent 而不是 Search Engine因为 RssAgent 不是在「搜索」而是在「决策」。传统搜索引擎的流程是写死的分词 → 倒排 → 排序 → 返回而 RssAgent 的每一步——选平台、选路由、判断抓取时机、决定是否重试——都是 LLM 根据中间结果实时判断的。Agent 的定义感知环境 → 做出决策 → 执行动作 → 观察结果 → 再次决策。Q3: RSSHub 路由不够用怎么办没有路由的网站怎么办这是一个在 2025 年前确实存在的问题——但现在已经有了质的突破。方案一AI 自动生成路由零代码InsCode快马 集成了 Kimi-K2 模型粘贴目标网站 URL → AI 自动分析 HTML DOM → 识别标题、链接、时间等字段 → 生成符合 RSSHub 规范的 JavaScript 路由文件 → 一键部署到你的 RSSHub 实例。实测对手工维护效率提升90%5。方案二AI Agent 驱动的通用 RSS 工具OpenRSS 支持 Claude Code、Codex、Cursor、Gemini CLI 等 36 种 AI Agent可以将任意网站包括需要登录的页面和 SPA 应用转为 RSS 订阅源。安装只需一行npx skills add RelientS/openrss3。方案三多 AI 模型驱动的 FeedHubFeedHub 内置沙箱化 JS 执行环境集成 DeepSeek、OpenAI、Gemini、通义千问、豆包、Ollama 共 6 种 LLM支持自定义解析脚本 AI 内容分析 多端部署Web / 桌面 / 移动端4。方案四Copilot Agent 直接写路由脚本在 VS Code 中使用 GitHub Copilot Agent底层为 Claude 3.7 Sonnet给一个提示词就能自动生成lib/routes/namespace/route.ts路由脚本Fork RSSHub → Agent 写代码 → 调试 → 提 PR整个流程都在 VS Code 内完成6。本质变化以前「没有 RSS 路由」 死胡同现在「没有 RSS 路由」 让 AI 生成一个分钟级解决。Q4: 依赖 DeepSeek API 会不会很贵RssAgent 的 AI 调用分为两层Agent 决策层Tool Callingtoken 消耗极小每次决策约 200-500 token和摘要生成层每篇文章一次约 500-1000 token。DeepSeek Chat 的定价 远低于 GPT-4实际使用中一次完整查询抓取 5 个路由、摘要 25 篇文章的成本约¥0.05-0.157。Q5: EclipseStore 是什么为什么不用 MySQLEclipseStore 是一个零配置的嵌入式对象图持久化引擎。不需要建表、写 SQL、配 ORM——Java 对象图直接序列化到磁盘重启即恢复。对于 RssAgent 这种「单机部署、按路由分区存储」的场景EclipseStore 比关系型数据库简洁得多。Q6: 53 个测试是怎么组织的域测试类数量类型DTOArticlesTest, ArticleTest6单元解析RssXmlParserTest2单元配置仓库RssConfigRepositoryTest8Spring 集成实例管理RssInstanceManagerTest4Spring 集成HTTP 抓取RssFetcherTest, RssFetcherIntegrationTest4单元 集成服务编排ArticlesFetchServiceTest, RouteFetchServiceTest6单元 (Mockito)控制器RssControllerIntegrationTest4全链路集成存储SummaryViewTest, StorageIntegrationTest6单元 集成AI 路由RouteCatalogTest5单元AI 对话ContextManagerTest, ConversationServiceTest7单元 (Mockito)应用启动RssAgentApplicationTests1Spring 集成Mock ChatModel 通过TestAiConfigspring.factories自动注入测试 profile 完全隔离真实 API 调用。八、写在最后RssAgent 想做的事很简单让 AI 搜索的每一个结论都能追溯到一个人可以验证的原始来源。这听起来像是一个基本要求——但今天的 AI 搜索产品几乎没有真正做到可溯源。RSS 的复兴感谢 RSSHub 社区加上 AI 自动生成路由的突破给了我们一个独特的机会用一个结构化的、开放的路由生态替代黑盒的倒排索引让 AI Agent 在可控的范围内自主决策。项目开源在 GitHub — Nexknit/rssgse欢迎 Star / Issue / PR。路线图上还有 Web UI 等特性待实现。如果你对「RSS Agent 可溯源搜索」这个方向感兴趣欢迎一起建设。本文由 RssAgent 项目作者撰写。项目基于 Spring Boot 4.1 Spring AI 2.0 DeepSeek Chat 构建53 个测试全绿。Spring AI 2.0 Tool Calling 官方文档: Spring AI — Tool Calling. Spring AI Chat Memory: Spring AI — Chat Memory. ↩︎ ↩︎Aggarwal et al.,“GEO: Generative Engine Optimization”, KDD 2024. Princeton University, Georgia Tech, Allen Institute for AI. 研究验证引用权威来源、加入统计数据和专家引语可分别将 AI 引用率提升 30-40%三者叠加后整体提升 41%。 ↩︎OpenRSS —“Turn any website into an RSS feed powered by AI agents”. GitHub: RelientS/openrss. npm:openrss. 支持 Claude Code、Codex、Cursor、Gemini CLI 等 36 AI Agent. ↩︎ ↩︎FeedHub —“Dynamic RSS/API feed generation platform with multi-AI integration”. GitHub: fillpit/FeedHub. 支持 DeepSeek、OpenAI、Gemini、通义千问、豆包、Ollama 共 6 种 LLM. ↩︎ ↩︎“用 AI 自动生成 RSSHub 规则零代码搭建信息聚合器”. CSDN, 2025. InsCode快马集成 Kimi-K2 模型粘贴 URL 即可自动分析 DOM 并生成 RSSHub 路由。 ↩︎ ↩︎ ↩︎“使用 GitHub Copilot Agent 创建新的 RSSHub 路由”. shan333.cn. 先用 LLM 生成 Prompt → 注入 VS Code Copilot AgentClaude 3.7 Sonnet→ 自动编写路由脚本。 ↩︎ ↩︎DeepSeek Chat API 定价: platform.deepseek.com. 输入 ¥0.001/1K tokens输出 ¥0.002/1K tokens约为 GPT-4 的 1/20。 ↩︎ ↩︎ ↩︎