This is an automated email from the ASF dual-hosted git repository. tomsun28 pushed a commit to branch 2.0.0 in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit 5dc7205fdac8f9b8e0594e65ed4bc3f2981bd5ab Author: tomsun28 <[email protected]> AuthorDate: Fri Oct 9 16:33:32 2026 +0800 fix(ai): let tool.search match natural language queries tool.search matched the raw query as a single substring against tool names and descriptions, so "alert analysis policy create" could never find alert_analysis_policy.create and the model could not discover on-demand tools. Tokenize both sides on non-alphanumeric characters and require every query token to appear in a name or description token, keeping the literal substring fast path. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../ai/gateway/tool/core/AgentToolRegistry.java | 29 ++++++++++++++++++---- .../gateway/tool/core/AgentToolRegistryTest.java | 12 +++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/hertzbeat-ai-gateway/src/main/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistry.java b/hertzbeat-ai-gateway/src/main/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistry.java index 2218b74c8b..74c26f3f6a 100644 --- a/hertzbeat-ai-gateway/src/main/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistry.java +++ b/hertzbeat-ai-gateway/src/main/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistry.java @@ -65,15 +65,34 @@ public class AgentToolRegistry { .filter(descriptor -> descriptor.getExposure() == AgentToolExposure.MODEL_ON_DEMAND) .filter(descriptor -> namespace == null || namespace.isBlank() || namespace.equalsIgnoreCase(descriptor.getNamespace())) - .filter(descriptor -> searchText == null || searchText.isBlank() - || descriptor.getName().toLowerCase(java.util.Locale.ROOT) - .contains(searchText) - || descriptor.getDescription().toLowerCase(java.util.Locale.ROOT) - .contains(searchText)) + .filter(descriptor -> matchesSearchText(descriptor, searchText)) .limit(MAX_DISCOVERED_TOOLS_PER_SEARCH) .toList(); } + private boolean matchesSearchText(AgentToolDescriptor descriptor, String searchText) { + if (searchText == null || searchText.isBlank()) { + return true; + } + String name = descriptor.getName().toLowerCase(java.util.Locale.ROOT); + String description = descriptor.getDescription().toLowerCase(java.util.Locale.ROOT); + if (name.contains(searchText) || description.contains(searchText)) { + return true; + } + // Model queries arrive as natural language ("alert analysis policy create") while tool + // names are delimited by dots and underscores ("alert_analysis_policy.create"), so also + // compare token-by-token: every query token must appear in a name or description token. + List<String> queryTokens = java.util.Arrays.stream(searchText.split("[^a-z0-9]+")) + .filter(token -> !token.isEmpty()) + .toList(); + if (queryTokens.isEmpty()) { + return false; + } + String[] haystackTokens = (name + " " + description).split("[^a-z0-9]+"); + return queryTokens.stream().allMatch(queryToken -> java.util.Arrays.stream(haystackTokens) + .anyMatch(part -> part.contains(queryToken))); + } + /** * Immutable catalog entry with its single execution function. */ diff --git a/hertzbeat-ai-gateway/src/test/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistryTest.java b/hertzbeat-ai-gateway/src/test/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistryTest.java index c59353ce8b..7c9537fb6a 100644 --- a/hertzbeat-ai-gateway/src/test/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistryTest.java +++ b/hertzbeat-ai-gateway/src/test/java/org/apache/hertzbeat/ai/gateway/tool/core/AgentToolRegistryTest.java @@ -77,6 +77,18 @@ class AgentToolRegistryTest { .map(AgentToolDescriptor::getName).toList()); } + @Test + void shouldMatchNaturalLanguageQueryAgainstDelimitedToolNames() { + AgentToolRegistry registry = new AgentToolRegistry(); + registry.register(handler("alert_analysis_policy.create", AgentToolExposure.MODEL_ON_DEMAND)); + registry.register(handler("jdbc.query", AgentToolExposure.MODEL_ON_DEMAND)); + + assertEquals(java.util.List.of("alert_analysis_policy.create"), + registry.discoverableDescriptors(null, "alert analysis policy create").stream() + .map(AgentToolDescriptor::getName).toList()); + assertTrue(registry.discoverableDescriptors(null, "no-matching-token").isEmpty()); + } + @Test void shouldRejectUnscopedDiscovery() { AgentToolRegistry registry = new AgentToolRegistry(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
