在springboot项目中调用通义千问api多轮对话并实现流式输出

官网文档

阿里灵积提供了详细的官方文档

如何实现多轮对话

官方文档中提到只需要把每轮对话中返回结果添加到消息管理器中,就可以实现多轮对话。本质上就是将历史对话再次发送给接口。

如何实现流式输出

官方文档中提出使用streamCall()方法就可以实现流式输出,在ResultCallback<GenerationResult>参数中可以指点每个事件的处理动作。

流式调用方法没有返回GenerationResult结果类,如何实现多轮对话

方法一

我们每次调用完成后把得到的结果手动构建消息对象并加入消息管理类。

不知道是不是我使用的sdk版本问题(因为老的版本有出现调用okhttp报错的情况,我的在阿里云提交工单后,工作人员给我的最新版本是2.10.1,我当前就在使用这个版本)。经过实际测试,msgManager.get()方法可能会出现第一条对话的发送对象是assistant的情况。

如果第一条对话的发送对象不是user或者system,并且user和assistant没有在历史对话中轮流出现接口会报错的!!!!(我没有报错的截图,哈哈哈哈)

Message assistantMsg = Message.builder().role(Role.ASSISTANT.getValue()).content("如何做西红柿炖牛腩?").build();
msgManager.add(assistantMsg);

方法二

我们自己来控制历史对话

@Component
public class QwenModelService{

    private Generation gen;

    @Resource
    private AiWebsocketService aiWebsocketService;

    public void createGen(){
        gen = new Generation();
    };

    private static final Logger logger = LoggerFactory.getLogger(QwenModelService.class);

    /**
     * prompt 用户对话
     * request 用户请求对象
     * identity 用户身份标识
     */
    public String answer(String prompt, HttpServletRequest request, String identity) {

        // 通过身份标识在缓存中获取对话对象、历史消息对象、参数对象
        List<AiDialogue> dialogues = CachePool.AI_DIALOGUE_LIST_MAP.get(identity)
                .computeIfAbsent(ConstValuePool.QWEN_DIALOGUES, k -> new LinkedList<>());
        dialogues.add(AiDialogue.createUserDialogue(prompt));
        List<Message> msgManager = CachePool.QWEN_MESSAGE_DIALOGUES_MAP.get(identity);
        QwenParam param = CachePool.QWEN_PARAM_MAP.get(identity);

        // 如果第一次发送消息需要初始化历史消息对象
        if (msgManager == null) {
            msgManager = new ArrayList<>();
            CachePool.QWEN_MESSAGE_DIALOGUES_MAP.put(identity, msgManager);
            Message systemMsg = Message.builder()
                    .role(Role.SYSTEM.getValue())
                    .content("You are a helpful assistant.")
                    .build();
            msgManager.add(systemMsg);
            Message userMsg = Message.builder()
                    .role(Role.USER.getValue())
                    .content(prompt)
                    .build();
            msgManager.add(userMsg);
        }else {
            msgManager.add(Message.builder().role("user").content(prompt).build());
            param.setMessages(msgManager);
        }

        // 如果第一次发送消息需要初始化参数对象
        if (param == null) {
            param = QwenParam.builder()
                    .model(Generation.Models.QWEN_MAX)
                    .messages(msgManager)
                    .resultFormat(QwenParam.ResultFormat.MESSAGE)
                    .topP(0.8)
                    .enableSearch(true)
                    .incrementalOutput(true)
                    .build();
            CachePool.QWEN_PARAM_MAP.put(identity, param);
        }


        try {
            logger.debug("发送的请求为{}",param);
            // 同步信号量
            Semaphore semaphore = new Semaphore(0);
            // 结果拼接对象
            StringBuilder resultBuilder = new StringBuilder();
            // 流式调用
            gen.streamCall(param, new ResultCallback<GenerationResult>(){

                @Override
                public void onEvent(GenerationResult generationResult) {
                    String newMessage = generationResult.getOutput().getChoices().get(0).getMessage().getContent();
                    StringBuilder finalResBuilder = resultBuilder.append(newMessage);

                    // 这里是对markdown代码块进行判断,如果当前代码块未结束,需要手动结束
                    // 否则前端的代码块显示会出问题
                    // 代码块判断的功能就是对"```"字符串计数,偶数个就是结束了,奇数个就是没结束
                    if (1 == (1 & StringUtil.countSubStr(finalResBuilder,ConstValuePool.MARKDOWN_CODE_BLOCK_START))) {
                        finalResBuilder = new StringBuilder(finalResBuilder)
                                .append(ConstValuePool.MARKDOWN_CODE_BLOCK_END);
                    }
                    // 通过websocket返回给前端
                    aiWebsocketService.sendMessage(finalResBuilder.toString(), identity);
                }

                // 结束或者报错需要释放同步信号量
                @Override
                public void onComplete() {
                    semaphore.release();
                }

                @Override
                public void onError(Exception e) {
                    semaphore.release();
                    logger.error("通义千问运行出错, 报错栈如下");
                    Throwable t = e;
                    while (t != null) {
                        logger.error( t.toString());
                        t = e.getCause();
                    }
                }
            });
            semaphore.acquire();
            String resString = resultBuilder.toString();
            
            // 把返回消息加入历史消息中
msgManager.add(Message.builder().role("assistant").content(resString).build());
            // 如果历史消息量过大或者第一条消息发送对象不是user,删除历史消息
            // 下标0是system消息
            while (msgManager.size() > ConstValuePool.QWEN_MAX_MESSAGE
                    || !"user".equals(msgManager.get(1).getRole())) {
                msgManager.remove(1);
            }
            // 添加到对话记录中,方便前端查询对话记录
            dialogues.add(AiDialogue.createAssistantDialogue(resString));
            return "";
        } catch (NoApiKeyException e) {
            logger.error("调用通义千问缺少ApiKey");
            throw new AiException("没有ApiKey", e);
        } catch (Exception e) {
            logger.error("调用通义千问出现问题:{}",e.getMessage());
            throw new AiException("出现了一些问题", e);
        }
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本届挑战赛亚军方案:基于大模型和多AGENT协同的运维

“轻舟已过万重山团队”荣获本届挑战赛亚军&#xff0c;该团队来自华为集团IT-UniAI 产品和openEuler系统智能团队。 方案介绍 自ChatGPT问世以来&#xff0c;AI迎来了奇点iPhone时刻&#xff0c;这一年来大模型深入影响企业办公&#xff0c;金融&#xff0c;广告&#xff0c;…

上位机图像处理和嵌入式模块部署(上、下位机通信的三个注意点)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果最终部署在客户现场的是一个嵌入式设备&#xff0c;那么上位机在做好了算法编辑和算法部署之后&#xff0c;很重要的一步就是处理上位机和下位…

Mybatis 主从表有名字相同,只能查询出一条数据

Mybatis 主从表有名字相同&#xff0c;只能查询出一条数据 重新命名后&#xff0c;可以正常查询

【HarmonyOS】鸿蒙开发之Stage模型-UIAbility的启动模式——第4.4章

UIAbi lity的启动模式简介 一共有四种:singleton,standard,specified,multion。在项目目录的:src/main/module.json5。默认开启模式为singleton(单例模式)。如下图 singleton&#xff08;单实例模式&#xff09;启动模式 每个UIAbility只存在唯一实例。任务列表中只会存在一…

多个地区地图可视化

1. 配置Json文件 1.1 获得每个省份的json数据 打开 阿里云数据可视化平台 主页。 在搜索框中输入所需省份。 将json文件下载到本地。 1.2 将各省份的json数据进行融合 打开 geojson.io 主页 点击 open&#xff0c;上传刚刚下载的 json 文件&#xff0c;对多个省份不断…

SpringCloud负载均衡源码解析 | 带你从表层一步步剖析Ribbon组件如何实现负载均衡功能

目录 1、负载均衡原理 2、源码分析 2.1、LoadBalanced 2.2、LoadBalancerClient 2.3、RibbonAutoConfiguration 2.4、LoadBalancerAutoConfiguration 2.5、LoadBalancerIntercepor⭐ 2.6、再回LoadBalancerClient 2.7、RibbonLoadBalancerClient 2.7.1、DynamicServe…

JavaScript进阶-高阶技巧

文章目录 高阶技巧深浅拷贝浅拷贝深拷贝 异常处理throw抛异常try/caych捕获异常debugger 处理thisthis指向改变this 性能优化防抖节流 高阶技巧 深浅拷贝 只针对引用类型 浅拷贝 拷贝对象后&#xff0c;里面的属性值是简单数据类型直接拷贝值&#xff0c;如果属性值是引用数…

matlab 写入格式化文本文件

目录 一、save函数 二、fprintf函数 matlab 写入文本文件可以使用save和fprintf函数 save输出结果: fprintf输出结果: 1.23, 2.34, 3.45 4.56, 5.67, 6.78 7.89, 8.90, 9.01 可以看出fprintf输出结果更加人性化,符合要求,下面分别介绍。 一、save函数 …

【前端寻宝之路】学习如何使用HTML实现简历展示和填写

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-iJ3Ou0qMGFVaqVQq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

哈希表是什么?

一、哈希表是什么&#xff1f; 哈希表&#xff0c;也称为散列表&#xff0c;是一种根据关键码值&#xff08;Key value&#xff09;直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录&#xff0c;从而加快查找速度。这个映射函数叫做散列函数&#xff08…

【字符串相加】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 字符串相加 方法一&#xff1a; 方法二&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#xff0c;一种是正在努力学习编程的…

给你N个整数,要求删除最大和最小的数之后按原顺序输出。

给你N个整数&#xff0c;要求删除最大和最小的数之后按原顺序输出。 输入输出格式 输入描述: 第一行输入一个整数N&#xff0c;N<100。 第二个输入N个整数。 输出描述: 按题意输出。#include <iostream> using namespace std; int main() {int n;cin >> n;int…

Vue2->3

Vue2->3 认识Vue31. Vue2 选项式 API vs Vue3 组合式API2. Vue3的优势 使用create-vue搭建Vue3项目1. 认识create-vue2. 使用create-vue创建项目 熟悉项目和关键文件组合式API - setup选项1. setup选项的写法和执行时机2. setup中写代码的特点3. <script setup>语法糖…

Scratch 第十六课-弹珠台游戏

第十六课-弹珠台游戏 大家好&#xff0c;今天我们一起做一款弹珠台scratch游戏&#xff0c;我们也可以叫它弹球游戏&#xff01;这款游戏在刚出来的时候非常火爆。小朋友们要认真学习下&#xff01; 这节课的学习目标 物体碰撞如何处理转向问题。复习键盘对角色的控制方式。…

ubuntu环境下docker容器详细安装使用

文章目录 一、简介二、ubuntu安装docker1.删除旧版本2.安装方法一3. 安装方法二&#xff08;推荐使用&#xff09;4.运行Docker容器5. 配置docker加速器 三、Docker镜像操作1. 拉取镜像2. 查看本地镜像3. 删除镜像4. 镜像打标签5. Dockerfile生成镜像 四、Docker容器操作1. 获取…

AtCoder ABC343 A-D题解

Problem A: 签到题。 #include <bits/stdc.h> using namespace std; int main(){int A,B;cin>>A>>B;for(int i0;i<10;i){if(i!(AB))cout<<i<<endl;//记得return} } Problem B: 依旧签到。 include <bits/stdc.h> using namespace …

实用工具:实时监控服务器CPU负载状态并邮件通知并启用开机自启

作用&#xff1a;在服务器CPU高负载时发送邮件通知 目录 一、功能代码 二、配置开机自启动该监控脚本 1&#xff0c;配置自启脚本 2&#xff0c;启动 三、功能测试 一、功能代码 功能&#xff1a;在CPU负载超过预设置的90%阈值时就发送邮件通知&#xff01;邮件内容显示…

求阶乘。。

&#xff01;&#xff01;&#xff01;答案解释摘录自蓝桥云课题解 问题描述 满足N!的末尾恰好有个0的最小的N是多少? 如果这样的N不存在输出-1。 输入格式 一个整数 K 输出格式 一个整数代表答案 样例输入 2 样例输出 10 import os import sys# 请在此输入您的代码 def coun…

懒人必备|视频号片段提取实战教程!

你是否也为如何提取视频号的视频感到困扰&#xff1f;想要留住那些美好瞬间&#xff0c;但又不知道改如何操作&#xff1f;别瞎找了&#xff01;今天就让我来教你正确的步骤&#xff0c;让你轻松成为“提取达人”&#xff01; 首先&#xff0c;打开想要提取的视频&#xff0c;找…

某u盘 对比 sd卡+读卡器

部分 u盘 性能甚至不如 读卡器SD卡 电脑 支持USB3 gen2 的 USBA接口(u盘用) 和 typec接口(读卡器用) 极致性能需求可考虑: m.2固态硬盘盒m.2固态 设备 速度对比 迅雷... 拷贝文件信息 共用格式化信息