mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +08:00
fix(agent): resolve agent tool bindings by class/bean/function name aliases (#24)
Issue #24: tools selected in the agent binding UI had no effect at runtime. mate_tool.name stores the Java class name (e.g. "BrowserUseTool") and was written into mate_agent_tool.tool_name, but AgentToolSet.withAllowedToolsOnly matched by the @Tool function name (e.g. "browser_use") — so every binding was silently filtered out. Fix: AgentToolSet builds an alias index per ToolCallback indexed by every equivalent identifier — function name, Spring bean name, and Java class simple name. withAllowedToolsOnly / withDeniedToolsFiltered / excluding all accept any of these aliases, mirroring how Spring's BeanFactory accepts bean names + aliases. ToolRegistry.getEnabledToolSet now threads a bean→beanName resolver into the new AgentToolSet.fromCallbacks(...) overload. Existing two-arg callers keep working; tests pass without changes. Zero data migration: stale mate_agent_tool rows that previously had no effect now resolve correctly via the class-name alias.
This commit is contained in:
parent
e64752a830
commit
5b24a599ca
@ -5,8 +5,8 @@ import org.springframework.ai.tool.ToolCallback;
|
|||||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent 统一工具集合
|
* Agent 统一工具集合
|
||||||
@ -14,6 +14,20 @@ import java.util.LinkedHashMap;
|
|||||||
* 将 @Tool Bean、ToolCallbackProvider、MCP server 暴露的 tool callbacks
|
* 将 @Tool Bean、ToolCallbackProvider、MCP server 暴露的 tool callbacks
|
||||||
* 统一收集为一致的 ToolCallback 列表,供 StateGraph 节点使用。
|
* 统一收集为一致的 ToolCallback 列表,供 StateGraph 节点使用。
|
||||||
*
|
*
|
||||||
|
* <h3>Alias index — why one tool has multiple names</h3>
|
||||||
|
* Each tool can be referenced by several equivalent identifiers:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code @Tool} function name (the runtime truth: {@code cb.getToolDefinition().name()},
|
||||||
|
* e.g. {@code browser_use})</li>
|
||||||
|
* <li>Spring bean name (e.g. {@code browserUseTool})</li>
|
||||||
|
* <li>Java class simple name (e.g. {@code BrowserUseTool} — what the seed data and
|
||||||
|
* legacy {@code mate_agent_tool.tool_name} bindings happen to store)</li>
|
||||||
|
* </ul>
|
||||||
|
* Filtering operations ({@link #withAllowedToolsOnly}, {@link #withDeniedToolsFiltered},
|
||||||
|
* {@link #excluding}) accept any of these aliases, so callers don't need to know which
|
||||||
|
* naming convention the persistence layer happens to use. This is the same pattern Spring's
|
||||||
|
* {@code BeanFactory} uses for bean names + aliases.
|
||||||
|
*
|
||||||
* @author MateClaw Team
|
* @author MateClaw Team
|
||||||
*/
|
*/
|
||||||
public class AgentToolSet {
|
public class AgentToolSet {
|
||||||
@ -21,26 +35,69 @@ public class AgentToolSet {
|
|||||||
private final List<Object> toolBeans;
|
private final List<Object> toolBeans;
|
||||||
private final List<ToolCallback> callbacks;
|
private final List<ToolCallback> callbacks;
|
||||||
private final Map<String, ToolCallback> callbackByName;
|
private final Map<String, ToolCallback> callbackByName;
|
||||||
|
/**
|
||||||
|
* Alias → callbacks. One alias may resolve to multiple callbacks
|
||||||
|
* (e.g. a Spring bean name pointing at a class that exposes several {@code @Tool} methods),
|
||||||
|
* which is why values are sets.
|
||||||
|
*/
|
||||||
|
private final Map<String, Set<ToolCallback>> aliasIndex;
|
||||||
|
|
||||||
private AgentToolSet(List<Object> toolBeans, List<ToolCallback> callbacks) {
|
private AgentToolSet(List<Object> toolBeans, List<ToolCallback> callbacks,
|
||||||
|
Function<Object, String> beanNameResolver) {
|
||||||
this.toolBeans = List.copyOf(toolBeans);
|
this.toolBeans = List.copyOf(toolBeans);
|
||||||
// 按工具名去重:内置工具在前(先添加),MCP 工具在后,同名时保留内置工具
|
// 按工具名去重:内置工具在前(先添加),MCP 工具在后,同名时保留内置工具
|
||||||
// 使用 LinkedHashMap 保证插入顺序,确保内置工具始终排在 MCP 工具前面(影响 LLM 工具选择倾向)
|
// 使用 LinkedHashMap 保证插入顺序,确保内置工具始终排在 MCP 工具前面(影响 LLM 工具选择倾向)
|
||||||
this.callbackByName = callbacks.stream()
|
LinkedHashMap<String, ToolCallback> byName = callbacks.stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
cb -> cb.getToolDefinition().name(),
|
cb -> cb.getToolDefinition().name(),
|
||||||
cb -> cb,
|
cb -> cb,
|
||||||
(a, b) -> a,
|
(a, b) -> a,
|
||||||
LinkedHashMap::new));
|
LinkedHashMap::new));
|
||||||
|
this.callbackByName = byName;
|
||||||
// callbacks 列表也使用去重后的结果,避免 Spring AI ToolCallingChatOptions 校验重名报错
|
// callbacks 列表也使用去重后的结果,避免 Spring AI ToolCallingChatOptions 校验重名报错
|
||||||
this.callbacks = List.copyOf(callbackByName.values());
|
this.callbacks = List.copyOf(byName.values());
|
||||||
|
this.aliasIndex = buildAliasIndex(this.toolBeans, byName, beanNameResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor for {@link #rebuild} — preserves a pre-filtered alias index
|
||||||
|
* so we don't need {@code beanNameResolver} on every {@code with*} call.
|
||||||
|
*/
|
||||||
|
private AgentToolSet(List<Object> toolBeans, List<ToolCallback> callbacks,
|
||||||
|
Map<String, Set<ToolCallback>> precomputedAliasIndex) {
|
||||||
|
this.toolBeans = List.copyOf(toolBeans);
|
||||||
|
LinkedHashMap<String, ToolCallback> byName = callbacks.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
cb -> cb.getToolDefinition().name(),
|
||||||
|
cb -> cb,
|
||||||
|
(a, b) -> a,
|
||||||
|
LinkedHashMap::new));
|
||||||
|
this.callbackByName = byName;
|
||||||
|
this.callbacks = List.copyOf(byName.values());
|
||||||
|
this.aliasIndex = Map.copyOf(precomputedAliasIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** No-op resolver for callers that don't have access to Spring bean names. */
|
||||||
|
private static final Function<Object, String> NO_BEAN_NAMES = bean -> null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从预构建的 ToolCallback 列表构建工具集(用于 i18n 等需要包装 callback 的场景)
|
* 从预构建的 ToolCallback 列表构建工具集(用于 i18n 等需要包装 callback 的场景)
|
||||||
*/
|
*/
|
||||||
public static AgentToolSet fromCallbacks(List<Object> toolBeans, List<ToolCallback> callbacks) {
|
public static AgentToolSet fromCallbacks(List<Object> toolBeans, List<ToolCallback> callbacks) {
|
||||||
return new AgentToolSet(toolBeans != null ? toolBeans : List.of(), callbacks);
|
return new AgentToolSet(toolBeans != null ? toolBeans : List.of(), callbacks, NO_BEAN_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #fromCallbacks(List, List)} but additionally indexes each tool bean by
|
||||||
|
* its Spring bean name and Java simple class name, so {@link #withAllowedToolsOnly} accepts
|
||||||
|
* any of those identifiers (in addition to the {@code @Tool} function name).
|
||||||
|
*
|
||||||
|
* @param beanNameResolver lookup from a tool bean instance to its Spring bean name;
|
||||||
|
* may return {@code null} if the bean has no registered name
|
||||||
|
*/
|
||||||
|
public static AgentToolSet fromCallbacks(List<Object> toolBeans, List<ToolCallback> callbacks,
|
||||||
|
Function<Object, String> beanNameResolver) {
|
||||||
|
return new AgentToolSet(toolBeans != null ? toolBeans : List.of(), callbacks, beanNameResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,36 +124,45 @@ public class AgentToolSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AgentToolSet(toolBeans != null ? toolBeans : List.of(), allCallbacks);
|
return new AgentToolSet(toolBeans != null ? toolBeans : List.of(), allCallbacks, NO_BEAN_NAMES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过滤掉 denied 工具后返回新的 AgentToolSet。
|
* 过滤掉 denied 工具后返回新的 AgentToolSet。
|
||||||
* denied 工具不会暴露给模型,模型完全不知道它们的存在。
|
* denied 工具不会暴露给模型,模型完全不知道它们的存在。
|
||||||
*
|
*
|
||||||
* @param deniedTools denied 工具名集合(为空或 null 时直接返回 this)
|
* @param deniedTools denied 工具名集合(接受 function name / bean name / class simple name;
|
||||||
|
* 为空或 null 时直接返回 this)
|
||||||
*/
|
*/
|
||||||
public AgentToolSet withDeniedToolsFiltered(Set<String> deniedTools) {
|
public AgentToolSet withDeniedToolsFiltered(Set<String> deniedTools) {
|
||||||
if (deniedTools == null || deniedTools.isEmpty()) {
|
if (deniedTools == null || deniedTools.isEmpty()) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
List<ToolCallback> filtered = new ArrayList<>(callbacks);
|
Set<ToolCallback> denied = resolveAliases(deniedTools);
|
||||||
filtered.removeIf(cb -> deniedTools.contains(cb.getToolDefinition().name()));
|
if (denied.isEmpty()) {
|
||||||
return new AgentToolSet(toolBeans, filtered);
|
return this;
|
||||||
|
}
|
||||||
|
List<ToolCallback> filtered = callbacks.stream()
|
||||||
|
.filter(cb -> !denied.contains(cb))
|
||||||
|
.toList();
|
||||||
|
return rebuild(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅保留指定名称的工具(白名单模式,用于 per-agent 绑定)
|
* 仅保留指定名称的工具(白名单模式,用于 per-agent 绑定)
|
||||||
*
|
*
|
||||||
* @param allowedTools 允许的工具名集合(为 null 时直接返回 this,表示使用全局默认)
|
* @param allowedTools 允许的工具名集合(接受 function name / Spring bean name / Java class simple name;
|
||||||
|
* 为 null 时直接返回 this,表示使用全局默认)
|
||||||
*/
|
*/
|
||||||
public AgentToolSet withAllowedToolsOnly(Set<String> allowedTools) {
|
public AgentToolSet withAllowedToolsOnly(Set<String> allowedTools) {
|
||||||
if (allowedTools == null) {
|
if (allowedTools == null) {
|
||||||
return this; // null = 无绑定,使用全局默认
|
return this; // null = 无绑定,使用全局默认
|
||||||
}
|
}
|
||||||
List<ToolCallback> filtered = new ArrayList<>(callbacks);
|
Set<ToolCallback> allowed = resolveAliases(allowedTools);
|
||||||
filtered.removeIf(cb -> !allowedTools.contains(cb.getToolDefinition().name()));
|
List<ToolCallback> filtered = callbacks.stream()
|
||||||
return new AgentToolSet(toolBeans, filtered);
|
.filter(allowed::contains)
|
||||||
|
.toList();
|
||||||
|
return rebuild(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,15 +188,21 @@ public class AgentToolSet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回排除指定工具名后的新 AgentToolSet
|
* 返回排除指定工具名后的新 AgentToolSet
|
||||||
|
*
|
||||||
|
* @param toolNames 要排除的工具名集合(接受 function name / bean name / class simple name)
|
||||||
*/
|
*/
|
||||||
public AgentToolSet excluding(Set<String> toolNames) {
|
public AgentToolSet excluding(Set<String> toolNames) {
|
||||||
if (toolNames == null || toolNames.isEmpty()) {
|
if (toolNames == null || toolNames.isEmpty()) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
Set<ToolCallback> excluded = resolveAliases(toolNames);
|
||||||
|
if (excluded.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
List<ToolCallback> filtered = callbacks.stream()
|
List<ToolCallback> filtered = callbacks.stream()
|
||||||
.filter(cb -> !toolNames.contains(cb.getToolDefinition().name()))
|
.filter(cb -> !excluded.contains(cb))
|
||||||
.toList();
|
.toList();
|
||||||
return new AgentToolSet(toolBeans, filtered);
|
return rebuild(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,4 +218,107 @@ public class AgentToolSet {
|
|||||||
public int size() {
|
public int size() {
|
||||||
return callbacks.size();
|
return callbacks.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Internals ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a set of aliases (any mix of function name / bean name / class simple name)
|
||||||
|
* into the set of {@link ToolCallback} instances they refer to. Unknown aliases are
|
||||||
|
* silently dropped — the caller is expected to be tolerant of stale persistence data.
|
||||||
|
*/
|
||||||
|
private Set<ToolCallback> resolveAliases(Set<String> aliases) {
|
||||||
|
Set<ToolCallback> resolved = new LinkedHashSet<>();
|
||||||
|
for (String alias : aliases) {
|
||||||
|
Set<ToolCallback> hits = aliasIndex.get(alias);
|
||||||
|
if (hits != null) {
|
||||||
|
resolved.addAll(hits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconstruct a new {@code AgentToolSet} after filtering callbacks, carrying forward
|
||||||
|
* only the alias entries whose targets survived. This avoids re-running
|
||||||
|
* {@link ToolCallbacks#from(Object)} reflection on every {@code with*} call.
|
||||||
|
*/
|
||||||
|
private AgentToolSet rebuild(List<ToolCallback> filteredCallbacks) {
|
||||||
|
Set<ToolCallback> survivors = new HashSet<>(filteredCallbacks);
|
||||||
|
Map<String, Set<ToolCallback>> filteredAliases = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, Set<ToolCallback>> e : aliasIndex.entrySet()) {
|
||||||
|
Set<ToolCallback> kept = new LinkedHashSet<>();
|
||||||
|
for (ToolCallback cb : e.getValue()) {
|
||||||
|
if (survivors.contains(cb)) {
|
||||||
|
kept.add(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!kept.isEmpty()) {
|
||||||
|
filteredAliases.put(e.getKey(), Set.copyOf(kept));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new AgentToolSet(toolBeans, filteredCallbacks, filteredAliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the alias index. Function names are always indexed (they are the runtime truth);
|
||||||
|
* bean names and class simple names are indexed when {@code beanNameResolver} is provided
|
||||||
|
* — typically only the production registry has the {@link org.springframework.context.ApplicationContext}
|
||||||
|
* needed to map bean instances to names. Unit tests that pass empty {@code toolBeans}
|
||||||
|
* naturally get a function-name-only index.
|
||||||
|
*/
|
||||||
|
private static Map<String, Set<ToolCallback>> buildAliasIndex(
|
||||||
|
List<Object> toolBeans,
|
||||||
|
Map<String, ToolCallback> callbackByName,
|
||||||
|
Function<Object, String> beanNameResolver) {
|
||||||
|
|
||||||
|
Map<String, Set<ToolCallback>> aliases = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// 1. Always index by function name (the runtime identifier)
|
||||||
|
for (Map.Entry<String, ToolCallback> e : callbackByName.entrySet()) {
|
||||||
|
aliases.computeIfAbsent(e.getKey(), k -> new LinkedHashSet<>()).add(e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If we have bean info, also index by Spring bean name and Java class simple name.
|
||||||
|
// A single bean may expose multiple @Tool methods → the alias maps to a set.
|
||||||
|
if (beanNameResolver != null) {
|
||||||
|
for (Object bean : toolBeans) {
|
||||||
|
String beanName = beanNameResolver.apply(bean);
|
||||||
|
String simpleName = bean.getClass().getSimpleName();
|
||||||
|
|
||||||
|
// Find which callbacks belong to this bean, looking them up in the
|
||||||
|
// (possibly i18n-wrapped) callbackByName so we point at the same
|
||||||
|
// instances the rest of the set uses.
|
||||||
|
Set<ToolCallback> beanCallbacks = new LinkedHashSet<>();
|
||||||
|
ToolCallback[] rawCallbacks;
|
||||||
|
try {
|
||||||
|
rawCallbacks = ToolCallbacks.from(bean);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// Defensive: a misbehaving bean shouldn't break the whole tool set
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ToolCallback raw : rawCallbacks) {
|
||||||
|
ToolCallback wrapped = callbackByName.get(raw.getToolDefinition().name());
|
||||||
|
if (wrapped != null) {
|
||||||
|
beanCallbacks.add(wrapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (beanCallbacks.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (beanName != null && !beanName.isBlank()) {
|
||||||
|
aliases.computeIfAbsent(beanName, k -> new LinkedHashSet<>()).addAll(beanCallbacks);
|
||||||
|
}
|
||||||
|
if (simpleName != null && !simpleName.isBlank()) {
|
||||||
|
aliases.computeIfAbsent(simpleName, k -> new LinkedHashSet<>()).addAll(beanCallbacks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Freeze inner sets
|
||||||
|
Map<String, Set<ToolCallback>> frozen = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, Set<ToolCallback>> e : aliases.entrySet()) {
|
||||||
|
frozen.put(e.getKey(), Set.copyOf(e.getValue()));
|
||||||
|
}
|
||||||
|
return Map.copyOf(frozen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import vip.mate.i18n.LocaleAwareToolCallback;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -71,6 +73,19 @@ public class ToolRegistry {
|
|||||||
* 通过数据库 enabled 标志过滤,确保 UI 开关真正生效
|
* 通过数据库 enabled 标志过滤,确保 UI 开关真正生效
|
||||||
*/
|
*/
|
||||||
public List<Object> getEnabledTools() {
|
public List<Object> getEnabledTools() {
|
||||||
|
return List.copyOf(getEnabledToolBeansByName().values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate Spring beans once, returning a {@code beanName → bean} map of every
|
||||||
|
* currently-enabled @Tool bean.
|
||||||
|
* <p>
|
||||||
|
* This is the single source of truth for "which @Tool beans should the agent see"; both
|
||||||
|
* {@link #getEnabledTools()} and {@link #getEnabledToolSet()} build on it. Returning
|
||||||
|
* {@link LinkedHashMap} preserves the discovery order from {@code getBeansWithAnnotation},
|
||||||
|
* which {@link AgentToolSet} relies on (built-in tools first, MCP tools second).
|
||||||
|
*/
|
||||||
|
private LinkedHashMap<String, Object> getEnabledToolBeansByName() {
|
||||||
// 1. 从数据库获取明确禁用的 beanName 黑名单
|
// 1. 从数据库获取明确禁用的 beanName 黑名单
|
||||||
// 逻辑:只有 DB 中存在记录且 enabled=false 的才跳过
|
// 逻辑:只有 DB 中存在记录且 enabled=false 的才跳过
|
||||||
// DB 中没有记录的 bean 默认启用(向后兼容 + 新工具自动可用)
|
// DB 中没有记录的 bean 默认启用(向后兼容 + 新工具自动可用)
|
||||||
@ -82,7 +97,7 @@ public class ToolRegistry {
|
|||||||
.map(ToolEntity::getBeanName)
|
.map(ToolEntity::getBeanName)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
List<Object> tools = new ArrayList<>();
|
LinkedHashMap<String, Object> enabled = new LinkedHashMap<>();
|
||||||
|
|
||||||
// 2. 扫描 Spring 容器中所有带 @Tool 方法的 Bean
|
// 2. 扫描 Spring 容器中所有带 @Tool 方法的 Bean
|
||||||
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Component.class);
|
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Component.class);
|
||||||
@ -93,19 +108,20 @@ public class ToolRegistry {
|
|||||||
boolean hasToolMethod = java.util.Arrays.stream(bean.getClass().getMethods())
|
boolean hasToolMethod = java.util.Arrays.stream(bean.getClass().getMethods())
|
||||||
.anyMatch(m -> m.isAnnotationPresent(Tool.class));
|
.anyMatch(m -> m.isAnnotationPresent(Tool.class));
|
||||||
|
|
||||||
if (hasToolMethod) {
|
if (!hasToolMethod) {
|
||||||
// 3. 只有 DB 中明确 enabled=false 的才跳过,其余全部启用
|
continue;
|
||||||
if (disabledBeanNames.contains(beanName)) {
|
}
|
||||||
log.debug("Skipped disabled tool bean: {} (beanName={})", bean.getClass().getSimpleName(), beanName);
|
// 3. 只有 DB 中明确 enabled=false 的才跳过,其余全部启用
|
||||||
} else {
|
if (disabledBeanNames.contains(beanName)) {
|
||||||
tools.add(bean);
|
log.debug("Skipped disabled tool bean: {} (beanName={})", bean.getClass().getSimpleName(), beanName);
|
||||||
log.debug("Registered tool bean: {} (beanName={})", bean.getClass().getSimpleName(), beanName);
|
} else {
|
||||||
}
|
enabled.put(beanName, bean);
|
||||||
|
log.debug("Registered tool bean: {} (beanName={})", bean.getClass().getSimpleName(), beanName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Total enabled tools: {}", tools.size());
|
log.info("Total enabled tools: {}", enabled.size());
|
||||||
return tools;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,7 +132,16 @@ public class ToolRegistry {
|
|||||||
* 2. 当前容器中所有 ToolCallbackProvider(MCP server 等)
|
* 2. 当前容器中所有 ToolCallbackProvider(MCP server 等)
|
||||||
*/
|
*/
|
||||||
public AgentToolSet getEnabledToolSet() {
|
public AgentToolSet getEnabledToolSet() {
|
||||||
List<Object> toolBeans = getEnabledTools();
|
// Build both the bean list and the identity-based name lookup in one pass — the
|
||||||
|
// latter lets AgentToolSet's alias index resolve a saved binding like
|
||||||
|
// "BrowserUseTool" or "browserUseTool" back to the same callback as "browser_use".
|
||||||
|
LinkedHashMap<String, Object> beansByName = getEnabledToolBeansByName();
|
||||||
|
List<Object> toolBeans = new ArrayList<>(beansByName.values());
|
||||||
|
IdentityHashMap<Object, String> nameByBean = new IdentityHashMap<>();
|
||||||
|
for (Map.Entry<String, Object> e : beansByName.entrySet()) {
|
||||||
|
nameByBean.put(e.getValue(), e.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, ToolCallbackProvider> providerBeans = applicationContext.getBeansOfType(ToolCallbackProvider.class);
|
Map<String, ToolCallbackProvider> providerBeans = applicationContext.getBeansOfType(ToolCallbackProvider.class);
|
||||||
List<ToolCallbackProvider> providers = new ArrayList<>(providerBeans.values());
|
List<ToolCallbackProvider> providers = new ArrayList<>(providerBeans.values());
|
||||||
|
|
||||||
@ -164,7 +189,7 @@ public class ToolRegistry {
|
|||||||
|
|
||||||
log.info("Building AgentToolSet: toolBeans={}, providers={}, pluginTools={}, totalCallbacks={}",
|
log.info("Building AgentToolSet: toolBeans={}, providers={}, pluginTools={}, totalCallbacks={}",
|
||||||
toolBeans.size(), providers.size(), pluginToolCount, localizedCallbacks.size());
|
toolBeans.size(), providers.size(), pluginToolCount, localizedCallbacks.size());
|
||||||
return AgentToolSet.fromCallbacks(toolBeans, localizedCallbacks);
|
return AgentToolSet.fromCallbacks(toolBeans, localizedCallbacks, nameByBean::get);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user