Spring AI Alibaba 10分钟快速入门

Spring AI Alibaba 快速入门指南

本文案例源码笔记见:https://gitee.com/zhengqingya/java-workspace

1. 环境准备

1.1 API Key 配置

访问 阿里云百炼平台 获取API Key,并在Windows环境变量中配置:

复制代码
AI_DASHSCOPE_API_KEY=your_api_key_here

配置完成后重启电脑使环境变量生效,可通过命令行验证:

shell 复制代码
echo %AI_DASHSCOPE_API_KEY%

1.2 Ollama本地模型安装(可选)

Ollama是一个用于在本地运行大型语言模型(LLM)的工具和平台。

Windows安装步骤:
  1. 下载安装包:https://github.com/ollama/ollama/releases/download/v0.12.3/OllamaSetup.exe

  2. 配置环境变量(可选):

    复制代码
    OLLAMA_MODELS=D:\zhengqingya\soft\soft-dev\AI\LLM\Ollama\models
  3. 自定义安装路径:

    shell 复制代码
    OllamaSetup.exe /DIR="D:\zhengqingya\soft\soft-dev\AI\LLM\Ollama"
  4. 验证安装:

    shell 复制代码
    ollama --version
常用Ollama命令:
shell 复制代码
# 启动 Ollama 服务(默认端口:11434)
ollama serve

# 列出已下载的模型
ollama list

# 下载指定模型
ollama pull <model_name>

# 运行指定模型
ollama run <model_name>

# 停止运行中的模型
ollama stop <model_name>

# 删除已下载的模型
ollama rm <model_name>

# 查看当前运行的模型实例
ollama ps

2. 项目依赖配置

2.1 Maven依赖

xml 复制代码
<properties>
    <!-- Spring Boot -->
    <spring-boot.version>3.4.0</spring-boot.version>
    
    <!-- Spring AI -->
    <spring-ai.version>1.0.0</spring-ai.version>

    <!-- Spring AI Alibaba -->
    <spring-ai-alibaba.version>1.0.0.3</spring-ai-alibaba.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <!-- 用于为所有组件做统一版本管理 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-bom</artifactId>
            <version>${spring-ai-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- 核心依赖 -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>

<!-- Ollama本地模型支持(可选) -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
    <version>1.0.0</version>
</dependency>

2.2 基础配置

application.yml 配置
yaml 复制代码
server:
  port: 888
  servlet:
    encoding:
      enabled: true
      charset: utf-8
      force: true

spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
    
    # Ollama配置(可选)
    ollama:
      base-url: http://localhost:11434
      chat:
        model: deepseek-r1 # qwen3:8b | deepseek-r1

3. 基础使用

3.1 简单对话示例

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RequiredArgsConstructor
@RestController
public class HelloWorldController {

    private final ChatModel chatModel;

    /**
     * 简单调用
     * http://localhost:888/simple/chat?msg=你是谁?
     */
    @GetMapping("/simple/chat")
    public String simpleChat(@RequestParam String msg) {
        return chatModel.call(msg);
    }

    /**
     * 流式调用
     * http://localhost:888/stream/chat?msg=你是谁?
     */
    @GetMapping("/stream/chat")
    public Flux<String> streamChat(@RequestParam String msg) {
        return chatModel.stream(msg);
    }
}

3.2 ChatClient使用

ChatClient是更高层次的抽象,提供了简化的聊天接口和流式处理能力。

java 复制代码
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class ChatClientController {

    private final ChatClient chatClient;

    public ChatClientController(DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel).build();
    }

    /**
     * 简单调用
     * http://localhost:888/chat-client/simple/chat?msg=你是谁?
     */
    @GetMapping("/chat-client/simple/chat")
    public String simpleChat(@RequestParam String msg) {
        return chatClient.prompt().user(msg).call().content();
    }

    /**
     * 流式调用
     * http://localhost:888/chat-client/stream/chat?msg=你是谁?
     */
    @GetMapping("/chat-client/stream/chat")
    public Flux<String> streamChat(@RequestParam String msg) {
        return chatClient.prompt().user(msg).stream().content();
    }
}

3.3 多模型动态配置

java 复制代码
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.Map;

@RestController
@RequestMapping("/mutli-platform-model")
@Tag(name = "多模型动态配置")
public class MutliPlatformModelController {

    private Map<String, ChatModel> STRATEGY = Maps.newHashMap();

    public MutliPlatformModelController(DashScopeChatModel dashScopeChatModel,
                                        OllamaChatModel ollamaChatModel) {
        STRATEGY.put("dashscope", dashScopeChatModel);
        STRATEGY.put("ollama", ollamaChatModel);
    }

    /**
     * http://localhost:888/mutli-platform-model/stream/chat?platform=dashscope&model=qwen3-max&temperature=0.8&msg=你是谁?
     * http://localhost:888/mutli-platform-model/stream/chat?platform=ollama&model=deepseek-r1&temperature=0.8&msg=你是谁?
     */
    @Operation(summary = "动态切换模型")
    @GetMapping("/stream/chat")
    public Flux<String> streamChat(@ModelAttribute MutliPlatformModelOptions options) {
        ChatModel chatModel = STRATEGY.get(options.platform());
        ChatClient chatClient = ChatClient.builder(chatModel)
                // 模型配置参数
                .defaultOptions(
                        ChatOptions.builder()
                                .model(options.model())
                                .temperature(options.temperature())
                                .build()
                ).build();
        return chatClient.prompt().user(options.msg()).stream().content();
    }
}

4. 提示词(Prompt)使用

4.1 四大角色

java 复制代码
public enum MessageType {
    USER("user"),      // 用户发起的消息或请求
    ASSISTANT("assistant"), // AI助手生成的回复或响应
    SYSTEM("system"),  // 系统级别的指令或信息
    TOOL("tool");      // 工具调用产生的消息
}

4.2 提示词模板

java 复制代码
@RestController
@RequestMapping("/prompt")
@Tag(name = "提示词")
public class PromptController {

    private ChatClient chatClient;
    private ChatModel chatModel;

    @Value("classpath:/prompt-template/tell-joke.txt")
    private org.springframework.core.io.Resource promptTemplateRes;

    public PromptController(DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel).build();
        chatModel = dashScopeChatModel;
    }

    private static final String DEFAULT_PROMPT = """
            你是一位资深Java架构师,专注于企业级Java后端开发。
            请严格按照以下规则回答:
            1. 只回答Java及相关技术栈的问题;
            2. 提供准确、专业的技术解答;
            3. 对于非Java后端相关问题,请礼貌说明超出了专业范围。
            """;

    /**
     * http://localhost:888/prompt/chat-client?msg=今天吃什么?
     */
    @GetMapping("/chat-client")
    public String chatClient(@RequestParam String msg) {
        return chatClient.prompt()
                .system(DEFAULT_PROMPT)
                .user(msg)
                .call().content();
    }

    /**
     * 提示词模板使用
     * http://localhost:888/prompt/prompt-template?topic=无聊
     */
    @GetMapping("/prompt-template")
    public Flux<String> promptTemplate(@RequestParam String topic) {
        // 创建提示词模板
        PromptTemplate promptTemplate = new PromptTemplate("给我讲一个有关于{topic}的笑话");
        // 创建 Prompt 实例
        Prompt prompt = promptTemplate.create(Map.of("topic", topic));
        // 流式返回结果
        return chatModel.stream(prompt).map(chatResponse -> chatResponse.getResult().getOutput().getText());
    }
}

5. Advisor对话拦截

Advisor主要用于拦截ChatClient的对话请求和响应,在对话过程中添加通用处理逻辑。

5.1 日志记录

java 复制代码
@RestController
@RequestMapping("/advisor")
@Tag(name = "Advisor对话拦截--日志记录")
public class AdvisorLogController {

    private ChatClient chatClient;

    public AdvisorLogController(DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }

    /**
     * http://localhost:888/advisor/log?msg=你好
     */
    @GetMapping("/log")
    public Flux<String> log(@RequestParam String msg) {
        return chatClient.prompt()
                .user(msg)
                .stream().content();
    }
}

5.2 敏感词拦截

java 复制代码
@RestController
@RequestMapping("/advisor")
@Tag(name = "Advisor对话拦截--敏感词拦截")
public class AdvisorSafeGuardController {

    private ChatClient chatClient;

    public AdvisorSafeGuardController(DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(
                        new SimpleLoggerAdvisor(),
                        new SafeGuardAdvisor(
                                Lists.newArrayList("敏感词", "WC"),
                                "由于包含敏感内容,我无法对此进行回复。我们可以重新表述或讨论其他话题吗?",
                                1)
                )
                .build();
    }

    /**
     * http://localhost:888/advisor/safe-guard?msg=你好
     * http://localhost:888/advisor/safe-guard?msg=WC
     */
    @GetMapping("/safe-guard")
    public Flux<String> safeGuard(@RequestParam String msg) {
        return chatClient.prompt()
                .user(msg)
                .stream().content();
    }
}

5.3 自定义拦截器

java 复制代码
/**
 * <p> 自定义Advisor拦截器 -- 实现重写提示词 </p>
 */
public class RewritePromptAdvisor implements BaseAdvisor {

    private static final String REWRITE_TEXT = """
            让我们仔细分析这个问题:
            
            问题: {input_msg}
            
            分析步骤:
            1. 问题理解: 这个问题在问什么?
            2. 关键信息: 有哪些重要的细节需要注意?
            3. 回答构建: 基于以上分析,给出准确详细的回答:
            
            回答要求:
            - 使用通俗易懂的语言
            - 结合实际情况举例说明
            - 如涉及技术内容,提供具体的操作步骤
            - 返回html格式内容
            """;

    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        String contents = chatClientRequest.prompt().getContents();
        String inputMsg = PromptTemplate.builder().template(REWRITE_TEXT).build()
                .render(Map.of("input_msg", contents));
        return chatClientRequest.mutate()
                .prompt(Prompt.builder().content(inputMsg).build())
                .build();
    }

    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        return chatClientResponse;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

6. 对话记忆(Chat Memory)

用于维护对话上下文状态。

6.1 MySQL存储示例

依赖配置
xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId>
    <version>1.0.0.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
</dependency>
配置文件
yaml 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-ai-alibaba-demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: root
代码示例
java 复制代码
@RestController
@RequestMapping("/chat-memory")
@Tag(name = "对话记忆")
public class ChatMemoryController {

    private final ChatClient chatClient;

    // 注入 JdbcTemplate, ChatClient
    public ChatMemoryController(JdbcTemplate jdbcTemplate, DashScopeChatModel dashScopeChatModel) {
        // 构造 ChatMemoryRepository 和 ChatMemory
        ChatMemoryRepository chatMemoryRepository = MysqlChatMemoryRepository.mysqlBuilder()
                .jdbcTemplate(jdbcTemplate)
                .build();
        ChatMemory chatMemory = MessageWindowChatMemory.builder()
                .chatMemoryRepository(chatMemoryRepository)
                .maxMessages(10) // 消息存储条数
                .build();

        this.chatClient = ChatClient.builder(dashScopeChatModel)
                // 增加聊天记忆能力
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                // 实现 Logger 的 Advisor
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }

    /**
     * 使用自定义的 Advisor 增加聊天记忆能力
     * eg:
     * http://127.0.0.1:888/chat-memory/chat/123?msg=你好,我叫郑清,之后的会话中都带上我的名字
     * http://127.0.0.1:888/chat-memory/chat/123?msg=我叫什么名字?
     * http://127.0.0.1:888/chat-memory/chat/111?msg=我叫什么名字?
     */
    @GetMapping("/chat/{id}")
    public Flux<String> advisorChat(HttpServletResponse response, @PathVariable String id, @RequestParam String msg) {
        response.setCharacterEncoding("UTF-8");
        return this.chatClient.prompt(msg)
                .advisors(
                        a -> a
                                .param(ChatMemory.CONVERSATION_ID, id) // 多用户记忆隔离
                ).stream().content();
    }
}

7. 结构化输出

将模型的输出转换为特定的数据结构。

java 复制代码
// 定义数据结构
public record StructuredVO(String category, String question, String answer) {
}

@RestController
@RequestMapping("/structured-output")
@Tag(name = "结构化输出")
public class StructuredOutputController {

    private ChatClient chatClient;

    public StructuredOutputController(DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel).build();
    }

    /**
     * http://localhost:888/structured-output/test
     * {"category":"售后服务","question":"这件商品的质量不好,我可以申请退货吗?","answer":"如果商品存在质量问题,通常可以申请退货。请查看商家的退换货政策或联系客服确认具体流程。"}
     */
    @GetMapping("/test")
    public StructuredVO structuredOutput() {
        return chatClient.prompt()
                .system("""
                        对问题分类并简要总结,并给出答案
                        """)
                .user("这件商品的质量不好,我可以申请退货吗?")
                .call().entity(StructuredVO.class);
    }
}

8. 工具调用(Tool Calling)

允许AI模型调用外部工具和函数来执行特定任务。

8.1 定义工具

方法工具
java 复制代码
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

@Service
public class TimeTools {

    @Tool(description = "获取当前时间,默认时间格式:YYYY-MM-DD HH:mm:ss")
    public String getCurrentTime(@ToolParam(description = "时间格式") String format) {
        log.info("获取当前时间格式:{}", format);
        return DateUtil.now();
    }
}
函数工具
java 复制代码
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Service;

import java.util.function.Function;

@Slf4j
@Service(value = "getWeather")
public class WeatherFunction implements Function<String, String> {

    @Override
    @Description("获取天气")
    public String apply(@JsonPropertyDescription("城市") String city) {
        log.info("获取{}天气", city);
        return city + "天气:晴";
    }
}

8.2 注册工具 & 调用

java 复制代码
@RestController
@RequestMapping("/tool-calling")
@Tag(name = "工具调用")
public class ToolCallingController {

    private ChatClient chatClient;
    @Autowired
    private TimeTools timeTools;

    public ToolCallingController(DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel).build();
    }

    /**
     * 法1:方法工具
     * http://localhost:888/tool-calling/time?msg=现在时间?
     */
    @GetMapping("/time")
    public Flux<String> time(@RequestParam String msg) {
        return chatClient.prompt().user(msg)
                .tools(timeTools)
                .stream().content();
    }

    /**
     * 法2:函数工具
     * http://localhost:888/tool-calling/weather?msg=成都天气?
     */
    @GetMapping("/weather")
    public Flux<String> weather(@RequestParam String msg) {
        return chatClient.prompt().user(msg)
                .toolNames(new String[]{"getWeather"})
                .stream().content();
    }
}

8.3 动态注册工具

java 复制代码
@RestController
@RequestMapping("/tool-calling")
@Tag(name = "工具调用-动态注册")
public class ToolCallingDynamicController {

    private ChatClient chatClient;
    @Autowired
    private TimeTools timeTools;

    public ToolCallingDynamicController(DashScopeChatModel dashScopeChatModel,
                                        TimeTools timeTools) {
        chatClient = ChatClient.builder(dashScopeChatModel).build();
    }

    /**
     * 法一:方法工具
     * http://localhost:888/tool-calling/dynamic/1?msg=现在时间?
     * http://localhost:888/tool-calling/dynamic/2?msg=现在时间?
     */
    @GetMapping("/dynamic/{userId}")
    public Flux<String> dynamic(@PathVariable Long userId, @RequestParam String msg) {
        return chatClient.prompt().user(msg)
                // 动态注册工具
                .toolCallbacks(getToolCallbacks(userId))
                .stream().content();
    }

    private List<ToolCallback> getToolCallbacks(Long userId) {
        List<ToolCallback> toolCallbacks = Lists.newArrayList();
        if (userId != 1) {
            return toolCallbacks;
        }
        Method method = ReflectionUtils.findMethod(TimeTools.class, "getCurrentTime", String.class);
        if (method == null) {
            throw new RuntimeException("Method not found");
        }
        String inputSchema = JsonSchemaGenerator.generateForMethodInput(method);
        ToolCallback timeToolCallback = MethodToolCallback.builder()
                .toolDefinition(ToolDefinition.builder()
                        .name("getCurrentTime")
                        .description("获取当前时间,默认时间格式:YYYY-MM-DD HH:mm:ss")
                        .inputSchema(inputSchema)
                        .build())
                .toolMethod(method)
                .toolObject(timeTools)
                .build();
        toolCallbacks.add(timeToolCallback);
        return toolCallbacks;
    }

    /**
     * 法二:函数工具
     * http://localhost:888/tool-calling/dynamic2/1?msg=成都天气?
     * http://localhost:888/tool-calling/dynamic2/2?msg=成都天气?
     */
    @GetMapping("/dynamic2/{userId}")
    public Flux<String> dynamic2(@PathVariable Long userId, @RequestParam String msg) {
        String[] toolNames = userId == 1 ? new String[]{"getWeather"} : new String[0];
        return chatClient.prompt().user(msg)
                // 动态注册工具
                .toolNames(toolNames)
                .stream().content();
    }
}

9. MCP(Model Context Protocol)

MCP是一种模型上下文协议,主要用于AI模型调用外部工具和函数。

9.1 传输方式

  1. STDIO(标准输入输出):通过标准输入输出流进行数据传输
  2. SSE(Server-Sent Events):基于HTTP的单向服务器推送技术
  3. Streamable HTTP:基于HTTP协议的流式传输

9.2 自定义MCP服务 - STDIO

服务端实现
java 复制代码
// pom.xml依赖
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>

// application.yml配置
spring:
  main:
    web-application-type: none # 设置为非 Web 应用类型
    banner-mode: off
  ai:
    mcp:
      server:
        stdio: true
        name: my-weather-server
        version: 1.0.0

// 实现MCP工具
@Service
public class MyWeatherService {
    @Tool(description = "获取天气")
    public String getWeather(@ToolParam(description = "城市") String city) {
        return city + "天气:雨";
    }
}

// 注册MCP工具
@Bean
public ToolCallbackProvider weatherTools(MyWeatherService myWeatherService) {
    return MethodToolCallbackProvider.builder()
            .toolObjects(myWeatherService)
            .build();
}
客户端接入
yaml 复制代码
spring:
  ai:
    mcp:
      client:
        stdio:
          servers-configuration: classpath:/mcp/mcp-servers-config.json
json 复制代码
{
  "mcpServers": {
    "my-weather-server": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-Dspring.main.web-application-type=none",
        "-Dlogging.pattern.console=",
        "-jar",
        "D:\\path\\to\\your\\mcp-server.jar"
      ]
    }
  }
}

9.3 自定义MCP服务 - SSE

服务端实现
java 复制代码
// application.yml配置
server:
  port: 10088

spring:
  main:
    banner-mode: off
  ai:
    mcp:
      server:
        name: my-sse-server
        version: 1.0.0
        type: ASYNC
        sse-endpoint: /sse
        sse-message-endpoint: /mcp
        capabilities:
          tool: true
          resource: true
          prompt: true
          completion: true
客户端接入
yaml 复制代码
spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            my-sse-server:
              url: http://localhost:10088

10. RAG(Retrieval-Augmented Generation)

RAG是一种结合检索和生成的技术架构,通过引入"外接知识库"让模型在回答问题前先查阅权威资料。

10.1 嵌入模型 (Embedding Model)

yaml 复制代码
spring:
  ai:
    dashscope:
      embedding:
        options:
          model: text-embedding-v4
          dimensions: 64
java 复制代码
@RestController
@RequestMapping("/rag")
@Tag(name = "RAG")
public class RagController {
    private EmbeddingModel embeddingModel;

    public RagController(DashScopeEmbeddingModel dashScopeEmbeddingModel) {
        embeddingModel = dashScopeEmbeddingModel;
    }

    /**
     * http://localhost:888/rag/embedding?msg=你好
     */
    @GetMapping("/embedding")
    public Object embedding(@RequestParam String msg) {
        EmbeddingResponse embeddingResponse = this.embeddingModel.embedForResponse(List.of(msg));
        return Map.of("embedding", embeddingResponse);
    }
}

10.2 向量数据库

使用SimpleVectorStore轻量级向量数据库:

java 复制代码
@RestController
@RequestMapping("/rag")
@Tag(name = "RAG")
public class RagController {

    private VectorStore vectorStore;

    public RagController(DashScopeEmbeddingModel dashScopeEmbeddingModel) {
        vectorStore = SimpleVectorStore.builder(dashScopeEmbeddingModel).build();
    }

    /**
     * http://localhost:888/rag/vectorStore?topK=3&threshold=0.1&msg=有哪些开源的向量数据库?
     */
    @GetMapping("/vectorStore")
    public Object vectorStore(@RequestParam String msg, @RequestParam int topK, @RequestParam double threshold) {
        // 存储向量
        vectorStore.add(Lists.newArrayList(
                Document.builder().text("LangChain是一个用于开发由语言模型驱动的应用程序的框架。").build(),
                Document.builder().text("Milvus是一款开源向量数据库,专为大规模向量相似性搜索而设计。").build()
        ));

        // 相似性搜索
        return vectorStore.similaritySearch(SearchRequest.builder()
                .query(msg)
                .topK(topK)
                .similarityThreshold(threshold)
                .build());
    }
}

10.3 ELT (Extract, Load, Transform)

文本读取器
java 复制代码
@Value("classpath:rag/pet.txt")
private Resource textRes;

/**
 * 文本读取器
 * http://localhost:888/rag/elt/text
 */
@GetMapping("/text")
public Object text() {
    // 1. 创建文本读取器
    TextReader textReader = new TextReader(textRes);
    // 2. 读取文档内容
    List<Document> documents = textReader.read();
    // 3. 返回结果
    return documents;
}
Markdown读取器
xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>
java 复制代码
@Value("classpath:rag/iphone.md")
private Resource MdRes;

/**
 * markdown读取器
 * http://localhost:888/rag/elt/markdown
 */
@GetMapping("/markdown")
public Object markdown() {
    // 1. 创建Markdown文档读取器
    MarkdownDocumentReader markdownReader = new MarkdownDocumentReader(MdRes, MarkdownDocumentReaderConfig.builder()
            .withAdditionalMetadata("filename", MdRes.getFilename())
            .withHorizontalRuleCreateDocument(false)
            .withIncludeCodeBlock(false)
            .withIncludeBlockquote(false)
            .build());
    // 2. 读取Markdown文档内容
    List<Document> documents = markdownReader.read();
    // 3. 返回文档列表
    return documents;
}

10.4 文档分割器

TokenTextSplitter是RAG应用中关键的文档预处理工具。

java 复制代码
/**
 * 文本读取器
 * http://localhost:888/rag/elt/split/text
 */
@GetMapping("/text")
public Object text() {
    // 1. 创建文本读取器
    TextReader textReader = new TextReader(textRes);
    // 2. 读取文档内容
    List<Document> documents = textReader.read();
    // 3. 文档分割处理
    List<Document> splitDocs = new TokenTextSplitter(800, 350, 5, 10000, true).split(documents);
    // 4. 返回结果
    return splitDocs;
}

10.5 自定义文档分割器

java 复制代码
/**
 * 学校规章制度专用文档分割器 - 针对规章制度结构优化
 * 支持按"第.*条"分割,保留规章制度的结构完整性
 */
public class RegulationDocumentSplitter extends TextSplitter {
    // 实现细节...
    
    public static Builder builder() {
        return new Builder();
    }
    
    // ... 其他方法实现
}

10.6 文档转换器

关键字提取
java 复制代码
@GetMapping("/keyword")
public Object keyword() {
    // 1. 创建Markdown文档读取器
    MarkdownDocumentReader markdownReader = new MarkdownDocumentReader(MdRes, MarkdownDocumentReaderConfig.builder()
            .withAdditionalMetadata("filename", MdRes.getFilename())
            .build());
    // 2. 读取文档内容
    List<Document> documents = markdownReader.read();
    // 3. 关键字提取
    KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 3);
    List<Document> enricherDocs = enricher.apply(documents);
    return enricherDocs;
}
摘要生成
java 复制代码
@GetMapping("/summary")
public Object summary() {
    // 1. 创建Markdown文档读取器
    MarkdownDocumentReader markdownReader = new MarkdownDocumentReader(MdRes, MarkdownDocumentReaderConfig.builder()
            .withAdditionalMetadata("filename", MdRes.getFilename())
            .build());
    // 2. 读取文档内容
    List<Document> documents = markdownReader.read();
    // 3. 摘要生成
    SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(chatModel,
            List.of(SummaryMetadataEnricher.SummaryType.CURRENT, SummaryMetadataEnricher.SummaryType.NEXT),
            """
            请为以下文档内容生成摘要:
            {context_str}
            
            要求:
            1. 摘要长度不超过100字
            2. 突出关键信息
            3. 保持原意不变
            """,
            MetadataMode.ALL);
    List<Document> enricherDocs = enricher.apply(documents);
    return enricherDocs;
}
元数据过滤
java 复制代码
@GetMapping("/metadata-filter")
public Object metadataFilter() {
    // 1. 创建Markdown文档读取器
    MarkdownDocumentReader markdownReader = new MarkdownDocumentReader(MdRes, MarkdownDocumentReaderConfig.builder()
            .withAdditionalMetadata("filename", MdRes.getFilename())
            .build());
    // 2. 读取文档内容
    List<Document> documents = markdownReader.read();
    // 3. 关键字提取
    KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 3);
    List<Document> enricherDocs = enricher.apply(documents);
    // 4. 存储向量
    vectorStore.add(enricherDocs);
    // 5. 相似性搜索
    List<Document> result = vectorStore.similaritySearch(SearchRequest.builder()
            .query("手机处理器")
            .topK(3)
            // 基于元数据的过滤条件
            .filterExpression(new FilterExpressionBuilder().eq("filename", "phone.md").build())
            .build());
    // 6. 返回结果
    return result;
}

10.7 检索增强生成

java 复制代码
@RestController
@RequestMapping("/rag/retrieval-augmentation")
@Tag(name = "RAG-检索增强生成")
public class RagRetrievalAugmentationAdvisorController {

    @Value("classpath:rag/iphone.md")
    private Resource MdRes;

    private ChatModel chatModel;
    private ChatClient chatClient;
    private VectorStore vectorStore;

    public RagRetrievalAugmentationAdvisorController(DashScopeChatModel dashScopeChatModel,
                                                     DashScopeEmbeddingModel dashScopeEmbeddingModel) {
        chatModel = dashScopeChatModel;
        chatClient = ChatClient.builder(dashScopeChatModel).defaultAdvisors(new SimpleLoggerAdvisor()).build();
        vectorStore = SimpleVectorStore.builder(dashScopeEmbeddingModel).build();
    }

    /**
     * RetrievalAugmentationAdvisor 检索增强生成
     * http://localhost:888/rag/retrieval-augmentation/chat?msg=下雨天,我不想出门,你能不能帮我了解下手机店里的iphone18价格?
     */
    @GetMapping("/chat")
    public Flux<String> chat(@RequestParam String msg) {
        // 1. 创建Markdown文档读取器
        MarkdownDocumentReader markdownReader = new MarkdownDocumentReader(MdRes, MarkdownDocumentReaderConfig.builder()
                .withAdditionalMetadata("filename", MdRes.getFilename())
                .build());
        // 2. 读取文档内容
        List<Document> documents = markdownReader.read();
        vectorStore.add(documents);

        // 3. 检索增强生成
        Advisor advisor = RetrievalAugmentationAdvisor.builder()
                // documentRetriever:文档检索器
                .documentRetriever(VectorStoreDocumentRetriever.builder()
                        .vectorStore(vectorStore)
                        .similarityThreshold(0.5)
                        .topK(3)
                        .build())
                // queryAugmenter:查询增强器
                .queryAugmenter(ContextualQueryAugmenter.builder()
                        .allowEmptyContext(false)
                        .emptyContextPromptTemplate(PromptTemplate.builder().template("用户查询位于知识库之外。礼貌地告知用户您无法回答。").build())
                        .build())
                // QueryTransformer:查询转换器
                .queryTransformers(
                        // 查询重写
                        RewriteQueryTransformer.builder()
                                .chatClientBuilder(ChatClient.builder(chatModel))
                                .targetSearchSystem("知识库")
                                .build(),
                        // 翻译转换器
                        TranslationQueryTransformer.builder()
                                .chatClientBuilder(ChatClient.builder(chatModel))
                                .targetLanguage("中文")
                                .build()
                )
                .build();
        return chatClient.prompt().user(msg).advisors(advisor).stream().content();
    }
}

10.8 重排序

java 复制代码
@RestController
@RequestMapping("/rag/rerank")
@Tag(name = "重排序")
public class RerankController {

    private RerankModel rerankModel;
    private ChatClient chatClient;
    private VectorStore vectorStore;

    public RerankController(DashScopeChatModel dashScopeChatModel,
                            DashScopeEmbeddingModel dashScopeEmbeddingModel,
                            DashScopeRerankModel dashScopeRerankModel) {
        chatClient = ChatClient.builder(dashScopeChatModel).defaultAdvisors(new SimpleLoggerAdvisor()).build();
        vectorStore = SimpleVectorStore.builder(dashScopeEmbeddingModel).build();
        rerankModel = dashScopeRerankModel;
    }

    /**
     * http://localhost:888/rag/rerank/chat?msg=iphone20价格
     */
    @GetMapping("/chat")
    public Flux<String> chat(@RequestParam String msg) {
        vectorStore.add(Lists.newArrayList(
                Document.builder().text("iPhone18 基础价格: 5999元, 税率: 13%, 运费: 0元").build(),
                Document.builder().text("iPhone20 基础价格: 12999元, 税率: 13%, 运费: 50元").build(),
                Document.builder().text("iPhone10 基础价格: 1299元, 税率: 13%, 运费: 0元").build()
        ));

        // 重排序
        RetrievalRerankAdvisor rerankAdvisor = new RetrievalRerankAdvisor(vectorStore, rerankModel,
                SearchRequest.builder().topK(10).build());

        return chatClient.prompt().user(msg)
                .advisors(rerankAdvisor)
                .stream().content();
    }
}

10.9 幻觉评估

java 复制代码
@RestController
@RequestMapping("/rag/fact-check")
@Tag(name = "RAG-幻觉评估")
public class RagFactCheckController {

    private ChatModel chatModel;

    public RagFactCheckController(DashScopeChatModel dashScopeChatModel) {
        chatModel = dashScopeChatModel;
    }

    @GetMapping("/test")
    public Object test() {
        // 准备评估数据
        ArrayList<Document> documents = Lists.newArrayList(
                Document.builder().text("iPhone18 基础价格: 5999元, 税率: 13%, 运费: 0元").build(),
                Document.builder().text("iPhone20 基础价格: 12999元, 税率: 13%, 运费: 50元").build(),
                Document.builder().text("iPhone10 基础价格: 1299元, 税率: 13%, 运费: 0元").build()
        );

        // AI回答
        String aiRes = "iPhone20 6000元";

        // 执行评估
        FactCheckingEvaluator evaluator = new FactCheckingEvaluator(ChatClient.builder(chatModel));
        EvaluationRequest evaluationRequest = new EvaluationRequest(documents, aiRes);
        EvaluationResponse evaluationResponse = evaluator.evaluate(evaluationRequest);
        return evaluationResponse; // {"pass":false,"score":0.0,"feedback":"","metadata":{}}
    }
}

11. AI Agent

AI Agent是一种能够自主感知环境、做出决策并执行动作以实现特定目标的智能实体。

11.1 三种人机协同模式

模式名称 核心关系 (人类 : AI) 特点与工作方式 典型场景
嵌入模式 (Embedded) 我做你看 将AI能力作为功能模块嵌入现有产品 智能客服中的自动回复、推荐系统
副驾驶模式 (Copilot) 我们一起做 AI作为实时协作的伙伴,提供建议和辅助 GitHub Copilot代码补全
智能体模式 (Agent) 你做我看 AI拥有高度自主权,可独立规划、决策、执行复杂任务 自动交易系统、智能助手

11.2 AI Agent的五大工作模式

  1. 提示链模式:将复杂任务拆解成一系列顺序执行的子任务
  2. 路由模式:根据输入内容特点,动态选择最合适的处理路径
  3. 并行化模式:同时启动多个Agent处理独立的子任务
  4. 协调者-工作者模式:模拟指挥体系,由协调者负责任务分解和调度
  5. 评估者-优化者模式:引入自我反思和优化的闭环

12. 可观测性

软件的可观测性是指通过系统输出(如日志、指标、跟踪等)来推断其内部状态的能力。

12.1 Zipkin部署

参考:https://gitee.com/zhengqingya/docker-compose

12.2 Java项目配置

依赖配置
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-tool-calling-weather</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
    <version>1.5.0-M2</version>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
    <version>3.4.3</version>
</dependency>
配置文件
yaml 复制代码
spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      observations:
        log-completion: true
        log-prompt: true

    # Chat config items
    chat:
      client:
        observations:
          log-prompt: true
          log-completion: true
          include-error-logging: true

  # tools config items
  tools:
    observability:
      include-content: true

  http:
    client:
      read-timeout: 60s

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always
  tracing:
    sampling:
      probability: 1.0
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans

13. Spring AI Alibaba Graph

Spring AI Alibaba Graph是一个强大且直观的框架,能让你像搭积木一样,通过编排节点和流程来构建复杂的AI应用。

13.1 快速入门

依赖配置
xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-graph-core</artifactId>
</dependency>
创建自定义节点
java 复制代码
public class ExpanderNode implements NodeAction {
    private final ChatClient chatClient;

    public ExpanderNode(DashScopeChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel).build();
    }

    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        // 1. 从全局状态中获取输入
        String query = state.value("query", "");
        // 2. 构建提示词并调用大模型
        String promptTemplate = "请为以下问题生成3个不同角度的变体问题:{query}";
        String result = chatClient.prompt()
                .user(u -> u.text(promptTemplate).param("query", query))
                .call()
                .content();
        // 3. 处理结果
        List<String> queryVariants = Arrays.asList(result.split("\n"));
        // 4. 将结果放入Map
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("expander_content", queryVariants);
        return resultMap;
    }
}
配置状态图
java 复制代码
@Configuration
public class SimpleGraphConfiguration {

    @SneakyThrows
    @Bean
    public StateGraph simpleGraph(DashScopeChatModel chatModel) {
        // 全局变量的替换策略
        KeyStrategyFactory keyStrategyFactory = () -> {
            HashMap<String, KeyStrategy> strategies = new HashMap<>();
            strategies.put("query", new ReplaceStrategy());
            strategies.put("expander_content", new ReplaceStrategy());
            return strategies;
        };
        // 构建状态图
        StateGraph stateGraph = new StateGraph("问题扩展工作流", keyStrategyFactory)
                // 添加节点
                .addNode("expander", AsyncNodeAction.node_async(new ExpanderNode(chatModel)))
                // 添加边
                .addEdge(StateGraph.START, "expander")
                .addEdge("expander", StateGraph.END);

        return stateGraph;
    }
}
编译并运行工作流
java 复制代码
@RestController
@RequestMapping("/graph")
@Tag(name = "Graph")
public class SimpleGraphController {

    private final CompiledGraph compiledGraph;

    // 注入定义好的StateGraph,并编译成CompiledGraph
    public SimpleGraphController(@Qualifier("simpleGraph") StateGraph stateGraph) throws GraphStateException {
        this.compiledGraph = stateGraph.compile();
    }

    /**
     * http://localhost:888/graph/expand?query=什么是人工智能
     */
    @GetMapping("/expand")
    public Map<String, Object> expandQuery(@RequestParam String query) throws GraphRunnerException {
        // 设置初始状态
        Map<String, Object> initialState = Map.of("query", query);
        // 执行工作流
        Optional<OverAllState> result = compiledGraph.invoke(initialState);
        // 从最终状态中获取结果
        return result.map(OverAllState::data).orElse(Map.of());
    }
}

学习资源

技术选型

  1. Spring AI Alibaba
  2. LangChain4j

教学视频

已阅 来源
√(1天看完-推荐看) Spring AI 1.0全套教程(入门+实战+原理&源码;大模型+tools+mcp+Agent全流程落地)
√(1h看完-推荐看) LangChain4j实战教程
√(1天看完) Spring AI Alibaba入门到进阶实战
√(1h看完) Coze(扣子)智能体教程
√(1天看完,不建议看,个人觉得讲的太啰嗦) 尚硅谷Spring AI Alibaba教程,2025最新springai从基础到实战

文档资源

tips: 书写本项目案例代码的时候,个人建议根据Spring AISpring AI Alibaba 2个官方文档以及官方demo来,避免踩坑~


今日励志语句

每一次代码提交都是进步的足迹,每一行注释都是智慧的积累。编程路上没有捷径,只有不断的学习和实践。遇到bug不要气馁,它们是成长路上的垫脚石。保持好奇心,拥抱变化,让每一行代码都闪耀着创新的光芒。今天也要元气满满地coding哦!

相关推荐
学术头条3 小时前
用视觉压缩文本!清华、智谱推出Glyph框架:通过视觉-文本压缩扩展上下文窗口
人工智能·深度学习·计算机视觉
枫叶丹43 小时前
破局政务数字化核心难题:金仓数据库以国产化方案引领电子证照系统升级之路
数据库·政务·1024程序员节·金仓
你的电影很有趣3 小时前
lesson76:Vue.js 核心特性详解:事件处理、计算属性与侦听器
javascript·vue·1024程序员节
belldeep3 小时前
python:怎样用 Django 开发电子商务程序
django·电子商务·1024程序员节
Neil今天也要学习3 小时前
永磁同步电机无速度算法--基于相位超前校正的LESO
算法·1024程序员节
zl9798993 小时前
SpringBoot-Web开发之数据响应
java·spring boot·后端
消失的旧时光-19433 小时前
Kotlin × Gson:为什么遍历 JsonObject 要用 entrySet()
android·kotlin·数据处理·1024程序员节
360智汇云3 小时前
从0到1理解智能体模式
1024程序员节
旷野说3 小时前
Spring Boot 1.x、2.x 3.x区别汇总
java·spring·tomcat·1024程序员节