MethodRouterService.java
3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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<ToolMeta> 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<ToolMeta> 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;
}
}