|
@@ -15,11 +15,13 @@ import java.util.stream.Collectors;
|
|
|
/**
|
|
/**
|
|
|
* AI模型场景分发器(统一入口)
|
|
* AI模型场景分发器(统一入口)
|
|
|
* <p>
|
|
* <p>
|
|
|
- * 替代 MultiModelRouter 的核心路由角色。
|
|
|
|
|
|
|
+ * 自动根据场景下配置的模型数量决定调用策略:
|
|
|
* <ul>
|
|
* <ul>
|
|
|
- * <li>单模型场景(single):按排序取优先级最高的可用模型,直接调用</li>
|
|
|
|
|
- * <li>多模型流水线(multi_pipeline):委托给 MultiModelPipelineEngine</li>
|
|
|
|
|
|
|
+ * <li>0个模型 → 降级兜底,取全局第一个可用模型</li>
|
|
|
|
|
+ * <li>1个模型 → 直接单模型调用</li>
|
|
|
|
|
+ * <li>≥2个模型 → 自动走多模型流水线(sequential/scoring)</li>
|
|
|
* </ul>
|
|
* </ul>
|
|
|
|
|
+ * 不再需要手动指定 sceneType,系统自动检测。
|
|
|
*/
|
|
*/
|
|
|
@Service
|
|
@Service
|
|
|
public class AiSceneDispatcher {
|
|
public class AiSceneDispatcher {
|
|
@@ -39,7 +41,12 @@ public class AiSceneDispatcher {
|
|
|
private MultiModelPipelineEngine pipelineEngine;
|
|
private MultiModelPipelineEngine pipelineEngine;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 场景化模型调用(单模型场景入口)
|
|
|
|
|
|
|
+ * 场景化模型调用(自动检测单/多模型)
|
|
|
|
|
+ * <ul>
|
|
|
|
|
+ * <li>0个模型 → 降级兜底 → 单模型</li>
|
|
|
|
|
+ * <li>1个模型 → 直接单模型调用</li>
|
|
|
|
|
+ * <li>≥2个模型 → 自动走流水线(sequential/scoring)</li>
|
|
|
|
|
+ * </ul>
|
|
|
*
|
|
*
|
|
|
* @param prompt 用户提示词
|
|
* @param prompt 用户提示词
|
|
|
* @param sceneCode 场景编码
|
|
* @param sceneCode 场景编码
|
|
@@ -48,24 +55,28 @@ public class AiSceneDispatcher {
|
|
|
*/
|
|
*/
|
|
|
public String dispatch(String prompt, String sceneCode, String systemPrompt) {
|
|
public String dispatch(String prompt, String sceneCode, String systemPrompt) {
|
|
|
try {
|
|
try {
|
|
|
- AdminAiModel model = pickBestModel(sceneCode);
|
|
|
|
|
- if (model == null) {
|
|
|
|
|
|
|
+ List<AdminAiModel> models = sceneService.getEnabledModels(sceneCode);
|
|
|
|
|
+
|
|
|
|
|
+ // 0个模型 → 降级兜底
|
|
|
|
|
+ if (models.isEmpty()) {
|
|
|
log.warn("[AiSceneDispatcher] 场景 {} 无可用模型,尝试降级", sceneCode);
|
|
log.warn("[AiSceneDispatcher] 场景 {} 无可用模型,尝试降级", sceneCode);
|
|
|
- model = pickFallbackModel();
|
|
|
|
|
|
|
+ AdminAiModel fallback = pickFallbackModel();
|
|
|
|
|
+ if (fallback == null) {
|
|
|
|
|
+ log.error("[AiSceneDispatcher] 无任何可用模型");
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ return callSingleModel(prompt, systemPrompt, fallback, sceneCode);
|
|
|
}
|
|
}
|
|
|
- if (model == null) {
|
|
|
|
|
- log.error("[AiSceneDispatcher] 无任何可用模型");
|
|
|
|
|
- return "";
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 1个模型 → 直接单模型调用
|
|
|
|
|
+ if (models.size() == 1) {
|
|
|
|
|
+ return callSingleModel(prompt, systemPrompt, models.get(0), sceneCode);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- AiProviderManager.ProviderConfig cfg = modelService.toProviderConfig(model);
|
|
|
|
|
- ModelResponse resp = aiModelGateway.chatWithConfig(prompt, systemPrompt, cfg);
|
|
|
|
|
- String content = resp != null ? resp.getContent() : "";
|
|
|
|
|
- log.debug("[AiSceneDispatcher] 场景 {} → 模型 {} | tokens: in={} out={}",
|
|
|
|
|
- sceneCode, model.getModelName(),
|
|
|
|
|
- resp != null ? resp.getPromptTokens() : 0,
|
|
|
|
|
- resp != null ? resp.getCompletionTokens() : 0);
|
|
|
|
|
- return content;
|
|
|
|
|
|
|
+ // ≥2个模型 → 自动走多模型流水线
|
|
|
|
|
+ log.info("[AiSceneDispatcher] 场景 {} 有{}个模型,自动走流水线", sceneCode, models.size());
|
|
|
|
|
+ AdminAiScene scene = sceneService.getScene(sceneCode);
|
|
|
|
|
+ return dispatchAsPipeline(prompt, systemPrompt, models, scene, sceneCode);
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("[AiSceneDispatcher] 场景 {} 调用失败: {}", sceneCode, e.getMessage());
|
|
log.error("[AiSceneDispatcher] 场景 {} 调用失败: {}", sceneCode, e.getMessage());
|
|
@@ -74,16 +85,24 @@ public class AiSceneDispatcher {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 场景化调用(返回详细信息含Token用量)
|
|
|
|
|
|
|
+ * 场景化调用(返回详细信息含Token用量,自动检测单/多模型)
|
|
|
|
|
+ * <p>多模型场景同样走流水线,返回最终阶段的内容和Token汇总。
|
|
|
*/
|
|
*/
|
|
|
public ModelResponse dispatchWithTokens(String prompt, String sceneCode, String systemPrompt) {
|
|
public ModelResponse dispatchWithTokens(String prompt, String sceneCode, String systemPrompt) {
|
|
|
try {
|
|
try {
|
|
|
- AdminAiModel model = pickBestModel(sceneCode);
|
|
|
|
|
- if (model == null) model = pickFallbackModel();
|
|
|
|
|
- if (model == null) return new ModelResponse("", null, null, "none");
|
|
|
|
|
-
|
|
|
|
|
- AiProviderManager.ProviderConfig cfg = modelService.toProviderConfig(model);
|
|
|
|
|
- return aiModelGateway.chatWithConfig(prompt, systemPrompt, cfg);
|
|
|
|
|
|
|
+ List<AdminAiModel> models = sceneService.getEnabledModels(sceneCode);
|
|
|
|
|
+ if (models.isEmpty()) {
|
|
|
|
|
+ AdminAiModel fallback = pickFallbackModel();
|
|
|
|
|
+ if (fallback == null) return new ModelResponse("", null, null, "none");
|
|
|
|
|
+ return callSingleModelWithTokens(prompt, systemPrompt, fallback);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (models.size() == 1) {
|
|
|
|
|
+ return callSingleModelWithTokens(prompt, systemPrompt, models.get(0));
|
|
|
|
|
+ }
|
|
|
|
|
+ // 多模型走流水线
|
|
|
|
|
+ AdminAiScene scene = sceneService.getScene(sceneCode);
|
|
|
|
|
+ String content = dispatchAsPipeline(prompt, systemPrompt, models, scene, sceneCode);
|
|
|
|
|
+ return new ModelResponse(content, null, null, models.get(models.size() - 1).getModelName());
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("[AiSceneDispatcher] 场景 {} dispatchWithTokens失败: {}", sceneCode, e.getMessage());
|
|
log.error("[AiSceneDispatcher] 场景 {} dispatchWithTokens失败: {}", sceneCode, e.getMessage());
|
|
|
return new ModelResponse("", null, null, "error");
|
|
return new ModelResponse("", null, null, "error");
|
|
@@ -91,18 +110,20 @@ public class AiSceneDispatcher {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 多模型流水线入口
|
|
|
|
|
|
|
+ * 多模型流水线入口(显式调用)
|
|
|
|
|
+ * <p>自动根据模型数量决定是否启用流水线,
|
|
|
|
|
+ * 不再检查 sceneType 字段。
|
|
|
*
|
|
*
|
|
|
- * @param sceneCode 多模型场景编码
|
|
|
|
|
|
|
+ * @param sceneCode 场景编码
|
|
|
* @param params 场景参数(由调用方按需传入)
|
|
* @param params 场景参数(由调用方按需传入)
|
|
|
* @return 场景执行结果容器
|
|
* @return 场景执行结果容器
|
|
|
*/
|
|
*/
|
|
|
public Map<String, Object> executePipeline(String sceneCode, Map<String, Object> params) {
|
|
public Map<String, Object> executePipeline(String sceneCode, Map<String, Object> params) {
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
AdminAiScene scene = sceneService.getScene(sceneCode);
|
|
AdminAiScene scene = sceneService.getScene(sceneCode);
|
|
|
- if (scene == null || !"multi_pipeline".equals(scene.getSceneType())) {
|
|
|
|
|
|
|
+ if (scene == null) {
|
|
|
result.put("success", false);
|
|
result.put("success", false);
|
|
|
- result.put("error", "场景不存在或非多模型场景: " + sceneCode);
|
|
|
|
|
|
|
+ result.put("error", "场景不存在: " + sceneCode);
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -113,23 +134,21 @@ public class AiSceneDispatcher {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ("sequential".equals(scene.getPipelineType())) {
|
|
|
|
|
- // ── 顺序调用流水线 ──
|
|
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
|
|
- List<String> prompts = (List<String>) params.getOrDefault("prompts", Collections.emptyList());
|
|
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
|
|
- List<String> systemPrompts = (List<String>) params.getOrDefault("systemPrompts", Collections.emptyList());
|
|
|
|
|
- String fallback = (String) params.getOrDefault("fallback", "{}");
|
|
|
|
|
-
|
|
|
|
|
- MultiModelPipelineEngine.SequentialPipelineResult pipeResult =
|
|
|
|
|
- pipelineEngine.executeSequential(models, prompts, systemPrompts, fallback);
|
|
|
|
|
-
|
|
|
|
|
- result.put("success", pipeResult.isSuccess());
|
|
|
|
|
- result.put("finalOutput", pipeResult.getFinalOutput());
|
|
|
|
|
- result.put("stages", pipeResult.getStages());
|
|
|
|
|
|
|
+ // 单模型 → 降级为直接调用
|
|
|
|
|
+ if (models.size() == 1) {
|
|
|
|
|
+ AdminAiModel model = models.get(0);
|
|
|
|
|
+ String prompt = (String) params.getOrDefault("prompt", "");
|
|
|
|
|
+ String systemPrompt = (String) params.get("systemPrompt");
|
|
|
|
|
+ String content = callSingleModel(prompt, systemPrompt, model, sceneCode);
|
|
|
|
|
+ result.put("success", !content.isEmpty());
|
|
|
|
|
+ result.put("finalOutput", content);
|
|
|
|
|
+ result.put("modelName", model.getModelName());
|
|
|
|
|
+ result.put("singleModel", true);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- } else if ("scoring".equals(scene.getPipelineType())) {
|
|
|
|
|
- // ── 质量评分流水线 ──
|
|
|
|
|
|
|
+ // ≥2个模型 → 走流水线
|
|
|
|
|
+ if ("scoring".equals(scene.getPipelineType())) {
|
|
|
String generatePrompt = (String) params.get("generatePrompt");
|
|
String generatePrompt = (String) params.get("generatePrompt");
|
|
|
MultiModelPipelineEngine.ScoringPromptBuilder scoringBuilder =
|
|
MultiModelPipelineEngine.ScoringPromptBuilder scoringBuilder =
|
|
|
(MultiModelPipelineEngine.ScoringPromptBuilder) params.get("scoringBuilder");
|
|
(MultiModelPipelineEngine.ScoringPromptBuilder) params.get("scoringBuilder");
|
|
@@ -148,9 +167,21 @@ public class AiSceneDispatcher {
|
|
|
result.put("scoringEnabled", pipeResult.isScoringEnabled());
|
|
result.put("scoringEnabled", pipeResult.isScoringEnabled());
|
|
|
result.put("feedback", pipeResult.getFeedback());
|
|
result.put("feedback", pipeResult.getFeedback());
|
|
|
result.put("stages", pipeResult.getStages());
|
|
result.put("stages", pipeResult.getStages());
|
|
|
|
|
+
|
|
|
} else {
|
|
} else {
|
|
|
- result.put("success", false);
|
|
|
|
|
- result.put("error", "未知流水线类型: " + scene.getPipelineType());
|
|
|
|
|
|
|
+ // sequential 顺序调用
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ List<String> prompts = (List<String>) params.getOrDefault("prompts", Collections.emptyList());
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ List<String> systemPrompts = (List<String>) params.getOrDefault("systemPrompts", Collections.emptyList());
|
|
|
|
|
+ String fallback = (String) params.getOrDefault("fallback", "{}");
|
|
|
|
|
+
|
|
|
|
|
+ MultiModelPipelineEngine.SequentialPipelineResult pipeResult =
|
|
|
|
|
+ pipelineEngine.executeSequential(models, prompts, systemPrompts, fallback);
|
|
|
|
|
+
|
|
|
|
|
+ result.put("success", pipeResult.isSuccess());
|
|
|
|
|
+ result.put("finalOutput", pipeResult.getFinalOutput());
|
|
|
|
|
+ result.put("stages", pipeResult.getStages());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
@@ -160,21 +191,62 @@ public class AiSceneDispatcher {
|
|
|
// 私有方法
|
|
// 私有方法
|
|
|
// ═══════════════════════════════════════════
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
+ /** 单模型调用(返回字符串内容) */
|
|
|
|
|
+ private String callSingleModel(String prompt, String systemPrompt, AdminAiModel model, String sceneCode) {
|
|
|
|
|
+ AiProviderManager.ProviderConfig cfg = modelService.toProviderConfig(model);
|
|
|
|
|
+ ModelResponse resp = aiModelGateway.chatWithConfig(prompt, systemPrompt, cfg);
|
|
|
|
|
+ String content = resp != null ? resp.getContent() : "";
|
|
|
|
|
+ log.debug("[AiSceneDispatcher] 场景 {} → 单模型 {} | tokens: in={} out={}",
|
|
|
|
|
+ sceneCode, model.getModelName(),
|
|
|
|
|
+ resp != null ? resp.getPromptTokens() : 0,
|
|
|
|
|
+ resp != null ? resp.getCompletionTokens() : 0);
|
|
|
|
|
+ return content;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 单模型调用(返回ModelResponse含Token) */
|
|
|
|
|
+ private ModelResponse callSingleModelWithTokens(String prompt, String systemPrompt, AdminAiModel model) {
|
|
|
|
|
+ AiProviderManager.ProviderConfig cfg = modelService.toProviderConfig(model);
|
|
|
|
|
+ return aiModelGateway.chatWithConfig(prompt, systemPrompt, cfg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * 从场景中挑选最佳模型(优先级最高且启用)
|
|
|
|
|
|
|
+ * 多模型流水线自动分发
|
|
|
|
|
+ * <p>根据场景的 pipelineType 自动选择 sequential 或 scoring 流水线。
|
|
|
*/
|
|
*/
|
|
|
- private AdminAiModel pickBestModel(String sceneCode) {
|
|
|
|
|
- List<AdminAiModel> models = sceneService.getEnabledModels(sceneCode);
|
|
|
|
|
- if (models.isEmpty()) {
|
|
|
|
|
- // 场景未绑定模型,尝试全局默认
|
|
|
|
|
- List<AdminAiModel> allEnabled = modelService.listEnabled();
|
|
|
|
|
- if (!allEnabled.isEmpty()) {
|
|
|
|
|
- log.info("[AiSceneDispatcher] 场景 {} 未绑定模型,使用全局第一个可用模型", sceneCode);
|
|
|
|
|
- return allEnabled.get(0);
|
|
|
|
|
|
|
+ private String dispatchAsPipeline(String prompt, String systemPrompt,
|
|
|
|
|
+ List<AdminAiModel> models, AdminAiScene scene, String sceneCode) {
|
|
|
|
|
+ if (scene != null && "scoring".equals(scene.getPipelineType())) {
|
|
|
|
|
+ // 质量评分流水线
|
|
|
|
|
+ String fallback = "";
|
|
|
|
|
+ int threshold = scene.getQualityThreshold() != null ? scene.getQualityThreshold() : 120;
|
|
|
|
|
+ MultiModelPipelineEngine.ScoringPipelineResult result =
|
|
|
|
|
+ pipelineEngine.executeScoring(models, prompt, null, null, threshold, fallback);
|
|
|
|
|
+ log.debug("[AiSceneDispatcher] 场景 {} scoring完成 | success={} score={}",
|
|
|
|
|
+ sceneCode, result.isSuccess(), result.getFinalScore());
|
|
|
|
|
+ return result.getFinalContent();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 顺序调用流水线(默认)
|
|
|
|
|
+ List<String> prompts = new ArrayList<>();
|
|
|
|
|
+ for (int i = 0; i < models.size(); i++) {
|
|
|
|
|
+ if (i == 0) {
|
|
|
|
|
+ prompts.add(prompt);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 后续阶段自动拼接:上一阶段输出 + 完善指令
|
|
|
|
|
+ prompts.add(null); // null → engine 会用 "请完善以上内容" 自动拼接
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- return null;
|
|
|
|
|
|
|
+ List<String> systemPrompts = new ArrayList<>();
|
|
|
|
|
+ systemPrompts.add(systemPrompt);
|
|
|
|
|
+ for (int i = 1; i < models.size(); i++) {
|
|
|
|
|
+ systemPrompts.add(null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MultiModelPipelineEngine.SequentialPipelineResult result =
|
|
|
|
|
+ pipelineEngine.executeSequential(models, prompts, systemPrompts, "");
|
|
|
|
|
+ log.debug("[AiSceneDispatcher] 场景 {} sequential完成 | success={} stages={}",
|
|
|
|
|
+ sceneCode, result.isSuccess(), result.getStages().size());
|
|
|
|
|
+ return result.getFinalOutput();
|
|
|
}
|
|
}
|
|
|
- return models.get(0); // 已按pipeline_order和sort_weight排序,取第一个
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|