diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java b/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java
index 035e824..e927513 100644
--- a/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java
+++ b/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java
@@ -4,27 +4,27 @@ package com.supervision.livedigitalavatarmanage.constant;
* 提示词缓存
*/
public class PromptTemplate {
-
/**
- * 生成销售话术模板
+ * 生成文案要点
*/
- public static final String GENERATE_SALESPITCH_TEMPLATE = """
+ public static final String GENERATE_COPYWRITING_POINTS_TEMPLATE = """
你是一个直播销售专家,请根据产品信息给出销售话术。
要求:
- 1. 要求话术中包含产品的商品名、商品规格、商品详情,不要遗漏商品的任何信息。
- 2. 在遵循产品信息的基础下尽可能的吸引顾客,对提供的信息进行拓展描述。要求字数大于500字。
- 3. 生成结果中不要包含任何表情。
- 4. 生成的结果中所有的**尺寸数字**和单位**替换**为中文。
- 5. 依次生成10组销售话术,每组话术之间**禁止**重复。
- 6. 响应结果必须是jsonArray。格式["话术1","话术2"]/no_think
+ 1.分析出文案要点
+ 2. 响应结果必须是jsonArray。格式["要点1","要点2"]/no_think
""";
- private String aa_back = """
- 商品名:北京实木广岛椅
- 商品规格:1、尺寸:12X43cm,售价:100元 2、尺寸:16X54cm,售价:120元
- 商品详情:价格有点小贵,只因质量更好便宜的够好,为什么要买贵的?选材好东南亚FAS级橡胶木*拼板少原木方直出 木纹流畅更环保品牌净味环保油漆【FAS级橡胶木)
- 。原木直出,木纹均匀清晰,不易变形
- 。天然选材,原木清香、拒绝甲醛
- ·质地坚硬细腻,稳固性好、防蛀虫、防腐增添家居氛围实木软包藤编椅品质实术,透气藤椅,倒V椅腿极简追光素色温柔在素色的温柔空间中,打造了如光影般的自在与宁静这种几乎无染、无色的生活,也投射了某种观念上的极简一切都展现出感性而放松的设计哲学,从内在寻找生活的自在DESIGNCONCEPTI设计理念四大亮点优势颜值与舒适体验的融合带来品质生活。/no_think
+
+ /**
+ * 生成销售话术模板
+ */
+ public static final String GENERATE_SALESPITCH_TEMPLATE = """
+ 你是一个直播销售专家,请根据**重点介绍点**和**完整产品信息**给出销售话术。
+ 要求:
+ 1. 在遵循产品**重点介绍点**的基础下尽可能的吸引顾客,参考完整产品信息进行拓展描述。要求字数小于150字。
+ 2. 包含完整的**重点介绍点**。
+ 2. 生成结果中不要包含任何表情。
+ 3. 依次生成{num}组销售话术,每组话术之间**禁止**重复。
+ 4. 响应结果必须是jsonArray。格式["话术1","话术2"]/no_think
""";
}
diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java
index ca1faab..a56142b 100644
--- a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java
+++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java
@@ -1,5 +1,6 @@
package com.supervision.livedigitalavatarmanage.service.impl;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
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.ollama.OllamaChatModel;
import org.springframework.stereotype.Service;
-import java.util.List;
+import java.util.*;
@Slf4j
@Service
@@ -28,24 +29,159 @@ public class LiveDigitalServiceImpl implements LiveDigitalService {
Assert.notEmpty(salespitchReqVo.getSpecifications(), "产品规格不能为空");
Assert.notEmpty(salespitchReqVo.getDetail(), "产品详情不能为空");
- for (int i = 0; i < 3; i++) {
- try {
- String salesPitchTemplate = PromptTemplate.GENERATE_SALESPITCH_TEMPLATE;
- SystemMessage systemMessage = new SystemMessage(salesPitchTemplate);
- UserMessage userMessage = new UserMessage(salespitchReqVo.buildTemplate());
- Prompt prompt = new Prompt(systemMessage, userMessage);
-
- ChatResponse call = ollamaChatModel.call(prompt);
- String text = call.getResult().getOutput().getText();
- // 去除think标签
- if (StrUtil.isNotBlank(text) && text.contains("") && text.contains("")) {
- text = text.replaceAll("(?s).*?", "").trim();
+ List points = generateCopywritingPoints(salespitchReqVo);
+ Assert.notEmpty(points, "生成话术失败!请稍后重试");
+ List prompts = generatePrompts(points, salespitchReqVo);
+ List result = new ArrayList<>();
+ for (Prompt prompt : prompts) {
+ for (int i = 0; i < 2; i++) {
+ try {
+ log.info("======================>>>>");
+ log.info("开始生成销售话术,请求内容:{}", prompt.getContents());
+ ChatResponse call = ollamaChatModel.call(prompt);
+ String text = call.getResult().getOutput().getText();
+ // 去除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 generatePrompts(List points, SalesPitchReqVo salespitchReqVo) {
+ int promptNum = 10; // 需要生成的销售话术数量
+ int bestNum = 5;
+ List> pointIndexList = generateIndexCombinations(points.size());
+ List> lists = pickBestSalesPitch(pointIndexList, bestNum);
+ List prompts = new ArrayList<>();
+ int quotient = promptNum / lists.size(); // 商
+ int remainder = promptNum % lists.size(); // 余数
+ for (List 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> pickBestSalesPitch(List> pointIndexList,int num) {
+ if (CollUtil.isEmpty(pointIndexList)){
+ return new ArrayList<>();
+ }
+ if (pointIndexList.size() <= num){
+ return pointIndexList;
+ }
+ List> bestCombinations = new ArrayList<>();
+ // 先挑选出组合为2的数据
+ for (List 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 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 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 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 indices : pointIndexList) {
+ if (indices.size() == 1 && bestCombinations.size() < num) {
+ bestCombinations.add(indices);
+ }
+ }
+ return bestCombinations;
+ }
+
+ /**
+ * 生成销售话术要点
+ * @param salespitchReqVo 销售话术请求对象
+ * @return
+ */
+ private List 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("") && text.contains("")) {
+ text = text.replaceAll("(?s).*?", "").trim();
+ }
+ return text;
+ }
+
+ /**
+ * 生成长度为n的数组的所有索引组合
+ * @param n 数组长度
+ * @return
+ */
+ private List> generateIndexCombinations(int n) {
+ List> result = new ArrayList<>();
+ int total = 1 << n; // 2^n
+
+ for (int mask = 1; mask < total; mask++) { // 从1开始,跳过空集
+ List current = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ if ((mask & (1 << i)) != 0) { // 检查第i位是否被选中
+ current.add(i);
+ }
+ }
+ result.add(current);
+ }
+ return result;
}
}
diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java
index 364b226..5daf230 100644
--- a/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java
+++ b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java
@@ -29,16 +29,37 @@ public class SalesPitchReqVo {
public String buildTemplate() {
+ return "请根据产品信息生成销售话术:" +
+ "\n" +
+ buildProductInfo() + "/no_think";
+ }
+
+ public String buildProductInfo() {
+ StringBuilder info = new StringBuilder();
+ info.append("产品名称:").append(productName).append("\n");
+ info.append("产品规格:");
+ for (ProductSpecification specification : specifications) {
+ info.append("产品类型:").append(specification.getSize()).append("\n");
+ info.append("产品价格:").append(specification.getPrice()).append("\n");
+ }
+ info.append("产品详情:").append(detail);
+ return info.toString();
+ }
+
+ public String buildTemplate(List copyingPoints) {
StringBuilder template = new StringBuilder();
- template.append("请根据产品信息生成销售话术:");
+ template.append("## 重点介绍点:");
template.append("\n");
- template.append("产品名称:").append(productName).append("\n");
- template.append("产品规格:");
- for (ProductSpecification specification : specifications) {
- template.append("产品类型:").append(specification.getSize()).append("\n");
- template.append("产品价格:").append(specification.getPrice()).append("\n");
+ template.append("```").append("\n");
+ for (int i = 0; i < copyingPoints.size(); i++) {
+ template.append(i + 1).append(". ").append(copyingPoints.get(i)).append("\n");
}
- template.append("产品详情:").append(detail).append("/no_think");
+ template.append("```").append("\n");
+ template.append("## 完整产品信息").append("\n");
+ template.append("```").append("\n");
+ template.append(buildProductInfo()).append("\n");
+ template.append("```").append("\n");
+ template.append("/no_think");
return template.toString();
}
}