Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.ai.tool;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Utility class for filtering {@link ToolCallback} instances based on metadata and other
* criteria. This class provides various predicate-based filters that can be used to
* select tools dynamically based on their metadata properties.
*
* <p>
* Example usage:
* </p>
*
* <pre class="code">
* // Filter tools by type
* List&lt;ToolCallback&gt; filteredTools = ToolCallbackFilters.filterByType(allTools, "RealTimeAnalysis");
*
* // Filter tools by multiple criteria
* Predicate&lt;ToolCallback&gt; filter = ToolCallbackFilters.byType("RealTimeAnalysis")
* .and(ToolCallbackFilters.byMinPriority(7));
* List&lt;ToolCallback&gt; filtered = allTools.stream().filter(filter).collect(Collectors.toList());
* </pre>
*
*/
public final class ToolCallbackFilters {

private ToolCallbackFilters() {
}

/**
* Creates a predicate that filters tools by their type metadata.
* @param type the required type
* @return a predicate that tests if a tool has the specified type
*/
public static Predicate<ToolCallback> byType(String type) {
return toolCallback -> {
Map<String, Object> metadata = toolCallback.getToolDefinition().metadata();
Object typeValue = metadata.get("type");
return typeValue != null && type.equals(typeValue.toString());
};
}

/**
* Creates a predicate that filters tools by their category metadata.
* @param category the required category
* @return a predicate that tests if a tool has the specified category
*/
public static Predicate<ToolCallback> byCategory(String category) {
return toolCallback -> {
Map<String, Object> metadata = toolCallback.getToolDefinition().metadata();
Object categoryValue = metadata.get("category");
return categoryValue != null && category.equals(categoryValue.toString());
};
}

/**
* Creates a predicate that filters tools by their priority metadata. Only tools with
* priority greater than or equal to the specified minimum are included.
* @param minPriority the minimum priority
* @return a predicate that tests if a tool's priority meets the threshold
*/
public static Predicate<ToolCallback> byMinPriority(int minPriority) {
return toolCallback -> {
Map<String, Object> metadata = toolCallback.getToolDefinition().metadata();
Object priorityValue = metadata.get("priority");
if (priorityValue != null) {
try {
int priority = priorityValue instanceof Number ? ((Number) priorityValue).intValue()
: Integer.parseInt(priorityValue.toString());
return priority >= minPriority;
}
catch (NumberFormatException e) {
return false;
}
}
return false;
};
}

/**
* Creates a predicate that filters tools by their tags metadata. Tools must have at
* least one of the specified tags.
* @param tags the required tags
* @return a predicate that tests if a tool has any of the specified tags
*/
public static Predicate<ToolCallback> byTags(String... tags) {
Set<String> tagSet = Set.of(tags);
return toolCallback -> {
Map<String, Object> metadata = toolCallback.getToolDefinition().metadata();
Object tagsValue = metadata.get("tags");
if (tagsValue instanceof List) {
List<?> toolTags = (List<?>) tagsValue;
return toolTags.stream().anyMatch(tag -> tagSet.contains(tag.toString()));
}
return false;
};
}

/**
* Creates a predicate that filters tools by a custom metadata field.
* @param key the metadata key
* @param expectedValue the expected value
* @return a predicate that tests if a tool has the specified metadata value
*/
public static Predicate<ToolCallback> byMetadata(String key, Object expectedValue) {
return toolCallback -> {
Map<String, Object> metadata = toolCallback.getToolDefinition().metadata();
Object value = metadata.get(key);
return expectedValue.equals(value);
};
}

/**
* Filters a list of tool callbacks by type.
* @param toolCallbacks the list of tool callbacks to filter
* @param type the required type
* @return a filtered list containing only tools with the specified type
*/
public static List<ToolCallback> filterByType(List<ToolCallback> toolCallbacks, String type) {
return toolCallbacks.stream().filter(byType(type)).collect(Collectors.toList());
}

/**
* Filters an array of tool callbacks by type.
* @param toolCallbacks the array of tool callbacks to filter
* @param type the required type
* @return a filtered array containing only tools with the specified type
*/
public static ToolCallback[] filterByType(ToolCallback[] toolCallbacks, String type) {
return Arrays.stream(toolCallbacks).filter(byType(type)).toArray(ToolCallback[]::new);
}

/**
* Filters a list of tool callbacks by category.
* @param toolCallbacks the list of tool callbacks to filter
* @param category the required category
* @return a filtered list containing only tools with the specified category
*/
public static List<ToolCallback> filterByCategory(List<ToolCallback> toolCallbacks, String category) {
return toolCallbacks.stream().filter(byCategory(category)).collect(Collectors.toList());
}

/**
* Filters a list of tool callbacks by minimum priority.
* @param toolCallbacks the list of tool callbacks to filter
* @param minPriority the minimum priority
* @return a filtered list containing only tools with priority >= minPriority
*/
public static List<ToolCallback> filterByMinPriority(List<ToolCallback> toolCallbacks, int minPriority) {
return toolCallbacks.stream().filter(byMinPriority(minPriority)).collect(Collectors.toList());
}

/**
* Filters a list of tool callbacks by tags.
* @param toolCallbacks the list of tool callbacks to filter
* @param tags the required tags (tool must have at least one)
* @return a filtered list containing only tools with at least one of the specified
* tags
*/
public static List<ToolCallback> filterByTags(List<ToolCallback> toolCallbacks, String... tags) {
return toolCallbacks.stream().filter(byTags(tags)).collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.ai.tool.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to add metadata to a tool method. Can be used in conjunction with
* {@link Tool} annotation. Metadata can be used for filtering, categorization, and other
* purposes when managing large sets of tools.
*
* <p>
* Example usage:
* </p>
*
* <pre class="code">
* &#64;Tool(description = "Analyzes real-time market data")
* &#64;ToolMetadata(type = "RealTimeAnalysis", category = "market", priority = 8)
* public String analyzeMarketData(String symbol) {
* // implementation
* }
* </pre>
*
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToolMetadata {

/**
* Additional metadata entries in key=value format. Example:
* {"environment=production", "version=1.0"}
* @return array of metadata key-value pairs
*/
String[] value() default {};

/**
* Tool category for classification purposes. Can be used to group related tools
* together.
* @return the category name
*/
String category() default "";

/**
* Tool type for filtering purposes. Useful for distinguishing between different kinds
* of operations (e.g., "RealTimeAnalysis", "HistoricalAnalysis").
* @return the type identifier
*/
String type() default "";

/**
* Priority level for tool selection (1-10, where 10 is highest priority). Can be used
* to influence tool selection when multiple tools are available.
* @return the priority level
*/
int priority() default 5;

/**
* Tags associated with the tool. Useful for flexible categorization and filtering.
* @return array of tags
*/
String[] tags() default {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package org.springframework.ai.tool.definition;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.ai.util.ParsingUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
Expand All @@ -26,12 +30,21 @@
* @author Thomas Vitale
* @since 1.0.0
*/
public record DefaultToolDefinition(String name, String description, String inputSchema) implements ToolDefinition {
public record DefaultToolDefinition(String name, String description, String inputSchema,
Map<String, Object> metadata) implements ToolDefinition {

public DefaultToolDefinition {
Assert.hasText(name, "name cannot be null or empty");
Assert.hasText(description, "description cannot be null or empty");
Assert.hasText(inputSchema, "inputSchema cannot be null or empty");
metadata = Map.copyOf(metadata);
}

/**
* Constructor for backward compatibility without metadata.
*/
public DefaultToolDefinition(String name, String description, String inputSchema) {
this(name, description, inputSchema, Collections.emptyMap());
}

public static Builder builder() {
Expand All @@ -46,6 +59,8 @@ public static final class Builder {

private String inputSchema;

private Map<String, Object> metadata = new HashMap<>();

private Builder() {
}

Expand All @@ -64,12 +79,22 @@ public Builder inputSchema(String inputSchema) {
return this;
}

public Builder metadata(Map<String, Object> metadata) {
this.metadata = new HashMap<>(metadata);
return this;
}

public Builder addMetadata(String key, Object value) {
this.metadata.put(key, value);
return this;
}

public ToolDefinition build() {
if (!StringUtils.hasText(this.description)) {
Assert.hasText(this.name, "toolName cannot be null or empty");
this.description = ParsingUtils.reConcatenateCamelCase(this.name, " ");
}
return new DefaultToolDefinition(this.name, this.description, this.inputSchema);
return new DefaultToolDefinition(this.name, this.description, this.inputSchema, this.metadata);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package org.springframework.ai.tool.definition;

import java.util.Collections;
import java.util.Map;

/**
* Definition used by the AI model to determine when and how to call the tool.
*
Expand All @@ -39,6 +42,16 @@ public interface ToolDefinition {
*/
String inputSchema();

/**
* The metadata associated with the tool. This can be used for filtering,
* categorization, and other purposes. Default implementation returns an empty map.
* @return an unmodifiable map of metadata key-value pairs
* @since 1.0.0
*/
default Map<String, Object> metadata() {
return Collections.emptyMap();
}

/**
* Create a default {@link ToolDefinition} builder.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public static DefaultToolDefinition.Builder builder(Method method) {
return DefaultToolDefinition.builder()
.name(ToolUtils.getToolName(method))
.description(ToolUtils.getToolDescription(method))
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method));
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.metadata(ToolUtils.getToolMetadata(method));
}

/**
Expand Down
Loading