LobsterMessageTemplateRenderer.java 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package com.fs.company.service.workflow.execution;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.fs.company.domain.CompanyWorkflowLobsterNode;
  4. import com.fs.company.service.workflow.VariableSubstitutionEngine;
  5. import com.fs.company.service.workflow.config.LobsterBrandContextHelper;
  6. import com.fs.company.service.workflow.config.LobsterIndustryCatalog;
  7. import com.fs.company.service.workflow.contact.ContactInfo;
  8. import java.util.LinkedHashMap;
  9. import java.util.Map;
  10. import java.util.regex.Pattern;
  11. /**
  12. * 消息模板渲染:品牌占位 {advisorName} + 默认 ${customerName},过滤内部 instruction 文案。
  13. */
  14. public final class LobsterMessageTemplateRenderer {
  15. private static final Pattern INTERNAL_INSTRUCTION_PREFIX = Pattern.compile(
  16. "^(Evaluate customer intent|Reply professionally|Collect key|Create follow-up|"
  17. + "Send personalized care|Politely hand off)",
  18. Pattern.CASE_INSENSITIVE);
  19. private LobsterMessageTemplateRenderer() {
  20. }
  21. /**
  22. * 是否配置了可用的开场话术(非 system/instruction 占位)。
  23. */
  24. public static boolean hasConfiguredOpeningScript(CompanyWorkflowLobsterNode node, Map<String, Object> variables) {
  25. return !extractConfiguredOpeningScript(node, variables).isEmpty();
  26. }
  27. /**
  28. * 提取开场话术优先级:流程变量 &gt; greetingConfig &gt; messageTemplate。
  29. */
  30. public static String extractConfiguredOpeningScript(CompanyWorkflowLobsterNode node, Map<String, Object> variables) {
  31. if (variables != null) {
  32. for (String key : new String[]{"greetingMessage", "welcomeMessage", "openingMessage", "bindingGreeting"}) {
  33. Object val = variables.get(key);
  34. if (val != null && !val.toString().isBlank() && !isInternalInstructionText(val.toString())) {
  35. return val.toString().trim();
  36. }
  37. }
  38. }
  39. if (node == null) {
  40. return "";
  41. }
  42. String fromGreeting = extractGreetingConfigMessage(node.getGreetingConfig());
  43. if (!fromGreeting.isEmpty() && !isInternalInstructionText(fromGreeting)) {
  44. return fromGreeting;
  45. }
  46. String template = node.getMessageTemplate();
  47. if (template != null && !template.isBlank() && !isInternalInstructionText(template.trim())) {
  48. return template.trim();
  49. }
  50. return "";
  51. }
  52. public static String extractGreetingConfigMessage(String greetingConfig) {
  53. if (greetingConfig == null || greetingConfig.isBlank()) {
  54. return "";
  55. }
  56. String trimmed = greetingConfig.trim();
  57. if (trimmed.startsWith("{")) {
  58. try {
  59. JSONObject obj = JSONObject.parseObject(trimmed);
  60. if (obj.containsKey("message")) {
  61. String msg = obj.getString("message");
  62. return msg != null ? msg.trim() : "";
  63. }
  64. if (obj.containsKey("text")) {
  65. String text = obj.getString("text");
  66. return text != null ? text.trim() : "";
  67. }
  68. } catch (Exception ignored) {
  69. }
  70. return "";
  71. }
  72. return trimmed;
  73. }
  74. /** instruction / 内部占位话术识别(含英文 Evaluate... 及默认「请回复客户」等)。 */
  75. public static boolean isInternalInstructionText(String text) {
  76. if (text == null || text.isBlank()) {
  77. return true;
  78. }
  79. String t = text.trim();
  80. if ("请根据上下文回复客户,保持自然亲切的语气。".equals(t)
  81. || "请回复客户。".equals(t)) {
  82. return true;
  83. }
  84. return INTERNAL_INSTRUCTION_PREFIX.matcher(t).find();
  85. }
  86. public static Map<String, Object> enrichConversationVariables(Map<String, Object> variables, ContactInfo contactInfo) {
  87. Map<String, Object> out = variables != null ? new LinkedHashMap<>(variables) : new LinkedHashMap<>();
  88. String customerName = resolveCustomerName(out, contactInfo);
  89. out.putIfAbsent("customerName", customerName);
  90. out.putIfAbsent("customer_name", customerName);
  91. Map<String, String> brands = brandMapFromVariables(out);
  92. out.putIfAbsent("companyShortName", brands.get("companyShortName"));
  93. out.putIfAbsent("advisorName", brands.get("advisorName"));
  94. out.putIfAbsent("mainProduct", brands.get("mainProduct"));
  95. out.putIfAbsent("company_short_name", brands.get("companyShortName"));
  96. out.putIfAbsent("advisor_name", brands.get("advisorName"));
  97. out.putIfAbsent("main_product", brands.get("mainProduct"));
  98. return out;
  99. }
  100. public static String render(String template, Map<String, Object> variables, VariableSubstitutionEngine varEngine) {
  101. if (template == null || template.isEmpty()) {
  102. return "";
  103. }
  104. Map<String, String> brands = brandMapFromVariables(variables);
  105. String withBrand = LobsterIndustryCatalog.applyBrandVars(template, brands);
  106. if (varEngine == null) {
  107. return stripUnresolvedCustomerName(withBrand);
  108. }
  109. String substituted = withBrand.contains("${") || withBrand.contains("{{")
  110. ? varEngine.substitute(withBrand, variables)
  111. : withBrand;
  112. return stripUnresolvedCustomerName(substituted);
  113. }
  114. public static boolean hasCustomerSpoken(Map<String, Object> variables) {
  115. if (variables == null) {
  116. return false;
  117. }
  118. Object inbound = variables.get("_customerInboundCount");
  119. if (inbound instanceof Number && ((Number) inbound).intValue() > 0) {
  120. return true;
  121. }
  122. Object replyCount = variables.get("replyCount");
  123. if (replyCount instanceof Number && ((Number) replyCount).intValue() > 0) {
  124. return true;
  125. }
  126. Object lastReply = variables.get("lastReply");
  127. return lastReply != null && !lastReply.toString().isBlank();
  128. }
  129. public static void markCustomerInbound(Map<String, Object> variables) {
  130. if (variables == null) {
  131. return;
  132. }
  133. int count = 0;
  134. Object raw = variables.get("_customerInboundCount");
  135. if (raw instanceof Number) {
  136. count = ((Number) raw).intValue();
  137. }
  138. variables.put("_customerInboundCount", count + 1);
  139. }
  140. private static String resolveCustomerName(Map<String, Object> variables, ContactInfo contactInfo) {
  141. if (variables != null) {
  142. for (String key : new String[]{"customerName", "customer_name", "contactName", "nickname"}) {
  143. Object val = variables.get(key);
  144. if (val != null && !val.toString().isBlank() && !val.toString().contains("${")) {
  145. return val.toString().trim();
  146. }
  147. }
  148. }
  149. if (contactInfo != null && contactInfo.getName() != null && !contactInfo.getName().isBlank()) {
  150. return contactInfo.getName().trim();
  151. }
  152. return "您";
  153. }
  154. public static Map<String, String> brandMapFromVariables(Map<String, Object> variables) {
  155. Map<String, String> brands = LobsterBrandContextHelper.resolve(null, null, null, null, null);
  156. if (variables == null) {
  157. return brands;
  158. }
  159. copyBrandVar(variables, brands, "companyShortName", "company_short_name");
  160. copyBrandVar(variables, brands, "advisorName", "advisor_name");
  161. copyBrandVar(variables, brands, "mainProduct", "main_product");
  162. return brands;
  163. }
  164. private static void copyBrandVar(Map<String, Object> variables, Map<String, String> brands,
  165. String camelKey, String snakeKey) {
  166. Object camel = variables.get(camelKey);
  167. if (camel != null && !camel.toString().isBlank()) {
  168. brands.put(camelKey, camel.toString().trim());
  169. return;
  170. }
  171. Object snake = variables.get(snakeKey);
  172. if (snake != null && !snake.toString().isBlank()) {
  173. brands.put(camelKey, snake.toString().trim());
  174. }
  175. }
  176. private static String stripUnresolvedCustomerName(String text) {
  177. if (text == null) {
  178. return "";
  179. }
  180. return text.replace("${customerName}", "您").replace("${customer_name}", "您");
  181. }
  182. }