| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- package com.telerobot.fs.robot.impl;
- import cn.hutool.json.JSONUtil;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONArray;
- import com.alibaba.fastjson.JSONObject;
- import com.telerobot.fs.config.AppContextProvider;
- import com.telerobot.fs.entity.dao.LlmKb;
- import com.telerobot.fs.entity.dto.LlmAiphoneRes;
- import com.telerobot.fs.entity.dto.llm.LlmAccount;
- import com.telerobot.fs.entity.po.HangupCause;
- import com.telerobot.fs.entity.pojo.LlmToolRequest;
- import com.telerobot.fs.robot.AbstractChatRobot;
- import com.telerobot.fs.service.SysService;
- import com.telerobot.fs.utils.CommonUtils;
- import okhttp3.*;
- import okio.BufferedSource;
- import org.apache.commons.lang.StringUtils;
- import org.apache.http.HttpStatus;
- import java.io.IOException;
- import java.util.List;
- public class DeepSeekChat extends AbstractChatRobot {
- @Override
- public LlmAiphoneRes talkWithAiAgent(String question, Boolean... withKbResponse) {
- LlmAiphoneRes aiphoneRes = new LlmAiphoneRes();
- aiphoneRes.setStatus_code(1);
- aiphoneRes.setClose_phone(0);
- aiphoneRes.setIfcan_interrupt(0);
- if (firstRound) {
- firstRound = false;
- String llmTips = ((LlmAccount) getAccount()).getLlmTips();
- int catId = llmAccountInfo.kbCatId;
- String topicsVar = "${kbTopicList}";
- if(catId != -1) {
- if (llmTips.contains(topicsVar)) {
- List<LlmKb> kbList = AppContextProvider.getBean(SysService.class).getKbListByCatId(catId);
- StringBuilder topics = new StringBuilder();
- for (LlmKb llmKb : kbList) {
- topics.append("*").append(llmKb.getTitle()).append("\r\n");
- }
- topics.append("\r\n");
- llmTips = llmTips.replace(topicsVar, topics.toString());
- logger.info("{} topic list: {}", uuid, topics.toString());
- } else {
- logger.warn("{} {} tag not found, the knowledge base function will be unavailable. ", uuid, topicsVar);
- }
- }else {
- logger.warn("{} The current model {} doesn’t have a knowledge base linked to it. ", uuid, ((LlmAccount) getAccount()).getModelName());
- }
- String tips = llmTips + "\r\n\r\n" + ((LlmAccount) getAccount()).getFaqContext();
- JSONObject bizJson = new JSONObject();
- if (null != callDetail && null != callDetail.getOutboundPhoneInfo() && StringUtils.isNotBlank(callDetail.getOutboundPhoneInfo().getBizJson())) {
- tips += "\n bizJson:" + callDetail.getOutboundPhoneInfo().getBizJson();
- bizJson = JSONObject.parseObject(callDetail.getOutboundPhoneInfo().getBizJson());
- }
- addDialogue(ROLE_SYSTEM, tips);
- String openingRemarks = replaceParams(llmAccountInfo.openingRemarks, bizJson);
- addDialogue(ROLE_ASSISTANT, openingRemarks);
- ttsTextCache.add(openingRemarks);
- sendToTts();
- closeTts();
- aiphoneRes.setBody(openingRemarks);
- return aiphoneRes;
- } else {
- if (withKbResponse.length > 0 && !withKbResponse[0]) {
- if (!StringUtils.isEmpty(question)) {
- addDialogue(ROLE_USER, question);
- } else {
- addDialogue(ROLE_USER, "NO_VOICE");
- String noVoiceTips = llmAccountInfo.customerNoVoiceTips;
- addDialogue(ROLE_ASSISTANT, noVoiceTips);
- ttsTextCache.add(noVoiceTips);
- sendToTts();
- closeTts();
- aiphoneRes.setBody(noVoiceTips);
- return aiphoneRes;
- }
- }
- try {
- JSONObject response = sendStreamingRequest(aiphoneRes, llmRoundMessages);
- if (null != response) {
- llmRoundMessages.add(response);
- } else {
- aiphoneRes.setStatus_code(0);
- }
- } catch (Throwable throwable) {
- aiphoneRes.setStatus_code(0);
- logger.error("{} talkWithAiAgent error: {}", uuid, CommonUtils.getStackTraceString(throwable.getStackTrace()));
- }
- return aiphoneRes;
- }
- }
- private JSONObject sendStreamingRequest(LlmAiphoneRes aiphoneRes, List<JSONObject> messages) throws IOException {
- JSONObject requestBody = new JSONObject();
- requestBody.put("model", ((LlmAccount)getAccount()).getModelName());
- requestBody.put("stream", true);
- // enable stream output
- JSONArray messagesArray = new JSONArray();
- messagesArray.addAll(messages);
- requestBody.put("messages", messagesArray);
- RequestBody body = RequestBody.create(
- MediaType.parse("application/json"),
- requestBody.toJSONString()
- );
- Request request = new Request.Builder()
- .url(getAccount().serverUrl)
- .post(body)
- .addHeader("Authorization", "Bearer " + ((LlmAccount)getAccount()).getApiKey())
- .build();
- boolean recvData = false;
- long startTime = System.currentTimeMillis();
- try (Response response = CLIENT.newCall(request).execute()) {
- if (!response.isSuccessful()) {
- ResponseBody responseBody = response.body();
- String bodyStr = responseBody != null ? responseBody.string() : "空响应体";
- // 2. 组装 完整日志(请求+响应全部信息)
- String log = "\n======= OKHTTP 完整请求响应 =======\n"
- + "请求URL:" + response.request().url() + "\n"
- + "请求方法:" + response.request().method() + "\n"
- + "请求头:\n" + response.request().headers() + "\n"
- + "响应状态码:" + response.code() + "\n"
- + "响应头:\n" + response.headers() + "\n"
- + "响应体:\n" + bodyStr + "\n"
- + "====================================\n";
- // 3. 用 error 级别打印(你要的)
- logger.error(log);
- logger.error("Model api error: http-code={}, msg={}, url={}",
- response.code(),
- response.message(),
- getAccount().serverUrl
- );
- if(response.code() == HttpStatus.SC_UNAUTHORIZED) {
- CommonUtils.setHangupCauseDetail(
- callDetail,
- HangupCause.LLM_API_KEY_INCORRECT,
- "http-status-code=" + response.code()
- );
- CommonUtils.hangupCallSession(uuid, HangupCause.LLM_API_KEY_INCORRECT.getCode());
- return null;
- }else{
- CommonUtils.setHangupCauseDetail(
- callDetail,
- HangupCause.LLM_API_SERVER_ERROR,
- "http-status-code=" + response.code()
- );
- }
- if(response.code() == HttpStatus.SC_UNAUTHORIZED || response.code() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
- throw new IOException("Unexpected code " + response);
- }else{
- return null;
- }
- }
- BufferedSource source = response.body().source();
- StringBuilder responseBuilder = new StringBuilder();
- Integer completionTokens = 0; // 模型生成回复转换为 Token 后的长度。
- Integer promptTokens = 0; // 用户的输入转换成 Token 后的长度。
- while (!source.exhausted()) {
- String line = source.readUtf8Line();
- if (line != null && line.startsWith("data: ")) {
- String jsonData = line.substring(6).trim(); // 去掉 "data: " 前缀
- if (jsonData.equals("[DONE]")) {
- break; // 流式响应结束
- }
- JSONObject jsonResponse = JSON.parseObject(jsonData);
- JSONObject message = jsonResponse.getJSONArray("choices")
- .getJSONObject(0)
- .getJSONObject("delta"); // 注意:流式响应中消息在 "delta" 字段中
- if (message.containsKey("content")) {
- String speechContent = message.getString("content");
- logger.info("{} speechContent: {}", getTraceId(), speechContent);
- if (!StringUtils.isEmpty(speechContent)) {
- if (speechContent.contains(LlmToolRequest.TRANSFER_TO_AGENT)) {
- aiphoneRes.setTransferToAgent(1);
- logger.info("{} `TRANSFER_TO_AGENT` command detected. ", getTraceId());
- }
- if (speechContent.contains(LlmToolRequest.HANGUP)) {
- aiphoneRes.setClose_phone(1);
- logger.info("{} `HANGUP` command detected. ", getTraceId());
- }
- if (!StringUtils.isEmpty(speechContent)) {
- speechContent = speechContent.replace(LlmToolRequest.TRANSFER_TO_AGENT,"")
- .replace(LlmToolRequest.HANGUP,"")
- .replace("`","");
- ttsTextCache.add(speechContent);
- ttsTextLength += speechContent.length();
- // 积攒足够的字数之后,才发送给tts,避免播放异常;
- if (ttsTextLength >= 5 && checkPauseFlag(speechContent)) {
- sendToTts();
- if (!recvData) {
- recvData = true;
- long costTime = (System.currentTimeMillis() - startTime);
- logger.info("http request cost time : {} ms.", costTime);
- aiphoneRes.setCostTime(costTime);
- }
- }
- }
- responseBuilder.append(speechContent);
- }
- }
- if (null != jsonResponse.get("usage")) {
- JSONObject usage = jsonResponse.getJSONObject("usage");
- completionTokens = usage.getInteger("completion_tokens");
- promptTokens = usage.getInteger("prompt_tokens");
- }
- }
- }
- String answer = responseBuilder.toString();
- logger.info("{} recv llm response end flag. answer={}", this.uuid, answer);
- if(ttsTextLength > 0){
- sendToTts();
- }
- closeTts();
- JSONObject finalResponse = new JSONObject();
- finalResponse.put("role", "assistant");
- finalResponse.put("content", answer);
- finalResponse.put("completionTokens", completionTokens); // 模型生成回复转换为 Token 后的长度。
- finalResponse.put("promptTokens", promptTokens); // 用户的输入转换成 Token 后的长度。
- aiphoneRes.setBody(answer);
- return finalResponse;
- }
- }
- }
|