boss преди 2 седмици
родител
ревизия
d8ac0a9df4

+ 1 - 1
fs-service/src/main/java/com/fs/company/domain/AdminAiScene.java

@@ -18,7 +18,7 @@ public class AdminAiScene {
     /** 场景名称 */
     private String sceneName;
 
-    /** 场景类型: single单模型 / multi_pipeline多模型流水线 */
+    /** 场景类型(已废弃-系统自动根据模型数量决定单/多模型): single单模型 / multi_pipeline多模型流水线 */
     private String sceneType;
 
     /** 流水线类型: sequential顺序调用 / scoring质量评分链 */

+ 130 - 58
fs-service/src/main/java/com/fs/company/service/ai/AiSceneDispatcher.java

@@ -15,11 +15,13 @@ import java.util.stream.Collectors;
 /**
  * AI模型场景分发器(统一入口)
  * <p>
- * 替代 MultiModelRouter 的核心路由角色。
+ * 自动根据场景下配置的模型数量决定调用策略:
  * <ul>
- *   <li>单模型场景(single):按排序取优先级最高的可用模型,直接调用</li>
- *   <li>多模型流水线(multi_pipeline):委托给 MultiModelPipelineEngine</li>
+ *   <li>0个模型 → 降级兜底,取全局第一个可用模型</li>
+ *   <li>1个模型 → 直接单模型调用</li>
+ *   <li>≥2个模型 → 自动走多模型流水线(sequential/scoring)</li>
  * </ul>
+ * 不再需要手动指定 sceneType,系统自动检测。
  */
 @Service
 public class AiSceneDispatcher {
@@ -39,7 +41,12 @@ public class AiSceneDispatcher {
     private MultiModelPipelineEngine pipelineEngine;
 
     /**
-     * 场景化模型调用(单模型场景入口)
+     * 场景化模型调用(自动检测单/多模型)
+     * <ul>
+     *   <li>0个模型 → 降级兜底 → 单模型</li>
+     *   <li>1个模型 → 直接单模型调用</li>
+     *   <li>≥2个模型 → 自动走流水线(sequential/scoring)</li>
+     * </ul>
      *
      * @param prompt       用户提示词
      * @param sceneCode    场景编码
@@ -48,24 +55,28 @@ public class AiSceneDispatcher {
      */
     public String dispatch(String prompt, String sceneCode, String systemPrompt) {
         try {
-            AdminAiModel model = pickBestModel(sceneCode);
-            if (model == null) {
+            List<AdminAiModel> models = sceneService.getEnabledModels(sceneCode);
+
+            // 0个模型 → 降级兜底
+            if (models.isEmpty()) {
                 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) {
             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) {
         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) {
             log.error("[AiSceneDispatcher] 场景 {} dispatchWithTokens失败: {}", sceneCode, e.getMessage());
             return new ModelResponse("", null, null, "error");
@@ -91,18 +110,20 @@ public class AiSceneDispatcher {
     }
 
     /**
-     * 多模型流水线入口
+     * 多模型流水线入口(显式调用)
+     * <p>自动根据模型数量决定是否启用流水线,
+     * 不再检查 sceneType 字段。
      *
-     * @param sceneCode 多模型场景编码
+     * @param sceneCode 场景编码
      * @param params    场景参数(由调用方按需传入)
      * @return 场景执行结果容器
      */
     public Map<String, Object> executePipeline(String sceneCode, Map<String, Object> params) {
         Map<String, Object> result = new HashMap<>();
         AdminAiScene scene = sceneService.getScene(sceneCode);
-        if (scene == null || !"multi_pipeline".equals(scene.getSceneType())) {
+        if (scene == null) {
             result.put("success", false);
-            result.put("error", "场景不存在或非多模型场景: " + sceneCode);
+            result.put("error", "场景不存在: " + sceneCode);
             return result;
         }
 
@@ -113,23 +134,21 @@ public class AiSceneDispatcher {
             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");
             MultiModelPipelineEngine.ScoringPromptBuilder scoringBuilder =
                 (MultiModelPipelineEngine.ScoringPromptBuilder) params.get("scoringBuilder");
@@ -148,9 +167,21 @@ public class AiSceneDispatcher {
             result.put("scoringEnabled", pipeResult.isScoringEnabled());
             result.put("feedback", pipeResult.getFeedback());
             result.put("stages", pipeResult.getStages());
+
         } 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;
@@ -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排序,取第一个
     }
 
     /**