通过 Spring AI 为项目添加 AI 相关功能

sunjk 发布于 2026-01-09 106 次阅读


本文中,我们将探讨如何借助Spring AI为项目添加AI相关功能,并对RAG(检索增强生成)进行初步了解,并且给出了使用到以上技术的具有摘要生成、对话功能的项目示例。

引言


RAG的作用

Spring AI支持聊天对话、记忆和检索增强生成(RAG),此功能允许我们为项目添加AI相关功能,如和文章对话、生成摘要等。

何谓RAG(检索增强生成)?
矢量数据库(Vector Store)用于将数据与 AI 模型集成。 使用它们的第一步是将您的数据加载到矢量数据库中。 然后,当要将用户查询发送到 AI 模型时,首先检索一组类似的文档。 然后,这些文档用作用户问题的上下文,并与用户的查询一起发送到 AI 模型。 这种技术被称为检索增强生成 (RAG)。

Vector Store 是 RAG 系统中负责“检索”这一步的核心基础设施

通俗来说,大型语言模型(LLMs)在训练后会被冻结,导致知识陈旧,无法访问或修改外部数据。所以问它没有被训练到的内容时,就会容易胡说八道。RAG的使用,可以避免得到不准确的信息。

RAG的流程

RAG 的核心思想是:不只靠模型“记”下来的知识,而是让模型在回答的当下“查”到最新/最相关的知识

其典型流程为:

  • 用户问题 → 转成向量(Embedding)
  • 向量检索 → 在海量文档块(chunk)的向量库里找最相似的 Top K 段文字
  • 召回 → 得到 K 段最相关的文本(retrieved chunks)
  • 增强 → 把这 K 段文本塞到 Prompt 里(通常跟系统提示、用户问题一起组成很长的上下文)
  • 生成 → 大语言模型拿着这个“加强版Prompt”进行生成
  • 输出 → 得到最终答案(理论上更准确、更新的信息)

使用


引入依赖

引入管理依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

引入模型依赖

Spring AI为我们提供了许多AI模型的starter,以DeepSeek为例:

<dependency> 
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>

使用其他AI将spring-ai-starter-model-xxx替换为要使用的AI即可,详见聊天模型

配置模型

以OpenAI为例:

spring:
    ai:
        openai:
            base-url:
            api-key:
            chat:
                options:
                    model:
                    temperature:

配置客户端

配置Client

使用ChatClient工厂构建Client

@Bean
public ChatClient chatClient(OllamaChatModel model) {
    return ChatClient.builder(model)
                    .defaultSystem("你是一个AI助手")
                    .build();
}
  • 阻塞式返回结果(等待完全生成后返回)
String content = chatClient.prompt()
        .user("你是谁?")
        .call()
        .content();
  • 流式返回结果(边生成边返回)
Flux<String> content = chatClient.prompt()
        .user()
        .stream()
        .content();

什么是Flux?
是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流行响应式编程出现的框架。

示例


以知文摘要生成、对话功能为例,项目地址

以下内容主要展示AI相关功能,故部分代码省略

知文摘要生成

大模型配置类LlmConfig.java

@Configuration
public class LlmConfig {
    @Bean
    public ChatClient chatClient(@Qualifier("deepSeekChatModel") ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}

@Qualifier("deepSeekChatModel")是什么?
@Qualifier 是 Spring 框架中用于解决 bean 名称冲突的一种注解写法,在 Spring AI 中,当你引入 spring-ai-deepseek-spring-boot-starter(或相关依赖)后,框架会自动创建一个 DeepSeekChatModel 类型的 bean,在本例中即为"deepSeekChatModel",用于在代码中明确注入这个特定的 DeepSeek 模型

知文摘要生成接口 KnowPostDescriptionService.java

public interface KnowPostDescriptionService { 
    String generateDescription(String content); 
}

AI 生成知文摘要实现 KnowPostDescriptionServiceImpl.java

@Service
@RequiredArgsConstructor
public class KnowPostDescriptionServiceImpl implements KnowPostDescriptionService {

    private final ChatClient chatClient;

    public String generateDescription(String content){

        // 正文内容合法判断,略

        String system = "你是中文文案编辑,请基于用户提供的知文正文,生成一个中文描述,简洁有吸引力,"
        String user = "正文如下" + content + "\n\n 请直接给出不超过50字的中文描述";

        try {
            String result = chatClient
                    .prompt()
                    .system(system)
                    .user(user)
                    .options(DeepSeekChatOptions.builder()
                        .model("deepseek-chat")
                        .temperature(0.8)
                        .maxTokens(120)
                        .build())
                    .call()
                    .content();
            return result;
        } catch (Exception e) {
            // 抛出大模型调用失败异常,略
        }
    }
}
  • prompt()是 Spring AI 中 ChatClient 接口的核心入口方法,作用为:开启一次新的对话 Prompt(提示)的构建过程,返回一个建造者(Builder),让你可以一步步“拼装”这次要发给大模型的完整 Prompt。
  • .system():系统提示词
  • .user():用户消息

DeepSeekChatOptions属性详见:DeepSeek聊天属性

@RequiredArgsConstructorLombok 提供的一个非常常用的注解,主要作用是:自动生成一个包含「所有 final 字段 + @NonNull 字段」的构造方法**

接口控制层 KnowPostAiController.java

@RestController
@RequestMapping(path = "/api/v1/knowposts", produces = "MediaType.APPLICATION_JSON_VALUE")
@RequiredArgsConstructor
public class KnowPostAiController {

    private final KnowPostDescriptionService descriptionService;

    /**
     * 生成不超过50字的知文描述
     */
    @PostMapping(path = "/description/suggest", consumes = MediaType._APPLICATION_JSON_VALUE_)
    public DescriptionSuggestResponse suggest(@Valid @RequestBody DescriptionSuggestRequest req) {
        String desc = descriptionService.generateDescription(req.content());
        return new DescriptionSuggestResponse(desc);
    }
}

对话功能(使用RAG)

RAG 索引构建服务 RagIndexService.java

@Service
@RequiredArgsConstructor
public class RagIndexService {
    // 创建一个专门属于当前类(RagIndexService)的日志工具对象 log
    private static final Logger log = LoggerFactory.getLogger(RagIndexService.class);

    // 向量库封装,负责写入/检索向量
    private final VectorStore vectorStore;
    // 数据访问:根据postId查询知文详情
    private final KnowPostMapper knowPostMapper;
    // 拉取Markdown正文内容
    private final RestTemplate http = new RestTemplate();
    // 使用ES客户端操作切片
    private final ElatsticsearchClient es;
    // ES相关配置
    private final EsProperties esProps;

    public int reindexSinglePost(long postId) {
        KnowPostDetailRow row = knowPostMapper.findDetailById(postId);

        // 判断row是否为空
        // 仅索引公开的已发布的知文
        // 判断内容地址是否缺失

        // 抓取Markdown正文
        String text = fetchContent(row.getContentUrl());
        // 判断正文是否为空

        List<String> chunks = chunkMarkdown(text);

        deleteExistingChunks(postId);

        // 组装 Document(文本 + 业务元数据),用于向量写入与检索过滤
        List<Document> docs = new ArrayList<>(chunks.size());
        for(int i = 0; i < chunks.size(); i++){
            String cid = postId + "#" + i; 
            Map<String, Object> meta = new HashMap<>(); 
            meta.put("postId", String._valueOf_(postId)); 
            meta.put("chunkId", cid); meta.put("position", i);
            meta.put("contentEtag", currentEtag); 
            meta.put("contentSha256", currentSha); 
            meta.put("contentUrl", row.getContentUrl()); 
            meta.put("title", row.getTitle()); 
            docs.add(new Document(chunks.get(i), meta));
        }
        try {
            vectorStore.add(docs);
        } catch (Exception e) {
            log.error("VectorStore add failed");
            return 0;
        }
        // 返回本次写入的切片数量
        return docs.size();
    }

    /** 
     *获取正文内容
     */
    private String fetchContent(String url) {}

    /**
     *按Markdown 标题切段 
     */
    private List<String> chunkMarkdown(String text) {}

RAG 问答查询服务 RagQueryService.java

@Service
@RequiredArgsConstructor
public class RagQueryService { 
    // 向量检索接口(Elasticsearch 向量库封装) 
    private final VectorStore vectorStore; 
    // 大模型对话客户端(在 LlmConfig 中通过 @Qualifier 绑定 deepSeekChatModel)
    private final ChatClient chatClient;
    // 索引服务:确保帖子在问答前已建立/更新索引
    private final RagIndexService indexService;

    public Flux<String> streamAnswerFlux(long postId, String question, int topK, int maxTokens) { 
    // 检索上下文:先宽召回,再按 postId 做服务端过滤 
    List<String> contexts = searchContexts(String._valueOf_(postId), question, Math._max_(1, topK)); 
    // 组装上下文文本,分隔符用于提示词中分块标识
    String context = String._join_("\n\n---\n\n", contexts);
    // 系统提示:限定只依据提供的上下文作答,无法确定需明确说明 
    String system = "你是中文知识助手。只能依据提供的知文上下文回答;无法确定的请说明不确定。"; 
    // 用户消息:包含问题和召回到的上下文 
    String user = "问题:" + question + "\n\n上下文如下(可能不完整):\n" + context + "\n\n请基于以上上下文作答。"; 
    return chatClient.prompt() // 构建对话 
        .system(system) 
        .user(user) 
        .options(DeepSeekChatOptions.builder()
            .model("deepseek-chat") // 指定 DeepSeek 模型 
            .temperature(0.2) // 低温度:更稳健、少发散 
            .maxTokens(maxTokens) // 控制最大输出长度 
            .build()) 
        .stream() // 以流式(SSE)返回模型输出 
        .content(); // 转换为 Flux<String> 
}

接口控制层 KnowPostRagController.java

@RestController
@RequestMapping("/api/v1/knowposts")
@Validated
@RequiredArgsConstructor
public class KnowPostRagController {
    private final RagIndexService indexService;
    private final RagQueryService ragQueryService;

    @GetMapping(value = "/{id}/qa/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> qaStream(@PathVariable("id") long id,
                                @RequestParam("question") String question, 
                                @RequestParam(value = "topK", defaultValue = "5") int topK, 
                                @RequestParam(value = "maxTokens", defaultValue = "1024") int maxTokens) { 
        return ragQueryService.streamAnswerFlux(id, question, topK, maxTokens); 
    }

References


Spring AI概述 - SpringBoot教程
矢量数据库 - SpringBoot教程

此作者没有提供个人介绍。
最后更新于 2026-01-10