调用ai模型分析家居场景

This commit is contained in:
zhonghua1
2026-01-11 10:06:00 +08:00
parent 599110d1a1
commit aca05abd73
5 changed files with 438 additions and 43 deletions

View File

@@ -139,5 +139,6 @@ hikari:

View File

@@ -7,6 +7,7 @@ import com.rj.common.AudioManagementConstants;
import com.rj.common.DataEnrichmentUtil;
import com.rj.entity.AudioManagement;
import com.rj.entity.AudioManagementSegments;
import com.rj.entity.AudioTextAnalysisFurniture;
import com.rj.entity.CustomerManagement;
import com.rj.service.*;
import com.rj.dto.AsrRequest;
@@ -44,7 +45,7 @@ import java.util.UUID;
public class AudioManagementController {
// 场景常量,避免在解析逻辑中硬编码字符串
private static final String SCENARIO_FURNITURE_SALE = "FURNITURE_SALE";
private static final String SCENARIO_FURNITURE_SALE = "FURNITURE";
private static final String SCENARIO_MEETING_SUMMARY = "MEETING_SUMMARY";
private static final String SCENARIO_CAR_SALE = "CAR_SALE";
private static final String SCENARIO_SPEAKING_TRAINING= "SPEAKING_TRAINING"; //租赁模式
@@ -637,6 +638,7 @@ public class AudioManagementController {
@Parameter(description = "录音信息", required = true)
@RequestBody AudioManagement audioManagement) {
Map<String, Object> result = new HashMap<>();
boolean success = true;
try {
if (audioManagement.getId() == null || audioManagement.getId().trim().isEmpty()) {
result.put("success", false);
@@ -651,6 +653,14 @@ public class AudioManagementController {
result.put("message", "记录不存在");
return ResponseEntity.badRequest().body(result);
}
// 2. 检查summary字段如果已存在总结内容直接返回提示不进行AI分析
String summary = audioManagement.getSummary();
if (summary != null && !summary.trim().isEmpty()) {
result.put("success", true);
result.put("message", "AI分析已完成");
result.put("data", audioManagement);
return ResponseEntity.ok(result);
}
// 3. 获取录音文本;如主表为空,则从分段表拼接
String recordingText = audioManagementFromDB.getRecordingText();
if (recordingText == null || recordingText.trim().isEmpty()) {
@@ -688,23 +698,35 @@ public class AudioManagementController {
// 4. 根据场景动态选择业务场景类型,调用业务层通用服务生成总结
AudioAnalysisSceneType sceneType = resolveSceneType(audioManagementFromDB.getScenario());
String rawContent = audioTextAnalysisLlmService.generateSummaryByLLM(
if (recordingText == null || recordingText.trim().isEmpty()) {
result.put("success", false);
result.put("message", "录音文本为空");
return ResponseEntity.badRequest().body(result);
}
// 4. 通过业务层通用服务调用大模型生成总结并保存数据
AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSave(
sceneType,
recordingText
recordingText,
audioManagement.getId(),
audioManagement.getSalesName(),
audioManagement.getSalesPhone(),
audioManagement.getCustomerName(),
audioManagement.getCustomerPhone()
);
boolean success = true;
if (success) {
log.info("llm return furniture Analysis : ",furniture.toString() );
if (recordingText == null || recordingText.trim().isEmpty()) {
result.put("success", false);
result.put("message", "录音文本为空");
return ResponseEntity.badRequest().body(result);
} else {
result.put("success", true);
result.put("message", "录音finishServiceById更新成功");
result.put("data", audioManagement);
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "录音finishServiceById更新失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
result.put("success", false);
result.put("message", "更新异常:" + e.getMessage());

View File

@@ -237,6 +237,14 @@ public class AudioTextAnalysisFurnitureController {
result.put("message", "记录不存在");
return ResponseEntity.badRequest().body(result);
}
// 2. 检查summary字段如果已存在总结内容直接返回提示不进行AI分析
String summary = audioManagement.getSummary();
if (summary != null && !summary.trim().isEmpty()) {
result.put("success", true);
result.put("message", "AI分析已完成");
result.put("data", audioManagement);
return ResponseEntity.ok(result);
}
// 3. 获取录音文本
String recordingText = audioManagement.getRecordingText();
if (recordingText == null || recordingText.trim().isEmpty()) {
@@ -244,16 +252,10 @@ public class AudioTextAnalysisFurnitureController {
result.put("message", "录音文本为空");
return ResponseEntity.badRequest().body(result);
}
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
furniture.setRecordingText(recordingText);
// 4. 通过业务层通用服务调用大模型生成总结,然后解析结构化结果
String rawContent = audioTextAnalysisLlmService.generateSummaryByLLM(
AudioAnalysisSceneType.SCENARIO_MEETING_SUMMARY,
furniture.getRecordingText()
);
applyStructuredResult(
furniture,
rawContent,
// 4. 通过业务层通用服务调用大模型生成总结并保存数据
AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSave(
AudioAnalysisSceneType.SCENARIO_FURNITURE_SALE,
recordingText,
id,
audioManagement.getSalesName(),
audioManagement.getSalesPhone(),
@@ -261,34 +263,14 @@ public class AudioTextAnalysisFurnitureController {
audioManagement.getCustomerPhone()
);
audioManagement = new AudioManagement();
audioManagement.setSummary(furniture.getSummarySentence());
audioManagement.setId(id);
audioManagementService.updateById(audioManagement);
// 5. 保存总结到数据库
LambdaQueryWrapper<AudioTextAnalysisFurniture> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AudioTextAnalysisFurniture::getParentId, id);
AudioTextAnalysisFurniture one = furnitureService.getOne(wrapper);
if (one != null) {
furniture.setId(one.getId());
}
furniture.setUpdatedAt(LocalDateTime.now());
furniture.setParentId( id);
furniture.setRecordingText(null);
boolean updateSuccess = furnitureService.saveOrUpdate(furniture);
if (updateSuccess) {
if (furniture != null) {
result.put("success", true);
result.put("message", "生成总结成功");
result.put("data", furniture);
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "保存总结失败");
result.put("message", "生成总结并保存失败");
return ResponseEntity.badRequest().body(result);
}

View File

@@ -1,6 +1,7 @@
package com.rj.service;
import com.rj.common.AudioAnalysisSceneType;
import com.rj.entity.AudioTextAnalysisFurniture;
/**
* 音频文本分析 - 大模型通用服务
@@ -19,6 +20,28 @@ public interface IAudioTextAnalysisLlmService {
* @return 大模型返回的原始内容(一般是 JSON 字符串或结构化文本)
*/
String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText);
/**
* 处理完整的业务逻辑调用大模型生成总结解析JSON保存数据
* 此方法用于家具场景的完整业务处理
*
* @param sceneType 业务场景类型
* @param recordingText 录音转写文本
* @param parentId 父IDAudioManagement的ID
* @param ownerName 所属人姓名
* @param ownerPhone 所属人电话
* @param customerName 客户姓名
* @param customerPhone 客户电话
* @return 保存后的AudioTextAnalysisFurniture对象如果处理失败返回null
*/
AudioTextAnalysisFurniture generateSummaryAndSave(
AudioAnalysisSceneType sceneType,
String recordingText,
String parentId,
String ownerName,
String ownerPhone,
String customerName,
String customerPhone);
}

View File

@@ -7,16 +7,31 @@ import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.entity.AudioManagement;
import com.rj.entity.AudioTextAnalysisFurniture;
import com.rj.entity.AudioTextAnalysisSop;
import com.rj.entity.TodoItem;
import com.rj.service.IAudioManagementService;
import com.rj.service.IAudioTextAnalysisFurnitureService;
import com.rj.service.IAudioTextAnalysisLlmService;
import com.rj.service.IAudioTextAnalysisSopService;
import com.rj.service.ITodoItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -29,6 +44,21 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
private static final Map<String, String> SYSTEM_PROMPT_CACHE = new ConcurrentHashMap<>();
private static final Map<String, String> USER_PROMPT_CACHE = new ConcurrentHashMap<>();
@Autowired
private ObjectMapper objectMapper;
@Autowired
private IAudioTextAnalysisFurnitureService furnitureService;
@Autowired
private IAudioManagementService audioManagementService;
@Autowired
private ITodoItemService todoItemService;
@Autowired
private IAudioTextAnalysisSopService sopService;
@Override
public String generateSummaryByLLM(AudioAnalysisSceneType sceneType, String recordingText) {
long startTime = System.currentTimeMillis();
@@ -129,6 +159,343 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
return null;
}
}
/**
* 处理完整的业务逻辑调用大模型生成总结解析JSON保存数据
* 此方法用于家具场景的完整业务处理
*
* @param sceneType 业务场景类型
* @param recordingText 录音转写文本
* @param parentId 父IDAudioManagement的ID
* @param ownerName 所属人姓名
* @param ownerPhone 所属人电话
* @param customerName 客户姓名
* @param customerPhone 客户电话
* @return 保存后的AudioTextAnalysisFurniture对象如果处理失败返回null
*/
@Override
public AudioTextAnalysisFurniture generateSummaryAndSave(
AudioAnalysisSceneType sceneType,
String recordingText,
String parentId,
String ownerName,
String ownerPhone,
String customerName,
String customerPhone) {
try {
// 1. 调用大模型生成总结
String rawContent = generateSummaryByLLM(sceneType, recordingText);
if (rawContent == null || rawContent.trim().isEmpty()) {
log.warn("大模型返回内容为空");
return null;
}
// 2. 创建AudioTextAnalysisFurniture对象
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
furniture.setRecordingText(recordingText);
// 3. 解析JSON并填充对象
applyStructuredResult(furniture, rawContent, parentId, ownerName, ownerPhone, customerName, customerPhone);
// 4. 更新AudioManagement的summary字段
AudioManagement audioManagement = new AudioManagement();
audioManagement.setId(parentId);
audioManagement.setSummary(furniture.getSummarySentence());
audioManagementService.updateById(audioManagement);
// 5. 保存或更新AudioTextAnalysisFurniture记录
LambdaQueryWrapper<AudioTextAnalysisFurniture> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AudioTextAnalysisFurniture::getParentId, parentId);
AudioTextAnalysisFurniture existing = furnitureService.getOne(wrapper);
LocalDateTime now = LocalDateTime.now();
if (existing != null) {
furniture.setId(existing.getId());
// 更新记录:只设置更新时间,不修改创建时间
furniture.setUpdatedAt(now);
} else {
// 新增记录:需要设置创建时间和更新时间
furniture.setCreatedAt(now);
furniture.setUpdatedAt(now);
}
furniture.setParentId(parentId);
furniture.setRecordingText(null); // 保存时清除recordingText
furniture.setOwnerPhone(ownerPhone);
furniture.setCustomerPhone(customerPhone);
boolean success = furnitureService.saveOrUpdate(furniture);
if (success) {
log.info("保存AudioTextAnalysisFurniture成功ID: {}, parentId: {}", furniture.getId(), parentId);
return furniture;
} else {
log.warn("保存AudioTextAnalysisFurniture失败parentId: {}", parentId);
return null;
}
} catch (Exception e) {
log.error("生成总结并保存失败parentId: {}", parentId, e);
throw new RuntimeException("生成总结并保存失败: " + e.getMessage(), e);
}
}
/**
* 解析大模型返回的JSON结果并填充到AudioTextAnalysisFurniture对象
*
* @param furniture 家具意向分析对象
* @param rawContent 大模型返回的原始内容
* @param parentId 父ID
* @param ownerName 所属人姓名
* @param ownerPhone 所属人电话
* @param customerName 客户姓名
* @param customerPhone 客户电话
*/
private void applyStructuredResult(
AudioTextAnalysisFurniture furniture,
String rawContent,
String parentId,
String ownerName,
String ownerPhone,
String customerName,
String customerPhone) {
String normalized = normalizeJson(rawContent);
try {
JsonNode root = objectMapper.readTree(normalized);
// 基础字段
furniture.setCustomerType(textValue(root, "customer_type"));
furniture.setFamilyStructure(textValue(root, "family_structure"));
furniture.setIntentionProducts(textValue(root, "intention_products"));
furniture.setSummarySentence(textValue(root, "summary"));
furniture.setDecorationStyle(textValue(root, "decoration_style"));
// 产品维度字段每个都是JSON对象需要转换为JSON字符串
furniture.setSofa(jsonObjectToString(root, "sofa"));
furniture.setTeaTable(jsonObjectToString(root, "tea_table"));
furniture.setDiningTable(jsonObjectToString(root, "dining_table"));
furniture.setStudyDesk(jsonObjectToString(root, "study_desk"));
furniture.setTvCabinet(jsonObjectToString(root, "tv_cabinet"));
furniture.setCabinet(jsonObjectToString(root, "cabinet"));
furniture.setWineCabinet(jsonObjectToString(root, "wine_cabinet"));
furniture.setMasterBedroomCabinet(jsonObjectToString(root, "master_bedroom_cabinet"));
furniture.setSecondaryBedroomCabinet(jsonObjectToString(root, "secondary_bedroom_cabinet"));
furniture.setShoeCabinet(jsonObjectToString(root, "shoe_cabinet"));
furniture.setBedAndMattress(jsonObjectToString(root, "bed_and_mattress"));
furniture.setSummary1(jsonObjectToString(root, "summary1"));
furniture.setSummary2(jsonObjectToString(root, "summary2"));
furniture.setSummary3(jsonObjectToString(root, "summary3"));
// 解析并保存待办事项
saveTodoItems(root, parentId, ownerName, ownerPhone);
// 解析并保存SOP评分信息
saveSopInfo(root, parentId, ownerName, ownerPhone, customerName, customerPhone);
} catch (JsonProcessingException e) {
log.warn("解析大模型返回JSON失败使用原始内容作为总结: {}", e.getMessage());
furniture.setSummarySentence(rawContent);
}
}
/**
* 规范化JSON字符串去除markdown代码块标记等
*/
private String normalizeJson(String content) {
if (content == null) {
return "";
}
String trimmed = content.trim();
if (trimmed.startsWith("```")) {
int firstLineBreak = trimmed.indexOf('\n');
int lastFence = trimmed.lastIndexOf("```");
if (firstLineBreak >= 0 && lastFence > firstLineBreak) {
trimmed = trimmed.substring(firstLineBreak + 1, lastFence).trim();
}
}
return trimmed;
}
/**
* 从JSON节点中获取文本值
*/
private String textValue(JsonNode root, String field) {
JsonNode node = root.get(field);
return node == null || node.isNull() ? null : node.asText();
}
/**
* 将JSON对象节点转换为JSON字符串
*/
private String jsonObjectToString(JsonNode root, String field) {
JsonNode node = root.get(field);
if (node == null || node.isNull()) {
return null;
}
try {
if (node.isObject()) {
return objectMapper.writeValueAsString(node);
} else if (node.isTextual()) {
return node.asText();
} else {
return objectMapper.writeValueAsString(node);
}
} catch (JsonProcessingException e) {
log.warn("转换JSON对象失败字段: {}, 错误: {}", field, e.getMessage());
return node.isTextual() ? node.asText() : node.toString();
}
}
/**
* 解析并保存待办事项
*/
private void saveTodoItems(JsonNode root, String parentId, String ownerName, String ownerPhone) {
try {
JsonNode todoItemsNode = root.get("todo_item");
if (todoItemsNode == null || !todoItemsNode.isArray()) {
log.info("未找到待办事项数组或格式不正确");
return;
}
LocalDateTime now = LocalDateTime.now();
int savedCount = 0;
for (JsonNode todoItemNode : todoItemsNode) {
if (!todoItemNode.isObject()) {
log.warn("待办事项项不是JSON对象跳过");
continue;
}
String todoTitle = textValue(todoItemNode, "todo_title");
String todoDetail = textValue(todoItemNode, "todo_detail");
// 如果标题和详情都为空,跳过
if ((todoTitle == null || todoTitle.trim().isEmpty()) &&
(todoDetail == null || todoDetail.trim().isEmpty())) {
log.warn("待办事项标题和详情都为空,跳过");
continue;
}
TodoItem todoItem = new TodoItem();
todoItem.setId(UUID.randomUUID().toString());
todoItem.setParentId(parentId);
todoItem.setTodoTitle(todoTitle != null ? todoTitle.trim() : "");
todoItem.setTodoDetail(todoDetail != null ? todoDetail.trim() : "");
todoItem.setOwnerName(ownerName);
todoItem.setOwnerPhone(ownerPhone);
todoItem.setStatus("待处理");
todoItem.setCreateTime(now);
todoItem.setUpdateTime(now);
todoItem.setPendingDate(now.plusDays(3));
boolean success = todoItemService.save(todoItem);
if (success) {
savedCount++;
log.info("保存待办事项成功ID: {}, 标题: {}", todoItem.getId(), todoItem.getTodoTitle());
} else {
log.warn("保存待办事项失败,标题: {}", todoItem.getTodoTitle());
}
}
log.info("待办事项保存完成,共保存 {} 条", savedCount);
} catch (Exception e) {
log.error("保存待办事项时发生异常", e);
// 不抛出异常,避免影响主流程
}
}
/**
* 解析并保存SOP评分信息
*/
private void saveSopInfo(
JsonNode root,
String parentId,
String ownerName,
String ownerPhone,
String customerName,
String customerPhone) {
try {
JsonNode comprehensiveScoreNode = root.get("comprehensive_score");
if (comprehensiveScoreNode == null) {
log.info("未找到SOP评分信息跳过保存");
return;
}
AudioTextAnalysisSop sop = new AudioTextAnalysisSop();
sop.setId(UUID.randomUUID().toString());
sop.setParentId(parentId);
sop.setOwnerName(ownerName);
sop.setOwnerPhone(ownerPhone);
sop.setCustomerName(customerName);
sop.setCustomerPhone(customerPhone);
sop.setIndustryType("furniture");
// 解析SOP评分字段
sop.setComprehensiveScore(intValue(root, "comprehensive_score"));
sop.setScoreSummaryReason(textValue(root, "score_summary_reason"));
sop.setGreetingIceBreaking(intValue(root, "greeting_ice_breaking"));
sop.setBrandIntroduction(intValue(root, "brand_introduction"));
sop.setGoldenThreeQuestions(intValue(root, "golden_three_questions"));
sop.setNeedsGuidance(intValue(root, "needs_guidance"));
sop.setServiceProgression(intValue(root, "service_progression"));
sop.setReassuranceHandbook(intValue(root, "reassurance_handbook"));
sop.setThreeLevelPricing(intValue(root, "three_level_pricing"));
sop.setObjectionHandling(intValue(root, "objection_handling"));
sop.setActivityImplantation(intValue(root, "activity_implantation"));
sop.setClosingCooperation(intValue(root, "closing_cooperation"));
sop.setProactiveWechatAdd(intValue(root, "proactive_wechat_add"));
sop.setPoliteFarewell(intValue(root, "polite_farewell"));
// 设置时间
LocalDateTime now = LocalDateTime.now();
sop.setCreatedAt(now);
sop.setUpdatedAt(now);
// 检查是否已存在相同parentId的记录如果存在则更新否则新增
LambdaQueryWrapper<AudioTextAnalysisSop> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AudioTextAnalysisSop::getParentId, parentId);
AudioTextAnalysisSop existingSop = sopService.getOne(wrapper);
if (existingSop != null) {
sop.setId(existingSop.getId());
boolean success = sopService.updateById(sop);
if (success) {
log.info("更新SOP评分信息成功ID: {}, parentId: {}", sop.getId(), parentId);
} else {
log.warn("更新SOP评分信息失败parentId: {}", parentId);
}
} else {
boolean success = sopService.save(sop);
if (success) {
log.info("保存SOP评分信息成功ID: {}, parentId: {}", sop.getId(), parentId);
} else {
log.warn("保存SOP评分信息失败parentId: {}", parentId);
}
}
} catch (Exception e) {
log.error("保存SOP评分信息时发生异常", e);
// 不抛出异常,避免影响主流程
}
}
/**
* 从JSON节点中获取整数值
*/
private Integer intValue(JsonNode root, String field) {
JsonNode node = root.get(field);
if (node == null || node.isNull()) {
return null;
}
if (node.isInt()) {
return node.asInt();
} else if (node.isTextual()) {
try {
String text = node.asText();
if (text == null || text.trim().isEmpty()) {
return null;
}
return Integer.parseInt(text.trim());
} catch (NumberFormatException e) {
log.warn("无法将字段 {} 的值 {} 转换为整数", field, node.asText());
return null;
}
}
return null;
}
}