This is an automated email from the ASF dual-hosted git repository.
zbendhiba pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new f58d63b9f4da CAMEL-23642: camel-langchain4j-agent - support jsonSchema
endpoint pr… (#23793)
f58d63b9f4da is described below
commit f58d63b9f4da36c898aa49e766cc597a97d24c8d
Author: Zineb BENDHIBA <[email protected]>
AuthorDate: Mon Jun 8 18:06:03 2026 +0200
CAMEL-23642: camel-langchain4j-agent - support jsonSchema endpoint pr…
(#23793)
* CAMEL-23642: camel-langchain4j-agent - support jsonSchema endpoint
property for structured outputs
Add a jsonSchema URI property to the langchain4j-agent component that loads
a JSON schema (classpath, file, or inline) and applies it as a
ResponseFormat
on the internal AIService. This works only in agentConfiguration mode where
Camel creates the agent internally.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CAMEL-23642: camel-langchain4j-agent - improve error handling for
jsonSchema endpoint property
Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../catalog/components/langchain4j-agent.json | 20 ++--
.../langchain4j/agent/api/AbstractAgent.java | 36 +++++++
.../agent/LangChain4jAgentComponentConfigurer.java | 6 ++
.../LangChain4jAgentConfigurationConfigurer.java | 6 ++
.../agent/LangChain4jAgentEndpointConfigurer.java | 6 ++
.../agent/LangChain4jAgentEndpointUriFactory.java | 3 +-
.../langchain4j/agent/langchain4j-agent.json | 20 ++--
.../src/main/docs/langchain4j-agent-component.adoc | 90 +++++++-----------
.../agent/LangChain4jAgentConfiguration.java | 19 ++++
.../agent/LangChain4jAgentProducer.java | 75 +++++++++++++++
.../LangChain4jAgentStructuredOutputIT.java | 94 +++++++++++++------
.../LangChain4jAgentStructuredOutputTest.java | 104 +++++++++++++++++++++
.../Langchain4jAgentComponentBuilderFactory.java | 20 ++++
.../LangChain4jAgentEndpointBuilderFactory.java | 18 ++++
14 files changed, 418 insertions(+), 99 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
index fb06b6ebf076..d9d41b08123e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
@@ -28,11 +28,12 @@
"agentConfiguration": { "index": 1, "kind": "property", "displayName":
"Agent Configuration", "group": "producer", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentConfiguration",
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Agent [...]
"agentFactory": { "index": 2, "kind": "property", "displayName": "Agent
Factory", "group": "producer", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentFactory", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "The agent factory to us
[...]
"configuration": { "index": 3, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
configuration" },
- "lazyStartProducer": { "index": 4, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
- "tags": { "index": 5, "kind": "property", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
- "autowiredEnabled": { "index": 6, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
- "mcpClients": { "index": 7, "kind": "property", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client insta [...]
- "mcpServer": { "index": 8, "kind": "property", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
+ "jsonSchema": { "index": 4, "kind": "property", "displayName": "Json
Schema", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "supportFileReference": true,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "JSON schema for
structured output validat [...]
+ "lazyStartProducer": { "index": 5, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
+ "tags": { "index": 6, "kind": "property", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
+ "autowiredEnabled": { "index": 7, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "mcpClients": { "index": 8, "kind": "property", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client insta [...]
+ "mcpServer": { "index": 9, "kind": "property", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
},
"headers": {
"CamelLangChain4jAgentSystemMessage": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The system prompt.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#SYSTEM_MESSAGE" },
@@ -47,9 +48,10 @@
"agent": { "index": 1, "kind": "parameter", "displayName": "Agent",
"group": "producer", "label": "", "required": false, "type": "object",
"javaType": "org.apache.camel.component.langchain4j.agent.api.Agent",
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "The agent to use for the
component" },
"agentConfiguration": { "index": 2, "kind": "parameter", "displayName":
"Agent Configuration", "group": "producer", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentConfiguration",
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Agen [...]
"agentFactory": { "index": 3, "kind": "parameter", "displayName": "Agent
Factory", "group": "producer", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentFactory", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "The agent factory to u
[...]
- "tags": { "index": 4, "kind": "parameter", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
- "lazyStartProducer": { "index": 5, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
- "mcpClients": { "index": 6, "kind": "parameter", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client inst [...]
- "mcpServer": { "index": 7, "kind": "parameter", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
+ "jsonSchema": { "index": 4, "kind": "parameter", "displayName": "Json
Schema", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "supportFileReference": true,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "JSON schema for
structured output valida [...]
+ "tags": { "index": 5, "kind": "parameter", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
+ "lazyStartProducer": { "index": 6, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "mcpClients": { "index": 7, "kind": "parameter", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client inst [...]
+ "mcpServer": { "index": 8, "kind": "parameter", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
}
}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
index 4334011cab94..657fca943bb4 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
@@ -20,6 +20,9 @@ import java.util.ArrayList;
import java.util.List;
import dev.langchain4j.mcp.McpToolProvider;
+import dev.langchain4j.model.chat.request.ChatRequestParameters;
+import dev.langchain4j.model.chat.request.DefaultChatRequestParameters;
+import dev.langchain4j.model.chat.request.ResponseFormat;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;
import org.apache.camel.util.ObjectHelper;
@@ -45,6 +48,7 @@ import org.apache.camel.util.ObjectHelper;
public abstract class AbstractAgent<S> implements Agent {
protected final AgentConfiguration configuration;
+ private ResponseFormat responseFormat;
protected AbstractAgent(AgentConfiguration configuration) {
this.configuration = configuration;
@@ -59,6 +63,24 @@ public abstract class AbstractAgent<S> implements Agent {
return configuration;
}
+ /**
+ * Gets the response format for structured output.
+ *
+ * @return the response format, or {@code null} if not configured
+ */
+ public ResponseFormat getResponseFormat() {
+ return responseFormat;
+ }
+
+ /**
+ * Sets the response format for structured output (JSON schema).
+ *
+ * @param responseFormat the langchain4j response format to apply to AI
service requests
+ */
+ public void setResponseFormat(ResponseFormat responseFormat) {
+ this.responseFormat = responseFormat;
+ }
+
/**
* Configures the common aspects of the AI service builder.
*
@@ -125,5 +147,19 @@ public abstract class AbstractAgent<S> implements Agent {
if (configuration.getOutputGuardrailClasses() != null &&
!configuration.getOutputGuardrailClasses().isEmpty()) {
builder.outputGuardrailClasses((List)
configuration.getOutputGuardrailClasses());
}
+
+ // Response Format (structured output / JSON schema)
+ // Temporary fix: see https://issues.apache.org/jira/browse/CAMEL-23695
+ if (responseFormat != null) {
+ ResponseFormat format = responseFormat;
+ builder.chatRequestTransformer(req -> {
+ ChatRequestParameters existing = req.parameters();
+ ChatRequestParameters withFormat = existing != null
+ ? existing.overrideWith(
+
DefaultChatRequestParameters.builder().responseFormat(format).build())
+ :
DefaultChatRequestParameters.builder().responseFormat(format).build();
+ return req.toBuilder().parameters(withFormat).build();
+ });
+ }
}
}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java
index 0f476d995828..690c48c1d780 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java
@@ -38,6 +38,8 @@ public class LangChain4jAgentComponentConfigurer extends
PropertyConfigurerSuppo
case "autowiredenabled":
case "autowiredEnabled":
target.setAutowiredEnabled(property(camelContext, boolean.class, value));
return true;
case "configuration": target.setConfiguration(property(camelContext,
org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration.class,
value)); return true;
+ case "jsonschema":
+ case "jsonSchema":
getOrCreateConfiguration(target).setJsonSchema(property(camelContext,
java.lang.String.class, value)); return true;
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "mcpclients":
@@ -65,6 +67,8 @@ public class LangChain4jAgentComponentConfigurer extends
PropertyConfigurerSuppo
case "autowiredenabled":
case "autowiredEnabled": return boolean.class;
case "configuration": return
org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration.class;
+ case "jsonschema":
+ case "jsonSchema": return java.lang.String.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
case "mcpclients":
@@ -88,6 +92,8 @@ public class LangChain4jAgentComponentConfigurer extends
PropertyConfigurerSuppo
case "autowiredenabled":
case "autowiredEnabled": return target.isAutowiredEnabled();
case "configuration": return target.getConfiguration();
+ case "jsonschema":
+ case "jsonSchema": return
getOrCreateConfiguration(target).getJsonSchema();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
case "mcpclients":
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java
index 8e4ac8585095..406f70ada266 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java
@@ -28,6 +28,8 @@ public class LangChain4jAgentConfigurationConfigurer extends
org.apache.camel.su
case "agentConfiguration":
target.setAgentConfiguration(property(camelContext,
org.apache.camel.component.langchain4j.agent.api.AgentConfiguration.class,
value)); return true;
case "agentfactory":
case "agentFactory": target.setAgentFactory(property(camelContext,
org.apache.camel.component.langchain4j.agent.api.AgentFactory.class, value));
return true;
+ case "jsonschema":
+ case "jsonSchema": target.setJsonSchema(property(camelContext,
java.lang.String.class, value)); return true;
case "mcpclients":
case "mcpClients": target.setMcpClients(property(camelContext,
java.util.List.class, value)); return true;
case "mcpserver":
@@ -45,6 +47,8 @@ public class LangChain4jAgentConfigurationConfigurer extends
org.apache.camel.su
case "agentConfiguration": return
org.apache.camel.component.langchain4j.agent.api.AgentConfiguration.class;
case "agentfactory":
case "agentFactory": return
org.apache.camel.component.langchain4j.agent.api.AgentFactory.class;
+ case "jsonschema":
+ case "jsonSchema": return java.lang.String.class;
case "mcpclients":
case "mcpClients": return java.util.List.class;
case "mcpserver":
@@ -63,6 +67,8 @@ public class LangChain4jAgentConfigurationConfigurer extends
org.apache.camel.su
case "agentConfiguration": return target.getAgentConfiguration();
case "agentfactory":
case "agentFactory": return target.getAgentFactory();
+ case "jsonschema":
+ case "jsonSchema": return target.getJsonSchema();
case "mcpclients":
case "mcpClients": return target.getMcpClients();
case "mcpserver":
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java
index a90f44483690..9b7382db9773 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java
@@ -28,6 +28,8 @@ public class LangChain4jAgentEndpointConfigurer extends
PropertyConfigurerSuppor
case "agentConfiguration":
target.getConfiguration().setAgentConfiguration(property(camelContext,
org.apache.camel.component.langchain4j.agent.api.AgentConfiguration.class,
value)); return true;
case "agentfactory":
case "agentFactory":
target.getConfiguration().setAgentFactory(property(camelContext,
org.apache.camel.component.langchain4j.agent.api.AgentFactory.class, value));
return true;
+ case "jsonschema":
+ case "jsonSchema":
target.getConfiguration().setJsonSchema(property(camelContext,
java.lang.String.class, value)); return true;
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "mcpclients":
@@ -52,6 +54,8 @@ public class LangChain4jAgentEndpointConfigurer extends
PropertyConfigurerSuppor
case "agentConfiguration": return
org.apache.camel.component.langchain4j.agent.api.AgentConfiguration.class;
case "agentfactory":
case "agentFactory": return
org.apache.camel.component.langchain4j.agent.api.AgentFactory.class;
+ case "jsonschema":
+ case "jsonSchema": return java.lang.String.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
case "mcpclients":
@@ -72,6 +76,8 @@ public class LangChain4jAgentEndpointConfigurer extends
PropertyConfigurerSuppor
case "agentConfiguration": return
target.getConfiguration().getAgentConfiguration();
case "agentfactory":
case "agentFactory": return
target.getConfiguration().getAgentFactory();
+ case "jsonschema":
+ case "jsonSchema": return target.getConfiguration().getJsonSchema();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
case "mcpclients":
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java
index d6ce55a99b94..40ee98ce66db 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java
@@ -24,11 +24,12 @@ public class LangChain4jAgentEndpointUriFactory extends
org.apache.camel.support
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(8);
+ Set<String> props = new HashSet<>(9);
props.add("agent");
props.add("agentConfiguration");
props.add("agentFactory");
props.add("agentId");
+ props.add("jsonSchema");
props.add("lazyStartProducer");
props.add("mcpClients");
props.add("mcpServer");
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
b/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
index fb06b6ebf076..d9d41b08123e 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
+++
b/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
@@ -28,11 +28,12 @@
"agentConfiguration": { "index": 1, "kind": "property", "displayName":
"Agent Configuration", "group": "producer", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentConfiguration",
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Agent [...]
"agentFactory": { "index": 2, "kind": "property", "displayName": "Agent
Factory", "group": "producer", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentFactory", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "The agent factory to us
[...]
"configuration": { "index": 3, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
configuration" },
- "lazyStartProducer": { "index": 4, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
- "tags": { "index": 5, "kind": "property", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
- "autowiredEnabled": { "index": 6, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
- "mcpClients": { "index": 7, "kind": "property", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client insta [...]
- "mcpServer": { "index": 8, "kind": "property", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
+ "jsonSchema": { "index": 4, "kind": "property", "displayName": "Json
Schema", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "supportFileReference": true,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "JSON schema for
structured output validat [...]
+ "lazyStartProducer": { "index": 5, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
+ "tags": { "index": 6, "kind": "property", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
+ "autowiredEnabled": { "index": 7, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "mcpClients": { "index": 8, "kind": "property", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client insta [...]
+ "mcpServer": { "index": 9, "kind": "property", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
},
"headers": {
"CamelLangChain4jAgentSystemMessage": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The system prompt.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#SYSTEM_MESSAGE" },
@@ -47,9 +48,10 @@
"agent": { "index": 1, "kind": "parameter", "displayName": "Agent",
"group": "producer", "label": "", "required": false, "type": "object",
"javaType": "org.apache.camel.component.langchain4j.agent.api.Agent",
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "The agent to use for the
component" },
"agentConfiguration": { "index": 2, "kind": "parameter", "displayName":
"Agent Configuration", "group": "producer", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentConfiguration",
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Agen [...]
"agentFactory": { "index": 3, "kind": "parameter", "displayName": "Agent
Factory", "group": "producer", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.langchain4j.agent.api.AgentFactory", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "The agent factory to u
[...]
- "tags": { "index": 4, "kind": "parameter", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
- "lazyStartProducer": { "index": 5, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
- "mcpClients": { "index": 6, "kind": "parameter", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client inst [...]
- "mcpServer": { "index": 7, "kind": "parameter", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
+ "jsonSchema": { "index": 4, "kind": "parameter", "displayName": "Json
Schema", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "supportFileReference": true,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "JSON schema for
structured output valida [...]
+ "tags": { "index": 5, "kind": "parameter", "displayName": "Tags", "group":
"producer", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Tags for discovering and
calling Camel route tools" },
+ "lazyStartProducer": { "index": 6, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "mcpClients": { "index": 7, "kind": "parameter", "displayName": "Mcp
Clients", "group": "advanced", "label": "advanced", "required": false, "type":
"array", "javaType": "java.util.List<dev.langchain4j.mcp.client.McpClient>",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "Pre-built MCP (Model
Context Protocol) client inst [...]
+ "mcpServer": { "index": 8, "kind": "parameter", "displayName": "Mcp
Server", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>",
"prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration",
"configurationField": "configuration", "description": "MCP server [...]
}
}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
index c8fe9af8ba1a..f50192fba300 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
+++
b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
@@ -1345,76 +1345,58 @@ public class AgentConfig {
=== 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.
+The LangChain4j Agent component supports structured outputs by enforcing a
JSON schema on LLM responses. 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.
+==== Using the jsonSchema endpoint option
-==== Configuring Structured Outputs
+The simplest way to enable structured output is to use the `jsonSchema`
endpoint option. This works when using `agentConfiguration` (inline agent
creation mode). The option supports classpath references, file paths, and
inline JSON, the same way as the `camel-openai` component.
-To use structured outputs with the agent component:
+===== Example with classpath resource
-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`
+Create a JSON schema file, for example `src/main/resources/person-schema.json`:
-===== Complete Example with OpenAI
+[source,json]
+----
+{
+ "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
+}
+----
+
+Then use it in a route:
[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()
+AgentConfiguration agentConfiguration = new AgentConfiguration()
.withChatModel(chatModel);
-Agent agent = new AgentWithoutMemory(configuration);
-context.getRegistry().bind("structuredAgent", agent);
+context.getRegistry().bind("myAgentConfig", agentConfiguration);
-// 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
+ .to("langchain4j-agent:structured?agentConfiguration=#myAgentConfig"
+ + "&jsonSchema=classpath:person-schema.json")
+ .unmarshal().json()
.log("Extracted person: ${body}");
----
+The `jsonSchema` option accepts:
+
+* Classpath resources: `jsonSchema=classpath:schemas/person.json`
+* File paths: `jsonSchema=file:///path/to/schema.json`
+* Inline JSON:
`jsonSchema={"type":"object","properties":{"name":{"type":"string"}}}`
+* Property placeholders: `jsonSchema=resource:classpath:${schema.location}`
+
+==== Alternative: configuring ResponseFormat on the ChatModel
+
+You can also define the `ResponseFormat` at the `ChatModel` level. See the
https://docs.langchain4j.dev/tutorials/structured-outputs#using-json-schema-with-chatmodel[LangChain4j
Structured Outputs documentation] for details.
+
[NOTE]
====
* The LLM response will be guaranteed to conform to the provided schema
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java
index 987385b6772a..f9c9004d2279 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java
@@ -56,6 +56,12 @@ public class LangChain4jAgentConfiguration implements
Cloneable {
label = "advanced")
private List<McpClient> mcpClients;
+ @UriParam
+ @Metadata(description = "JSON schema for structured output validation. "
+ + "This option works only when using
agentConfiguration (inline agent creation mode).",
+ supportFileReference = true, largeInput = true, inputLanguage =
"json")
+ private String jsonSchema;
+
@UriParam(description = "MCP server definitions in the form of
mcpServer.<name>.<property>=<value>."
+ " Supported properties: transportType (stdio,
http, streamableHttp, or sse, default: stdio),"
+ " command (comma-separated, for stdio), url (for
http/sse),"
@@ -161,4 +167,17 @@ public class LangChain4jAgentConfiguration implements
Cloneable {
public void setMcpServer(Map<String, Object> mcpServer) {
this.mcpServer = mcpServer;
}
+
+ /**
+ * JSON schema for structured output validation
+ *
+ * @return the JSON schema string or resource reference
+ */
+ public String getJsonSchema() {
+ return jsonSchema;
+ }
+
+ public void setJsonSchema(String jsonSchema) {
+ this.jsonSchema = jsonSchema;
+ }
}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
index ac40b153ceee..027c6f82c885 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.component.langchain4j.agent;
+import java.io.IOException;
+import java.io.InputStream;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@@ -25,12 +27,17 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.McpClient;
+import dev.langchain4j.model.chat.request.ResponseFormat;
+import dev.langchain4j.model.chat.request.ResponseFormatType;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
+import dev.langchain4j.model.chat.request.json.JsonRawSchema;
+import dev.langchain4j.model.chat.request.json.JsonSchema;
import dev.langchain4j.service.tool.ToolExecutor;
import dev.langchain4j.service.tool.ToolProvider;
import dev.langchain4j.service.tool.ToolProviderRequest;
import dev.langchain4j.service.tool.ToolProviderResult;
import org.apache.camel.Exchange;
+import org.apache.camel.component.langchain4j.agent.api.AbstractAgent;
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.AgentFactory;
@@ -42,6 +49,7 @@ import
org.apache.camel.component.langchain4j.agent.api.Headers;
import
org.apache.camel.component.langchain4j.tools.spec.CamelToolExecutorCache;
import
org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification;
import org.apache.camel.support.DefaultProducer;
+import org.apache.camel.support.ResourceHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,6 +79,8 @@ public class LangChain4jAgentProducer extends DefaultProducer
{
agent = agentConfiguration.getChatMemoryProvider() != null
? new AgentWithMemory(agentConfiguration)
: new AgentWithoutMemory(agentConfiguration);
+ // Apply jsonSchema only when Camel creates the agent internally —
we cannot modify user-provided agents
+ resolveAndApplyJsonSchema();
} else {
agent =
endpoint.getCamelContext().getRegistry().lookupByNameAndType(endpoint.getAgentId(),
Agent.class);
}
@@ -294,6 +304,71 @@ public class LangChain4jAgentProducer extends
DefaultProducer {
return toolsByName;
}
+ /**
+ * Resolves the jsonSchema endpoint property: loads from
classpath/filesystem if it is a resource URI, otherwise
+ * treats it as inline JSON. Converts the raw JSON string into a
langchain4j ResponseFormat and sets it on the
+ * agent. Only called when the agent is created internally from
agentConfiguration.
+ */
+ private void resolveAndApplyJsonSchema() throws Exception {
+ String jsonSchema = endpoint.getConfiguration().getJsonSchema();
+ if (ObjectHelper.isEmpty(jsonSchema)) {
+ return;
+ }
+
+ String resolved =
endpoint.getCamelContext().resolvePropertyPlaceholders(jsonSchema);
+
+ String content = resolveResourceContent(resolved);
+ if (content != null) {
+ resolved = content;
+ }
+
+ // Validates that resolved is valid JSON (whether loaded from a
resource or used as inline content)
+ try {
+ objectMapper.readTree(resolved);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "jsonSchema endpoint property does not contain valid JSON.
Provided value: " + jsonSchema, e);
+ }
+ JsonRawSchema jsonRawSchema = JsonRawSchema.from(resolved);
+ // Use a fixed name consistent with camel-openai for cross-component
portability
+ ResponseFormat responseFormat = ResponseFormat.builder()
+ .type(ResponseFormatType.JSON)
+ .jsonSchema(JsonSchema.builder()
+ .name("camel_schema")
+ .rootElement(jsonRawSchema)
+ .build())
+ .build();
+
+ ((AbstractAgent<?>) agent).setResponseFormat(responseFormat);
+ }
+
+ /**
+ * Tries to load {@code property} as a Camel resource and return its
content as a String.
+ * <p>
+ * If {@code property} has an explicit scheme (e.g. {@code classpath:},
{@code file:}), the resource must exist — a
+ * missing resource throws {@link java.io.FileNotFoundException}.
+ * <p>
+ * If there is no scheme, classpath resolution is attempted. Returns
{@code null} on failure, signalling the caller
+ * to use {@code property} as-is (inline JSON).
+ */
+ private String resolveResourceContent(String property) throws IOException {
+ if (ResourceHelper.hasScheme(property)) {
+ // Explicit scheme: mandatory load — throws FileNotFoundException
if the resource does not exist
+ try (InputStream is =
ResourceHelper.resolveMandatoryResourceAsInputStream(endpoint.getCamelContext(),
property)) {
+ return
endpoint.getCamelContext().getTypeConverter().convertTo(String.class, is);
+ }
+ }
+ // No scheme: try implicit classpath resolution — fall through and
treat as inline JSON content if not found
+ try (InputStream is =
ResourceHelper.resolveResourceAsInputStream(endpoint.getCamelContext(),
property)) {
+ if (is != null) {
+ return
endpoint.getCamelContext().getTypeConverter().convertTo(String.class, is);
+ }
+ } catch (Exception e) {
+ // not a resolvable resource URI — fall through and treat as
inline JSON content
+ }
+ return null;
+ }
+
@Override
protected void doStart() throws Exception {
super.doStart();
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputIT.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputIT.java
index 7a3d497fe0c2..e989479a9261 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputIT.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputIT.java
@@ -16,16 +16,17 @@
*/
package org.apache.camel.component.langchain4j.agent.integration;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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 org.apache.camel.builder.RouteBuilder;
-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.mock.MockEndpoint;
import org.apache.camel.test.infra.ollama.services.OllamaService;
import org.apache.camel.test.infra.ollama.services.OllamaServiceFactory;
@@ -33,8 +34,8 @@ import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
-import static dev.langchain4j.model.chat.request.ResponseFormatType.JSON;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -73,6 +74,9 @@ public class LangChain4jAgentStructuredOutputIT extends
CamelTestSupport {
protected ChatModel chatModel;
+ @TempDir
+ Path tempDir;
+
@RegisterExtension
static OllamaService OLLAMA =
OllamaServiceFactory.createSingletonService();
@@ -80,27 +84,43 @@ public class LangChain4jAgentStructuredOutputIT extends
CamelTestSupport {
protected void setupResources() throws Exception {
super.setupResources();
- JsonRawSchema jsonRawSchema = JsonRawSchema.from(PERSON_SCHEMA);
- ResponseFormat responseFormat = ResponseFormat.builder()
- .type(JSON)
- .jsonSchema(JsonSchema.builder()
- .name("person_schema")
- .rootElement(jsonRawSchema)
- .build())
- .build();
+ chatModel = ModelHelper.loadChatModel(OLLAMA);
+ }
+
+ @Test
+ void testStructuredOutputWithClasspathSchema() throws Exception {
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:classpath-result");
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:classpath-schema",
TEST_PROMPT, String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertValidPersonResponse(response);
+ }
+
+ @Test
+ void testStructuredOutputWithInlineSchema() throws Exception {
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:inline-result");
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:inline-schema",
TEST_PROMPT, String.class);
- chatModel = ModelHelper.loadChatModel(OLLAMA, responseFormat);
+ mockEndpoint.assertIsSatisfied();
+ assertValidPersonResponse(response);
}
@Test
- void testStructuredOutputWithJsonSchema() throws Exception {
- MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+ void testStructuredOutputWithFileSchema() throws Exception {
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:file-result");
mockEndpoint.expectedMessageCount(1);
- String response = template.requestBody("direct:structured-output",
TEST_PROMPT, String.class);
+ String response = template.requestBody("direct:file-schema",
TEST_PROMPT, String.class);
mockEndpoint.assertIsSatisfied();
+ assertValidPersonResponse(response);
+ }
+ private void assertValidPersonResponse(String response) throws Exception {
assertNotNull(response);
JsonNode jsonResponse = objectMapper.readTree(response);
@@ -113,21 +133,43 @@ public class LangChain4jAgentStructuredOutputIT extends
CamelTestSupport {
assertThat(jsonResponse.get("occupation").asText().toLowerCase()).containsAnyOf("engineer",
"developer");
}
+ private String loadSchemaFromClasspath() throws IOException {
+ try (InputStream is =
getClass().getClassLoader().getResourceAsStream("person-schema.json")) {
+ return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+
@Override
- protected RouteBuilder createRouteBuilder() {
- AgentConfiguration configuration = new AgentConfiguration()
- .withChatModel(chatModel);
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ String inlineSchema = loadSchemaFromClasspath();
- Agent agent = new AgentWithoutMemory(configuration);
- context.getRegistry().bind("structuredOutputAgent", agent);
+ Path schemaFile = tempDir.resolve("person-schema.json");
+ Files.writeString(schemaFile, inlineSchema);
+
+ AgentConfiguration agentConfiguration = new
AgentConfiguration().withChatModel(chatModel);
+ context.getRegistry().bind("myAgentConfig", agentConfiguration);
return new RouteBuilder() {
@Override
public void configure() {
- from("direct:structured-output")
-
.to("langchain4j-agent:structured-output?agent=#structuredOutputAgent")
+ from("direct:classpath-schema")
+
.to("langchain4j-agent:classpath-test?agentConfiguration=#myAgentConfig"
+ + "&jsonSchema=classpath:person-schema.json")
+ .to("json-validator:classpath:person-schema.json")
+ .to("mock:classpath-result");
+
+ from("direct:inline-schema")
+
.to("langchain4j-agent:inline-test?agentConfiguration=#myAgentConfig"
+ + "&jsonSchema=RAW(" + inlineSchema + ")")
.to("json-validator:classpath:person-schema.json")
- .to("mock:result");
+ .to("mock:inline-result");
+
+ from("direct:file-schema")
+
.to("langchain4j-agent:file-test?agentConfiguration=#myAgentConfig"
+ + "&jsonSchema=file:" +
schemaFile.toAbsolutePath())
+ .to("json-validator:classpath:person-schema.json")
+ .to("mock:file-result");
+
}
};
}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
new file mode 100644
index 000000000000..fb30f0678dd4
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.apache.camel.component.langchain4j.agent.integration;
+
+import java.io.FileNotFoundException;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Unit tests for jsonSchema error handling in the langchain4j-agent
component. These tests do not require a running LLM
+ * and always run in CI. For happy-path integration tests that exercise
structured output end-to-end, see
+ * {@link LangChain4jAgentStructuredOutputIT}.
+ *
+ * <p>
+ * Each test creates its own {@link org.apache.camel.impl.DefaultCamelContext}
instead of using
+ * {@link org.apache.camel.test.junit6.CamelTestSupport}. This is intentional:
the jsonSchema error is detected during
+ * producer initialisation ({@code doInit()}), which runs as part of context
startup. In CamelTestSupport, context
+ * startup happens inside {@code setUp()} — before the {@code @Test} method —
so a startup failure would abort the test
+ * before {@code assertThrows} could capture it. Owning the context lifecycle
inside the test method keeps the exception
+ * within the scope of {@code assertThrows}.
+ */
+public class LangChain4jAgentStructuredOutputTest {
+
+ @Test
+ void testNonExistentClasspathSchemaThrowsFileNotFoundException() throws
Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ AgentConfiguration config = new AgentConfiguration();
+ context.getRegistry().bind("myConfig", config);
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:test")
+
.to("langchain4j-agent:test?agentConfiguration=#myConfig&jsonSchema=classpath:non-existent-schema.json");
+ }
+ });
+
+ // FailedToStartRouteException -> RuntimeCamelException ->
FileNotFoundException
+ Exception ex = assertThrows(Exception.class, context::start);
+ assertInstanceOf(FileNotFoundException.class,
ex.getCause().getCause());
+ }
+ }
+
+ @Test
+ void testNonExistentFileSchemaThrowsFileNotFoundException() throws
Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ AgentConfiguration config = new AgentConfiguration();
+ context.getRegistry().bind("myConfig", config);
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:test")
+
.to("langchain4j-agent:test?agentConfiguration=#myConfig&jsonSchema=file:/tmp/non-existent-schema-12345.json");
+ }
+ });
+
+ // FailedToStartRouteException -> RuntimeCamelException ->
FileNotFoundException
+ Exception ex = assertThrows(Exception.class, context::start);
+ assertInstanceOf(FileNotFoundException.class,
ex.getCause().getCause());
+ }
+ }
+
+ @Test
+ void testInvalidJsonContentThrowsIllegalArgumentException() throws
Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ AgentConfiguration config = new AgentConfiguration();
+ context.getRegistry().bind("myConfig", config);
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:test")
+
.to("langchain4j-agent:test?agentConfiguration=#myConfig&jsonSchema=RAW(this is
not json)");
+ }
+ });
+
+ // FailedToStartRouteException -> RuntimeCamelException ->
IllegalArgumentException
+ Exception ex = assertThrows(Exception.class, context::start);
+ assertInstanceOf(IllegalArgumentException.class,
ex.getCause().getCause());
+ }
+ }
+
+}
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Langchain4jAgentComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Langchain4jAgentComponentBuilderFactory.java
index 2460f65744ed..a6d6fa9f43e6 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Langchain4jAgentComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Langchain4jAgentComponentBuilderFactory.java
@@ -117,6 +117,25 @@ public interface Langchain4jAgentComponentBuilderFactory {
return this;
}
+ /**
+ * JSON schema for structured output validation. This option works only
+ * when using agentConfiguration (inline agent creation mode).
+ *
+ * This option can also be loaded from an existing file, by prefixing
+ * with file: or classpath: followed by the location of the file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: producer
+ *
+ * @param jsonSchema the value to set
+ * @return the dsl builder
+ */
+ default Langchain4jAgentComponentBuilder jsonSchema(java.lang.String
jsonSchema) {
+ doSetProperty("jsonSchema", jsonSchema);
+ return this;
+ }
+
/**
* Whether the producer should be started lazy (on the first message).
@@ -243,6 +262,7 @@ public interface Langchain4jAgentComponentBuilderFactory {
case "agentConfiguration":
getOrCreateConfiguration((LangChain4jAgentComponent)
component).setAgentConfiguration((org.apache.camel.component.langchain4j.agent.api.AgentConfiguration)
value); return true;
case "agentFactory":
getOrCreateConfiguration((LangChain4jAgentComponent)
component).setAgentFactory((org.apache.camel.component.langchain4j.agent.api.AgentFactory)
value); return true;
case "configuration": ((LangChain4jAgentComponent)
component).setConfiguration((org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration)
value); return true;
+ case "jsonSchema":
getOrCreateConfiguration((LangChain4jAgentComponent)
component).setJsonSchema((java.lang.String) value); return true;
case "lazyStartProducer": ((LangChain4jAgentComponent)
component).setLazyStartProducer((boolean) value); return true;
case "tags": getOrCreateConfiguration((LangChain4jAgentComponent)
component).setTags((java.lang.String) value); return true;
case "autowiredEnabled": ((LangChain4jAgentComponent)
component).setAutowiredEnabled((boolean) value); return true;
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
index eafc10a8233e..bbdf10af20b8 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
@@ -142,6 +142,24 @@ public interface LangChain4jAgentEndpointBuilderFactory {
doSetProperty("agentFactory", agentFactory);
return this;
}
+ /**
+ * JSON schema for structured output validation. This option works only
+ * when using agentConfiguration (inline agent creation mode).
+ *
+ * This option can also be loaded from an existing file, by prefixing
+ * with file: or classpath: followed by the location of the file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: producer
+ *
+ * @param jsonSchema the value to set
+ * @return the dsl builder
+ */
+ default LangChain4jAgentEndpointBuilder jsonSchema(String jsonSchema) {
+ doSetProperty("jsonSchema", jsonSchema);
+ return this;
+ }
/**
* Tags for discovering and calling Camel route tools.
*