|
|
@@ -13,6 +13,7 @@ import com.fs.company.mapper.CompanyWorkflowLobsterMapper;
|
|
|
import com.fs.company.mapper.CompanyWorkflowLobsterNodeMapper;
|
|
|
import com.fs.company.mapper.LobsterChatSessionMapper;
|
|
|
import com.fs.company.mapper.LobsterChatMsgMapper;
|
|
|
+import com.fs.company.mapper.LobsterAuxiliaryMapper;
|
|
|
import com.fs.company.mapper.LobsterNodeExecutionLogMapper;
|
|
|
import com.fs.company.mapper.LobsterWorkflowInstanceMapper;
|
|
|
import com.fs.company.domain.LobsterChatSession;
|
|
|
@@ -95,11 +96,11 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
@Autowired
|
|
|
private SemanticAnalyzer semanticAnalyzer;
|
|
|
|
|
|
- /** 个性化引擎:实现千人千面,根据用户画像和偏好定制消息内�?*/
|
|
|
+ /** 个性化引擎:实现千人千面,根据用户画像和偏好定制消息内容 */
|
|
|
@Autowired
|
|
|
private PersonalizationEngine personalizationEngine;
|
|
|
|
|
|
- /** 用户级节点优化器:针对特定用户自动优化后续节点内�?*/
|
|
|
+ /** 用户级节点优化器:针对特定用户自动优化后续节点内容 */
|
|
|
@Autowired
|
|
|
private com.fs.company.service.workflow.evolution.UserNodeOptimizer userNodeOptimizer;
|
|
|
|
|
|
@@ -119,7 +120,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
private DuplicateReplyDetector dedupDetector;
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
- private auxMapper auxMapper;
|
|
|
+ private LobsterAuxiliaryMapper auxMapper;
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
private ChannelTypeRegistry channelRegistry;
|
|
|
@@ -152,7 +153,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
|
|
|
List<CompanyWorkflowLobsterNode> nodes = nodeMapper.selectByWorkflowIdAndCompanyId(workflowId, companyId);
|
|
|
if (nodes == null || nodes.isEmpty()) {
|
|
|
- return AjaxResult.error("工作流节点为�?);
|
|
|
+ return AjaxResult.error("工作流节点为空");
|
|
|
}
|
|
|
|
|
|
nodes.sort(Comparator.comparingInt(CompanyWorkflowLobsterNode::getSortNo));
|
|
|
@@ -199,41 +200,41 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
|
|
|
MessageChannelResult sendResult = deliverMessage(companyId, contactId, channelType, message, enrichedVars, instance.getId(), workflowId);
|
|
|
if (sendResult != null && !sendResult.isSuccess()) {
|
|
|
- logger.warn("首节点消息发送失�? instanceId={}, error={}", instance.getId(), sendResult.getErrorMsg());
|
|
|
+ logger.warn("首节点消息发送失败, instanceId={}, error={}", instance.getId(), sendResult.getErrorMsg());
|
|
|
}
|
|
|
|
|
|
heartbeatScheduler.registerInstance(companyId, instance.getId(),
|
|
|
HeartbeatConfig.defaultConfig(companyId, instance.getId(), workflowId, contactId, channelType));
|
|
|
|
|
|
- return AjaxResult.success("工作流启动成�?, instance);
|
|
|
+ return AjaxResult.success("工作流启动成功", instance);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 节点类型常量(与前端 visual.vue 1-12 + skill.md 13/14 对齐�?
|
|
|
- * 编号统一规则�?
|
|
|
- * 1 开�? 2 消息(AI回复) 3 判断 4 等待 5 结束
|
|
|
- * 6 API 7 购物�?成单) 8 优惠�?订单) 9 标签
|
|
|
+ * 节点类型常量(与前端 visual.vue 1-12 + skill.md 13/14 对齐)
|
|
|
+ * 编号统一规则:
|
|
|
+ * 1 开始 2 消息(AI回复) 3 判断 4 等待 5 结束
|
|
|
+ * 6 API 7 购物车(成单) 8 优惠券(订单) 9 标签
|
|
|
* 10 赠礼(关怀) 11 文档(调查) 12 用户(画像)
|
|
|
* 13 复购(skill.md 新增) 14 智能API(skill.md 新增)
|
|
|
* 15/16/99 为历史兼容编号,保留不删,新数据不要再用
|
|
|
*/
|
|
|
private static final int NODE_TYPE_START = 1;
|
|
|
- private static final int NODE_TYPE_AI_PROCESS = 2; // 消息节点(AI 回复�?
|
|
|
- private static final int NODE_TYPE_SEND_MESSAGE = 3; // 判断节点(前�?visual.vue 编号 3�?
|
|
|
+ private static final int NODE_TYPE_AI_PROCESS = 2; // 消息节点(AI 回复)
|
|
|
+ private static final int NODE_TYPE_SEND_MESSAGE = 3; // 判断节点(前端 visual.vue 编号 3)
|
|
|
private static final int NODE_TYPE_WAIT = 4;
|
|
|
- private static final int NODE_TYPE_CONDITION = 5; // 结束节点(前�?visual.vue 编号 5)—�?历史名保�?
|
|
|
+ private static final int NODE_TYPE_CONDITION = 5; // 结束节点(前端 visual.vue 编号 5)——历史名保留
|
|
|
private static final int NODE_TYPE_TASK = 6; // API 节点
|
|
|
- private static final int NODE_TYPE_COLLECT_INFO = 7; // 购物车节点(≈skill.md 成单�?
|
|
|
- private static final int NODE_TYPE_TRANSFER_HUMAN = 8; // 优惠券节点(≈skill.md 订单)—�?历史名保�?
|
|
|
+ private static final int NODE_TYPE_COLLECT_INFO = 7; // 购物车节点(≈skill.md 成单)
|
|
|
+ private static final int NODE_TYPE_TRANSFER_HUMAN = 8; // 优惠券节点(≈skill.md 订单)——历史名保留
|
|
|
private static final int NODE_TYPE_TAG_OPERATION = 9; // 标签节点
|
|
|
- private static final int NODE_TYPE_HTTP_CALL = 10; // 赠礼节点(≈skill.md 关怀�?
|
|
|
- private static final int NODE_TYPE_RAG_QUERY = 11; // 文档节点(≈skill.md 调查�?
|
|
|
- private static final int NODE_TYPE_CODE_EXEC = 12; // 用户节点(≈skill.md 画像�?
|
|
|
- private static final int NODE_TYPE_LOOP = 13; // 复购节点(skill.md 新增�?
|
|
|
- private static final int NODE_TYPE_DB_QUERY = 14; // 智能 API 节点(skill.md 新增�?
|
|
|
+ private static final int NODE_TYPE_HTTP_CALL = 10; // 赠礼节点(≈skill.md 关怀)
|
|
|
+ private static final int NODE_TYPE_RAG_QUERY = 11; // 文档节点(≈skill.md 调查)
|
|
|
+ private static final int NODE_TYPE_CODE_EXEC = 12; // 用户节点(≈skill.md 画像)
|
|
|
+ private static final int NODE_TYPE_LOOP = 13; // 复购节点(skill.md 新增)
|
|
|
+ private static final int NODE_TYPE_DB_QUERY = 14; // 智能 API 节点(skill.md 新增)
|
|
|
private static final int NODE_TYPE_SUB_WORKFLOW = 15; // 历史保留
|
|
|
private static final int NODE_TYPE_VARIABLE = 16; // 历史保留
|
|
|
- private static final int NODE_TYPE_END = 99; // 历史保留(新数据�?5�?
|
|
|
+ private static final int NODE_TYPE_END = 99; // 历史保留(新数据用 5)
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
private MultiTurnDialogueManager multiTurnDialogueManager;
|
|
|
@@ -249,7 +250,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
return AjaxResult.error("工作流实例不存在");
|
|
|
}
|
|
|
if (!"running".equals(instance.getStatus())) {
|
|
|
- return AjaxResult.error("工作流实例不在运行状�?);
|
|
|
+ return AjaxResult.error("工作流实例不在运行状态");
|
|
|
}
|
|
|
/* 人工接管模式: 跳过AI处理, 等待人工回复 */
|
|
|
if ("human".equals(instance.getControlMode())) {
|
|
|
@@ -263,7 +264,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
|
|
|
List<CompanyWorkflowLobsterNode> nodes = nodeMapper.selectByWorkflowIdAndCompanyId(instance.getWorkflowId(), companyId);
|
|
|
if (nodes == null || nodes.isEmpty()) {
|
|
|
- return AjaxResult.error("工作流节点为�?);
|
|
|
+ return AjaxResult.error("工作流节点为空");
|
|
|
}
|
|
|
nodes.sort(Comparator.comparingInt(CompanyWorkflowLobsterNode::getSortNo));
|
|
|
|
|
|
@@ -278,13 +279,13 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
String nodeCode = currentNode.getNodeCode();
|
|
|
|
|
|
/*
|
|
|
- * ===== 处理客户回复:语义分�?+ 节点类型路由 =====
|
|
|
+ * ===== 处理客户回复:语义分析 + 节点类型路由 =====
|
|
|
*/
|
|
|
if (customerReply != null && !customerReply.isEmpty()) {
|
|
|
/* 1. 记录已接收的日志 */
|
|
|
logNodeExecution(companyId, instanceId, instance.getWorkflowId(), currentIndex, currentNode, null, customerReply, "received");
|
|
|
|
|
|
- /* 1.5 客户消息速率风控�?秒内超过5条消�?�?触发冷却/转人�?*/
|
|
|
+ /* 1.5 客户消息速率风控:10秒内超过5条消息,触发冷却/转人工 */
|
|
|
String rateKey = "rate_" + instance.getContactId() + "_" + instanceId;
|
|
|
long now = System.currentTimeMillis();
|
|
|
Object rtObj = variables.get(rateKey);
|
|
|
@@ -293,22 +294,22 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
if (now - rateWindow[1] > 5_000) { newCount = 1; }
|
|
|
variables.put(rateKey, new long[]{newCount, now});
|
|
|
if (newCount > 5) {
|
|
|
- logger.warn("[LobsterWorkflow] 客户消息速率异常: instanceId={}, {}�?5�? 冷却30�?, instanceId, newCount);
|
|
|
+ logger.warn("[LobsterWorkflow] 客户消息速率异常: instanceId={}, count={}", instanceId, newCount);
|
|
|
String cooldownReply = "您发的消息有点快,请稍等片刻,我马上回来~";
|
|
|
deliverMessage(companyId, instance.getContactId(), channelType, cooldownReply, variables, instanceId, instance.getWorkflowId());
|
|
|
variables.put("_cooldown_until", System.currentTimeMillis() + 30_000);
|
|
|
instance.setVariables(JSON.toJSONString(variables));
|
|
|
instance.setUpdateTime(DateUtils.getNowDate());
|
|
|
instanceMapper.updateById(instance);
|
|
|
- return AjaxResult.error("消息频率过高,已进入30秒冷�?);
|
|
|
+ return AjaxResult.error("消息频率过高,已进入30秒冷却");
|
|
|
}
|
|
|
|
|
|
- // 冷却期检�?
|
|
|
+ // 冷却期检查
|
|
|
Object cdObj = variables.get("_cooldown_until");
|
|
|
if (cdObj instanceof Number) {
|
|
|
long cooldownUntil = ((Number) cdObj).longValue();
|
|
|
if (System.currentTimeMillis() < cooldownUntil) {
|
|
|
- return AjaxResult.error("冷却中,请稍后再�?);
|
|
|
+ return AjaxResult.error("冷却中,请稍后再试");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -318,12 +319,12 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
if (gcObj instanceof Number) globalCount = ((Number) gcObj).intValue();
|
|
|
variables.put("replyCount", globalCount + 1);
|
|
|
|
|
|
- /* 2.5 死循环防护:全局上限 + 节点访问频率检�?*/
|
|
|
+ /* 2.5 死循环防护:全局上限 + 节点访问频率检查 */
|
|
|
if (globalCount + 1 > 50) {
|
|
|
logger.warn("[LobsterWorkflow] 全局回复超过50轮,强制终止: instanceId={}", instanceId);
|
|
|
completeInstance(instance);
|
|
|
heartbeatScheduler.unregisterInstance(instanceId);
|
|
|
- return AjaxResult.error("工作流交互轮次过多,已自动终�?);
|
|
|
+ return AjaxResult.error("工作流交互轮次过多,已自动终止");
|
|
|
}
|
|
|
String prevNode = (String) variables.get("_prevNodeCode");
|
|
|
String transitionKey = prevNode + "->" + nodeCode;
|
|
|
@@ -333,14 +334,14 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
transitionCount++;
|
|
|
variables.put("_trans_" + transitionKey, transitionCount);
|
|
|
if (transitionCount > 8) {
|
|
|
- logger.warn("[LobsterWorkflow] 节点跳转死循环检�? {} 出现{}次,强制终止", transitionKey, transitionCount);
|
|
|
+ logger.warn("[LobsterWorkflow] 节点跳转死循环检测,{} 出现{}次,强制终止", transitionKey, transitionCount);
|
|
|
completeInstance(instance);
|
|
|
heartbeatScheduler.unregisterInstance(instanceId);
|
|
|
return AjaxResult.error("检测到死循环,工作流已自动终止");
|
|
|
}
|
|
|
variables.put("_prevNodeCode", nodeCode);
|
|
|
|
|
|
- /* 3. 单节点轮次计�?*/
|
|
|
+ /* 3. 单节点轮次计数 */
|
|
|
String nodeRoundKey = "nodeRound_" + nodeCode;
|
|
|
int nodeRound = 0;
|
|
|
Object nrObj = variables.get(nodeRoundKey);
|
|
|
@@ -357,11 +358,11 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
|
|
|
recordEvolution(companyId, instance, variables, customerReply, semanticResult);
|
|
|
|
|
|
- /* 语义分析AI计费:按Token量估算(中文�?.5 token/字) */
|
|
|
+ /* 语义分析AI计费:按Token量估算(中文约1.5 token/字) */
|
|
|
int estimatedTokens = customerReply != null ? (int)(customerReply.length() * 1.5) + 200 : 200;
|
|
|
billingService.tryConsumeByTokens(companyId, estimatedTokens, 100, defaultAiModel);
|
|
|
|
|
|
- /* 5. COLLECT_INFO 节点:收集信�?*/
|
|
|
+ /* 5. COLLECT_INFO 节点:收集信息 */
|
|
|
if (nodeType != null && nodeType == NODE_TYPE_COLLECT_INFO) {
|
|
|
return handleCollectInfo(companyId, instance, currentNode, nodes, currentIndex, variables, channelType, customerReply, nodeRound);
|
|
|
}
|
|
|
@@ -371,7 +372,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
return handleTransferHuman(companyId, instance, currentNode, variables, customerReply);
|
|
|
}
|
|
|
|
|
|
- /* 7. 多轮对话检查:max_rounds > 0 且未达上�?�?停留当前节点 */
|
|
|
+ /* 7. 多轮对话检查:max_rounds > 0 且未达上限,停留当前节点 */
|
|
|
Integer maxRounds = currentNode.getMaxRounds();
|
|
|
if (maxRounds != null && maxRounds > 0 && nodeRound < maxRounds) {
|
|
|
String repeatMessage = generateNodeMessage(currentNode, variables);
|
|
|
@@ -393,10 +394,10 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
result.put("nodeRound", nodeRound);
|
|
|
result.put("maxRounds", maxRounds);
|
|
|
result.put("stayOnNode", true);
|
|
|
- return AjaxResult.success("多轮对话�? + nodeRound + "/" + maxRounds + "�?, result);
|
|
|
+ return AjaxResult.success("多轮对话(" + nodeRound + "/" + maxRounds + ")", result);
|
|
|
}
|
|
|
|
|
|
- /* 8. MultiTurnDialogueManager 集成:专业多轮对话管�?*/
|
|
|
+ /* 8. MultiTurnDialogueManager 集成:专业多轮对话管理 */
|
|
|
if (multiTurnDialogueManager != null && nodeType != null &&
|
|
|
(nodeType == NODE_TYPE_SEND_MESSAGE || nodeType == NODE_TYPE_AI_PROCESS)) {
|
|
|
MultiTurnDialogueManager.DialogueResult dialogueResult = multiTurnDialogueManager.processDialogue(
|
|
|
@@ -416,12 +417,12 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
result.put("message", dialogueResult.getReply());
|
|
|
result.put("stayOnNode", true);
|
|
|
result.put("dialogueRound", dialogueResult.getCurrentRound());
|
|
|
- return AjaxResult.success("多轮对话(MM)�? + dialogueResult.getCurrentRound() + "�?, result);
|
|
|
+ return AjaxResult.success("多轮对话(MM)(" + dialogueResult.getCurrentRound() + ")", result);
|
|
|
}
|
|
|
variables.putAll(dialogueResult.getCollectedVariables());
|
|
|
}
|
|
|
} else {
|
|
|
- /* 无客户回复:推进前先记录前序节点发�?*/
|
|
|
+ /* 无客户回复:推进前先记录前序节点发送 */
|
|
|
logNodeExecution(companyId, instanceId, instance.getWorkflowId(), currentIndex, currentNode, null, null, "received");
|
|
|
}
|
|
|
|
|
|
@@ -484,7 +485,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
return handleTagOperation(companyId, instance, nextNode, nodes, currentIndex, nextIndex, variables, channelType);
|
|
|
}
|
|
|
|
|
|
- /* 未知节点类型 �?AI动态生成执行逻辑 */
|
|
|
+ /* 未知节点类型 → AI动态生成执行逻辑 */
|
|
|
if (nextNode.getNodeType() != null && nextNode.getNodeType() > 0 &&
|
|
|
nextNode.getNodeType() != NODE_TYPE_START && nextNode.getNodeType() != NODE_TYPE_END) {
|
|
|
boolean isKnown = false;
|
|
|
@@ -511,7 +512,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
|
|
|
MessageChannelResult sendResult = deliverMessage(companyId, instance.getContactId(), channelType, message, variables, instanceId, instance.getWorkflowId());
|
|
|
if (sendResult != null && !sendResult.isSuccess()) {
|
|
|
- logger.warn("节点消息发送失�? instanceId={}, nodeIndex={}, error={}", instanceId, nextIndex, sendResult.getErrorMsg());
|
|
|
+ logger.warn("节点消息发送失败, instanceId={}, nodeIndex={}, error={}", instanceId, nextIndex, sendResult.getErrorMsg());
|
|
|
}
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
@@ -525,7 +526,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * COLLECT_INFO 节点:多轮信息收�?
|
|
|
+ * COLLECT_INFO 节点:多轮信息收集
|
|
|
* 从nodeConfig中读取collect_fields配置,逐轮询问每个字段
|
|
|
* 全部收集完毕后推进到下一节点
|
|
|
*/
|
|
|
@@ -593,10 +594,10 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
result.put("collectField", nextField);
|
|
|
result.put("collectProgress", (collectedIndex + 1) + "/" + fieldsToCollect.size());
|
|
|
result.put("stayOnNode", true);
|
|
|
- return AjaxResult.success("信息采集�?" + (collectedIndex + 1) + "/" + fieldsToCollect.size() + ")", result);
|
|
|
+ return AjaxResult.success("信息采集中(" + (collectedIndex + 1) + "/" + fieldsToCollect.size() + ")", result);
|
|
|
}
|
|
|
|
|
|
- /* 信息收集完毕 �?推进 */
|
|
|
+ /* 信息收集完毕 → 推进 */
|
|
|
message = completionMessage;
|
|
|
if (message.contains("${")) message = varEngine.substitute(message, variables);
|
|
|
|
|
|
@@ -649,7 +650,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
|
|
|
String nodeConfig = node.getNodeConfig();
|
|
|
String handoffMsg = promptService != null ?
|
|
|
- promptService.getContent("handoff_default", null) : "已为您转接人工客服,请稍�?..";
|
|
|
+ promptService.getContent("handoff_default", null) : "已为您转接人工客服,请稍候...";
|
|
|
if (nodeConfig != null && !nodeConfig.isEmpty()) {
|
|
|
try {
|
|
|
JSONObject config = JSON.parseObject(nodeConfig);
|
|
|
@@ -681,30 +682,30 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult pauseWorkflow(Long companyId, Long instanceId) {
|
|
|
LobsterWorkflowInstance instance = instanceMapper.selectByIdAndCompanyId(instanceId, companyId);
|
|
|
- if (instance == null) return AjaxResult.error("实例不存�?);
|
|
|
+ if (instance == null) return AjaxResult.error("实例不存在");
|
|
|
instanceMapper.updateStatus(instanceId, companyId, "paused");
|
|
|
heartbeatScheduler.unregisterInstance(instanceId);
|
|
|
- return AjaxResult.success("已暂�?);
|
|
|
+ return AjaxResult.success("已暂停");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult resumeWorkflow(Long companyId, Long instanceId) {
|
|
|
LobsterWorkflowInstance instance = instanceMapper.selectByIdAndCompanyId(instanceId, companyId);
|
|
|
- if (instance == null) return AjaxResult.error("实例不存�?);
|
|
|
+ if (instance == null) return AjaxResult.error("实例不存在");
|
|
|
instanceMapper.updateStatus(instanceId, companyId, "running");
|
|
|
Map<String, Object> variables = parseVariables(instance.getVariables());
|
|
|
String channelType = (String) variables.getOrDefault("channelType", "QW");
|
|
|
heartbeatScheduler.registerInstance(companyId, instanceId,
|
|
|
HeartbeatConfig.defaultConfig(companyId, instanceId, instance.getWorkflowId(), instance.getContactId(), channelType));
|
|
|
- return AjaxResult.success("已恢�?);
|
|
|
+ return AjaxResult.success("已恢复");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult terminateWorkflow(Long companyId, Long instanceId, String reason) {
|
|
|
LobsterWorkflowInstance instance = instanceMapper.selectByIdAndCompanyId(instanceId, companyId);
|
|
|
- if (instance == null) return AjaxResult.error("实例不存�?);
|
|
|
+ if (instance == null) return AjaxResult.error("实例不存在");
|
|
|
instance.setStatus("terminated");
|
|
|
instance.setErrorMessage(reason);
|
|
|
instance.setEndTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
|
|
@@ -712,7 +713,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
instance.setUpdateTime(DateUtils.getNowDate());
|
|
|
instanceMapper.updateById(instance);
|
|
|
heartbeatScheduler.unregisterInstance(instanceId);
|
|
|
- return AjaxResult.success("已终�?);
|
|
|
+ return AjaxResult.success("已终止");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -747,7 +748,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
try {
|
|
|
return contactAdapterRouter.resolve(companyId, contactId, channelType);
|
|
|
} catch (Exception e) {
|
|
|
- logger.warn("解析联系人信息失�? contactId={}, channelType={}", contactId, channelType, e);
|
|
|
+ logger.warn("解析联系人信息失败, contactId={}, channelType={}", contactId, channelType, e);
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
@@ -756,12 +757,12 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
String message, Map<String, Object> variables,
|
|
|
Long instanceId, Long workflowId) {
|
|
|
try {
|
|
|
- /* 重复检测:跳过已发送过的相同消�?*/
|
|
|
+ /* 重复检测:跳过已发送过的相同消息 */
|
|
|
if (dedupDetector != null && message != null && !message.isEmpty()) {
|
|
|
if (dedupDetector.isDuplicate(contactId, message)) {
|
|
|
message = dedupDetector.rewriteIfDuplicate(contactId, message);
|
|
|
if (message == null) {
|
|
|
- logger.info("[Dedup] 消息已重复且无法改写, 跳过发�? contactId={}", contactId);
|
|
|
+ logger.info("[Dedup] 消息已重复且无法改写, 跳过发送, contactId={}", contactId);
|
|
|
return MessageChannelResult.ok(channelType, "dedup_skipped");
|
|
|
}
|
|
|
}
|
|
|
@@ -798,12 +799,11 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
logger.error("消息触达失败: companyId={}, contactId={}, channelType={}", companyId, contactId, channelType, e);
|
|
|
/* 记录到死信队列,自动重试 */
|
|
|
if (deadLetterQueue != null && instanceId != null) {
|
|
|
- deadLetterQueue.recordFailure(instanceId, companyId, contactId, channelType,
|
|
|
- message, e.getMessage());
|
|
|
+ deadLetterQueue.enqueue(companyId, "msg_delivery", message, e.getMessage());
|
|
|
}
|
|
|
return MessageChannelResult.fail(channelType, "消息触达异常: " + e.getMessage());
|
|
|
} finally {
|
|
|
- /* 渠道消息计费(每发送一条消息扣企微助手余额�?*/
|
|
|
+ /* 渠道消息计费(每发送一条消息扣企微助手余额) */
|
|
|
if (channelType != null) {
|
|
|
billingService.tryConsume(companyId, BillingService.CONSUME_WECHAT_HELPER,
|
|
|
new java.math.BigDecimal("0.005"), "龙虾引擎消息发送[" + channelType + "]");
|
|
|
@@ -812,10 +812,10 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 同步写入chat_msg�? 打通ChatSession聚合页面
|
|
|
+ * 同步写入chat_msg表 打通ChatSession聚合页面
|
|
|
* 以lobster_unified_contact为桥梁,支持任意渠道即插即用
|
|
|
*
|
|
|
- * 架构: contact_id(channelType) �?lobster_unified_contact �?chat_session(contact_id+channel_source_id)
|
|
|
+ * 架构: contact_id(channelType) → lobster_unified_contact → chat_session(contact_id+channel_source_id)
|
|
|
*/
|
|
|
private void bridgeToChatMsg(Long companyId, Long contactId, String channelType,
|
|
|
String message, Long instanceId, boolean success) {
|
|
|
@@ -847,13 +847,13 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 会话查找/创建 �?以lobster_unified_contact为统一桥梁
|
|
|
+ * 会话查找/创建 → 以lobster_unified_contact为统一桥梁
|
|
|
*
|
|
|
* 支持渠道: QW(qw_user) / WX(company_wx_user) / IM(im_user) /
|
|
|
* WHATSAPP(whatsapp_contact) / LINE(line_contact) /
|
|
|
* TELEGRAM(telegram_contact) / APP_IM(app_im_user) / OTHER
|
|
|
*
|
|
|
- * 新增渠道: 只需在ChannelTypeRegistry中注册即�? 无需修改此处代码
|
|
|
+ * 新增渠道: 只需在ChannelTypeRegistry中注册即可, 无需修改此处代码
|
|
|
*/
|
|
|
private Long findOrCreateSession(Long companyId, Long contactId, String channelType, Long instanceId) {
|
|
|
if (chatSessionMapper == null) return null;
|
|
|
@@ -909,27 +909,27 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
context.setVariables(variables);
|
|
|
evolutionEngine.recordInteraction(context);
|
|
|
} catch (Exception e) {
|
|
|
- logger.warn("记录进化上下文失�? instanceId={}", instance.getId(), e);
|
|
|
+ logger.warn("记录进化上下文失败, instanceId={}", instance.getId(), e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 生成节点消息(千人千�?用户级优化版�?
|
|
|
+ * 生成节点消息(千人千面/用户级优化版本)
|
|
|
*
|
|
|
- * 消息生成流程�?
|
|
|
- * 1. 变量替换:将模板中的${xxx}替换为实际变量�?
|
|
|
- * 2. 个性化定制:调用PersonalizationEngine根据用户画像和偏好定制消�?
|
|
|
- * - 分群话术覆盖:不同用户分群使用不同话�?
|
|
|
+ * 消息生成流程:
|
|
|
+ * 1. 变量替换:将模板中的${xxx}替换为实际变量值
|
|
|
+ * 2. 个性化定制:调用PersonalizationEngine根据用户画像和偏好定制消息
|
|
|
+ * - 分群话术覆盖:不同用户分群使用不同话术
|
|
|
* - 用户变量替换:替换用户专属变量(昵称、问候语等)
|
|
|
- * - 语气调整:根据分群策略调整语气(正式/轻松/温暖�?
|
|
|
+ * - 语气调整:根据分群策略调整语气(正式/轻松/温暖)
|
|
|
* 3. 用户级节点优化:查询该用户是否有已应用的优化内容
|
|
|
* - 如果龙虾引擎针对该用户优化了此节点,使用优化后的内容
|
|
|
* - 优化内容经过审核确认后才应用(根据配置决定是否需要人工确认)
|
|
|
- * 4. 用户偏好学习:记录本次消息发送的偏好信息,用于后续优�?
|
|
|
+ * 4. 用户偏好学习:记录本次消息发送的偏好信息,用于后续优化
|
|
|
* 5. 记录交互数据:将交互数据传给UserNodeOptimizer用于后续分析
|
|
|
*
|
|
|
* @param node 工作流节点,包含消息模板
|
|
|
- * @param variables 变量上下文,包含用户信息和业务变�?
|
|
|
+ * @param variables 变量上下文,包含用户信息和业务变量
|
|
|
* @return 个性化+优化后的消息内容
|
|
|
*/
|
|
|
private String generateNodeMessage(CompanyWorkflowLobsterNode node, Map<String, Object> variables) {
|
|
|
@@ -960,7 +960,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
companyId, userId, node.getNodeCode());
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- logger.warn("[LobsterWorkflow] 个性化定制失败,使用原始消�? companyId={}, nodeCode={}",
|
|
|
+ logger.warn("[LobsterWorkflow] 个性化定制失败,使用原始消息, companyId={}, nodeCode={}",
|
|
|
companyId, node.getNodeCode(), e);
|
|
|
}
|
|
|
|
|
|
@@ -970,14 +970,14 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
companyId, userId, node.getNodeCode());
|
|
|
if (optimizedContent != null && !optimizedContent.isEmpty()) {
|
|
|
message = optimizedContent;
|
|
|
- logger.info("[LobsterWorkflow] 使用用户级优化内�? companyId={}, userId={}, nodeCode={}",
|
|
|
+ logger.info("[LobsterWorkflow] 使用用户级优化内容, companyId={}, userId={}, nodeCode={}",
|
|
|
companyId, userId, node.getNodeCode());
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- logger.debug("[LobsterWorkflow] 查询用户级优化内容失�? {}", e.getMessage());
|
|
|
+ logger.debug("[LobsterWorkflow] 查询用户级优化内容失败, {}", e.getMessage());
|
|
|
}
|
|
|
|
|
|
- /* 第四步:学习用户偏好(异步,不影响主流程�?*/
|
|
|
+ /* 第四步:学习用户偏好(异步,不影响主流程) */
|
|
|
try {
|
|
|
if (variables.containsKey("customerIntent")) {
|
|
|
personalizationEngine.learnUserPreference(
|
|
|
@@ -993,7 +993,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
logger.debug("[LobsterWorkflow] 学习用户偏好失败: {}", e.getMessage());
|
|
|
}
|
|
|
|
|
|
- /* 第五步:记录交互数据到UserNodeOptimizer,用于后续优化分�?*/
|
|
|
+ /* 第五步:记录交互数据到UserNodeOptimizer,用于后续优化分析 */
|
|
|
try {
|
|
|
Map<String, Object> interaction = new HashMap<>();
|
|
|
interaction.put("sentMessage", message);
|
|
|
@@ -1151,8 +1151,8 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * RAG_QUERY 节点:知识库检�?
|
|
|
- * 双路召回:向量语义检�?VectorPatternMatcher) + LLM兜底
|
|
|
+ * RAG_QUERY 节点:知识库检索
|
|
|
+ * 双路召回:向量语义检索(VectorPatternMatcher) + LLM兜底
|
|
|
* nodeConfig: {"knowledgeBase":"course_kb", "topK":3, "threshold":0.7}
|
|
|
*/
|
|
|
private AjaxResult handleRagQuery(Long companyId, LobsterWorkflowInstance instance,
|
|
|
@@ -1183,7 +1183,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
String ragResult;
|
|
|
StringBuilder sources = new StringBuilder();
|
|
|
|
|
|
- /* 第一路:向量语义检索(VectorPatternMatcher�?*/
|
|
|
+ /* 第一路:向量语义检索(VectorPatternMatcher) */
|
|
|
if (vectorPatternMatcher != null) {
|
|
|
try {
|
|
|
List<VectorPatternMatcher.VectorMatchResult> vectorResults =
|
|
|
@@ -1192,7 +1192,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
StringBuilder context = new StringBuilder();
|
|
|
for (int i = 0; i < vectorResults.size(); i++) {
|
|
|
VectorPatternMatcher.VectorMatchResult vr = vectorResults.get(i);
|
|
|
- context.append("【来�?).append(i + 1).append("�?);
|
|
|
+ context.append("【来源").append(i + 1).append("】");
|
|
|
if (vr.getKey() != null) context.append(vr.getKey()).append(": ");
|
|
|
context.append(vr.getText()).append("\n");
|
|
|
if (sources.length() > 0) sources.append(",");
|
|
|
@@ -1205,20 +1205,20 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
ragVars.put("context", context.toString());
|
|
|
String llmPrompt = promptService != null ?
|
|
|
promptService.getContent("rag_vector_llm", ragVars) :
|
|
|
- ("根据以下知识库内容回答问题,如果内容不相关请直接说明:\n\n【问题�? + query + "\n\n【知识库内容】\n" + context + "\n请用简洁的语言回答,引用来源时标注【来源N】�?);
|
|
|
- String llmSystemRole = promptService != null ? promptService.getSystemRole("rag_vector_llm") : "你是企业知识库助手,只基于提供的知识回答问题";
|
|
|
- String ragModel = promptService != null ? promptService.getModelName("rag_vector_llm") : defaultAiModel;
|
|
|
+ ("根据以下知识库内容回答问题,如果内容不相关请直接说明:\n\n【问题】" + query + "\n\n【知识库内容】\n" + context + "\n请用简洁的语言回答,引用来源时标注【来源N】");
|
|
|
+ String llmSystemRole = promptService != null ? promptService.getSystemRole("rag_vector_llm", companyId, null) : "你是企业知识库助手,只基于提供的知识回答问题";
|
|
|
+ String ragModel = promptService != null ? promptService.getModelName("rag_vector_llm", companyId, null) : defaultAiModel;
|
|
|
String llmAnswer = multiModelRouter.generateResponse(llmPrompt, ragModel, llmSystemRole);
|
|
|
|
|
|
ragResult = llmAnswer != null && !llmAnswer.isEmpty() ?
|
|
|
- llmAnswer : "未找到相关知�?;
|
|
|
- logger.info("[RAG] 向量检索命中{}�? 来源: {}", vectorResults.size(), sources.toString());
|
|
|
+ llmAnswer : "未找到相关知识";
|
|
|
+ logger.info("[RAG] 向量检索命中{}条, 来源: {}", vectorResults.size(), sources.toString());
|
|
|
} else {
|
|
|
ragResult = null; // 向量无结果,走LLM兜底
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
ragResult = null;
|
|
|
- logger.warn("[RAG] 向量检索失�? {}", e.getMessage());
|
|
|
+ logger.warn("[RAG] 向量检索失败, {}", e.getMessage());
|
|
|
}
|
|
|
} else {
|
|
|
ragResult = null;
|
|
|
@@ -1231,13 +1231,13 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
fallbackVars.put("query", query);
|
|
|
String fbPrompt = promptService != null ?
|
|
|
promptService.getContent("rag_fallback_llm", fallbackVars) :
|
|
|
- ("请根据以下知识库内容回答问题,如果知识库中没有相关信息请直接说明。\n问题�? + query + "\n知识库:请参考公司内部文档和课程资料(通过语义匹配检索)");
|
|
|
- String fbModel = promptService != null ? promptService.getModelName("rag_fallback_llm") : defaultAiModel;
|
|
|
- String fbRole = promptService != null ? promptService.getSystemRole("rag_fallback_llm") : "你是企业知识库助手,请基于内部知识回答问�?;
|
|
|
+ ("请根据以下知识库内容回答问题,如果知识库中没有相关信息请直接说明。\n问题】" + query + "\n知识库:请参考公司内部文档和课程资料(通过语义匹配检索)");
|
|
|
+ String fbModel = promptService != null ? promptService.getModelName("rag_fallback_llm", companyId, null) : defaultAiModel;
|
|
|
+ String fbRole = promptService != null ? promptService.getSystemRole("rag_fallback_llm", companyId, null) : "你是企业知识库助手,请基于内部知识回答问题";
|
|
|
String modelResult = multiModelRouter.generateResponse(fbPrompt, fbModel, fbRole);
|
|
|
- ragResult = modelResult != null ? modelResult : "知识库暂无相关信�?;
|
|
|
+ ragResult = modelResult != null ? modelResult : "知识库暂无相关信息";
|
|
|
} catch (Exception e) {
|
|
|
- ragResult = "知识库检索失�? " + e.getMessage();
|
|
|
+ ragResult = "知识库检索失败, " + e.getMessage();
|
|
|
logger.warn("RAG_QUERY failed", e);
|
|
|
}
|
|
|
}
|
|
|
@@ -1260,7 +1260,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
if (ragResult.length() > 500)
|
|
|
msg = promptService != null ?
|
|
|
promptService.getContent("kb_result_truncated", kbVars) :
|
|
|
- ("为您查询到相关信息,详情请查�?..\n" + ragResult.substring(0, Math.min(ragResult.length(), 500)) + "...");
|
|
|
+ ("为您查询到相关信息,详情请查看...\n" + ragResult.substring(0, Math.min(ragResult.length(), 500)) + "...");
|
|
|
deliverMessage(companyId, instance.getContactId(), channelType, msg, variables, instance.getId(), instance.getWorkflowId());
|
|
|
|
|
|
logNodeExecution(companyId, instance.getId(), instance.getWorkflowId(), nextIndex, node, ragResult, query, "completed");
|
|
|
@@ -1270,13 +1270,13 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
result.put("nodeName", node.getNodeName());
|
|
|
result.put("ragResult", ragResult);
|
|
|
result.put("sources", sources.toString());
|
|
|
- return AjaxResult.success("RAG检索完�?, result);
|
|
|
+ return AjaxResult.success("RAG检索完成", result);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * CODE_EXEC 节点(type=12):代码执�?
|
|
|
+ * CODE_EXEC 节点(type=12):代码执行
|
|
|
* nodeConfig: {"language":"python|javascript|groovy", "code":"...", "timeout":10000}
|
|
|
- * 安全策略:脚本内可访�?${variables} 通过变量替换注入
|
|
|
+ * 安全策略:脚本内可访问${variables} 通过变量替换注入
|
|
|
*/
|
|
|
private AjaxResult handleCodeExec(Long companyId, LobsterWorkflowInstance instance,
|
|
|
CompanyWorkflowLobsterNode node, List<CompanyWorkflowLobsterNode> nodes,
|
|
|
@@ -1345,7 +1345,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
instance.setUpdateTime(DateUtils.getNowDate());
|
|
|
instanceMapper.updateById(instance);
|
|
|
|
|
|
- String msg = node.getMessageTemplate() != null ? node.getMessageTemplate() : "代码执行完成�? + execResult.substring(0, Math.min(execResult.length(), 200));
|
|
|
+ String msg = node.getMessageTemplate() != null ? node.getMessageTemplate() : "代码执行完成: " + execResult.substring(0, Math.min(execResult.length(), 200));
|
|
|
msg = varEngine.substitute(msg, variables);
|
|
|
deliverMessage(companyId, instance.getContactId(), channelType, msg, variables, instance.getId(), instance.getWorkflowId());
|
|
|
|
|
|
@@ -1359,9 +1359,9 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * LOOP 节点(type=13):循环迭�?
|
|
|
+ * LOOP 节点(type=13):循环迭代
|
|
|
* nodeConfig: {"loopType":"count|foreach|while", "count":3, "listVar":"items", "whileCondition":"loopCount<3", "loopNodeCode":"target_node"}
|
|
|
- * 执行时推进到 loopNodeCode 指定的节点,循环完成后进�?nextNodeCode
|
|
|
+ * 执行时推进到 loopNodeCode 指定的节点,循环完成后进入 nextNodeCode
|
|
|
*/
|
|
|
private AjaxResult handleLoop(Long companyId, LobsterWorkflowInstance instance,
|
|
|
CompanyWorkflowLobsterNode node, List<CompanyWorkflowLobsterNode> nodes,
|
|
|
@@ -1420,14 +1420,14 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
instanceMapper.updateById(instance);
|
|
|
|
|
|
logNodeExecution(companyId, instance.getId(), instance.getWorkflowId(), currentIndex, node,
|
|
|
- "循环�? + loopIndex + "�?, loopNodeCode, "loop_iteration");
|
|
|
+ "循环(" + loopIndex + ")", loopNodeCode, "loop_iteration");
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("instanceId", instance.getId());
|
|
|
result.put("nodeName", node.getNodeName());
|
|
|
result.put("loopIndex", loopIndex);
|
|
|
result.put("nextNodeCode", loopNodeCode);
|
|
|
- return AjaxResult.success("LOOP循环�? + loopIndex + "�?�?" + loopNodeCode, result);
|
|
|
+ return AjaxResult.success("LOOP循环(" + loopIndex + ")-" + loopNodeCode, result);
|
|
|
}
|
|
|
|
|
|
variables.remove(loopKey);
|
|
|
@@ -1439,13 +1439,13 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
instanceMapper.updateById(instance);
|
|
|
|
|
|
logNodeExecution(companyId, instance.getId(), instance.getWorkflowId(), nextIndex, node,
|
|
|
- "循环结束(�? + loopIndex + "�?", null, "completed");
|
|
|
+ "循环结束(" + loopIndex + ")", null, "completed");
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("instanceId", instance.getId());
|
|
|
result.put("nodeName", node.getNodeName());
|
|
|
result.put("totalIterations", loopIndex);
|
|
|
- return AjaxResult.success("LOOP完成(�? + loopIndex + "�?", result);
|
|
|
+ return AjaxResult.success("LOOP完成(" + loopIndex + ")", result);
|
|
|
}
|
|
|
|
|
|
private boolean evaluateWhileCondition(String condition, Map<String, Object> variables) {
|
|
|
@@ -1493,7 +1493,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
return AjaxResult.success("智能API节点执行完成", apiResult);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- logger.warn("[SmartApi] 节点 {} 模式判定失败,降�?SQL: {}", node.getId(), e.getMessage());
|
|
|
+ logger.warn("[SmartApi] 节点 {} 模式判定失败,降级SQL: {}", node.getId(), e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1624,9 +1624,9 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * VARIABLE 节点(type=16):显式变量赋�?
|
|
|
+ * VARIABLE 节点(type=16):显式变量赋值
|
|
|
* nodeConfig: {"assignments":{"var1":"value1","var2":"${lastReply}"}}
|
|
|
- * messageTemplate 可作为条件表达式控制是否赋�?
|
|
|
+ * messageTemplate 可作为条件表达式控制是否赋值
|
|
|
*/
|
|
|
private AjaxResult handleVariable(Long companyId, LobsterWorkflowInstance instance,
|
|
|
CompanyWorkflowLobsterNode node, List<CompanyWorkflowLobsterNode> nodes,
|
|
|
@@ -1663,12 +1663,12 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("instanceId", instance.getId());
|
|
|
result.put("nodeName", node.getNodeName());
|
|
|
- return AjaxResult.success("变量赋值完�?, result);
|
|
|
+ return AjaxResult.success("变量赋值完成", result);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * TAG_OPERATION 节点(type=9):标签操�?
|
|
|
- * nodeConfig: {"addTags":["意向客户","高净�?],"removeTags":["沉睡客户"]}
|
|
|
+ * TAG_OPERATION 节点(type=9):标签操作
|
|
|
+ * nodeConfig: {"addTags":["意向客户","高净值"],"removeTags":["沉睡客户"]}
|
|
|
*/
|
|
|
private AjaxResult handleTagOperation(Long companyId, LobsterWorkflowInstance instance,
|
|
|
CompanyWorkflowLobsterNode node, List<CompanyWorkflowLobsterNode> nodes,
|
|
|
@@ -1713,8 +1713,8 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 动态AI兜底:未知节点类型自动通过LLM推导执行逻辑并生成结�?
|
|
|
- * 这是"即插即用"的关键——任何新节点类型无需改代码即可运�?
|
|
|
+ * 动态AI兜底:未知节点类型自动通过LLM推导执行逻辑并生成结果
|
|
|
+ * 这是"即插即用"的关键——任何新节点类型无需改代码即可运行
|
|
|
*/
|
|
|
private AjaxResult handleUnknownNodeDynamically(Long companyId, LobsterWorkflowInstance instance,
|
|
|
CompanyWorkflowLobsterNode node, List<CompanyWorkflowLobsterNode> nodes,
|
|
|
@@ -1724,17 +1724,17 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
java.util.Map<String, String> dynVars = new java.util.HashMap<>();
|
|
|
dynVars.put("nodeName", node.getNodeName());
|
|
|
dynVars.put("nodeType", String.valueOf(node.getNodeType()));
|
|
|
- dynVars.put("messageTemplate", node.getMessageTemplate() != null ? node.getMessageTemplate() : "�?);
|
|
|
+ dynVars.put("messageTemplate", node.getMessageTemplate() != null ? node.getMessageTemplate() : "");
|
|
|
dynVars.put("config", nodeConfig);
|
|
|
dynVars.put("variables", JSON.toJSONString(variables));
|
|
|
String prompt = promptService != null ?
|
|
|
promptService.getContent("unknown_node_dynamic", companyId, null, dynVars) :
|
|
|
("你是一个CRM工作流引擎。请根据节点配置生成执行结果文本。\n节点名称: " + node.getNodeName() + "\n节点类型: " + node.getNodeType() + "...");
|
|
|
- String dynModel = promptService != null ? promptService.getModelName("unknown_node_dynamic") : defaultAiModel;
|
|
|
+ String dynModel = promptService != null ? promptService.getModelName("unknown_node_dynamic", companyId, null) : defaultAiModel;
|
|
|
|
|
|
String dynamicResult;
|
|
|
try {
|
|
|
- // 节点�?sceneCode/modelName 优先(user 在画布上指定�?admin/shezhi/aiModel 场景�?
|
|
|
+ // 节点取sceneCode/modelName 优先(user 在画布上指定,admin/shezhi/aiModel 场景下)
|
|
|
String nodeScene = node.getSceneCode();
|
|
|
String nodeModel = node.getModelName();
|
|
|
if ((nodeScene != null && !nodeScene.isEmpty()) || (nodeModel != null && !nodeModel.isEmpty())) {
|
|
|
@@ -1745,10 +1745,10 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
dynamicResult = multiModelRouter.generateResponse(prompt, dynModel, null);
|
|
|
}
|
|
|
if (dynamicResult == null || dynamicResult.isEmpty()) {
|
|
|
- dynamicResult = "节点[" + node.getNodeName() + "]已执行完�?type=" + node.getNodeType() + ")";
|
|
|
+ dynamicResult = "节点[" + node.getNodeName() + "]已执行完成(type=" + node.getNodeType() + ")";
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- dynamicResult = "节点[" + node.getNodeName() + "]已执�?类型" + node.getNodeType() + "当前不支持,使用默认逻辑)";
|
|
|
+ dynamicResult = "节点[" + node.getNodeName() + "]已执行(类型" + node.getNodeType() + "当前不支持,使用默认逻辑)";
|
|
|
}
|
|
|
|
|
|
variables.put("dynamicResult", dynamicResult);
|
|
|
@@ -1773,9 +1773,9 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
return AjaxResult.success("动态执行完成[type=" + node.getNodeType() + "]", result);
|
|
|
}
|
|
|
|
|
|
- // ══════════════════════════════════════════�?
|
|
|
- // 工作流模拟执�?
|
|
|
- // ══════════════════════════════════════════�?
|
|
|
+ // ═══════════════════════════════════════════
|
|
|
+ // 工作流模拟执行
|
|
|
+ // ═══════════════════════════════════════════
|
|
|
|
|
|
@Override
|
|
|
public Map<String, Object> simulateExecution(Long companyId, Long workflowId,
|
|
|
@@ -1807,8 +1807,8 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
return report;
|
|
|
}
|
|
|
|
|
|
- // 逐节点模拟推�?
|
|
|
- int maxSteps = nodes.size() * 3; // 最�?倍节点数�?
|
|
|
+ // 逐节点模拟推进
|
|
|
+ int maxSteps = nodes.size() * 3; // 最多3倍节点数防止
|
|
|
String currentNodeCode = nodes.get(0).getNodeCode();
|
|
|
int steps = 0;
|
|
|
|
|
|
@@ -1816,7 +1816,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
steps++;
|
|
|
CompanyWorkflowLobsterNode node = findNodeByCode(nodes, currentNodeCode);
|
|
|
if (node == null) {
|
|
|
- failures.add(Map.of("nodeCode", currentNodeCode, "reason", "节点编码不存�?));
|
|
|
+ failures.add(Map.of("nodeCode", currentNodeCode, "reason", "节点编码不存在"));
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
@@ -1828,7 +1828,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
// 生成模拟客户回复
|
|
|
String mockReply = generateMockReply(nodeType, message, simVars, mockCustomerProfile);
|
|
|
|
|
|
- // 条件判断节点:模拟条件匹�?
|
|
|
+ // 条件判断节点:模拟条件匹配
|
|
|
if (nodeType == 3 && node.getConditionExpr() != null) {
|
|
|
try {
|
|
|
JSONObject cond = JSON.parseObject(node.getConditionExpr());
|
|
|
@@ -1868,7 +1868,7 @@ public class LobsterWorkflowExecutorImpl implements LobsterWorkflowExecutor {
|
|
|
}
|
|
|
|
|
|
if (steps >= maxSteps) {
|
|
|
- failures.add(Map.of("nodeCode", currentNodeCode != null ? currentNodeCode : "END", "reason", "超过最大模拟步数,疑似死循�?));
|
|
|
+ failures.add(Map.of("nodeCode", currentNodeCode != null ? currentNodeCode : "END", "reason", "超过最大模拟步数,疑似死循环"));
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
logger.warn("模拟执行失败: {}", e.getMessage());
|