Spring AI 集成实战:从 Prompt 模板到 RAG 管道的全链路方案

AI2天前发布 beixibaobao
3 0 0

Spring AI 集成实战:从 Prompt 模板到 RAG 管道的全链路方案

一、裸调 API 的集成困局——企业级大模型接入的工程断层

在 Java 后端体系中集成大模型能力,最常见的做法是直接使用 HTTP 客户端调用模型供应商的 REST API。这种"裸调"方式在 POC 阶段快速验证可行,但在企业级生产环境中会暴露出一系列工程断层。

第一,Prompt 管理混乱。Prompt 模板散落在各个 Service 类的字符串常量中,修改一个 Prompt 需要重新部署应用。不同开发者的 Prompt 风格不统一,变量占位符格式各异(有的用 {},有的用 ${},有的用 Mustache),缺乏标准化管理。

第二,上下文窗口浪费。大模型的上下文窗口是稀缺资源,GPT-4o 的 128K Token 看似充裕,但系统指令、Few-shot 示例、对话历史和用户查询叠加后,很容易触及上限。没有合理的上下文裁剪策略,要么关键信息被截断,要么 Token 被无效内容浪费。

第三,RAG 管道缺乏框架支撑。检索增强生成(RAG)需要文档切分、向量化、检索、重排序、上下文组装等多个环节串联。每个环节用不同库实现,接口不统一,切换向量数据库或重排序模型时需要大量重构。

Spring AI 作为 Spring 生态的 AI 集成框架,提供了从 Prompt 模板到 RAG 管道的全链路抽象。本文将深入分析其架构设计,并给出生产级的集成方案。

二、Spring AI 的分层架构——从模型抽象到 RAG 编排

Spring AI 的核心设计理念是"模型可替换、管道可编排"。通过分层抽象,将模型调用、Prompt 管理、向量存储和 RAG 管道解耦,使每个环节可以独立替换和优化。

flowchart TB
    subgraph 应用层
        A[ChatService] --> B[DocumentQAService]
    end
    subgraph Spring AI 核心抽象
        C[ChatClient 统一调用接口]
        D[Prompt Template 模板引擎]
        E[Advisors 拦截器链]
    end
    subgraph RAG 管道
        F[DocumentReader 文档读取]
        G[DocumentTransformer 文档切分]
        H[EmbeddingModel 向量化]
        I[VectorStore 向量存储与检索]
        J[QuestionAnswerAdvisor RAG 顾问]
    end
    subgraph 模型适配层
        K[OpenAI ChatModel]
        L[DeepSeek ChatModel]
        M[Ollama ChatModel]
    end
    A --> C
    B --> J
    C --> D
    C --> E
    J --> I
    I --> H
    F --> G --> H
    E --> K
    E --> L
    E --> M

ChatClient 是 Spring AI 的统一调用入口,类似于 Spring 的 RestTemplateWebClient。它封装了模型选择、Prompt 组装、Advisor 拦截和响应解析的完整流程。

Advisors 是 Spring AI 的拦截器机制,类似于 Servlet Filter。可以在请求发送前修改 Prompt,在响应返回后进行后处理。RAG 的上下文注入、对话历史的裁剪、内容安全过滤等都可以通过 Advisor 实现。

VectorStore 抽象了向量数据库的读写接口,支持 Milvus、PgVector、Chroma、Redis 等多种实现,切换向量数据库只需更换依赖和配置,无需修改业务代码。

三、生产级代码:Spring AI 全链路集成实现

3.1 Prompt 模板管理与动态变量注入

/**
 * Prompt 模板服务:集中管理所有业务场景的 Prompt 模板
 * 模板使用 Spring AI 的 PromptTemplate 语法,
 * 支持 {variable} 占位符和条件逻辑
 */
@Service
public class PromptTemplateService {
    private final Map<String, PromptTemplate> templateRegistry;
    /**
     * 初始化时加载所有模板文件
     * 模板文件存放在 classpath:prompts/ 目录下,
     * 支持 .st(Spring AI 模板)格式
     */
    @PostConstruct
    public void loadTemplates() {
        templateRegistry = new HashMap<>();
        // 加载代码审查模板
        templateRegistry.put("code-review",
            new PromptTemplate("""
                你是一位资深的代码审查专家,请对以下代码进行审查。
                审查维度:
                1. 代码规范:命名、格式、注释
                2. 潜在缺陷:空指针、资源泄漏、并发问题
                3. 性能风险:不必要的对象创建、低效算法
                4. 安全隐患:SQL注入、XSS、敏感信息泄露
                编程语言:{language}
                代码片段:
                ```
                {code}
                ```
                请按维度逐一给出审查意见,标注严重等级(高/中/低)。
                """
            )
        );
    }
    /**
     * 渲染模板:注入变量并生成 Prompt
     * 变量缺失时抛出异常,避免模型收到不完整的 Prompt
     */
    public Prompt render(String templateName, Map<String, Object> variables) {
        PromptTemplate template = templateRegistry.get(templateName);
        if (template == null) {
            throw new IllegalArgumentException(
                "模板不存在: " + templateName
            );
        }
        // 校验必需变量是否全部提供
        Set<String> requiredVars = template.getInputTypes().keySet();
        for (String var : requiredVars) {
            if (!variables.containsKey(var)) {
                throw new IllegalArgumentException(
                    "模板 " + templateName + " 缺少必需变量: " + var
                );
            }
        }
        return template.create(variables);
    }
}

3.2 RAG 管道:文档切分、向量化与检索增强

/**
 * RAG 文档问答服务:完整的检索增强生成管道
 * 流程:用户提问 -> 向量检索相关文档片段 -> 组装上下文 -> 调用模型生成回答
 */
@Service
public class DocumentQAService {
    private final ChatClient chatClient;
    private final VectorStore vectorStore;
    private final TokenCountEstimator tokenEstimator;
    /**
     * 构建带 RAG Advisor 的 ChatClient
     * QuestionAnswerAdvisor 自动完成"检索 + 上下文注入"流程
     */
    public DocumentQAService(ChatClient.Builder clientBuilder,
                             VectorStore vectorStore,
                             TokenCountEstimator tokenEstimator) {
        this.vectorStore = vectorStore;
        this.tokenEstimator = tokenEstimator;
        this.chatClient = clientBuilder
            .defaultAdvisors(
                // RAG 顾问:检索相关文档并注入到 Prompt 上下文
                new QuestionAnswerAdvisor(vectorStore, 
                    SearchRequest.builder()
                        .topK(5)                    // 检索 Top5 相关片段
                        .similarityThreshold(0.75)   // 相似度阈值
                        .build()
                ),
                // 对话历史顾问:裁剪历史消息,控制上下文窗口
                new MessageChatMemoryAdvisor(
                    new InMemoryChatMemory(),
                    ChatMemory.CONVERSATION_ID_KEY,
                    10  // 保留最近 10 轮对话
                )
            )
            .build();
    }
    /**
     * 文档问答接口
     * 自动完成检索增强,无需手动组装上下文
     */
    public String ask(String question, String conversationId) {
        return chatClient.prompt()
            .user(question)
            .system("""
                你是一个专业的技术文档助手。请基于提供的上下文文档回答用户问题。
                如果上下文中没有相关信息,请明确说明"文档中未找到相关信息",
                不要编造答案。
                """)
            .advisors(a -> a
                .param(ChatMemory.CONVERSATION_ID_KEY, conversationId)
            )
            .call()
            .content();
    }
    /**
     * 导入文档到向量库
     * 包含切分策略和元数据标注
     */
    public void importDocument(Resource document, String docType) {
        // 文档切分:按段落切分,保留语义完整性
        TextSplitter splitter = new TokenTextSplitter(
            800,    // 每个片段目标 Token 数
            200,    // 相邻片段重叠 Token 数
            5,      // 最大片段数上限
            10000,  // 单片段最大字符数
            true    // 保留分隔符
        );
        List<Document> chunks = splitter.split(
            new TextReader(document).get()
        );
        // 为每个片段添加元数据,便于后续过滤
        chunks.forEach(chunk ->
            chunk.getMetadata().put("doc_type", docType)
        );
        // 批量写入向量库(Spring AI 自动调用 EmbeddingModel 向量化)
        vectorStore.add(chunks);
    }
}

3.3 多模型切换与降级配置

# application.yml - Spring AI 多模型配置
spring:
  ai:
    # 默认模型配置
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
          temperature: 0.3
          max-tokens: 2048
    # 备选模型:DeepSeek(通过 OpenAI 兼容接口)
    deepseek:
      base-url: https://api.deepseek.com
      api-key: ${DEEPSEEK_API_KEY}
      chat:
        options:
          model: deepseek-chat
          temperature: 0.3
    # 向量模型配置
    embedding:
      options:
        model: text-embedding-3-small
    # 向量数据库配置
    vectorstore:
      milvus:
        client: ${MILVUS_HOST:localhost}
        port: 19530
        database-name: ai_docs
        collection-name: document_chunks
        dimension: 1536
        metric-type: COSINE

四、Spring AI 的成熟度边界与生产风险

1. 框架成熟度与 API 稳定性

Spring AI 目前仍在快速迭代中,API 在小版本升级时可能发生不兼容变更。例如 1.0.0-M5 到 M6 之间,ChatClient 的构建方式从 Builder 模式变更为流式 API。在生产项目中引入 Spring AI,需要锁定版本并预留升级适配的工作量。

2. 向量数据库的性能瓶颈

Spring AI 的 VectorStore 抽象虽然统一了接口,但不同向量数据库的性能差异巨大。Milvus 在百万级向量检索时 P99 延迟约 50ms,而 PgVector 在同等数据量下可能达到 500ms。框架层的抽象屏蔽了这些差异,但也意味着开发者无法利用特定数据库的优化特性。

3. RAG 的检索质量瓶颈

RAG 的回答质量高度依赖检索的准确性。Top-K 检索可能返回语义相似但无关的片段,导致模型生成"看似合理但事实错误"的回答。Spring AI 的 QuestionAnswerAdvisor 目前不支持重排序(Reranking),需要自行集成 Cohere Reranker 或 BGE-Reranker 等模型。

4. Advisor 链的执行顺序敏感性

多个 Advisor 的执行顺序直接影响结果。例如,对话历史裁剪 Advisor 应在 RAG Advisor 之前执行,否则裁剪后的历史可能导致 RAG 检索方向偏移。Spring AI 的 Advisor 排序机制目前不够直观,需要开发者手动管理优先级。

风险维度 影响程度 缓解策略
API 不兼容 锁定版本 + 升级适配测试
向量库性能 基准测试选型 + 读写分离
检索准确率 引入 Reranking + 人工评测
Advisor 排序 显式声明顺序 + 集成测试

五、总结

Spring AI 为 Java 生态提供了大模型集成的标准化方案,其核心价值在于将 Prompt 管理、模型调用、RAG 管道和向量存储统一到 Spring 编程模型中。通过 ChatClient 和 Advisor 机制,开发者可以用声明式的方式组装 AI 能力,而不必关心底层模型供应商的差异。

落地路线建议:第一步,在非核心业务中引入 Spring AI 的 ChatClient 和 PromptTemplate,建立 Prompt 管理规范;第二步,对知识库问答场景搭建 RAG 管道,从文档切分到向量检索全链路验证;第三步,引入 Advisor 拦截器实现对话历史裁剪和内容安全过滤;第四步,在 RAG 管道中增加 Reranking 环节提升检索准确率,并建立人工评测机制持续优化。

© 版权声明

相关文章