提示词完善
This commit is contained in:
@@ -15,8 +15,8 @@ public enum AudioAnalysisSceneType {
|
|||||||
"qwen-plus"
|
"qwen-plus"
|
||||||
),
|
),
|
||||||
SCENARIO_SOFT_SALE(
|
SCENARIO_SOFT_SALE(
|
||||||
"prompts/audio_text_analysis_furniture_system.txt",
|
"prompts/audio_text_analysis_soft_sale_system_prompts.txt",
|
||||||
"prompts/audio_text_analysis_furniture_user.txt",
|
"prompts/audio_text_analysis_soft_sale_user_prompts.txt",
|
||||||
"qwen-plus"
|
"qwen-plus"
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ public enum AudioAnalysisSceneType {
|
|||||||
* 会议纪要/会议分析场景
|
* 会议纪要/会议分析场景
|
||||||
* 提示词文件和模型可根据实际需要进行调整。
|
* 提示词文件和模型可根据实际需要进行调整。
|
||||||
*/
|
*/
|
||||||
SCENARIO_COMMON_SALE(
|
SCENARIO_SUMMARY(
|
||||||
"prompts/audio_text_analysis_meeting_system.txt",
|
"prompts/audio_text_analysis_summary_system_prompts.txt",
|
||||||
"prompts/audio_text_analysis_meeting_user.txt",
|
"prompts/audio_text_analysis_summary_user_prompts.txt",
|
||||||
"qwen-plus"
|
"qwen-plus"
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class AudioManagementController {
|
|||||||
private static final String SCENARIO_FURNITURE_SALE = "FURNITURE";
|
private static final String SCENARIO_FURNITURE_SALE = "FURNITURE";
|
||||||
private static final String SCENARIO_MEETING_SUMMARY = "MEETING_SUMMARY";
|
private static final String SCENARIO_MEETING_SUMMARY = "MEETING_SUMMARY";
|
||||||
private static final String SCENARIO_CAR_SALE = "CAR_SALE";
|
private static final String SCENARIO_CAR_SALE = "CAR_SALE";
|
||||||
private static final String SCENARIO_COMMON_SALE = "COMMON_SALE";
|
private static final String SCENARIO_SUMMARY = "SUMMARY";
|
||||||
private static final String SCENARIO_SPEAKING_TRAINING= "SPEAKING_TRAINING"; //租赁模式
|
private static final String SCENARIO_SPEAKING_TRAINING= "SPEAKING_TRAINING"; //租赁模式
|
||||||
|
|
||||||
|
|
||||||
@@ -510,6 +510,7 @@ public class AudioManagementController {
|
|||||||
customerToUpdate.setDetailedAddress(audioManagement.getRemarks());
|
customerToUpdate.setDetailedAddress(audioManagement.getRemarks());
|
||||||
customerToUpdate.setSalesPhone(audioManagement.getSalesPhone());
|
customerToUpdate.setSalesPhone(audioManagement.getSalesPhone());
|
||||||
customerToUpdate.setSalesName(audioManagement.getSalesName());
|
customerToUpdate.setSalesName(audioManagement.getSalesName());
|
||||||
|
customerToUpdate.setRecordingCount(customerToUpdate.getRecordingCount()+1);
|
||||||
customerToUpdate.setUpdateTime(LocalDateTime.now());
|
customerToUpdate.setUpdateTime(LocalDateTime.now());
|
||||||
customerManagementService.updateById(customerToUpdate);
|
customerManagementService.updateById(customerToUpdate);
|
||||||
log.info("同步更新客户信息成功,客户ID: {}", customerToUpdate.getId());
|
log.info("同步更新客户信息成功,客户ID: {}", customerToUpdate.getId());
|
||||||
@@ -728,6 +729,15 @@ public class AudioManagementController {
|
|||||||
audioManagement.getCustomerName(),
|
audioManagement.getCustomerName(),
|
||||||
audioManagement.getCustomerPhone()
|
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() );
|
log.info("llm return furniture Analysis : ",furniture.toString() );
|
||||||
if (recordingText == null || recordingText.trim().isEmpty()) {
|
if (recordingText == null || recordingText.trim().isEmpty()) {
|
||||||
result.put("success", false);
|
result.put("success", false);
|
||||||
@@ -762,8 +772,8 @@ public class AudioManagementController {
|
|||||||
if (SCENARIO_FURNITURE_SALE.equals(s)) {
|
if (SCENARIO_FURNITURE_SALE.equals(s)) {
|
||||||
return AudioAnalysisSceneType.SCENARIO_FURNITURE_SALE;
|
return AudioAnalysisSceneType.SCENARIO_FURNITURE_SALE;
|
||||||
}
|
}
|
||||||
if (SCENARIO_COMMON_SALE.equals(s)) {
|
if (SCENARIO_SUMMARY.equals(s)) {
|
||||||
return AudioAnalysisSceneType.SCENARIO_COMMON_SALE;
|
return AudioAnalysisSceneType.SCENARIO_SUMMARY;
|
||||||
}
|
}
|
||||||
if (SCENARIO_CAR_SALE.equals(s)) {
|
if (SCENARIO_CAR_SALE.equals(s)) {
|
||||||
return AudioAnalysisSceneType.SCENARIO_CAR_SALE;
|
return AudioAnalysisSceneType.SCENARIO_CAR_SALE;
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ public class SalesManagementController {
|
|||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
try {
|
try {
|
||||||
LambdaQueryWrapper<SalesManagement> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<SalesManagement> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(SalesManagement::getLoginAccount, loginAccount);
|
queryWrapper.like(SalesManagement::getLoginAccount, loginAccount);
|
||||||
List<SalesManagement> salesList = salesManagementService.list(queryWrapper);
|
List<SalesManagement> salesList = salesManagementService.list(queryWrapper);
|
||||||
result.put("success", true);
|
result.put("success", true);
|
||||||
result.put("message", "查询成功");
|
result.put("message", "查询成功");
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ public interface IAudioTextAnalysisLlmService {
|
|||||||
*/
|
*/
|
||||||
String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与 {@link #generateSummaryByLLM} 相同的提示词与场景逻辑,但通过本地 OpenAI 兼容接口调用大模型
|
||||||
|
* (配置项与客户问答本地模型一致:{@code ai.qa.local.chat-url}、{@code ai.qa.local.default-model}、{@code ai.qa.local.max-tokens};
|
||||||
|
* 另支持 {@code ai.qa.local.context-length}、{@code ai.qa.local.token-margin}、{@code ai.qa.local.min-output-tokens}、{@code ai.qa.local.input-tokens-per-char} 以避免超出本地模型上下文。)
|
||||||
|
*
|
||||||
|
* @param sceneType 业务场景类型
|
||||||
|
* @param recordingText 录音转写文本
|
||||||
|
* @return 大模型返回的原始内容
|
||||||
|
*/
|
||||||
|
String generateSummaryByLocalLLM(AudioAnalysisSceneType sceneType, String recordingText);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理完整的业务逻辑:调用大模型生成总结,解析JSON,保存数据
|
* 处理完整的业务逻辑:调用大模型生成总结,解析JSON,保存数据
|
||||||
* 此方法用于家具场景的完整业务处理
|
* 此方法用于家具场景的完整业务处理
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ import com.rj.service.IAudioTextAnalysisFurnitureService;
|
|||||||
import com.rj.service.IAudioTextAnalysisLlmService;
|
import com.rj.service.IAudioTextAnalysisLlmService;
|
||||||
import com.rj.service.IAudioTextAnalysisSopService;
|
import com.rj.service.IAudioTextAnalysisSopService;
|
||||||
import com.rj.service.ITodoItemService;
|
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 lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -33,6 +39,9 @@ import java.util.Map;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 音频文本分析 - 大模型通用服务实现
|
* 音频文本分析 - 大模型通用服务实现
|
||||||
@@ -59,6 +68,54 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IAudioTextAnalysisSopService sopService;
|
private IAudioTextAnalysisSopService sopService;
|
||||||
|
|
||||||
|
@Value("${ai.qa.local.chat-url:http://192.168.1.44:8000/v1/chat/completions}")
|
||||||
|
private String chatCompletionsUrl;
|
||||||
|
|
||||||
|
@Value("${ai.qa.local.default-model:Qwen2.5-7B-Instruct}")
|
||||||
|
private String localDefaultModel;
|
||||||
|
|
||||||
|
/** 单次补全上限(OpenAI max_tokens);与「模型总上下文 context-length」不是同一概念 */
|
||||||
|
@Value("${ai.qa.local.max-tokens:512}")
|
||||||
|
private int localMaxTokens;
|
||||||
|
|
||||||
|
/** 本地模型上下文长度(与推理侧 max context 一致,用于避免 input+max_tokens 超过上限;默认 16384) */
|
||||||
|
@Value("${ai.qa.local.context-length:16384}")
|
||||||
|
private int localContextLength;
|
||||||
|
|
||||||
|
/** 预留余量,避免服务端计数与本地估算不一致 */
|
||||||
|
@Value("${ai.qa.local.token-margin:80}")
|
||||||
|
private int localTokenMargin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截断转写时希望保留的「最小输出预算」启发值(须显著小于 context-length,且不得大于 max-tokens,
|
||||||
|
* 因为实际 effMax 恒为 min(max-tokens, 剩余上下文))。
|
||||||
|
*/
|
||||||
|
@Value("${ai.qa.local.min-output-tokens:128}")
|
||||||
|
private int configuredLocalMinOutputTokens;
|
||||||
|
|
||||||
|
/** {@link #configuredLocalMinOutputTokens} 经与 max-tokens 对齐后的实际取值 */
|
||||||
|
private int effectiveLocalMinOutputTokens;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对完整 prompt 字符串做输入 token 的保守上界估算(偏大会多截断、偏少仍可能 400)。
|
||||||
|
* 中文为主可保持默认;英文偏多时可酌情调低(如 0.35)。
|
||||||
|
*/
|
||||||
|
@Value("${ai.qa.local.input-tokens-per-char:1.25}")
|
||||||
|
private double localInputTokensPerChar;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
void clampLocalMinOutputTokens() {
|
||||||
|
effectiveLocalMinOutputTokens = Math.max(1, Math.min(configuredLocalMinOutputTokens, localMaxTokens));
|
||||||
|
if (effectiveLocalMinOutputTokens < configuredLocalMinOutputTokens) {
|
||||||
|
log.warn(
|
||||||
|
"ai.qa.local.min-output-tokens={} 大于 ai.qa.local.max-tokens={}:有效输出 effMax 恒 ≤ max-tokens,已钳制为 {}。"
|
||||||
|
+ " 切勿将 min-output-tokens 配成与 context-length 相同。",
|
||||||
|
configuredLocalMinOutputTokens,
|
||||||
|
localMaxTokens,
|
||||||
|
effectiveLocalMinOutputTokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
public String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
@@ -112,6 +169,195 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSummaryByLocalLLM(AudioAnalysisSceneType sceneType, String recordingText) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
log.info("开始调用本地大模型生成总结,场景: {}", sceneType);
|
||||||
|
|
||||||
|
String systemPrompt = getSystemPrompt(sceneType);
|
||||||
|
String userPromptTemplate = getUserPromptTemplate(sceneType);
|
||||||
|
String recordingForPrompt = fitRecordingTextToLocalContext(systemPrompt, userPromptTemplate, recordingText);
|
||||||
|
String userPrompt = buildPrompt(userPromptTemplate, recordingForPrompt);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int effectiveMaxTokens = computeEffectiveLocalMaxTokens(systemPrompt, userPrompt);
|
||||||
|
String rawContent = callLocalOpenAiChatCompletionsByLangChain4j(
|
||||||
|
systemPrompt,
|
||||||
|
userPrompt,
|
||||||
|
localDefaultModel,
|
||||||
|
effectiveMaxTokens);
|
||||||
|
log.info("本地大模型生成总结完成,场景: {}, 结果长度: {}",
|
||||||
|
sceneType, rawContent != null ? rawContent.length() : 0);
|
||||||
|
return rawContent;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用本地大模型生成总结失败, 场景: {}", sceneType, e);
|
||||||
|
throw new RuntimeException("调用本地大模型失败: " + e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
long durationMillis = endTime - startTime;
|
||||||
|
double durationSeconds = durationMillis / 1000.0;
|
||||||
|
double durationMinutes = durationSeconds / 60.0;
|
||||||
|
log.info("调用本地大模型耗时: {} 毫秒, {} 秒, {} 分钟, 场景: {}",
|
||||||
|
durationMillis,
|
||||||
|
String.format("%.2f", durationSeconds),
|
||||||
|
String.format("%.2f", durationMinutes),
|
||||||
|
sceneType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与 {@link AiQaCustomerAskServiceImpl} 中非流式问答路径一致:LangChain4j + OpenAI 兼容接口,流式聚合为完整文本。
|
||||||
|
*/
|
||||||
|
private String callLocalOpenAiChatCompletionsByLangChain4j(
|
||||||
|
String systemPrompt, String userContent, String model, int maxTokens) {
|
||||||
|
log.info("音频分析本地 LLM:model={}, chatCompletionsUrl={}", model, chatCompletionsUrl);
|
||||||
|
String openAiBaseUrl = normalizeOpenAiBaseUrl(chatCompletionsUrl);
|
||||||
|
OpenAiStreamingChatModel chatModel = OpenAiStreamingChatModel.builder()
|
||||||
|
.baseUrl(openAiBaseUrl)
|
||||||
|
.apiKey("not-used")
|
||||||
|
.modelName(model)
|
||||||
|
.maxTokens(maxTokens)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String prompt = buildFullLocalLlmPrompt(systemPrompt, userContent);
|
||||||
|
StringBuilder answerBuffer = new StringBuilder();
|
||||||
|
CountDownLatch done = new CountDownLatch(1);
|
||||||
|
AtomicReference<Throwable> errorRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
chatModel.chat(prompt, new StreamingChatResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onPartialResponse(String partialResponse) {
|
||||||
|
if (partialResponse != null && !partialResponse.isEmpty()) {
|
||||||
|
answerBuffer.append(partialResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompleteResponse(ChatResponse completeResponse) {
|
||||||
|
done.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable error) {
|
||||||
|
errorRef.set(error);
|
||||||
|
done.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean finished = done.await(120, TimeUnit.SECONDS);
|
||||||
|
if (!finished) {
|
||||||
|
throw new IllegalStateException("本地大模型流式调用超时(120s)");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IllegalStateException("本地大模型流式调用被中断", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorRef.get() != null) {
|
||||||
|
throw new IllegalStateException("本地大模型流式调用失败: " + errorRef.get().getMessage(), errorRef.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
String answer = answerBuffer.toString();
|
||||||
|
if (answer.isEmpty()) {
|
||||||
|
throw new IllegalStateException("本地大模型流式调用返回空响应");
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOpenAiBaseUrl(String rawUrl) {
|
||||||
|
if (rawUrl == null || rawUrl.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("ai.qa.local.chat-url 不能为空");
|
||||||
|
}
|
||||||
|
String normalized = rawUrl.trim();
|
||||||
|
while (normalized.endsWith("/")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1);
|
||||||
|
}
|
||||||
|
String suffix = "/chat/completions";
|
||||||
|
if (normalized.endsWith(suffix)) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - suffix.length());
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildFullLocalLlmPrompt(String systemPrompt, String userContent) {
|
||||||
|
return "系统指令:" + systemPrompt + "\n\n用户输入:" + userContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int estimateLocalPromptInputTokens(String fullPrompt) {
|
||||||
|
if (fullPrompt == null || fullPrompt.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int) Math.ceil(fullPrompt.length() * localInputTokensPerChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeEffectiveLocalMaxTokens(String systemPrompt, String userPrompt) {
|
||||||
|
String full = buildFullLocalLlmPrompt(systemPrompt, userPrompt);
|
||||||
|
int estIn = estimateLocalPromptInputTokens(full);
|
||||||
|
int capByContext = localContextLength - localTokenMargin - estIn;
|
||||||
|
int effective = Math.min(localMaxTokens, capByContext);
|
||||||
|
return Math.max(1, effective);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在不超过本地模型上下文的前提下,尽量保留更多转写文本;必要时从尾部截断。
|
||||||
|
*/
|
||||||
|
private String fitRecordingTextToLocalContext(
|
||||||
|
String systemPrompt, String userPromptTemplate, String recordingText) {
|
||||||
|
String recording = recordingText != null ? recordingText : "";
|
||||||
|
if (recording.isEmpty()) {
|
||||||
|
return recording;
|
||||||
|
}
|
||||||
|
String userFull = buildPrompt(userPromptTemplate, recording);
|
||||||
|
String full = buildFullLocalLlmPrompt(systemPrompt, userFull);
|
||||||
|
int est = estimateLocalPromptInputTokens(full);
|
||||||
|
int effMax = Math.min(localMaxTokens, localContextLength - localTokenMargin - est);
|
||||||
|
if (effMax >= effectiveLocalMinOutputTokens) {
|
||||||
|
return recording;
|
||||||
|
}
|
||||||
|
int lo = 0;
|
||||||
|
int hi = recording.length();
|
||||||
|
int best = 0;
|
||||||
|
while (lo <= hi) {
|
||||||
|
int mid = (lo + hi) >>> 1;
|
||||||
|
String sub = recording.substring(0, mid);
|
||||||
|
userFull = buildPrompt(userPromptTemplate, sub);
|
||||||
|
full = buildFullLocalLlmPrompt(systemPrompt, userFull);
|
||||||
|
est = estimateLocalPromptInputTokens(full);
|
||||||
|
effMax = Math.min(localMaxTokens, localContextLength - localTokenMargin - est);
|
||||||
|
if (effMax >= effectiveLocalMinOutputTokens) {
|
||||||
|
best = mid;
|
||||||
|
lo = mid + 1;
|
||||||
|
} else {
|
||||||
|
hi = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best >= recording.length()) {
|
||||||
|
return recording;
|
||||||
|
}
|
||||||
|
if (best <= 0) {
|
||||||
|
userFull = buildPrompt(userPromptTemplate, "");
|
||||||
|
full = buildFullLocalLlmPrompt(systemPrompt, userFull);
|
||||||
|
int estEmpty = estimateLocalPromptInputTokens(full);
|
||||||
|
int effMaxEmpty = Math.min(localMaxTokens, localContextLength - localTokenMargin - estEmpty);
|
||||||
|
log.warn(
|
||||||
|
"本地 LLM 上下文过紧:无转写时估算剩余输出预算 effMax≈{}(max-tokens={}),仍小于 effective min-output-tokens={}"
|
||||||
|
+ "(配置 min-output-tokens={})。请减小提示词、提高 context-length,或降低 min-output-tokens / 提高 max-tokens。将仍尝试调用。",
|
||||||
|
effMaxEmpty,
|
||||||
|
localMaxTokens,
|
||||||
|
effectiveLocalMinOutputTokens,
|
||||||
|
configuredLocalMinOutputTokens);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
log.warn(
|
||||||
|
"录音转写过长,已按本地模型上下文截断:原长度 {} 字符,保留 {} 字符(context-length={},max-tokens={})",
|
||||||
|
recording.length(),
|
||||||
|
best,
|
||||||
|
localContextLength,
|
||||||
|
localMaxTokens);
|
||||||
|
return recording.substring(0, best);
|
||||||
|
}
|
||||||
|
|
||||||
private String buildPrompt(String template, String recordingText) {
|
private String buildPrompt(String template, String recordingText) {
|
||||||
if (template == null) {
|
if (template == null) {
|
||||||
return recordingText != null ? recordingText : "";
|
return recordingText != null ? recordingText : "";
|
||||||
@@ -245,10 +491,18 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
|
|||||||
String ownerPhone,
|
String ownerPhone,
|
||||||
String customerName,
|
String customerName,
|
||||||
String customerPhone) {
|
String customerPhone) {
|
||||||
|
// 1. 调用大模型生成总结
|
||||||
|
String rawContent = generateSummaryByLocalLLM(sceneType, recordingText);
|
||||||
|
if (rawContent == null || rawContent.trim().isEmpty()) {
|
||||||
|
log.warn("大模型返回内容为空");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
|
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
|
||||||
furniture.setRecordingText(recordingText);
|
// 4. 更新AudioManagement的summary字段
|
||||||
|
AudioManagement audioManagement = new AudioManagement();
|
||||||
|
audioManagement.setId(parentId);
|
||||||
|
audioManagement.setSummary(rawContent);
|
||||||
|
audioManagementService.updateById(audioManagement);
|
||||||
|
|
||||||
return furniture;
|
return furniture;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ ai:
|
|||||||
local:
|
local:
|
||||||
chat-url: http://192.168.1.44:8000/v1/chat/completions
|
chat-url: http://192.168.1.44:8000/v1/chat/completions
|
||||||
default-model: Qwen2.5-7B-Instruct
|
default-model: Qwen2.5-7B-Instruct
|
||||||
max-tokens: 512
|
# 模型总上下文(与推理服务一致);勿与 max-tokens、min-output-tokens 混用同一数值
|
||||||
|
context-length: 16384
|
||||||
|
# 单次补全上限。过小(如 512)会导致课堂纪要等 JSON 总结被截断、字段残缺或不准;建议 2048~4096(须保证 input+max_tokens 不超过 context-length)
|
||||||
|
max-tokens: 2048
|
||||||
|
min-output-tokens: 128
|
||||||
|
|
||||||
langchain4j:
|
langchain4j:
|
||||||
open-ai:
|
open-ai:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
请阅读以下录音文本,从中提取客户信息并生成 JSON。必须覆盖所有字段,缺失信息请基于语境合理推断或标注"暂无信息"。
|
请阅读以下录音文本,从中提取重要关键信息并生成 JSON。必须覆盖所有字段,缺失信息请基于语境合理推断或标注"暂无信息"。
|
||||||
|
|
||||||
字段要求:
|
字段要求:
|
||||||
1. style_type:主题类型,仅可填「会议纪要」「课堂纪要」「面试纪要」。
|
1. style_type:主题类型,仅可填「会议纪要」「课堂纪要」「面试纪要」。
|
||||||
|
|||||||
Reference in New Issue
Block a user