DeepSeekChat.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package com.telerobot.fs.robot.impl;
  2. import cn.hutool.json.JSONUtil;
  3. import com.alibaba.fastjson.JSON;
  4. import com.alibaba.fastjson.JSONArray;
  5. import com.alibaba.fastjson.JSONObject;
  6. import com.telerobot.fs.config.AppContextProvider;
  7. import com.telerobot.fs.entity.dao.LlmKb;
  8. import com.telerobot.fs.entity.dto.LlmAiphoneRes;
  9. import com.telerobot.fs.entity.dto.llm.LlmAccount;
  10. import com.telerobot.fs.entity.po.HangupCause;
  11. import com.telerobot.fs.entity.pojo.LlmToolRequest;
  12. import com.telerobot.fs.robot.AbstractChatRobot;
  13. import com.telerobot.fs.service.SysService;
  14. import com.telerobot.fs.utils.CommonUtils;
  15. import okhttp3.*;
  16. import okio.BufferedSource;
  17. import org.apache.commons.lang.StringUtils;
  18. import org.apache.http.HttpStatus;
  19. import java.io.IOException;
  20. import java.util.List;
  21. public class DeepSeekChat extends AbstractChatRobot {
  22. @Override
  23. public LlmAiphoneRes talkWithAiAgent(String question, Boolean... withKbResponse) {
  24. LlmAiphoneRes aiphoneRes = new LlmAiphoneRes();
  25. aiphoneRes.setStatus_code(1);
  26. aiphoneRes.setClose_phone(0);
  27. aiphoneRes.setIfcan_interrupt(0);
  28. if (firstRound) {
  29. firstRound = false;
  30. String llmTips = ((LlmAccount) getAccount()).getLlmTips();
  31. int catId = llmAccountInfo.kbCatId;
  32. String topicsVar = "${kbTopicList}";
  33. if(catId != -1) {
  34. if (llmTips.contains(topicsVar)) {
  35. List<LlmKb> kbList = AppContextProvider.getBean(SysService.class).getKbListByCatId(catId);
  36. StringBuilder topics = new StringBuilder();
  37. for (LlmKb llmKb : kbList) {
  38. topics.append("*").append(llmKb.getTitle()).append("\r\n");
  39. }
  40. topics.append("\r\n");
  41. llmTips = llmTips.replace(topicsVar, topics.toString());
  42. logger.info("{} topic list: {}", uuid, topics.toString());
  43. } else {
  44. logger.warn("{} {} tag not found, the knowledge base function will be unavailable. ", uuid, topicsVar);
  45. }
  46. }else {
  47. logger.warn("{} The current model {} doesn’t have a knowledge base linked to it. ", uuid, ((LlmAccount) getAccount()).getModelName());
  48. }
  49. String tips = llmTips + "\r\n\r\n" + ((LlmAccount) getAccount()).getFaqContext();
  50. JSONObject bizJson = new JSONObject();
  51. if (null != callDetail && null != callDetail.getOutboundPhoneInfo() && StringUtils.isNotBlank(callDetail.getOutboundPhoneInfo().getBizJson())) {
  52. tips += "\n bizJson:" + callDetail.getOutboundPhoneInfo().getBizJson();
  53. bizJson = JSONObject.parseObject(callDetail.getOutboundPhoneInfo().getBizJson());
  54. }
  55. addDialogue(ROLE_SYSTEM, tips);
  56. String openingRemarks = replaceParams(llmAccountInfo.openingRemarks, bizJson);
  57. addDialogue(ROLE_ASSISTANT, openingRemarks);
  58. ttsTextCache.add(openingRemarks);
  59. sendToTts();
  60. closeTts();
  61. aiphoneRes.setBody(openingRemarks);
  62. return aiphoneRes;
  63. } else {
  64. if (withKbResponse.length > 0 && !withKbResponse[0]) {
  65. if (!StringUtils.isEmpty(question)) {
  66. addDialogue(ROLE_USER, question);
  67. } else {
  68. addDialogue(ROLE_USER, "NO_VOICE");
  69. String noVoiceTips = llmAccountInfo.customerNoVoiceTips;
  70. addDialogue(ROLE_ASSISTANT, noVoiceTips);
  71. ttsTextCache.add(noVoiceTips);
  72. sendToTts();
  73. closeTts();
  74. aiphoneRes.setBody(noVoiceTips);
  75. return aiphoneRes;
  76. }
  77. }
  78. try {
  79. JSONObject response = sendStreamingRequest(aiphoneRes, llmRoundMessages);
  80. if (null != response) {
  81. llmRoundMessages.add(response);
  82. } else {
  83. aiphoneRes.setStatus_code(0);
  84. }
  85. } catch (Throwable throwable) {
  86. aiphoneRes.setStatus_code(0);
  87. logger.error("{} talkWithAiAgent error: {}", uuid, CommonUtils.getStackTraceString(throwable.getStackTrace()));
  88. }
  89. return aiphoneRes;
  90. }
  91. }
  92. private JSONObject sendStreamingRequest(LlmAiphoneRes aiphoneRes, List<JSONObject> messages) throws IOException {
  93. JSONObject requestBody = new JSONObject();
  94. requestBody.put("model", ((LlmAccount)getAccount()).getModelName());
  95. requestBody.put("stream", true);
  96. // enable stream output
  97. JSONArray messagesArray = new JSONArray();
  98. messagesArray.addAll(messages);
  99. requestBody.put("messages", messagesArray);
  100. RequestBody body = RequestBody.create(
  101. MediaType.parse("application/json"),
  102. requestBody.toJSONString()
  103. );
  104. Request request = new Request.Builder()
  105. .url(getAccount().serverUrl)
  106. .post(body)
  107. .addHeader("Authorization", "Bearer " + ((LlmAccount)getAccount()).getApiKey())
  108. .build();
  109. boolean recvData = false;
  110. long startTime = System.currentTimeMillis();
  111. try (Response response = CLIENT.newCall(request).execute()) {
  112. if (!response.isSuccessful()) {
  113. ResponseBody responseBody = response.body();
  114. String bodyStr = responseBody != null ? responseBody.string() : "空响应体";
  115. // 2. 组装 完整日志(请求+响应全部信息)
  116. String log = "\n======= OKHTTP 完整请求响应 =======\n"
  117. + "请求URL:" + response.request().url() + "\n"
  118. + "请求方法:" + response.request().method() + "\n"
  119. + "请求头:\n" + response.request().headers() + "\n"
  120. + "响应状态码:" + response.code() + "\n"
  121. + "响应头:\n" + response.headers() + "\n"
  122. + "响应体:\n" + bodyStr + "\n"
  123. + "====================================\n";
  124. // 3. 用 error 级别打印(你要的)
  125. logger.error(log);
  126. logger.error("Model api error: http-code={}, msg={}, url={}",
  127. response.code(),
  128. response.message(),
  129. getAccount().serverUrl
  130. );
  131. if(response.code() == HttpStatus.SC_UNAUTHORIZED) {
  132. CommonUtils.setHangupCauseDetail(
  133. callDetail,
  134. HangupCause.LLM_API_KEY_INCORRECT,
  135. "http-status-code=" + response.code()
  136. );
  137. CommonUtils.hangupCallSession(uuid, HangupCause.LLM_API_KEY_INCORRECT.getCode());
  138. return null;
  139. }else{
  140. CommonUtils.setHangupCauseDetail(
  141. callDetail,
  142. HangupCause.LLM_API_SERVER_ERROR,
  143. "http-status-code=" + response.code()
  144. );
  145. }
  146. if(response.code() == HttpStatus.SC_UNAUTHORIZED || response.code() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
  147. throw new IOException("Unexpected code " + response);
  148. }else{
  149. return null;
  150. }
  151. }
  152. BufferedSource source = response.body().source();
  153. StringBuilder responseBuilder = new StringBuilder();
  154. Integer completionTokens = 0; // 模型生成回复转换为 Token 后的长度。
  155. Integer promptTokens = 0; // 用户的输入转换成 Token 后的长度。
  156. while (!source.exhausted()) {
  157. String line = source.readUtf8Line();
  158. if (line != null && line.startsWith("data: ")) {
  159. String jsonData = line.substring(6).trim(); // 去掉 "data: " 前缀
  160. if (jsonData.equals("[DONE]")) {
  161. break; // 流式响应结束
  162. }
  163. JSONObject jsonResponse = JSON.parseObject(jsonData);
  164. JSONObject message = jsonResponse.getJSONArray("choices")
  165. .getJSONObject(0)
  166. .getJSONObject("delta"); // 注意:流式响应中消息在 "delta" 字段中
  167. if (message.containsKey("content")) {
  168. String speechContent = message.getString("content");
  169. logger.info("{} speechContent: {}", getTraceId(), speechContent);
  170. if (!StringUtils.isEmpty(speechContent)) {
  171. if (speechContent.contains(LlmToolRequest.TRANSFER_TO_AGENT)) {
  172. aiphoneRes.setTransferToAgent(1);
  173. logger.info("{} `TRANSFER_TO_AGENT` command detected. ", getTraceId());
  174. }
  175. if (speechContent.contains(LlmToolRequest.HANGUP)) {
  176. aiphoneRes.setClose_phone(1);
  177. logger.info("{} `HANGUP` command detected. ", getTraceId());
  178. }
  179. if (!StringUtils.isEmpty(speechContent)) {
  180. speechContent = speechContent.replace(LlmToolRequest.TRANSFER_TO_AGENT,"")
  181. .replace(LlmToolRequest.HANGUP,"")
  182. .replace("`","");
  183. ttsTextCache.add(speechContent);
  184. ttsTextLength += speechContent.length();
  185. // 积攒足够的字数之后,才发送给tts,避免播放异常;
  186. if (ttsTextLength >= 5 && checkPauseFlag(speechContent)) {
  187. sendToTts();
  188. if (!recvData) {
  189. recvData = true;
  190. long costTime = (System.currentTimeMillis() - startTime);
  191. logger.info("http request cost time : {} ms.", costTime);
  192. aiphoneRes.setCostTime(costTime);
  193. }
  194. }
  195. }
  196. responseBuilder.append(speechContent);
  197. }
  198. }
  199. if (null != jsonResponse.get("usage")) {
  200. JSONObject usage = jsonResponse.getJSONObject("usage");
  201. completionTokens = usage.getInteger("completion_tokens");
  202. promptTokens = usage.getInteger("prompt_tokens");
  203. }
  204. }
  205. }
  206. String answer = responseBuilder.toString();
  207. logger.info("{} recv llm response end flag. answer={}", this.uuid, answer);
  208. if(ttsTextLength > 0){
  209. sendToTts();
  210. }
  211. closeTts();
  212. JSONObject finalResponse = new JSONObject();
  213. finalResponse.put("role", "assistant");
  214. finalResponse.put("content", answer);
  215. finalResponse.put("completionTokens", completionTokens); // 模型生成回复转换为 Token 后的长度。
  216. finalResponse.put("promptTokens", promptTokens); // 用户的输入转换成 Token 后的长度。
  217. aiphoneRes.setBody(answer);
  218. return finalResponse;
  219. }
  220. }
  221. }