随着AI浪潮的推进,越来越多的传统项目融入AI服务已是一种趋势。若依作为目前最好用的后台管理框架之一,SpringAIAlibaba对Qwen系列模型的完美集成,如何在若依框架中集成AI服务来实现一个聊天机器人呢?

        最近在写毕设用到了若依并集成了Qwen实现了一些AI服务业务,写这篇文章就是作为一个技术分享,为一些对于SpringAI不怎么熟悉的同学提供帮助:

1.环境要求

        SpringAI要求JDK版本最低17,SpringBoot版本在3以上,因此选择的若依框架需要是SpringBoot3 jdk17+的:
https://gitee.com/y_project/RuoYi-Vue/tree/springboot3

2.阿里百炼获取API

前往阿里云百炼平台获取API Key,复制生成的API Key。

3.后端配置

3.1 在父POM中声明依赖

<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>

<!-- Spring AI ALIBABA的依赖配置-->
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-agent-framework</artifactId>
                <version>${spring-ai-alibaba.version}</version>
            </dependency>

3.2 配置application.yml

首先,要在admin模块中引入依赖:

其中memory-jdbc依赖是MySQL的记忆存储

<!-- spring ai alibaba-->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId>
        </dependency>

其次,在yml中配置ai:

model是你选择的模型名称,${AI_DASHSCOPE_API_KEY}是你的APIKEY,通过idea的配置注入更安全

# spring ai alibaba
spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen3.5-plus

4.接口配置

基础的配置完成了,下面就来进行代码的书写了,在你写业务代码的模块中或者若依其他模块中,这里我用我自己的业务模块:

3.1 pom中引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hl</groupId>
        <artifactId>QwenDishRecomSys</artifactId>
        <version>3.9.1</version>
    </parent>

    <artifactId>QwenDishRecomSys-service</artifactId>

    <description>
        service业务模块
    </description>

    <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>com.hl</groupId>
            <artifactId>QwenDishRecomSys-framework</artifactId>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- spring ai alibaba-->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId>
        </dependency>

    </dependencies>


</project>

3.2 config配置chatclient

        这里我们配置chatclient,chatclient已经封装好了大模型交流的一切复杂api,我们只需简单调用即可。

        配置chatclient如下,添加日志拦截器和模型记忆存储

        MySQL的记忆存储不需要我们像springai那样去手动创建存储表,运行之后springaialibaba会自动帮助我们创建。

package com.hl.service.config;

import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.hl.service.tools.FeedbackTool;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfig {

    @Resource
    private MessageWindowChatMemory messageWindowChatMemory;


    @Bean
    public ChatClient client() {
        // 初始化 ChatModel
        DashScopeApi dashScopeApi = DashScopeApi.builder()
                .apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
                .build();

        ChatModel chatModel = DashScopeChatModel.builder()
                .dashScopeApi(dashScopeApi)
                .build();
        // 初始化日志拦截器
        SimpleLoggerAdvisor simpleLoggerAdvisor = new SimpleLoggerAdvisor(
                request -> "问题: " + request.prompt().getUserMessage().getText(),
                response -> "回答: " + response.getResult().getOutput().getText(),
                0
        );
        ChatClient client = ChatClient.builder(chatModel)
                .defaultAdvisors(
                        simpleLoggerAdvisor,
                        MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build()
                )
                .build();
        return client;
    }
}

        MySQL的记忆配置如下,也可用Redis的:

package com.hl.service.config;

import com.alibaba.cloud.ai.memory.jdbc.MysqlChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
public class MysqlMemoryConfig {

    @Bean
    public MysqlChatMemoryRepository mysqlChatMemoryRepository(JdbcTemplate jdbcTemplate) {
        return MysqlChatMemoryRepository.mysqlBuilder()
                .jdbcTemplate(jdbcTemplate)
                .build();
    }

    @Bean
    public MessageWindowChatMemory messageWindowChatMemory(MysqlChatMemoryRepository mysqlChatMemoryRepository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(mysqlChatMemoryRepository)
                .maxMessages(10)
                .build();
    }
}

3.3 调用接口与模型交流

        接口我们所必须得参数:quention(内容)和conversationId(模型实现记忆的必须参数)

    @Resource
    private ChatClient chatClient;
    @Resource
    private QwenAIChatMapper qwenAIChatMapper;

    @Override
    public String Chat(QwenAIChat qwenAIChat) {

        // 大模型交流
        String answer = chatClient.prompt(qwenAIChat.getQuestion())
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, qwenAIChat.getConversationId()))
                .call().content();

        return answer;
    }

3.4 前端调用接口实现交互

        首先需要在idea的环境配置中配置,运行后端

AI_DASHSCOPE_API_KEY=你的apikey

        前端配置的api接口如下,因为大模型的响应不像一些普通的接口那样很快,因此我们转为为大模型接口复制一个若依的request,只修改它的timeout,否则的话大模型的一些响应会返回timeout。

import config from '@/config'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { useUserStore } from '@/store/modules/user'
import { toast, showConfirm, tansParams } from '@/utils/common'

let timeout = 200000
const baseUrl = config.baseUrl

const request = config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  config.header = config.header || {}
  if (getToken() && !isToken) {
    config.header['Authorization'] = 'Bearer ' + getToken()
  }
  // get请求映射params参数
  if (config.params) {
    let url = config.url + '?' + tansParams(config.params)
    url = url.slice(0, -1)
    config.url = url
  }
  return new Promise((resolve, reject) => {
    uni.request({
        method: config.method || 'get',
        timeout: config.timeout ||  timeout,
        url: config.baseUrl || baseUrl + config.url,
        data: config.data,
        header: config.header,
        dataType: 'json'
      }).then(response => {
        const res = response
        const code = res.data.code || 200
        const msg = errorCode[code] || res.data.msg || errorCode['default']
        if (code === 401) {
          showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
            if (res.confirm) {
              useUserStore().logOut().then(res => {
                uni.reLaunch({ url: '/pages/login' })
              })
            }
          })
          reject('无效的会话,或者会话已过期,请重新登录。')
        } else if (code === 500) {
          toast(msg)
          reject('500')
        } else if (code !== 200) {
          toast(msg)
          reject(code)
        }
        resolve(res.data)
      })
      .catch(error => {
        let { message } = error
        if (message === 'Network Error') {
          message = '后端接口连接异常'
        } else if (message && message.includes('timeout')) {
          message = '系统接口请求超时'
        } else if (message &&message.includes('Request failed with status code')) {
          message = '系统接口' + message.slice(-3) + '异常'
        }
        toast(message)
        reject(error)
      })
  })
}

export function smartCommendChat(data) {
  return request({
    url: '/service/ai/chat',
    method: 'get',
    data: data
  })
}

export function feedback(data) {
  return request({
    url: '/service/ai/feedback',
    method: 'post',
    data: data
  })
}

export function getChatDetail(conversationId) {
  return request({
    url: '/service/ai/chatDetail/' + conversationId,
    method: 'get',
  })
}

        写完前端页面,我们进行交互:

5.流式输出

        大模型最常见的输出模式是流式输出,但是搭配若依框架,流式输出会报错Access Denied,原因是异步请求没有被正确放行,导致 Security 拦截了流式响应。

        我们需要在 Spring Security 的 SecurityFilterChain 配置中,允许异步请求访问:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            // 放行异步请求,避免 SSE 被误拦截
            .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
            .anyRequest().authenticated()
        );
    return http.build();
}

5.1 流式输出小技巧

        响应前端进行流式输出除了后端配置Flux<String>,我们还可以后端响应String,前端接收后进行流式输出模拟来达到一样的效果:

// 模拟流式输出
        let index = 0;
        const interval = setInterval(() => {
          if (index < fullAnswer.length) {
            this.messages[assistantMessageIndex].content += fullAnswer.charAt(index);
            index++;
          } else {
            clearInterval(interval);
          }
        }, 50);

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐