spring ai alibaba

本文章将从源码开始分析spring ai alibaba,带你一步一步了解spring ai alibaba,适合新手来学习,下面是作者使用的版本

-spring-ai-alibaba版本1.0.0-M5.1

-springboot版本3.2.4

-jdk版本17

若要使用jdbc持久化需要以下版本

-mysql-connector-j版本8.1.0

-mybatis-plus版本3.5.7

一、初始化

1、添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.4</version>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud.ai</groupId>
  <artifactId>spring-ai-alibaba-starter</artifactId>
  <version>1.0.0-M5.1</version>
</dependency>

2、配置yml(注意这个是没有提示的)

spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}

注意初始化的ai是阿里云的qwen-plus,故我们需要去阿里云的百炼平台申请它的api-key,当然也可以修改其他平台的ai平台,这个后续会讲

源码如下:

在自动配置类中可以看出来,在需要导入的配置属性文件中反射出了七个类(DashScopeConnectionProperties、DashScopeChatProperties、DashScopeImageProperties、DashScopeSpeechSynthesisProperties、DashScopeAudioTranscriptionProperties、DashScopeEmbeddingProperties、DashScopeRerankProperties)

image-20250609101555821

DashScopeChatProperties配置属性中我们可以清楚的看见使用的是阿里云的qwen-plus

image-20250609102010265

二、Chat Client

1、简介

首先我们讲解一下chat client

我们在阿里云官网中我们可以得到以下的定义:

image-20250609103031952

说白话就是chatClient相当于封装了chat Model 使使用更为简单,但相对应的其的灵活性将大大降低。

其主要功能有:

包括一些基础功能,如:

  • 定制和组装模型的输入(Prompt)
  • 格式化解析模型的输出(Structured Output)
  • 调整模型交互参数(ChatOptions)

还支持更多高级功能:

  • 聊天记忆(Chat Memory)
  • 工具/函数调用(Function Calling)
  • RAG

2、创建Chat Client

使用 ChatClient.Builder 对象创建 ChatClient 实例,您可以自动注入由Spring Boot 自动配置创建的默认 ChatClient.Builder 实例,您也可以通过编程方式自行创建一个 ChatClient.Builder 实例并用它来得到 ChatClient 实例。

下面使用ChatClient.Builder 对象创建ChatClient 实例

@RestController
public class ChatController {
  private final ChatClient chatClient;
  public ChatController(ChatClient.Builder builder) {
    this.chatClient = builder.build();
  }
}

3、创建一个简单的问答系统

创建好chatClient后我们就可以使用这个chatClient进行制作简单的问答系统

@RestController
@RequestMapping("/helloworld")
   public class ChatController {

     private final ChatClient chatClient;

     public ChatController(ChatClient.Builder builder) {
       this.chatClient = builder.build();
     }

     @GetMapping("/chat")
     public String chat(String input) {
         //将用户输入的 input 构造成一个提示。
       return this.chatClient.prompt()
           .user(input) //将用户输入的 input 作为问题添加到对话上下文中;
           .call() // 调用大模型进行推理处理。
           .content(); // 获取模型返回的回答内容。最终将模型的回答结果作为字符串返回给前端。
         // .entity(ActorFilms.class); // 可以使用该 entity 方法轻松地将 AI 模型的输出映射到 ActorFilms 类型
     }
   }

prompt和user区别:prompt通常由开发者或应用系统提供,用户看不到。用来设置对话的基础规则、语境和行为模式,定义 AI 的角色、语调和限制。user由最终用户(如你)输入。用来向模型提问或提供上下文信息,引导模型生成具体的回答。

当我们访问http:localhost:8080/helloworld/chat?input="讲个笑话?"就可以调用ai进行回复

以下是ai的回复:

image-20250609105951270

3、进阶使用(此模块需要有一点源码知识)

但是我们发现这个对话系统只能回答我们当前问的问题,不会记忆以前的问题,那么我们怎么去设置呢?

出现这个问题我们就应该去查看源码看看如何去设置。

下面是我截出的设置的源码并对其附上了解释

// 设置对应的Advisor,我们可以在此设置对原功能增强功能,当然我们可以设计自己的Advisor
Builder defaultAdvisors(Advisor... advisor);
Builder defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer);
Builder defaultAdvisors(List<Advisor> advisors);
// 此方法通常用于设置默认的聊天选项,供构建器(Builder)模式使用,以便在创建对象时预设一些配置。
Builder defaultOptions(ChatOptions chatOptions);
// 该方法通常用于构建者模式中,设置默认用户信息
Builder defaultUser(String text);
Builder defaultUser(Resource text, Charset charset);
Builder defaultUser(Resource text);
Builder defaultUser(Consumer<PromptUserSpec> userSpecConsumer);
// 通常用于构建器模式中,允许链式调用设置默认系统信息。
Builder defaultSystem(String text);
Builder defaultSystem(Resource text, Charset charset);
Builder defaultSystem(Resource text);
Builder defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer);
(1)defaultAdvisors

此方法可以在此设置对原功能进行增强

那么系统自带的Advisor有哪些呢?通过继承关系可以看出来继承的Advisor有两类接口分别是正常的传输或流式传输。其下有其的实现方法。下面我调一些重点的来讲解一下!!

image-20250610083622694

AbstractChatMemoryAdvisor // 实现记忆存储功能
BaseAdvisor // 用于在调用前后处理请求和响应。
DashScopeDocumentRetrievalAdvisor  // 基于检索文档的问答增强功能,主要作用是在大模型调用前后插入文档检索与引用处理逻辑。
DocumentRetrievalAdvisor // 是一个增强处理器,用于在调用模型前根据用户输入检索文档,并将文档内容注入到提示词中。
QuestionAnswerAdvisor // 问答顾问组件,主要用于在调用模型前通过向量检索补充上下文信息,再将增强后的上下文用于生成回答。
RequestResponseAdvisor // 已弃用
RetrievalRerankAdvisor // 检索-重排序的拦截器,用于增强问答系统的上下文信息。
SafeGuardAdvisor // 用于在用户输入中检测敏感词,若存在敏感词,则返回预定义的失败响应,避免直接处理敏感内容
SimpleLoggerAdvisor // 是一个用于记录 ChatClient 的 request 和 response 数据 Advisor,这对于调试和监控您的 AI 交互非常有用

有人发现DashScopeDocumentRetrievalAdvisor和DocumentRetrievalAdvisor类似那么这两个有什么区别呢?

  1. 功能复杂度与用途
特性 DocumentRetrievalAdvisor DashScopeDocumentRetrievalAdvisor
用途 基础文档检索增强器,适用于通用问答场景 支持高级引用格式的问答增强器,专为 DashScope 等大模型平台设计
提示词模板 提供简单提示模板,直接拼接检索内容到用户输入中 提供结构化提示模板,支持引用编号(如 [1])和推理步骤
响应后处理 仅将检索结果附加到元数据中 支持提取回复中的引用标记(如 [1]),并关联原始文档
  1. 对引用的支持
特性 DocumentRetrievalAdvisor DashScopeDocumentRetrievalAdvisor
是否支持引用标注 ❌ 不支持引用标注机制 ✅ 支持引用标注(通过正则匹配 [1])
是否自动构建引用文档映射 ❌ 仅保存原始文档列表 ✅ 构建索引 ID 到文档的映射表(便于后续引用)
是否启用引用过滤 ❌ 不涉及引用判断逻辑 ✅ 可配置 enableReference 控制是否返回引用文档

总结

  • 如果你只需要基础的文档检索和上下文注入,选择 DocumentRetrievalAdvisor;
  • 如果你需要支持引用标注、结构化提示词、引用文档映射与返回等功能,尤其是对接 DashScope 平台,应选择 DashScopeDocumentRetrievalAdvisor。
  • 两个类都继承自 BaseAdvisor,共享基本的拦截逻辑,但在具体实现上针对不同需求进行了扩展和优化。
AbstractChatMemoryAdvisor讲解

这个接口实现有以下三种方式

  • MessageChatMemoryAdvisor:内存被检索并作为消息集合添加到提示中
  • PromptChatMemoryAdvisor:检索内存并将其添加到提示的系统文本中。
  • VectorStoreChatMemoryAdvisor :构造函数VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize)允许您指定要从中检索聊天历史记录的 VectorStore、唯一的对话 ID、要检索的聊天历史记录的大小(以令牌大小为单位)

我们使用MessageChatMemoryAdvisor来讲解:

根据此方法的构造函数可知若要实现此类需要ChatMemory参数,那么ChatMemory是什么呢?

public MessageChatMemoryAdvisor(ChatMemory chatMemory) {
    super(chatMemory);
}

ChatMemory 接口表示聊天对话历史记录的存储,它提供向对话添加消息、从对话中检索消息以及清除对话历史记录的方法。

目前提供两种实现方式 InMemoryChatMemory(基于内存存储)CassandraChatMemory(在Cassandra中带有过期时间的持久化存储。)(在1.0.0-M5.1中可以自动配置)、JdbcChatMemory(在JDBC中没有过期时间限制的持久化存储) Neo4jChatMemory(在Neo4j中没有过期时间限制的持久化存储),分别为聊天对话历史记录提供内存存储和 time-to-live 类型的持久存储。

CassandraChatMemory的改变

image-20250610103341174

image-20250610103246679

CassandraChatMemory的实现

由源码可知若要使用这个方法则必须导入cassandraChatMemory

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-cassandra-store</artifactId>
    <version>1.0.0-M5</version>
</dependency>
public HelloworldController(ChatClient.Builder chatClientBuilder) {
    this.dashScopeChatClient = chatClientBuilder
            .defaultSystem(DEFAULT_PROMPT)
            // 实现 Chat Memory 的 Advisor
            // 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
            .defaultAdvisors(
                    new MessageChatMemoryAdvisor(new CassandraChatMemory(CassandraChatMemoryConfig.builder().build()))
            .build();
}

InMemoryChatMemory实现方法

public HelloworldController(ChatClient.Builder chatClientBuilder) {
    this.dashScopeChatClient = chatClientBuilder
            .defaultSystem(DEFAULT_PROMPT)
            // 实现 Chat Memory 的 Advisor
            // 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
            .defaultAdvisors(
                    new MessageChatMemoryAdvisor(new InMemoryChatMemory())
            )
            .build();
}

JdbcChatMemory 的实现

如果要将对话持久化到数据库中,就可以使用JdbcChatMemory。但是spring-ai-starter-model-jdbc依赖模板版本很少,而且缺乏相关介绍,Maven官方仓库还搜不到依赖,所以不推荐使用。在Spring仓库能搜到,但是用的人太少了,并且会遇到版本兼容问题,故我们决定自己创建一个在数据库存储的功能。

导入坐标

<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
    <version>1.0.0-M5.1</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.1.0</version>
 </dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.7</version>
</dependency>

导入数据结构

CREATE TABLE ai_chat_memory (
    id              BIGINT AUTO_INCREMENT PRIMARY KEY,
    conversation_id VARCHAR(255) NOT NULL comment '会话id',
    type            VARCHAR(20)  NOT NULL comment '消息类型',
    content         TEXT         NOT NULL comment '消息内容',
    create_time      TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
    update_time      TIMESTAMP default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    is_delete        tinyint  default 0                 not null comment '是否删除',
    INDEX idx_conv (conversation_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建数据实体类

@Data
@TableName("ai_chat_memory")
public class AIPersistenceEntity implements Serializable {
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 会话id
     */
    @TableField("conversation_id")
    private String conversationId;

    /**
     * 消息类型
     */
    @TableField("type")
    private String type;

    /**
     * 消息内容
     */
    @TableField("content")
    private String content;

    /**
     * 创建时间
     */
    @TableField("create_time")
    private Date createTime;

    /**
     * 更新时间
     */
    @TableField("update_time")
    private Date updateTime;

    /**
     * 是否删除
     */
    @TableLogic
    @TableField("is_delete")
    private Integer isDelete;
}

创建映射接口并继承基础映射

@Mapper
public interface AIPersistenceDao extends BaseMapper<AIPersistenceEntity> {
}

创建实体类

@Component
public class MyBatisPlusChatMemory implements ChatMemory {

    @Autowired
    private AIPersistenceDao aiPersistenceDao;

    @Override
    public void add(String conversationId, Message message) {
        AIPersistenceEntity aiChatMemory = new AIPersistenceEntity();
        aiChatMemory.setConversationId(conversationId);
        aiChatMemory.setType(message.getMessageType().getValue());
        aiChatMemory.setContent(message.getText());
        aiPersistenceDao.insert(aiChatMemory);
    }

    @Override
    public void add(String conversationId, List<Message> messages) {
        List<AIPersistenceEntity> aiChatMemories = new ArrayList<>();
        for (Message message : messages) {
            AIPersistenceEntity aiChatMemory = new AIPersistenceEntity();
            aiChatMemory.setConversationId(conversationId);
            aiChatMemory.setType(message.getMessageType().getValue());
            aiChatMemory.setContent(message.getText());
            aiChatMemories.add(aiChatMemory);
        }

        aiPersistenceDao.insert(aiChatMemories);
    }

    @Override
    public List<Message> get(String conversationId, int lastN) {
        Page<AIPersistenceEntity> page = new Page<>(1, lastN);
        QueryWrapper<AIPersistenceEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("conversation_id", conversationId)
                .orderByDesc("create_time");
        List<AIPersistenceEntity> aiChatMemories = aiPersistenceDao.selectList(wrapper);
        Collections.reverse(aiChatMemories);
        List<Message> messages = new ArrayList<>();
        for (AIPersistenceEntity aiChatMemory : aiChatMemories) {
            String type = aiChatMemory.getType();
            switch (type) {
                case "user" -> messages.add(new UserMessage(aiChatMemory.getContent()));
                case "assistant" -> messages.add(new AssistantMessage(aiChatMemory.getContent()));
                case "system" -> messages.add(new SystemMessage(aiChatMemory.getContent()));
                default -> throw new IllegalArgumentException("Unknown message type: " + type);
            }
        }
        return messages;
    }

    @Override
    public void clear(String conversationId) {
        QueryWrapper<AIPersistenceEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("conversation_id", conversationId);
        aiPersistenceDao.delete(wrapper);
    }
}
SimpleLoggerAdvisor讲解

SimpleLoggerAdvisor 是一个用于记录 ChatClient 的 requestresponse 数据 Advisor,这对于调试和监控您的 AI 交互非常有用。

要启用日志记录,请在创建 ChatClient 时将 SimpleLoggerAdvisor 添加到 Advisor 链中。建议将其添加到链的末尾:

    ChatResponse response = ChatClient.create(chatModel).prompt()
            .advisors(new SimpleLoggerAdvisor())
            .user("Tell me a joke?")
            .call()
            .chatResponse();
org.springframework.ai.chat.client.advisor=DEBUG

将其添加到您的 application.propertiesapplication.yaml 文件中。

您可以使用以下构造函数自定义如何使用 SimpleLoggerAdvisor 记录来自 AdvisedRequest 和 ChatResponse 的数据:

    SimpleLoggerAdvisor(
        Function<AdvisedRequest, String> requestToString,
        Function<ChatResponse, String> responseToString
    )

例如:

javaCopySimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
    request -> "Custom request: " + request.userText,
    response -> "Custom response: " + response.getResult()
);
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐