|
|
@ -1,5 +1,6 @@
|
|
|
|
package com.supervision.livedigitalavatarmanage.service.impl;
|
|
|
|
package com.supervision.livedigitalavatarmanage.service.impl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
|
import cn.hutool.core.lang.Assert;
|
|
|
|
import cn.hutool.core.lang.Assert;
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
import cn.hutool.json.JSONUtil;
|
|
|
|
import cn.hutool.json.JSONUtil;
|
|
|
@ -14,7 +15,7 @@ import org.springframework.ai.chat.model.ChatResponse;
|
|
|
|
import org.springframework.ai.chat.prompt.Prompt;
|
|
|
|
import org.springframework.ai.chat.prompt.Prompt;
|
|
|
|
import org.springframework.ai.ollama.OllamaChatModel;
|
|
|
|
import org.springframework.ai.ollama.OllamaChatModel;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
@Slf4j
|
|
|
|
@Service
|
|
|
|
@Service
|
|
|
@ -28,24 +29,159 @@ public class LiveDigitalServiceImpl implements LiveDigitalService {
|
|
|
|
Assert.notEmpty(salespitchReqVo.getSpecifications(), "产品规格不能为空");
|
|
|
|
Assert.notEmpty(salespitchReqVo.getSpecifications(), "产品规格不能为空");
|
|
|
|
Assert.notEmpty(salespitchReqVo.getDetail(), "产品详情不能为空");
|
|
|
|
Assert.notEmpty(salespitchReqVo.getDetail(), "产品详情不能为空");
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
List<String> points = generateCopywritingPoints(salespitchReqVo);
|
|
|
|
try {
|
|
|
|
Assert.notEmpty(points, "生成话术失败!请稍后重试");
|
|
|
|
String salesPitchTemplate = PromptTemplate.GENERATE_SALESPITCH_TEMPLATE;
|
|
|
|
List<Prompt> prompts = generatePrompts(points, salespitchReqVo);
|
|
|
|
SystemMessage systemMessage = new SystemMessage(salesPitchTemplate);
|
|
|
|
List<String> result = new ArrayList<>();
|
|
|
|
UserMessage userMessage = new UserMessage(salespitchReqVo.buildTemplate());
|
|
|
|
for (Prompt prompt : prompts) {
|
|
|
|
Prompt prompt = new Prompt(systemMessage, userMessage);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
ChatResponse call = ollamaChatModel.call(prompt);
|
|
|
|
log.info("======================>>>>");
|
|
|
|
String text = call.getResult().getOutput().getText();
|
|
|
|
log.info("开始生成销售话术,请求内容:{}", prompt.getContents());
|
|
|
|
// 去除think标签
|
|
|
|
ChatResponse call = ollamaChatModel.call(prompt);
|
|
|
|
if (StrUtil.isNotBlank(text) && text.contains("<think>") && text.contains("</think>")) {
|
|
|
|
String text = call.getResult().getOutput().getText();
|
|
|
|
text = text.replaceAll("(?s)<think>.*?</think>", "").trim();
|
|
|
|
// 去除think标签
|
|
|
|
|
|
|
|
text = removeThinkTag(text);
|
|
|
|
|
|
|
|
log.info("生成销售话术成功,结果:{}", text);
|
|
|
|
|
|
|
|
log.info("<<<<======================");
|
|
|
|
|
|
|
|
result.addAll(JSONUtil.toList(text, String.class));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}catch (Exception e) {
|
|
|
|
|
|
|
|
log.error("生成销售话术失败,尝试第 {} 次。请求内容:{}", i + 1, salespitchReqVo.buildTemplate(),e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return JSONUtil.toList(text, String.class);
|
|
|
|
|
|
|
|
}catch (Exception e) {
|
|
|
|
|
|
|
|
log.error("生成销售话术失败,尝试第 {} 次。请求内容:{}", i + 1, salespitchReqVo.buildTemplate(),e);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new RuntimeException("生成销售话术失败,请稍后重试");
|
|
|
|
if (result.size() > 10){
|
|
|
|
|
|
|
|
result = result.subList(0, 10);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private List<Prompt> generatePrompts(List<String> points, SalesPitchReqVo salespitchReqVo) {
|
|
|
|
|
|
|
|
int promptNum = 10; // 需要生成的销售话术数量
|
|
|
|
|
|
|
|
int bestNum = 5;
|
|
|
|
|
|
|
|
List<List<Integer>> pointIndexList = generateIndexCombinations(points.size());
|
|
|
|
|
|
|
|
List<List<Integer>> lists = pickBestSalesPitch(pointIndexList, bestNum);
|
|
|
|
|
|
|
|
List<Prompt> prompts = new ArrayList<>();
|
|
|
|
|
|
|
|
int quotient = promptNum / lists.size(); // 商
|
|
|
|
|
|
|
|
int remainder = promptNum % lists.size(); // 余数
|
|
|
|
|
|
|
|
for (List<Integer> list : lists) {
|
|
|
|
|
|
|
|
int num1 = quotient + (remainder > 0 ? 1 : 0);
|
|
|
|
|
|
|
|
SystemMessage systemMessage = new SystemMessage(StrUtil.format(PromptTemplate.GENERATE_SALESPITCH_TEMPLATE, Map.of("num", num1)));
|
|
|
|
|
|
|
|
UserMessage userMessage = new UserMessage(salespitchReqVo.buildTemplate(list.stream().map(points::get).toList()));
|
|
|
|
|
|
|
|
prompts.add(new Prompt(systemMessage, userMessage));
|
|
|
|
|
|
|
|
if (remainder > 0){
|
|
|
|
|
|
|
|
remainder --;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return prompts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 挑选出最优的卖点组合
|
|
|
|
|
|
|
|
* @param pointIndexList 所有卖点组合的索引
|
|
|
|
|
|
|
|
* @param num 需要挑选的组合数量
|
|
|
|
|
|
|
|
* @return 最优的卖点组合
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private List<List<Integer>> pickBestSalesPitch(List<List<Integer>> pointIndexList,int num) {
|
|
|
|
|
|
|
|
if (CollUtil.isEmpty(pointIndexList)){
|
|
|
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pointIndexList.size() <= num){
|
|
|
|
|
|
|
|
return pointIndexList;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<Integer>> bestCombinations = new ArrayList<>();
|
|
|
|
|
|
|
|
// 先挑选出组合为2的数据
|
|
|
|
|
|
|
|
for (List<Integer> indices : pointIndexList) {
|
|
|
|
|
|
|
|
if (indices.size() == 2 && bestCombinations.size() < num) {
|
|
|
|
|
|
|
|
// 检查当前组合是否与已有组合有交集
|
|
|
|
|
|
|
|
boolean noneMatch = bestCombinations.stream().filter(i->i.size() == 2).noneMatch(i -> i.stream().anyMatch(indices::contains));
|
|
|
|
|
|
|
|
if (noneMatch){
|
|
|
|
|
|
|
|
bestCombinations.add(indices);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pointIndexList.size() % 2 != 0){
|
|
|
|
|
|
|
|
Optional<Integer> max = pointIndexList.stream().filter(i -> i.size() == 1).flatMap(Collection::stream).min((i1, i2) -> i2 - i1);
|
|
|
|
|
|
|
|
bestCombinations.add(List.of(max.get())); // 如果组合数为奇数,先添加第一个组合
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bestCombinations.size() >= num){
|
|
|
|
|
|
|
|
return bestCombinations;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 如果组合为2的数据不够,再挑选出组合为3的数据
|
|
|
|
|
|
|
|
for (List<Integer> indices : pointIndexList) {
|
|
|
|
|
|
|
|
if (indices.size() == 3 && bestCombinations.size() < num) {
|
|
|
|
|
|
|
|
// 检查当前组合是否与已有组合有交集
|
|
|
|
|
|
|
|
boolean noneMatch = bestCombinations.stream().filter(i->i.size() == 3).noneMatch(i -> i.stream().anyMatch(indices::contains));
|
|
|
|
|
|
|
|
if (noneMatch){
|
|
|
|
|
|
|
|
bestCombinations.add(indices);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bestCombinations.size() >= num){
|
|
|
|
|
|
|
|
return bestCombinations;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 如果组合为3的数据也不够,再挑选出组合为4的数据
|
|
|
|
|
|
|
|
for (List<Integer> indices : pointIndexList) {
|
|
|
|
|
|
|
|
if (indices.size() == 4 && bestCombinations.size() < num) {
|
|
|
|
|
|
|
|
// 检查当前组合是否与已有组合有交集
|
|
|
|
|
|
|
|
boolean noneMatch = bestCombinations.stream().filter(i->i.size() == 4).noneMatch(i -> i.stream().anyMatch(indices::contains));
|
|
|
|
|
|
|
|
if (noneMatch){
|
|
|
|
|
|
|
|
bestCombinations.add(indices);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 如果组合为4的数据也不够,再挑选出组合为1的数据
|
|
|
|
|
|
|
|
for (List<Integer> indices : pointIndexList) {
|
|
|
|
|
|
|
|
if (indices.size() == 1 && bestCombinations.size() < num) {
|
|
|
|
|
|
|
|
bestCombinations.add(indices);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return bestCombinations;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 生成销售话术要点
|
|
|
|
|
|
|
|
* @param salespitchReqVo 销售话术请求对象
|
|
|
|
|
|
|
|
* @return
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private List<String> generateCopywritingPoints(SalesPitchReqVo salespitchReqVo) {
|
|
|
|
|
|
|
|
SystemMessage systemMessage = new SystemMessage(PromptTemplate.GENERATE_COPYWRITING_POINTS_TEMPLATE);
|
|
|
|
|
|
|
|
UserMessage userMessage = new UserMessage(salespitchReqVo.buildProductInfo());
|
|
|
|
|
|
|
|
Prompt prompt = new Prompt(systemMessage, userMessage);
|
|
|
|
|
|
|
|
ChatResponse call = ollamaChatModel.call(prompt);
|
|
|
|
|
|
|
|
String text = call.getResult().getOutput().getText();
|
|
|
|
|
|
|
|
// 去除think标签
|
|
|
|
|
|
|
|
text = removeThinkTag(text);
|
|
|
|
|
|
|
|
return JSONUtil.toList(text, String.class);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String removeThinkTag(String text) {
|
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(text) && text.contains("<think>") && text.contains("</think>")) {
|
|
|
|
|
|
|
|
text = text.replaceAll("(?s)<think>.*?</think>", "").trim();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 生成长度为n的数组的所有索引组合
|
|
|
|
|
|
|
|
* @param n 数组长度
|
|
|
|
|
|
|
|
* @return
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private List<List<Integer>> generateIndexCombinations(int n) {
|
|
|
|
|
|
|
|
List<List<Integer>> result = new ArrayList<>();
|
|
|
|
|
|
|
|
int total = 1 << n; // 2^n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int mask = 1; mask < total; mask++) { // 从1开始,跳过空集
|
|
|
|
|
|
|
|
List<Integer> current = new ArrayList<>();
|
|
|
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
|
|
|
if ((mask & (1 << i)) != 0) { // 检查第i位是否被选中
|
|
|
|
|
|
|
|
current.add(i);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
result.add(current);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|