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();
            // 获取token
            OAuthToken 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 {
    @Resource
    private 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。默认为 500ms
        TurnDetection 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左右,具体还在排查问题

相关推荐
ma_king5 分钟前
入门 java 和 数据库
java·数据库·后端
后端AI实验室12 分钟前
我用Cursor开发了3个月,整理出这套提效4倍的工作流
java·ai
zhl7733 分钟前
YOLOv5:从0搭建你的第一个目标检测模型
人工智能
TechFind34 分钟前
用 OpenClaw 搭建企业微信 AI Agent:从零到自动化客服只需 30 分钟
人工智能·agent
FishCoderh35 分钟前
OpenClaw部署后Tools工具权限被禁用?一行配置解决
人工智能
Java水解40 分钟前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解1 小时前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
飞哥数智坊2 小时前
openclaw 不是全站第一!但它的爆发,足以引人深思
人工智能
zone77393 小时前
001:LangChain的LCEL语法学习
人工智能·后端·面试
程序员鱼皮4 小时前
微软竟然出了免费的 AI 应用开发课?!我已经学上了
人工智能·程序员·ai编程