You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gn...@apache.org on 2026/07/29 09:21:29 UTC
(camel) 01/01: CAMEL-23963: configurable strategies for tool errors and hallucinated tool names
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch investigate-and-fix-camel-23963-camel-openai-conf
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9e08e4b110995d4f0c9b48c3e1e9f728a362f3a9
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Wed Jul 29 11:21:13 2026 +0200
CAMEL-23963: configurable strategies for tool errors and hallucinated tool names
Add two new endpoint options to camel-openai for configuring the agentic
loop's error handling policies:
- toolExecutionErrorStrategy (default: repromptModel): controls whether
tool execution exceptions are caught and fed back to the model
(repromptModel) or propagated to the Camel exchange for standard error
handling via onException/dead-letter (failExchange).
- hallucinatedToolNameStrategy (default: failExchange): controls whether
unknown/hallucinated tool names throw an IllegalStateException
(failExchange) or send a corrective tool result listing available tools
so the model can self-correct (repromptModel).
Both strategies are applied consistently in OpenAIProducer (agentic loop)
and OpenAIToolExecutionProducer (manual tool loop). Defaults preserve
backward compatibility. The existing maxToolIterations option bounds
retries when repromptModel strategies are used.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../component/openai/OpenAIEndpointConfigurer.java | 12 ++
.../component/openai/OpenAIEndpointUriFactory.java | 4 +-
.../org/apache/camel/component/openai/openai.json | 86 ++++----
.../camel-openai/src/main/docs/openai-mcp.adoc | 21 +-
.../openai/HallucinatedToolNameStrategy.java | 36 ++++
.../component/openai/OpenAIConfiguration.java | 31 +++
.../camel/component/openai/OpenAIProducer.java | 20 +-
.../openai/OpenAIToolExecutionProducer.java | 24 ++-
.../openai/ToolExecutionErrorStrategy.java | 35 +++
.../openai/OpenAIToolErrorStrategyTest.java | 239 +++++++++++++++++++++
10 files changed, 458 insertions(+), 50 deletions(-)
diff --git a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
index 3949023299e3..825570b9a22b 100644
--- a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
+++ b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
@@ -62,6 +62,8 @@ public class OpenAIEndpointConfigurer extends PropertyConfigurerSupport implemen
case "encodingFormat": target.getConfiguration().setEncodingFormat(property(camelContext, java.lang.String.class, value)); return true;
case "filesearchvectorstoreids":
case "fileSearchVectorStoreIds": target.getConfiguration().setFileSearchVectorStoreIds(property(camelContext, java.lang.String.class, value)); return true;
+ case "hallucinatedtoolnamestrategy":
+ case "hallucinatedToolNameStrategy": target.getConfiguration().setHallucinatedToolNameStrategy(property(camelContext, org.apache.camel.component.openai.HallucinatedToolNameStrategy.class, value)); return true;
case "hostedmcptools":
case "hostedMcpTools": target.getConfiguration().setHostedMcpTools(property(camelContext, java.lang.String.class, value)); return true;
case "jsonschema":
@@ -139,6 +141,8 @@ public class OpenAIEndpointConfigurer extends PropertyConfigurerSupport implemen
case "systemmessage":
case "systemMessage": target.getConfiguration().setSystemMessage(property(camelContext, java.lang.String.class, value)); return true;
case "temperature": target.getConfiguration().setTemperature(property(camelContext, java.lang.Double.class, value)); return true;
+ case "toolexecutionerrorstrategy":
+ case "toolExecutionErrorStrategy": target.getConfiguration().setToolExecutionErrorStrategy(property(camelContext, org.apache.camel.component.openai.ToolExecutionErrorStrategy.class, value)); return true;
case "topp":
case "topP": target.getConfiguration().setTopP(property(camelContext, java.lang.Double.class, value)); return true;
case "usermessage":
@@ -189,6 +193,8 @@ public class OpenAIEndpointConfigurer extends PropertyConfigurerSupport implemen
case "encodingFormat": return java.lang.String.class;
case "filesearchvectorstoreids":
case "fileSearchVectorStoreIds": return java.lang.String.class;
+ case "hallucinatedtoolnamestrategy":
+ case "hallucinatedToolNameStrategy": return org.apache.camel.component.openai.HallucinatedToolNameStrategy.class;
case "hostedmcptools":
case "hostedMcpTools": return java.lang.String.class;
case "jsonschema":
@@ -266,6 +272,8 @@ public class OpenAIEndpointConfigurer extends PropertyConfigurerSupport implemen
case "systemmessage":
case "systemMessage": return java.lang.String.class;
case "temperature": return java.lang.Double.class;
+ case "toolexecutionerrorstrategy":
+ case "toolExecutionErrorStrategy": return org.apache.camel.component.openai.ToolExecutionErrorStrategy.class;
case "topp":
case "topP": return java.lang.Double.class;
case "usermessage":
@@ -317,6 +325,8 @@ public class OpenAIEndpointConfigurer extends PropertyConfigurerSupport implemen
case "encodingFormat": return target.getConfiguration().getEncodingFormat();
case "filesearchvectorstoreids":
case "fileSearchVectorStoreIds": return target.getConfiguration().getFileSearchVectorStoreIds();
+ case "hallucinatedtoolnamestrategy":
+ case "hallucinatedToolNameStrategy": return target.getConfiguration().getHallucinatedToolNameStrategy();
case "hostedmcptools":
case "hostedMcpTools": return target.getConfiguration().getHostedMcpTools();
case "jsonschema":
@@ -394,6 +404,8 @@ public class OpenAIEndpointConfigurer extends PropertyConfigurerSupport implemen
case "systemmessage":
case "systemMessage": return target.getConfiguration().getSystemMessage();
case "temperature": return target.getConfiguration().getTemperature();
+ case "toolexecutionerrorstrategy":
+ case "toolExecutionErrorStrategy": return target.getConfiguration().getToolExecutionErrorStrategy();
case "topp":
case "topP": return target.getConfiguration().getTopP();
case "usermessage":
diff --git a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
index ab83d5467df2..60bf0d2c653d 100644
--- a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
+++ b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class OpenAIEndpointUriFactory extends org.apache.camel.support.component
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(63);
+ Set<String> props = new HashSet<>(65);
props.add("additionalBodyProperty");
props.add("additionalHeader");
props.add("additionalResponseHeader");
@@ -45,6 +45,7 @@ public class OpenAIEndpointUriFactory extends org.apache.camel.support.component
props.add("embeddingModel");
props.add("encodingFormat");
props.add("fileSearchVectorStoreIds");
+ props.add("hallucinatedToolNameStrategy");
props.add("hostedMcpTools");
props.add("jsonSchema");
props.add("lazyStartProducer");
@@ -86,6 +87,7 @@ public class OpenAIEndpointUriFactory extends org.apache.camel.support.component
props.add("stripThinking");
props.add("systemMessage");
props.add("temperature");
+ props.add("toolExecutionErrorStrategy");
props.add("topP");
props.add("userMessage");
PROPERTY_NAMES = Collections.unmodifiableSet(props);
diff --git a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
index a8ae3a444deb..30e0e7d10517 100644
--- a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
+++ b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
@@ -107,47 +107,49 @@
"embeddingModel": { "index": 18, "kind": "parameter", "displayName": "Embedding Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for embeddings" },
"encodingFormat": { "index": 19, "kind": "parameter", "displayName": "Encoding Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float", "base64" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "base64", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The format for embedding ou [...]
"fileSearchVectorStoreIds": { "index": 20, "kind": "parameter", "displayName": "File Search Vector Store Ids", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated vector store ids required when builtinTools [...]
- "hostedMcpTools": { "index": 21, "kind": "parameter", "displayName": "Hosted Mcp Tools", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON array of hosted MCP tool definitions (OpenAI Tool.Mcp) passed through to the [...]
- "jsonSchema": { "index": 22, "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.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON schema for structured output validation" },
- "maxAgenticTokens": { "index": 23, "kind": "parameter", "displayName": "Max Agentic Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum cumulative prompt plus completion tokens allowed across the M [...]
- "maxHistoryMessages": { "index": 24, "kind": "parameter", "displayName": "Max History Messages", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, retain at most this many messa [...]
- "maxHistoryTokens": { "index": 25, "kind": "parameter", "displayName": "Max History Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, trim conversation history using a [...]
- "maxRetries": { "index": 26, "kind": "parameter", "displayName": "Max Retries", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 2, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of times the OpenAI SDK client retries failed requests. The SDK retr [...]
- "maxTokens": { "index": 27, "kind": "parameter", "displayName": "Max Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tokens to generate" },
- "maxToolIterations": { "index": 28, "kind": "parameter", "displayName": "Max Tool Iterations", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 50, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tool call loop iterations to prevent infinite loops" },
- "mcpProtocolVersions": { "index": 29, "kind": "parameter", "displayName": "Mcp Protocol Versions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated list of MCP protocol versions to advertise when connect [...]
- "mcpReconnect": { "index": 30, "kind": "parameter", "displayName": "Mcp Reconnect", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Automatically reconnect to MCP servers when a tool call fails due to a t [...]
- "mcpServer": { "index": 31, "kind": "parameter", "displayName": "Mcp Server", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "MCP (Model Cont [...]
- "mcpTimeout": { "index": 32, "kind": "parameter", "displayName": "Mcp Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP tool call requests. Applies to all MCP operations inclu [...]
- "model": { "index": 33, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for chat completion" },
- "outputClass": { "index": 34, "kind": "parameter", "displayName": "Output Class", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Fully qualified class name for structured output using response format" },
- "previousResponseId": { "index": 35, "kind": "parameter", "displayName": "Previous Response Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Previous response id for OpenAI server-side conversation state (Responses [...]
- "requestTimeout": { "index": 36, "kind": "parameter", "displayName": "Request Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "HTTP request timeout in milliseconds for the OpenAI SDK client. When 0 or [...]
- "speechInstructions": { "index": 37, "kind": "parameter", "displayName": "Speech Instructions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Optional instructions to control the voice of the generated audio. Does no [...]
- "speechModel": { "index": 38, "kind": "parameter", "displayName": "Speech Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)" },
- "speechResponseFormat": { "index": 39, "kind": "parameter", "displayName": "Speech Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "mp3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "descrip [...]
- "speechSpeed": { "index": 40, "kind": "parameter", "displayName": "Speech Speed", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The speed of the generated audio, from 0.25 to 4.0 where 1.0 is normal speed" },
- "speechVoice": { "index": 41, "kind": "parameter", "displayName": "Speech Voice", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "alloy", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The voice to use for text-to-speech (e.g., alloy, echo, fable, [...]
- "storeFullResponse": { "index": 42, "kind": "parameter", "displayName": "Store Full Response", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Store the full SDK response in non-streaming mode: chat-comp [...]
- "streaming": { "index": 43, "kind": "parameter", "displayName": "Streaming", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Enable streaming responses" },
- "stripThinking": { "index": 44, "kind": "parameter", "displayName": "Strip Thinking", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strip ... blocks from model responses (used by reasoning models like [...]
- "systemMessage": { "index": 45, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "System message to prepend. When set and conversationMemory is enabled, the conversat [...]
- "temperature": { "index": 46, "kind": "parameter", "displayName": "Temperature", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Temperature for response generation (0.0 to 2.0)" },
- "topP": { "index": 47, "kind": "parameter", "displayName": "Top P", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Top P for response generation (0.0 to 1.0)" },
- "userMessage": { "index": 48, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Default user message text to use when no prompt is provided" },
- "lazyStartProducer": { "index": 49, "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 produ [...]
- "oauthProfile": { "index": 50, "kind": "parameter", "displayName": "Oauth Profile", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "OAuth profile name for obtaining an access token via the OAuth 2.0 Client Cred [...]
- "sslContextParameters": { "index": 51, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "SSLContextParameters to use [...]
- "sslEndpointAlgorithm": { "index": 52, "kind": "parameter", "displayName": "Ssl Endpoint Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "security": "insecure:ssl", "insecureValue": "none", "defaultValue": "https", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", [...]
- "sslKeymanagerAlgorithm": { "index": 53, "kind": "parameter", "displayName": "Ssl Keymanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "SunX509", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the key [...]
- "sslKeyPassword": { "index": 54, "kind": "parameter", "displayName": "Ssl Key Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password of the private key in the key store file" },
- "sslKeystoreLocation": { "index": 55, "kind": "parameter", "displayName": "Ssl Keystore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the key store file. This is optional and can be [...]
- "sslKeystorePassword": { "index": 56, "kind": "parameter", "displayName": "Ssl Keystore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The store password for the key store file" },
- "sslKeystoreType": { "index": 57, "kind": "parameter", "displayName": "Ssl Keystore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the key store file" },
- "sslProtocol": { "index": 58, "kind": "parameter", "displayName": "Ssl Protocol", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "TLSv1.3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The SSL protocol used to generate the SSLContext" },
- "sslTrustmanagerAlgorithm": { "index": 59, "kind": "parameter", "displayName": "Ssl Trustmanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "PKIX", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the tru [...]
- "sslTruststoreLocation": { "index": 60, "kind": "parameter", "displayName": "Ssl Truststore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the trust store file, used to validate the [...]
- "sslTruststorePassword": { "index": 61, "kind": "parameter", "displayName": "Ssl Truststore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password for the trust store file. [...]
- "sslTruststoreType": { "index": 62, "kind": "parameter", "displayName": "Ssl Truststore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the trust store file" }
+ "hallucinatedToolNameStrategy": { "index": 21, "kind": "parameter", "displayName": "Hallucinated Tool Name Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.HallucinatedToolNameStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAICo [...]
+ "hostedMcpTools": { "index": 22, "kind": "parameter", "displayName": "Hosted Mcp Tools", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON array of hosted MCP tool definitions (OpenAI Tool.Mcp) passed through to the [...]
+ "jsonSchema": { "index": 23, "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.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON schema for structured output validation" },
+ "maxAgenticTokens": { "index": 24, "kind": "parameter", "displayName": "Max Agentic Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum cumulative prompt plus completion tokens allowed across the M [...]
+ "maxHistoryMessages": { "index": 25, "kind": "parameter", "displayName": "Max History Messages", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, retain at most this many messa [...]
+ "maxHistoryTokens": { "index": 26, "kind": "parameter", "displayName": "Max History Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, trim conversation history using a [...]
+ "maxRetries": { "index": 27, "kind": "parameter", "displayName": "Max Retries", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 2, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of times the OpenAI SDK client retries failed requests. The SDK retr [...]
+ "maxTokens": { "index": 28, "kind": "parameter", "displayName": "Max Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tokens to generate" },
+ "maxToolIterations": { "index": 29, "kind": "parameter", "displayName": "Max Tool Iterations", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 50, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tool call loop iterations to prevent infinite loops" },
+ "mcpProtocolVersions": { "index": 30, "kind": "parameter", "displayName": "Mcp Protocol Versions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated list of MCP protocol versions to advertise when connect [...]
+ "mcpReconnect": { "index": 31, "kind": "parameter", "displayName": "Mcp Reconnect", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Automatically reconnect to MCP servers when a tool call fails due to a t [...]
+ "mcpServer": { "index": 32, "kind": "parameter", "displayName": "Mcp Server", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "MCP (Model Cont [...]
+ "mcpTimeout": { "index": 33, "kind": "parameter", "displayName": "Mcp Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP tool call requests. Applies to all MCP operations inclu [...]
+ "model": { "index": 34, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for chat completion" },
+ "outputClass": { "index": 35, "kind": "parameter", "displayName": "Output Class", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Fully qualified class name for structured output using response format" },
+ "previousResponseId": { "index": 36, "kind": "parameter", "displayName": "Previous Response Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Previous response id for OpenAI server-side conversation state (Responses [...]
+ "requestTimeout": { "index": 37, "kind": "parameter", "displayName": "Request Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "HTTP request timeout in milliseconds for the OpenAI SDK client. When 0 or [...]
+ "speechInstructions": { "index": 38, "kind": "parameter", "displayName": "Speech Instructions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Optional instructions to control the voice of the generated audio. Does no [...]
+ "speechModel": { "index": 39, "kind": "parameter", "displayName": "Speech Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)" },
+ "speechResponseFormat": { "index": 40, "kind": "parameter", "displayName": "Speech Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "mp3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "descrip [...]
+ "speechSpeed": { "index": 41, "kind": "parameter", "displayName": "Speech Speed", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The speed of the generated audio, from 0.25 to 4.0 where 1.0 is normal speed" },
+ "speechVoice": { "index": 42, "kind": "parameter", "displayName": "Speech Voice", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "alloy", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The voice to use for text-to-speech (e.g., alloy, echo, fable, [...]
+ "storeFullResponse": { "index": 43, "kind": "parameter", "displayName": "Store Full Response", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Store the full SDK response in non-streaming mode: chat-comp [...]
+ "streaming": { "index": 44, "kind": "parameter", "displayName": "Streaming", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Enable streaming responses" },
+ "stripThinking": { "index": 45, "kind": "parameter", "displayName": "Strip Thinking", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strip ... blocks from model responses (used by reasoning models like [...]
+ "systemMessage": { "index": 46, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "System message to prepend. When set and conversationMemory is enabled, the conversat [...]
+ "temperature": { "index": 47, "kind": "parameter", "displayName": "Temperature", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Temperature for response generation (0.0 to 2.0)" },
+ "toolExecutionErrorStrategy": { "index": 48, "kind": "parameter", "displayName": "Tool Execution Error Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.ToolExecutionErrorStrategy", "enum": [ "repromptModel", "failExchange" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "repromptModel", "configurationClass": "org.apache.camel.component.openai.OpenAIConfigu [...]
+ "topP": { "index": 49, "kind": "parameter", "displayName": "Top P", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Top P for response generation (0.0 to 1.0)" },
+ "userMessage": { "index": 50, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Default user message text to use when no prompt is provided" },
+ "lazyStartProducer": { "index": 51, "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 produ [...]
+ "oauthProfile": { "index": 52, "kind": "parameter", "displayName": "Oauth Profile", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "OAuth profile name for obtaining an access token via the OAuth 2.0 Client Cred [...]
+ "sslContextParameters": { "index": 53, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "SSLContextParameters to use [...]
+ "sslEndpointAlgorithm": { "index": 54, "kind": "parameter", "displayName": "Ssl Endpoint Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "security": "insecure:ssl", "insecureValue": "none", "defaultValue": "https", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", [...]
+ "sslKeymanagerAlgorithm": { "index": 55, "kind": "parameter", "displayName": "Ssl Keymanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "SunX509", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the key [...]
+ "sslKeyPassword": { "index": 56, "kind": "parameter", "displayName": "Ssl Key Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password of the private key in the key store file" },
+ "sslKeystoreLocation": { "index": 57, "kind": "parameter", "displayName": "Ssl Keystore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the key store file. This is optional and can be [...]
+ "sslKeystorePassword": { "index": 58, "kind": "parameter", "displayName": "Ssl Keystore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The store password for the key store file" },
+ "sslKeystoreType": { "index": 59, "kind": "parameter", "displayName": "Ssl Keystore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the key store file" },
+ "sslProtocol": { "index": 60, "kind": "parameter", "displayName": "Ssl Protocol", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "TLSv1.3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The SSL protocol used to generate the SSLContext" },
+ "sslTrustmanagerAlgorithm": { "index": 61, "kind": "parameter", "displayName": "Ssl Trustmanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "PKIX", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the tru [...]
+ "sslTruststoreLocation": { "index": 62, "kind": "parameter", "displayName": "Ssl Truststore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the trust store file, used to validate the [...]
+ "sslTruststorePassword": { "index": 63, "kind": "parameter", "displayName": "Ssl Truststore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password for the trust store file. [...]
+ "sslTruststoreType": { "index": 64, "kind": "parameter", "displayName": "Ssl Truststore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the trust store file" }
}
}
diff --git a/components/camel-ai/camel-openai/src/main/docs/openai-mcp.adoc b/components/camel-ai/camel-openai/src/main/docs/openai-mcp.adoc
index 866621a92f51..1cf9e66a4115 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-mcp.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-mcp.adoc
@@ -522,15 +522,30 @@ YAML::
=== Error Handling in the Agentic Loop
+Two endpoint options control what happens when a tool call fails:
+
+* **`toolExecutionErrorStrategy`** (default `repromptModel`): controls what happens when a tool
+ execution throws an exception (including malformed tool arguments).
+ - `repromptModel` -- catches the error, logs a WARN, and sends it back to the model as a tool
+ result so the model can attempt recovery.
+ - `failExchange` -- propagates the exception to the Camel exchange so that standard Camel error
+ handling (`onException`, dead-letter channel) can process it.
+* **`hallucinatedToolNameStrategy`** (default `failExchange`): controls what happens when the
+ model requests a tool that does not exist in any configured MCP server.
+ - `failExchange` -- throws an `IllegalStateException`, failing the exchange immediately.
+ - `repromptModel` -- sends a corrective tool result listing the available tools so the model
+ can self-correct and retry with a valid tool name. The `maxToolIterations` option bounds
+ the number of retries.
+
[cols="1,3"]
|===
| Scenario | Behavior
| MCP client initialization failure | Route fails to start (`RuntimeException` during `doStart()`)
-| Tool execution throws an exception | Error is caught, logged as WARN, and sent as tool result text to the model
-| MCP transport error (`mcpReconnect=true`) | Automatic reconnection and retry (once). If retry fails, error is sent to the model
+| Tool execution throws an exception | Governed by `toolExecutionErrorStrategy`: `repromptModel` (default) catches the error, logs it as WARN, and sends it as tool result text to the model; `failExchange` propagates the exception to the exchange
+| MCP transport error (`mcpReconnect=true`) | Automatic reconnection and retry (once). If retry fails, behavior follows `toolExecutionErrorStrategy`
| MCP `CallToolResult.isError()` is true | Error content is sent as tool result text to the model
-| Tool name not found in any server | `IllegalStateException` is thrown
+| Tool name not found in any server | Governed by `hallucinatedToolNameStrategy`: `failExchange` (default) throws `IllegalStateException`; `repromptModel` sends a corrective tool result listing available tools
| Max iterations exceeded | `IllegalStateException` is thrown with the tool call log
| Streaming + MCP tools with autoToolExecution | Falls back to non-streaming (logged as INFO)
|===
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/HallucinatedToolNameStrategy.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/HallucinatedToolNameStrategy.java
new file mode 100644
index 000000000000..fd05e709ec1c
--- /dev/null
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/HallucinatedToolNameStrategy.java
@@ -0,0 +1,36 @@
+/*
+ * 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.openai;
+
+/**
+ * Strategy for handling tool names hallucinated by the model (i.e. the model requests a tool that does not exist in any
+ * configured MCP server).
+ */
+public enum HallucinatedToolNameStrategy {
+
+ /**
+ * Throw an {@link IllegalStateException}, failing the exchange immediately. This is the default behavior.
+ */
+ FAIL_EXCHANGE,
+
+ /**
+ * Send a corrective tool result back to the model listing the available tools, giving the model a chance to
+ * self-correct and retry with a valid tool name. The agentic loop's {@code maxToolIterations} bounds the number of
+ * retries.
+ */
+ REPROMPT_MODEL
+}
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
index 51c80de213f3..d556f0a39022 100644
--- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
@@ -208,6 +208,21 @@ public class OpenAIConfiguration implements Cloneable {
+ "and retry the call once.")
private boolean mcpReconnect = true;
+ @UriParam(enums = "repromptModel,failExchange", defaultValue = "repromptModel")
+ @Metadata(description = "Strategy for handling exceptions thrown during MCP tool execution. "
+ + "'repromptModel' (default) catches the error and sends it back to the model as a tool result "
+ + "so the model can attempt to recover. "
+ + "'failExchange' propagates the exception to the Camel exchange so that standard Camel "
+ + "error handling (onException, dead-letter channel) can process it.")
+ private ToolExecutionErrorStrategy toolExecutionErrorStrategy = ToolExecutionErrorStrategy.REPROMPT_MODEL;
+
+ @UriParam(enums = "failExchange,repromptModel", defaultValue = "failExchange")
+ @Metadata(description = "Strategy for handling tool names hallucinated by the model (tool not found in any MCP server). "
+ + "'failExchange' (default) throws an IllegalStateException, failing the exchange immediately. "
+ + "'repromptModel' sends a corrective tool result listing the available tools so the model "
+ + "can self-correct and retry. The maxToolIterations option bounds retries.")
+ private HallucinatedToolNameStrategy hallucinatedToolNameStrategy = HallucinatedToolNameStrategy.FAIL_EXCHANGE;
+
// ========== EMBEDDINGS CONFIGURATION ==========
@UriParam
@@ -720,6 +735,22 @@ public class OpenAIConfiguration implements Cloneable {
this.mcpReconnect = mcpReconnect;
}
+ public ToolExecutionErrorStrategy getToolExecutionErrorStrategy() {
+ return toolExecutionErrorStrategy;
+ }
+
+ public void setToolExecutionErrorStrategy(ToolExecutionErrorStrategy toolExecutionErrorStrategy) {
+ this.toolExecutionErrorStrategy = toolExecutionErrorStrategy;
+ }
+
+ public HallucinatedToolNameStrategy getHallucinatedToolNameStrategy() {
+ return hallucinatedToolNameStrategy;
+ }
+
+ public void setHallucinatedToolNameStrategy(HallucinatedToolNameStrategy hallucinatedToolNameStrategy) {
+ this.hallucinatedToolNameStrategy = hallucinatedToolNameStrategy;
+ }
+
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
index 282e9b022311..d36c7d6e48e2 100644
--- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
@@ -545,8 +545,18 @@ public class OpenAIProducer extends DefaultAsyncProducer {
McpToolState mcpToolState = getEndpoint().getMcpToolState();
McpSyncClient mcpClient = mcpToolState.toolClientMap().get(toolName);
if (mcpClient == null) {
- throw new IllegalStateException(
- "Tool '" + toolName + "' not found in any configured MCP server");
+ if (config.getHallucinatedToolNameStrategy() == HallucinatedToolNameStrategy.FAIL_EXCHANGE) {
+ throw new IllegalStateException(
+ "Tool '" + toolName + "' not found in any configured MCP server");
+ }
+ // repromptModel: send a corrective tool result listing available tools
+ String available = String.join(", ", mcpToolState.toolClientMap().keySet());
+ String errorMsg = "Error: tool '" + toolName
+ + "' does not exist. Available tools: " + available;
+ LOG.warn("Hallucinated tool name '{}', sending corrective result to model", toolName);
+ batchResults.add(new ToolResultEntry(toolCallId, errorMsg));
+ allReturnDirect = false;
+ continue;
}
LOG.debug("Executing MCP tool '{}' with args: {}", toolName, argsJson);
@@ -567,10 +577,16 @@ public class OpenAIProducer extends DefaultAsyncProducer {
}
}
} catch (JsonProcessingException e) {
+ if (config.getToolExecutionErrorStrategy() == ToolExecutionErrorStrategy.FAIL_EXCHANGE) {
+ throw e;
+ }
LOG.warn("Invalid tool arguments for '{}': {}", toolName, argsJson, e);
resultContent = "Error: invalid tool arguments: " + e.getMessage();
allReturnDirect = false;
} catch (Exception e) {
+ if (config.getToolExecutionErrorStrategy() == ToolExecutionErrorStrategy.FAIL_EXCHANGE) {
+ throw e;
+ }
LOG.warn("MCP tool '{}' execution failed: {}", toolName, e.getMessage(), e);
resultContent = "Error: Tool execution failed: " + e.getMessage();
allReturnDirect = false;
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIToolExecutionProducer.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIToolExecutionProducer.java
index e5a01352dddf..a5313f0d7089 100644
--- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIToolExecutionProducer.java
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIToolExecutionProducer.java
@@ -139,8 +139,22 @@ public class OpenAIToolExecutionProducer extends DefaultProducer {
McpToolState mcpToolState = getEndpoint().getMcpToolState();
McpSyncClient mcpClient = mcpToolState.toolClientMap().get(toolName);
if (mcpClient == null) {
- throw new IllegalStateException(
- "Tool '" + toolName + "' not found in any configured MCP server");
+ if (config.getHallucinatedToolNameStrategy() == HallucinatedToolNameStrategy.FAIL_EXCHANGE) {
+ throw new IllegalStateException(
+ "Tool '" + toolName + "' not found in any configured MCP server");
+ }
+ // repromptModel: send a corrective tool result listing available tools
+ String available = String.join(", ", mcpToolState.toolClientMap().keySet());
+ String errorMsg = "Error: tool '" + toolName
+ + "' does not exist. Available tools: " + available;
+ LOG.warn("Hallucinated tool name '{}', sending corrective result to model", toolName);
+ history.add(ChatCompletionMessageParam.ofTool(
+ ChatCompletionToolMessageParam.builder()
+ .toolCallId(toolCallId)
+ .content(errorMsg)
+ .build()));
+ executedCount++;
+ continue;
}
String resultContent;
@@ -157,9 +171,15 @@ public class OpenAIToolExecutionProducer extends DefaultProducer {
resultContent = extractTextContent(toolResult.content());
}
} catch (JsonProcessingException e) {
+ if (config.getToolExecutionErrorStrategy() == ToolExecutionErrorStrategy.FAIL_EXCHANGE) {
+ throw e;
+ }
LOG.warn("Invalid tool arguments for '{}': {}", toolName, argsJson, e);
resultContent = "Error: invalid tool arguments: " + e.getMessage();
} catch (Exception e) {
+ if (config.getToolExecutionErrorStrategy() == ToolExecutionErrorStrategy.FAIL_EXCHANGE) {
+ throw e;
+ }
LOG.warn("MCP tool '{}' execution failed: {}", toolName, e.getMessage(), e);
resultContent = "Error: Tool execution failed: " + e.getMessage();
}
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/ToolExecutionErrorStrategy.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/ToolExecutionErrorStrategy.java
new file mode 100644
index 000000000000..889b9f511b8d
--- /dev/null
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/ToolExecutionErrorStrategy.java
@@ -0,0 +1,35 @@
+/*
+ * 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.openai;
+
+/**
+ * Strategy for handling exceptions thrown during MCP tool execution in the agentic loop.
+ */
+public enum ToolExecutionErrorStrategy {
+
+ /**
+ * Catch the exception, format it as a tool result error message, and send it back to the model so it can attempt to
+ * recover. This is the default behavior.
+ */
+ REPROMPT_MODEL,
+
+ /**
+ * Propagate the exception to the Camel exchange so that standard Camel error handling ({@code onException},
+ * dead-letter channel) can process it.
+ */
+ FAIL_EXCHANGE
+}
diff --git a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIToolErrorStrategyTest.java b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIToolErrorStrategyTest.java
new file mode 100644
index 000000000000..8cb0cad96b61
--- /dev/null
+++ b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIToolErrorStrategyTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.openai;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.client.McpSyncClient;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.infra.openai.mock.OpenAIMock;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the configurable tool error strategies introduced by CAMEL-23963.
+ *
+ * <p>
+ * Two endpoint options are tested:
+ * <ul>
+ * <li>{@code hallucinatedToolNameStrategy} -- controls what happens when the model requests a tool that does not exist
+ * in any configured MCP server.</li>
+ * <li>{@code toolExecutionErrorStrategy} -- controls what happens when a tool execution throws an exception.</li>
+ * </ul>
+ */
+class OpenAIToolErrorStrategyTest extends CamelTestSupport {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final AtomicInteger hallucinatedCallCounter = new AtomicInteger();
+ private final AtomicInteger failExchangeCallCounter = new AtomicInteger();
+
+ @RegisterExtension
+ public OpenAIMock openAIMock = new OpenAIMock().builder()
+ // hallucinated tool: first call returns a tool call with a non-existent tool name,
+ // second call (after corrective feedback) returns the final answer
+ .when("use hallucinated tool")
+ .thenRespondWith((exchange, input) -> {
+ try {
+ if (hallucinatedCallCounter.getAndIncrement() == 0) {
+ return toolCallResponse("nonexistent_tool", "{\"arg\": \"value\"}");
+ }
+ return simpleTextResponse("Recovered after hallucinated tool correction");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .end()
+ // tool execution error with failExchange: first call returns a tool call that will throw
+ .when("trigger tool error")
+ .thenRespondWith((exchange, input) -> {
+ try {
+ if (failExchangeCallCounter.getAndIncrement() == 0) {
+ return toolCallResponse("get_weather", "{\"city\": \"London\"}");
+ }
+ return simpleTextResponse("Should not reach here");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .end()
+ .build();
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // Route with hallucinatedToolNameStrategy=repromptModel
+ from("direct:hallucinated-reprompt")
+ .to("openai:chat-completion?model=gpt-5&apiKey=dummy&autoToolExecution=true"
+ + "&hallucinatedToolNameStrategy=repromptModel&baseUrl="
+ + openAIMock.getBaseUrl() + "/v1");
+
+ // Route with toolExecutionErrorStrategy=failExchange
+ from("direct:error-fail-exchange")
+ .to("openai:chat-completion?model=gpt-5&apiKey=dummy&autoToolExecution=true"
+ + "&toolExecutionErrorStrategy=failExchange&baseUrl="
+ + openAIMock.getBaseUrl() + "/v1");
+ }
+ };
+ }
+
+ @Test
+ void hallucinatedToolNameRepromptModelSendsCorrectiveResultToModel() {
+ // Set up a real tool so the model can self-correct
+ McpSyncClient client = mock(McpSyncClient.class);
+ McpSchema.CallToolResult toolResult = McpSchema.CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent(null, "Sunny", null)))
+ .isError(false)
+ .build();
+ when(client.callTool(any(McpSchema.CallToolRequest.class))).thenReturn(toolResult);
+
+ String endpointUri = "openai:chat-completion?model=gpt-5&apiKey=dummy&autoToolExecution=true"
+ + "&hallucinatedToolNameStrategy=repromptModel&baseUrl="
+ + openAIMock.getBaseUrl() + "/v1";
+ OpenAIEndpoint endpoint = context.getEndpoint(endpointUri, OpenAIEndpoint.class);
+ List<McpSchema.Tool> mcpTools = List.of(
+ McpSchema.Tool.builder("get_weather", Map.of("type", "object"))
+ .description("Mock tool: get_weather")
+ .build());
+ endpoint.setMcpToolState(new McpToolState(
+ McpToolConverter.convert(mcpTools),
+ Map.of("get_weather", client),
+ Map.of(),
+ Set.of()));
+
+ Exchange result = template.request("direct:hallucinated-reprompt",
+ e -> e.getIn().setBody("use hallucinated tool"));
+
+ assertThat(result.getException())
+ .as("With repromptModel, hallucinated tool names should not crash the exchange")
+ .isNull();
+ assertThat(result.getMessage().getBody(String.class))
+ .isEqualTo("Recovered after hallucinated tool correction");
+ }
+
+ @Test
+ void hallucinatedToolNameDefaultFailExchangeThrowsException() {
+ // Use the default strategy (failExchange) via a separate endpoint
+ McpSyncClient client = mock(McpSyncClient.class);
+ String defaultUri = "openai:chat-completion?model=gpt-5&apiKey=dummy&autoToolExecution=true&baseUrl="
+ + openAIMock.getBaseUrl() + "/v1";
+ OpenAIEndpoint endpoint = context.getEndpoint(defaultUri, OpenAIEndpoint.class);
+ List<McpSchema.Tool> mcpTools = List.of(
+ McpSchema.Tool.builder("get_weather", Map.of("type", "object"))
+ .description("Mock tool: get_weather")
+ .build());
+ endpoint.setMcpToolState(new McpToolState(
+ McpToolConverter.convert(mcpTools),
+ Map.of("get_weather", client),
+ Map.of(),
+ Set.of()));
+
+ // The default hallucinatedToolNameStrategy is failExchange, so this should throw
+ assertThatThrownBy(() -> template.requestBody(defaultUri, "use hallucinated tool"))
+ .isInstanceOf(CamelExecutionException.class)
+ .hasCauseInstanceOf(IllegalStateException.class)
+ .cause()
+ .hasMessageContaining("not found in any configured MCP server");
+ }
+
+ @Test
+ void toolExecutionErrorFailExchangePropagatesToExchange() {
+ McpSyncClient client = mock(McpSyncClient.class);
+ when(client.callTool(any(McpSchema.CallToolRequest.class)))
+ .thenThrow(new RuntimeException("Connection refused to weather service"));
+
+ String endpointUri = "openai:chat-completion?model=gpt-5&apiKey=dummy&autoToolExecution=true"
+ + "&toolExecutionErrorStrategy=failExchange&baseUrl="
+ + openAIMock.getBaseUrl() + "/v1";
+ OpenAIEndpoint endpoint = context.getEndpoint(endpointUri, OpenAIEndpoint.class);
+ List<McpSchema.Tool> mcpTools = List.of(
+ McpSchema.Tool.builder("get_weather", Map.of("type", "object"))
+ .description("Mock tool: get_weather")
+ .build());
+ endpoint.setMcpToolState(new McpToolState(
+ McpToolConverter.convert(mcpTools),
+ Map.of("get_weather", client),
+ Map.of(),
+ Set.of()));
+
+ assertThatThrownBy(() -> template.requestBody("direct:error-fail-exchange", "trigger tool error"))
+ .isInstanceOf(CamelExecutionException.class)
+ .hasCauseInstanceOf(RuntimeException.class)
+ .cause()
+ .hasMessageContaining("Connection refused to weather service");
+ }
+
+ // ---------- helpers ----------
+
+ private static String toolCallResponse(String toolName, String arguments) throws Exception {
+ Map<String, Object> function = new HashMap<>();
+ function.put("name", toolName);
+ function.put("arguments", arguments);
+
+ Map<String, Object> toolCall = new HashMap<>();
+ toolCall.put("id", UUID.randomUUID().toString());
+ toolCall.put("type", "function");
+ toolCall.put("function", function);
+
+ Map<String, Object> message = new HashMap<>();
+ message.put("role", "assistant");
+ message.put("content", null);
+ message.put("tool_calls", List.of(toolCall));
+
+ return chatCompletion(message, "tool_calls");
+ }
+
+ private static String simpleTextResponse(String content) throws Exception {
+ Map<String, Object> message = new HashMap<>();
+ message.put("role", "assistant");
+ message.put("content", content);
+ return chatCompletion(message, "stop");
+ }
+
+ private static String chatCompletion(Map<String, Object> message, String finishReason) throws Exception {
+ Map<String, Object> choice = new HashMap<>();
+ choice.put("finish_reason", finishReason);
+ choice.put("index", 0);
+ choice.put("message", message);
+
+ Map<String, Object> completion = new HashMap<>();
+ completion.put("id", UUID.randomUUID().toString());
+ completion.put("choices", List.of(choice));
+ completion.put("created", System.currentTimeMillis() / 1000L);
+ completion.put("model", "openai-mock");
+ completion.put("object", "chat.completion");
+ return MAPPER.writeValueAsString(completion);
+ }
+}