欢迎光临
我们一直在努力

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 自动配置 ChatModel Bean。后续业务代码始终面向 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:skyourkeyhere}

踩坑记录: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)
    未经允许不得转载:171主机测评 » Spring AI 2.0 从入门到 Agent:用 Tool Calling 构建可溯源 RAG 应用(RssHarness 实战全解)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址