本地大漠模型联调,阿里百炼token
This commit is contained in:
@@ -57,7 +57,7 @@ public class AiQaCustomerAskServiceImpl implements IAiQaCustomerAskService {
|
||||
IAiQaItemService aiQaItemService,
|
||||
@Value("${ai.qa.local.chat-url:http://192.168.1.44:8000/v1/chat/completions}") String chatCompletionsUrl,
|
||||
@Value("${ai.qa.local.default-model:Qwen2.5-7B-Instruct}") String configDefaultModel,
|
||||
@Value("${ai.qa.local.max-tokens:512}") int configMaxTokens) {
|
||||
@Value("${ai.qa.local.max-tokens:2048}") int configMaxTokens) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
this.aiQaMainService = aiQaMainService;
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.rj.service.impl;
|
||||
import com.alibaba.dashscope.aigc.generation.Generation;
|
||||
import com.alibaba.dashscope.aigc.generation.GenerationParam;
|
||||
import com.alibaba.dashscope.aigc.generation.GenerationResult;
|
||||
import com.alibaba.dashscope.aigc.generation.GenerationUsage;
|
||||
import com.alibaba.dashscope.common.Message;
|
||||
import com.alibaba.dashscope.common.Role;
|
||||
import com.alibaba.dashscope.exception.InputRequiredException;
|
||||
@@ -12,6 +13,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rj.common.AudioAnalysisSceneType;
|
||||
import com.rj.common.LocalLlmSummaryResult;
|
||||
import com.rj.entity.AudioManagement;
|
||||
import com.rj.entity.AudioTextAnalysisFurniture;
|
||||
import com.rj.entity.AudioTextAnalysisSop;
|
||||
@@ -24,6 +26,7 @@ import com.rj.service.ITodoItemService;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||
import dev.langchain4j.model.output.TokenUsage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -50,6 +53,9 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Slf4j
|
||||
public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmService {
|
||||
|
||||
/** LangChain4j 流式调用结束后的文本与(可选)服务端 usage */
|
||||
private record LocalChatOutcome(String text, TokenUsage tokenUsage) {}
|
||||
|
||||
private static final Map<String, String> SYSTEM_PROMPT_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, String> USER_PROMPT_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -75,7 +81,7 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
private String localDefaultModel;
|
||||
|
||||
/** 单次补全上限(OpenAI max_tokens);与「模型总上下文 context-length」不是同一概念 */
|
||||
@Value("${ai.qa.local.max-tokens:512}")
|
||||
@Value("${ai.qa.local.max-tokens:2048}")
|
||||
private int localMaxTokens;
|
||||
|
||||
/** 本地模型上下文长度(与推理侧 max context 一致,用于避免 input+max_tokens 超过上限;默认 16384) */
|
||||
@@ -117,7 +123,7 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
||||
public GenerationResult generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("开始调用大模型生成总结,场景: {}", sceneType);
|
||||
|
||||
@@ -145,8 +151,9 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
try {
|
||||
GenerationResult call = gen.call(param);
|
||||
String rawContent = call.getOutput().getChoices().get(0).getMessage().getContent();
|
||||
log.info("大模型生成总结完成,场景: {}, 结果长度: {}", sceneType, rawContent != null ? rawContent.length() : 0);
|
||||
return rawContent;
|
||||
log.info("大模型生成总结完成,场景: {}, 输入长度:{},结果长度: {}", sceneType, recordingText.length(),rawContent != null ? rawContent.length() : 0);
|
||||
|
||||
return call;
|
||||
} catch (NoApiKeyException e) {
|
||||
log.error("API密钥未配置", e);
|
||||
throw new RuntimeException("API密钥未配置: " + e.getMessage(), e);
|
||||
@@ -170,7 +177,7 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSummaryByLocalLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
||||
public LocalLlmSummaryResult generateSummaryByLocalLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("开始调用本地大模型生成总结,场景: {}", sceneType);
|
||||
|
||||
@@ -181,14 +188,25 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
|
||||
try {
|
||||
int effectiveMaxTokens = computeEffectiveLocalMaxTokens(systemPrompt, userPrompt);
|
||||
String rawContent = callLocalOpenAiChatCompletionsByLangChain4j(
|
||||
LocalChatOutcome outcome = callLocalOpenAiChatCompletionsByLangChain4j(
|
||||
systemPrompt,
|
||||
userPrompt,
|
||||
localDefaultModel,
|
||||
effectiveMaxTokens);
|
||||
String rawContent = outcome.text();
|
||||
TokenUsage usage = outcome.tokenUsage();
|
||||
Integer total = usage != null ? usage.totalTokenCount() : null;
|
||||
Integer input = usage != null ? usage.inputTokenCount() : null;
|
||||
Integer output = usage != null ? usage.outputTokenCount() : null;
|
||||
log.info(
|
||||
"本地大模型生成总结完成,token花费,总token: {}, 输入token: {}, 输出token: {}, 输出token详情: {}",
|
||||
total,
|
||||
input,
|
||||
output,
|
||||
usage != null ? usage.toString() : "无");
|
||||
log.info("本地大模型生成总结完成,场景: {}, 结果长度: {}",
|
||||
sceneType, rawContent != null ? rawContent.length() : 0);
|
||||
return rawContent;
|
||||
return new LocalLlmSummaryResult(rawContent, total, input, output);
|
||||
} catch (Exception e) {
|
||||
log.error("调用本地大模型生成总结失败, 场景: {}", sceneType, e);
|
||||
throw new RuntimeException("调用本地大模型失败: " + e.getMessage(), e);
|
||||
@@ -208,9 +226,9 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
/**
|
||||
* 与 {@link AiQaCustomerAskServiceImpl} 中非流式问答路径一致:LangChain4j + OpenAI 兼容接口,流式聚合为完整文本。
|
||||
*/
|
||||
private String callLocalOpenAiChatCompletionsByLangChain4j(
|
||||
private LocalChatOutcome callLocalOpenAiChatCompletionsByLangChain4j(
|
||||
String systemPrompt, String userContent, String model, int maxTokens) {
|
||||
log.info("音频分析本地 LLM:model={}, chatCompletionsUrl={}", model, chatCompletionsUrl);
|
||||
log.info("音频分析本地 LLM:model={}, chatCompletionsUrl={}, maxTokens={}", model, chatCompletionsUrl,maxTokens);
|
||||
String openAiBaseUrl = normalizeOpenAiBaseUrl(chatCompletionsUrl);
|
||||
OpenAiStreamingChatModel chatModel = OpenAiStreamingChatModel.builder()
|
||||
.baseUrl(openAiBaseUrl)
|
||||
@@ -223,6 +241,7 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
StringBuilder answerBuffer = new StringBuilder();
|
||||
CountDownLatch done = new CountDownLatch(1);
|
||||
AtomicReference<Throwable> errorRef = new AtomicReference<>();
|
||||
AtomicReference<TokenUsage> usageRef = new AtomicReference<>();
|
||||
|
||||
chatModel.chat(prompt, new StreamingChatResponseHandler() {
|
||||
@Override
|
||||
@@ -234,6 +253,9 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
|
||||
@Override
|
||||
public void onCompleteResponse(ChatResponse completeResponse) {
|
||||
if (completeResponse != null) {
|
||||
usageRef.set(completeResponse.tokenUsage());
|
||||
}
|
||||
done.countDown();
|
||||
}
|
||||
|
||||
@@ -262,7 +284,7 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
if (answer.isEmpty()) {
|
||||
throw new IllegalStateException("本地大模型流式调用返回空响应");
|
||||
}
|
||||
return answer;
|
||||
return new LocalChatOutcome(answer, usageRef.get());
|
||||
}
|
||||
|
||||
private String normalizeOpenAiBaseUrl(String rawUrl) {
|
||||
@@ -429,8 +451,11 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
String customerName,
|
||||
String customerPhone) {
|
||||
try {
|
||||
GenerationResult generationResult = generateSummaryByLLM(sceneType, recordingText);
|
||||
log.info("大模型生成总结完成,token花费,总token: {}, 输入token: {}, 输出token: {}, 输出token详情: {}",
|
||||
generationResult.getUsage().getTotalTokens(),generationResult.getUsage().getInputTokens(),generationResult.getUsage().getOutputTokens(),generationResult.getUsage().getOutputTokensDetails());
|
||||
// 1. 调用大模型生成总结
|
||||
String rawContent = generateSummaryByLLM(sceneType, recordingText);
|
||||
String rawContent = generationResult.getOutput().getChoices().get(0).getMessage().getContent();
|
||||
if (rawContent == null || rawContent.trim().isEmpty()) {
|
||||
log.warn("大模型返回内容为空");
|
||||
return null;
|
||||
@@ -443,10 +468,11 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
// 3. 解析JSON并填充对象
|
||||
applyStructuredResult(furniture, rawContent, parentId, ownerName, ownerPhone, customerName, customerPhone);
|
||||
|
||||
// 4. 更新AudioManagement的summary字段
|
||||
// 4. 更新AudioManagement的summary与 token 用量
|
||||
AudioManagement audioManagement = new AudioManagement();
|
||||
audioManagement.setId(parentId);
|
||||
audioManagement.setSummary(furniture.getSummarySentence());
|
||||
audioManagement.setSummary(rawContent);
|
||||
applyDashScopeUsageToAudioManagement(audioManagement, generationResult);
|
||||
audioManagementService.updateById(audioManagement);
|
||||
|
||||
// 5. 保存或更新AudioTextAnalysisFurniture记录
|
||||
@@ -491,21 +517,47 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
||||
String ownerPhone,
|
||||
String customerName,
|
||||
String customerPhone) {
|
||||
// 1. 调用大模型生成总结
|
||||
String rawContent = generateSummaryByLocalLLM(sceneType, recordingText);
|
||||
LocalLlmSummaryResult llmResult = generateSummaryByLocalLLM(sceneType, recordingText);
|
||||
String rawContent = llmResult.rawContent();
|
||||
if (rawContent == null || rawContent.trim().isEmpty()) {
|
||||
log.warn("大模型返回内容为空");
|
||||
return null;
|
||||
}
|
||||
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
|
||||
// 4. 更新AudioManagement的summary字段
|
||||
AudioManagement audioManagement = new AudioManagement();
|
||||
audioManagement.setId(parentId);
|
||||
audioManagement.setSummary(rawContent);
|
||||
applyLocalLlmTokenCountsToAudioManagement(audioManagement, llmResult);
|
||||
audioManagementService.updateById(audioManagement);
|
||||
|
||||
return furniture;
|
||||
}
|
||||
|
||||
private void applyDashScopeUsageToAudioManagement(AudioManagement target, GenerationResult result) {
|
||||
if (result == null || result.getUsage() == null) {
|
||||
return;
|
||||
}
|
||||
GenerationUsage usage = result.getUsage();
|
||||
target.setTotalTokens(usage.getTotalTokens());
|
||||
target.setInputTokens(usage.getInputTokens());
|
||||
target.setOutputTokens(usage.getOutputTokens());
|
||||
}
|
||||
|
||||
private void applyLocalLlmTokenCountsToAudioManagement(AudioManagement target, LocalLlmSummaryResult result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
if (result.totalTokens() != null) {
|
||||
target.setTotalTokens(result.totalTokens());
|
||||
}
|
||||
if (result.inputTokens() != null) {
|
||||
target.setInputTokens(result.inputTokens());
|
||||
}
|
||||
if (result.outputTokens() != null) {
|
||||
target.setOutputTokens(result.outputTokens());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析大模型返回的JSON结果并填充到AudioTextAnalysisFurniture对象
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user