找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2406

积分

0

好友

336

主题
发表于 17 小时前 | 查看: 0| 回复: 0

想要快速上手,在 Java 生态中构建功能丰富的 AI 应用吗?本文将带你使用 Spring Boot 3.5.9、JDK 21 以及 Spring AI Alibaba 1.1.2,从零开始,逐步实现一个集成了基础对话、会话记忆、结构化输出、工具调用和 RAG(检索增强生成)的完整 AI 应用。我们将以阿里百炼作为大模型服务,让你一站式体验企业级 AI 集成的核心功能。

一、技术栈

  • Spring Boot 3.5.9
  • JDK 21
  • Spring AI 1.1.2
  • 阿里百炼

二、项目创建与配置

  1. 创建Spring Boot项目
    使用 Maven 构建,以下是 pom.xml 的核心依赖配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.5.10</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>springai</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springai</name>
        <description>springai</description>
        <url/>
        <licenses>
            <license/>
        </licenses>
        <developers>
            <developer/>
        </developers>
        <scm>
            <connection/>
            <developerConnection/>
            <tag/>
            <url/>
        </scm>
        <properties>
            <java.version>21</java.version>
            <spring-ai.version>1.1.2</spring-ai.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- Spring AI Alibaba Agent Framework -->
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-agent-framework</artifactId>
                <version>1.1.0.0</version>
            </dependency>
            <!-- DashScope ChatModel 支持 -->
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
                <version>1.1.0.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.ai</groupId>
                    <artifactId>spring-ai-bom</artifactId>
                    <version>${spring-ai.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
  2. 配置application.yml
    接下来,配置 application.yml 文件,填入从阿里百炼平台获取的 API Key。

    spring:
      application:
        name: spring ai
      ai:
        dash-scope:
          api-key: 你的api key

三、第一个AI对话接口

让我们从一个最简单的对话接口开始。

首先,创建控制器 ChatController:

package com.example.springai.controller;

import com.example.springai.service.ChatService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatService chatService ;

    public ChatController(ChatService chatService){
        this.chatService = chatService;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "你好") String message) {
        return chatService.chat(message);
    }
}

然后,创建服务层 ChatService,通过 ChatClient 调用大模型:

package com.example.springai.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class ChatService {

    private static final String SYSTEM_PROMPT = """
            你是一个友好、专业的 AI 助手。
            用简洁、清晰、准确的语言回答用户问题。
            """;

    private final ChatClient chatClient;

    public ChatService(ChatClient.Builder chatClient){
        this.chatClient = chatClient
                .defaultSystem(SYSTEM_PROMPT)
                .build();
    }
    public String chat(String message){
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

启动应用后,访问 GET http://localhost:8080/chat?message=什么是spring AI 进行测试。

调用基础对话接口测试结果

四、实现会话记忆

为了让AI能记住上下文,我们需要引入会话记忆功能。首先添加依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
</dependency>

修改 ChatService,注入 ChatMemory 并通过 MessageChatMemoryAdvisor 启用记忆功能。注意,这里通过 sessionId 来区分不同的对话会话。

package com.example.springai.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.stereotype.Service;

@Service
public class ChatService {

    private static final String SYSTEM_PROMPT = """
            你是一个友好、专业的 AI 助手。
            用简洁、清晰、准确的语言回答用户问题。
            """;

    private final ChatClient chatClient;

    public ChatService(ChatClient.Builder chatClient, ChatMemory chatMemory){
        this.chatClient = chatClient
                .defaultSystem(SYSTEM_PROMPT)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }
    public String chat(String message,String sessionId){
        return chatClient.prompt()
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,sessionId))
                .call()
                .content();
    }
}

控制器也需要修改,增加 sessionId 参数:

@GetMapping("/chat")
public String chat(@RequestParam String message, @RequestParam String sessionId) {
    return chatService.chat(message,sessionId);
}

现在,使用相同的 sessionId 进行连续对话,AI就能记住之前的信息了。

带会话记忆的连续对话测试(自我介绍)
带会话记忆的连续对话测试(询问姓名)

五、结构化输出

有时我们需要让AI返回结构化的数据,比如JSON对象。Spring AI可以轻松实现这一点。

首先,定义一个记录(Record)来表示期望的结构:

// 使用 @JsonPropertyOrder 指定生成JSON Schema中属性的顺序(可选)
@JsonPropertyOrder({ "title", "year", "genre", "rating" })
public record MovieRecord(
        String title,      // 电影标题
        Integer year,      // 上映年份
        String genre,      // 类型(如 "Sci-Fi", "Comedy")
        Double rating      // 评分(如 8.5)
){}

然后,在服务中创建一个方法,通过调用 .entity(MovieRecord.class) 直接获取结构化的对象:

public MovieRecord extractMovieWithChatClient(String userInput){
    return chatClient.prompt()
                .user(userInput)
                .call()
                .entity(MovieRecord.class); // 直接指定目标类型,自动处理Converter
    }

添加对应的控制器接口:

@GetMapping("/extract-movie")
public MovieRecord chat(@RequestParam String message) {
    return chatService.extractMovieWithChatClient(message);
}

测试一下,输入一段包含电影信息的文本,看看AI是否能准确提取并返回结构化的JSON。

prompt《盗梦空间》是一部于2010年上映的科幻惊悚片,由克里斯托弗·诺兰执导,莱昂纳多·迪卡普里奥主演。该片获得了广泛好评,IMDb评分高达8.8分。

结构化输出提取电影信息测试结果

六、工具调用(Function Calling)

让AI不仅能说,还能“做”事,这就是工具调用的魅力。我们来定义两个简单的工具:获取当前时间和设置闹钟。

首先,创建一个工具类 AssistantTools

package com.example.springai.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class AssistantTools {

    // 模拟一个简单的内存存储,用于记录设置的闹钟
    private final Map<String, String> alarms = new ConcurrentHashMap<>();

    /**
     * 工具1: 获取当前日期和时间 (信息检索)
     */
    @Tool(description = "获取当前用户时区的日期和时间")
    public String getCurrentDateTime(){
        return LocalDateTime.now().atZone(ZoneId.systemDefault()).toString();
    }

    /**
     * 工具2: 设置闹钟 (执行操作)
     */
    @Tool(description = "为用户设置一个闹钟。时间必须以ISO-8601格式提供,例如 '2024-01-01T12:00:00'")
    public void setAlarm(
            @ToolParam(description = "设置闹钟的时间,格式为ISO-8601,例如 '2024-01-01T12:00:00'") String time
    ) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println(">>> 闹钟已设置: " + alarmTime);
        // 在实际应用中,这里可以调用你的定时任务服务或数据库操作
        alarms.put("alarm", alarmTime.toString());
    }
}

接着,创建一个新的 ToolService,在构建 ChatClient 时通过 .defaultTools() 注册我们的工具:

package com.example.springai.service;

import com.example.springai.tools.AssistantTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class ToolService {
    private static final String SYSTEM_PROMPT = """
            你是一个友好、专业的 AI 助手。
            用简洁、清晰、准确的语言回答用户问题,。
            如果遇到需要调用工具解决的,你也会利用工具解决问题。
            """;
    private final ChatClient chatClient;

    public ToolService(ChatClient.Builder chatClient, AssistantTools assistantTools){
        this.chatClient = chatClient
                .defaultSystem(SYSTEM_PROMPT)
                .defaultTools(assistantTools)
                .build();
    }
    public String chat(String message){
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

添加控制器接口:

@GetMapping("/tool")
public String tool(@RequestParam String message) {
    return toolService.chat(message);
}

现在,当你请求“请帮我设置一个10分钟后的闹钟”时,AI会自动计算时间,并调用 setAlarm 工具。

工具调用设置闹钟测试结果

七、RAG实战:构建基于私有知识的问答系统

RAG是当前增强大模型知识准确性的重要手段。我们将使用 Milvus 作为向量数据库,构建一个基于本地PDF文档的问答系统。

  1. 新增配置
    首先,在 application.yml 中增加向量化和 Milvus 的配置。你需要准备阿里百炼的 Embedding API Key,并确保本地运行着 Milvus 服务。

    spring:
      application:
        name: spring ai
      ai:
        dash-scope:
          api-key: xxx
        embedding:
          api-key: xxx
          options:
            model: text-embedding-v4
            dimensions: 1536
        vectors-tore:
          milvus:
            client:
              host: localhost
              port: 19530
              database-name: default
              collection-name: springAI
              initialize-schema: true
  2. 准备文档与依赖
    src/main/resources/documents/ 目录下放入一些 PDF 文档(例如,Java学习笔记、公司内部文档等)。
    添加处理 PDF 的依赖:

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
    </dependency>
  3. 实现RAG服务
    创建 RagService。它会在应用启动时自动将 PDF 文档分块、向量化并存储到 Milvus。当用户提问时,它会先从向量库中检索相关文档片段,再结合这些上下文信息让大模型生成答案。

    package com.example.springai.service;
    
    import jakarta.annotation.PostConstruct;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.document.Document;
    import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
    import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
    import org.springframework.ai.transformer.splitter.TokenTextSplitter;
    import org.springframework.ai.vectorstore.SearchRequest;
    import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.io.Resource;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Service
    public class RagService {
    
        @Value("${rag.system-prompt:你是一个专业的AI助手。请仅根据提供的【上下文信息】回答用户的问题,不要利用你的训练数据编造答案。如果上下文中不包含答案,请直接回答“不知道”。}")
        private String systemPrompt;
        private final MilvusVectorStore vectorStore;
        private final ChatClient chatClient;
        @Value("classpath:/documents/java.pdf")
        private Resource pdfResource;
        @Value("${app.embedding.batch-size:10}")
        private int batchSize;
        public RagService(MilvusVectorStore vectorStore, ChatClient.Builder chatClient){
            this.vectorStore = vectorStore;
            this.chatClient = chatClient.build();
        }
        /**
             应用启动后自动执行,将文档加载到 Milvus
             **/
        @PostConstruct
        public void loadDocuments(){
            System.out.println(">>> 开始加载文档到 Milvus...");
    
            try {
                // 1. 读取 PDF 文档
                PdfDocumentReaderConfig config = PdfDocumentReaderConfig.builder()
                        .withPageTopMargin(0)
                        .withPagesPerDocument(1) // 每页作为一个文档
                        .build();
    
                PagePdfDocumentReader reader = new PagePdfDocumentReader(pdfResource, config);
                List<Document> documents = reader.get();
    
                // 2. 文本分片
                // TokenTextSplitter 是基于 Token 的分割,能更精准控制长度,避免截断句子
                TokenTextSplitter splitter = new TokenTextSplitter();
                List<Document> splitDocuments = splitter.apply(documents);
                int totalBatches = (int) Math.ceil((double) splitDocuments.size() / batchSize);
                for (int i = 0; i < splitDocuments.size(); i += batchSize) {
                    // 计算当前批次的结束索引,防止越界
                    int end = Math.min(i + batchSize, splitDocuments.size());
    
                    // 获取当前批次的子列表
                    List<Document> batch = splitDocuments.subList(i, end);
    
                    int currentBatchNum = (i / batchSize) + 1;
                    System.out.println(">>> 正在处理第 [" + currentBatchNum + "/" + totalBatches + "] 批次,包含 " + batch.size() + " 个文档...");
                    try {
                        // 3. 写入 Milvus
                        // vectorStore.add 会自动调用 EmbeddingModel 进行向量化并存入数据库
                        vectorStore.add(batch);
                    } catch (Exception e) {
                        // 捕获单批次异常,避免整个流程中断,同时打印详细错误
                        System.err.println(">>> 第 [" + currentBatchNum + "] 批次写入失败: " + e.getMessage());
                        // 可选:如果希望任何错误都停止,这里可以使用 throw e;
                    }
                    // (可选) 如果遇到 429 Too Many Requests 错误,可以在这里加短暂延时
                    // try { Thread.sleep(500); } catch (InterruptedException ignored) {}
                }
                System.out.println(">>> 文档加载完成!");
            } catch (Exception e) {
                System.err.println(">>> 文档加载失败: " + e.getMessage());
                // 生产环境建议抛出异常或记录日志
            }
        }
        /**
             * 执行 RAG 查询
             */
        public String query(String userQuery){
            // 1. 检索相关文档
            // SearchRequest 构建检索请求
            SearchRequest searchRequest = SearchRequest.builder()
                    .query(userQuery)
                    .topK(4) // 返回最相似的前 4 个片段
                    .similarityThreshold(0.7d) // 相似度阈值,低于此值的过滤掉
                    .build();
            List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);
    
            if (relevantDocs.isEmpty()) {
                return "对不起,我没有找到相关的信息。";
            }
    
            // 2. 构建上下文
            String context = relevantDocs.stream()
                    .map(Document::getText)
                    .collect(Collectors.joining("\n\n------------------\n\n"));
    
            // 3. 调用 LLM 生成回答
            // 使用 ChatClient 流式 API,体验更好
            return chatClient.prompt()
                    .system(systemPrompt)
                    .user(u -> u.text("""
                            上下文信息:
                            {context}
    
                            用户问题:
                            {question}
                            """)
                            .param("context", context)
                            .param("question", userQuery))
                    .call()
                    .content();
        }
    }
  4. 添加查询接口
    最后,提供一个简单的 REST 接口来触发 RAG 查询:

    @GetMapping("/rag")
    public String rag(@RequestParam String message) {
        return ragService.query(message);
    }

现在,你可以向 /rag 接口提问,AI 会基于你提供的 PDF 文档内容进行回答。这种技术在构建企业知识库、智能客服等场景中非常有用。

基于文档的RAG问答测试结果

总结与拓展

通过以上步骤,我们已经使用 Spring Boot 3 和 Spring AI Alibaba 实现了一个包含多种核心 人工智能 功能的应用程序。Spring AI 的抽象层极大简化了集成流程,而其模块化设计让你可以灵活选择所需功能。

本文涵盖的只是 Spring AI 能力的冰山一角。它还包括更复杂的智能体(Agent)编排、多种模型供应商支持、以及与其他 Spring 生态项目的深度集成。如果你想深入探索,可以参考以下官方资源:

希望这篇实战指南能帮助你快速上手,在 Java 和 Spring 生态中开启你的 AI 应用开发之旅。欢迎在云栈社区交流更多开发心得。




上一篇:淘特导购团队Java项目AI编程实践:从代码补全、Agent到SDD的演进与思考
下一篇:Python脚本+Obsidian本地存储:在AI时代实现个人笔记定制化查询
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-25 20:36 , Processed in 0.310872 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表