From 760c69e3a03815e7fd429b6e193b050bf7c96c58 Mon Sep 17 00:00:00 2001 From: gitee Date: Tue, 12 Aug 2025 15:00:09 +0800 Subject: [PATCH] =?UTF-8?q?1.=E8=B0=83=E6=95=B4ai=E8=AF=9D=E6=9C=AF?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constant/PromptTemplate.java | 32 ++-- .../service/impl/LiveDigitalServiceImpl.java | 170 ++++++++++++++++-- .../vo/SalesPitchReqVo.java | 35 +++- 3 files changed, 197 insertions(+), 40 deletions(-) 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(); } }