【LLM】Spring AI Alibaba的简单使用

news2024/12/27 6:58:43

前提条件

  • SpringBoot版本为3.x以上
  • JDK为17以上
  • 申请api-key,地址:百炼平台

引入依赖

说明:我的springboot版本为3.2.4,spring-ai-alibaba-starter版本为1.0.0-M2.1(对应spring-ai版本为1.0.0-M2),jdk版本为17。

1. pom.xml中引入

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

spring-ai-alibaba 基于 spring-ai 开发,由于 spring-ai 相关依赖包还没有发布到中央仓库,如出现 spring-ai-core 等相关依赖解析问题,请在您项目的 pom.xml 依赖中加入如下仓库配置。

<repositories>
    <repository>
        <id>maven2</id>
        <name>maven2</name>
        <url>https://repo1.maven.org/maven2/</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

并且在maven的setting.xml中做出如下更改:

<mirror>  
    <id>alimaven</id>  
    <name>aliyun maven</name>  
    <url>https://maven.aliyun.com/repository/public</url> 
    <!-- 表示除了spring-milestones、maven2其它都走阿里云镜像  --> 
    <mirrorOf>*,!spring-milestones,!maven2</mirrorOf>  
</mirror>

2. application.yml中引入

spring:
    ai:
      dashscope:
        api-key: 申请的api-key
      chat:
        client:
          enabled: true

3. 代码中引入

    @Resource
    private ChatModel chatModel;

开发示例

1. 简单的对话

@GetMapping("/simple")
public String simpleChat(@RequestBody JSONObject param) {
    // 接收并校验参数
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");
    // 构建chatClient
    ChatClient.Builder builder = ChatClient.builder(chatModel);
    ChatClient chatClient = builder.defaultSystem("你是一个精通Java、Python的程序大佬。").build();
    return chatClient.prompt().user(inputInfo).call().content();
}

2. 流式对话

使用Server Sent Event(SSE)事件返回,对应前端需要处理SSE事件的数据。

@GetMapping("/stream")
public Flux<ServerSentEvent<String>> streamChat(@RequestBody JSONObject param) {
    // 接收并校验参数
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");
    
    return chatModel.stream(new Prompt(inputInfo))
            .map(response -> ServerSentEvent.<String>builder()
                    .data(response.getResult().getOutput().getContent()).build())
            .doOnComplete(() -> log.info("响应完成!"))
            .doOnError(e -> log.error("响应异常:", e));
}

3. 带记忆对话(原生API存储)

使用原生的:MessageChatMemoryAdvisor

private final ChatClient chatClient;
public AIChatController(ChatClient.Builder builder) {
    this.chatClient = builder
            .defaultSystem("你是一个精通Java、Python的程序大佬。")
            .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
            .build();
}

@GetMapping("/memoryStreamWithApi")
public Flux<ServerSentEvent<String>> memoryStreamWithApi(@RequestBody JSONObject param) {
    // 接收并校验参数
    // 对话记忆ID,我是通过前端传参获取,你可以获取一个UUID
    String accessKey = CommonUtil.getAndCheck(param, "accessKey", "您没有改功能访问权限!");
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");

    // 请求数据
    return chatClient.prompt()
            .user(inputInfo)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, accessKey)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 5))    // 从上下文中检索聊天内存响应大小
            .stream()
            .chatResponse()
            .map(response -> {
                return ServerSentEvent.<String>builder()
                        .data(response.getResult().getOutput().getContent())
                        .build();
            })
            .doOnComplete(() -> {
                log.info("响应完成!");
            })
            .doOnError((e) -> {
                log.warn("响应异常:", e);
            });
}

4. 带记忆对话(Redis存储)

使用redis存储:

@GetMapping("/memoryStreamWithRedis")
public Flux<ServerSentEvent<String>> memoryStreamWithRedis(@RequestBody JSONObject param) {
    // 接收并校验参数
    // 对话记忆ID,我是通过前端传参获取,你可以获取一个UUID
    String accessKey = CommonUtil.getAndCheck(param, "accessKey", "您没有改功能访问权限!");
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");

    List<Message> messageList = new ArrayList<>();
    // 拼接redis key
    String redisKey = CommonConst.MY_ACCESS_KEY_PREFIX + accessKey;
    // 判断是否存在聊天记忆,有的话将其带入本次对话中
    if (redisService.hasKey(redisKey)) {
        // 从redis中获取聊天记忆数据
        List<String> strList = redisService.getCacheList(redisKey);
        List<Message> memoryList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(strList)) {
            strList.forEach(s -> memoryList.add(new UserMessage(s)));
            // 反转聊天记忆数据
            Collections.reverse(memoryList);
            messageList.addAll(memoryList);
        }
    }
    messageList.add(new UserMessage(inputInfo));

    StringBuilder resultStr = new StringBuilder();
    return chatModel.stream(new Prompt(messageList))
            .map(response -> {
                resultStr.append(response.getResult().getOutput().getContent());
                return ServerSentEvent.<String>builder()
                        .data(response.getResult().getOutput().getContent())
                        .build();
            })
            .doOnComplete(() -> {
                // 将LLM返回的文本存入redis,存储的数据类型是List,enqueue()方法的定义看下文
                redisService.enqueue(redisKey, resultStr.toString(), MAX_LENGTH);
                log.info("响应完成!");
            })
            .doOnError(e -> log.warn("响应异常:", e));
}

RedisService中部分代码:

/**
 * 添加固定长度的队列,左添加
 *
 * @param key       key
 * @param value     value
 * @param maxLength 最大存储的聊天记录长度
 */
public void enqueue(String key, String value, int maxLength) {
    // 使用Redis的LPUSH命令添加元素,然后确保长度不超过最大值
    redisTemplate.opsForList().leftPush(key, value);
    // 保持队列长度不超过指定长度,LTRIM会自动删除超出长度的旧元素
    if (redisTemplate.opsForList().size(key) > maxLength) {
        redisTemplate.opsForList().trim(key, 0, maxLength - 1);
    }
}

/**
 * 获得缓存的list对象
 *
 * @param key 缓存的键值
 * @return 缓存键值对应的数据
 */
public <T> List<T> getCacheList(final String key) {
    return redisTemplate.opsForList().range(key, 0, -1);
}

参考文档

 官方参考文档:Spring AI Alibaba 官方 、Spring AI 官方、Spring AI API 文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2266237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在一个C工程文件中,如果一个函数要引用另一个文件中的函数,并不是用extern进行声明,而是在包含的头文件中进行声明

在C语言的某个工程中&#xff0c;一个不是主函数的函数如果引用另一个文件中的某个函数&#xff0c;是不是要先用extern引入&#xff1f;比如下面的代码&#xff1a; void DisplayInit(void) {extern void FramebufferInit(void);FramebufferInit(); }在C语言中&#xff0c;不…

Qt天气预报系统设计_更改窗口名称和图标

Qt 天气预报系统 1、更改窗口名称2、更改窗口图标2.1先把资源加入项目2.2选择资源文件更改窗口图标 1、更改窗口名称 更改MainWindow下的windowTitle即可 2、更改窗口图标 2.1先把资源加入项目 右键项目文件夹&#xff0c;选择 Add New... 选择Qt&#xff0c;接着选择 Qt …

数据仓库工具箱—读书笔记02(Kimball维度建模技术概述04、使用一致性维度集成)

Kimball维度建模技术概述 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 第二章前言部分作者提到&#xff1a;技术的介绍应该通过涵盖各种行业的熟悉的用例展开&#xff08;赞同…

Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的

文章目录 一、前言二、主要内容三、小结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中&#xff0c;数组索引从 1 开始是很常见的。例如&#xff0c;Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…

Redis - Token JWT 概念解析及双token实现分布式session存储实战

Token 定义&#xff1a;令牌&#xff0c;访问资源接口&#xff08;API&#xff09;时所需要的资源凭证 一、Access Token 定义&#xff1a;访问资源接口&#xff08;API&#xff09;时所需要的资源凭证&#xff0c;存储在客户端 组成 组成部分说明uid用户唯一的身份标识time…

vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)

1.安装video-player npm install video.js videojs-player/vue --save在main.js中配置全局引入 // 导入视频播放组件 import VueVideoPlayer from videojs-player/vue import video.js/dist/video-js.cssconst app createApp(App) // 视频播放组件 app.use(VueVideoPlayer)2…

数据库MySQL(1)

一、数据库简介 数据库是一类软件&#xff0c;其作用就是更加高效的组织数据&#xff0c;我们要讲的数据库软件是MySQL&#xff0c;MySQL所使用的是sql语句&#xff0c;SQL语句就是专门操作数据库的编程语言 数据库组织形式为&#xff1a;数据库→表→行→列 每一个数据库里…

启动动效流程梳理(二)

performSurfacePlacementNoTrace() 这一段从performSurfacePlacement()开始讲起&#xff0c;因为在trace中可以看到在SystemServer中&#xff0c;动效会从performSurfacePlacement这个tag点触发。这里的流程就是在窗口状态改变之后&#xff0c;会触发performSurfacePlacement流…

js-000000000000

1、js书写的位置 - 内部 <body> <!-- 习惯把 js 放到 /body 的后面 --> <script> console.log(这是内部 js 的书写位置) alert(内部js) </script> </body> <body><!-- 习惯把 js 放到 /body 的后面 --><script>console.log(这…

深度学习工作:从追求 SoTA 到揭示新现象

TLDR&#xff1a;主要讨论了从追求模型 SoTA 到揭示新现象的转变。通过几个例子&#xff0c;包括ACNet到RepVGG的发展&#xff0c;RIFE插帧、Film插帧&#xff0c;以及OpenAI的近期工作&#xff0c;阐述了这种转变的重要性。 知乎&#xff1a;黄哲威 hzwer链接&#xff1a;http…

USB Hub 检测设备

系列文章目录 xHCI 简单分析 USB Root Hub 分析 USB Hub 检测设备 文章目录 系列文章目录一、引言二、hub_eventshub_port_connect_changeusb_alloc_devusb_set_device_statehub_port_initusb_new_device 一、引言 USB Hub 检测设备 一文中讲到&#xff0c;当有 USB 插入时&…

upload-labs关卡记录13

这里和关卡12非常类似&#xff0c;唯一不同就是12关用到get方法&#xff0c;这里用到post方法。因此对应的截断方式也不一样&#xff0c;依旧是使用我们的bp进行抓包&#xff0c; 然后依旧是在upload后加上shell.php&#xff0c;这里用是为了hex时好区别我们要在哪里更改&#…

鸿蒙UI开发——使用WidthTheme实现局部深浅色

1、场景描述 在实际的应用开发中&#xff0c;我们可能需要在界面中局部应用深色或者浅色的界面样式&#xff0c;与全局的深色、亮色同时生效。场景例如&#xff1a;深/亮色预览。此时&#xff0c;我们可以使用WithTheme能力来达到我们的效果。 2、WithTheme WithTheme组件可…

骑砍2霸主MOD开发(26)-Mono脚本系统

一.游戏启动流程 <1.启动器Launcher 运行TaleWorlds.MountAndBlade.Launcher.exe启动C#程序Program,完成MOD列表页面的加载. public class Program {public static void Main(string[] args){#加载启动器GUI界面ResourceDepot resourceDepot new ResourceDepot();resource…

Gmsh有限元网格剖分(Python)---点、直线、平面的移动

Gmsh有限元网格剖分(Python)—点、直线、平面的移动和旋转 最近在学习有限元的网格剖分算法&#xff0c;主要还是要参考老外的开源Gmsh库进行&#xff0c;写一些博客记录下学习过程&#xff0c;方便以后回忆嘞。 Gmsh的官方英文文档可以参考&#xff1a;gmsh.pdf 但咋就说&a…

代码随想录Day39 198.打家劫舍,213.打家劫舍II,337.打家劫舍 III。

1.打家劫舍 力扣题目链接(opens new window) 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统…

01-linux基础命令

一、linux命令概述 命令 选项 参数命令 &#xff1a;就是命令名&#xff1b;选项&#xff1a;一般以 - 开头 比如&#xff1a;-a -p -r 等扩展命令的功能&#xff1b;参数&#xff1a;描述命令的目标&#xff0c;如我们要打开一个文件的话&#xff0c;参数就是要打开的文件名…

MATLAB符号计算-初步认识符号对象

1.1自顶向下的程序设计方法 【例1-1】列主元Guass消去法解方程组 a[2 -3 5 -1;1 4 2 -3;-2 4 -3 -7;8 0 -2 1]; b[3 7 -1 8]; sLZYguass(a,b) 代码还有问题&#xff0c;x11.1913,x21.1157,x30.8114,x40.0923 学长帮忙修改过&#xff1a; 反思&#xff1a; length()、size(…

支持向量机入门指南:从原理到实践

目录 1 支持向量机的基本概念 1.2 数学表达 2 间隔与支持向量 2.1 几何间隔 2.2 支持向量的概念 2.3 规范化超平面 2.4 支持向量的深入分析 2.4.1 支持向量的特征 2.4.2 支持向量的作用 2.4.3 支持向量的代数表示 2.5 KKT条件 3 最优化问题 3.1 问题的形成 3.2 规…

快速汇总Word表格

示例需求&#xff1a;Word文档中的有多个表格&#xff0c;其中最后一个表格为汇总表格&#xff0c;其他的为数据表格&#xff0c;如下图中左侧所示。 现在需要根据Category1和Category2&#xff0c;在数据表格中查找&#xff0c;如果找到匹配行&#xff0c;那么 为数据表中改…