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 | 配置 Key |
|---|---|---|
| DeepSeek | spring-ai-starter-deepseek |
spring.ai.deepseek.api-key |
| OpenAI | spring-ai-starter-openai |
spring.ai.openai.api-key |
| Ollama(本地) | spring-ai-starter-ollama |
spring.ai.ollama.base-url |
| Qwen / 通义千问 | spring-ai-starter-qwen |
spring.ai.qwen.api-key |
RssHarness 选用 DeepSeek——中文理解强、成本约为 GPT-4 的 1/20:
<!-- pom.xml — 换成其他模型只需改 artifactId + 配置 key -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-deepseek</artifactId>
<version>2.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 或启动命令中注入
export DEEPSEEK_API_KEY=sk-your-key-here
对于生产环境,RssHarness 使用 Docker Compose 的环境变量注入:
# docker-compose.yml
services:
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 编程:
@Configuration
public class AiConfig {
@Bean
@Primary
public ChatClient chatClient(ChatModel chatModel, RssTools rssTools,
ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultTools(rssTools)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
new SimpleLoggerAdvisor())
.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():
@Service
public class ConversationService {
@Autowired private ChatClient chatClient;
@Autowired private RssTools rssTools;
public List<FetchResponse> searchStreaming(String sessionId,
String question,
SearchCallback cb) {
cb.onThinking("Thinking …");
try {
ChatResponse last = chatClient.prompt()
.user(question)
.stream()
.chatResponse() // ← Flux<ChatResponse>,非 Flux<String>
.doOnNext(resp -> {
String text = resp.getResult().getOutput().getText();
if (text != null) cb.onResponseToken(text);
})
.blockLast(); // ← 最终 ChatResponse 含 Usage 元数据
// 从 ChatResponse 元数据中拿到真实 Token 消耗
if (last != null && last.getMetadata().getUsage() != null) {
cb.onTokens(last.getMetadata().getUsage().getTotalTokens());
}
} catch (Exception e) {
cb.onError("ai", e.getMessage());
}
return rssTools.getLastResults();
}
}
关键点:
.stream().chatResponse()返回Flux<ChatResponse>而非Flux<String>——每个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/Milvus) | RSS 订阅源 + EclipseStore 持久化 |
| 可溯源性 | 弱——文本片段脱离原始 URL | 强——每条摘要有 title + URL + publisher + publishTime |
| 索引维护 | 需要定期 re-embedding | RSS 天然增量更新,无需 embedding |
RssHarness 的 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 步骤 |
|---|---|---|---|
| 1 | searchPlatforms(keyword) |
在 ~80 个平台中按关键词搜索 | 索引检索 |
| 2 | listRoutes(platform) |
列出某平台的可用 RSS 路由 | 索引检索(细化) |
| 3 | fetchRss(routes) |
对指定路由发起实时 RSS 抓取 | 数据获取 |
| 4 | readSummaries(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.
""")
public List<FetchResponse> fetchRss(
@ToolParam(description = "Exact paths from listRoutes with :params filled")
List<String> routes
) {
List<FetchResponse> results = rssController.fetchRss(routes).join();
lastResults.set(results);
return results;
}
LLM 在看到 @Tool(description = ...) 和 @ToolParam(description = ...) 后,会自动判断何时调用、传什么参数。开发者只需要声明工具——框架负责编排。
这就是 Agent 的实质
RssHarness 之所以叫 Agent 而不是"搜索工具",是因为它的控制流是不确定的——每步取决于 LLM 对中间结果的实时判断:
用户: "最近AI有什么进展?"
→ LLM: 先 searchPlatforms("AI") → 返回 5 个平台
→ LLM: 选"机器之心",listRoutes → 返回 8 个路由
→ LLM: 选 /jiqizhixin/latest,fetchRss → 20 篇文章
→ LLM: readSummaries → 53 条 AI 摘要
→ LLM: 聚合为 3 条核心结论 + 溯源链接
全程没有一行代码规定"先搜什么再读什么"。这就是 Agent 的定义:感知 → 决策 → 执行 → 观察 → 再决策[^2]。
⑦ 多实例容错与异步管道
从 Demo 走向生产,稳定性是首要考量。RssHarness 在 RSS 抓取层实现了三层容错:
滑动窗口健康评分
多个 RSSHub 实例的负载均衡不能用简单轮询——故障实例每轮都会被选到,浪费 3 秒 HTTP 超时。
// RssInstanceManager 的核心逻辑
// Deque<Boolean> — 最近 10 次成功/失败
// 按成功率排序 → 高成功率优先 → 故障实例自动下沉
原子 CAS 消除竞态
@Async + CompletableFuture.allOf 扇出模式下,多个线程可能同时刷新同一个路由:
// ConcurrentHashMap.compute() — 合并 check+set,消除 TOCTOU 窗口
boolean alreadyRefreshing = refreshMarks.compute(route, (k, v) -> {
if (v != null && v) return true; // 已在刷新中
return true; // 标记为刷新中
});
降级保护
AI 摘要失败时不丢失核心数据——自动回退到 placeholder 摘要(保留 title + URL + publishTime)。
设计决策:RssHarness 的全异步管道(
@Async+allOf)配合tryMarkRefresh原子 CAS 和三实例容错,实测在单实例故障时延迟仅增加 3-5 秒(取决于超时配置),无数据丢失。
⑧ 性能优化与生产部署
流式输出是体验底线
RssHarness 的 CliRunner 实现了三级颜色渲染:灰色=思考过程,青色=工具调用,白色=最终回复。流式输出的感知延迟比非流式低 60% 以上。
成本控制
RssHarness 的 AI 调用分为两层:
| 层级 | 单次 Token | 频率 | 成本占比 |
|---|---|---|---|
| Agent 决策层(Tool Calling) | 200-500 | 5-10 次/查询 | ~20% |
| 摘要生成层 | 500-1000 | N 篇文章 | ~80% |
以 DeepSeek Chat 的定价(约为 GPT-4 的 1/20),一次完整查询(5 个路由、25 篇文章)的总成本约 ¥0.05-0.15。
Docker 一键部署
export DEEPSEEK_API_KEY=sk-your-key
docker-compose up -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 个 @Tool:searchPlatforms / listRoutes
│ │ / fetchRss / readSummaries
│ ├─ RouteCatalog ← 内存路由索引,本地 JSON 持久化
│ └─ RouteSyncTask ← DOM+XPath 从 RSSHub 同步路由
│
RSS Domain (rss/) ← 执行层:异步管道
├─ RouteFetchService ← async allOf 扇出编排
│ ├─ RssFetcher ← 多实例容错 + 滑动窗口
│ ├─ AiSummaryService ← DeepSeek 摘要生成
│ └─ SummaryStorageService ← 适配层 → 存储域
│
Storage Domain (storage/) ← EclipseStore 零配置持久化
├─ DataRoot ← 聚合根即数据库
└─ SummaryView ← CQS 读写视图
| 维度 | 数据 |
|---|---|
| 运行时 | Java 21 + Spring Boot 4.1.0 |
| AI 框架 | Spring AI 2.0.0 |
| 模型 | DeepSeek Chat |
| Tool 数 | 4 个 @Tool |
| 平台覆盖 | ~80 个 |
| 路由覆盖 | 2000+(+ AI 可自动生成新路由) |
| 测试 | 53 个,0 失败 |
| 部署 | Docker 一键启动 |
FAQ
Q1: 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 是模型级协议——模型决定调用哪个函数、传什么参数。MCP(Model Context Protocol)是工具级协议——定义工具如何被发现和调用。Spring AI 2.0 目前原生支持 Tool Calling,MCP 支持在路线图上。对 RssHarness 这种工具数量少但调用逻辑复杂的场景,Tool Calling 已经足够。
Q3: RSSHub 路由不够用怎么办?
2025-2026 年,AI 已经可以自动为任意网站生成 RSS 路由了:OpenRSS(36+ AI Agent 驱动)、FeedHub(6 种 LLM)、InsCode(Kimi-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/RssHarness,Docker 镜像在 makeiny/rss-harness。Star / Issue / PR 都欢迎。
本文基于 Spring Boot 4.1 + Spring AI 2.0.0 + DeepSeek Chat 撰写。RssHarness 53 个测试全绿,Docker 一键部署。
更多推荐




所有评论(0)