Interface LLMProvider.ToolSpec

Enclosing interface:
LLMProvider

public static interface LLMProvider.ToolSpec
A framework-agnostic tool definition that the LLM can invoke.

Unlike vendor-specific tool annotations (e.g., LangChain4j's @Tool, Spring AI's @Tool), this interface allows tools to be defined programmatically without depending on any specific AI framework.

Tool definitions are typically provided by AIController implementations through their AIController.getTools() method.

  • Method Summary

    Modifier and Type
    Method
    Description
    execute(tools.jackson.databind.JsonNode arguments)
    Executes the tool with the given arguments.
    Gets a human-readable description of what this tool does.
    Gets the unique name of this tool.
    Gets the JSON Schema describing the parameters this tool accepts.
  • Method Details

    • getName

      String getName()
      Gets the unique name of this tool. The name must contain only alphanumeric characters, underscores, and hyphens, with a maximum length of 64 characters (matching the pattern ^[a-zA-Z0-9_-]{1,64}$), as required by popular LLM APIs. Names that do not match this pattern will be rejected by the orchestrator at request time. To avoid name collisions, use a namespaced name such as "MyController_updateConfig".
      Returns:
      the tool name, never null or empty
    • getDescription

      String getDescription()
      Gets a human-readable description of what this tool does. This description is sent to the LLM to help it decide when to invoke the tool.
      Returns:
      the tool description, never null
    • getParametersSchema

      String getParametersSchema()
      Gets the JSON Schema describing the parameters this tool accepts. The schema should follow the JSON Schema specification.

      Example:

       {
         "type": "object",
         "properties": {
           "query": { "type": "string", "description": "The SQL query" }
         },
         "required": ["query"]
       }
       

      Hand-writing the JSON is fine for small schemas but becomes error-prone as they grow. For larger schemas, a schema generator catches keyword typos and structural mistakes at compile time. When using SpringAILLMProvider, Spring AI's JsonSchemaGenerator is available without an extra dependency:

       import com.fasterxml.jackson.annotation.JsonPropertyDescription;
       import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
      
       record UpdateGridArgs(
               @JsonPropertyDescription("The SQL query") String query) {}
      
       private static final String SCHEMA = JsonSchemaGenerator
               .generateForType(UpdateGridArgs.class);
      
       // in execute(JsonNode arguments):
       UpdateGridArgs args = new ObjectMapper().treeToValue(arguments,
               UpdateGridArgs.class);
       

      For other setups, any general-purpose JSON Schema generator that produces a schema string from a typed Java class works equivalently.

      Whichever approach you pick, verify the output stays inside the portable JSON Schema subset (string, integer, number, boolean, array, object, plus anyOf and enum). Generators may emit keywords such as format, pattern, or $ref that not every LLM provider accepts.

      For a tool that takes no parameters, return null. The built-in providers then declare a placeholder schema to the LLM — some LLM APIs misbehave when a tool declares no properties at all — and always invoke execute(JsonNode) with an empty arguments object, whatever the model sends.

      Returns:
      the JSON Schema string, or null if the tool takes no parameters
    • execute

      String execute(tools.jackson.databind.JsonNode arguments)
      Executes the tool with the given arguments.

      Implementations should return a human-readable result string on success. On failure, throw a ToolException to pass its message to the LLM so it can correct its next attempt; any other runtime exception is caught, logged, and replaced with a generic error message so internal details are not leaked.

      May be invoked from a background thread — with a streaming provider, or with background execution enabled — where Vaadin thread locals such as UI.getCurrent() are not bound. Wrap UI component access in ui.access(), or work on state captured in AIController.onRequest(com.vaadin.flow.component.ai.orchestrator.RequestListener.RequestEvent).

      An unbound thread local returns null rather than throwing, so a tool that reads one without checking does not fail — it proceeds with a missing value and can return a confidently wrong answer the LLM has no way to recognize as wrong. Capture what the tool needs in AIController.onRequest(com.vaadin.flow.component.ai.orchestrator.RequestListener.RequestEvent), which always runs on the UI thread, rather than reading thread locals here.

      Parameters:
      arguments - the tool arguments as a JsonNode matching the parameters schema
      Returns:
      the result of the tool execution as a string, never null