SpringAIAlibaba学习使用 ---核心API、RAG、Tool Calling

SpringAIAlibaba学习使用 ---核心API、RAG、Tool Calling

本文为学习记录文档,学习参考于三更草堂springAI视频

SpringAIAlibaba概述

它本质上是一个基于 Spring AI 抽象层的"增强插件集",其核心价值在于提供了统一、便捷的接口,让你 可以灵活地选择和切换底层模型。【用于调用大模型的api,将原始的调用方法进行封装,便于用户填参数调用 】 官方文档地址:java2ai.com/docs/overvi...

本次采用调用智谱AI为例

1、快速入门

创建父工程,便于后续开发、调用

版本说明:

  • jdk:17(最低要用17)
  • springAi:1.0.0
  • SpringBoot:3.4.0
  • Spring Ai Alibaba: 1.0.0.4
pom 复制代码
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xiaoyu</groupId>
    <artifactId>springai-examples</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <project.version>1.0-SNAPSHOT</project.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <spring-ai.version>1.0.0</spring-ai.version>
        <spring-ai-alibaba.version>1.0.0.4</spring-ai-alibaba.version>

        <!-- Spring Boot -->
        <spring-boot.version>3.4.0</spring-boot.version>

        <!-- maven plugin -->
        <maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
        <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.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>



    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>${maven-deploy-plugin.version}</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <release>17</release>
                    <compilerArgs>
                        <compilerArg>-parameters</compilerArg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>flatten-maven-plugin</artifactId>
                <version>${flatten-maven-plugin.version}</version>
                <inherited>true</inherited>
                <executions>
                    <execution>
                        <id>flatten</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>flatten</goal>
                        </goals>
                        <configuration>
                            <updatePomFile>true</updatePomFile>
                            <flattenMode>ossrh</flattenMode>
                            <pomElements>
                                <distributionManagement>remove</distributionManagement>
                                <dependencyManagement>remove</dependencyManagement>
                                <repositories>remove</repositories>
                                <scm>keep</scm>
                                <url>keep</url>
                                <organization>resolve</organization>
                            </pomElements>
                        </configuration>
                    </execution>
                    <execution>
                        <id>flatten.clean</id>
                        <phase>clean</phase>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

创建子工程

添加依赖:

pom 复制代码
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-zhipuai</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

由于我们要调用的是智谱AI,所以需要引入spring-ai-starter-model-zhipuai。如果用的是别的AI模型引入 对于的启动器即可

  • deepseek - spring-ai-starter-model-deepseek
  • 千问 - spring-ai-alibaba-starter-dashscope
  • OpenAI - spring-ai-starter-model-openai

配置文件

yaml 复制代码
server:
  port: 8080
  servlet:
    encoding:
      charset: utf-8
      enabled: true    # 启用流
      force: true

spring:
  ai:
    zhipuai:
      api-key: *******************  # 配置 API Key
      base-url: "https://open.bigmodel.cn/api/paas"   # 配置 模型地址
      chat:
        options:
          model: glm-4.5

  http:
    client:
      read-timeout: 1000000

启动类

java 复制代码
package com.xiaoyu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ZhipuAIApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZhipuAIApplication.class, args);
    }

}

代码

java 复制代码
@RestController
@RequestMapping("/zhipuai")
public class ZhipuChatController {
    private final ChatModel chatModel;
    // 通过构造器注入 ChatModel 
    public ZhipuChatController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    @GetMapping("/simple")
    public String simpleChat(@RequestParam(name = "query") String query) {
        // 调用ChatModel 的call方法传入问题完成模型调用
        return chatModel.call(query);
    }

2、 核心Api

2.1 Message

智谱api地址:docs.bigmodel.cn/cn/api/intr... 选自其中的一段调用实列:

bash 复制代码
curl -X POST "https://open.bigmodel.cn/api/paas/v4/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
    "model": "glm-5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ],
    "temperature": 1.0,
    "stream": true
}'

messages是一个对象数组,其中的每个对象都有role和content两个属性。 智谱文档中对Message的介绍:

SpringAI中的设计的Message的继承体系:

从这个继承关系中我们可以看到AbstractMessage相对于把我们接口中看到的role字段抽象成 messageType属性。content字段抽象成textContent属性。

MessageType定义的枚举源码如下:

java 复制代码
public enum MessageType {
  USER("user"),
  ASSISTANT("assistant"),
  SYSTEM("system"),
  TOOL("tool");
}

和目前role可以使用的4种值对应 因此简单实现为:

java 复制代码
    @GetMapping("/message")
    public String message(@RequestParam("query") String query) {

        SystemMessage systemMessage = new SystemMessage("你是一个AI小助手");
        UserMessage userMessage = new UserMessage(query);

        return chatModel.call(systemMessage, userMessage);
    }
}

2.2 Prompt

Prompt 提示词 是引导 AI 模型生成特定输出的输入格式,Prompt 的设计和措辞会显著影响模型的响 应。

在SpringAI中,是把模型参数和消息列表的组合抽象为Prompt。 所以Prompt中主要是有两个属性【messages和chatOptions】

执行chatModel.call方法时填入prompt参数,直接设置强求参数

ChatOptions

不同厂商的模型支持的模型参数不同,当然也有些参数上普遍都支持的。所以SpringAI在 设计的时候把共有的模型参数都设计到了ChatOptions中。
每个模型厂商可以创建自己的ChatOptions实现定义厂商支持的模型参数。
所以我们看下当前项目中ChatOptions的实现有哪些。就发现了ZhiPuAiChatOptions。

ZhiPuAiChatOptions对象的创建

java 复制代码
 ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
        zhiPuAiChatOptions.setModel("glm-4.5");
        zhiPuAiChatOptions.setTemperature(0.0);
        zhiPuAiChatOptions.setMaxTokens(15536);

// 流式赋值
 ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                .model("glm-4.5")
                .maxTokens(15536)  // 最大tokens数
                .temperature(0.0) // 0到1之间 值越高(如0.8)使输出更加随机,值越低(如0.2)使其更集中,更有确定性
                .build();
                

改造调用:

java 复制代码
    @GetMapping("/chatOptions")
    public ChatResponse chatOptions(@RequestParam("query") String query) {

        SystemMessage systemMessage = new SystemMessage("你是一个AI助手");
        UserMessage userMessage = new UserMessage(query);
        ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
        zhiPuAiChatOptions.setTemperature(0.0);
        zhiPuAiChatOptions.setMaxTokens(15536);

//        zhiPuAiChatOptions.builder().model("glm-4.5").temperature(0.0).maxTokens(15536);

        return chatModel.call(new Prompt(List.of(systemMessage, userMessage), zhiPuAiChatOptions));
    }

2.3 ChatModel

如图,ChatModel API 让应用开发者可以非常方便的与 AI 模型进行文本交互,它抽象了应用与 模型交互的过程,包括使用 Prompt作为输入,使用ChatResponse作为输出等。

代码编写

1、从容器注入

java 复制代码
@RestController
@RequestMapping("/zhipu")
public class ZhiPuChatController {

    private final ChatModel chatModel;

    public ZhiPuChatController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
}

2、模型调用

java 复制代码
    /* 同步响应 */
    @GetMapping("/simple")
    public String simple(@RequestParam("query") String query) {
        return chatModel.call(query);
    }

    /* 流式响应 */
    @GetMapping("/stream/chat")
    public Flux<String>  stream(@RequestParam("query") String query) {

        Flux<String> chatResponse = chatModel.stream(query);
        return chatResponse;
    }

3、乱码问题解决:

yaml 复制代码
server:
  servlet:
    encoding:
      charset: utf-8
      enabled: true    # 启用流
      force: true

2.4 ChatClient

通过chatmodel调用模型,方法步骤有些繁琐,springAI提供了ChatClient方便我们的调用
大致功能

  • 定制和组装模型的输入(Prompt)
  • 格式化解析模型的输出(Structured Output)
  • 调整模型交互参数(ChatOptions)
  • 聊天记忆(Chat Memory)
  • 工具/函数调用(Function Calling)
  • RAG
  • 等等..........

代码编写 1、创建ChatClient ①容器注入

java 复制代码
@RestController
@RequestMapping("chatclient")
public class ZhiPuChatClientController {
    private final ChatClient chatClient;
    public ZhiPuChatClientController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
  }

②创建ChatClient.Builder来构造ChatClient

java 复制代码
@RestController
@RequestMapping("/chatclient")
public class ZhipuChatClientController {
    private final ChatClient chatClient;
    public ZhipuChatClientController(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
                .build();
    }
}

2、调用模型

java 复制代码
    /* 调用流式赋值  */
    @GetMapping("/simple0")
    public String simple0(@RequestParam("query") String query) {
        ZhiPuAiChatOptions build = ZhiPuAiChatOptions.builder()
                .temperature(0.0)
                .maxTokens(15536)
                .build();

        return chatClient.prompt().system("你是一个AI助手").user(query)
                .options(build).call().content();
    }


    /* 普通赋值  */
    @GetMapping("/simple")
    public String simple(@RequestParam("query") String query) {

        SystemMessage systemMessage = new SystemMessage("你是一个AI助手");
        UserMessage userMessage = new UserMessage(query);
        ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
        zhiPuAiChatOptions.setTemperature(0.0);
        zhiPuAiChatOptions.setMaxTokens(15536);

        return chatClient
                .prompt(new Prompt(List.of(systemMessage,userMessage), zhiPuAiChatOptions))
                .call()
                .content();
    }

3、响应处理

java 复制代码
    @GetMapping("/simple3")
    public ChatResponse simple3(@RequestParam("query") String query) {
        UserMessage userMessage = new UserMessage(query);
        ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
        zhiPuAiChatOptions.setTemperature(0.0);
        zhiPuAiChatOptions.setMaxTokens(15536);
      
       ChatResponse chatResponse = chatClient.prompt()
                .system("你是一个AI助手")
                .user(query)
                .options(zhiPuAiChatOptions)
                .call()
                .chatResponse();
        return chatResponse;
    }

4、响应数据转化为实体 ①定义实体

java 复制代码
@Data
public class Book {
    private String name;
    private String author;

}

②使用chatClient调用完call方法后调用entity方法传入要转化类的字节码对象

java 复制代码
    @GetMapping("/entity")
    public Book entity() {
        Book book = chatClient.prompt()
                .system("你是一个AI助手")
                .user("请帮我生成一本书,要求书名和作者为中文")
                .call().entity(Book.class);
        return book;
    }

5、响应数据流式返回

java 复制代码
    @GetMapping("/stream")
    public Flux<String> stream() {
        Flux<String> flux = chatClient.prompt()
                .system("你是一个AI助手")
                .user("请帮我生成一本书,要求书名和作者为中文")
                .stream()
                .content();
        return flux;
    }

2.4 Advisors

Spring AI Advisors 是 Spring AI 框架中的核心拦截器组件,专门用于处理和增强 AI 应用程序中的请求 与响应流。其设计于过滤器拦截器非常类似。 优势:

  • 将常见的生成式 AI 模式(如对话记忆、敏感词过滤、RAG 检索)打包成可重用单元,简化开发流程
  • 创建可跨不同模型和用例工作的可重用转换组件,提升代码灵活性

1、Advisor增强流程:

2、继承体系:

  • 同步调用增强可以使用CallAdvisor
  • 流式调用(响应式调用)增强可以 使用StreamAdvisor

3、代码编写

  • 1、添加Advisor进行增强
  • 2、实现CallAdvisor接口重写其中的方法
  • 3、adviseCall方法中的chatClientRequest 是封装了AI请求的对象,我们可以在Advisor方法中对其进行增强
  • 4、adviseCall方法中的callAdvisorChain可以让我们对当前的AI请求进行放行,并且返回 ChatClientResponse。我们可以增强ChatClientResponse后再返回。

第一个advisor

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;

@Slf4j
public class SGCallAdvisor1 implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {

        log.info("advisor1请求");
        ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
        log.info("advisor1响应");
        return chatClientResponse;
    }

    @Override
    public String getName() {
        return "SGCallAdvisor1";
    }

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

第二个advisor

java 复制代码
package com.sangeng.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;

@Slf4j
public class SGCallAdvisor2 implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {

        log.info("advisor2请求");
        ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
        log.info("advisor2响应");
        return chatClientResponse;
    }

    @Override
    public String getName() {
        return "SGCallAdvisor2";
    }

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

ChatClient添加Advisor

java 复制代码
@RestController
@RequestMapping("/advisor")
public class AdvisorController {
    private final ChatClient chatClient;
    public AdvisorController(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
                .build();
    }
    @GetMapping("/simple")
    public String simpleChat(@RequestParam(name = "query") String query) {
        ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                .maxTokens(15536)
                .temperature(0.0)
                .model("glm-4.5")
                .build();
        return chatClient.prompt()
                .system("你是一个有用的AI助手。")
                .user(query)
                .advisors(new SGCallAdvisor1(),new SGCallAdvisor2())
                .options(chatOptions)
                .call().content();
    }
    
}

输出:

调整顺序 Advisor链中的顺序只需要修改getOrder的返回值,getOrder的返回值越小 在Advisor链中的顺序越靠前,也就是越早处理请求。
案例,自定义Advisor实现理解语义的对话,即:能够在进行多轮对话时,模型能知道我们之前聊过什么内容。

定义Advisor

java 复制代码
package com.sangeng.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 演示增加上下文功能,实际中不使用该方法
 * */
@Slf4j
public class SimpleMessageChatMemoryAdvisor implements BaseAdvisor {


    private static Map<String, List<Message>> chatMemory = new HashMap<>();

    // 请求增强
    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {

        // 1、通过对话id查询之前的对话
        String conversationId = chatClientRequest.context().get("conversationId").toString(); // 通过上下文获取
        List<Message> messages = chatMemory.get(conversationId);
        if (messages == null) {
            messages = new ArrayList<>();
        }


        // 2、把这次的对话添加到对话记录中
        List<Message> requestMessage = chatClientRequest.prompt().getInstructions();
        messages.addAll(requestMessage);

        chatMemory.put(conversationId, messages); // 放入会话id 和 记录

        // 3、添加后的List<Message>放入请求中
        Prompt oldPrompt = chatClientRequest.prompt();
        Prompt newPrompt = oldPrompt.mutate().messages(messages).build();
        ChatClientRequest request = chatClientRequest.mutate().prompt(newPrompt).build();

        return request;
    }

    // 响应处理
    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {

        // 1、通过会话id查询之前的对话记录
        String conversationId = chatClientResponse.context().get("conversationId").toString(); // 通过上下文获取
        List<Message> historyMessages = chatMemory.get(conversationId);
        if (historyMessages == null) {
            historyMessages = new ArrayList<>();
        }

        // 2、获取response中的ai消息 添加到对话记录中
        ChatResponse chatResponse = chatClientResponse.chatResponse();
        if (ObjectUtils.isEmpty(chatResponse)) {
            return chatClientResponse;
        }
        AssistantMessage output = chatResponse.getResult().getOutput();
        historyMessages.add(output);
        chatMemory.put(conversationId, historyMessages);
        return chatClientResponse;
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

使用Advisor

java 复制代码
package com.sangeng.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("chatMemory")
public class ZhiPuChatMemoryClientController {

    private final ChatClient chatClient;


    public ZhiPuChatMemoryClientController(ChatClient.Builder builder) {

        // 设置默认的memoryAdvisor实现上下文
        MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory
                .builder()
                .build();
        MessageChatMemoryAdvisor memoryAdvisor = MessageChatMemoryAdvisor
                .builder(messageWindowChatMemory)
                .build();
        this.chatClient = builder.
                defaultAdvisors(memoryAdvisor)
                .build();
    }

    // 系统提供的
    @GetMapping("/MessageChatMemoryAdvisor")
    public Flux<String> MessageChatMemoryAdvisor(@RequestParam(name = "query") String query,
                                                 @RequestParam(name = "conversationId") String conversationId) {
        Flux<String> flux = chatClient.prompt()
                .advisors(advisorSpec -> advisorSpec.param(MessageWindowChatMemory.CONVERSATION_ID, conversationId))
                .user(query)
                .stream()
                .content();
        return flux;
    }


    /* prompt Template */

    @GetMapping("/PromptTemplate")
    public Flux<String> PromptTemplate() {
        // 1、用户提示词
        PromptTemplate promptTemplate = new PromptTemplate(
                "你是一个有用的人工智能,名字为{name}请用{voice}的风格回答问题:{question}");
        Message message = promptTemplate.createMessage(
                Map.of("name", "小宇", "voice", "御姐", "question", "平凡的人指的被爱吗?"));
        Prompt prompt = new Prompt(List.of(message));
        return chatClient.prompt(prompt)
                .stream()
                .content();
    }

    @GetMapping("/SystemPromptTemplate")
    public Flux<String> SystemPromptTemplate() {
        // 2、系统提示词
        PromptTemplate systemPromptTemplate = new SystemPromptTemplate(
                "你是一个有用的人工智能,名字为{name}请用{voice}的风格回答问题");
        Message sysMessage = systemPromptTemplate.createMessage(Map.of("name", "小宇", "voice", "御姐" ));

        PromptTemplate userPromptTemplate = new PromptTemplate("请回答问题:{question}");
        Message userMessage = userPromptTemplate.createMessage(Map.of("question", "平凡的人指的被爱吗?"));
        // 组装prompt
        Prompt prompt = new Prompt(List.of(sysMessage, userMessage));

        return chatClient.prompt(prompt)
                .stream()
                .content();
    }


    @GetMapping("/SystemPromptTemplate2")
    public Flux<String> SystemPromptTemplate2() {
        // 另一种写法请求
        PromptTemplate systemPromptTemplate = new SystemPromptTemplate(
                "你是一个有用的人工智能,名字为{name}请用{voice}的风格回答问题");
        Prompt sysPrompt = systemPromptTemplate.create(Map.of("name", "小宇", "voice", "御姐"));

        PromptTemplate userPromptTemplate = new PromptTemplate("请回答问题:{question}");
        Prompt userPrompt = userPromptTemplate.create(Map.of("question", "平凡的人指的被爱吗?"));

        return chatClient.prompt(sysPrompt)
                .stream()
                .content();
    }

}

2.5 Prompt Template

①使用原因 假设提示词为:你是一个有用的人工智能助手,名字是小白请用幽默的风格回答以下问题:推荐上海的三个景点 我们希望助手的名字,风格,还需要回答的问题都是可变的。如果我们没有没有PromptTemplate的情 况下可以需要写成这样的:

java 复制代码
String name = "小王";
String voice = "幽默";
String userQuestion = "推荐上海的三个景点";
// 硬编码拼接提示词
String promptText = "你是一个有用的人工智能助手,名字是" + name + ",请用" + voice + "的
风格回答以下问题:" + userQuestion;

存在几个明显问题:

  • **代码臃肿:**每次需要改变提示词结构或角色设定时,都必须修改代码并重新部署。
  • **难以维护:**如果提示词逻辑变得复杂(例如需要加入系统指令、上下文历史等),字符串拼接会变 得非常混乱且容易出错。
  • **缺乏复用性:**相同的提示词结构无法轻松应用于其他类似场景。

使用PromptTemplate就可以解决这些问题。

②是什么 在 Spring AI 的开发过程中,Prompt Template(提示词模板) 的核心价值在于它能够将静态的提示 词结构与动态的业务数据分离,从而提升代码的可维护性和复用性

当你的应用需要与大型语言模型 (LLM)交互,且交互内容会根据用户输入或业务状态发生变化时,就是使用 Prompt Template 的典型 场景。

③如何使用

渲染用户提示词

java 复制代码
    @GetMapping("/PromptTemplate")
    public Flux<String> PromptTemplate() {
        // 1、用户提示词
        PromptTemplate promptTemplate = new PromptTemplate(
                "你是一个有用的人工智能,名字为{name}请用{voice}的风格回答问题:{question}");
        Message message = promptTemplate.createMessage(
                Map.of("name", "小宇", "voice", "御姐", "question", "平凡的人指的被爱吗?"));
        Prompt prompt = new Prompt(List.of(message));
        return chatClient.prompt(prompt)
                .stream()


    @GetMapping("/SystemPromptTemplate")
    public Flux<String> SystemPromptTemplate() {
        // 2、系统提示词
        PromptTemplate systemPromptTemplate = new SystemPromptTemplate(
                "你是一个有用的人工智能,名字为{name}请用{voice}的风格回答问题");
        Message sysMessage = systemPromptTemplate.createMessage(Map.of("name", "小宇", "voice", "御姐" ));

        PromptTemplate userPromptTemplate = new PromptTemplate("请回答问题:{question}");
        Message userMessage = userPromptTemplate.createMessage(Map.of("question", "平凡的人指的被爱吗?"));
        // 组装prompt
        Prompt prompt = new Prompt(List.of(sysMessage, userMessage));

        return chatClient.prompt(prompt)
                .stream()
                .content();
    }


    @GetMapping("/SystemPromptTemplate2")
    public Flux<String> SystemPromptTemplate2() {
        // 另一种写法请求
        PromptTemplate systemPromptTemplate = new SystemPromptTemplate(
                "你是一个有用的人工智能,名字为{name}请用{voice}的风格回答问题");
        Prompt sysPrompt = systemPromptTemplate.create(Map.of("name", "小宇", "voice", "御姐"));

        PromptTemplate userPromptTemplate = new PromptTemplate("请回答问题:{question}");
        Prompt userPrompt = userPromptTemplate.create(Map.of("question", "平凡的人指的被爱吗?"));

        return chatClient.prompt(sysPrompt)
                .stream()
                .content();
    }

3、RAG

3.1 概述

我们去问AI一些问题的时候,如果这个知识是模型在训练时没有涉及到的,这个时候AI就会出现幻觉(看似合理但实则错误或虚构的信息)。

所以我们想要避免AI出现幻觉就需要让AI知道相关的知识。

检索增强生成(RAG)技术其核心价值在于,它以一种成本效益高且灵活的方式,为LLM连接了一个可实时更新的"外部知识库",从而显著提升了AI应用的可靠性、时效性和专业性。

3.2 什么是RAG

RAG(Retrieval-Augmented Generation) 检索增强生成。通过外部数据库检索出相关的知识,然后把问题和相关知识一起给到大模型来让模型生成回答。

3.3 RAG流程

3.4. 相关概念

概念 解释
向量 向量就像是一个有序的数字列表,或者说是多维空间中的一个点、一条有方向的线段。这些数字共同描述了对象在某一个"特征空间"中的位置。
为什么要把文本转换成向量 计算机无法直接理解人类语言,但极其擅长处理数字。一旦文本变成了向量(即高维空间中的点),我们就可以运用强大的数学工具来处理它们,计算相似度。(比如余弦相似度算法)
Embedding 嵌入 把文本->多维向量的过程叫做Embedding 嵌入。
EmbeddingMode嵌入模型 嵌入模型可以把文本转换成多维向量
向量数据库 能够存储向量并且具有向量相似度检索相关能力的数据库
向量维度 向量的维度就是这个列表里数字的个数。例如,向量 [0.12, -0.87, 0.33,1.24]的维度是4; 较高维度通常能提供更丰富的语义信息和更强的区分能力,尤其利于处理复杂语义或需要高精度的场景。但这会增加计算量,占用更多内存和存储,也可能使计算速度变慢

地址(智谱)docs.bigmodel.cn/api-referen...

准备向量数据库

  • 1、使用redis-stack作为向量数据库。
  • 2、提供一个接口,接口被调用的时候可以把文本向量化然后存入向量数据库
  • 3、另外再提供一个接口,这个接口能基于问题去向量数据库的中进行相似度搜索,查询出相似度较高的几个数据。

1、向量数据库安装 使用docker部署redis-stack【好像windows不能直接安装redis-stack???】

bash 复制代码
docker run -d --name redis-stack 
-p 6379:6379 
-p 8001:8001 redis/redis-stack:latest

2、创建项目并添加如下依赖,主要是redis作为向量数据库的依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-redis</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-zhipuai</artifactId>
        </dependency>
        

3、添加相关配置

yaml 复制代码
# 服务器配置
server:
  port: 8080 # 应用服务监听端口,默认8080
spring:
  # 应用基本信息配置
  application:
    name: sangeng-redis-rag # 应用名称
  data:
    redis:
      host: 127.0.0.1
      port: 6379
  # Spring AI 配置
  ai:
    # 智谱AI大模型配置
    zhipuai:
      api-key:  ZHIPU_KEY # 智谱API密钥
      chat:
        options:
          model: glm-4.6 # 使用的聊天模型名称(GLM-4.6)
      embedding:
        options:
          model: embedding-3 # 使用的嵌入模型名称(embedding-3)
          dimensions: 256 # 嵌入向量的维度(256维)
    # 向量存储配置
    vectorstore:
      redis:
        initialize-schema: true # 启动时自动创建Redis向量索引结构(首次部署需开启)
        prefix: sangeng_rag_prefix # Redis键名前缀,用于区分不同应用的向量数据
        index: sangeng_rag_index # Redis向量索引名称

4、代码编写

conrtoller编写【支持导入和检索】

java 复制代码
package com.sangeng.controller;

import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;



@RestController
@RequestMapping("/rag")
public class RagController {


    // springAI提供的向量数据库操作方法
    private final VectorStore vectorStore;


    public RagController(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    /* 插入数据 */
    @GetMapping("/importData")
    public String importData(@RequestParam("data") String data) {

        Document document = Document.builder()
                .text(data)
                .build();
        vectorStore.add(List.of(document));
        return "success";

    }

    /* 检索数据 */
    @GetMapping("/search")
    public List<Document> search(@RequestParam("query") String query) {

        SearchRequest searchRequest = SearchRequest.builder()
                .query(query)
                .topK(3) //搜索后返回前几个
                .similarityThreshold(0) // 阈值,低于该阈值的不返回
                .build();

        List<Document> documents = vectorStore.similaritySearch(searchRequest);
        return documents;

    }
}

3.5.咖啡店智能客服RAG实战

目标: 使用RetrievalAugmentationAdvisor把常见咖啡店相关的QA数据存入向量数据库。让用户问智能客服相 关问题的时候能够回答出来。 思路:

  • 1、先把csv文件中每一个QA作为一个Document存入向量数据库
  • 2、用户提问的时候先基于用户的问题去向量数据库中检索相关的知识。然后把让AI能够基于这些知识去回答。

代码编写 添加依赖

xml 复制代码
        <!-- 解析csv文件的依赖 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.10.0</version>
        </dependency>

        <!-- 解析RAG依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-rag</artifactId>
        </dependency>

contoller编写

java 复制代码
package com.sangeng.controller;

import com.sangeng.tool.TimeTools;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/coffee")
public class CoffeeController {


    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public CoffeeController(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) {

        this.vectorStore = vectorStore;

        // 设置检索增强的advisor

//        VectorStoreDocumentRetriever vectorStoreDocumentRetriever =
//                new VectorStoreDocumentRetriever(vectorStore, 0.5, 2,null);

        VectorStoreDocumentRetriever vectorStoreDocumentRetriever = VectorStoreDocumentRetriever.builder()
                .vectorStore(vectorStore)
                .topK(2)
                .similarityThreshold(0.5)
                .build();

        RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(vectorStoreDocumentRetriever)
                .build();
        this.chatClient = chatClientBuilder
                .defaultAdvisors(retrievalAugmentationAdvisor)
                .defaultTools(new TimeTools())  //工具
                .build();

    }


    /* 导入csv数据到向量数据库 */
    @GetMapping("/importCsvData")
    public String importCsvData() {
        try {


            // 读取classpath下的QA.csv文件
            ClassPathResource resource = new ClassPathResource("QA.csv");
            InputStreamReader reader = new InputStreamReader(resource.getInputStream());

            // 使用Apache Commons CSV解析CSV文件
            CSVParser csvParser = CSVFormat.DEFAULT
                    .builder()
                    .setHeader()  // 第一行作为标题
                    .setSkipHeaderRecord(true)  // 跳过标题行
                    .build()
                    .parse(reader);

            List<Document> documents = new ArrayList<>();


            // 遍历每一行记录
            for (CSVRecord record : csvParser) {
                // 获取问题和回答字段
                String question = record.get("问题");
                String answer = record.get("回答");

                // 将问题和回答组合成文档内容
                String content = "问题: " + question + "\n回答: " + answer;

                // 创建Document对象
                Document document = new Document(content);

                // 添加到文档列表
                documents.add(document);
            }

            // 关闭解析器
            csvParser.close();

            // 将文档存入向量数据库
            vectorStore.add(documents);
            return "成功导入 " + documents.size() + " 条记录到向量数据库";
        } catch (IOException e) {
            e.printStackTrace();
            return "导入失败" + e.getMessage();
        }
    }


    /* RAG回答接口,明确展示查询向量数据库的过程 */
    @GetMapping("/rag-ask")
    public String ragAskQuestion(@RequestParam("question") String question) {
        // 先从向量数据库检索相关信息
        // 这里会使用RetrievalAugmentationAdvisor自动检索相关文档

        // 将问题和检索到的上下文一同发给ai模型生成回答
        String content = chatClient.prompt()
                .system("你是咖啡店的服务员,你需要回答用户的问题")
                .user(question)
                .call().content();
        return content;
    }
}

4、Tool Calling

1、概述 如果我们现在问我们前面的智能客服说:我现在下单一杯美式几点几分可以做好 ? 你会发现他没办法很好的回答问题。因为模型能力是有限的(比如不知道当前时间),如果想要让AI应用具备更强的功能,就可以使用tool calling。相当于是给模型提供一些工具,当模型判断需要使用工具的时候就会进行使用。 2、tool calling调用流程

  • 其实就是把工具的name,description,input schema等信息也放入模型的请求中。
  • 模型如果觉得需要调用工具就会返回相关的工具调用消息,这个时候SpringAI会基于这个消息去调用工具,得到一个工具调用的结果给到模型。
  • 模型再综合这个调用结果返回最终的答案。

3、代码编写

定义一个工具类

java 复制代码
package com.sangeng.tool;

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

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class TimeTools {

    @Tool(description = "通过时区id获取当前时间")
    public String getTimeByZoneId(@ToolParam(description = "时区id,比如 Asia/ShangHai") String zoneId) {

        ZoneId zone = ZoneId.of(zoneId);
        ZonedDateTime now = ZonedDateTime.now(zone);
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}

传入工具调用

java 复制代码
@GetMapping("/ask")
public String askQuestion(@RequestParam("question") String question) {
  return chatClient.prompt()
              .user(question)      
              .tools(new TimeTools())
              .call()
              .content();
}

逻辑探究

通过@Tool 和 @ToolParam的描述 ai生成变量传入后调用?

相关推荐
RealPluto5 小时前
Spring AOP 失效排查
java·spring
用户8307196840826 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
NE_STOP9 天前
springMVC-HTTP消息转换器与文件上传、下载、异常处理
spring
JavaGuide10 天前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
玹外之音10 天前
Spring AI MCP 实战:将你的服务升级为 AI 可调用的智能工具
spring·ai编程
来一斤小鲜肉10 天前
Spring AI入门:第一个AI应用跑起来
spring·ai编程
NE_STOP10 天前
springMVC-常见视图组件与RESTFul编程风格
spring