Spring AI 2.0 从入门到 Agent:用 Tool Calling 构建可溯源 RAG 应用(RssHarness 实战全解) Spring AI 2.0 从入门到 Agent用 Tool Calling 构建可溯源 RAG 应用RssHarness 实战全解在本地开发环境中集成大语言模型能力曾经是让许多开发者望而却步的难题。随着模型即服务MaaS模式的成熟和 Spring AI 2.0 的发布普通 Java 后端工程师无需 Python 生态、无需算法背景就能在几分钟内构建出具备智能对话和自主工具调用能力的 AI Agent。本文以RssHarness——一个基于 Spring AI 2.0 DeepSeek RSSHub 构建的可溯源搜索 Agent——为贯穿全文的实战案例覆盖从环境搭建到生产部署的完整路径。53 个测试全绿Docker 一键部署源码开源。① 开发环境搭建与依赖配置工欲善其事必先利其器。Spring AI 2.0 的起步只需要一个标准的 Spring Boot 项目加上一个 Maven 坐标。核心依赖Spring AI 2.0 的 Starter 体系为每个模型提供商封装了独立的依赖——模型不同Maven 坐标不同模型提供商Maven Artifact配置 KeyDeepSeekspring-ai-starter-deepseekspring.ai.deepseek.api-keyOpenAIspring-ai-starter-openaispring.ai.openai.api-keyOllama本地spring-ai-starter-ollamaspring.ai.ollama.base-urlQwen / 通义千问spring-ai-starter-qwenspring.ai.qwen.api-keyRssHarness 选用 DeepSeek——中文理解强、成本约为 GPT-4 的 1/20!-- pom.xml — 换成其他模型只需改 artifactId 配置 key --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-deepseek/artifactIdversion2.0.0/version/dependency引入对应 Starter 后Spring AI 自动配置ChatModelBean。后续业务代码始终面向ChatClient抽象层编程——切换模型只改 Maven 坐标和配置 Key不改一行业务逻辑。密钥安全第一道防线切勿将 API Key 硬编码。推荐三层隔离# application.properties — 通过环境变量注入 spring.ai.deepseek.api-key${DEEPSEEK_API_KEY:}# .bashrc / .zshrc 或启动命令中注入exportDEEPSEEK_API_KEYsk-your-key-here对于生产环境RssHarness 使用 Docker Compose 的环境变量注入# docker-compose.ymlservices:rssharness:environment:-DEEPSEEK_API_KEY${DEEPSEEK_API_KEY:-sk-your-key-here}踩坑记录Spring AI 的自动配置在找不到 API Key 时不会报错启动失败——它只会在第一次请求时抛出AuthenticationException。建议在ApplicationRunner中做一个启动时的连通性检查。② 核心概念解析与 LLM 连接Token 并非字符Token 是模型处理文本的基本单位大致相当于 0.75 个英文单词或半个汉字。理解 Token 机制至关重要——它直接决定了输入输出长度上限和计费成本。以 DeepSeek Chat 为例输入 ¥0.001/1K tokens输出 ¥0.002/1K tokens一次完整的 Agent 调用含 Tool Calling 循环和摘要生成约 ¥0.05-0.15。Spring AI 的抽象层Spring AI 的核心价值在于模型无关的抽象。无论是 DeepSeek、OpenAI 还是 Ollama你的业务代码始终面向ChatClient编程ConfigurationpublicclassAiConfig{BeanPrimarypublicChatClientchatClient(ChatModelchatModel,RssToolsrssTools,ChatMemorychatMemory){returnChatClient.builder(chatModel).defaultTools(rssTools).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. ).build();}}ChatClient是线程安全的单例。不需要每次请求创建新实例——Spring 容器管理其生命周期。同时通过default系列可以向Client注入默认选项减少重复编码。通过Spring Boot提供的注解我们可以提供多个不同的Client供不同的服务调用。default系列方法在此更加灵活和高效。③ 构建第一个 AI 对话应用核心逻辑接收用户输入 → 封装消息 → 发送给模型 → 解析流式响应。RssHarness 的ConversationService展示了一次调用完成全链路编排的模式——核心是一行chatClient.prompt().user(question).stream().chatResponse()ServicepublicclassConversationService{AutowiredprivateChatClientchatClient;AutowiredprivateRssToolsrssTools;publicListFetchResponsesearchStreaming(StringsessionId,Stringquestion,SearchCallbackcb){cb.onThinking(Thinking …);try{ChatResponselastchatClient.prompt().user(question).stream().chatResponse()// ← FluxChatResponse非 FluxString.doOnNext(resp-{Stringtextresp.getResult().getOutput().getText();if(text!null)cb.onResponseToken(text);}).blockLast();// ← 最终 ChatResponse 含 Usage 元数据// 从 ChatResponse 元数据中拿到真实 Token 消耗if(last!nulllast.getMetadata().getUsage()!null){cb.onTokens(last.getMetadata().getUsage().getTotalTokens());}}catch(Exceptione){cb.onError(ai,e.getMessage());}returnrssTools.getLastResults();}}关键点.stream().chatResponse()返回FluxChatResponse而非FluxString——每个ChatResponse既含增量文本最终响应还携带Usage元数据真实 Token 消耗非字符数估算MessageChatMemoryAdvisor自动管理多轮对话历史开发者无需手动维护messages列表doOnNext回调实现了 CLI 的逐步渲染对比 springStart.md 的 Python 示例Python 版需要手动维护messages [...]列表、手动 append user/assistant 消息、手动处理流式块拼接。Spring AI 将这些全部封装在 Advisor 和 reactive stream 中。④ 提示词工程与上下文管理提示词工程并非玄学而是一门关于如何清晰表达需求的艺术。以 RssHarness 的 System Prompt 为例.defaultSystem( You are an RSS aggregation engine. Your core capability lies in retrieving real-time information via precise RSSHub routes. Every step must adhere to structured route definitions; fuzzy searches or guessing routes are prohibited. Your final response must follow the format: [Core Conclusion] [Supporting Information] (ordered by importance, max 30 chars per item source link) Before outputting, check for vague terms like various types or multiple aspects. If present, replace immediately with specific titles. )这个 System Prompt 包含了提示词工程的四个要素角色设定— “RSS aggregation engine”明确行为边界任务描述— “retrieving real-time information via precise routes”约束条件— “fuzzy searches prohibited”禁止幻觉式猜测输出格式—[Core Conclusion][Supporting Information]结构化输出上下文管理的三层策略随着 Tool Calling 循环的进行上下文窗口面临溢出风险。Spring AI 提供了三层策略策略实现适用场景滑动窗口MessageChatMemoryAdvisor的maxMessages参数长对话只保留最近 N 轮Token 预算裁剪ContextManager自定义逻辑多轮 Tool Calling按 Token 数精确裁剪摘要压缩用一次额外 LLM 调用将历史对话压缩为摘要需要保留早期关键信息但 Token 紧张RssHarness 使用MessageChatMemoryAdvisor配合/new命令手动重置——对于单次搜索场景对话轮数通常不超过 15 轮Token 压力在可控范围内。⑤ RAG 实战让 RssHarness 的答案可溯源大模型的知识截止于训练结束之日且无法知晓 RSSHub 的实时路由信息。RAG检索增强生成是标准解决方案但 RssHarness 做了一层关键增强。标准 RAG vs. 可溯源 RAG维度标准 RAG向量检索RssHarness 的可溯源 RAG检索目标语义相似文本片段RSSHub 结构化路由 → 实时文章数据来源向量数据库Chroma/MilvusRSS 订阅源 EclipseStore 持久化可溯源性弱——文本片段脱离原始 URL强——每条摘要有 title URL publisher publishTime索引维护需要定期 re-embeddingRSS 天然增量更新无需 embeddingRssHarness 的 RAG 流程用户提问 → LLM 分析意图 → searchPlatforms(AI) ← 检索在 ~80 个平台中定位 → listRoutes(机器之心) ← 检索在平台内定位具体频道 → fetchRss(routes) ← 获取实时 HTTP 抓取 → readSummaries(routes) ← 增强读取 AI 摘要 → LLM 聚合输出 溯源链接 ← 生成带 URL 的回答关键差异在于结构化路由替代了向量相似度检索。RSSHub 的路由命名空间天然是分层的、精确的——不存在语义相似但不相关的噪声。GEO 提示根据 Princeton Georgia Tech Allen AI 在 KDD 2024 发布的 GEO 研究论文引用权威来源可使 AI 引用率提升 30-40%加入统计数据再提升 30-40%——三者叠加后 AI 引用率整体提升 41%。可溯源 RAG 不仅是用户信任问题也是 AI 是否愿意引用你内容的技术前提[^1]。⑥ Tool Calling让模型行动起来这是全文最关键的章节。现代大模型不仅能聊天还能行动。通过 Function Calling 机制模型可以识别用户意图中需要执行的具体操作并提取参数交由本地代码执行。Spring AI 2.0 的 Tool Calling在 Spring AI 2.0 中你只需要给方法加上Tool注解并注册到ChatClient框架会自动处理 Tool Calling 循环LLM 输出 tool_call → Spring AI 执行 → 结果注入上下文 → LLM 观察结果 → 决定下一步 → 重复直到输出最终回答RssHarness 暴露给 LLM 的工具只有 4 个#Tool 方法作用对应传统 RAG 步骤1searchPlatforms(keyword)在 ~80 个平台中按关键词搜索索引检索2listRoutes(platform)列出某平台的可用 RSS 路由索引检索细化3fetchRss(routes)对指定路由发起实时 RSS 抓取数据获取4readSummaries(routes)读取已存储的 AI 摘要增强生成以fetchRss为例Tool 定义的完整代码Tool(description FETCH real-time RSS content. MANDATORY — call after listRoutes. Drop OPTIONAL params (? suffix) entirely. Fill REQUIRED params with real values. )publicListFetchResponsefetchRss(ToolParam(descriptionExact paths from listRoutes with :params filled)ListStringroutes){ListFetchResponseresultsrssController.fetchRss(routes).join();lastResults.set(results);returnresults;}LLM 在看到Tool(description ...)和ToolParam(description ...)后会自动判断何时调用、传什么参数。开发者只需要声明工具——框架负责编排。这就是 Agent 的实质RssHarness 之所以叫 Agent 而不是搜索工具是因为它的控制流是不确定的——每步取决于 LLM 对中间结果的实时判断用户: 最近AI有什么进展 → LLM: 先 searchPlatforms(AI) → 返回 5 个平台 → LLM: 选机器之心listRoutes → 返回 8 个路由 → LLM: 选 /jiqizhixin/latestfetchRss → 20 篇文章 → LLM: readSummaries → 53 条 AI 摘要 → LLM: 聚合为 3 条核心结论 溯源链接全程没有一行代码规定先搜什么再读什么。这就是 Agent 的定义感知 → 决策 → 执行 → 观察 → 再决策[^2]。⑦ 多实例容错与异步管道从 Demo 走向生产稳定性是首要考量。RssHarness 在 RSS 抓取层实现了三层容错滑动窗口健康评分多个 RSSHub 实例的负载均衡不能用简单轮询——故障实例每轮都会被选到浪费 3 秒 HTTP 超时。// RssInstanceManager 的核心逻辑// DequeBoolean — 最近 10 次成功/失败// 按成功率排序 → 高成功率优先 → 故障实例自动下沉原子 CAS 消除竞态AsyncCompletableFuture.allOf扇出模式下多个线程可能同时刷新同一个路由// ConcurrentHashMap.compute() — 合并 checkset消除 TOCTOU 窗口booleanalreadyRefreshingrefreshMarks.compute(route,(k,v)-{if(v!nullv)returntrue;// 已在刷新中returntrue;// 标记为刷新中});降级保护AI 摘要失败时不丢失核心数据——自动回退到 placeholder 摘要保留 title URL publishTime。设计决策RssHarness 的全异步管道AsyncallOf配合tryMarkRefresh原子 CAS 和三实例容错实测在单实例故障时延迟仅增加 3-5 秒取决于超时配置无数据丢失。⑧ 性能优化与生产部署流式输出是体验底线RssHarness 的CliRunner实现了三级颜色渲染灰色思考过程青色工具调用白色最终回复。流式输出的感知延迟比非流式低 60% 以上。成本控制RssHarness 的 AI 调用分为两层层级单次 Token频率成本占比Agent 决策层Tool Calling200-5005-10 次/查询~20%摘要生成层500-1000N 篇文章~80%以 DeepSeek Chat 的定价约为 GPT-4 的 1/20一次完整查询5 个路由、25 篇文章的总成本约 ¥0.05-0.15。Docker 一键部署exportDEEPSEEK_API_KEYsk-your-keydocker-composeup-d# RssHarness RSSHub 全套就绪生产环境建议配合消息队列削峰填谷并设置熔断器——当上游 DeepSeek API 不稳定时自动降级为缓存结果或友好提示。⑨ 安全与合规防注入RssHarness 的 System Prompt 中明确设定了行为边界——“fuzzy searches or guessing routes are prohibited”——这是最基础的防注入层即使用户试图用 Prompt Injection 让模型绕过路由系统System Prompt 的约束也会阻止。API Key 管理三层隔离环境变量 →application.properties占位符 → Docker Compose 注入。绝不出现在源码或配置文件中。数据隐私RssHarness 处理的全是公开 RSS 订阅源内容不涉及用户个人身份信息PII。私有部署场景下可切换为 Ollama 本地模型确保数据不出域。⑩ 完整案例回顾RssHarness 全貌CLI (CliRunner) ← 交互式 REPL/sync /routes /new │ AI Domain (ai/) ← Agent 大脑LLM 决策 Tool Calling ├─ ConversationService ← 唯一一次 ChatClient 调用 │ ├─ RssTools ← 4 个 ToolsearchPlatforms / listRoutes │ │ / fetchRss / readSummaries │ ├─ RouteCatalog ← 内存路由索引本地 JSON 持久化 │ └─ RouteSyncTask ← DOMXPath 从 RSSHub 同步路由 │ RSS Domain (rss/) ← 执行层异步管道 ├─ RouteFetchService ← async allOf 扇出编排 │ ├─ RssFetcher ← 多实例容错 滑动窗口 │ ├─ AiSummaryService ← DeepSeek 摘要生成 │ └─ SummaryStorageService ← 适配层 → 存储域 │ Storage Domain (storage/) ← EclipseStore 零配置持久化 ├─ DataRoot ← 聚合根即数据库 └─ SummaryView ← CQS 读写视图维度数据运行时Java 21 Spring Boot 4.1.0AI 框架Spring AI 2.0.0模型DeepSeek ChatTool 数4 个 Tool平台覆盖~80 个路由覆盖2000 AI 可自动生成新路由测试53 个0 失败部署Docker 一键启动FAQQ1: Spring AI 和 LangChain 怎么选Spring AI 是 Java 生态的原生方案LangChain 是 Python 生态的方案。如果你已有 Spring Boot 技术栈Spring AI 2.0 的 Tool Calling、Advisor、ChatMemory 机制完全覆盖了 LangChain 的核心能力且类型安全、IDE 友好、无需跨语言调用。Q2: Tool Calling 和 MCP 是什么关系Tool Calling 是模型级协议——模型决定调用哪个函数、传什么参数。MCPModel Context Protocol是工具级协议——定义工具如何被发现和调用。Spring AI 2.0 目前原生支持 Tool CallingMCP 支持在路线图上。对 RssHarness 这种工具数量少但调用逻辑复杂的场景Tool Calling 已经足够。Q3: RSSHub 路由不够用怎么办2025-2026 年AI 已经可以自动为任意网站生成 RSS 路由了OpenRSS36 AI Agent 驱动、FeedHub6 种 LLM、InsCodeKimi-K2 零代码生成。以前没有 RSS 路由是阻塞问题现在让 AI 生成一个分钟级解决[^3]。Q4: 一次查询 5 个路由、25 篇文章成本真的只要 ¥0.05是的。DeepSeek Chat 的定价为输入 ¥0.001/1K tokens输出 ¥0.002/1K tokens。Agent 决策层 Tool Calling 每次约 200-500 tokens摘要生成每篇约 500-1000 tokens。实测 5 路由 25 篇文章约消耗 30K-60K tokens总成本 ¥0.05-0.15。你可以在 DeepSeek 控制台 实时监控用量。写在最后从Tool注解到 Agent 自主编排从 SSE 流式输出到多实例容错——Spring AI 2.0 把曾经需要数百行胶水代码的工作压缩到了框架层。RssHarness 只是一个例子任何需要 LLM 自主决策检索策略的场景都可以用同一套 Tool Calling 模式解决。项目开源在 GitHub — RssHarness-dev/RssHarnessDocker 镜像在 makeiny/rss-harness。Star / Issue / PR 都欢迎。本文基于 Spring Boot 4.1 Spring AI 2.0.0 DeepSeek Chat 撰写。RssHarness 53 个测试全绿Docker 一键部署。