java Springboot使用扣子Coze实现实时音频对话智能客服

一、背景

        因公司业务需求,需要使用智能客服实时接听顾客电话。

        现在已经完成的操作是,智能体已接入系统进行对练,所以本文章不写对联相关的功能。只有coze对接~

        扣子提供了试用Realtime WebSocket,点击右上角setting配置好智能体token之后就可以试用了

注意:只有扣子专业版支持实时音视频,所以需要开通专业版,开发测试阶段可以先充值1元买1000资源点对接测试, 注意超额会单独收费哦,

二、准备工作

1、发布智能体为AI服务

        a.登陆扣子平台注册账号

扣子扣子是新一代 AI 大模型智能体开发平台。整合了插件、长短期记忆、工作流、卡片等丰富能力,扣子能帮你低门槛、快速搭建个性化或具备商业价值的智能体,并发布到豆包、飞书等各个平台。https://www.coze.cn/home        b. 在左侧导航栏中选择工作空间,并在页面顶部空间列表中选择个人空间或团队空间

        c. 在项目开发页面,新建智能体

       d.创建智能体完成之后,点击右上角的发布,在发布页面,选择API选项,然后点击发布

    c.获取智能体ID,后续开发要用

        点开 工作空间->项目开发->你的智能体,点进新建的智能体,链接地址后的数字则为智能体ID

2、获取访问令牌

因公司业务需要经过对比我们选用了JWT方式,开发测试阶段也可以选择个人访问令牌       

        a.在扣子API页面,进入授权-> Oauth应用页面->创建新应用,注意客户端类型为服务端应用

       

        b.保存后进行下一步授权,将自动生成的公钥复制保存好,自动下载的私钥也要存储好,后续接口认证会用到!

        

3、安装Java SDK,参考扣子官网

扣子扣子是新一代 AI 大模型智能体开发平台。整合了插件、长短期记忆、工作流、卡片等丰富能力,扣子能帮你低门槛、快速搭建个性化或具备商业价值的智能体,并发布到豆包、飞书等各个平台。https://www.coze.cn/open/docs/developer_guides/java_installation

三、实践开发

1、添加maven依赖

 <dependency><groupId>com.coze</groupId><artifactId>coze-api</artifactId><version>0.3.0</version>
</dependency><!-- 以下非必须!!!!我把私钥文件放到resources下了,所以打包需要加上这个类型->
<build><resources><resource><directory>src/main/resources</directory><includes><include>**/*.pem</include></includes><filtering>false</filtering></resource></resources>
</build>

2、获取token

@Slf4j
@Component
public class CozeOAuth {/*** JWT鉴权token*/public String getJWTToken() {String token = "";try {// 获取私钥文件String jwtOauthPrivateKeyFilePath = "这里是你的私钥文件地址";ClassLoader classLoader = this.getClass().getClassLoader();java.net.URL resourceUrl = classLoader.getResource(jwtOauthPrivateKeyFilePath);if (resourceUrl == null) {log.info("私钥资源文件未找到,{}", jwtOauthPrivateKeyFilePath);return token;}String jwtOauthPrivateKey = new String(Files.readAllBytes(Paths.get(resourceUrl.toURI())), StandardCharsets.UTF_8);JWTOAuthClient oauth = new JWTOAuthClient.JWTOAuthBuilder().clientID("这里是你之前创建的OAuth应用Id").privateKey(jwtOauthPrivateKey).publicKey("这里是你的公钥").baseURL(com.coze.openapi.service.config.Consts.COZE_CN_BASE_URL).build();// 获取tokenOAuthToken resp = oauth.getAccessToken();System.out.println(resp);if (Objects.nonNull(resp)) {token = resp.getAccessToken();}} catch (Exception e) {log.error("获取coze JWT token异常!", e);}log.info("获取coze JWT token:{}", token);return token;}}

3、创建新类继承WebsocketsChatCallbackHandler,接收扣子服务端返回消息并做业务处理

@Slf4j
public class MyWebsocketsChatCallbackHandler extends WebsocketsChatCallbackHandler {public void onChatCreated(WebsocketsChatClient client, ChatCreatedEvent event) {log.info("扣子服务端返回,对话连接成功,{}", JSON.toJSONString(event));}public void onChatUpdated(WebsocketsChatClient client, ChatUpdatedEvent event) {log.info("扣子服务端返回,对话配置成功,{}", JSON.toJSONString(event));}public void onConversationChatCreated(WebsocketsChatClient client, ConversationChatCreatedEvent event) {log.info("扣子服务端返回,对话开始,{}", JSON.toJSONString(event));}public void onConversationChatInProgress(WebsocketsChatClient client, ConversationChatInProgressEvent event) {log.info("扣子服务端返回,对话正在处理,{}", JSON.toJSONString(event));}public void onConversationMessageDelta(WebsocketsChatClient client, ConversationMessageDeltaEvent event) {log.info("扣子服务端返回,增量消息,{}", JSON.toJSONString(event));}public void onConversationAudioDelta(WebsocketsChatClient client, ConversationAudioDeltaEvent event) {log.info("扣子服务端返回,增量语音,{}", JSON.toJSONString(event));// TODO 处理实际业务,比如返回给用户的语音}public void onConversationMessageCompleted(WebsocketsChatClient client, ConversationMessageCompletedEvent event) {log.info("扣子服务端返回,消息完成,{}", JSON.toJSONString(event));}public void onConversationAudioCompleted(WebsocketsChatClient client, ConversationAudioCompletedEvent event) {log.info("扣子服务端返回,语音回复完成,{}", JSON.toJSONString(event));}public void onConversationChatCompleted(WebsocketsChatClient client, ConversationChatCompletedEvent event) {log.info("扣子服务端返回,对话完成,{}", JSON.toJSONString(event));}public void onConversationChatFailed(WebsocketsChatClient client, ConversationChatFailedEvent event) {log.info("扣子服务端返回,对话失败,{}", JSON.toJSONString(event));}public void onInputAudioBufferCompleted(WebsocketsChatClient client, InputAudioBufferCompletedEvent event) {log.info("扣子服务端返回,流式提交的音频完成,{}", JSON.toJSONString(event));}public void onInputAudioBufferCleared(WebsocketsChatClient client, InputAudioBufferClearedEvent event) {log.info("扣子服务端返回,清除缓冲区音频成功,{}", JSON.toJSONString(event));}public void onConversationCleared(WebsocketsChatClient client, ConversationClearedEvent event) {log.info("扣子服务端返回,上下文清除完成,{}", JSON.toJSONString(event));}public void onConversationChatCanceled(WebsocketsChatClient client, ConversationChatCanceledEvent event) {log.info("扣子服务端返回,智能体输出中断,{}", JSON.toJSONString(event));}public void onConversationAudioTranscriptUpdate(WebsocketsChatClient client, ConversationAudioTranscriptUpdateEvent event) {log.info("扣子服务端返回,用户语音识别字幕,{}", JSON.toJSONString(event));}public void onConversationAudioTranscriptCompleted(WebsocketsChatClient client, ConversationAudioTranscriptCompletedEvent event) {log.info("扣子服务端返回,用户语音识别完成,{}", JSON.toJSONString(event));}public void onConversationChatRequiresAction(WebsocketsChatClient client, ConversationChatRequiresActionEvent event) {log.info("扣子服务端返回,端插件请求,{}", JSON.toJSONString(event));}public void onInputAudioBufferSpeechStarted(WebsocketsChatClient client, InputAudioBufferSpeechStartedEvent event) {log.info("扣子服务端返回,用户开始说话,{}", JSON.toJSONString(event));}public void onInputAudioBufferSpeechStopped(WebsocketsChatClient client, InputAudioBufferSpeechStoppedEvent event) {log.info("扣子服务端返回,用户结束说话,{}", JSON.toJSONString(event));}public void onClosing(WebsocketsChatClient client, int code, String reason) {log.info("扣子服务端返回,onClosing,code:{},reason:{}",code, reason);}public void onClosed(WebsocketsChatClient client, int code, String reason) {log.info("扣子服务端返回,onClosed,code:{},reason:{}", code,reason);}public void onError(WebsocketsChatClient client, ErrorEvent event) {log.info("扣子服务端返回,onError,event:{}", JSON.toJSONString(event));}public void onFailure(WebsocketsChatClient client, Throwable t) {log.info("扣子服务端返回,onFailure,event:{}", JSON.toJSONString(t));}public void onClientException(WebsocketsChatClient client, Throwable t) {log.info("扣子服务端返回,onFailure,event:{}", JSON.toJSONString(t.getMessage()));}
}

4、创建工具类

抽取跟业务无关的代码到该类中

@Slf4j
@Component
public class WebSocketUtils {@Resourceprivate CozeOAuth cozeOAuth;/*** 更新对话配置 请求参数*/public ChatUpdateEventData initChatUpdateEventData() {// 对话配置ChatConfig chatConfig = new ChatConfig();chatConfig.setAutoSaveHistory(true);// 输入音频格式InputAudio inputAudio = new InputAudio("pcm", "g711a", 8000, 1, 16);PCMConfig pcmConfig = new PCMConfig(100,8000);// 输出音频格式OutputAudio outputAudio = new OutputAudio("pcm", pcmConfig, null, null, null);// 转检测配置// server_vad 模式下,VAD 检测到语音之前要包含的音频量,单位为 ms。默认为 600ms。// server_vad 模式下,检测语音停止的静音持续时间,单位为 ms。默认为 500msTurnDetection turnDetection = new TurnDetection("server_vad", 300, 500);return cChatUpdateEventData.builder().inputAudio(inputAudio).outputAudio(outputAudio).chatConfig(chatConfig).turnDetection(turnDetection).build();}public CozeAPI getCozeApi(){return new CozeAPI.Builder().baseURL(Consts.COZE_CN_BASE_URL).auth(new TokenAuth(cozeOAuth.getJWTToken())).readTimeout(10000).build();}
}

5、使用websocket双向流式对话

我们用到了第三方的用户进线传输,直接sip协议拿包,将包传输给扣子,之后再将扣子的增量语音返回给第三方就行。所以选择了websocket的方式

        byte[] buffer = new byte[1500];CozeAPI cozeAPI = webSocketUtils.getCozeApi();WebsocketsChatClient websocketsChatClient = cozeAPI.websockets().chat().create(new WebsocketsChatCreateReq("这里是你的智能体ID", new MyWebsocketsChatCallbackHandler()));// 更新对话配置               websocketsChatClient.chatUpdate(webSocketUtils.initChatUpdateEventData());// 此处可以根据实际业务接收语音流byte[] audioData = Files.readAllBytes(Paths.get("/音频.pcm"));// 流式上传音频片段websocketsChatClient.inputAudioBufferAppend(audioData);

四、踩过的的坑

1、SDK版本会落后服务端功能

        扣子提供的SDK跟接口文档中描述的功能有部分差异,比如更新对话接口的入参limit_config,在SDK中是没有的。

        遇到这种情况则需要自己封装参数,比如继承某个SDK的类,然后在子类中写自己需要但是SDK没有的参数。

2、自动打断功能配置

        想要实现自动打断功能,需要使用server_vad模式,并且需要配置输出音频的限制limit_config,限制每次服务端返回的包,否则会等服务端返回完成之后才能打断。

3、工作流模式服务端响应较慢

        实际应用场景中会需要给智能体传配置好的参数,目前智能通过工作流的方式记住上下文,但是该模式服务端响应在3s左右,具体还在排查问题

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

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

相关文章

栈和字符串,力扣.43.字符串相乘力扣1047.删除字符串中的所有相邻重复项力扣.844比较含退格的字符串力扣227.基本计算器II

目录 力扣.43.字符串相乘 力扣1047.删除字符串中的所有相邻重复项 力扣.844比较含退格的字符串 力扣227.基本计算器II 力扣.43.字符串相乘 我们剩下的落两个数字即可。 class Solution {public static String multiply(String num1, String num2) {int mnum1.length();int n…

Spring Boot单元测试实战指南:从零到高效测试

在Spring Boot开发中&#xff0c;单元测试是保障代码质量的核心环节。本文将基于实际开发场景&#xff0c;手把手教你如何快速实现分层测试、模拟依赖、编写高效断言&#xff0c;并分享最佳实践&#xff01; 一、5分钟环境搭建 添加依赖 在pom.xml中引入spring-boot-starter-te…

React状态提升深度解析:原理、实战与最佳实践

一、状态提升的本质认知 React状态提升&#xff08;State Lifting&#xff09;是组件间通信的核心模式&#xff0c;其本质是通过组件树层级关系重构实现状态共享。与传统父子传参不同&#xff0c;它通过将状态提升到最近的共同祖先组件&#xff0c;建立单向数据流高速公路。 …

https nginx 负载均衡配置

我的系统是OpenEuler。 安装nginx yum install -y nginx 启动&开机启动 systemctl start nginx systemctl enable nginx 自定义conf配置文件 cat <<EOF >> /etc/nginx/conf.d/load_balancer.conf upstream backend {ip_hash; # 防止验证码验证失败server…

各种插值方法的Python实现

插值方法的Python实现 1. 线性插值&#xff08;Linear Interpolation&#xff09; 原理&#xff1a;用直线连接相邻数据点&#xff0c;计算中间点的值。 实现&#xff1a; import numpy as np from scipy.interpolate import interp1dx np.array([0, 1, 2, 3, 4]) y np.arr…

重新定义户外防护!基于DeepSeek的智能展开伞棚系统技术深度解析

从“手动操作”到“感知决策”&#xff0c;AI重构城市空间弹性 全球极端天气事件频发&#xff0c;传统伞棚依赖人工展开/收纳&#xff0c;存在响应滞后&#xff08;暴雨突袭时展开需3-5分钟&#xff09;、抗风能力弱&#xff08;8级风损毁率超60%&#xff09;、空间利用率低等痛…

Redis 基础和高级用法入门

redis 是什么&#xff1f; Redis是一个远程内存数据库&#xff0c;它不仅性能强劲&#xff0c;而且还具有复制特性以及为解决问题而生的独一无二的数据模型。Redis提供了5种不同类型的数据结构&#xff0c;各式各样的问题都可以很自然地映射到这些数据结构上&#xff1a…

常见数据库关键字示例 SQL 及执行顺序分析(带详细注释)

示例 SQL 及执行顺序分析&#xff08;带详细注释&#xff09; 示例 1&#xff1a;基础查询&#xff08;含多表关联、过滤、分组、排序&#xff09; SELECT -- 1. 选择字段&#xff08;包含聚合函数和别名&#xff09;e.department, COUNT(e.employee_id) AS total_employees, …

设计模式--建造者模式详解

建造者模式 建造者模式也属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式 定义&#xff1a;将一个复杂对象的构建和它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示&#xff08;假设有不同的建造者实现类&#xff0c;可以产生不同的产品&#xff09…

PCB 过孔铜厚的深入指南

***前言&#xff1a;在上一期的文章中介绍了PCB制造的工艺流程&#xff0c;但仍然想在过孔的铜厚和PCB的过孔厚径比两个方面再深入介绍。 PCB铜厚的定义 电路中铜的厚度以盎司(oz)**表示。那么&#xff0c;为什么用重量单位来表示厚度呢? 盎司(oz)的定义 将1盎司(28.35 克)的铜…

如何配置 Conda 使用镜像源加速

如何配置 Conda 使用镜像源加速 为了提高使用 Anaconda 或 Miniconda 时包管理的速度&#xff0c;特别是在国内网络环境下&#xff0c;可以通过配置镜像源来实现更快的下载。以下是详细的步骤说明&#xff1a; 1. 安装 Conda&#xff08;如果尚未安装&#xff09; 如果你还没…

【k8s】k8s是怎么实现自动扩缩的

Kubernetes 提供了多种自动扩缩容机制&#xff0c;主要包括 Pod 水平自动扩缩&#xff08;HPA&#xff09;、垂直 Pod 自动扩缩&#xff08;VPA&#xff09; 和 集群自动扩缩&#xff08;Cluster Autoscaler&#xff09;。以下是它们的实现原理和配置方法&#xff1a; 1. Pod …

Reflex 完全指南:用 Python 构建现代 Web 应用的终极体验

“写 Python&#xff0c;就能构建 Web 前端。”——这不再是梦想&#xff0c;而是由 Reflex 带来的现实。 过去&#xff0c;构建一个现代 Web 应用意味着你要学会前端&#xff08;React/JS/HTML/CSS&#xff09; 后端&#xff08;Flask/Django&#xff09; API 交互&#xff08…

Vue实战(08)解决 Vue 项目中路径别名 `@` 在 IDE 中报错无法识别的问题

一、引言 ​ 在 Vue 项目开发过程中&#xff0c;路径别名是一个非常实用的特性&#xff0c;它能够帮助开发者简化文件引用路径&#xff0c;提高代码的可读性和可维护性。其中&#xff0c; 作为一个常见的路径别名&#xff0c;通常被用来指向项目的 src 目录。然而&#xff0c;…

5.学习笔记-SpringMVC(P61-P70)

SpringMVC-SSM整合-接口测试 (1)业务层接口使用junit接口做测试 (2)表现层用postman做接口测试 (3)事务处理— 1&#xff09;在SpringConfig.java&#xff0c;开启注解&#xff0c;是事务驱动 2&#xff09;配置事务管理器&#xff08;因为事务管理器是要配置数据源对象&…

[论文阅读]REPLUG: Retrieval-Augmented Black-Box Language Models

REPLUG: Retrieval-Augmented Black-Box Language Models REPLUG: Retrieval-Augmented Black-Box Language Models - ACL Anthology NAACL-HLT 2024 在这项工作中&#xff0c;我们介绍了RePlug&#xff08;Retrieve and Plug&#xff09;&#xff0c;这是一个新的检索增强型…

Mysql的深度分页查询优化

一、深度分页为什么慢&#xff1f; 当执行 SELECT * FROM orders ORDER BY id LIMIT 1000000, 10 时&#xff1a; MySQL 会扫描前 1,000,010 行&#xff0c;丢弃前 100 万行&#xff0c;仅返回 10 行。偏移量&#xff08;offset&#xff09;越大&#xff0c;扫描行数越多&…

最新扣子(Coze)案例教程:Excel数据生成统计图表,自动清洗数据+转换可视化图表+零代码,完全免费教程

大家好&#xff0c;我是斜杠君。 知识星球群有同学和我说每天的工作涉及很多数据表的重复操作&#xff0c;想学习Excel数据表通过大模型自动转数据图片的功能。 今天斜杠君就带大家一起搭建一个智能体&#xff0c;以一个销售行业数据为例&#xff0c;可以快速实现自动清洗Exc…

Uniapp 中缓存操作指南

在 Uniapp 中,你可以使用三种方式操作缓存:同步方法、异步方法和 Vuex 持久化存储。以下是详细的设置、获取和清除缓存的方法: 1. 同步方法 设置缓存 uni.setStorageSync(key, value); // 示例 uni.setStorageSync(token, abc123); 获取缓存 const value = uni.getStor…

k8s的yaml文件里的volume跟volumeMount的区别

volume 是 Pod 级别的资源&#xff0c;用于定义存储卷。它是一个独立于容器的存储资源&#xff0c;可以被一个或多个容器共享使用。volume 的定义位于 Pod 的 spec.volumes 部分。 特点 独立性&#xff1a;volume 是 Pod 的一部分&#xff0c;而不是容器的一部分。它独立于容…