Skills

Agent Skills is an open specification for reusable, model-agnostic skill definitions that can be loaded from the filesystem and exposed to an LLM as tools. For general information about skills, see the LangChain4j Skills documentation.

The quarkus-langchain4j-skills extension provides integration with the LangChain4j Skills module. When configured, it automatically loads skills from specified directories and registers a SkillsToolProvider CDI bean that exposes them to your AI services.

Only the tool mode is supported for skills at the moment. Shell skills don’t have Quarkus integration (yet), but you may use the dev.langchain4j:langchain4j-experimental-skills-shell module directly if you want.

Dependency

<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-skills</artifactId>
</dependency>

Configuration

The extension is configured via the quarkus.langchain4j.skills.directories property, which accepts a list of directories from which to load skills. Each directory is in the list is a relative or absolute filesystem path. If referencing a classpath resource, prefix the path with classpath:.

quarkus.langchain4j.skills.directories=skills,classpath:other-skills

Each listed directory is expected to contain subdirectories, where each subdirectory represents a single skill and must contain a SKILL.md file with YAML front matter defining the skill’s name and description. The body of the file becomes the skill’s instructions. See the Agent Skills specification for details on the SKILL.md format.

Example

Given the following directory structure on the classpath:

skills/
  poem-writing/
    SKILL.md

With skills/poem-writing/SKILL.md containing:

---
name: poem-writing
description: Instructions for writing a poem
---

When asked to write a poem, follow these rules strictly:

1. The poem MUST have exactly 12 lines.
2. The poem MUST be about a sad mushroom.

And application.properties:

quarkus.langchain4j.skills.directories=skills

The @Skills annotation

The @Skills annotation (io.quarkiverse.langchain4j.skills.Skills) is the recommended way to wire skills into your AI services and agents. It automatically configures both the tool provider (exposing the activate_skill tool) and a system message that tells the LLM which skills are available.

With @RegisterAiService

Place @Skills on the AI service interface:

@RegisterAiService
@Skills (1)
public interface PoemAiService {

    String chat(@UserMessage String message);
}
1 All loaded skills are available.

To restrict the service to specific skills, pass their names:

@RegisterAiService
@Skills("poem-writing") (1)
public interface PoemAiService {

    String chat(@UserMessage String message);
}
1 Only the poem-writing skill is available. If the name does not match any loaded skill, the application will fail at startup.

You can also place @Skills on individual methods to give each method its own set of skills:

@RegisterAiService
public interface TravelService {

    @Skills("adventure-trip")
    String planAdventure(@UserMessage String request);

    @Skills("family-trip")
    String planFamilyTrip(@UserMessage String request);
}

When @Skills is present on both the interface and a method, the method-level annotation takes precedence. Methods without @Skills inherit the class-level skills.

@RegisterAiService
@Skills (1)
public interface TravelService {

    @Skills("adventure-trip") (2)
    String planAdventure(@UserMessage String request);

    String generalChat(@UserMessage String request); (3)
}
1 Class-level: all skills available by default.
2 Method-level overrides: only adventure-trip is available here.
3 No method-level annotation: inherits all skills from the class.

With @Agent

Place @Skills on the @Agent-annotated method:

public interface TripPlannerAgent {

    @Agent(description = "Plans trips", outputKey = "plan")
    @Skills("adventure-trip", "family-trip") (1)
    String planTrip(@V("request") String request);
}
1 Only the named skills are available to this agent.

Fail-fast validation

If any skill name passed to @Skills does not match a loaded skill, the application will fail at startup with a clear error listing the available skill names.

Custom SkillsConfigurator

The built-in DefaultSkillsConfigurator generates an English system message describing the available skills. To customize the message (for example, to use a different language), provide your own SkillsConfigurator CDI bean. It will automatically override the default:

@ApplicationScoped
public class MySkillsConfigurator implements SkillsConfigurator {

    @Inject
    DefaultSkillsConfigurator delegate; (1)

    @Override
    public ToolProvider createToolProvider(List<String> skillNames) {
        return delegate.createToolProvider(skillNames);
    }

    @Override
    public String formatAvailableSkills(List<String> skillNames) {
        return delegate.formatAvailableSkills(skillNames);
    }

    @Override
    public String buildSkillsSystemMessage(List<String> skillNames) {
        return "Hai accesso alle seguenti competenze:\n"
                + formatAvailableSkills(skillNames)
                + "\nQuando la richiesta riguarda una di queste competenze, "
                + "attivala prima con lo strumento `activate_skill`.";
    }
}
1 You can inject and delegate to the default implementation to reuse its tool provider and formatting logic.

Using the SkillsToolProvider

Another way to wire skills to your AI services is through The skills ToolProvider which is automatically registered as a CDI bean of type SkillsToolProvider. Any @RegisterAiService that uses a ToolProvider will pick it up:

@RegisterAiService(systemMessageProviderSupplier = SkillsSystemMessageProvider.class)
public interface PoemAiService {

    String chat(String message);
}
The SkillsSystemMessageProvider (io.quarkiverse.langchain4j.skills.SkillsSystemMessageProvider) is a built-in sample implementation of SystemMessageProvider that generates a system message containing the descriptions of all loaded skills. You can implement your own SystemMessageProvider (and reuse parts of the SkillsSystemMessageProvider) if you need to customize the system message further. If you don’t specify a system message provider, then the LLM might not be aware which skills are available and thus won’t be able to use them unless the end user provides the exact skill name during the chat.
@Path("/poem")
public class PoemResource {

    @Inject
    PoemAiService poemAiService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String poem() {
        return poemAiService.chat(
                "Activate the poem-writing skill and write a poem following its instructions.");
    }
}

A complete working example is available in the skills sample.