java Springboot使用扣子Coze實現(xiàn)實時音頻對話智能客服功能
一、背景
因公司業(yè)務(wù)需求,需要使用智能客服實時接聽顧客電話。
現(xiàn)在已經(jīng)完成的操作是,智能體已接入系統(tǒng)進(jìn)行對練,所以本文章不寫對聯(lián)相關(guān)的功能。只有coze對接~
扣子提供了試用Realtime WebSocket,點擊右上角setting配置好智能體token之后就可以試用了
注意:只有扣子專業(yè)版支持實時音視頻,所以需要開通專業(yè)版,開發(fā)測試階段可以先充值1元買1000資源點對接測試, 注意超額會單獨收費哦,
二、準(zhǔn)備工作
1、發(fā)布智能體為AI服務(wù)
a.登陸扣子平臺注冊賬號
扣子
扣子是新一代 AI 大模型智能體開發(fā)平臺。整合了插件、長短期記憶、工作流、卡片等豐富能力,扣子能幫你低門檻、快速搭建個性化或具備商業(yè)價值的智能體,并發(fā)布到豆包、飛書等各個平臺。
b. 在左側(cè)導(dǎo)航欄中選擇工作空間,并在頁面頂部空間列表中選擇個人空間或團隊空間

c. 在項目開發(fā)頁面,新建智能體

d.創(chuàng)建智能體完成之后,點擊右上角的發(fā)布,在發(fā)布頁面,選擇API選項,然后點擊發(fā)布

c.獲取智能體ID,后續(xù)開發(fā)要用
點開 工作空間->項目開發(fā)->你的智能體,點進(jìn)新建的智能體,鏈接地址后的數(shù)字則為智能體ID

2、獲取訪問令牌
因公司業(yè)務(wù)需要經(jīng)過對比我們選用了JWT方式,開發(fā)測試階段也可以選擇個人訪問令牌
a.在扣子API頁面,進(jìn)入授權(quán)-> Oauth應(yīng)用頁面->創(chuàng)建新應(yīng)用,注意客戶端類型為服務(wù)端應(yīng)用


b.保存后進(jìn)行下一步授權(quán),將自動生成的公鑰復(fù)制保存好,自動下載的私鑰也要存儲好,后續(xù)接口認(rèn)證會用到!

3、安裝Java SDK,參考扣子官網(wǎng)
扣子
扣子是新一代 AI 大模型智能體開發(fā)平臺。整合了插件、長短期記憶、工作流、卡片等豐富能力,扣子能幫你低門檻、快速搭建個性化或具備商業(yè)價值的智能體,并發(fā)布到豆包、飛書等各個平臺。
https://www.coze.cn/open/docs/developer_guides/java_installation
三、實踐開發(fā)
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鑒權(quán)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("這里是你之前創(chuàng)建的OAuth應(yīng)用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、創(chuàng)建新類繼承WebsocketsChatCallbackHandler,接收扣子服務(wù)端返回消息并做業(yè)務(wù)處理
@Slf4j
public class MyWebsocketsChatCallbackHandler extends WebsocketsChatCallbackHandler {
public void onChatCreated(WebsocketsChatClient client, ChatCreatedEvent event) {
log.info("扣子服務(wù)端返回,對話連接成功,{}", JSON.toJSONString(event));
}
public void onChatUpdated(WebsocketsChatClient client, ChatUpdatedEvent event) {
log.info("扣子服務(wù)端返回,對話配置成功,{}", JSON.toJSONString(event));
}
public void onConversationChatCreated(WebsocketsChatClient client, ConversationChatCreatedEvent event) {
log.info("扣子服務(wù)端返回,對話開始,{}", JSON.toJSONString(event));
}
public void onConversationChatInProgress(WebsocketsChatClient client, ConversationChatInProgressEvent event) {
log.info("扣子服務(wù)端返回,對話正在處理,{}", JSON.toJSONString(event));
}
public void onConversationMessageDelta(WebsocketsChatClient client, ConversationMessageDeltaEvent event) {
log.info("扣子服務(wù)端返回,增量消息,{}", JSON.toJSONString(event));
}
public void onConversationAudioDelta(WebsocketsChatClient client, ConversationAudioDeltaEvent event) {
log.info("扣子服務(wù)端返回,增量語音,{}", JSON.toJSONString(event));
// TODO 處理實際業(yè)務(wù),比如返回給用戶的語音
}
public void onConversationMessageCompleted(WebsocketsChatClient client, ConversationMessageCompletedEvent event) {
log.info("扣子服務(wù)端返回,消息完成,{}", JSON.toJSONString(event));
}
public void onConversationAudioCompleted(WebsocketsChatClient client, ConversationAudioCompletedEvent event) {
log.info("扣子服務(wù)端返回,語音回復(fù)完成,{}", JSON.toJSONString(event));
}
public void onConversationChatCompleted(WebsocketsChatClient client, ConversationChatCompletedEvent event) {
log.info("扣子服務(wù)端返回,對話完成,{}", JSON.toJSONString(event));
}
public void onConversationChatFailed(WebsocketsChatClient client, ConversationChatFailedEvent event) {
log.info("扣子服務(wù)端返回,對話失敗,{}", JSON.toJSONString(event));
}
public void onInputAudioBufferCompleted(WebsocketsChatClient client, InputAudioBufferCompletedEvent event) {
log.info("扣子服務(wù)端返回,流式提交的音頻完成,{}", JSON.toJSONString(event));
}
public void onInputAudioBufferCleared(WebsocketsChatClient client, InputAudioBufferClearedEvent event) {
log.info("扣子服務(wù)端返回,清除緩沖區(qū)音頻成功,{}", JSON.toJSONString(event));
}
public void onConversationCleared(WebsocketsChatClient client, ConversationClearedEvent event) {
log.info("扣子服務(wù)端返回,上下文清除完成,{}", JSON.toJSONString(event));
}
public void onConversationChatCanceled(WebsocketsChatClient client, ConversationChatCanceledEvent event) {
log.info("扣子服務(wù)端返回,智能體輸出中斷,{}", JSON.toJSONString(event));
}
public void onConversationAudioTranscriptUpdate(WebsocketsChatClient client, ConversationAudioTranscriptUpdateEvent event) {
log.info("扣子服務(wù)端返回,用戶語音識別字幕,{}", JSON.toJSONString(event));
}
public void onConversationAudioTranscriptCompleted(WebsocketsChatClient client, ConversationAudioTranscriptCompletedEvent event) {
log.info("扣子服務(wù)端返回,用戶語音識別完成,{}", JSON.toJSONString(event));
}
public void onConversationChatRequiresAction(WebsocketsChatClient client, ConversationChatRequiresActionEvent event) {
log.info("扣子服務(wù)端返回,端插件請求,{}", JSON.toJSONString(event));
}
public void onInputAudioBufferSpeechStarted(WebsocketsChatClient client, InputAudioBufferSpeechStartedEvent event) {
log.info("扣子服務(wù)端返回,用戶開始說話,{}", JSON.toJSONString(event));
}
public void onInputAudioBufferSpeechStopped(WebsocketsChatClient client, InputAudioBufferSpeechStoppedEvent event) {
log.info("扣子服務(wù)端返回,用戶結(jié)束說話,{}", JSON.toJSONString(event));
}
public void onClosing(WebsocketsChatClient client, int code, String reason) {
log.info("扣子服務(wù)端返回,onClosing,code:{},reason:{}",code, reason);
}
public void onClosed(WebsocketsChatClient client, int code, String reason) {
log.info("扣子服務(wù)端返回,onClosed,code:{},reason:{}", code,reason);
}
public void onError(WebsocketsChatClient client, ErrorEvent event) {
log.info("扣子服務(wù)端返回,onError,event:{}", JSON.toJSONString(event));
}
public void onFailure(WebsocketsChatClient client, Throwable t) {
log.info("扣子服務(wù)端返回,onFailure,event:{}", JSON.toJSONString(t));
}
public void onClientException(WebsocketsChatClient client, Throwable t) {
log.info("扣子服務(wù)端返回,onFailure,event:{}", JSON.toJSONString(t.getMessage()));
}
}4、創(chuàng)建工具類
抽取跟業(yè)務(wù)無關(guān)的代碼到該類中
@Slf4j
@Component
public class WebSocketUtils {
@Resource
private CozeOAuth cozeOAuth;
/**
* 更新對話配置 請求參數(shù)
*/
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);
// 轉(zhuǎn)檢測配置
// server_vad 模式下,VAD 檢測到語音之前要包含的音頻量,單位為 ms。默認(rèn)為 600ms。
// server_vad 模式下,檢測語音停止的靜音持續(xù)時間,單位為 ms。默認(rèn)為 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雙向流式對話
我們用到了第三方的用戶進(jìn)線傳輸,直接sip協(xié)議拿包,將包傳輸給扣子,之后再將扣子的增量語音返回給第三方就行。所以選擇了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());
// 此處可以根據(jù)實際業(yè)務(wù)接收語音流
byte[] audioData =
Files.readAllBytes(Paths.get("/音頻.pcm"));
// 流式上傳音頻片段
websocketsChatClient.inputAudioBufferAppend(audioData);四、踩過的的坑
1、SDK版本會落后服務(wù)端功能
扣子提供的SDK跟接口文檔中描述的功能有部分差異,比如更新對話接口的入?yún)imit_config,在SDK中是沒有的。
遇到這種情況則需要自己封裝參數(shù),比如繼承某個SDK的類,然后在子類中寫自己需要但是SDK沒有的參數(shù)。
2、自動打斷功能配置
想要實現(xiàn)自動打斷功能,需要使用server_vad模式,并且需要配置輸出音頻的限制limit_config,限制每次服務(wù)端返回的包,否則會等服務(wù)端返回完成之后才能打斷。
3、工作流模式服務(wù)端響應(yīng)較慢
實際應(yīng)用場景中會需要給智能體傳配置好的參數(shù),目前智能通過工作流的方式記住上下文,但是該模式服務(wù)端響應(yīng)在3s左右,具體還在排查問題
到此這篇關(guān)于java Springboot使用扣子Coze實現(xiàn)實時音頻對話智能客服功能的文章就介紹到這了,更多相關(guān)springboot音頻對話智能客服內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java equals 方法與hashcode 方法的深入解析
面試時經(jīng)常會問起字符串比較相關(guān)的問題,比如:字符串比較時用的什么方法,內(nèi)部實現(xiàn)如何?hashcode的作用,以及重寫equal方法,為什么要重寫hashcode方法?以下就為大家解答,需要的朋友可以參考下2013-07-07
Mybatis一級緩存和結(jié)合Spring Framework后失效的源碼探究
這篇文章主要介紹了Mybatis一級緩存和結(jié)合Spring Framework后失效的源碼探究,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
IntelliJ Plugin 開發(fā)之添加第三方j(luò)ar的示例代碼
這篇文章主要介紹了IntelliJ Plugin 開發(fā)之添加第三方j(luò)ar的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
java基于雙向環(huán)形鏈表解決丟手帕問題的方法示例
這篇文章主要介紹了java基于雙向環(huán)形鏈表解決丟手帕問題的方法,簡單描述了丟手帕問題,并結(jié)合實例形式給出了Java基于雙向環(huán)形鏈表解決丟手帕問題的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-11-11
springboot整合xxl-job實現(xiàn)分布式定時任務(wù)的過程
XXL-JOB是一個分布式任務(wù)調(diào)度平臺,其核心設(shè)計目標(biāo)是開發(fā)迅速、學(xué)習(xí)簡單、輕量級、易擴展,這篇文章主要介紹了springboot整合xxl-job分布式定時任務(wù),需要的朋友可以參考下2022-08-08
jmeter設(shè)置全局變量與正則表達(dá)式提取器過程圖解
這篇文章主要介紹了jmeter設(shè)置全局變量與正則表達(dá)式提取器過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10

