优化提示词
This commit is contained in:
parent
737432cc49
commit
70c6f1c789
@ -34,6 +34,7 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -62,6 +63,9 @@ public class GenerationController {
|
|||||||
private final ExecutorService sseExecutor = Executors.newCachedThreadPool();
|
private final ExecutorService sseExecutor = Executors.newCachedThreadPool();
|
||||||
private final ExecutorService llmExecutor = Executors.newCachedThreadPool();
|
private final ExecutorService llmExecutor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
/** SSE Emitter 工厂:默认创建 5 分钟超时的真实 Emitter;单测可注入可控实现以脱离 HTTP 客户端 */
|
||||||
|
private Supplier<SseEmitter> emitterFactory = () -> new SseEmitter(600000L);
|
||||||
|
|
||||||
private static final List<Map<String, String>> STYLES = List.of(
|
private static final List<Map<String, String>> STYLES = List.of(
|
||||||
Map.of("id", "friendly", "name", "亲和风格", "desc", "温暖亲切,像朋友聊天"),
|
Map.of("id", "friendly", "name", "亲和风格", "desc", "温暖亲切,像朋友聊天"),
|
||||||
Map.of("id", "professional", "name", "专业风格", "desc", "数据驱动,专业权威"),
|
Map.of("id", "professional", "name", "专业风格", "desc", "数据驱动,专业权威"),
|
||||||
@ -289,7 +293,7 @@ public class GenerationController {
|
|||||||
@RequestParam(value = "staffName", required = false) String staffName) {
|
@RequestParam(value = "staffName", required = false) String staffName) {
|
||||||
log.info("流式生成多风格话术参数:{},{},{},{}", fromUser, toUser, roomid, staffName);
|
log.info("流式生成多风格话术参数:{},{},{},{}", fromUser, toUser, roomid, staffName);
|
||||||
// 生成过程可能超过 30s,SSE 总超时放宽到 5 分钟
|
// 生成过程可能超过 30s,SSE 总超时放宽到 5 分钟
|
||||||
SseEmitter emitter = new SseEmitter(300000L);
|
SseEmitter emitter = emitterFactory.get();
|
||||||
|
|
||||||
sseExecutor.execute(() -> {
|
sseExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
@ -434,12 +438,12 @@ public class GenerationController {
|
|||||||
* 在后台线程执行 LLM 生成,返回最终话术文本
|
* 在后台线程执行 LLM 生成,返回最终话术文本
|
||||||
*/
|
*/
|
||||||
private String generateUtteranceText(List<ArchiveMessage> top15, String chatContext,
|
private String generateUtteranceText(List<ArchiveMessage> top15, String chatContext,
|
||||||
String external_str, String staffName) throws Exception {
|
String externalStr, String staffName) throws Exception {
|
||||||
String generated = "";
|
String generated = "";
|
||||||
//如果最后几条都是客户消息返回不为空,则直接返回结果,否则走原逻辑
|
//如果最后几条都是客户消息返回不为空,则直接返回结果,否则走原逻辑
|
||||||
if (!StringUtils.isBlank(external_str)) { //不为空 发起知识库的查询
|
if (!StringUtils.isBlank(externalStr)) { //不为空 发起知识库的查询
|
||||||
String answer = getAnswer(top15, external_str);
|
String answer = getAnswer(top15, externalStr);
|
||||||
log.info("原问题:{},命中知识库结果:{}", external_str, answer);
|
log.info("原问题:{},命中知识库结果:{}", externalStr, answer);
|
||||||
if (!StringUtils.isBlank(answer)) {
|
if (!StringUtils.isBlank(answer)) {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
List<String> list = mapper.readValue(answer, new TypeReference<List<String>>(){});
|
List<String> list = mapper.readValue(answer, new TypeReference<List<String>>(){});
|
||||||
@ -453,7 +457,7 @@ public class GenerationController {
|
|||||||
String[] parts = generated.split("\\|\\|\\|");
|
String[] parts = generated.split("\\|\\|\\|");
|
||||||
if (parts.length < 3) {
|
if (parts.length < 3) {
|
||||||
for (int i = 0; i < 3 - parts.length; i++) {
|
for (int i = 0; i < 3 - parts.length; i++) {
|
||||||
String systemPrompt = promptEngine.buildPrompt(chatContext, staffName);
|
String systemPrompt = promptEngine.buildPrompt(chatContext, staffName, externalStr);
|
||||||
String generatedNew = qianwenClient.generate(systemPrompt, "", 1200);
|
String generatedNew = qianwenClient.generate(systemPrompt, "", 1200);
|
||||||
generated = generated + generatedNew;
|
generated = generated + generatedNew;
|
||||||
}
|
}
|
||||||
@ -464,7 +468,7 @@ public class GenerationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(generated)) {
|
if (StringUtils.isBlank(generated)) {
|
||||||
String systemPrompt = promptEngine.buildSystemPrompt(chatContext, staffName);
|
String systemPrompt = promptEngine.buildSystemNewPrompt(chatContext, staffName, externalStr);
|
||||||
String userPrompt = promptEngine.buildMultiStyleUserPrompt(staffName);
|
String userPrompt = promptEngine.buildMultiStyleUserPrompt(staffName);
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|||||||
@ -36,32 +36,89 @@ public class PromptEngine {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 构建生成话术的提示词(角色设定)
|
* 构建生成话术的系统提示词(角色设定)
|
||||||
*/
|
*/
|
||||||
public String buildPrompt(String chatHistoryContext, String staffName) {
|
public String buildSystemNewPrompt(String chatHistoryContext, String staffName, String externalStr) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("你是第九联盟培训机构资深销售,仅输出符合规范的 3 条推荐回复话术,全程禁止输出任何对话分析、客户判断、前置解读、总结说明文字,任何分析类文字一律不写,正文只保留 3 条话术条目。\n");
|
sb.append("你是第九联盟培训机构资深销售顾问。你的唯一任务是:基于「最新的问题」生成 3 条推荐回复话术。\n\n");
|
||||||
|
|
||||||
|
sb.append("## 输入数据(按优先级处理)\n");
|
||||||
|
sb.append("【最高优先级 · 必须直接回应】\n");
|
||||||
|
sb.append("最新的问题:").append(externalStr).append("\n\n");
|
||||||
|
sb.append("【参考上下文 · 仅辅助理解背景】\n");
|
||||||
|
sb.append("对话历史:").append(chatHistoryContext).append("\n");
|
||||||
|
sb.append("(说明:INTERNAL=销售,EXTERNAL=客户)\n\n");
|
||||||
|
|
||||||
|
sb.append("## 核心任务\n");
|
||||||
|
sb.append("1. 首先判断「最新的问题」是否为结束语(如'好的谢谢'、'我先考虑下'、'再见'、'不用了'等)。\n");
|
||||||
|
sb.append(" - 若是结束语:生成 3 条礼貌、得体、留有余地的结束回复,不强行推销。\n");
|
||||||
|
sb.append(" - 若不是结束语:针对最新问题直接回应,对话历史仅用于避免重复、保持连贯,不得让历史话题覆盖最新问题。\n");
|
||||||
|
sb.append("2. 内部自动完成 5 项分析(客户意图、情绪状态、购买阶段、关键信号、未解决问题),仅用于生成话术,严禁输出。\n\n");
|
||||||
|
|
||||||
|
sb.append("## 输出铁律(违反任何一条即不合格)\n");
|
||||||
|
sb.append("- 仅输出 3 条话术条目,严禁输出任何分析、解读、总结、标题、过渡段、前置说明。\n");
|
||||||
|
sb.append("- 单条话术≤100 字,整体风格专业、自然、有温度,像真人销售顾问在沟通。\n");
|
||||||
|
sb.append("- 禁止输出:客户情况总结、对话解读、阶段判断、需求分析、铺垫文字、总述段落。\n\n");
|
||||||
|
|
||||||
if (staffName != null && !staffName.isEmpty()) {
|
if (staffName != null && !staffName.isEmpty()) {
|
||||||
sb.append("## 重要约束\n");
|
sb.append("## 身份约束\n");
|
||||||
sb.append("你的名字是「").append(staffName).append("」,在回复中如果需要自称或介绍自己,必须使用这个名字,绝对禁止编造任何其他名字(如小王、小李、小张等)。\n");
|
sb.append("你的名字是「").append(staffName).append("」。如需自称或介绍,必须使用此名,绝对禁止编造其他名字(如小王、小李等)。\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append("## 对话历史\n");
|
sb.append("## 内容规范\n");
|
||||||
|
sb.append("1. 严禁编造具体金额、折扣比例、名额数量、截止日期等精确数值;缺乏准确数据时,使用模糊表述(如'一定减免'、'额外优惠')或引导私信/电话确认。\n");
|
||||||
|
sb.append("2. 若用户附带参考文本:优先基于参考文本,再用自有知识补充,不得与参考文本事实冲突。\n");
|
||||||
|
sb.append("3. 必须引用价格时,使用【具体金额以官方最新政策为准】等占位符,禁止直接生成数字。\n");
|
||||||
|
sb.append("4. 回答紧扣用户最新提问,不主动脑补用户未提出的需求,不随意延伸无关内容。\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 构建生成话术的提示词(角色设定)
|
||||||
|
*/
|
||||||
|
public String buildPrompt(String chatHistoryContext, String staffName, String externalStr) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append("你是第九联盟培训机构的资深销售顾问。你的唯一任务是:根据下方信息,输出 3 条推荐回复话术。\n\n");
|
||||||
|
|
||||||
|
// === 核心优先级:最新问题永远是第一响应目标 ===
|
||||||
|
sb.append("## 最高优先级:最新问题(必须优先直接回应)\n");
|
||||||
|
sb.append(externalStr);
|
||||||
|
sb.append("\n\n");
|
||||||
|
|
||||||
|
// === 对话历史降级为参考 ===
|
||||||
|
sb.append("## 参考上下文:对话历史(仅作背景参考,不优先回应历史话题)\n");
|
||||||
sb.append(chatHistoryContext);
|
sb.append(chatHistoryContext);
|
||||||
sb.append("\n");
|
sb.append("\n\n");
|
||||||
sb.append("## 说明\n");
|
|
||||||
sb.append("其中INTERNAL表示销售,EXTERNAL表示客户\n");
|
// === 身份约束 ===
|
||||||
sb.append(" ## 内置分析逻辑(仅 AI 内部运算,绝对不能打印 / 展示出来)\n");
|
if (staffName != null && !staffName.isEmpty()) {
|
||||||
sb.append("内部自动完成 5 项分析,仅用于生成话术,不对外输出:1. 客户意图;2. 情绪状态;3. 购买阶段;4. 关键信号;5. 未解决问题\n");
|
sb.append("## 身份约束\n");
|
||||||
sb.append("## 硬性输出铁律(违规判定:出现任何分析文字直接不合格)\n");
|
sb.append("你的名字是「").append(staffName).append("」,在回复中如需自称或介绍自己,必须使用此名字,绝对禁止编造其他任何名字(如小王、小李、小张等)。\n\n");
|
||||||
sb.append("禁止输出:客户情况总结、对话解读、阶段判断、需求分析、前置铺垫、过渡介绍、标题说明、总述段落;\n");
|
}
|
||||||
sb.append("单条话术文本≤100 字,整体风格专业、自然、有温度。\n");
|
|
||||||
sb.append("##规范");
|
// === 内置分析(不可输出) ===
|
||||||
sb.append("1. 你可以调用自身储备的可靠知识进行回答、推理、拓展。\n" +
|
sb.append("## 内置分析逻辑(仅 AI 内部运算,严禁输出)\n");
|
||||||
"2. 区分信息可信度:确定的常识/成熟理论直接输出;存在多种说法、没有统一结论的内容,主动说明存在不同观点。\n" +
|
sb.append("请在内部自动完成以下 5 项分析,仅用于生成话术,不对外输出:\n");
|
||||||
"3. 严禁编造不存在的数据、案例、文档、人名、规则、专业定义。不确定、没有把握的内容,直接说明“暂无确切信息”,禁止依靠想象强行给出答案。\n" +
|
sb.append("1. 客户意图;2. 情绪状态;3. 购买阶段;4. 关键信号;5. 未解决问题。\n\n");
|
||||||
"4. 如果用户附带参考文本:优先基于参考文本内容,再使用自有知识补充拓展,不能和参考文本事实冲突。\n" +
|
|
||||||
"5. 不要随意延伸无关内容,回答紧扣用户提问,不主动脑补用户未提出的需求。");
|
// === 结束语识别分支 ===
|
||||||
|
sb.append("## 结束语识别规则\n");
|
||||||
|
sb.append("若最新问题属于结束语(如“好的”“谢谢”“我先考虑一下”“回头再说”“拜拜”“再见”等),则输出 3 条简短、礼貌、得体的结束回复,不强行推销、不追问、不延伸新话题。\n\n");
|
||||||
|
|
||||||
|
// === 输出铁律 ===
|
||||||
|
sb.append("## 硬性输出铁律(违规即不合格)\n");
|
||||||
|
sb.append("- 严禁输出:客户情况总结、对话解读、阶段判断、需求分析、前置铺垫、过渡介绍、标题说明、总述段落、任何分析类文字。\n");
|
||||||
|
sb.append("- 正文只保留 3 条话术条目,每条话术文本 ≤100 字。\n");
|
||||||
|
sb.append("- 整体风格:专业、自然、有温度,像真人销售顾问在沟通。\n\n");
|
||||||
|
|
||||||
|
// === 内容规范 ===
|
||||||
|
sb.append("## 内容规范\n");
|
||||||
|
sb.append("1. 严禁编造具体金额、折扣比例、名额数量、截止日期等精确数值;如缺乏准确数据,禁止填写任何数字,使用模糊表述(如“一定减免”“额外优惠”“具体金额我帮您确认”)或引导用户私信/电话确认细节。\n");
|
||||||
|
sb.append("2. 若话术包含任何未经核实的数字,全部替换为模糊表述或占位符。\n");
|
||||||
|
sb.append("3. 必须引用价格时,使用【具体金额以官方最新政策为准】等占位符,禁止直接生成数字。\n");
|
||||||
|
sb.append("4. 若用户附带参考文本:优先基于参考文本内容,再使用自有知识补充拓展,不得与参考文本事实冲突。\n");
|
||||||
|
sb.append("5. 不要随意延伸无关内容,回答紧扣用户最新提问,不主动脑补用户未提出的需求。\n");
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user