SpringAI基础一

AI基础概念

  • 大型语言模型(Large Language Model,LLM):是一种通过海量文本数据训练后,理解和生成人类语言的人工智能系统

  • Token:是AI大模型调用中模型处理文本的基本单位,一个Token约是一个汉字,可自行根据下述地址测试:

  • 模型参数:调用大模型时可以设置的一些参数,常见如下:

    • Temperature (温度):控制生成文本的随机性。值越低(如0.2),输出越确定、保守和专注;值越高(如0.9),输出越具有创造性和多样性,但也可能更不连贯。
      • 医疗用途时,值一般比较低
      • 画图等创造性发散思维时,值一般比较高
    • Top-p (核采样):与Temperature类似,用于控制采样的多样性。它从累积概率超过阈值p的最可能候选词中随机选择。较低的值(如0.5)限制选择范围,输出更可预测;较高的值(如0.9)则扩大选择范围
    • Max Tokens (最大生成长度):限制模型单次响应所能生成的最大token数量。设置过短可能导致回答被截断。
  • 流式响应:类似于我们使用Deepseek时AI给我们一个字一个字的回答效果。 目前流式响应主要是使用SSE技术实现的。

  • message - 目前主要是4种消息角色。

    • system(系统消息,用于设定AI的行为和角色)
    • user(用户消息,来自用户的输入)
    • assistant(助手消息,来自AI的回复)
    • tool(工具调用消息)
  • prompt - Prompt(提示词) 是您传递给AI模型的指令或问题。简单来说,Prompt是你用来告诉AI"做什么"和"怎么做"的话。

基本示例入门

本示例以智谱AI为例,可自行前往

智谱密钥创建

  • 前往智谱AI官网注册,注册完成后,去按照图示方式获取AIP密钥

模型接口调用

  • Step1:获取到API Key后,打开官网的开发文档,选择API文档,以对话补全为例,按照图示步骤复制CURL在Postman或Apifox中进行官方API调用

    • CURL如下:

      json 复制代码
      curl --location --request POST 'https://open.bigmodel.cn/api/paas/v4/chat/completions' \
      --header 'Authorization: Bearer {API Key}' \
      --header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
      --header 'Content-Type: application/json' \
      --data-raw '{
        "model": "glm-4.6",
        "messages": [
          {
            "role": "system",
            "content": "你是一个有用的AI助手。"
          },
          {
            "role": "user",
            "content": "请介绍一下人工智能的发展历程。"
          }
        ],
        "temperature": 0.8,
        "max_tokens": 65536,
        "stream": false,
        "thinking": {
          "type": "enabled"
        },
        "do_sample": true,
        "top_p": 0.6,
        "tool_stream": false,
        "response_format": {
          "type": "text"
        }
      }'
  • Step2:将CURL复制到Apifox中进行调用,运行结果如图所示

流式响应

  • 上述运行截图为非流式结果输出,流式响应如下所示

    • 流式某一条响应数据格式如下:

      json 复制代码
      data: {
        "id": "202512141804462ec463c8fe2a4fd6",
        "created": 1765706686,
        "object": "chat.completion.chunk",
        "model": "glm-4.6",
        "choices": [
          {
            "index": 0,
            "delta": {
              "role": "assistant",
              "reasoning_content": "拆"
            }
          }
        ]
      }
  • 若要将流式响应的数据进行合并拼接,则可利用Apifox进行自动合并或自定义提取规则进行合并,具体自定义合并规则可详见Apifox官网

    • Apifox进行自动合并

    • 自定义提取规则合并

      • 注意:当 SSE 返回的事件内容是 JSON 格式 ,但不符合 OpenAI、Gemni、Claude 等内置的识别规则时,则可以手动配置 JSONPath 来提取所需内容,示例图如下

      • 博主所测试的API接口的流式响应合并规则代码为:$.choices[0].delta.content,运行如下图所示

SpringAIAlibaba

  • SpringAIAlibaba:本质上是一个基于 Spring AI 抽象层的"增强插件集",提供了统一、便捷的接口,让使用者可以灵活地选择和切换底层模型。它对Spring AI 中的 Graph(流程编排框架)进行了具体的集成实现,可用于编排 多智能体与工作流 ,详细了解可见官方文档

快速入门

该示例Demo已上传至Gitee,可详见Gitee官网

微服务项目搭建此处不再演示,具体可详见SpringCloud项目搭建快速入门,此处只进行关键步骤说明

  • Step1: 在父工程SpringAi的pom.xml文件中导入相关依赖,完整pom.xml文件代码如下:

    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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>at.guigu</groupId>
        <artifactId>SpringAi</artifactId>
        <version>${project.version}</version>
        <packaging>pom</packaging>
    
        <modules>
    
            <module>QuickStart</module>
        </modules>
    
        <properties>
            <java.version>17</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.version>1.0-SNAPSHOT</project.version>
            <!-- 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.version>1.0.0.4</spring-ai-alibaba.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>
            <hutool-all.version>5.8.25</hutool-all.version>
        </properties>
    
        <dependencies>
            <!--启动类相关依赖:spring-boot-starter-web-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--springbootTest坐标-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--lombok坐标-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <!--hutool坐标-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool-all.version}</version>
            </dependency>
        </dependencies>
    
        <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>
  • Step2: 创建子模块QuickStart,并在该模块的pom文件中导入相关依赖,完整pom.xml文件代码如下:

    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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>at.guigu</groupId>
            <artifactId>SpringAi</artifactId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
    
        <artifactId>QuickStart</artifactId>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-model-zhipuai</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    • 注意:博主此处使用的是智谱AI模型,若使用其他模型则导入对应依赖即可

      xml 复制代码
      <!--OpenAI-->
      <dependency>
          <groupId>org.springframework.ai</groupId>
          <artifactId>spring-ai-starter-model-openai</artifactId>
      </dependency>
      <!--千问-->
      <dependency>
          <groupId>com.alibaba.cloud.ai</groupId>
          <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
      </dependency>
      <!--deepseek-->
      <dependency>
          <groupId>org.springframework.ai</groupId>
          <artifactId>spring-ai-starter-model-deepseek</artifactId>
      </dependency>
  • Step3: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建配置文件application.yml,代码如下:

    yaml 复制代码
    server:
      port: 8080
    logging:
      level:
        at.guigu: debug # 配置指定包及其子包下的所有类的日志级别为debug
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS  # 配置日志输出的时间戳格式
    
    spring:
      application:
        name: quick-start
      ai:
        zhipuai:
          api-key: 3cgrs572f6ef770a89d4187ab6c8cd0ef8004ad.iSxAQRG0eOGDzrvF # 配置API Key
          base-url: "https://open.bigmodel.cn/api/paas"   #配置模型地址
          chat:
            options:
              model: glm-4.5 #配置模型名称及版本
  • Step4: 创建表现层包controller,并在该包下创建类ZhipuChatController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.model.ChatModel;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/simple")
        public String simpleChat(@RequestParam(name = "query") String query) {
            // 调用ChatModel的call方法传入问题完成模型调用
            return chatModel.call(query);
        }
    }
  • Step5: 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

核心API--Message

AI基础概念--基本示例入门--模型接口调用 内容可知(如图一所示):

  • 在请求体中有一个数组类型的字段messages,数组中的每一个元素就是一个消息(即message),如图所示

  • 每个消息(即message)内都有一个角色(即role)和内容(即content

由官网可知messages一共支持四种角色(如图二所示)

因此,在SpringAI中定义了一个枚举类MessageTypemessages支持四种角色相对应,如图三所示

SpringAIAlibaba快速入门 中可知,在Java代码中我们可通过ChatModel接口中的call方法来进行大模型的调用,call方法主要有三个,如下图所示,快速入门中使用的是default String call(String message)

从上述图中可看出default String call(Message... messages)参数为Message接口,进入该接口后可发现:

  • 我们看一下AbstractMessage可知:该类为抽象类,并且在该类中有两个重要属性messageTypetextContent分别对应请求体数组类型字段messages中的消息message中的角色(即role)和内容(即content

    • 抽象类AbstractMessage有四个子类:AssistantMessageSystemMessageToolResponseMessageUserMessage,分别与智谱官网messages所支持的四种角色相对应,不同子类中的messageType属性值分别对应不同角色

SpringAI设计的Message继承体系如图所示

案例Demo

案例:写出一个与以下curl调用效果相同的Java代码

注意:

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ]
}'
  • Step1: 在表现层ZhipuChatController类中新增方法,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatModel;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/message")
        public String message(@RequestParam(name = "query") String query) {
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
            // 调用模型
            return chatModel.call(systemMessage,userMessage);
        }
    }
  • Step2: 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

  • 注意:curl中设置了一个模型参数(即model),此处在代码中未呈现是因为博主已在配置文件application.yml中配置。若想要在代码中呈现可详见 核心API--Prompt 这一部分内容

核心API--Prompt

  • Prompt(即提示词)用来引导 AI 模型生成特定输出的输入格式。它的设计和措辞会显著影响模型的响应。

  • curl请求体中的数组类型字段messages和模型参数(可详见 AI基础概念 部分内容)共同组成了Prompt

    • 因此,在SpringAI中,有对应的类Prompt与Prompt(即提示词)相对应,并且该类中有两个重要属性messageschatOptions分别与数组类型字段messages和模型参数一一对应

    • 其中ChatOptions接口中包含了通用的模型参数

      • 原因:不同的模型支持的模型参数可能不同,因此SpringAI就将通用的模型参数都设计到了ChatOptions接口中
      • 不同模型厂商实现该接口即可添加属于自己模型厂商的模型参数
  • 不同厂商的ChatOptions接口的实现类举例:

    • 智谱:ZhiPuAiChatOptions

    • OpenAI:OpenAiChatOptions

    • 千问:DashScopeAgentOptionsDashScopeChatOptions

    • DeepSeek:DeepSeekChatOptions

案例Demo

SpringAIAlibaba快速入门 中可知,在Java代码中我们可通过ChatModel接口中的call方法来进行大模型的调用,call方法主要有三个,如下图所示,快速入门中使用的是default String call(String message)

本次案例中使用ChatResponse call(Prompt prompt)

案例:写出一个与以下curl调用效果相同的Java代码

注意:

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ],
    "temperature": 0.0,
    "maxTokens":15536
}'
  • Step1: 在表现层ZhipuChatController类中新增方法,代码如下:

    • 注意
      • 返回类型是ChatResponse
      • 博主已在配置文件application.yml中配置模型版本,此处在代码中又进行了配置,则会以代码中的模型版本为准,以此来解决未来多模型使用不同版本的问题
    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.messages.Message;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/chatOptions")
        public ChatResponse chatOptions(@RequestParam(name = "query") String query) {
            
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
            List<Message> messages = List.of(systemMessage,userMessage);
            
            ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
            zhiPuAiChatOptions.setModel("glm-4.5");
            zhiPuAiChatOptions.setTemperature(0.0);
            zhiPuAiChatOptions.setMaxTokens(15536);
            
            /* 等同于
            ZhiPuAiChatOptions zhiPuAiChatOptions =ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
            */
            
            // 调用模型
            return chatModel.call(new Prompt(messages,zhiPuAiChatOptions));
        }
    }
  • Step2: 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

获取ChatResponse内文本信息

  • ChatResponse完整响应内容如下:

    json 复制代码
    {
        "result": {
            "metadata": {
                "finishReason": "STOP",
                "contentFilters": [],
                "empty": true
            },
            "output": {
                "messageType": "ASSISTANT",
                "metadata": {
                    "finishReason": "STOP",
                    "role": "ASSISTANT",
                    "id": "2025121521544885717d4fa5664df5",
                    "messageType": "ASSISTANT"
                },
                "toolCalls": [],
                "media": [],
                "text": "你好!我是一个AI助手,旨在通过对话为你提供帮助。我可以回答问题、提供信息、参与讨论、协助解决问题,以及进行各种类型的对话。无论是学习、工作还是日常生活中的疑问,我都很乐意尽我所能为你提供支持。请随时告诉我你需要什么帮助!"
            }
        },
        "metadata": {
            "id": "2025121521544885717d4fa5664df5",
            "model": "glm-4.5",
            "rateLimit": {
                "requestsRemaining": 0,
                "tokensRemaining": 0,
                "tokensReset": "PT0S",
                "requestsReset": "PT0S",
                "requestsLimit": 0,
                "tokensLimit": 0
            },
            "usage": {
                "promptTokens": 19,
                "completionTokens": 131,
                "totalTokens": 150,
                "nativeUsage": {
                    "completion_tokens": 131,
                    "prompt_tokens": 19,
                    "total_tokens": 150
                }
            },
            "promptMetadata": [],
            "empty": false
        },
        "results": [
            {
                "metadata": {
                    "finishReason": "STOP",
                    "contentFilters": [],
                    "empty": true
                },
                "output": {
                    "messageType": "ASSISTANT",
                    "metadata": {
                        "finishReason": "STOP",
                        "role": "ASSISTANT",
                        "id": "2025121521544885717d4fa5664df5",
                        "messageType": "ASSISTANT"
                    },
                    "toolCalls": [],
                    "media": [],
                    "text": "你好!我是一个AI助手,旨在通过对话为你提供帮助。我可以回答问题、提供信息、参与讨论、协助解决问题,以及进行各种类型的对话。无论是学习、工作还是日常生活中的疑问,我都很乐意尽我所能为你提供支持。请随时告诉我你需要什么帮助!"
                }
            }
        ]
    }
  • 从以上内容可知,大模型回答的文本内容text存在于resultresults中,因此,若想要直接返回text内容,则表现层ZhipuChatController类代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.messages.Message;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/getChatOptionsText")
        public String chatOptionsText(@RequestParam(name = "query") String query) {
    
            // 创建消息列表设置消息message
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
            List<Message> messages = List.of(systemMessage,userMessage);
    
            // 创建ChatOptions对象设置模型参数
            ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
            zhiPuAiChatOptions.setModel("glm-4.5");
            zhiPuAiChatOptions.setTemperature(0.0);
            zhiPuAiChatOptions.setMaxTokens(15536);
    
            /* 等同于
            ZhiPuAiChatOptions zhiPuAiChatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
            */
            // 调用模型
            ChatResponse chatResponse = chatModel.call(new Prompt(messages, zhiPuAiChatOptions));
            // 返回文本信息
            return chatResponse.getResult().getOutput().getText();
            /* 等同于
            return chatResponse.getResults().get(0).getOutput().getText();
            */
        }
    }
    • 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

核心API--ChatModel

核心API--Prompt 的内容中可知,大模型使用的整体流程是传入一个Prompt进入ChatModelChatModel处理完后会返回一个ChatResponse

其中ChatModel封装了一些具体的调用细节,它会将Prompt中的数据封装为一个Http的Request请求,然后利用该Request去请求大模型返回Response响应,最终ChatModel会对该响应进行处理转换为ChatResponse 返回。详细图示如下所示

在本小节的案例Demo中,博主使用的均是ChatModel接口中的call方法,该方法属于 同步响应 ,也就是说ChatModel会在获取模型的最终响应结果后才会去返回ChatResponse,因此在使用时会有一些等待

而我们日常在使用chatgpt、deepseek等大模型的过程中可发现,并不是一下把最终响应结果返回给我们,而是一个字一个字的响应返回,以此带给我们更好的体验,这就是流式响应

  • ChatModel接口有一个父类接口StreamingChatModel,在该接口中有三个重载的stream方法可实现 流式响应 ,如图所示。案例Demo中会用三个案例对应进行代码示例

    • 需要注意的是,在使用流式响应时,需在配置文件中添加如下代码避免流式响应乱码:

      yaml 复制代码
      server:
        servlet:
          encoding:
            charset: UTF-8
            enabled: true
            force: true

案例Demo

注意:


  • 公共步骤:在配置文件中添加如下代码避免 流式响应 乱码

    yaml 复制代码
    server:
      servlet:
        encoding:
          charset: UTF-8
          enabled: true
          force: true
    • 配置文件application.yml完整代码如下:

      yaml 复制代码
      server:
        port: 8080
        servlet:
          # 避免流式响应乱码
          encoding:
            charset: UTF-8
            enabled: true
            force: true
      
      spring:
        application:
          name: quick-start
        ai:
          zhipuai:
            # 配置API Key
            api-key: 3cgrs572f6ef770a89d4187ab6c8cd0ef8004ad.iSxAQRG0eOGDzrvF
            #配置模型地址
            base-url: "https://open.bigmodel.cn/api/paas"
            chat:
              options:
                model: glm-4.5 #配置模型名称及版本
                
      
      logging:
        # 配置指定包及其子包下的所有类的日志级别为debug
        level:
          at.guigu: debug
        # 配置日志输出的时间戳格式
        pattern:
          dateformat: MM-dd HH:mm:ss:SSS

案例1:写出一个与以下curl调用效果相同的Java代码

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ]
}'
  • Step1: 在表现层ZhipuChatController类中新增方法,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.model.ChatModel;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/stream/chatModelOne")
        public Flux<String> stream(@RequestParam(name = "query") String query) {
            return chatModel.stream(query);
        }
    }
  • Step2: 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

案例2:写出一个与以下curl调用效果相同的Java代码

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ]
}'
  • Step1: 在表现层ZhipuChatController类中新增方法,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.messages.Message;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatModel;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/stream/chatModelTwo")
        public Flux<String> stream() {
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage("你好,请介绍一下自己。");
            return chatModel.stream(systemMessage, userMessage);
        }
    }
  • Step2: 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

案例3:写出一个与以下curl调用效果相同的Java代码

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ],
    "temperature": 0.0,
    "maxTokens":15536
}'
  • Step1: 在表现层ZhipuChatController类中新增方法,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.messages.Message;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuChatController {
    
        private final ChatModel chatModel;
    
        @GetMapping("/stream/chatModel")
        public Flux<ChatResponse> stream(@RequestParam(name = "query") String query) {
            // 创建消息列表设置消息message
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
            List<Message> messages = List.of(systemMessage,userMessage);
    
            // 创建ChatOptions对象设置模型参数
            ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
            zhiPuAiChatOptions.setModel("glm-4.5");
            zhiPuAiChatOptions.setTemperature(0.0);
            zhiPuAiChatOptions.setMaxTokens(15536);
            // 调用模型
            return chatModel.stream(new Prompt(messages, zhiPuAiChatOptions));
        }
    }
  • Step2: 运行子模块QuickStart的启动类QuickStartApplication,在Apifox中调用,运行如图所示

ChatClient

在核心API的案例Demo中可知,虽然我们可以使用ChatModel去调用模型,但不够精简,因此SpringAI提供了ChatClient接口来简化对大模型的调用

  • ChatClient优势
    • 基础功能:
      • 定制和组装模型的输入(Prompt)
      • 格式化解析模型的输出(Structured Output)
      • 调整模型交互参数(ChatOptions)
    • 高级功能:
      • 初级聊天记忆(Chat Memory)
      • 工具/函数调用(Function Calling)
      • RAG

快速入门

案例:写出一个与以下curl调用效果相同的Java代码

注意:

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ],
    "temperature": 0.0,
    "maxTokens":15536
}'
  • Step1: 创建一个与表现层controller包同级的config包,并在该包下创建配置类ChatClient,代码如下:

    java 复制代码
    package at.guigu.config;
    
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ChatClientConfig {
    
        /**
         * 创建一个默认的ChatClient,使用默认的ChatModel
         */
        @Bean("defaultChatClient")
        public ChatClient defaultChatClient(ChatClient.Builder builder) {
            return builder.build();
        }
    }
  • Step2: 在表现层controller包下创建类ZhipuChatClientController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import jakarta.annotation.Resource;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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("/chatclient")
    public class ZhipuChatClientController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
        
        @GetMapping("/simpleChat")
        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() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .call() // 模型调用
                    .content(); // 获取文本数据
        }
        /*等同于
        @GetMapping("/simpleChat")
        public String simpleChat(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    // 调用大模型
                    .call()
                    .content(); // 获取文本数据
        }
        */
    }
  • Step3: 运行启动类QuickStartApplication,在Apifox中调用,运行如图所示

    • 由于在pom.xml文件中只导入了一个智谱AI模型的依赖,并未导入其它模型依赖。因此,在配置类中ChatClientConfig所配置的默认ChatModel就是智谱AI模型,

若想要响应数据以流式返回,则除以上步骤之外,主要代码如下:

  • Step1: 在配置文件中添加代码避免 流式响应 乱码,完整代码如下:

    yaml 复制代码
    server:
      port: 8080
      servlet:
        # 避免流式响应乱码
        encoding:
          charset: UTF-8
          enabled: true
          force: true
    logging:
      # 配置指定包及其子包下的所有类的日志级别为debug
      level:
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
    spring:
      application:
        name: quick-start
      ai:
        zhipuai:
          # 配置API Key---替换为自己的密钥
          api-key: 3cgrs572f6ef770a89d4187ab6c8cd0ef8004ad.iSxAQRG0eOGDzrvF
          #配置模型地址
          base-url: "https://open.bigmodel.cn/api/paas"
          chat:
            options:
              model: glm-4.5 #配置模型名称及版本
  • Step2:ZhipuChatClientController类中新增方法getStreamText(),代码如下

    • 注意:此时返回类型为Flux<String>
    java 复制代码
    package at.guigu.controller;
    
    import jakarta.annotation.Resource;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("/chatclient")
    public class ZhipuChatClientController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        /**
         * 响应数据流式返回
         */
        @GetMapping(value = "/getStreamText")
        public Flux<String> getStreamText(@RequestParam(name = "query") String query) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
            return chatClient.prompt()
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query)
                    .options(chatOptions)
                    .stream()
                    .content();
        }
    }

注意

  • 若pom.xml文件中除了智谱AI模型外,还包括DeepSeek模型,并且该模型也在配置文件application.yml中进行了相关配置,此时就不能在通过 Step1 中的方法来获取ChatClientBean。因为,此时Spring在进行自动装配时会发现有两个ChatModel(即ZhiPuAiChatModelDeepSeekChatModel),就会报如图所示错误

    • 此时在配置类中可通过以下代码进行自定义ChatModel

      多模型切换ChatModel示例此处不再详细演示,可详见Gitee项目SpringAi分支--ChatClient/自定义ChatModel

      JAVA 复制代码
      package at.guigu.config;
      
      import org.springframework.ai.chat.client.ChatClient;
      import org.springframework.ai.deepseek.DeepSeekChatModel;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class ChatClientConfig {
      
           /**
           * 创建一个自定义的ChatClient,使用自定义的ChatModel
           * 使用智谱AI模型
           */
          @Bean("zhipuChatClient")
          public ChatClient zhipuChatClient(ZhiPuAiChatModel chatModel) {
              return ChatClient.builder(chatModel)
                      .build();
          }
      
          /**
           * 创建一个自定义的ChatClient,使用自定义的ChatModel
           * 使用DeepSeek模型
           */
          @Bean("deepSeekChatClient")
          public ChatClient deepSeekChatClient(DeepSeekChatModel chatModel) {
              return ChatClient.builder(chatModel)
                      .build();
          }
      }
  • 快速入门中的示例都是直接返回的String类型的文本数据。若想要返回类型是ChatResponse,则主要代码如下,具体可详见Gitee项目SpringAi分支--ChatClient/快速入门

    java 复制代码
    package at.guigu.controller;
    
    import jakarta.annotation.Resource;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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("/chatclient")
    public class ZhipuChatClientController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/simpleChat2")
        public ChatResponse simpleChat2(@RequestParam(name = "query") String query) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .call() // 模型调用
                    .chatResponse(); // 返回类型是`ChatResponse`
        }
        /*等同于
        @GetMapping("/simpleChat2")
        public ChatResponse simpleChat2(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    // 调用大模型
                    .call()
                    .chatResponse(); // 返回类型是`ChatResponse`
        }
        */
    }

响应数据转化为实体

在实际开发中,一般都会将响应数据封装到对象中返回,以便于前端可以对数据进行处理,因此在调用大模型获取响应后也可将数据转化为实体类响应对象返回

案例Demo

案例:随机生成一本书,要有书名和作者。

注意:

  • Step1: 创建一个与表现层controller包同级的config包,并在该包下创建配置类ChatClient,代码如下:

    java 复制代码
    package at.guigu.config;
    
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ChatClientConfig {
    
        /**
         * 创建一个默认的ChatClient,使用默认的ChatModel
         */
        @Bean("defaultChatClient")
        public ChatClient defaultChatClient(ChatClient.Builder builder) {
            return builder.build();
        }
    }
  • Step2: 创建一个与表现层controller包同级的entity包,并在该包下创建实体类Book,代码如下:

    java 复制代码
    package at.guigu.entity;
    
    import lombok.Data;
    
    @Data
    public class Book {
        private String name;
        private String author;
    }
  • Step3: 在表现层controller包下创建类ZhipuChatClientController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import at.guigu.entity.Book;
    import jakarta.annotation.Resource;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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("/chatclient")
    public class ZhipuChatClientController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/getEntityResponse")
        public Book getEntityResponse(@RequestParam(name = "query") String query) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .call() // 模型调用
                    .entity(Book.class); // 将模型返回数据映射到实体类Book中响应返回
        }
    
        /*等同于
        @GetMapping("/getEntityResponse")
        public Book getEntityResponse(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    // 调用大模型
                    .call()
                    .entity(Book.class); // 将模型返回数据映射到实体类Book中响应返回
        }
        */
    }
  • Step4: 运行启动类QuickStartApplication,在Apifox中调用,运行如图所示

原理: SpringAI会自动在原有提示词的基础上进一步增加提示词,然后将其传给大模型,让其以指定形式输出,最终将数据映射到实体类中返回响应。

  • SpringAI将提示词包装后形成的完整形态的新提示词如下:

    json 复制代码
    你是一个有用的AI助手。给我随机生成一本书,要求书名和作者都是中文
    Your response should be in JSON format.
    Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
    Do not include markdown code blocks in your response.
    Remove the ```json markdown from the output.
    Here is the JSON Schema instance your output must adhere to:
    ```{
      "$schema" : "https://json-schema.org/draft/2020-12/schema",
      "type" : "object",
      "properties" : {
        "author" : {
          "type" : "string"
        },
        "name" : {
          "type" : "string"
        }
      },
      "additionalProperties" : false
    }```

Prompt Template

在之前的示例Demo中可看到,所有的提示词都是需要手动输入的,如果有一个固定模板,用户只需要输入指定的内容,此时就会大大提高效率。
假设有个提示词为:

  • 你是一个有用的人工智能助手,名字是 小白 请用 幽默 的风格回答以下问题: 推荐上海的三个景点

现在我希望加粗的部分是可以动态变化的,而其余部分固定不变

因此就需要用到Prompt Template(提示词模板)

  • Prompt Template(提示词模板)能够将静态的提示词结构与动态的业务数据分离,从而提升代码的可维护性和复用性。
    • 当你的应用需要与大型语言模型(LLM)交互,且交互内容会根据用户输入或业务状态发生变化时,就是使用 Prompt Template 的典型场景。
  • 官方根据消息(Message)分类将提示词分为了四种:
    • 用户提示词:PromptTemplate
    • 系统提示词:SystemPromptTemplate
    • 助手提示词:AssistantPromptTemplate
    • 工具提示词:FunctionPromptTemplate
    • 注意:用户提示词对应类是其它三种提示词对应类的父类

快速入门

本快速入门以 ChatClient-->响应数据转化为实体 的案例Demo为例进行演示,只演示变化的部分,其余部分均省略

该Demo可详见Gitee项目SpringAi分支--Prompt-Template案例

  • 在表现层controller包下创建类ZhipuChatClientController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import at.guigu.entity.Book;
    import jakarta.annotation.Resource;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.messages.Message;
    import org.springframework.ai.chat.messages.SystemMessage;
    import org.springframework.ai.chat.messages.UserMessage;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.chat.prompt.PromptTemplate;
    import org.springframework.ai.chat.prompt.SystemPromptTemplate;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/chatclient")
    public class ZhipuChatClientController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/promptTemplate")
        public Book getEntityResponse(@RequestParam(name = "systemPrompt") String systemPrompt, @RequestParam(name = "name") String name, @RequestParam(name = "voice") String voice, @RequestParam(name = "userQuestion") String userQuestion) {
            // 配置模型参数
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
            // 系统提示词
            SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是一个{systemPrompt},你的名字是{name}");
            // 根据系统提示词生成完整系统消息
            SystemMessage systemMessage = (SystemMessage) systemPromptTemplate.createMessage(Map.of("systemPrompt", systemPrompt, "name", name));
            // 用户提示词
            PromptTemplate userPrompt = new PromptTemplate("请用{voice}的风格回答问题:{userQuestion}");
            // 根据用户提示词生成完整用户消息
            UserMessage userMessage = (UserMessage) userPrompt.createMessage(Map.of("voice", voice, "userQuestion", userQuestion));
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .messages(systemMessage, userMessage) // 配置用户消息及系统消息
                    .options(chatOptions) // 配置模型参数
                    .call() // 模型调用
                    .entity(Book.class); // 将模型返回数据映射到实体类Book中响应返回
        }
    
        /*等同于
        @GetMapping("/getEntityResponse")
        public Book getEntityResponse(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = (SystemMessage) SystemPromptTemplate.builder()
                    .template("你是一个{systemPrompt},你的名字是{name}")
                    .build()
                    .createMessage(Map.of("systemPrompt", systemPrompt, "name", name));
                    
            UserMessage userMessage = (UserMessage) PromptTemplate.builder()
                    .template("请用{voice}的风格回答问题:{userQuestion}")
                    .build()
                    .createMessage(Map.of("voice", voice, "userQuestion", userQuestion));
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    // 调用大模型
                    .call()
                    .entity(Book.class); // 将模型返回数据映射到实体类Book中响应返回
        }
        */
    }
  • 运行启动类QuickStartApplication,在Apifox中调用,运行如图所示

Advisors

  • Spring AI Advisors 是 Spring AI 框架中的 核心拦截器组件 ,专门用于处理和增强 AI 应用程序中的请求与响应流。其

    • 它可将常见的生成式 AI 模式(如对话记忆、敏感词过滤、RAG 检索)打包成 可重用单元 ,简化开发流程
    • 它可创建跨不同模型和用例工作的可重用转换组件,提升代码灵活性
  • Spring AI Advisors 的设计与过滤器拦截器非常类似,其增强流程如下

  • Spring AI Advisors 的继承体系如下

    • 若对同步调用进行增强可以实现CallAdvisor接口。若对流式调用(响应式调用)进行增强可以实现StreamAdvisor接口。
    • 若要对同步调用和流式调用都进行增强,则可以同时实现CallAdvisor接口和StreamAdvisor接口
    • Ordered接口的getOrder()方法的返回值决定Advisor在Advisor链中的执行顺序,返回值越小,执行顺序越靠前,越早处理请求

快速入门--同步调用

案例:写出一个与以下curl调用效果相同的Java代码

注意:

json 复制代码
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-4.5",
    "messages": [
        {
            "role": "system",
            "content": "你是一个有用的AI助手。"
        },
        {
            "role": "user",
            "content": "你好,请介绍一下自己。"
        }
    ],
    "temperature": 0.0,
    "maxTokens":15536
}'
  • Step1: 创建一个与表现层controller包同级的config包,并在该包下创建配置类ChatClientConfig,代码如下:

    java 复制代码
    package at.guigu.config;
    
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ChatClientConfig {
    
        /**
         * 创建一个默认的ChatClient,使用默认的ChatModel
         */
        @Bean("defaultChatClient")
        public ChatClient defaultChatClient(ChatClient.Builder builder) {
            return builder.build();
        }
    }
  • Step2: 创建与controller包同级的advisor包,并在该包下创建CallAdvisor接口的实现类SGCallAdvisor1,然后重写adviseCallgetNamegetOrder方法

    java 复制代码
    package at.guigu.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 SGCallAdvisor1 implements CallAdvisor {
    
        /**
         * @param chatClientRequest 模型调用的请求对象
         * @param callAdvisorChain 增强链,可以用来放行AI请求到下一个Advisor
         * @return 返回响应给上一个advisor
         */
        @Override
        public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
            log.info("SGCallAdvisor1 增强请求");
            /*--------增强请求逻辑代码---------*/
            ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest); // 放行请求到下一个advisor,并接收下一个advisor传来的响应
            log.info("SGCallAdvisor1 获取响应");
            /*--------响应处理逻辑代码---------*/
            return chatClientResponse; // 返回响应给上一个advisor
        }
    
        /**
         * @return 当前advisor的自定义名称
         */
        @Override
        public String getName() {
            return "SGCallAdvisor1";
        }
    
        /**
         * return 当前advisor在Advisor链中的自定义执行顺序排名
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
  • Step3: 在advisor包下继续创建CallAdvisor接口的实现类SGCallAdvisor2,并重写adviseCallgetNamegetOrder方法,代码如下:

    java 复制代码
    package at.guigu.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 {
    
        /**
         * @param chatClientRequest 模型调用的请求对象
         * @param callAdvisorChain 增强链,可以用来放行AI请求到下一个Advisor
         * @return 返回响应给上一个advisor
         */
        @Override
        public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
            log.info("SGCallAdvisor2 增强请求");
            /*--------增强请求逻辑代码---------*/
            ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest); // 放行请求到下一个advisor,并接收下一个advisor传来的响应
            log.info("SGCallAdvisor2 获取响应");
            /*--------响应处理逻辑代码---------*/
            return chatClientResponse; // 返回响应给上一个advisor
        }
    
        /**
         * @return 当前advisor的自定义名称
         */
        @Override
        public String getName() {
            return "SGCallAdvisor2";
        }
    
        /**
         * return 当前advisor在Advisor链中的自定义执行顺序排名
         */
        @Override
        public int getOrder() {
            return 1;
        }
    }
  • Step4: 在表现层controller包下创建类ZhipuAdvisorsController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import at.guigu.advisor.SGCallAdvisor1;
    import at.guigu.advisor.SGCallAdvisor2;
    import jakarta.annotation.Resource;
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuAdvisorsController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/simpleChat")
        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() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .advisors(new SGCallAdvisor1(), new SGCallAdvisor2()) // 配置增强链
                    .call() // 模型调用
                    .content(); // 获取文本数据
        }
        /*等同于
        @GetMapping("/simpleChat")
        public String simpleChat(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    .advisors(new SGCallAdvisor1(), new SGCallAdvisor2()) // 配置增强链
                    // 调用大模型
                    .call()
                    .content(); // 获取文本数据
        }
        */
    }
  • Step5: 运行启动类QuickStartApplication,在Apifox中调用,运行如图所示

快速入门--流式调用

案例:写出一个与以下curl调用效果相同的Java代码

注意:

  • Step1: 在配置文件中添加代码避免 流式响应 乱码,完整代码如下:

    yaml 复制代码
    server:
      port: 8080
      servlet:
        # 避免流式响应乱码
        encoding:
          charset: UTF-8
          enabled: true
          force: true
    
    spring:
      application:
        name: quick-start
      ai:
        zhipuai:
          # 配置API Key---替换为自己的密钥
          api-key: 3cgrs572f6ef770a89d4187ab6c8cd0ef8004ad.iSxAQRG0eOGDzrvF
          #配置模型地址
          base-url: "https://open.bigmodel.cn/api/paas"
          chat:
            options:
              model: glm-4.5 #配置模型名称及版本
    
    logging:
      # 配置指定包及其子包下的所有类的日志级别为debug
      level:
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
  • Step2: 创建一个与表现层controller包同级的config包,并在该包下创建配置类ChatClientConfig,代码如下:

    java 复制代码
    package at.guigu.config;
    
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ChatClientConfig {
    
        /**
         * 创建一个默认的ChatClient,使用默认的ChatModel
         */
        @Bean("defaultChatClient")
        public ChatClient defaultChatClient(ChatClient.Builder builder) {
            return builder.build();
        }
    }
  • Step3: 创建与controller包同级的advisor包,并在该包下创建StreamAdvisor接口的实现类SGCallAdvisor1,然后重写adviseStreamgetNamegetOrder方法

    java 复制代码
    package at.guigu.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.StreamAdvisor;
    import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
    import reactor.core.publisher.Flux;
    
    @Slf4j
    public class SGCallAdvisor1 implements StreamAdvisor {
    
        /**
         * @param chatClientRequest 模型调用的请求对象
         * @param streamAdvisorChain 增强链,可以用来放行AI请求到下一个Advisor
         * @return 返回响应给上一个advisor
         */
        @Override
        public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
            log.info("SGCallAdvisor1 增强请求");
            /*--------增强请求逻辑代码---------*/
            Flux<ChatClientResponse> chatClientResponseFlux = streamAdvisorChain.nextStream(chatClientRequest);// 放行请求到下一个advisor,并接收下一个advisor传来的响应
            log.info("SGCallAdvisor1 获取响应");
            /*--------响应处理逻辑代码---------*/
            return chatClientResponseFlux;
        }
    
        /**
         * @return 当前advisor的自定义名称
         */
        @Override
        public String getName() {
            return "SGCallAdvisor1";
        }
    
        /**
         * return 当前advisor在Advisor链中的自定义执行顺序排名
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
  • Step4: 在advisor包下继续创建StreamAdvisor接口的实现类SGCallAdvisor2,并重写adviseStreamgetNamegetOrder方法,代码如下:

    java 复制代码
    package at.guigu.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.StreamAdvisor;
    import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
    import reactor.core.publisher.Flux;
    
    @Slf4j
    public class SGCallAdvisor2 implements StreamAdvisor {
    
        /**
         * @param chatClientRequest 模型调用的请求对象
         * @param streamAdvisorChain 增强链,可以用来放行AI请求到下一个Advisor
         * @return 返回响应给上一个advisor
         */
        @Override
        public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
            log.info("SGCallAdvisor2 增强请求");
            /*--------增强请求逻辑代码---------*/
            Flux<ChatClientResponse> chatClientResponseFlux = streamAdvisorChain.nextStream(chatClientRequest);// 放行请求到下一个advisor,并接收下一个advisor传来的响应
            log.info("SGCallAdvisor2 获取响应");
            /*--------响应处理逻辑代码---------*/
            return chatClientResponseFlux;
        }
    
        /**
         * @return 当前advisor的自定义名称
         */
        @Override
        public String getName() {
            return "SGCallAdvisor2";
        }
    
        /**
         * return 当前advisor在Advisor链中的自定义执行顺序排名
         */
        @Override
        public int getOrder() {
            return 1;
        }
    }
  • Step5: 在表现层controller包下创建类ZhipuAdvisorsController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import at.guigu.advisor.SGCallAdvisor1;
    import at.guigu.advisor.SGCallAdvisor2;
    import jakarta.annotation.Resource;
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuAdvisorsController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/simpleChat")
        public Flux<String> simpleChat(@RequestParam(name = "query") String query) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .advisors(new SGCallAdvisor1(), new SGCallAdvisor2()) // 配置增强链
                    .stream() // 调用大模型并获取流式响应
                    .content(); // 获取文本数据
        }
        /*等同于
        @GetMapping("/simpleChat")
        public Flux<String> simpleChat(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    .advisors(new SGCallAdvisor1(), new SGCallAdvisor2()) // 配置增强链
                    .stream() // 调用大模型并获取流式数据
                    .content(); // 获取文本数据
        }
        */
    }
  • Step6: 运行启动类QuickStartApplication,在Apifox中调用,运行如图所示

BaseAdvisor接口

  • BaseAdvisor接口是CallAdvisor接口和StreamAdvisor接口的子接口。定义了增强请求或响应的基本能力。若要对同步调用和流式调用都进行增强,则可以直接利用BaseAdvisor接口来实现,它内部有两个主要方法。

    注意:具体示例可详见 自定义Advisor示例

    BaseAdvisor接口方法 解释
    ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) 在请求发送前对请求进行修改或增强
    ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) 在收到响应后对响应进行处理或转换

自定义Advisor示例

案例:让模型根据上下文信息回复用户问题

原理:

​ 将对话记录(包含用户发送的消息以及AI模型回复的消息)存入到内存中,由于每个用户在会话时都会有一个会话id,因此在用户发送信息时可将会话id传入后端,后端可通过会话id拿到对话记录,然后将对话记录以及用户当前新发送的消息一起传给模型,由此实现模型根据上下文信息回复用户问题

注意:

  • 此处将对话记录存入到内存中是为了方便该示例的演示,实际肯定不是这样存储信息的
  • 本案例Demo沿用 SpringAIAlibaba---快速入门 项目,并且只写重要部分代码
  • 该Demo可详见Gitee项目SpringAi分支--Advisors/自定义Advisor
  • Step1: 在配置文件中添加代码避免 流式响应 乱码,完整代码如下:

    注意:

    • 由于该示例中自定义的Advisor实现了BaseAdvisor接口,而该接口是CallAdvisor接口和StreamAdvisor接口的子接口,也就是说包含同步调用和流式调用,因此为了避免使用流式调用时导致乱码问题,因此需要提前加上相关配置代码进行预防
    • 在配置文件中配置了 客户端等待服务器响应的最长时间 ,目的是为了方便Debug调试验证该自定义Advisor是否生效,因此可自行根据情况判断是否需要该配置,博主不进行Debug演示,可自行演示
    yaml 复制代码
    server:
      port: 8080
      servlet:
        # 避免流式响应乱码
        encoding:
          charset: UTF-8
          enabled: true
          force: true
    
    spring:
      application:
        name: quick-start
      ai:
        zhipuai:
          # 配置API Key---替换为自己的密钥
          api-key: 3cgrs572f6ef770a89d4187ab6c8cd0ef8004ad.iSxAQRG0eOGDzrvF
          #配置模型地址
          base-url: "https://open.bigmodel.cn/api/paas"
          chat:
            options:
              model: glm-4.5 #配置模型名称及版本
      http:
        client:
          read-timeout: 1000000 # 客户端等待服务器响应的最长时间,超过这个时间会抛出超时异常 单位:ms
    
    logging:
      # 配置指定包及其子包下的所有类的日志级别为debug
      level:
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
  • Step2: 创建一个与表现层controller包同级的config包,并在该包下创建配置类ChatClientConfig,代码如下:

    java 复制代码
    package at.guigu.config;
    
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ChatClientConfig {
    
        /**
         * 创建一个默认的ChatClient,使用默认的ChatModel
         */
        @Bean("defaultChatClient")
        public ChatClient defaultChatClient(ChatClient.Builder builder) {
            return builder.build();
        }
    }
  • Step3: 创建与controller包同级的advisor包,并在该包下创建BaseAdvisor接口的实现类SimpleMessageChatMemoryAdvisor,然后重写beforeaftergetOrder方法,代码如下:

    注意:beforeafter方法的详细步骤具体可见代码

    java 复制代码
    package at.guigu.advisor;
    
    import cn.hutool.core.collection.CollUtil;
    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 java.util.*;
    
    public class SimpleMessageChatMemoryAdvisor implements BaseAdvisor {
    
        // 模拟一个内存中的会话存储
        private static Map<String, List<Message>> chatMemory = new HashMap<>();
        @Override
        public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
            // 存储历史消息记录
            List<Message> messages = new ArrayList<>();
    
            // Step1:从上下文中获取会话id---此处模拟从上下文中获取会话id
            String conversationId = "ssg";
            // Step2:通过会话id获取历史对话记录(即历史消息)
            List<Message> hisMessages = chatMemory.get(conversationId);
            // Step3:如果存在历史消息,则添加到包含所有Message的列表messages中
            if (Objects.nonNull(hisMessages) && CollUtil.isNotEmpty(hisMessages)) {
                messages.addAll(hisMessages);
            }
    
            // Step4:获取用户当前发送的最新消息
            List<Message> currentNewMessages = chatClientRequest.prompt().getInstructions();
            // Step5:将最新的发送消息添加到包含所有Message的列表messages中
            messages.addAll(currentNewMessages);
    
            // Step6:将包含所有Message的列表messages设置到请求中---及包含所有历史消息及当前用户发送的最新消息
            ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
                    .prompt(chatClientRequest.prompt()
                            .mutate()
                            .messages(messages)
                            .build())
                    .build();
    
            // Step7:将会话存储的消息更新
            chatMemory.put(conversationId, messages);
            return processedChatClientRequest;
        }
    
        @Override
        public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
            // 存储历史消息记录
            List<Message> messages = new ArrayList<>();
    
            // Step1:判断是否有响应,若无则直接返回
            if(Objects.isNull(chatClientResponse)){
                return chatClientResponse;
            }
    
            // Step2:有响应,则从上下文中获取会话id------此处模拟从上下文中获取会话id
            String conversationId = "ssg";
            // Step3:通过会话id查询历史对话记录
            List<Message> hisMessages = chatMemory.get(conversationId);
            // Step4:如果存在历史消息,则添加到包含所有Message的列表messages中
            if (Objects.nonNull(hisMessages) && CollUtil.isNotEmpty(hisMessages)) {
                messages.addAll(hisMessages);
            }
    
            // Step5:获取模型响应消息
            AssistantMessage assistantMessage = chatClientResponse.chatResponse()
                    .getResult()
                    .getOutput();
            // Step6:将模型响应消息添加到包含所有Message的列表messages中
            messages.add(assistantMessage);
    
            // Step7:将会话存储的消息更新
            chatMemory.put(conversationId, messages);
            return chatClientResponse;
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
  • Step4: 在表现层controller包下创建类ZhipuAdvisorsController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import at.guigu.advisor.SimpleMessageChatMemoryAdvisor;
    import jakarta.annotation.Resource;
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuAdvisorsController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/simpleMessageChatMemoryAdvisor")
        public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .advisors(new SimpleMessageChatMemoryAdvisor()) // 配置增强链
                    .call() // 模型调用
                    .content(); // 获取文本数据
        }
        /*等同于
        @GetMapping("/simpleMessageChatMemoryAdvisor")
        public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    .advisors(new SimpleMessageChatMemoryAdvisor()) // 配置增强链
                    // 调用大模型
                    .call()
                    .content(); // 获取文本数据
        }
        */
    }
  • Step5: 运行启动类QuickStartApplication

    • Step5-1: 在Apifox中第一次调用,告诉模型我的名字,如图所示

    • Step5-2: 在Apifox中第二次调用,问模型我的名字,运行如图所示可知,模型已实现根据上下文信息回复用户问题

自定义Advisor示例优化

自定义Advisor示例 中,会话id并不是前端传入的,而是在SimpleMessageChatMemoryAdvisor类的方法中利用String conversationId = "ssg";(相当于用字符串随便定了一个会话id)来模拟从上下文中获取会话id。因此,可对其进行优化

注意:

  • Step1: 创建与controller包同级的advisor包,并在该包下创建BaseAdvisor接口的实现类SimpleMessageChatMemoryAdvisor,然后重写beforeaftergetOrder方法,代码如下:

    • 注意:
      • 获取会话id时,需先获取Chat请求(即聊天请求)的上下文容器,然后通过上下文容器在获取当前用户对应的会话id
      • beforeafter方法中可分别利用ChatClientRequestChatClientResponse中的context()方法来获取Chat请求(即聊天请求)的上下文容器(即Map集合Map<String, Object>
    java 复制代码
    package at.guigu.advisor;
    
    import cn.hutool.core.collection.CollUtil;
    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 java.util.*;
    
    public class SimpleMessageChatMemoryAdvisor implements BaseAdvisor {
    
        // 模拟一个内存中的会话存储
        private static Map<String, List<Message>> chatMemory = new HashMap<>();
        @Override
        public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
            // 存储历史消息记录
            List<Message> messages = new ArrayList<>();
    
            // Step1:从上下文中获取会话id
            String conversationId = chatClientRequest.context() // 获取Chat请求(即聊天请求)的上下文容器---即Map集合Map<String, Object>
                    .get("conversationId") // 通过上下文容器获取当前用户对应的会话id
                    .toString();
            // Step2:通过会话id获取历史对话记录(即历史消息)
            List<Message> hisMessages = chatMemory.get(conversationId);
            // Step3:如果存在历史消息,则添加到包含所有Message的列表messages中
            if (Objects.nonNull(hisMessages) && CollUtil.isNotEmpty(hisMessages)) {
                messages.addAll(hisMessages);
            }
    
            // Step4:获取用户当前发送的最新消息
            List<Message> currentNewMessages = chatClientRequest.prompt().getInstructions();
            // Step5:将最新的发送消息添加到包含所有Message的列表messages中
            messages.addAll(currentNewMessages);
    
            // Step6:将包含所有Message的列表messages设置到请求中---及包含所有历史消息及当前用户发送的最新消息
            ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
                    .prompt(chatClientRequest.prompt()
                            .mutate()
                            .messages(messages)
                            .build())
                    .build();
    
            // Step7:将会话存储的消息更新
            chatMemory.put(conversationId, messages);
            return processedChatClientRequest;
        }
    
        @Override
        public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
            // 存储历史消息记录
            List<Message> messages = new ArrayList<>();
    
            // Step1:判断是否有响应,若无则直接返回
            if(Objects.isNull(chatClientResponse)){
                return chatClientResponse;
            }
    
            // Step2:有响应,则从上下文中获取会话id
            String conversationId = chatClientResponse.context() // 获取Chat请求(即聊天请求)的上下文容器---即Map集合Map<String, Object>
                    .get("conversationId") // 通过上下文容器获取当前用户对应的会话id
                    .toString();
            // Step3:通过会话id查询历史对话记录
            List<Message> hisMessages = chatMemory.get(conversationId);
            // Step4:如果存在历史消息,则添加到包含所有Message的列表messages中
            if (Objects.nonNull(hisMessages) && CollUtil.isNotEmpty(hisMessages)) {
                messages.addAll(hisMessages);
            }
    
            // Step5:获取模型响应消息
            AssistantMessage assistantMessage = chatClientResponse.chatResponse()
                    .getResult()
                    .getOutput();
            // Step6:将模型响应消息添加到包含所有Message的列表messages中
            messages.add(assistantMessage);
    
            // Step7:将会话存储的消息更新
            chatMemory.put(conversationId, messages);
            return chatClientResponse;
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
  • Step2: 在表现层controller包下创建类ZhipuAdvisorsController,代码如下:

    • 注意:此时会话id由前端传入,然后利用ChatClientRequestSpec接口中的ChatClientRequestSpec advisors(Consumer<AdvisorSpec> consumer)方法将其存入当前Chat请求(即聊天请求)的上下文容器中,以便于博主自定义的Advisor类SimpleMessageChatMemoryAdvisor能够通过Chat请求(即聊天请求)的上下文容器获取到对应的会话id
    java 复制代码
    package at.guigu.controller;
    
    import at.guigu.advisor.SimpleMessageChatMemoryAdvisor;
    import jakarta.annotation.Resource;
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuAdvisorsController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/simpleMessageChatMemoryAdvisor")
        public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query, @RequestParam(name = "conversationId") String conversationId) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .advisors(advisorSpec -> advisorSpec.param("conversationId",conversationId)) // 将前端传入的当前用户的会话id存入当前Chat请求(即聊天请求)的上下文容器
                    .advisors(new SimpleMessageChatMemoryAdvisor()) // 配置增强链
                    .call() // 模型调用
                    .content(); // 获取文本数据
        }
        /*等同于
        @GetMapping("/simpleMessageChatMemoryAdvisor")
        public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    .advisors(advisorSpec -> advisorSpec.param("conversationId",conversationId)) // 将前端传入的当前用户的会话id存入当前Chat请求(即聊天请求)的上下文容器
                    .advisors(new SimpleMessageChatMemoryAdvisor()) // 配置增强链
                    // 调用大模型
                    .call()
                    .content(); // 获取文本数据
        }
        */
    }
  • Step3: 运行启动类QuickStartApplication

    • Step3-1: 在Apifox中第一次调用,告诉模型我的名字,如图所示

    • Step3-2: 在Apifox中第二次调用,问模型我的名字,运行如图所示可知,模型已实现根据上下文信息回复用户问题

  • 在 上述示例优化 中,使用了ChatClientRequestSpec接口中的两个advisors方法来分别配置当前Chat请求(即聊天请求)的上下文容器以及增强链,advisors方法解释如下:

    ChatClientRequestSpec接口的advisors方法 解释 备注
    ChatClientRequestSpec advisors(Advisor... advisors) 配置增强链
    ChatClientRequestSpec advisors(List<Advisor> advisors) 配置增强链
    ChatClientRequestSpec advisors(Consumer<AdvisorSpec> consumer) 配置增强链及Chat请求(即聊天请求)的上下文容器 可直接利用该方法进行增强链以及上下文容器的配置
    AdvisorSpec接口的四个方法 解释 备注
    AdvisorSpec param(String k, Object v) 配置Chat请求(即聊天请求)的上下文容器
    AdvisorSpec params(Map<String, Object> p) 配置Chat请求(即聊天请求)的上下文容器
    AdvisorSpec advisors(Advisor... advisors) 配置增强链
    AdvisorSpec advisors(List<Advisor> advisors) 配置增强链
    • 因此,优化后的ZhipuAdvisorsController类的代码如下:

      java 复制代码
      package at.guigu.controller;
      
      import at.guigu.advisor.SimpleMessageChatMemoryAdvisor;
      import jakarta.annotation.Resource;
      import lombok.RequiredArgsConstructor;
      import org.springframework.ai.chat.client.ChatClient;
      import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
      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;
      
      /**
       * 本类是对ZhipuAdvisorsController类的进一步优化
       *
       */
      @RestController
      @RequestMapping("zhipuai")
      @RequiredArgsConstructor
      public class ZhipuAdvisorsController2 {
      
          @Resource(name = "defaultChatClient")
          private ChatClient chatClient;
      
          @GetMapping("/simpleMessageChatMemoryAdvisor2")
          public String simpleMessageChatMemoryAdvisor2(@RequestParam(name = "query") String query, @RequestParam(name = "conversationId") String conversationId) {
              // 配置模型参数
              ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                      .maxTokens(15536)
                      .temperature(0.0)
                      .model("glm-4.5")
                      .build();
      
              return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                      .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                      .user(query) // 配置消息message的角色role为user,并设置提示词
                      .options(chatOptions) // 配置模型参数
                      .advisors(advisorSpec -> {
                          advisorSpec.param("conversationId",conversationId); // 将前端传入的当前用户的会话id存入当前Chat请求(即聊天请求)的上下文容器
                          advisorSpec.advisors((new SimpleMessageChatMemoryAdvisor())); // // 配置增强链
                      })
                      .call() // 模型调用
                      .content(); // 获取文本数据
          }
          /*等同于
          @GetMapping("/simpleMessageChatMemoryAdvisor2")
          public String simpleMessageChatMemoryAdvisor2(@RequestParam(name = "query") String query) {
              // 配置messages
              SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
              UserMessage userMessage = new UserMessage(query);
      
              // 配置模型参数
              ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                      .model("glm-4.5")
                      .maxTokens(15536)
                      .temperature(0.0)
                      .build();
      
              Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
              return chatClient.prompt(prompt)
                      .advisors(advisorSpec -> {
                          advisorSpec.param("conversationId",conversationId); // 将前端传入的当前用户的会话id存入当前Chat请求(即聊天请求)的上下文容器
                          advisorSpec.advisors((new SimpleMessageChatMemoryAdvisor())); // 配置增强链
                      })
                      // 调用大模型
                      .call()
                      .content(); // 获取文本数据
          }
          */
      }

BaseAdvisor子接口

  • BaseAdvisor接口的子接口BaseChatMemoryAdvisor是专门做对话记忆(Chat Memory)增强的抽象层。它关心的是历史对话应该以什么形式,被塞进当前请求里。它有两个实现类:

    • PromptChatMemoryAdvisor:把历史对话,从 ChatMemory 中取出来,并拼接成一段文本,塞到Prompt中。特点如下

      1. 历史对话 退化成纯文本上下文
      2. 模型无法从结构上区分 role
      3. 更依赖 prompt 工程技巧
      text 复制代码
      Prompt = """
      以下是历史对话:
      User: 你好
      AI: 你好,有什么可以帮你?
      
      当前问题:
      帮我写 SQL
      """
    • MessageChatMemoryAdvisor(推荐):把历史对话,从 ChatMemory 中取出来,并当成Message注入到当前请求中。特点如下

      1. 最符合 Chat 模型的原生语义
      2. 模型能正确区分 user / assistant
      3. 支持多轮对话、上下文理解最好
      text 复制代码
      历史消息:
      User: 你好
      AI: 你好,有什么可以帮你?
      
      当前请求:
      User: 帮我写 SQL
      
      ↓ 注入后 ↓
      
      messages = [
        User: 你好
        AI: 你好,有什么可以帮你?
        User: 帮我写 SQL
      ]
  • ChatMemory 接口是用来保存历史对话消息,供后续请求在合适时机"回放"到 Chat 请求中。它并不会自动参与推理,需要注入到Advisor中

    • ChatMemory 接口已定义好两个会话id,直接拿来用即可
      • String DEFAULT_CONVERSATION_ID = "default";
      • String CONVERSATION_ID = "chat_memory_conversation_id";
    • ChatMemory 接口有一个实现类MessageWindowChatMemory,可创建该实现类对象并配置到Advisor中
  • ChatMemory 接口及实现类MessageWindowChatMemory的常用方法如下

    • 注意:实际应用场景中,该接口及其实现类的方法可能并不符合真实场景。比如实现类MessageWindowChatMemory中重写的add()方法会调用process()方法(可自行查看源码),进而导致无法存储所有的会话记录。因此,若想要存储完整的对话记录或实现其它功能,则需自行创建ChatMemory 接口的实现类进行相关改造
    ChatMemory 接口 解释 备注
    default void add(String conversationId, Message message) 向指定会话id的Chat请求上下文容器中新增Message
    void add(String conversationId, List<Message> messages) 向指定会话id的Chat请求上下文容器中新增Message
    List<Message> get(String conversationId) 获取指定会话id的Chat请求上下文容器中的Message列表
    void clear(String conversationId) 删除指定会话id的Chat请求上下文容器中的Message 即清空指定会话(conversationId)的所有对话记忆
    MessageWindowChatMemory的构造器及常用方法 解释 备注
    private MessageWindowChatMemory(ChatMemoryRepository chatMemoryRepository, int maxMessages) 有参构造器 参数一:指定消息存储的位置,比如:ES、DB、Redis、内存等 参数二:可存储的最大消息数量
    List<Message> process(List<Message> memoryMessages, List<Message> newMessages) 合并旧记忆与新消息,在保证 SystemMessage 最新且不被裁剪的前提下,按最大消息数窗口淘汰最早的非系统消息
    static Builder builder() 创建 MessageWindowChatMemoryBuilder,以 Builder 模式集中、可读地构造MessageWindowChatMemory实例并配置参数 可近似看作MessageWindowChatMemory的有参构造器,可以设置ChatMemoryRepository以及maxMessages的属性值
    MessageWindowChatMemory的内部静态常量类Builder方法 解释 备注
    Builder chatMemoryRepository(ChatMemoryRepository chatMemoryRepository) 设置ChatMemoryRepository 的属性值 设置指定消息存储的位置,比如:ES、DB、Redis等
    Builder maxMessages(int maxMessages) 设置maxMessages的属性值 设置可存储的最大消息数量
  • ChatMemoryRepository接口用于指定消息存储的位置,可以存储在ES、DB、Redis、内存等位置

    • 默认情况下,消息是存储在内存中。若想存储在ES、DB、Redis等除内存外的位置的话,则可创建ChatMemoryRepository接口的实现类来实现
    ChatMemoryRepository接口方法 解释 备注
    List<String> findConversationIds() 返回当前存储介质中所有已存在的会话id(conversationId)列表,以便上层进行会话枚举、管理或清理操作
    List<Message> findByConversationId(String conversationId) 返回指定会话id所存储的所有消息 返回的是包含所有消息的消息列表
    void saveAll(String conversationId, List<Message> messages) 将指定会话标识下的一组对话消息整体持久化保存,作为该会话当前的完整对话记忆状态。
    void deleteByConversationId(String conversationId) 删除指定会话标识下的全部对话消息,实现会话记忆的清空或重置
    ChatMemoryRepository接口实现类 解释 备注
    InMemoryChatMemoryRepository 代表在内存中存储

官方Advisor示例

案例:让模型根据上下文信息回复用户问题

注意:

  • Step1: 在配置文件中添加代码避免 流式响应 乱码,完整代码如下:

    注意:

    • 由于该示例中自定义的Advisor实现了BaseAdvisor接口,而该接口是CallAdvisor接口和StreamAdvisor接口的子接口,也就是说包含同步调用和流式调用,因此为了避免使用流式调用时导致乱码问题,因此需要提前加上相关配置代码进行预防
    • 在配置文件中配置了 客户端等待服务器响应的最长时间 ,目的是为了方便Debug调试验证该自定义Advisor是否生效,因此可自行根据情况判断是否需要该配置,博主不进行Debug演示,可自行演示
    yaml 复制代码
    server:
      port: 8080
      servlet:
        # 避免流式响应乱码
        encoding:
          charset: UTF-8
          enabled: true
          force: true
    
    spring:
      application:
        name: quick-start
      ai:
        zhipuai:
          # 配置API Key---替换为自己的密钥
          api-key: 3cgrs572f6ef770a89d4187ab6c8cd0ef8004ad.iSxAQRG0eOGDzrvF
          #配置模型地址
          base-url: "https://open.bigmodel.cn/api/paas"
          chat:
            options:
              model: glm-4.5 #配置模型名称及版本
      http:
        client:
          read-timeout: 1000000 # 客户端等待服务器响应的最长时间,超过这个时间会抛出超时异常 单位:ms
    
    logging:
      # 配置指定包及其子包下的所有类的日志级别为debug
      level:
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
  • Step2: 创建一个与表现层controller包同级的config包,并在该包下创建配置类ChatClientConfig,代码如下:

    java 复制代码
    package at.guigu.config;
    
    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.ai.chat.memory.MessageWindowChatMemory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ChatClientConfig {
    
        /**
         * 创建一个默认的ChatClient,使用默认的ChatModel
         * 并且指定使用MessageChatMemoryAdvisor进行对话记忆(Chat Memory)的增强
         */
        @Bean("defaultChatClient")
        public ChatClient defaultChatClient(ChatClient.Builder builder) {
            // Step1:创建ChatMemory对象---使用的是其实现类MessageWindowChatMemory
            ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
    
            // Step2:创建MessageChatMemoryAdvisor对象,并将对话记忆ChatMemory对象注入到Advisor中
            MessageChatMemoryAdvisor messageChatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory).build();
    
            // Step3:在生成的ChatClient的Bean中配置默认增强链
            return builder.defaultAdvisors(messageChatMemoryAdvisor).build();
        }
    }
    • 注意:在代码中并未指定消息存储的位置,则默认存储在内存中,等同于如下代码

      java 复制代码
      package at.guigu.config;
      
      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.ai.chat.memory.InMemoryChatMemoryRepository;
      import org.springframework.ai.chat.memory.MessageWindowChatMemory;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class ChatClientConfig {
      
          /**
           * 创建一个默认的ChatClient,使用默认的ChatModel
           * 并且指定使用MessageChatMemoryAdvisor进行对话记忆(Chat Memory)的增强
           */
          @Bean("defaultChatClient")
          public ChatClient defaultChatClient(ChatClient.Builder builder) {
              // Step1:创建ChatMemory对象---使用的是其实现类MessageWindowChatMemory
              ChatMemory chatMemory = MessageWindowChatMemory.builder()
                      .chatMemoryRepository(new InMemoryChatMemoryRepository()) // 消息存储在内存中
                      .build();
      
              // Step2:创建MessageChatMemoryAdvisor对象,并将对话记忆ChatMemory对象注入到Advisor中
              MessageChatMemoryAdvisor messageChatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory).build();
      
              // Step3:在生成的ChatClient的Bean中配置默认增强链
              return builder.defaultAdvisors(messageChatMemoryAdvisor).build();
          }
      }
  • Step3: 在表现层controller包下创建类ZhipuAdvisorsController,代码如下:

    java 复制代码
    package at.guigu.controller;
    
    import jakarta.annotation.Resource;
    import lombok.RequiredArgsConstructor;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.memory.ChatMemory;
    import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
    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;
    
    @RestController
    @RequestMapping("zhipuai")
    @RequiredArgsConstructor
    public class ZhipuAdvisorsController {
    
        @Resource(name = "defaultChatClient")
        private ChatClient chatClient;
    
        @GetMapping("/simpleMessageChatMemoryAdvisor")
        public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query, @RequestParam(name = "conversationId") String conversationId) {
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .maxTokens(15536)
                    .temperature(0.0)
                    .model("glm-4.5")
                    .build();
    
            return chatClient.prompt() // 数组类型字段`messages`和模型参数共同组成了Prompt,因此可借助prompt来配置模型参数和消息
                    .system("你是一个有用的AI助手。") // 配置消息message的角色role为system,并设置提示词
                    .user(query) // 配置消息message的角色role为user,并设置提示词
                    .options(chatOptions) // 配置模型参数
                    .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,conversationId)) // 将前端传入的当前用户的会话id存入当前Chat请求(即聊天请求)的上下文容器
                    .call() // 模型调用
                    .content(); // 获取文本数据
        }
        /*等同于
        @GetMapping("/simpleMessageChatMemoryAdvisor")
        public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query) {
            // 配置messages
            SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
            UserMessage userMessage = new UserMessage(query);
    
            // 配置模型参数
            ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
                    .model("glm-4.5")
                    .maxTokens(15536)
                    .temperature(0.0)
                    .build();
    
            Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);
            return chatClient.prompt(prompt)
                    .advisors(advisorSpec -> advisorSpec.param("conversationId",conversationId)) // 将前端传入的当前用户的会话id存入当前Chat请求(即聊天请求)的上下文容器
                    // 调用大模型
                    .call()
                    .content(); // 获取文本数据
        }
        */
    }
  • Step4: 运行启动类QuickStartApplication

    • Step4-1: 在Apifox中第一次调用,告诉模型我的名字,如图所示

    • Step4-2: 在Apifox中第二次调用,问模型我的名字,运行如图所示可知,模型已实现根据上下文信息回复用户问题

相关推荐
陌陌6232 小时前
10 天 AI 协作开发实录:一份可复用的 AI 开发流程样例
人工智能·ai开发·vibecoding
张小凡vip2 小时前
数据挖掘(六)--conda安装与使用指南:Miniconda篇
人工智能·数据挖掘·conda
TechubNews2 小时前
BEATOZ区块链专业企业与韩国头部旅游集团MODETOUR从签署MOU迈向网络验证节点合作
大数据·人工智能·区块链
杜子不疼.3 小时前
进程控制(四):自主Shell命令行解释器
linux·c语言·人工智能
编码小哥10 小时前
OpenCV Haar级联分类器:人脸检测入门
人工智能·计算机视觉·目标跟踪
程序员:钧念10 小时前
深度学习与强化学习的区别
人工智能·python·深度学习·算法·transformer·rag
数据与后端架构提升之路11 小时前
TeleTron 源码揭秘:如何用适配器模式“无缝魔改” Megatron-Core?
人工智能·python·适配器模式
Chef_Chen11 小时前
数据科学每日总结--Day44--机器学习
人工智能·机器学习
这张生成的图像能检测吗11 小时前
(论文速读)FR-IQA:面向广义图像质量评价:放松完美参考质量假设
人工智能·计算机视觉·图像增强·图像质量评估指标