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左右,具体还在排查问题

### 将 Coze 集成到 Spring Boot 项目的指南 目前关于 `Coze` 的具体集成方法并未在提供的引用中提及。然而,基于常见的第三方库与框架的集成模式以及 Spring Boot 的扩展机制,可以推测出一种通用的方法来实现其集成。 #### 1. 添加依赖项 通常情况下,任何外部工具或库都需要通过 Maven 或 Gradle 来引入相应的依赖项。假设存在一个名为 `coze-spring-boot-starter` 的 Starter,则可以通过以下方式将其添加至项目中: 对于 **Maven** 用户: ```xml <dependency> <groupId>com.coze</groupId> <artifactId>coze-spring-boot-starter</artifactId> <version>{latest-version}</version> </dependency> ``` 对于 **Gradle** 用户: ```gradle implementation 'com.coze:coze-spring-boot-starter:{latest-version}' ``` 上述 `{latest-version}` 应替换为实际可用版本号[^5]。 #### 2. 自动化配置支持 Spring Boot 提供了一种自动化的配置机制,即当检测到某些类存在于 classpath 中时会触发特定行为。如果 `Coze` 支持这种自动化配置模型,则无需额外编写代码即可完成大部分初始化工作。例如,在 Dubbo-Spring-Boot-Starter 中就实现了类似的生产级特性[^3]。 #### 3. 手动配置选项 假如官方未提供专用 starter 文件或者希望自定义更多参数,则可能需要手动创建 Configuration 类并注册 Bean 实例。下面是一个简单的例子展示如何定义此类组件: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CozeConfig { @Bean public CozeService cozeService() { return new DefaultCozeServiceImpl(); } } ``` 另外需要注意的是,有时即使遵循标准流程仍可能出现异常情况。比如静态资源映射失败的问题可能是由于路径冲突引起[^4];此时建议仔细检查 application.yml/properties 设置是否合理,并适当调整扫描范围以避开潜在风险区域。 最后提醒一点,虽然 Jasypt 可用于加密敏感数据字段[^1] ,但它并不直接影响其他功能模块间的协作关系。因此除非明确知道两者之间存在关联性,否则不必特别考虑它们之间的交互影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值