家居大模型联调,分析sop执行情况

This commit is contained in:
ZLI263
2025-11-23 10:58:19 +08:00
parent 23fe2e70b1
commit 64fb90c381
9 changed files with 313 additions and 10 deletions

View File

@@ -14,9 +14,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.IAudioTextAnalysisSopService;
import com.rj.service.ITodoItemService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -55,6 +57,9 @@ public class AudioTextAnalysisFurnitureController {
@Autowired
private ITodoItemService todoItemService;
@Autowired
private IAudioTextAnalysisSopService sopService;
@Autowired
private ObjectMapper objectMapper;
@@ -238,8 +243,9 @@ public class AudioTextAnalysisFurnitureController {
}
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
furniture.setRecordingText(recordingText);
// 4. 调用大模型生成总结,传递待办事项所需的关联信息
generateSummaryByLLM(furniture, id, audioManagement.getSalesName(), audioManagement.getSalesPhone());
// 4. 调用大模型生成总结,传递待办事项和SOP所需的关联信息
generateSummaryByLLM(furniture, id, audioManagement.getSalesName(), audioManagement.getSalesPhone(),
audioManagement.getCustomerName(), audioManagement.getCustomerPhone());
audioManagement = new AudioManagement();
@@ -287,8 +293,11 @@ public class AudioTextAnalysisFurnitureController {
* @param parentId 父IDAudioManagement的ID
* @param ownerName 所属人姓名
* @param ownerPhone 所属人电话
* @param customerName 客户姓名
* @param customerPhone 客户电话
*/
private void generateSummaryByLLM(AudioTextAnalysisFurniture furniture, String parentId, String ownerName, String ownerPhone) {
private void generateSummaryByLLM(AudioTextAnalysisFurniture furniture, String parentId, String ownerName, String ownerPhone,
String customerName, String customerPhone) {
// 记录开始时间
long startTime = System.currentTimeMillis();
log.info("开始调用大模型生成总结");
@@ -314,7 +323,7 @@ public class AudioTextAnalysisFurnitureController {
GenerationResult call = gen.call(param);
String rawContent = call.getOutput().getChoices().get(0).getMessage().getContent();
log.info("大模型生成总结完成,结果长度: {},内容是:{}", rawContent.length(), rawContent);
applyStructuredResult(furniture, rawContent, parentId, ownerName, ownerPhone);
applyStructuredResult(furniture, rawContent, parentId, ownerName, ownerPhone, customerName, customerPhone);
} catch (NoApiKeyException e) {
log.error("API密钥未配置", e);
throw new RuntimeException("API密钥未配置: " + e.getMessage(), e);
@@ -583,7 +592,8 @@ public class AudioTextAnalysisFurnitureController {
return prompt.toString();
}
private void applyStructuredResult(AudioTextAnalysisFurniture furniture, String rawContent, String parentId, String ownerName, String ownerPhone) {
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);
@@ -606,9 +616,13 @@ public class AudioTextAnalysisFurnitureController {
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);
@@ -660,7 +674,7 @@ public class AudioTextAnalysisFurnitureController {
todoItem.setStatus("待处理"); // 默认状态
todoItem.setCreateTime(now);
todoItem.setUpdateTime(now);
todoItem.setPendingDate(now.plusDays(3)); // 延期处理时间为当前时间加三天
boolean success = todoItemService.save(todoItem);
if (success) {
savedCount++;
@@ -719,5 +733,110 @@ public class AudioTextAnalysisFurnitureController {
return node.isTextual() ? node.asText() : node.toString();
}
}
/**
* 解析并保存SOP评分信息
*
* @param root JSON根节点
* @param parentId 父IDAudioManagement的ID
* @param ownerName 所属人姓名
* @param ownerPhone 所属人电话
* @param customerName 客户姓名
* @param customerPhone 客户电话
*/
private void saveSopInfo(JsonNode root, String parentId, String ownerName, String ownerPhone,
String customerName, String customerPhone) {
try {
// 检查是否存在SOP相关字段
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节点中获取整数值
*
* @param root JSON根节点
* @param field 字段名
* @return 整数值如果不存在或为null则返回null
*/
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;
}
}

View File

@@ -47,6 +47,10 @@ public class TodoItemController {
if (todoItem.getId() == null || todoItem.getId().trim().isEmpty()) {
todoItem.setId(UUID.randomUUID().toString());
}
// 如果待处理日期为空则设置为当前时间加3天
if (todoItem.getPendingDate() == null) {
todoItem.setPendingDate(LocalDateTime.now().plusDays(3));
}
todoItem.setCreateTime(LocalDateTime.now());
todoItem.setUpdateTime(LocalDateTime.now());
boolean success = todoItemService.save(todoItem);
@@ -331,6 +335,104 @@ public class TodoItemController {
}
}
/**
* 更新待办事项状态为已完成
*/
@PostMapping("/updateTodoStatus")
@Operation(summary = "更新待办事项状态为已完成", description = "根据ID将待办事项状态修改为已完成")
public ResponseEntity<Map<String, Object>> updateTodoStatus(
@Parameter(description = "包含id的请求体格式{\"id\": \"待办事项ID\"}", required = true)
@RequestBody Map<String, String> request) {
Map<String, Object> result = new HashMap<>();
try {
String id = request.get("id");
if (id == null || id.trim().isEmpty()) {
result.put("success", false);
result.put("message", "待办事项ID不能为空");
return ResponseEntity.badRequest().body(result);
}
TodoItem todoItem = todoItemService.getById(id);
if (todoItem == null) {
result.put("success", false);
result.put("message", "待办事项不存在");
return ResponseEntity.badRequest().body(result);
}
todoItem.setStatus("已完成");
todoItem.setUpdateTime(LocalDateTime.now());
boolean success = todoItemService.updateById(todoItem);
if (success) {
result.put("success", true);
result.put("message", "状态更新成功");
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "状态更新失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
result.put("success", false);
result.put("message", "状态更新异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 延期处理待办事项
*/
@PostMapping("/delayProcessTodo")
@Operation(summary = "延期处理待办事项", description = "根据ID将待办事项的待处理日期延期3天")
public ResponseEntity<Map<String, Object>> delayProcessTodo(
@Parameter(description = "包含id的请求体格式{\"id\": \"待办事项ID\"}", required = true)
@RequestBody Map<String, String> request) {
Map<String, Object> result = new HashMap<>();
try {
String id = request.get("id");
if (id == null || id.trim().isEmpty()) {
result.put("success", false);
result.put("message", "待办事项ID不能为空");
return ResponseEntity.badRequest().body(result);
}
TodoItem todoItem = todoItemService.getById(id);
if (todoItem == null) {
result.put("success", false);
result.put("message", "待办事项不存在");
return ResponseEntity.badRequest().body(result);
}
// 计算延期3天后的日期
LocalDateTime newPendingDate;
if (todoItem.getPendingDate() != null) {
// 如果已有待处理日期则在原日期基础上延期3天
newPendingDate = todoItem.getPendingDate().plusDays(3);
} else {
// 如果没有待处理日期,则设置为当前日期+3天
newPendingDate = LocalDateTime.now().plusDays(3);
}
todoItem.setPendingDate(newPendingDate);
todoItem.setUpdateTime(LocalDateTime.now());
boolean success = todoItemService.updateById(todoItem);
if (success) {
result.put("success", true);
result.put("message", "延期处理成功,待处理日期已更新为:" + newPendingDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "延期处理失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
result.put("success", false);
result.put("message", "延期处理异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 获取待办事项统计信息
*/

View File

@@ -112,6 +112,18 @@ public class AudioTextAnalysisFurniture implements Serializable {
@TableField("customer_id")
private String customerId;
@Schema(description = "总结1")
@TableField("summary1")
private String summary1;
@Schema(description = "总结2")
@TableField("summary2")
private String summary2;
@Schema(description = "总结3")
@TableField("summary3")
private String summary3;
@Schema(description = "录音文本")
@TableField(exist = false)
private String recordingText;

View File

@@ -53,6 +53,10 @@ public class TodoItem implements Serializable {
@TableField("owner_phone")
private String ownerPhone;
@Schema(description = "待处理日期")
@TableField("pending_date")
private LocalDateTime pendingDate;
@Schema(description = "创建时间")
@TableField("create_time")
private LocalDateTime createTime;

View File

@@ -25,6 +25,9 @@
<result column="owner_id" property="ownerId"/>
<result column="customer_phone" property="customerPhone"/>
<result column="customer_id" property="customerId"/>
<result column="summary1" property="summary1"/>
<result column="summary2" property="summary2"/>
<result column="summary3" property="summary3"/>
<result column="created_at" property="createdAt"/>
<result column="updated_at" property="updatedAt"/>
</resultMap>
@@ -34,7 +37,8 @@
decoration_style, summary_sentence, sofa, tea_table, dining_table,
study_desk, tv_cabinet, cabinet, wine_cabinet, master_bedroom_cabinet,
secondary_bedroom_cabinet, shoe_cabinet, bed_and_mattress,
owner_phone, owner_id, customer_phone, customer_id, created_at, updated_at
owner_phone, owner_id, customer_phone, customer_id, summary1, summary2, summary3,
created_at, updated_at
</sql>
</mapper>

View File

@@ -11,13 +11,14 @@
<result column="todo_detail" property="todoDetail" />
<result column="owner_name" property="ownerName" />
<result column="owner_phone" property="ownerPhone" />
<result column="pending_date" property="pendingDate" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, parent_id, status, todo_title, todo_detail, owner_name, owner_phone, create_time, update_time
id, parent_id, status, todo_title, todo_detail, owner_name, owner_phone, pending_date, create_time, update_time
</sql>
</mapper>

View File

@@ -18,6 +18,26 @@
15. shoe_cabinet鞋柜需求或配置。
16. bed_and_mattress床及床垫配置。
17.todo_item: 待办事项,通过分析对话内容,把所有待办的事, 都放到这个字段属性里 。
18.summary1: 总结 客户疑虑点, 把同类的疑虑点放在一起总结3类分别是 :summary1,summary2,summary3 , 针对这三种分类的疑虑点,如何破解 客户的疑虑点,为成交做突破.。
19.summary2: 总结 客户疑虑点, 把同类的疑虑点放在一起总结3类分别是 :summary1,summary2,summary3 , 针对这三种分类的疑虑点,如何破解 客户的疑虑点,为成交做突破。
20.summary3 : 总结 客户疑虑点, 把同类的疑虑点放在一起总结3类分别是 :summary1,summary2,summary3 , 针对这三种分类的疑虑点,如何破解 客户的疑虑点,为成交做突破。
21. comprehensive_score综合得分根据所有SOP维度的表现综合评分范围0-100分。
22. score_summary_reason总结综合得分的原因详细说明综合得分的依据包括做得好的方面和需要改进的地方。
23. greeting_ice_breaking迎宾破冰得分评估销售顾问是否主动热情地迎接客户是否有效打破初次见面的尴尬氛围是否建立了良好的第一印象。
24. brand_introduction品牌介绍得分评估销售顾问是否适时介绍品牌优势、品牌故事、品牌价值介绍是否专业、有吸引力。
25. golden_three_questions黄金三问得分评估销售顾问是否通过关键问题了解客户需求如"您今天主要想了解什么?"、"您之前有了解过我们品牌吗?"、"您大概的预算范围是多少?"等。
26. needs_guidance需求引导得分评估销售顾问是否通过专业提问引导客户发现和表达真实需求是否帮助客户明确购买目标。
27. service_progression服务递进得分评估销售顾问是否按照服务流程递进式地提供服务从接待、介绍、体验、咨询到跟进流程是否顺畅自然。
28. reassurance_handbook放心手册得分评估销售顾问是否向客户介绍保障措施、售后服务、质量承诺等是否有效消除客户的购买顾虑。
29. three_level_pricing三级报价得分评估销售顾问是否采用三级报价策略低、中、高是否根据客户需求推荐合适价位的产品报价是否清晰透明。
30. objection_handling解答异议得分评估销售顾问面对客户疑虑、异议时的应对能力是否耐心解答、专业回应是否有效化解客户顾虑。
31. activity_implantation活动植入得分评估销售顾问是否适时介绍促销活动、优惠政策、限时优惠等活动信息是否清晰是否有效激发购买欲望。
32. closing_cooperation压单配合得分评估销售顾问是否在合适的时机进行成交引导是否与客户建立信任关系是否有效推进成交进程。
33. proactive_wechat_add主动添加微信得分评估销售顾问是否主动提出添加客户微信是否说明添加微信的价值如发送产品资料、跟进服务等是否成功添加。
34. polite_farewell礼貌道别得分评估销售顾问在服务结束时的表现是否礼貌送别是否表达感谢是否留下良好印象是否约定后续跟进。
重要每个产品维度sofa、tea_table、dining_table、study_desk、tv_cabinet、cabinet、wine_cabinet、master_bedroom_cabinet、secondary_bedroom_cabinet、shoe_cabinet、bed_and_mattress必须是一个JSON对象包含以下10个维度
1. preferenceStyleAndColor偏好风格与颜色
@@ -176,8 +196,45 @@
},{
"todo_detail": "待办事项详细说明" ,
"todo_title":"待办概要"
}]
}],
"summary1": {
"customerConcerns": "客户疑虑点1",
"answer": "如何破解 客户的疑虑点,为成交做突破"
},
"summary2": {
"customerConcerns": "客户疑虑点2",
"answer": "如何破解 客户的疑虑点,为成交做突破"
},
"summary3": {
"customerConcerns": "客户疑虑点3",
"answer": "如何破解 客户的疑虑点,为成交做突破"
},
"comprehensive_score": 85,
"score_summary_reason": "综合得分85分。销售顾问在迎宾破冰、需求引导方面表现优秀能够热情接待客户并有效了解需求。品牌介绍较为专业但活动植入和压单配合方面还有提升空间。整体服务流程顺畅客户体验良好。",
"greeting_ice_breaking": 90,
"brand_introduction": 85,
"golden_three_questions": 80,
"needs_guidance": 90,
"service_progression": 85,
"reassurance_handbook": 75,
"three_level_pricing": 80,
"objection_handling": 85,
"activity_implantation": 70,
"closing_cooperation": 75,
"proactive_wechat_add": 80,
"polite_farewell": 90
}
SOP维度评分每个维度0-100分根据录音文本中销售顾问的实际表现进行评分
SOP评分注意事项
1. 所有评分必须基于录音文本中的实际对话内容,不能随意编造。
2. 评分标准90-100分为优秀80-89分为良好70-79分为一般60-69分为较差0-59分为很差。
3. 如果录音文本中未体现某个SOP维度的内容该维度得分应为0分并在score_summary_reason中说明。
4. 综合得分应综合考虑所有维度的表现,通常取各维度得分的加权平均或根据整体表现综合评定。
5. score_summary_reason应详细说明评分依据包括做得好的方面和需要改进的地方字数建议100-300字。
6. 如果无法从录音文本中提取姓名、电话等信息,应填写"暂无信息"。
注意:
1. 必须严格按照JSON格式输出不要添加任何额外文字或markdown代码块标记。

View File

@@ -21,6 +21,9 @@ CREATE TABLE audio_text_analysis_furniture (
owner_id CHAR(36) NULL COMMENT '所属人或负责人的唯一标识',
customer_phone VARCHAR(30) NULL COMMENT '客户的联系方式',
customer_id CHAR(36) NULL COMMENT '客户的唯一标识',
summary1 TEXT NULL COMMENT '总结1',
summary2 TEXT NULL COMMENT '总结2',
summary3 TEXT NULL COMMENT '总结3',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录最近修改时间',
PRIMARY KEY (id),

View File

@@ -6,6 +6,7 @@ CREATE TABLE todo_item (
todo_detail TEXT NULL COMMENT '待办事项详情',
owner_name VARCHAR(100) NULL COMMENT '所属人姓名',
owner_phone VARCHAR(50) NULL COMMENT '所属人电话',
pending_date DATETIME NULL DEFAULT (DATE_ADD(NOW(), INTERVAL 3 DAY)) COMMENT '待处理日期默认为当前时间加3天',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (id),