|
@@ -59,6 +59,7 @@ import java.util.ArrayList;
|
|
|
import java.util.Base64;
|
|
import java.util.Base64;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
|
|
import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
import static cn.iocoder.byzs.framework.common.pojo.CommonResult.error;
|
|
import static cn.iocoder.byzs.framework.common.pojo.CommonResult.error;
|
|
@@ -71,7 +72,7 @@ import static cn.iocoder.byzs.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATI
|
|
|
@Service
|
|
@Service
|
|
|
@Validated
|
|
@Validated
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
-public class WebQSAiServiceImpl {
|
|
|
|
|
|
|
+public class WebQSAiServiceImpl {
|
|
|
|
|
|
|
|
// DeepSeek API URL 固定
|
|
// DeepSeek API URL 固定
|
|
|
private static final String DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions";
|
|
private static final String DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions";
|
|
@@ -150,6 +151,9 @@ public class WebQSAiServiceImpl {
|
|
|
|
|
|
|
|
StringBuilder answer = new StringBuilder();
|
|
StringBuilder answer = new StringBuilder();
|
|
|
AtomicBoolean serviceClosed = new AtomicBoolean(false);
|
|
AtomicBoolean serviceClosed = new AtomicBoolean(false);
|
|
|
|
|
+ AtomicBoolean tokenLimitWarningSent = new AtomicBoolean(false);
|
|
|
|
|
+ AtomicInteger outputTokens = new AtomicInteger();
|
|
|
|
|
+ AtomicInteger reasoningTokens = new AtomicInteger();
|
|
|
|
|
|
|
|
return Flux.<CommonResult<AiChatMessageSendRespVO>>create(sink -> {
|
|
return Flux.<CommonResult<AiChatMessageSendRespVO>>create(sink -> {
|
|
|
try {
|
|
try {
|
|
@@ -183,6 +187,10 @@ public class WebQSAiServiceImpl {
|
|
|
String data = line.substring(6).trim();
|
|
String data = line.substring(6).trim();
|
|
|
|
|
|
|
|
if ("[DONE]".equals(data)) {
|
|
if ("[DONE]".equals(data)) {
|
|
|
|
|
+ log.info("[QS评分-Token用量][conversationId({}) model({}) " +
|
|
|
|
|
+ "outputTokens({}) reasoningTokens({})]",
|
|
|
|
|
+ conversation.getId(), model.getModel(), outputTokens.get(),
|
|
|
|
|
+ reasoningTokens.get());
|
|
|
TenantUtils.executeIgnore(() ->
|
|
TenantUtils.executeIgnore(() ->
|
|
|
chatMessageMapper.updateById(
|
|
chatMessageMapper.updateById(
|
|
|
new AiChatMessageDO().setId(assistantMessage.getId())
|
|
new AiChatMessageDO().setId(assistantMessage.getId())
|
|
@@ -193,6 +201,12 @@ public class WebQSAiServiceImpl {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
JsonNode chunk = objectMapper.readTree(data);
|
|
JsonNode chunk = objectMapper.readTree(data);
|
|
|
|
|
+ JsonNode usage = chunk.path("usage");
|
|
|
|
|
+ if (!usage.isMissingNode() && !usage.isNull()) {
|
|
|
|
|
+ outputTokens.set(usage.path("completion_tokens").asInt(0));
|
|
|
|
|
+ reasoningTokens.set(usage.path("completion_tokens_details")
|
|
|
|
|
+ .path("reasoning_tokens").asInt(0));
|
|
|
|
|
+ }
|
|
|
JsonNode choices = chunk.path("choices");
|
|
JsonNode choices = chunk.path("choices");
|
|
|
if (choices.isEmpty()) {
|
|
if (choices.isEmpty()) {
|
|
|
return;
|
|
return;
|
|
@@ -227,6 +241,17 @@ public class WebQSAiServiceImpl {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ JsonNode finishReasonNode = choices.path(0).path("finish_reason");
|
|
|
|
|
+ if (!finishReasonNode.isMissingNode() && !finishReasonNode.isNull()
|
|
|
|
|
+ && "length".equals(finishReasonNode.asText())
|
|
|
|
|
+ && tokenLimitWarningSent.compareAndSet(false, true)) {
|
|
|
|
|
+ String tokenLimitMessage = "\n\n本次评分内容较长,模型输出已达到最大 Token " +
|
|
|
|
|
+ "限制,当前结果可能不完整。请减少单次评分人数、缩短材料或重新发起评分。";
|
|
|
|
|
+ answer.append(tokenLimitMessage);
|
|
|
|
|
+ sink.next(success(createTextResponse(userMessage, assistantMessage,
|
|
|
|
|
+ tokenLimitMessage)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.warn("解析 DeepSeek 响应失败: {}, line: {}", e.getMessage(), line);
|
|
log.warn("解析 DeepSeek 响应失败: {}, line: {}", e.getMessage(), line);
|
|
|
}
|
|
}
|
|
@@ -396,7 +421,7 @@ public class WebQSAiServiceImpl {
|
|
|
// 组合最终内容
|
|
// 组合最终内容
|
|
|
StringBuilder fullContent = new StringBuilder();
|
|
StringBuilder fullContent = new StringBuilder();
|
|
|
if (extractedText.length() > 0) {
|
|
if (extractedText.length() > 0) {
|
|
|
- fullContent.append("【附件内容】\n").append(extractedText);
|
|
|
|
|
|
|
+ fullContent.append("【录音文字稿:附件内容开始】\n").append(extractedText).append("【附件内容结束】\n");
|
|
|
}
|
|
}
|
|
|
if (StrUtil.isNotBlank(userContent)) {
|
|
if (StrUtil.isNotBlank(userContent)) {
|
|
|
if (fullContent.length() > 0) {
|
|
if (fullContent.length() > 0) {
|