= LangChain4j Agent Component
:doctitle: LangChain4j Agent
:shortname: langchain4j-agent
:artifactid: camel-langchain4j-agent
:description: LangChain4j Agent component
:since: 4.14
:supportlevel: Preview
:tabs-sync-option:
:component-header: Only producer is supported
//Manually maintained attributes
:group: AI
:camel-spring-boot-name: langchain4j-agent

*Since Camel {since}*

*{component-header}*

The LangChain4j Agent component provides comprehensive AI agent capabilities by integrating with the https://github.com/langchain4j/langchain4j[LangChain4j library]. This component supports advanced AI agent patterns including tool calling, MCP (Model Context Protocol) integration, conversation memory, retrieval-augmented generation (RAG), and input/output guardrails.

== Features

The LangChain4j Agent component offers the following key features:

* **Agent-Based Architecture**: Flexible agent creation using the `Agent` API interface
* **Tool Integration**: Seamless integration with Camel routes via the `langchain4j-tools` component
* **MCP Tools**: Integration with Model Context Protocol (MCP) tools for external system access
* **Conversation Memory**: Persistent chat memory for maintaining conversation context
* **RAG Support**: Integration with retrieval systems for naive and advanced RAG
* **Guardrails**: Input and output validation and transformation
* **Configuration Flexibility**: Centralized agent configuration using `AgentConfiguration`
* **Multimodal Content**: Support for images, PDFs, audio, video, and text files from file-based Camel components

== Component Options

The component has been simplified to use only two main options:

* **agent**: Reference to an `Agent` implementation registered in the Camel registry
* **tags**: Tags for discovering and calling Camel route tools (optional)

All other configuration (chat models, memory, RAG, guardrails, custom tools, MCP clients) is now handled through the `AgentConfiguration` when creating agents.

== URI format

[source]
----
langchain4j-agent:agentId[?options]
----

Where *agentId* is a unique identifier for the agent instance.


// component options: START
include::partial$component-configure-options.adoc[]
include::partial$component-endpoint-options.adoc[]
include::partial$component-endpoint-headers.adoc[]
// component options: END

== OAuth Authentication

=== Quarkus Runtime

When running in Quarkus, use the https://quarkus.io/extensions/io.quarkiverse.langchain4j/quarkus-langchain4j-oidc-mcp-auth-provider/[quarkus-langchain4j-oidc-mcp-auth-provider] extension for MCP server authentication. This is the recommended approach as it integrates natively with Quarkus OIDC, supports automatic token propagation, and follows Quarkus security best practices.

[source,xml]
----
<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-oidc-mcp-auth-provider</artifactId>
</dependency>
----

With this extension, the authenticated user's OIDC access token is automatically propagated to MCP servers. MCP clients are configured via `quarkus.langchain4j.mcp.*` properties and are injected as pre-built beans. Camel picks them up via the `mcpClients` parameter — no `oauthProfile` or `camel-oauth` is needed.

=== camel-main and manual MCP server configuration

When running outside Quarkus (e.g., camel-main) or when manually configuring MCP servers via inline `mcpServer.<name>.*` parameters, use the `oauthProfile` option to acquire an access token via the OAuth 2.0 Client Credentials grant. The token is injected as an `Authorization: Bearer` header on HTTP-based MCP transports. This requires `camel-oauth` on the classpath.

[source,properties]
----
camel.oauth.keycloak.client-id=camel-client
camel.oauth.keycloak.client-secret=camel-client-secret
camel.oauth.keycloak.token-endpoint=https://keycloak.example.com/realms/camel/protocol/openid-connect/token
----

[source,java]
----
from("direct:agent")
    .to("langchain4j-agent:myAgent"
        + "?mcpServer.tools.transportType=http"
        + "&mcpServer.tools.url=https://mcp.internal/mcp"
        + "&mcpServer.tools.oauthProfile=keycloak");
----

=== LLM Provider Authentication

The langchain4j-agent component accepts a pre-built `Agent` or `ChatModel` bean — it does not create the LLM client itself. For LLM providers that require OAuth authentication (e.g., Azure OpenAI), use `OAuthHelper` when building the bean programmatically:

[source,java]
----
import org.apache.camel.support.OAuthHelper;

public class MyRouteBuilder extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        String token = OAuthHelper.resolveOAuthToken(getContext(), "azure");

        ChatModel chatModel = OpenAiChatModel.builder()
                .apiKey(token)
                .modelName("gpt-4")
                .build();

        getContext().getRegistry().bind("myChatModel", chatModel);

        from("direct:agent")
            .to("langchain4j-agent:myAgent");
    }
}
----

NOTE: In Quarkus or Spring Boot, configure the ChatModel via the framework's own properties and dependency injection instead of programmatic bean registration.

include::spring-boot:partial$starter.adoc[]


== Usage

=== Creating Custom Agents

Starting from Camel 4.14, the LangChain4j Agent component uses an agent-based architecture where agents are created by implementing the `org.apache.camel.component.langchain4j.agent.api.Agent` interface. The component provides two built-in agent implementations:

* `AgentWithMemory` - For chat interactions with conversation history
* `AgentWithoutMemory` - For stateless chat interactions

==== Agent Configuration

Agents are configured using the `AgentConfiguration` class which provides a fluent API for setting up:

* Chat Model
* Chat Memory Provider (for memory-enabled agents)
* Retrieval Augmentor (for RAG functionality)
* Input and Output Guardrails

==== Creating an Agent without Memory

[source, java]
----
// Create and configure the chat model
ChatModel chatModel = OpenAiChatModel.builder()
    .apiKey(openApiKey)
    .modelName(GPT_3_5_TURBO)
    .temperature(0.3)
    .timeout(ofSeconds(3000))
    .build();

// Create agent configuration
AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel)
    .withInputGuardrailClasses(List.of())
    .withOutputGuardrailClasses(List.of());

// Create the agent
Agent simpleAgent = new AgentWithoutMemory(configuration);

// Register the agent in the Camel context
context.getRegistry().bind("simpleAgent", simpleAgent);
----

Use the agent in your Camel route:

[source, java]
----
from("direct:chat")
    .to("langchain4j-agent:test?agent=#simpleAgent")
----

==== Creating an Agent with Memory

[source, java]
----
// Create chat model (same as above)
ChatModel chatModel = OpenAiChatModel.builder()...

// Create memory provider
ChatMemoryProvider memoryProvider = memoryId -> MessageWindowChatMemory.builder()
    .id(memoryId)
    .maxMessages(10)
    .chatMemoryStore(persistentStore) // Your persistent store implementation
    .build();

// Create agent configuration with memory
AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel)
    .withChatMemoryProvider(memoryProvider)
    .withInputGuardrailClasses(List.of())
    .withOutputGuardrailClasses(List.of());

// Create the agent
Agent memoryAgent = new AgentWithMemory(configuration);

// Register the agent
context.getRegistry().bind("memoryAgent", memoryAgent);
----

[NOTE]
====
Add the `camel-langchain4j-agent-api` dependency to access the Agent API classes in your application:

[source,xml]
----
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-langchain4j-agent-api</artifactId>
    <version>x.x.x</version>
</dependency>
----
====

=== Basic Chat with only a userMessage

For simple chat interactions, you can use an agent without memory.

[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:simple?agent=#simpleAgent")
----

The body can either contain the prompt as a String, or you can create an object of type *org.apache.camel.component.langchain4j.agent.api.AiAgentBody* containing the userMessage.

.Usage example with a body as String:
[source, java]
----
var prompt = "What is Apache Camel";

String response = template.requestBody("direct:chat", prompt, String.class);
----

.Usage example with a body as AiAgentBody:
[source, java]
----
var prompt = "What is Apache Camel";
AiAgentBody body = new AiAgentBody(prompt);

String response = template.requestBody("direct:chat", body, String.class);
----

=== Basic Chat with user and system messages

For chat interactions with system prompts, you can use an agent without memory.

[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:simple?agent=#simpleAgent")
----

The body can either contain the user prompt as a String and specifying the *CamelLangChain4jAgentSystemMessage* header for the system prompt, or you can create an object of type *org.apache.camel.component.langchain4j.agent.api.AiAgentBody* containing both *userMessage* and *systemMessage*.

.Usage example with a body as String:
[source, java]
----
var userPrompt = "Write a short story about a lost cat.";
var systemPrompt = "You are a whimsical storyteller. Your responses should be imaginative, descriptive, and always include a touch of magic. Start every story with 'Once upon a starlit night...";

String response = template.requestBodyAndHeader("direct:chat",
                userPrompt, "CamelLangChain4jAgentSystemMessage", systemPrompt , String.class);
----

.Usage example with a body as AiAgentBody:
[source, java]
----
var userPrompt = "Write a short story about a lost cat.";
var systemPrompt = "You are a whimsical storyteller. Your responses should be imaginative, descriptive, and always include a touch of magic. Start every story with 'Once upon a starlit night...";

AiAgentBody body = new AiAgentBody()
                .withUserMessage(userPrompt)
                .withSystemMessage(systemPrompt);

String response = template.requestBody("direct:chat", body, String.class);
----

=== Chat with Tools

Integrate with Camel routes as tools. The LangChain4j Agent component integrates with Camel Routes defined using the Camel LangChain4j Tools component via the `tags` parameter.

[source,java]
----
// Define tool routes
from("langchain4j-tools:userDb?tags=users&description=Query user database&parameter.userId=string")
    .setBody(constant("{\"name\": \"John Doe\", \"id\": \"123\"}"));

from("langchain4j-tools:weather?tags=weather&description=Get weather information&parameter.city=string")
    .setBody(constant("{\"weather\": \"sunny\", \"temperature\": \"22°C\"}"));

// Agent with tools (using the created agent)
from("direct:chat")
    .to("langchain4j-agent:tools?agent=#simpleAgent&tags=users,weather");
----

.Usage example :
[source, java]
----
var userPrompt = "Can you tell me the name of user 123 and the weather in New York?";
var systemPrompt = "You are a helpful assistant that can access user database and weather information. Use the available tools to provide accurate information.";

String response = template.requestBodyAndHeader("direct:chat",
                userPrompt, "CamelLangChain4jAgentSystemMessage", systemPrompt , String.class);
----

[NOTE]
====
There's no need to add Camel LangChain4j Tools component as a dependency when using the tools with LangChain4j Agent component.
====

=== Custom LangChain4j Tools

You can also add custom LangChain4j tools using the `@Tool` annotation. These tools are passed directly to the agent via the `customTools` configuration parameter.

==== Creating Custom LangChain4j Tools

Create a class with methods annotated with `@Tool`:

[source,java]
----
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class CalculatorTool {
    
    @Tool("Adds two numbers")
    public int add(@P("First number") int a, @P("Second number") int b) {
        return a + b;
    }
    
    @Tool("Multiplies two numbers")
    public int multiply(@P("First number") int a, @P("Second number") int b) {
        return a * b;
    }
    
    @Tool("Gets the square root of a number")
    public double sqrt(@P("Number") double x) {
        return Math.sqrt(x);
    }
}
----

==== Using Custom LangChain4j Tools with Agent

Pass your custom tool instances to the agent configuration:

[source,java]
----
// Create tool instances
CalculatorTool calculator = new CalculatorTool();
WeatherTool weather = new WeatherTool();

// Create agent configuration with custom tools
AgentConfiguration config = new AgentConfiguration()
    .withChatModel(chatModel)
    .withCustomTools(Arrays.asList(calculator, weather));

// Create agent
Agent agent = new AgentWithoutMemory(config);

// Register agent in Camel context
context.getRegistry().bind("customToolsAgent", agent);
----

==== Route Configuration

Use the agent with custom tools in your routes:

[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:assistant?agent=#customToolsAgent")
    .to("mock:agent-response");
----

==== Usage Example

[source,java]
----
String response = template.requestBody("direct:chat", 
    "Calculate 10 * 5 and tell me the weather in Paris", String.class);
----

[NOTE]
====
Custom LangChain4j tools are executed directly by the LangChain4j framework. No additional configuration or tool Executor is needed for tool execution.
====

=== Mixed Tools (Camel Routes + Custom LangChain4j Tools)

You can combine both Camel route tools (via `tags`) and custom LangChain4j tools (via `customTools`) in the same agent:

[source,java]
----
// Define Camel route tools
from("langchain4j-tools:weatherService?tags=weather&description=Get current weather information&parameter.location=string")
    .setBody(constant("{\"weather\": \"sunny\", \"location\": \"Current Location\"}"));

// Create custom tool instances
CalculatorTool calculator = new CalculatorTool();
StringTool stringTool = new StringTool();

// Create agent configuration with both types of tools
AgentConfiguration config = new AgentConfiguration()
    .withChatModel(chatModel)
    .withCustomTools(Arrays.asList(calculator, stringTool));

// Create agent
Agent agent = new AgentWithMemory(config);

// Register agent in Camel context
context.getRegistry().bind("mixedToolsAgent", agent);
----

==== Route Configuration with Mixed Tools

[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:assistant?agent=#mixedToolsAgent&tags=weather")
    .to("mock:agent-response");
----

==== Usage Example

[source,java]
----
String response = template.requestBody("direct:chat", 
    "Calculate 10 * 5 and tell me the weather in London", String.class);
----

[NOTE]
====
When using mixed tools, Camel route tools are discovered dynamically via the `tags` parameter, while custom LangChain4j tools are provided statically via the `customTools` configuration.
====

=== MCP (Model Context Protocol) Tools

The LangChain4j Agent component supports integration with MCP (Model Context Protocol) tools, allowing agents to interact with external systems and services through standardized protocols.

==== Configuring MCP Clients

To use MCP tools with your agent, you need to configure MCP clients in your `AgentConfiguration`:

[source,java]
----
// Create MCP transport for filesystem server
McpTransport filesystemTransport = new StdioMcpTransport.Builder()
    .command(Arrays.asList("npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/directory"))
    .logEvents(true)
    .build();

// Create MCP client
McpClient filesystemClient = new DefaultMcpClient.Builder()
    .transport(filesystemTransport)
    .build();

// Create agent configuration with MCP clients
AgentConfiguration config = new AgentConfiguration()
    .withChatModel(chatModel)
    .withMcpClients(Arrays.asList(filesystemClient));

// Create agent
Agent agent = new AgentWithoutMemory(config);
----

==== MCP Tool Filtering

You can apply filters to control which MCP tools are available to your agent:

[source,java]
----
// Create security filter to only allow read operations
BiPredicate<McpClient, ToolSpecification> securityFilter = (client, toolSpec) -> {
    String toolName = toolSpec.name().toLowerCase();
    // Only allow read operations for safety
    return toolName.contains("read") || toolName.contains("list") || toolName.contains("get");
};

AgentConfiguration config = new AgentConfiguration()
    .withChatModel(chatModel)
    .withMcpClients(Arrays.asList(filesystemClient))
    .withMcpToolProviderFilter(securityFilter);
----

==== Complete MCP Example

Here's a complete example showing how to use MCP tools with an agent:

[source,java]
----
// Create MCP client for filesystem access
McpTransport transport = new StdioMcpTransport.Builder()
    .command(Arrays.asList("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"))
    .logEvents(true)
    .build();

McpClient mcpClient = new DefaultMcpClient.Builder()
    .transport(transport)
    .build();

// Create agent with MCP tools
AgentConfiguration config = new AgentConfiguration()
    .withChatModel(chatModel)
    .withMcpClient(mcpClient);

Agent agent = new AgentWithoutMemory(config);

// Register agent in Camel context
context.getRegistry().bind("mcpAgent", agent);
----

==== Route Configuration with MCP Tools

[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:assistant?agent=#mcpAgent")
    .to("mock:agent-response");
----

==== Usage Example with MCP Tools

[source,java]
----
String response = template.requestBody("direct:chat",
    "Can you list the files in the current directory and read the content of file.txt?", String.class);
----

==== Endpoint-Level MCP Configuration

MCP servers can also be configured directly on the endpoint, without setting them on the `AgentConfiguration`. This is useful for declarative route definitions and allows MCP tools to coexist with Camel route tools (via `tags`).

===== Bean References

Register pre-built `McpClient` beans and reference them via the `mcpClients` endpoint parameter:

[source,java]
----
// Register MCP clients as beans
McpClient timeClient = new DefaultMcpClient.Builder()
    .key("time")
    .transport(new StdioMcpTransport.Builder()
        .command(Arrays.asList("docker", "run", "-i", "--rm", "mcp/time"))
        .build())
    .build();
context.getRegistry().bind("timeMcpClient", timeClient);

// Reference in endpoint URI
from("direct:chat")
    .to("langchain4j-agent:assistant?agent=#myAgent&tags=users&mcpClients=#timeMcpClient")
    .to("mock:response");
----

===== Inline URI Configuration

MCP servers can be defined inline on the endpoint URI using the `mcpServer.<name>.<property>` pattern. The component creates and manages the MCP client lifecycle automatically.

Supported properties:

[cols="1,1,3"]
|===
| Property | Default | Description

| `transportType` | `stdio` | Transport type: `stdio`, `http` or `streamableHttp` (Streamable HTTP), or `sse` (legacy SSE)
| `command` | | Comma-separated command for stdio transport (e.g., `docker,run,-i,--rm,mcp/time`)
| `url` | | Server URL for `http` or `sse` transport
| `environment.<key>` | | Environment variables for stdio transport
| `timeout` | `60` | Timeout in seconds
| `logRequests` | `false` | Log requests sent to the MCP server
| `logResponses` | `false` | Log responses from the MCP server
| `oauthProfile` | | OAuth profile name for HTTP transport auth (Client Credentials grant, requires `camel-oauth`)
|===

Example with two MCP servers configured inline:

[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:assistant?agent=#myAgent&tags=users"
        + "&mcpServer.everything.transportType=http"
        + "&mcpServer.everything.url=http://localhost:3001/mcp"
        + "&mcpServer.time.transportType=stdio"
        + "&mcpServer.time.command=docker,run,-i,--rm,mcp/time")
    .to("mock:response");
----

Both `mcpClients` (bean references) and `mcpServer` (inline) can be used together on the same endpoint. All tool sources -- Camel route tools, endpoint-level MCP tools, and agent-level MCP tools -- are automatically composed into a single tool provider.

==== Dynamic Tool Exclusion via Headers

Tools can be dynamically excluded on a per-message basis using Camel headers. This allows routes to control which tools are available based on business logic, user roles, or message content.

[cols="1,3"]
|===
| Header | Description

| `CamelLangChain4jAgentExcludeTags` | Comma-separated list of Camel tool tags to exclude
| `CamelLangChain4jAgentExcludeMcpServers` | Comma-separated list of MCP server names (keys) to exclude
|===

Example -- exclude specific tools based on a condition:

[source,java]
----
from("direct:chat")
    .choice()
        .when(header("userRole").isEqualTo("readonly"))
            // Exclude write-capable tools for read-only users
            .setHeader("CamelLangChain4jAgentExcludeTags", constant("admin-tools"))
            .setHeader("CamelLangChain4jAgentExcludeMcpServers", constant("filesystem"))
    .end()
    .to("langchain4j-agent:assistant?agent=#myAgent&tags=users,admin-tools"
        + "&mcpClients=#fsMcpClient,#timeMcpClient")
    .to("mock:response");
----

MCP servers are matched by their key, which is set via `DefaultMcpClient.Builder.key(name)` or automatically from the server name in inline `mcpServer.<name>` configuration. MCP clients without a key are never excluded.

=== RAG Integration

RAG (Retrieval-Augmented Generation) is supported by configuring a `RetrievalAugmentor` in the `AgentConfiguration`. Create an agent with RAG capabilities:

[source,java]
----
// Create the retrieval augmentor (shown below)
RetrievalAugmentor retrievalAugmentor = createRetrievalAugmentor();

// Create agent configuration with RAG
AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel)
    .withRetrievalAugmentor(retrievalAugmentor)
    .withInputGuardrailClasses(List.of())
    .withOutputGuardrailClasses(List.of());

Agent ragAgent = new AgentWithoutMemory(configuration);
context.getRegistry().bind("ragAgent", ragAgent);

// Use the RAG agent
from("direct:chat")
    .to("langchain4j-agent:rag?agent=#ragAgent")
----

.Usage example with Retrieval Augmentor serving as naive RAG :
[source, java]
----
// creating the retrieval Augmentor
EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore) // the embedding store should be defined
                .embeddingModel(embeddingModel) // the embedding model should be defined
                .maxResults(3)
                .minScore(0.6)
                .build();

RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
                .contentRetriever(contentRetriever)
                // other options or steps can be included for Advanced RAG
                .build();

// bind the retrievalAugmentor in the context
context.getRegistry().bind("retrievalAugmentor", retrievalAugmentor);

// using the producer
String response = template.requestBody("direct:chat", body, String.class);
----

=== Chat with Memory

Memory functionality is supported by using `AgentWithMemory` and configuring a `ChatMemoryProvider` in the `AgentConfiguration`. The memory works for multiple users/sessions.

[NOTE]
====
The component requires using a Chat Memory Provider that uses a https://docs.langchain4j.dev/tutorials/chat-memory#persistence[persistent memory store].
====

The memory works for multiple users/sessions. For each context window, the users needs to set the memory ID:
- By setting the Header CamelLangChain4jAgentMemoryId. This supposes that user is using a body as String.
- By setting the AiAgentBody.memoryId field. This supposes that that user is using a body as AiAgentBody.

.Example of Route with Memory Agent
[source,java]
----
from("direct:chat")
    .to("langchain4j-agent:memory?agent=#memoryAgent")
----

.Example of usage with AiAgentBody
[source,java]
----
// Example of creating a Chat Memory Provider : Create a message window memory that keeps the last 10 messages
        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(10)
                .chatMemoryStore(store) // the Chat Memory store is previously created
                .build();

// bind the chat memory provider in the context
context.getRegistry().bind("chatMemoryProvider", chatMemoryProvider);


AiAgentBody request = new AiAgentBody("Hello!", null, "session-123");
String response = template.requestBody("direct:chat", request, String.class);
----


=== Input and Output Guardrails

Guardrails validate and filter AI interactions to ensure security and quality. The LangChain4j Agent component provides both custom guardrail support and a set of production-ready built-in guardrails.

==== Using Custom Guardrails

Create classes defining InputGuardrails and OutputGuardrails as defined in the https://docs.langchain4j.dev/tutorials/guardrails[LangChain4j Guardrails documentation] page.

[source,java]
----
// Create agent configuration with custom guardrails
AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel)
    .withInputGuardrailClassesList("com.example.MyInputGuardrail")
    .withOutputGuardrailClassesList("com.example.MyOutputGuardrail1,com.example.MyOutputGuardrail2");

Agent safeAgent = new AgentWithoutMemory(configuration);
context.getRegistry().bind("safeAgent", safeAgent);

// Use the agent with guardrails
from("direct:agent-with-guardrails")
    .to("langchain4j-agent:safe?agent=#safeAgent")
----

[NOTE]
====
The current version of the component returns a String as response. If the outputGuardrails extends JsonExtractorOutputGuardrail class, make sure to return a Json in String format.
====

==== Built-in Guardrails

The `camel-langchain4j-agent-api` module provides production-ready guardrails in the `org.apache.camel.component.langchain4j.agent.api.guardrails` package.

===== Available Input Guardrails

[cols="1,2"]
|===
|Guardrail Class |Description

|`InputLengthGuardrail`
|Validates message length (min/max characters). Prevents excessively long inputs.

|`PiiDetectorGuardrail`
|Detects Personally Identifiable Information (email, phone, SSN, credit cards, IP addresses).

|`PromptInjectionGuardrail`
|Detects prompt injection attacks (role manipulation, jailbreaks, delimiter injection).

|`KeywordFilterGuardrail`
|Blocks messages containing specific keywords or patterns.

|`LanguageGuardrail`
|Validates the language/script of user messages. Can allow or block specific languages (English, Cyrillic, Chinese, Japanese, Korean, Arabic, Hebrew, Greek, Thai, Devanagari).

|`CodeInjectionGuardrail`
|Detects potential code injection attempts including shell commands, SQL injection, JavaScript, HTML/XSS, path traversal, command chaining, and template injection.

|`RegexPatternGuardrail`
|Flexible guardrail using custom regex patterns. Can define deny patterns (block if matched) and require patterns (must be present).
|===

===== Available Output Guardrails

[cols="1,2"]
|===
|Guardrail Class |Description

|`OutputLengthGuardrail`
|Validates response length with optional truncation support.

|`SensitiveDataOutputGuardrail`
|Detects and optionally redacts sensitive data (API keys, passwords, JWT tokens, AWS keys).

|`JsonFormatGuardrail`
|Validates JSON format, extracts JSON from text, and checks for required fields.

|`KeywordOutputFilterGuardrail`
|Blocks or redacts specific content in AI responses.

|`NotEmptyGuardrail`
|Ensures AI responses are not empty or contain only whitespace. Can optionally detect refusal patterns (e.g., "I cannot", "I'm unable to").

|`WordCountGuardrail`
|Validates the word count of AI responses. Can enforce minimum, maximum, or range constraints.
|===

==== Quick Start with Default Guardrails

The `Guardrails` helper class provides convenient factory methods:

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.Guardrails;

// Create agent with default security guardrails
AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel)
    .withInputGuardrailClasses(Guardrails.defaultInputGuardrails())
    .withOutputGuardrailClasses(Guardrails.defaultOutputGuardrails());

Agent secureAgent = new AgentWithoutMemory(configuration);
context.getRegistry().bind("secureAgent", secureAgent);

from("direct:secure-chat")
    .to("langchain4j-agent:chat?agent=#secureAgent");
----

The default guardrails include:

* **Input**: `InputLengthGuardrail`, `PiiDetectorGuardrail`, `PromptInjectionGuardrail`
* **Output**: `OutputLengthGuardrail`, `SensitiveDataOutputGuardrail`

==== Using the Fluent Builder

The `Guardrails.configure()` method provides a fluent API for building configurations:

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.Guardrails;

AgentConfiguration configuration = Guardrails.configure()
    .withPiiDetection()
    .withPromptInjectionDetection()
    .withInputLengthValidation()
    .withSensitiveDataDetection()
    .withJsonFormatValidation()
    .build();

configuration.withChatModel(chatModel);

Agent agent = new AgentWithoutMemory(configuration);
----

==== Using Specific Guardrail Classes

You can specify exactly which guardrails to use:

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.*;

AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel)
    .withInputGuardrailClasses(List.of(
        InputLengthGuardrail.class,
        PiiDetectorGuardrail.class,
        PromptInjectionGuardrail.class
    ))
    .withOutputGuardrailClasses(List.of(
        SensitiveDataOutputGuardrail.class,
        JsonFormatGuardrail.class
    ));
----

==== Guardrail Presets

The `Guardrails` class provides preset configurations for different security levels:

[source,java]
----
// Minimal protection (just length validation)
configuration.withInputGuardrailClasses(Guardrails.minimalInputGuardrails());

// Standard protection (length, PII, prompt injection)
configuration.withInputGuardrailClasses(Guardrails.defaultInputGuardrails());

// Strict protection (all input guardrails)
configuration.withInputGuardrailClasses(Guardrails.strictInputGuardrails());
----

==== Factory Methods for Individual Guardrails

The `Guardrails` class provides static factory methods to create guardrail instances:

[source,java]
----
// Input guardrails
InputLengthGuardrail lengthGuard = Guardrails.inputLength(5000);  // Max 5000 chars
PiiDetectorGuardrail piiGuard = Guardrails.piiDetector();
PromptInjectionGuardrail injectionGuard = Guardrails.promptInjectionStrict();
KeywordFilterGuardrail keywordGuard = Guardrails.keywordFilter("banned", "blocked");

// Output guardrails
OutputLengthGuardrail outputLength = Guardrails.outputLengthTruncating(10000);
SensitiveDataOutputGuardrail sensitiveData = Guardrails.sensitiveDataRedacting();
JsonFormatGuardrail jsonGuard = Guardrails.jsonFormatWithFields("name", "email");
----

==== Advanced Guardrail Configuration with Builders

Each guardrail provides a builder for custom configuration:

===== PII Detection Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.PiiDetectorGuardrail;
import org.apache.camel.component.langchain4j.agent.api.guardrails.PiiDetectorGuardrail.PiiType;

// Detect only specific PII types
PiiDetectorGuardrail guardrail = PiiDetectorGuardrail.builder()
    .detectTypes(PiiType.EMAIL, PiiType.PHONE, PiiType.SSN)
    .blockOnDetection(true)
    .build();
----

===== Prompt Injection Detection Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.PromptInjectionGuardrail;

// Strict mode: fail on any single pattern match
PromptInjectionGuardrail strictGuardrail = PromptInjectionGuardrail.strict();

// Custom patterns
PromptInjectionGuardrail customGuardrail = PromptInjectionGuardrail.builder()
    .strict(true)
    .addPattern(
        PromptInjectionGuardrail.InjectionCategory.JAILBREAK,
        Pattern.compile("(?i)custom\\s+attack\\s+pattern"))
    .build();
----

===== Sensitive Data Output Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.SensitiveDataOutputGuardrail;
import org.apache.camel.component.langchain4j.agent.api.guardrails.SensitiveDataOutputGuardrail.*;

// Redact sensitive data instead of blocking
SensitiveDataOutputGuardrail guardrail = SensitiveDataOutputGuardrail.builder()
    .detectTypes(SensitiveDataType.API_KEY, SensitiveDataType.SECRET, SensitiveDataType.JWT)
    .action(Action.REDACT)
    .redactionText("***REDACTED***")
    .build();
----

===== JSON Format Validation Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.JsonFormatGuardrail;

// Validate JSON with required fields
JsonFormatGuardrail guardrail = JsonFormatGuardrail.builder()
    .requireFields("id", "name", "email")
    .extractJson(true)      // Extract JSON from surrounding text
    .allowArray(false)      // Only allow objects, not arrays
    .build();
----

===== Keyword Filtering Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.KeywordFilterGuardrail;
import org.apache.camel.component.langchain4j.agent.api.guardrails.KeywordOutputFilterGuardrail;

// Input keyword filter
KeywordFilterGuardrail inputFilter = KeywordFilterGuardrail.builder()
    .blockedWords("spam", "inappropriate", "banned")
    .blockedPattern("\\b(hack|exploit)\\b")
    .caseSensitive(false)
    .wholeWordMatch(true)
    .build();

// Output keyword filter with redaction
KeywordOutputFilterGuardrail outputFilter = KeywordOutputFilterGuardrail.builder()
    .blockedWords("confidential", "internal-only")
    .action(KeywordOutputFilterGuardrail.Action.REDACT)
    .redactionText("[REMOVED]")
    .build();
----

===== Language Validation Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.LanguageGuardrail;
import org.apache.camel.component.langchain4j.agent.api.guardrails.LanguageGuardrail.Language;

// Allow only English input
LanguageGuardrail englishOnly = LanguageGuardrail.allowOnly(Language.ENGLISH);

// Allow English and Latin script languages (Spanish, French, German, etc.)
LanguageGuardrail latinLanguages = LanguageGuardrail.allowOnly(
    Language.ENGLISH, Language.LATIN_SCRIPT);

// Block specific languages
LanguageGuardrail blockCyrillic = LanguageGuardrail.block(Language.CYRILLIC);

// Custom configuration with mixed content control
LanguageGuardrail customLanguage = LanguageGuardrail.builder()
    .allowedLanguages(Language.ENGLISH, Language.LATIN_SCRIPT)
    .blockedLanguages(Language.CYRILLIC)
    .allowMixed(false)  // Don't allow mixed language content
    .build();
----

===== Code Injection Detection Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.CodeInjectionGuardrail;
import org.apache.camel.component.langchain4j.agent.api.guardrails.CodeInjectionGuardrail.InjectionType;

// Default: detect all code injection types
CodeInjectionGuardrail defaultGuard = new CodeInjectionGuardrail();

// Strict mode: fail on any single pattern match
CodeInjectionGuardrail strictGuard = CodeInjectionGuardrail.strict();

// Detect only specific injection types
CodeInjectionGuardrail sqlAndShellOnly = CodeInjectionGuardrail.forTypes(
    InjectionType.SQL_INJECTION, InjectionType.SHELL_COMMAND);

// Custom configuration
CodeInjectionGuardrail customGuard = CodeInjectionGuardrail.builder()
    .detectTypes(InjectionType.SQL_INJECTION, InjectionType.JAVASCRIPT, InjectionType.PATH_TRAVERSAL)
    .strict(true)
    .build();
----

===== Regex Pattern Guardrail Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.RegexPatternGuardrail;

// Block messages containing URLs
RegexPatternGuardrail noUrls = RegexPatternGuardrail.blocking(
    "https?://[^\\s]+", "URLs are not allowed in messages");

// Require messages to contain a ticket number
RegexPatternGuardrail requireTicket = RegexPatternGuardrail.requiring(
    "TICKET-\\d+", "Please include a ticket number (e.g., TICKET-123)");

// Complex configuration with multiple patterns
RegexPatternGuardrail customPatterns = RegexPatternGuardrail.builder()
    .denyPattern("https?://[^\\s]+", "URLs are not allowed")
    .denyPattern("\\b(password|secret)\\b", "Sensitive keywords are not allowed")
    .requirePattern("[A-Z]{2,4}-\\d+", "Please include a valid issue ID")
    .failOnFirstMatch(true)  // Stop checking after first failure
    .build();
----

===== Not Empty Guardrail Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.NotEmptyGuardrail;

// Default: just ensure response is not empty
NotEmptyGuardrail notEmpty = new NotEmptyGuardrail();

// Also detect refusal patterns like "I cannot", "I'm unable to"
NotEmptyGuardrail withRefusalDetection = NotEmptyGuardrail.withRefusalDetection();

// Require minimum meaningful length
NotEmptyGuardrail minLength = NotEmptyGuardrail.withMinLength(50);

// Custom configuration
NotEmptyGuardrail customNotEmpty = new NotEmptyGuardrail(
    true,   // detectRefusals
    100     // minMeaningfulLength
);
----

===== Word Count Guardrail Configuration

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.guardrails.WordCountGuardrail;

// Require at least 10 words
WordCountGuardrail atLeast10 = WordCountGuardrail.atLeast(10);

// Limit to maximum 500 words
WordCountGuardrail atMost500 = WordCountGuardrail.atMost(500);

// Require between 50 and 200 words
WordCountGuardrail between = WordCountGuardrail.between(50, 200);

// Custom configuration
WordCountGuardrail custom = WordCountGuardrail.builder()
    .minWords(20)
    .maxWords(1000)
    .build();
----

==== Complete Example with Memory and Guardrails

[source,java]
----
import org.apache.camel.component.langchain4j.agent.api.*;
import org.apache.camel.component.langchain4j.agent.api.guardrails.*;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;

public class SecureAgentRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        // Create chat model
        ChatModel chatModel = OpenAiChatModel.builder()
            .apiKey("{{openai.api.key}}")
            .modelName("gpt-4o")
            .build();

        // Create memory provider
        ChatMemoryProvider memoryProvider = memoryId ->
            MessageWindowChatMemory.withMaxMessages(20);

        // Create secure agent configuration
        AgentConfiguration configuration = new AgentConfiguration()
            .withChatModel(chatModel)
            .withChatMemoryProvider(memoryProvider)
            .withInputGuardrailClasses(Guardrails.defaultInputGuardrails())
            .withOutputGuardrailClasses(Guardrails.defaultOutputGuardrails());

        Agent secureAgent = new AgentWithMemory(configuration);
        getContext().getRegistry().bind("secureAgent", secureAgent);

        // Secure chat route with error handling
        from("direct:chat")
            .setHeader("CamelLangChain4jAgentMemoryId", simple("${header.userId}"))
            .doTry()
                .to("langchain4j-agent:chat?agent=#secureAgent")
            .doCatch(dev.langchain4j.service.guardrail.GuardrailException.class)
                .log("Guardrail blocked request: ${exception.message}")
                .setBody(constant("Your message was blocked due to security policies."))
            .end();
    }
}
----

==== Spring Boot Configuration Example

===== Using LangChain4j Spring Boot Starters (Recommended)

When using Camel with Spring Boot, leverage LangChain4j's Spring Boot starters for automatic configuration of the ChatModel.

Add the dependency for LangChain4j OpenAI Spring Boot starter:

.pom.xml
[source,xml]
----
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    <version>1.10.0</version>
    <!-- use the same version as your LangChain4j version -->
</dependency>
----

Configure the OpenAI Chat Model in `application.properties`:

.application.properties
[source,properties]
----
langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.chat-model.model-name=gpt-4o
langchain4j.open-ai.chat-model.temperature=0.7
----

Create the Agent bean using the auto-configured ChatLanguageModel:

[source,java]
----
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.apache.camel.component.langchain4j.agent.api.Agent;
import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration;
import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory;
import org.apache.camel.component.langchain4j.agent.api.guardrails.Guardrails;

@Configuration
public class AgentConfig {

    @Bean("secureAgent")
    public Agent secureAgent(ChatLanguageModel chatLanguageModel) {
        AgentConfiguration config = new AgentConfiguration()
            .withChatModel(chatLanguageModel)
            .withInputGuardrailClasses(Guardrails.defaultInputGuardrails())
            .withOutputGuardrailClasses(Guardrails.defaultOutputGuardrails());

        return new AgentWithoutMemory(config);
    }
}
----

[NOTE]
====
The `ChatLanguageModel` bean is automatically configured by the LangChain4j Spring Boot starter and injected into your configuration. You can also use other auto-configured beans such as:

* `StreamingChatLanguageModel` - For streaming responses
* `EmbeddingModel` - For generating embeddings
* `ImageModel` - For image generation
* `ModerationModel` - For content moderation

Refer to the https://docs.langchain4j.dev/tutorials/spring-boot-integration[LangChain4j Spring Boot Integration documentation] for complete configuration options.
====

===== Manual Configuration (Alternative)

Alternatively, you can manually configure the ChatModel bean:

[source,java]
----
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AgentConfig {

    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4o")
            .build();
    }

    @Bean("secureAgent")
    public Agent secureAgent(ChatModel chatModel) {
        AgentConfiguration config = new AgentConfiguration()
            .withChatModel(chatModel)
            .withInputGuardrailClasses(Guardrails.defaultInputGuardrails())
            .withOutputGuardrailClasses(Guardrails.defaultOutputGuardrails());

        return new AgentWithoutMemory(config);
    }
}
----

==== Guardrail Behavior Summary

[cols="1,1,2"]
|===
|Guardrail |When Triggered |Result

|`InputLengthGuardrail`
|Input too short/long
|Blocks the request

|`PiiDetectorGuardrail`
|PII detected
|Blocks the request

|`PromptInjectionGuardrail`
|Injection attempt detected
|Blocks the request

|`KeywordFilterGuardrail`
|Blocked keyword found
|Blocks the request

|`LanguageGuardrail`
|Disallowed language/script detected
|Blocks the request

|`CodeInjectionGuardrail`
|Code injection pattern detected
|Blocks the request

|`RegexPatternGuardrail`
|Deny pattern matched or require pattern missing
|Blocks the request

|`OutputLengthGuardrail`
|Response too short/long
|Retries LLM or truncates

|`SensitiveDataOutputGuardrail`
|Secrets in response
|Blocks or redacts

|`JsonFormatGuardrail`
|Invalid JSON format
|Retries LLM call

|`KeywordOutputFilterGuardrail`
|Blocked content in response
|Blocks or redacts

|`NotEmptyGuardrail`
|Empty response or refusal detected
|Retries LLM call

|`WordCountGuardrail`
|Word count outside allowed range
|Retries LLM call
|===

=== Structured Outputs with JSON Schema

The LangChain4j Agent component supports structured outputs by configuring the `ChatModel` with a `ResponseFormat` that enforces a JSON schema. This feature is available for providers that support structured outputs including Amazon Bedrock, Azure OpenAI, Google AI Gemini, Mistral, Ollama, and OpenAI.

IMPORTANT: The `ResponseFormat` must be configured on the `ChatModel` itself before passing it to the `AgentConfiguration`. The LangChain4j `AiServices` builder does not support setting response format directly.

==== Configuring Structured Outputs

To use structured outputs with the agent component:

1. Define your JSON schema
2. Create a `ResponseFormat` with the schema
3. Configure your `ChatModel` with the `ResponseFormat`
4. Pass the configured model to `AgentConfiguration`

===== Complete Example with OpenAI

[source,java]
----
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.ResponseFormat;
import dev.langchain4j.model.chat.request.json.JsonRawSchema;
import dev.langchain4j.model.chat.request.json.JsonSchema;
import dev.langchain4j.model.openai.OpenAiChatModel;
import static dev.langchain4j.model.chat.request.ResponseFormatType.JSON;

// Define JSON schema
String personSchema = """
    {
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "The person's full name"},
            "age": {"type": "integer", "description": "The person's age in years"},
            "occupation": {"type": "string", "description": "The person's job"}
        },
        "required": ["name", "age", "occupation"],
        "additionalProperties": false
    }
    """;

// Create ResponseFormat with JSON Schema
JsonRawSchema jsonRawSchema = JsonRawSchema.from(personSchema);
ResponseFormat responseFormat = ResponseFormat.builder()
        .type(JSON)
        .jsonSchema(JsonSchema.builder()
                .name("person_schema")
                .rootElement(jsonRawSchema)
                .build())
        .build();

// Configure ChatModel with ResponseFormat
ChatModel chatModel = OpenAiChatModel.builder()
        .apiKey(openAiApiKey)
        .modelName("gpt-4o-mini")
        .responseFormat(responseFormat)  // Apply structured output here
        .build();

// Create agent with the configured model
AgentConfiguration configuration = new AgentConfiguration()
        .withChatModel(chatModel);

Agent agent = new AgentWithoutMemory(configuration);
context.getRegistry().bind("structuredAgent", agent);

// Use in route
from("direct:extract-person-info")
    .setBody(constant("Extract information about John Smith, a 35-year-old software engineer"))
    .to("langchain4j-agent:structured?agent=#structuredAgent")
    .unmarshal().json()  // Parse the JSON response
    .log("Extracted person: ${body}");
----

[NOTE]
====
* The LLM response will be guaranteed to conform to the provided schema
* No prompt engineering is needed for output formatting
* The same schema file can be shared across `camel-openai` and `camel-langchain4j-agent` components
====

=== Multimodal Content Support

The LangChain4j Agent component supports multimodal content, allowing you to send images, PDFs, audio, video, and text files to AI models that support vision and document understanding capabilities.

==== Sending Multimodal Content via AiAgentBody

You can explicitly create an `AiAgentBody` with multimodal content:

[source,java]
----
// Load an image and create ImageContent
byte[] imageBytes = Files.readAllBytes(Path.of("image.png"));
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
Image image = Image.builder()
    .base64Data(base64Image)
    .mimeType("image/png")
    .build();
ImageContent imageContent = ImageContent.from(image);

// Create request body with image content
AiAgentBody<ImageContent> body = new AiAgentBody<ImageContent>()
    .withUserMessage("What do you see in this image?")
    .withContent(imageContent);

String response = template.requestBody("direct:chat", body, String.class);
----

==== Automatic File Conversion from Camel Components

The agent component automatically converts files from various Camel components to multimodal content. This enables seamless integration with file-based sources.

===== Supported Input Types

[cols="1,2,2"]
|===
|Input Type |Source Components |MIME Type Detection

|`WrappedFile`
|`file:`, `ftp:`, `sftp:`, `smb:`
|From file extension or headers

|`byte[]`
|`aws2-s3:`, `azure-storage-blob:`, `google-storage:`
|From content type headers (required)

|`InputStream`
|Various streaming components
|From content type headers (required)
|===

===== Example: Processing Images from File Component

[source,java]
----
from("file:inbox/images?noop=true&include=.*\\.png")
    .setHeader("CamelLangChain4jAgentUserMessage", constant("Describe this image"))
    .to("langchain4j-agent:vision?agent=#visionAgent")
    .to("log:response");
----

===== Example: Processing Files from AWS S3

[source,java]
----
from("aws2-s3://my-bucket?prefix=images/&includeBody=true")
    .setHeader("CamelLangChain4jAgentUserMessage", constant("What do you see in this image?"))
    .to("langchain4j-agent:vision?agent=#visionAgent")
    .to("log:response");
----

[NOTE]
====
When using `byte[]` or `InputStream` inputs, a MIME type header is required since the type cannot be auto-detected from the content. The component checks for MIME type in this priority order:

1. `CamelLangChain4jAgentMediaType` header (highest priority - explicit override)
2. `CamelAwsS3ContentType` header (from AWS S3)
3. `Content-Type` header
4. `CamelFileContentType` header (from file components)
====

===== Example: Overriding MIME Type

[source,java]
----
from("direct:process-file")
    .setHeader("CamelLangChain4jAgentUserMessage", constant("Analyze this document"))
    .setHeader("CamelLangChain4jAgentMediaType", constant("application/pdf"))
    .to("langchain4j-agent:analyzer?agent=#analyzerAgent");
----

==== Complete Multimodal Route Example

Here's a complete example showing how to process images from a file system and send them to an AI agent for analysis:

[source,java]
----
// Create a vision-capable chat model
ChatModel chatModel = OpenAiChatModel.builder()
    .apiKey(apiKey)
    .modelName("gpt-4o")  // Vision-capable model
    .build();

// Create agent configuration
AgentConfiguration configuration = new AgentConfiguration()
    .withChatModel(chatModel);

Agent visionAgent = new AgentWithoutMemory(configuration);
context.getRegistry().bind("visionAgent", visionAgent);

// Route to process images
from("file:inbox/images?noop=true&include=.*\\.(png|jpg|jpeg)")
    .setHeader("CamelLangChain4jAgentUserMessage",
        constant("Describe what you see in this image. Be detailed but concise."))
    .to("langchain4j-agent:vision?agent=#visionAgent")
    .log("AI Response: ${body}")
    .to("file:outbox/descriptions");
----
