本地大漠模型联调,阿里百炼token

This commit is contained in:
2026-04-05 18:23:04 +08:00
parent db0bde39e3
commit 6043dc2665
8 changed files with 109 additions and 32 deletions

View File

@@ -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;

View File

@@ -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("音频分析本地 LLMmodel={}, chatCompletionsUrl={}", model, chatCompletionsUrl);
log.info("音频分析本地 LLMmodel={}, 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对象
*