package com.xly.service; import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson2.JSON; import com.xly.agent.SecMethodAiAgent; import com.xly.agent.SystemPromptGenerator; import com.xly.entity.MethodChoiceResult; import com.xly.entity.ToolMeta; import com.xly.entity.UserSceneSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Slf4j @Service @RequiredArgsConstructor public class MethodRouterService { private final SecMethodAiAgent aiAgent; /** confidence 低于此值视为未命中,走澄清兜底 */ private static final double CONFIDENCE_THRESHOLD = 0.5; /** * 根据用户输入路由到具体方法,命中则写入 session.currentTool 并返回该方法。 * * @return 命中的 ToolMeta;未命中返回 null(由上层决定澄清或走通用 chat) */ public ToolMeta route(UserSceneSession session, String userInput, List toolList, String sKnowledgeBase) { if (ObjectUtil.isEmpty(toolList)) { log.warn("方法路由:可选方法列表为空, userId={}", session.getUserId()); return null; } // ① 生成方法选择 prompt String sSystemPrompt = SystemPromptGenerator .generateChoiceMethodPrompt(toolList, sKnowledgeBase); // ② 调大模型 String raw = aiAgent.choiceMethod(session.getUserId(), userInput, sSystemPrompt).content(); log.debug("方法路由原始输出: {}", raw); // ③ 解析(含 ```json 容错) MethodChoiceResult result = parse(raw); if (result == null || ObjectUtil.isEmpty(result.getSMethodNo())) { return null; } String methodNo = result.getSMethodNo(); Double confidence = result.getConfidence(); // ④ none / 阈值 / 白名单三重校验 if ("none".equalsIgnoreCase(methodNo)) { log.info("方法路由:无匹配方法, input={}", userInput); return null; } if (confidence == null || confidence < CONFIDENCE_THRESHOLD) { log.info("方法路由:置信度不足({}), input={}", confidence, userInput); return null; } Optional hit = toolList.stream() .filter(t -> methodNo.equals(t.getSMethodNo())) .findFirst(); if (hit.isEmpty()) { log.warn("方法路由:模型返回了不存在的编号 {}", methodNo); // 防编造 return null; } // ⑤ 命中,写入当前工具,交给下游参数提取 ToolMeta chosen = hit.get(); session.setCurrentTool(chosen); log.info("方法路由命中: {} - {}, confidence={}", chosen.getSMethodNo(), chosen.getSMethodName(), confidence); return chosen; } /** 解析模型输出,兼容被 ```json ... ``` 包裹或前后带杂字符的情况 */ private MethodChoiceResult parse(String raw) { if (ObjectUtil.isEmpty(raw)) return null; try { String json = stripCodeFence(raw); return JSON.parseObject(json, MethodChoiceResult.class); } catch (Exception e) { log.error("方法路由结果解析失败, raw={}", raw, e); return null; } } /** 去掉 markdown 代码块,并截取第一个 { 到最后一个 } */ private String stripCodeFence(String raw) { String s = raw.trim() .replaceAll("(?s)```json", "") .replaceAll("(?s)```", "") .trim(); int start = s.indexOf('{'); int end = s.lastIndexOf('}'); return (start >= 0 && end > start) ? s.substring(start, end + 1) : s; } }