Skip to main content

Write and deploy custom tools

A custom tool is a Java method that the LLM can call during a conversation. Tools give the LLM the ability to perform actions: query a database, call an external API, read a file. This guide explains how to write a tool, package it, and deploy it.

How a custom tool fits in the system​

Figure: A custom tool JAR is scanned at startup, registered by IntegrationLoader, and invoked by ToolExecutor on LLM tool call requests.

Prerequisites​

  • Java 21 or later (the plugin JAR must target Java 21 bytecode)
  • Maven with access to the Uxopian AI BOM or individual dependency coordinates
  • The uxopian-ai model and common modules available as dependencies (or use the shaded JAR approach)

Steps​

1. Create a Maven project​

Create a standard Maven project. Add the required dependencies: the LangChain4J agent.tool annotations and the uxopian-ai tool annotations. The key annotations come from:

  • dev.langchain4j:langchain4j-core: provides @Tool and @P
  • com.uxopian.ai:annotation: provides @ToolService (groupId com.uxopian.ai, artifactId annotation)
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.uxopian.ai</groupId>
<artifactId>annotation</artifactId>
<version>${uxopian-ai.version}</version>
</dependency>

The annotation artifact is published to the Arondor Artifactory registry (artifactory.arondor.cloud). See Registry access for repository configuration. Set ${uxopian-ai.version} to the Uxopian AI release version you are targeting.

2. Write the tool class​

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import com.uxopian.ai.model.annotation.tool.ToolService;
import org.springframework.stereotype.Service;

@Service
@ToolService
public class MyCustomTool {

@Tool("Search the internal knowledge base for articles matching a topic. "
+ "Returns a list of article titles and summaries.")
public List<String> searchKnowledgeBase(
@P("The topic to search for") String topic,
@P("Maximum number of results") int maxResults) {

// Your implementation here
return List.of("Article about " + topic);
}
}

Rules for tool methods:

  • The method must be public.
  • The @Tool annotation value is the description sent to the LLM. Write it clearly: the LLM uses this description to decide when to call the tool.
  • Each parameter annotated with @P gets a description. The LLM uses these to construct the arguments.
  • The return type must be serializable. Strings and lists of simple objects work well. The result is converted to a string before being returned to the LLM.
  • Tool execution is synchronous. Long-running operations will block the request thread.

3. Package as a shaded JAR​

The plugin JAR must be self-contained (all dependencies included). Use the Maven Shade plugin:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>

Build the shaded JAR:

mvn clean package

The output JAR is in target/my-custom-tool-1.0.0.jar (or similar).

4. Deploy the JAR​

Copy the shaded JAR to the plugins/ directory on the uxopian-ai host. In Docker Compose deployments, mount the directory:

services:
uxopian-ai:
volumes:
- ./plugins:/app/plugins

Then copy the JAR:

cp target/my-custom-tool-1.0.0.jar ./plugins/

5. Restart uxopian-ai​

Plugins are scanned at startup. Restart the container to load the new plugin:

docker compose restart uxopian-ai

Check the logs to confirm the tool was registered:

docker compose logs uxopian-ai | grep "Registered"

You should see a line indicating the number of tool methods registered.

6. Verify the tool is available​

The LLM will use tools when they are relevant to the user's request. To confirm the tool is loaded, check the admin UI statistics or send a request that explicitly asks the LLM to use the tool.

Important constraints​

  • Shaded JARs must not include Spring Boot's own classes. Only include classes needed by the tool.
  • If a bean name collides with an already-registered bean, IntegrationLoader logs a warning and skips the duplicate.
  • Tools can be disabled globally with TOOLS_ENABLED=false.
  • The LLM only calls tools if the model configured for the conversation has functionCallSupported: true.
  • Prompts with reasoningDisabled: true do not receive tool specifications.