mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(skill): resolve tool deps by runtime function name, not class/bean name
This commit is contained in:
parent
52a9a785c1
commit
4c861006dc
@ -5,8 +5,10 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import vip.mate.skill.runtime.SkillFrontmatterParser.SkillDependencies;
|
||||
import vip.mate.tool.ToolRegistry;
|
||||
import vip.mate.tool.model.ToolEntity;
|
||||
import vip.mate.tool.repository.ToolMapper;
|
||||
|
||||
@ -15,6 +17,7 @@ import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -23,10 +26,20 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SkillDependencyChecker {
|
||||
|
||||
private final ToolMapper toolMapper;
|
||||
private final ToolRegistry toolRegistry;
|
||||
|
||||
/**
|
||||
* {@code @Lazy} on {@link ToolRegistry}: this bean is constructed during startup,
|
||||
* and ToolRegistry transitively depends on MCP / plugin infrastructure that also
|
||||
* runs early — the lazy proxy breaks that cycle.
|
||||
*/
|
||||
public SkillDependencyChecker(ToolMapper toolMapper, @Lazy ToolRegistry toolRegistry) {
|
||||
this.toolMapper = toolMapper;
|
||||
this.toolRegistry = toolRegistry;
|
||||
}
|
||||
|
||||
private static final String CURRENT_OS = detectOS();
|
||||
|
||||
@ -75,9 +88,13 @@ public class SkillDependencyChecker {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 内部工具检查
|
||||
// 4. 内部工具检查 — fetch the runtime function-name set once per skill
|
||||
// so we don't hit reflection N times when a skill lists many tools.
|
||||
Set<String> runtimeFunctionNames = dependencies.getTools().isEmpty()
|
||||
? Set.of()
|
||||
: fetchRuntimeFunctionNames();
|
||||
for (String toolName : dependencies.getTools()) {
|
||||
if (!isToolAvailable(toolName)) {
|
||||
if (!isToolAvailable(toolName, runtimeFunctionNames)) {
|
||||
missing.add("tool:" + toolName);
|
||||
allSatisfied = false;
|
||||
}
|
||||
@ -118,18 +135,25 @@ public class SkillDependencyChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isToolAvailable(String toolName) {
|
||||
/**
|
||||
* The runtime registry (ToolRegistry) is authoritative: it knows the exact
|
||||
* function names LLMs and skills call by ({@code @Tool} method name / MCP
|
||||
* tool id / plugin tool name). The {@code mate_tool} DB overlay stores
|
||||
* class names + bean names and does NOT match that vocabulary, so checking
|
||||
* the DB alone mis-reports every real skill dependency as "missing".
|
||||
*
|
||||
* <p>We keep the DB lookup as a secondary fallback for the edge case where
|
||||
* someone has inserted a custom row whose {@code name} happens to equal the
|
||||
* function name.
|
||||
*/
|
||||
private boolean isToolAvailable(String toolName, Set<String> runtimeFunctionNames) {
|
||||
if (runtimeFunctionNames.contains(toolName)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
// 先按 name 精确匹配
|
||||
Long count = toolMapper.selectCount(new LambdaQueryWrapper<ToolEntity>()
|
||||
.eq(ToolEntity::getName, toolName)
|
||||
.eq(ToolEntity::getEnabled, true));
|
||||
if (count > 0) return true;
|
||||
|
||||
// 再按 beanName 匹配(兼容 Spring Bean 名称)
|
||||
count = toolMapper.selectCount(new LambdaQueryWrapper<ToolEntity>()
|
||||
.eq(ToolEntity::getBeanName, toolName)
|
||||
.eq(ToolEntity::getEnabled, true));
|
||||
return count > 0;
|
||||
} catch (Exception e) {
|
||||
log.debug("Tool check failed for '{}': {}", toolName, e.getMessage());
|
||||
@ -137,6 +161,16 @@ public class SkillDependencyChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> fetchRuntimeFunctionNames() {
|
||||
try {
|
||||
return toolRegistry.availableFunctionNames();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to fetch runtime tool function names, falling back to DB check only: {}",
|
||||
e.getMessage());
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
return CURRENT_OS.equals("windows");
|
||||
}
|
||||
|
||||
@ -167,6 +167,24 @@ public class ToolRegistry {
|
||||
return AgentToolSet.fromCallbacks(toolBeans, localizedCallbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of runtime tool function names — i.e. the exact identifiers
|
||||
* LLMs and skill manifests use to reference tools (the @Tool method name, the
|
||||
* MCP tool name, or the registered plugin tool name).
|
||||
*
|
||||
* <p>Use this for dependency / availability checks instead of querying the
|
||||
* {@code mate_tool} table directly: the DB overlay stores class/bean names,
|
||||
* which do not match the function-name vocabulary skills declare against.
|
||||
*/
|
||||
public Set<String> availableFunctionNames() {
|
||||
Set<String> names = new java.util.HashSet<>();
|
||||
AgentToolSet toolSet = getEnabledToolSet();
|
||||
for (ToolCallback cb : toolSet.callbacks()) {
|
||||
names.add(cb.getToolDefinition().name());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库中的工具配置列表(全部)
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user