提示词管理代码架构

This commit is contained in:
2026-04-19 21:19:40 +08:00
parent 27a55bb0b6
commit 18ecd2f78f
12 changed files with 437 additions and 19 deletions

View File

@@ -10,7 +10,7 @@
</parent>
<groupId>com.cst</groupId>
<artifactId>AIDriverEEBackend</artifactId>
<version>1.26040401.1-SNAPSHOT</version>
<version>1.26040419.6-SNAPSHOT</version>
<name>Langchain4j-rj</name>
<description>Langchain4j-rj20250803</description>
<url/>

View File

@@ -19,7 +19,11 @@ public enum AudioAnalysisSceneType {
"prompts/audio_text_analysis_soft_sale_user_prompts.txt",
"qwen-plus"
),
SCENARIO_SMALL_BEEUTIFUL_SALE(
"prompts/audio_text_analysis_soft_sale_system_prompts.txt",
"prompts/audio_text_analysis_soft_sale_user_prompts.txt",
"qwen-plus"
),
/**
* 会议纪要/会议分析场景
* 提示词文件和模型可根据实际需要进行调整。

View File

@@ -0,0 +1,202 @@
package com.rj.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.rj.entity.AiPrompts;
import com.rj.service.IAiPromptsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 表 ai_prompts 前端控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/aiPrompts")
@Tag(name = "AI提示词配置(ai_prompts)", description = "增删改查与分页")
public class AiPromptsController {
@Autowired
private IAiPromptsService aiPromptsService;
@PostMapping("/add")
@Operation(summary = "新增")
public ResponseEntity<Map<String, Object>> add(
@Parameter(description = "实体", required = true) @RequestBody AiPrompts entity) {
Map<String, Object> result = new HashMap<>();
try {
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
entity.setId(UUID.randomUUID().toString());
}
LocalDateTime now = LocalDateTime.now();
if (entity.getCreateTime() == null) {
entity.setCreateTime(now);
}
entity.setUpdateTime(now);
boolean ok = aiPromptsService.save(entity);
if (ok) {
result.put("success", true);
result.put("message", "添加成功");
result.put("data", entity);
return ResponseEntity.ok(result);
}
result.put("success", false);
result.put("message", "添加失败");
return ResponseEntity.badRequest().body(result);
} catch (Exception e) {
log.error("ai_prompts add error", e);
result.put("success", false);
result.put("message", "添加异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
@GetMapping("/get/{id}")
@Operation(summary = "按ID查询")
public ResponseEntity<Map<String, Object>> getById(@PathVariable String id) {
Map<String, Object> result = new HashMap<>();
try {
AiPrompts one = aiPromptsService.getById(id);
if (one != null) {
result.put("success", true);
result.put("message", "查询成功");
result.put("data", one);
return ResponseEntity.ok(result);
}
result.put("success", false);
result.put("message", "记录不存在");
return ResponseEntity.notFound().build();
} catch (Exception e) {
log.error("ai_prompts get error, id={}", id, e);
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
@GetMapping("/list")
@Operation(summary = "分页查询")
public ResponseEntity<Map<String, Object>> list(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String sceneCode,
@RequestParam(required = false) String categoryCode,
@RequestParam(required = false) String fieldCode,
@RequestParam(required = false) String promptKeyword,
@RequestParam(required = false) String createStartTime,
@RequestParam(required = false) String createEndTime) {
Map<String, Object> result = new HashMap<>();
try {
Page<AiPrompts> page = new Page<>(current, size);
LambdaQueryWrapper<AiPrompts> q = new LambdaQueryWrapper<>();
if (sceneCode != null && !sceneCode.trim().isEmpty()) {
q.eq(AiPrompts::getSceneCode, sceneCode);
}
if (categoryCode != null && !categoryCode.trim().isEmpty()) {
q.eq(AiPrompts::getCategoryCode, categoryCode);
}
if (fieldCode != null && !fieldCode.trim().isEmpty()) {
q.eq(AiPrompts::getFieldCode, fieldCode);
}
if (promptKeyword != null && !promptKeyword.trim().isEmpty()) {
q.like(AiPrompts::getPromptText, promptKeyword);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
if (createStartTime != null && !createStartTime.trim().isEmpty()) {
try {
q.ge(AiPrompts::getCreateTime, LocalDateTime.parse(createStartTime, formatter));
} catch (Exception ex) {
result.put("success", false);
result.put("message", "创建开始时间格式错误请使用yyyy-MM-dd HH:mm:ss");
return ResponseEntity.badRequest().body(result);
}
}
if (createEndTime != null && !createEndTime.trim().isEmpty()) {
try {
q.le(AiPrompts::getCreateTime, LocalDateTime.parse(createEndTime, formatter));
} catch (Exception ex) {
result.put("success", false);
result.put("message", "创建结束时间格式错误请使用yyyy-MM-dd HH:mm:ss");
return ResponseEntity.badRequest().body(result);
}
}
q.orderByDesc(AiPrompts::getCreateTime);
Page<AiPrompts> data = aiPromptsService.page(page, q);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", data.getRecords());
result.put("total", data.getTotal());
result.put("current", data.getCurrent());
result.put("size", data.getSize());
result.put("pages", data.getPages());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("ai_prompts list error", e);
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
@PutMapping("/update")
@Operation(summary = "更新")
public ResponseEntity<Map<String, Object>> update(@RequestBody AiPrompts entity) {
Map<String, Object> result = new HashMap<>();
try {
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
result.put("success", false);
result.put("message", "ID不能为空");
return ResponseEntity.badRequest().body(result);
}
entity.setUpdateTime(LocalDateTime.now());
boolean ok = aiPromptsService.updateById(entity);
if (ok) {
result.put("success", true);
result.put("message", "更新成功");
result.put("data", aiPromptsService.getById(entity.getId()));
return ResponseEntity.ok(result);
}
result.put("success", false);
result.put("message", "更新失败");
return ResponseEntity.badRequest().body(result);
} catch (Exception e) {
log.error("ai_prompts update error", e);
result.put("success", false);
result.put("message", "更新异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
@DeleteMapping("/delete/{id}")
@Operation(summary = "删除")
public ResponseEntity<Map<String, Object>> delete(@PathVariable String id) {
Map<String, Object> result = new HashMap<>();
try {
boolean ok = aiPromptsService.removeById(id);
if (ok) {
result.put("success", true);
result.put("message", "删除成功");
return ResponseEntity.ok(result);
}
result.put("success", false);
result.put("message", "删除失败");
return ResponseEntity.badRequest().body(result);
} catch (Exception e) {
log.error("ai_prompts delete error", e);
result.put("success", false);
result.put("message", "删除异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
}

View File

@@ -370,7 +370,8 @@ public class AudioFileController {
@Parameter(description = "音频文件", required = true)
@RequestParam("file") MultipartFile file,
@Parameter(description = "录音ID", required = true)
@RequestParam("audioId") String audioId) {
@RequestParam("audioId") String audioId,
@RequestParam("audioDuration") BigDecimal audioDuration ) {
Map<String, Object> result = new HashMap<>();
@@ -388,7 +389,7 @@ public class AudioFileController {
audioManagement.setUpdateTime(TimeZoneUtils.now());
audioManagementService.updateById(audioManagement);
// 同步回写 audio_management_segments子表
saveOrUpdateManualUploadSegment(audioManagement, file, localFileName, localFilePath, uploadResult);
saveOrUpdateManualUploadSegment(audioManagement, file,audioDuration, localFileName, localFilePath, uploadResult);
} else {
log.warn("手动上传后未找到录音记录无法回写本地路径audioId={}", audioId);
}
@@ -418,6 +419,7 @@ public class AudioFileController {
*/
private void saveOrUpdateManualUploadSegment(AudioManagement audioManagement,
MultipartFile file,
BigDecimal audioDuration,
String localFileName,
String localFilePath,
Map<String, Object> uploadResult) {
@@ -456,11 +458,7 @@ public class AudioFileController {
segment.setAudioFileSize(file.getSize());
segment.setUploadTime(now);
segment.setRecordingTime(audioManagement.getRecordingTime() != null ? audioManagement.getRecordingTime() : now);
AudioManagement fileUrlObj = (AudioManagement)uploadResult.get("audioRecord");
if (fileUrlObj != null) {
segment.setAudioFileUrl(fileUrlObj.getAudioFileUrl());
}
segment.setDuration(audioDuration);
if (isNew) {
audioManagementSegmentsService.save(segment);

View File

@@ -47,7 +47,7 @@ public class AudioManagementController {
// 场景常量,避免在解析逻辑中硬编码字符串
private static final String SCENARIO_SOFT_SALE = "SOFT_SALE";
private static final String SCENARIO_FURNITURE_SALE = "FURNITURE";
private static final String SCENARIO_MEETING_SUMMARY = "MEETING_SUMMARY";
private static final String SCENARIO_SMALL_BEEUTIFUL_SALE = "smallBeautiful_sales";
private static final String SCENARIO_CAR_SALE = "CAR_SALE";
private static final String SCENARIO_SUMMARY = "SUMMARY";
private static final String SCENARIO_SPEAKING_TRAINING= "SPEAKING_TRAINING"; //租赁模式
@@ -763,6 +763,78 @@ public class AudioManagementController {
}
}
@PostMapping("/AIAnlyzByIdForDetail")
@Operation(summary = "AI分析", description = "AI分析的总入口")
public ResponseEntity<Map<String, Object>> AIAnlyByIdForDetail(
@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);
result.put("message", "录音ID不能为空");
return ResponseEntity.badRequest().body(result);
}
// 1. 根据ID查询家具意向分析记录
AudioManagement audioManagementFromDB = audioManagementService.getById(audioManagement.getId());
if (audioManagementFromDB == null) {
result.put("success", false);
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()) {
result.put("success", false);
result.put("message", "录音文本为空");
return ResponseEntity.badRequest().body(result);
}
// 4. 根据场景动态选择业务场景类型,调用业务层通用服务生成总结
AudioAnalysisSceneType sceneType = resolveSceneType(audioManagementFromDB.getScenario());
AudioTextAnalysisFurniture furniture = audioTextAnalysisLlmService.generateSummaryAndSaveForDetail(
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);
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);
}
} catch (Exception e) {
result.put("success", false);
result.put("message", "更新异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 根据录音场景字符串解析为大模型分析场景类型
*/
@@ -774,6 +846,10 @@ public class AudioManagementController {
if (SCENARIO_SOFT_SALE.equals(s)) {
return AudioAnalysisSceneType.SCENARIO_SOFT_SALE;
}
if (SCENARIO_SMALL_BEEUTIFUL_SALE.equals(s)) {
return AudioAnalysisSceneType.SCENARIO_SMALL_BEEUTIFUL_SALE;
}
if (SCENARIO_FURNITURE_SALE.equals(s)) {
return AudioAnalysisSceneType.SCENARIO_FURNITURE_SALE;
}

View File

@@ -0,0 +1,50 @@
package com.rj.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* AI 提示词配置(表 ai_prompts
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("ai_prompts")
@Schema(description = "AI提示词配置(ai_prompts)")
public class AiPrompts implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键UUID")
@TableId("id")
private String id;
@Schema(description = "场景编码")
private String sceneCode;
@Schema(description = "分类编码")
private String categoryCode;
@Schema(description = "分类描述")
private String categoryDesc;
@Schema(description = "字段编码")
private String fieldCode;
@Schema(description = "提示词")
private String promptText;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "修改时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,12 @@
package com.rj.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.rj.entity.AiPrompts;
import org.apache.ibatis.annotations.Mapper;
/**
* 表 ai_prompts Mapper
*/
@Mapper
public interface AiPromptsMapper extends BaseMapper<AiPrompts> {
}

View File

@@ -0,0 +1,10 @@
package com.rj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.rj.entity.AiPrompts;
/**
* 表 ai_prompts 服务;常规 CRUD 与分页见 {@link IService}。
*/
public interface IAiPromptsService extends IService<AiPrompts> {
}

View File

@@ -77,6 +77,15 @@ public interface IAudioTextAnalysisLlmService {
String ownerPhone,
String customerName,
String customerPhone);
AudioTextAnalysisFurniture generateSummaryAndSaveForDetail(
AudioAnalysisSceneType sceneType,
String recordingText,
String parentId,
String ownerName,
String ownerPhone,
String customerName,
String customerPhone);
}

View File

@@ -0,0 +1,14 @@
package com.rj.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rj.entity.AiPrompts;
import com.rj.mapper.AiPromptsMapper;
import com.rj.service.IAiPromptsService;
import org.springframework.stereotype.Service;
/**
* 表 ai_prompts 服务实现
*/
@Service
public class AiPromptsServiceImpl extends ServiceImpl<AiPromptsMapper, AiPrompts> implements IAiPromptsService {
}

View File

@@ -14,10 +14,7 @@ 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;
import com.rj.entity.TodoItem;
import com.rj.entity.*;
import com.rj.service.IAudioManagementService;
import com.rj.service.IAudioTextAnalysisFurnitureService;
import com.rj.service.IAudioTextAnalysisLlmService;
@@ -37,10 +34,7 @@ import jakarta.annotation.PostConstruct;
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.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -532,6 +526,56 @@ public class AudioTextAnalysisLlmServiceImpl implements IAudioTextAnalysisLlmSer
return furniture;
}
@Autowired
private com.rj.service.IAudioManagementSegmentsService audioManagementSegmentsService;
@Override
public AudioTextAnalysisFurniture generateSummaryAndSaveForDetail(
AudioAnalysisSceneType sceneType,
String recordingText,
String parentId,
String ownerName,
String ownerPhone,
String customerName,
String customerPhone) {
LambdaQueryWrapper<AudioManagementSegments> segQuery = new LambdaQueryWrapper<>();
segQuery.eq(AudioManagementSegments::getParentId, parentId)
.orderByAsc(AudioManagementSegments::getChunkIndex);
List<AudioManagementSegments> segmentList = audioManagementSegmentsService.list(segQuery);
int emptyCount = 0 ;
StringBuilder mergedText = new StringBuilder();
if (segmentList != null && !segmentList.isEmpty()) {
for (AudioManagementSegments seg : segmentList) {
String segText = seg.getRecordingText();
if (segText != null && !segText.trim().isEmpty()) {
if (mergedText.length() > 0) {
mergedText.append("\n");
}
mergedText.append(segText.trim());
}else {
emptyCount ++;
}
}
}
log.info(" AudioManagementSegments 总数是: {} ,未转文本记录条数是: {}",segmentList.size(), emptyCount);
LocalLlmSummaryResult llmResult = generateSummaryByLocalLLM(sceneType, mergedText.toString());
String rawContent = llmResult.rawContent();
if (rawContent == null || rawContent.trim().isEmpty()) {
log.warn("大模型返回内容为空");
return null;
}
AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture();
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) {

View File

@@ -132,7 +132,6 @@
"closing_cooperation": 75,
"proactive_wechat_add": 80,
"polite_farewell": 90
}
SOP维度评分每个维度0-100分根据录音文本中销售顾问的实际表现进行评分