本地大漠模型联调,阿里百炼token
This commit is contained in:
11
src/main/java/com/rj/common/LocalLlmSummaryResult.java
Normal file
11
src/main/java/com/rj/common/LocalLlmSummaryResult.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.rj.common;
|
||||
|
||||
/**
|
||||
* 本地 OpenAI 兼容接口生成总结的文本与 token 用量(来自 LangChain4j / 服务端 usage 字段)。
|
||||
*/
|
||||
public record LocalLlmSummaryResult(
|
||||
String rawContent,
|
||||
Integer totalTokens,
|
||||
Integer inputTokens,
|
||||
Integer outputTokens) {
|
||||
}
|
||||
@@ -720,16 +720,7 @@ public class AudioManagementController {
|
||||
|
||||
|
||||
// 4. 通过业务层通用服务调用大模型生成总结并保存数据
|
||||
AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSave(
|
||||
sceneType,
|
||||
recordingText,
|
||||
audioManagement.getId(),
|
||||
audioManagement.getSalesName(),
|
||||
audioManagement.getSalesPhone(),
|
||||
audioManagement.getCustomerName(),
|
||||
audioManagement.getCustomerPhone()
|
||||
);
|
||||
// AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSave_V2(
|
||||
// AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSave(
|
||||
// sceneType,
|
||||
// recordingText,
|
||||
// audioManagement.getId(),
|
||||
@@ -738,6 +729,15 @@ public class AudioManagementController {
|
||||
// audioManagement.getCustomerName(),
|
||||
// audioManagement.getCustomerPhone()
|
||||
// );
|
||||
AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSave_V2(
|
||||
sceneType,
|
||||
recordingText,
|
||||
audioManagement.getId(),
|
||||
audioManagement.getSalesName(),
|
||||
audioManagement.getSalesPhone(),
|
||||
audioManagement.getCustomerName(),
|
||||
audioManagement.getCustomerPhone()
|
||||
);
|
||||
log.info("llm return furniture Analysis : ",furniture.toString() );
|
||||
if (recordingText == null || recordingText.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
|
||||
@@ -28,6 +28,6 @@ public class AiQaCustomerAskRequestDto {
|
||||
@Schema(description = "可选系统提示词,不传则使用默认助手设定")
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "max_tokens,不传则使用配置 ai.qa.local.max-tokens(默认 512)")
|
||||
@Schema(description = "max_tokens,不传则使用配置 ai.qa.local.max-tokens(默认 2048)")
|
||||
private Integer maxTokens;
|
||||
}
|
||||
|
||||
@@ -189,6 +189,18 @@ public class AudioManagement implements Serializable {
|
||||
@TableField("summary")
|
||||
private String summary;
|
||||
|
||||
@Schema(description = "大模型调用总 token 数")
|
||||
@TableField("total_tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
@Schema(description = "大模型输入 token 数")
|
||||
@TableField("input_tokens")
|
||||
private Integer inputTokens;
|
||||
|
||||
@Schema(description = "大模型输出 token 数")
|
||||
@TableField("output_tokens")
|
||||
private Integer outputTokens;
|
||||
|
||||
/**
|
||||
* 前端上传的录音文件
|
||||
* 此字段不保存在数据库中,仅用于接收前端上传的文件
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.rj.service;
|
||||
|
||||
import com.alibaba.dashscope.aigc.generation.GenerationResult;
|
||||
import com.rj.common.AudioAnalysisSceneType;
|
||||
import com.rj.common.LocalLlmSummaryResult;
|
||||
import com.rj.entity.AudioTextAnalysisFurniture;
|
||||
|
||||
/**
|
||||
@@ -19,7 +21,7 @@ public interface IAudioTextAnalysisLlmService {
|
||||
* @param recordingText 录音转写文本
|
||||
* @return 大模型返回的原始内容(一般是 JSON 字符串或结构化文本)
|
||||
*/
|
||||
String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
||||
GenerationResult generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
||||
|
||||
/**
|
||||
* 与 {@link #generateSummaryByLLM} 相同的提示词与场景逻辑,但通过本地 OpenAI 兼容接口调用大模型
|
||||
@@ -28,9 +30,9 @@ public interface IAudioTextAnalysisLlmService {
|
||||
*
|
||||
* @param sceneType 业务场景类型
|
||||
* @param recordingText 录音转写文本
|
||||
* @return 大模型返回的原始内容
|
||||
* @return 大模型返回的原始内容及 token 用量(若接口未返回 usage 则对应字段可能为 null)
|
||||
*/
|
||||
String generateSummaryByLocalLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
||||
LocalLlmSummaryResult generateSummaryByLocalLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
||||
|
||||
/**
|
||||
* 处理完整的业务逻辑:调用大模型生成总结,解析JSON,保存数据
|
||||
|
||||
@@ -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对象
|
||||
*
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
字段要求:
|
||||
1. style_type:主题类型,仅可填「会议纪要」「课堂纪要」「面试纪要」。
|
||||
2. members:团队成员,描述 团队成员组成 ,比如: 经理,员工, 面试者,面试管,老师 , 重点反应每类人员的数量,姓名, 优点,贡献大小 等。
|
||||
4. summary:一句话总结,应该简洁明了总结关键信息, 重点列举5到15个重点信息, 等关键信息, summary应该是树形结构,总共分3层 ,比如 summary是第一层 ,第二层 类似: summary1,summary2,summary3,summary[N] ,第三层 类似:summary1_1,summary1_2,summary1_3,summary1_[N]。
|
||||
4. summary:一句话总结,生成的描述不少于200汉字,应该简洁明了总结关键信息, 重点列举5到15个重点信息, 等关键信息, summary应该是树形结构,总共分3层 ,比如 summary是第一层 ,第二层 类似: summary1,summary2,summary3,summary[N] ,第三层 类似:summary1_1,summary1_2,summary1_3,summary1_[N]。
|
||||
5.summary1: 对summary 提供支持,summary1是对summary的详细解释的第一个理由, summary1是对summary深入分析的一部分 。
|
||||
6.summary1_1: 对summary1提供支持,summary1_1是对summary1的详细解释的第一个理由, summary1_1是对summary1深入分析的一部分 。
|
||||
7.summary1_[N]: 对summary1提供支持,summary1_[N]是对summary1的详细解释的第一个理由, summary1_[N]是对summary1深入分析的一部分 ,[N] 这里的N不能超过数字 10。
|
||||
|
||||
Reference in New Issue
Block a user