Spring AI 开发专属于你的AI聊天机器人

举报
菜菜的后端私房菜 发表于 2024/11/13 09:15:52 2024/11/13
【摘要】 Spring AI 开发专属于你的AI聊天机器人 前言随着人工智能技术的飞速发展,聊天机器人在现代商业中的应用越来越广泛聊天机器人不仅提高了效率,还改善了用户体验,它们可以24/7不间断地为客户提供服务,解答常见问题本文将介绍如何设计并实现一个AI聊天机器人,该机器人能够理解用户的文本输入并给出相应的回答通过结合Spring Boot、Spring AI等技术,我们将构建一个AI聊天机器人...

Spring AI 开发专属于你的AI聊天机器人

前言

随着人工智能技术的飞速发展,聊天机器人在现代商业中的应用越来越广泛

聊天机器人不仅提高了效率,还改善了用户体验,它们可以24/7不间断地为客户提供服务,解答常见问题

本文将介绍如何设计并实现一个AI聊天机器人,该机器人能够理解用户的文本输入并给出相应的回答

通过结合Spring Boot、Spring AI等技术,我们将构建一个AI聊天机器人,适用于各种对话场景

最终效果演示如下:

演示效果

技术选型与设计

技术选型方面,我们选择Spring Boot自动装配简化开发,Spring AI定义模型的抽象,具体实现采用通义qwen系列大模型

Spring Boot:自动装配简化开发

Spring AI:定义文本、图片、音频等模型的抽象,具体实现由各大厂商接入实现,我们只需要调用顶层API进行开发

通义大模型:阿里qwen系列模型提供Spring AI接口的具体实现

对应版本:

JDK:17及以上

Spring Boot:3.2.4

Spring AI:【spring-ai-core 1.0.0-M1】

通义大模型:【spring-cloud-starter-alibaba-ai 2023.0.1.2】

环境搭建与开发

创建项目

创建Spring Boot项目,配置Maven pom.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.caicaijava</groupId>
    <artifactId>SpringBoot-AI</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot-AI</name>
    <description>SpringBoot-AI</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-ai</artifactId>
            <version>2023.0.1.2</version>
        </dependency>
    </dependencies>

    <!--  通义依赖的spring ai版本未发布maven中央仓库 要配置仓库才能引入  -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

开发接口

创建Controller

@RestController
@RequestMapping("/ai")
@CrossOrigin
public class AIController {

    @Autowired
    private AIService aiService;

    @GetMapping("/msg")
    public String sendMessage(String message) {
        return aiService.chat(message);
    }
}

创建AIService接口,可以定义其他模型相关功能,比如图片、音频等

public interface AIService {

    /**
     * 聊天
     * @param msg
     * @return
     */
    String chat(String msg);
}

编写具体实现:其中TongYiChatModel为通义实现,由自动装配类放入容器

Prompt为Spring AI定义的模型请求,其中包含请求消息列表(上下文),以及模型中通用的请求选项参数,比如用于控制模型生成文本的多样性的top_p、temperature

@Component
public class AIServiceImpl implements AIService{

    private final TongYiChatModel tongYiChatModel;

    @Autowired
    public AIServiceImpl(TongYiChatModel TongYiChatClient) {
        this.tongYiChatModel = TongYiChatClient;
    }

    @Override
    public String chat(String msg) {
        //请求
        Prompt prompt = new Prompt(new UserMessage(msg));
        //获取响应
        return tongYiChatModel.call(prompt).getResult().getOutput().getContent();
    }
}

ChatModel文本聊天接口的call方法具体实现,如请求、响应的处理,与大模型平台网络通信的实现细节,通义已经进行实现,我们只需要调用即可

在访问大模型平台时,通常还需要携带密钥,如果没有密钥可以去百炼平台申请密钥

申请密钥

在application.yml配置文件中进行配置密钥

spring:
  application:
    name: springboot-ai

  cloud:
    ai:
      tongyi:
        connection:
          api-key: 通义密钥 可以去申请

前端

前端界面采用通义示例中的前端界面进行微调

创建index.html文件放入resources/static目录中,关键代码如下:

html如下:

<div class="container">
    <h1>AI聊天机器人</h1>
    <form id="form">
        <label for="message">用户发言:</label>
        <input type="text" id="message" name="message" placeholder="请输入您要提问的内容~">
        <br>
        <br>
        <input type="submit" value="发送">
    </form>
    <br>
    <div id="loader" class="loader" style="display: none;"></div>
    <div id="chat-box" class="chat-box"></div>
</div>

js如下:

<script>
    var loader = document.getElementById("loader");

    document.getElementById("form").addEventListener("submit", function(event) {
        event.preventDefault();

        var messageInput = document.getElementById("message");
        var message = messageInput.value;
        messageInput.value = "";

        var chatBox = document.getElementById("chat-box");

        var userMessage = document.createElement("div");
        userMessage.className = "message user-message";
        userMessage.textContent = "用户提问:" + message;
        chatBox.appendChild(userMessage);
        chatBox.scrollTop = chatBox.scrollHeight;

        loader.style.display = "block";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", "http://localhost:8080/ai/msg?message=" + encodeURIComponent(message), true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                loader.style.display = "none";

                if (xhr.status === 200) {
                    var response = xhr.responseText;

                    var botMessage = document.createElement("div");
                    botMessage.className = "message bot-message";

                    var botMessageText = document.createElement("span");
                    botMessageText.className = "message-text";
                    botMessage.appendChild(botMessageText);
                    botMessageText.innerHTML = marked.marked(response);

                    chatBox.appendChild(botMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                } else if (xhr.status === 400) {
                    var error = JSON.parse(xhr.responseText);

                    var errorMessage = document.createElement("div");
                    errorMessage.className = "message bot-message";
                    errorMessage.textContent = "Bot: " + error.message;
                    chatBox.appendChild(errorMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                } else {
                    var errorMessage = document.createElement("div");
                    errorMessage.className = "message bot-message";
                    errorMessage.textContent = "Bot: Failed to connect to the backend service. Please make sure the backend service is running.";
                    chatBox.appendChild(errorMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                }
            }
        };

        xhr.onloadstart = function() {
            loader.style.display = "block";
        };

        xhr.onloadend = function() {
            loader.style.display = "none";
        };

        xhr.send();
    });
</script>

启动项目,访问localhost:8080,即可开始与聊天机器人对话~

源码分析与探究

各个大模型厂商定义的协议规则不同,因此对应的具体实现也是不同的,这里以通义文档为例:

采用HTTP进行请求,请求头携带密钥用于校验,其中请求体格式为JSON:

#请求路径
curl --location "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" \
#请求头携带密钥 用于校验
--header "Authorization: Bearer $DASHSCOPE_API_KEY" \
#请求头标识数据格式为JSON
--header "Content-Type: application/json" \
#请求体
--data '{
    "model": "qwen-plus",
    "input":{
        "messages":[      
            {
                "role": "system",
                "content": "You are a helpful assistant."
            },
            {
                "role": "user",
                "content": "你是谁?"
            }
        ]
    },
    "parameters": {
        "result_format": "message"
    }
}'

请求体

model为要使用的大模型

input为输入相关,messages为消息列表,可存上下文,其中具体消息会区分角色:

system表示系统角色设定,可以设定大模型平台为一位聊天达人

user表示提问的角色,assistant表示大模型的回复内容

它们组合在一起成为messages列表,能够让模型理解上下文的意思,便于给出满意的回复

parameters为选项参数,比如result_format规定响应格式为消息,还可以规定响应格式为json对象

{
  "model": "qwen-plus",
  "input": {
    "messages": [
      {
        "role": "system",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "你是谁?"
      }
    ]
  },
  "parameters": {
    "result_format": "message"
  }
}

响应

从响应JSON可以看出通常只关心它回复的内容

因此API通过 result.getOutput().getChoices().get(0).getMessage().getContent() 获取回复

{
  "status_code": 200,
  "request_id": "902fee3b-f7f0-9a8c-96a1-6b4ea25af114",
  "code": "",
  "message": "",
  "output": {
    "text": null,
    "finish_reason": null,
    "choices": [
      {
        "finish_reason": "stop",
        "message": {
          "role": "assistant",
          "content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
        }
      }
    ]
  },
  "usage": {
    "input_tokens": 22,
    "output_tokens": 17,
    "total_tokens": 39
  }
}

其他具体的参数说明还是参考文档

接下来,查看下它的具体实现:

public ChatResponse call(Prompt prompt) {
	
    //封装通义聊天文本请求
    ConversationParam params = toTongYiChatParams(prompt);

    // TongYi models context loader.
    //通义模型 上下文加载
    com.alibaba.dashscope.common.Message message = new com.alibaba.dashscope.common.Message();
    message.setRole(Role.USER.getValue());
    message.setContent(prompt.getContents());
    //将请求加入上下文msgManager
    msgManager.add(message);
    params.setMessages(msgManager.get());

    logger.trace("TongYi ConversationOptions: {}", params);
    //请求调用
    GenerationResult chatCompletions = this.callWithFunctionSupport(params);
    logger.trace("TongYi ConversationOptions: {}", params);

    //将回复加入上下文msgManager
    msgManager.add(chatCompletions);

    //处理结果
    List<org.springframework.ai.chat.model.Generation> generations =
          chatCompletions
                .getOutput()
                .getChoices()
                .stream()
                .map(choice ->
                      new org.springframework.ai.chat.model.Generation(
                            choice
                                  .getMessage()
                                  .getContent()
                      ).withGenerationMetadata(generateChoiceMetadata(choice)
                      ))
                .toList();

    return new ChatResponse(generations);

}

上面的代码看似没问题,但实际上这个上下文管理器msgManager没有处理隔离性问题,当多个用户进行提问时记录的上下文会导致混乱

你可能觉得隔离性应该由我们手动来进行隔离,但即时手动进行隔离,只要调用该方法还是会被加入上下文,从而产生隔离性问题的bug

但是好在下一个版本2023.0.1.3移除上下文管理器,这样我们就可以手动进行隔离,将上下文填充到入参Prompt中即可

并且该案例中我们使用的是同步调用,当可能返回大量回复时同步等待的时间可能会很长,导致用户体验差,可以采用流式调用,将回复分为多个响应,依次进行返回

总结

本篇文章通过Spring Boot、Spring AI、Alibaba-AI等技术实现AI聊天机器人

Spring AI在定义文本、图像、音视频等模型顶层接口以及通用请求、响应,具体实现由各个大模型厂商来实现

在案例中,我们使用通义大模型来实现文本模型,并发现其版本隔离性问题的一个“bug”

本篇文章只是简单入门,其他额外的功能与玩法,各位同学都可以查看文档进行深入探索

最后(点赞、收藏、关注求求啦~)

我是菜菜,热爱技术交流、分享与写作,喜欢图文并茂、通俗易懂的输出知识,掘金优秀创作者、腾讯云内容共创官、阿里云专家博主、华为云云享专家…

关注菜菜,分享更多技术干货,公众号:菜菜的后端私房菜

本篇文章被收入专栏 Java常用框架,感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 Gitee-CaiCaiJavaGithub-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的同学可以starred持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。