| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- package com.fs.company.service.workflow.execution;
- import com.fs.company.service.workflow.QualityScoringService;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.stereotype.Service;
- import java.util.Map;
- /**
- * 统一出站前 8 维质量评分:AI 生成 / fallback / 沉默唤醒等必须评分后才可发送;租户配置话术跳过。
- */
- @Service
- public class LobsterOutboundQualityGate {
- private static final Logger log = LoggerFactory.getLogger(LobsterOutboundQualityGate.class);
- public static final String VAR_SKIP_QUALITY_SCORE = "_skipQualityScore";
- public static final String VAR_QUALITY_SCORED = "_qualityScoredAtOutbound";
- public static final String VAR_OUTBOUND_SOURCE = "_outboundSource";
- @Autowired(required = false)
- private QualityScoringService qualityScoringService;
- @Value("${lobster.outbound.quality-gate.enabled:true}")
- private boolean enabled;
- public static void markTenantConfiguredScript(Map<String, Object> variables) {
- if (variables == null) {
- return;
- }
- variables.put(VAR_SKIP_QUALITY_SCORE, Boolean.TRUE);
- variables.put(VAR_OUTBOUND_SOURCE, "tenant_script");
- }
- public static void markAiGenerated(Map<String, Object> variables, String source) {
- if (variables == null) {
- return;
- }
- variables.remove(VAR_SKIP_QUALITY_SCORE);
- variables.remove(VAR_QUALITY_SCORED);
- if (source != null && !source.isBlank()) {
- variables.put(VAR_OUTBOUND_SOURCE, source);
- }
- }
- public static void markScoredInEvolution(Map<String, Object> variables) {
- if (variables == null) {
- return;
- }
- variables.put(VAR_QUALITY_SCORED, Boolean.TRUE);
- }
- public static boolean shouldSkip(Map<String, Object> variables) {
- if (variables == null) {
- return false;
- }
- if (Boolean.TRUE.equals(variables.get(VAR_SKIP_QUALITY_SCORE))) {
- return true;
- }
- return Boolean.TRUE.equals(variables.get(VAR_QUALITY_SCORED));
- }
- /**
- * @return 评分通过后的正文;null 表示拦截不发
- */
- public String applyBeforeDeliver(Long companyId, String message, Map<String, Object> variables) {
- if (message == null || message.isBlank()) {
- return message;
- }
- if (!enabled || shouldSkip(variables)) {
- return message;
- }
- if (LobsterMessageTemplateRenderer.isInternalInstructionText(message)) {
- log.warn("[OutboundQualityGate] block internal instruction text companyId={}", companyId);
- return null;
- }
- if (qualityScoringService == null) {
- log.warn("[OutboundQualityGate] QualityScoringService unavailable, block AI outbound companyId={}",
- companyId);
- return null;
- }
- String userQuestion = resolveUserQuestion(variables);
- String knowledgeBase = variables.get("_uddWebResearch") != null
- ? variables.get("_uddWebResearch").toString() : "";
- String goal = variables.get("_lastOutboundNodeCode") != null
- ? variables.get("_lastOutboundNodeCode").toString()
- : (variables.get("_greetNodeCode") != null ? variables.get("_greetNodeCode").toString() : "");
- String templateHint = variables.get("nodeMessageTemplate") != null
- ? variables.get("nodeMessageTemplate").toString() : "";
- String source = variables.get(VAR_OUTBOUND_SOURCE) != null
- ? variables.get(VAR_OUTBOUND_SOURCE).toString() : "outbound";
- try {
- QualityScoringService.ScoringResult scoring = qualityScoringService.scoreWithRetry(
- companyId, message, userQuestion, knowledgeBase, goal, templateHint, source, null);
- if (scoring == null) {
- log.warn("[OutboundQualityGate] scoring null, block companyId={}", companyId);
- return null;
- }
- String finalContent = scoring.getFinalContent() != null ? scoring.getFinalContent().trim() : "";
- if (finalContent.isEmpty()) {
- log.warn("[OutboundQualityGate] empty after scoring, block companyId={}", companyId);
- return null;
- }
- if (LobsterMessageTemplateRenderer.isInternalInstructionText(finalContent)) {
- log.warn("[OutboundQualityGate] block instruction-like content after scoring companyId={}", companyId);
- return null;
- }
- if (!scoring.isFinalPassed()) {
- log.warn("[OutboundQualityGate] scoring not passed companyId={} reason={} score={}",
- companyId, scoring.getReason(), scoring.getFirstScore());
- return null;
- }
- if (variables != null) {
- variables.put(VAR_QUALITY_SCORED, Boolean.TRUE);
- variables.put("_lastOutboundQualityScore", scoring.getFirstScore());
- }
- return finalContent;
- } catch (Exception e) {
- log.warn("[OutboundQualityGate] scoring failed, block companyId={}: {}", companyId, e.getMessage());
- return null;
- }
- }
- private static String resolveUserQuestion(Map<String, Object> variables) {
- if (variables == null) {
- return "";
- }
- for (String key : new String[]{"lastReply", "customerMessage", "_silenceWakeCustomerMessage"}) {
- Object val = variables.get(key);
- if (val != null && !val.toString().isBlank()) {
- String text = val.toString().trim();
- if (text.startsWith("[silence_wakeup]") || text.startsWith("[wait_wake]")) {
- return "";
- }
- return text;
- }
- }
- return "";
- }
- }
|