This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 289ff4b81dd2 CAMEL-23394: Limit OpenAI conversation history size
289ff4b81dd2 is described below
commit 289ff4b81dd2adeaec13d2f461a0714b52328790
Author: Omar Atie <[email protected]>
AuthorDate: Tue Jul 21 00:01:58 2026 -0700
CAMEL-23394: Limit OpenAI conversation history size
Add maxHistoryMessages and maxHistoryTokens options to camel-openai to
bound conversation history growth. Uses segment-based trimming that
preserves tool-call/tool-result message pairs, with long-based token
estimation that handles image payloads. Always retains the most recent
segment even if it alone exceeds the token budget.
Closes #24901
Co-Authored-By: Cursor <[email protected]>
---
.../apache/camel/catalog/components/openai.json | 64 ++---
.../component/openai/OpenAIEndpointConfigurer.java | 12 +
.../component/openai/OpenAIEndpointUriFactory.java | 4 +-
.../org/apache/camel/component/openai/openai.json | 64 ++---
.../src/main/docs/openai-component.adoc | 2 +
.../component/openai/OpenAIConfiguration.java | 32 +++
.../openai/OpenAIConversationHistoryTrimmer.java | 248 +++++++++++++++++++
.../camel/component/openai/OpenAIProducer.java | 22 +-
.../openai/OpenAIToolExecutionProducer.java | 2 +-
.../openai/OpenAIConversationHistorySizeTest.java | 104 ++++++++
.../OpenAIConversationHistoryTokenLimitTest.java | 110 +++++++++
.../OpenAIConversationHistoryTrimmerTest.java | 274 +++++++++++++++++++++
.../endpoint/dsl/OpenAIEndpointBuilderFactory.java | 82 ++++++
13 files changed, 949 insertions(+), 71 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
index 3784ba5f8579..08defe1693e5 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
@@ -96,36 +96,38 @@
"embeddingModel": { "index": 17, "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": 18, "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 [...]
"jsonSchema": { "index": 19, "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" },
- "maxRetries": { "index": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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" },
- "requestTimeout": { "index": 29, "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 [...]
- "storeFullResponse": { "index": 30, "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 response in the exchange
property 'CamelOpenA [...]
- "streaming": { "index": 31, "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": 32, "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": 33, "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": 34, "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": 35, "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": 36, "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": 37, "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": 38, "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": 39, "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": 40, "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": 41, "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": 42, "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": 43, "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": 44, "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": 45, "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": 46, "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": 47, "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": 48, "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": 49, "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": 50, "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" }
+ "maxHistoryMessages": { "index": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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": 30, "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" },
+ "requestTimeout": { "index": 31, "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 [...]
+ "storeFullResponse": { "index": 32, "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 response in the exchange
property 'CamelOpenA [...]
+ "streaming": { "index": 33, "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": 34, "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": 35, "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": 36, "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": 37, "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": 38, "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": 39, "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": 40, "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": 41, "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": 42, "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": 43, "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": 44, "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": 45, "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": 46, "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": 47, "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": 48, "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": 49, "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": 50, "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": 51, "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": 52, "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/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 4b16f943cc3a..59cbe85688bf 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,10 @@ public class OpenAIEndpointConfigurer extends
PropertyConfigurerSupport implemen
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 "maxhistorymessages":
+ case "maxHistoryMessages":
target.getConfiguration().setMaxHistoryMessages(property(camelContext,
int.class, value)); return true;
+ case "maxhistorytokens":
+ case "maxHistoryTokens":
target.getConfiguration().setMaxHistoryTokens(property(camelContext, int.class,
value)); return true;
case "maxretries":
case "maxRetries":
target.getConfiguration().setMaxRetries(property(camelContext, int.class,
value)); return true;
case "maxtokens":
@@ -165,6 +169,10 @@ public class OpenAIEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "jsonSchema": return java.lang.String.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
+ case "maxhistorymessages":
+ case "maxHistoryMessages": return int.class;
+ case "maxhistorytokens":
+ case "maxHistoryTokens": return int.class;
case "maxretries":
case "maxRetries": return int.class;
case "maxtokens":
@@ -269,6 +277,10 @@ public class OpenAIEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "jsonSchema": return target.getConfiguration().getJsonSchema();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
+ case "maxhistorymessages":
+ case "maxHistoryMessages": return
target.getConfiguration().getMaxHistoryMessages();
+ case "maxhistorytokens":
+ case "maxHistoryTokens": return
target.getConfiguration().getMaxHistoryTokens();
case "maxretries":
case "maxRetries": return target.getConfiguration().getMaxRetries();
case "maxtokens":
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 98171fe606ea..75f9eae5ba4b 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<>(51);
+ Set<String> props = new HashSet<>(53);
props.add("additionalBodyProperty");
props.add("additionalHeader");
props.add("additionalResponseHeader");
@@ -45,6 +45,8 @@ public class OpenAIEndpointUriFactory extends
org.apache.camel.support.component
props.add("encodingFormat");
props.add("jsonSchema");
props.add("lazyStartProducer");
+ props.add("maxHistoryMessages");
+ props.add("maxHistoryTokens");
props.add("maxRetries");
props.add("maxTokens");
props.add("maxToolIterations");
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 3784ba5f8579..08defe1693e5 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
@@ -96,36 +96,38 @@
"embeddingModel": { "index": 17, "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": 18, "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 [...]
"jsonSchema": { "index": 19, "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" },
- "maxRetries": { "index": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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" },
- "requestTimeout": { "index": 29, "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 [...]
- "storeFullResponse": { "index": 30, "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 response in the exchange
property 'CamelOpenA [...]
- "streaming": { "index": 31, "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": 32, "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": 33, "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": 34, "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": 35, "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": 36, "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": 37, "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": 38, "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": 39, "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": 40, "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": 41, "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": 42, "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": 43, "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": 44, "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": 45, "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": 46, "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": 47, "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": 48, "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": 49, "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": 50, "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" }
+ "maxHistoryMessages": { "index": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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": 30, "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" },
+ "requestTimeout": { "index": 31, "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 [...]
+ "storeFullResponse": { "index": 32, "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 response in the exchange
property 'CamelOpenA [...]
+ "streaming": { "index": 33, "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": 34, "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": 35, "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": 36, "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": 37, "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": 38, "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": 39, "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": 40, "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": 41, "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": 42, "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": 43, "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": 44, "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": 45, "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": 46, "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": 47, "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": 48, "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": 49, "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": 50, "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": 51, "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": 52, "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-component.adoc
b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
index ee3cac44cb51..9a6c87ccf537 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
@@ -672,6 +672,8 @@ The JSON schema must be a valid JSON object. Invalid schema
strings will result
When `conversationMemory=true`, the component maintains conversation history
in the `CamelOpenAIConversationHistory` exchange property (configurable via
`conversationHistoryProperty` option). This history is scoped to a single
Exchange and allows multi-turn conversations within a route.
+Use `maxHistoryMessages` and `maxHistoryTokens` to bound how much history is
retained in that property. Oldest conversation segments are dropped first;
assistant tool-call blocks are always removed together with their tool results.
The most recent segment is always kept, even when it alone exceeds
`maxHistoryMessages` or `maxHistoryTokens`. Token limits use a character-count
/ 4 estimate (including image payload size for multi-modal user messages).
+
IMPORTANT:
* Conversation history is automatically updated with each assistant response
for **non-streaming** responses only
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 21ca68ffcc4a..66dc4ee206af 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
@@ -101,6 +101,22 @@ public class OpenAIConfiguration implements Cloneable {
@Metadata(description = "Exchange property name for storing conversation
history")
private String conversationHistoryProperty =
"CamelOpenAIConversationHistory";
+ @UriParam(defaultValue = "0")
+ @Metadata(description = "When conversationMemory is enabled, retain at
most this many messages in the exchange "
+ + "conversation history. System and developer
messages are prepended separately and are not "
+ + "stored in history. Assistant tool-call blocks
are kept intact and may retain slightly "
+ + "more than this limit to preserve tool result
pairing. When 0, no message limit is applied.")
+ private int maxHistoryMessages;
+
+ @UriParam(defaultValue = "0")
+ @Metadata(description = "When conversationMemory is enabled, trim
conversation history using a token estimate "
+ + "(character count / 4, including image payload
size for multi-modal user messages). "
+ + "Oldest segments are dropped first until the
estimated tokens are within this limit. "
+ + "Assistant tool-call blocks are removed as a
unit with their tool results. The most recent "
+ + "segment is always retained, even when it alone
exceeds this limit. When 0, "
+ + "no token limit is applied.")
+ private int maxHistoryTokens;
+
@UriParam
@Metadata(description = "Default user message text to use when no prompt
is provided", largeInput = true)
private String userMessage;
@@ -382,6 +398,22 @@ public class OpenAIConfiguration implements Cloneable {
this.conversationHistoryProperty = conversationHistoryProperty;
}
+ public int getMaxHistoryMessages() {
+ return maxHistoryMessages;
+ }
+
+ public void setMaxHistoryMessages(int maxHistoryMessages) {
+ this.maxHistoryMessages = maxHistoryMessages;
+ }
+
+ public int getMaxHistoryTokens() {
+ return maxHistoryTokens;
+ }
+
+ public void setMaxHistoryTokens(int maxHistoryTokens) {
+ this.maxHistoryTokens = maxHistoryTokens;
+ }
+
public String getUserMessage() {
return userMessage;
}
diff --git
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConversationHistoryTrimmer.java
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConversationHistoryTrimmer.java
new file mode 100644
index 000000000000..39f63d1489ab
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConversationHistoryTrimmer.java
@@ -0,0 +1,248 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import com.openai.models.chat.completions.ChatCompletionAssistantMessageParam;
+import com.openai.models.chat.completions.ChatCompletionContentPart;
+import com.openai.models.chat.completions.ChatCompletionMessageParam;
+import com.openai.models.chat.completions.ChatCompletionMessageToolCall;
+import com.openai.models.chat.completions.ChatCompletionToolMessageParam;
+import com.openai.models.chat.completions.ChatCompletionUserMessageParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Applies sliding-window limits to exchange-scoped OpenAI conversation
history. Trimming removes whole segments from
+ * the oldest side so assistant tool-call blocks stay paired with their tool
result messages.
+ */
+final class OpenAIConversationHistoryTrimmer {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OpenAIConversationHistoryTrimmer.class);
+ private static final int CHARS_PER_TOKEN = 4;
+
+ private OpenAIConversationHistoryTrimmer() {
+ }
+
+ static List<ChatCompletionMessageParam> trim(
+ List<ChatCompletionMessageParam> history, OpenAIConfiguration
config) {
+ if (history == null) {
+ return null;
+ }
+ if (history.isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ int maxMessages = config.getMaxHistoryMessages();
+ int maxTokens = config.getMaxHistoryTokens();
+ if (maxMessages <= 0 && maxTokens <= 0) {
+ return history;
+ }
+
+ int originalSize = history.size();
+ List<Segment> segments = buildSegments(history);
+ int firstSegment = 0;
+
+ if (maxMessages > 0) {
+ firstSegment = findFirstSegmentForMessageLimit(segments,
maxMessages);
+ }
+
+ if (maxTokens > 0) {
+ firstSegment = Math.max(firstSegment,
+ findFirstSegmentForTokenLimit(history, segments,
maxTokens));
+ }
+
+ List<ChatCompletionMessageParam> trimmed;
+ if (firstSegment >= segments.size()) {
+ trimmed = new ArrayList<>();
+ } else {
+ Segment first = segments.get(firstSegment);
+ Segment last = segments.get(segments.size() - 1);
+ trimmed = new ArrayList<>(history.subList(first.start, last.end +
1));
+ }
+
+ int dropped = originalSize - trimmed.size();
+ if (dropped > 0) {
+ LOG.debug("Trimmed conversation history: dropped {} message(s),
retained {}", dropped, trimmed.size());
+ }
+
+ return trimmed;
+ }
+
+ static int estimateTokens(List<ChatCompletionMessageParam> messages) {
+ return estimateTokens(messages, 0, messages.size());
+ }
+
+ private static int estimateTokens(List<ChatCompletionMessageParam>
messages, int from, int to) {
+ long chars = 0;
+ for (int i = from; i < to; i++) {
+ chars += estimateMessageChars(messages.get(i));
+ }
+ return tokensFromChars(chars);
+ }
+
+ private static int tokensFromChars(long chars) {
+ if (chars <= 0) {
+ return 0;
+ }
+ long maxChars = (long) Integer.MAX_VALUE * CHARS_PER_TOKEN;
+ if (chars > maxChars) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) ((chars + CHARS_PER_TOKEN - 1) / CHARS_PER_TOKEN);
+ }
+
+ private static int findFirstSegmentForMessageLimit(List<Segment> segments,
int maxMessages) {
+ int firstSegment = segments.size() - 1;
+ int messageCount = segments.get(firstSegment).messageCount();
+ while (firstSegment > 0) {
+ Segment previous = segments.get(firstSegment - 1);
+ int nextCount = messageCount + previous.messageCount();
+ if (nextCount > maxMessages) {
+ break;
+ }
+ firstSegment--;
+ messageCount = nextCount;
+ }
+ return firstSegment;
+ }
+
+ private static int findFirstSegmentForTokenLimit(
+ List<ChatCompletionMessageParam> history, List<Segment> segments,
int maxTokens) {
+ int lastSegment = segments.size() - 1;
+ Segment last = segments.get(lastSegment);
+ int lastSegmentTokens = estimateTokens(history, last.start, last.end +
1);
+ if (lastSegmentTokens > maxTokens) {
+ LOG.warn(
+ "Last conversation segment alone exceeds maxHistoryTokens
(estimated {} > {}); "
+ + "retaining it anyway",
+ lastSegmentTokens, maxTokens);
+ return lastSegment;
+ }
+
+ for (int firstSegment = 0; firstSegment < segments.size();
firstSegment++) {
+ Segment first = segments.get(firstSegment);
+ if (estimateTokens(history, first.start, last.end + 1) <=
maxTokens) {
+ return firstSegment;
+ }
+ }
+ return lastSegment;
+ }
+
+ private static List<Segment>
buildSegments(List<ChatCompletionMessageParam> history) {
+ List<Segment> segments = new ArrayList<>();
+ int index = 0;
+ while (index < history.size()) {
+ ChatCompletionMessageParam message = history.get(index);
+ if (isAssistantWithToolCalls(message)) {
+ int end = index;
+ while (end + 1 < history.size() && history.get(end +
1).tool().isPresent()) {
+ end++;
+ }
+ segments.add(new Segment(index, end));
+ index = end + 1;
+ } else {
+ segments.add(new Segment(index, index));
+ index++;
+ }
+ }
+ return segments;
+ }
+
+ private static boolean isAssistantWithToolCalls(ChatCompletionMessageParam
message) {
+ return message.assistant()
+ .map(assistant ->
!assistant.toolCalls().orElse(List.of()).isEmpty())
+ .orElse(false);
+ }
+
+ private static int estimateMessageChars(ChatCompletionMessageParam
message) {
+ return message.user()
+
.map(OpenAIConversationHistoryTrimmer::estimateUserMessageChars)
+ .orElseGet(() -> message.assistant()
+
.map(OpenAIConversationHistoryTrimmer::estimateAssistantMessageChars)
+ .orElseGet(() -> message.tool()
+
.map(OpenAIConversationHistoryTrimmer::estimateToolMessageChars)
+ .orElse(0)));
+ }
+
+ private static int estimateUserMessageChars(ChatCompletionUserMessageParam
user) {
+ ChatCompletionUserMessageParam.Content content = user.content();
+ if (content.isText()) {
+ return content.asText().length();
+ }
+ if (content.isArrayOfContentParts()) {
+ return content.asArrayOfContentParts().stream()
+
.mapToInt(OpenAIConversationHistoryTrimmer::estimateContentPartChars)
+ .sum();
+ }
+ return 0;
+ }
+
+ private static int
estimateAssistantMessageChars(ChatCompletionAssistantMessageParam assistant) {
+ int chars = assistant.content()
+ .filter(ChatCompletionAssistantMessageParam.Content::isText)
+ .map(content -> content.asText().length())
+ .orElse(0);
+
+ for (ChatCompletionMessageToolCall toolCall :
assistant.toolCalls().orElse(List.of())) {
+ chars += toolCall.asFunction().function().name().length();
+ chars += toolCall.asFunction().function().arguments().length();
+ }
+
+ return chars;
+ }
+
+ private static int estimateToolMessageChars(ChatCompletionToolMessageParam
tool) {
+ ChatCompletionToolMessageParam.Content content = tool.content();
+ if (content.isText()) {
+ return content.asText().length();
+ }
+ if (content.isArrayOfContentParts()) {
+ return content.asArrayOfContentParts().stream()
+ .mapToInt(part -> part.text().length())
+ .sum();
+ }
+ return 0;
+ }
+
+ private static int estimateContentPartChars(ChatCompletionContentPart
part) {
+ int chars = 0;
+ if (part.text().isPresent()) {
+ chars += part.asText().text().length();
+ }
+ if (part.imageUrl().isPresent()) {
+ chars += part.asImageUrl().imageUrl().url().length();
+ }
+ return chars;
+ }
+
+ private static final class Segment {
+ private final int start;
+ private final int end;
+
+ private Segment(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ private int messageCount() {
+ return end - start + 1;
+ }
+ }
+}
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 37a79794a6ab..a9493c2d50dc 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
@@ -272,11 +272,16 @@ public class OpenAIProducer extends DefaultAsyncProducer {
return;
}
- List<ChatCompletionMessageParam> history =
in.getExchange().getProperty(
+ Exchange exchange = in.getExchange();
+ List<ChatCompletionMessageParam> history = exchange.getProperty(
config.getConversationHistoryProperty(),
List.class);
if (history != null) {
- messages.addAll(history);
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config);
+ if (trimmed.size() != history.size()) {
+ exchange.setProperty(config.getConversationHistoryProperty(),
trimmed);
+ }
+ messages.addAll(trimmed);
}
}
@@ -726,7 +731,8 @@ public class OpenAIProducer extends DefaultAsyncProducer {
.build());
history.add(assistantMessage);
- exchange.setProperty(config.getConversationHistoryProperty(), history);
+ exchange.setProperty(config.getConversationHistoryProperty(),
+ OpenAIConversationHistoryTrimmer.trim(history, config));
}
private void updateConversationHistory(
@@ -758,9 +764,10 @@ public class OpenAIProducer extends DefaultAsyncProducer {
.build());
history.add(assistantMessage);
- exchange.setProperty(config.getConversationHistoryProperty(), history);
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config);
+ exchange.setProperty(config.getConversationHistoryProperty(), trimmed);
LOG.debug("Updated conversation history with {} agentic messages +
final response, total entries: {}",
- agenticMessages.size(), history.size());
+ agenticMessages.size(), trimmed.size());
}
private void updateConversationHistory(
@@ -791,9 +798,10 @@ public class OpenAIProducer extends DefaultAsyncProducer {
.build());
history.add(assistantMessage);
- exchange.setProperty(config.getConversationHistoryProperty(), history);
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config);
+ exchange.setProperty(config.getConversationHistoryProperty(), trimmed);
LOG.debug("Updated conversation history with {} agentic messages +
returnDirect result, total entries: {}",
- agenticMessages.size(), history.size());
+ agenticMessages.size(), trimmed.size());
}
private ResponseFormatJsonSchema.JsonSchema.Schema
buildSchemaFromJson(String jsonSchemaString) throws Exception {
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 857e0e04f1f1..e5a01352dddf 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
@@ -173,7 +173,7 @@ public class OpenAIToolExecutionProducer extends
DefaultProducer {
}
// Update conversation history and clear body for the next
chat-completion call
- exchange.setProperty(historyProperty, history);
+ exchange.setProperty(historyProperty,
OpenAIConversationHistoryTrimmer.trim(history, config));
exchange.getMessage().setBody(null);
exchange.getMessage().setHeader(OpenAIConstants.TOOL_ITERATIONS,
executedCount);
}
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistorySizeTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistorySizeTest.java
new file mode 100644
index 000000000000..9055b6b76232
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistorySizeTest.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.openai;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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;
+
+class OpenAIConversationHistorySizeTest extends CamelTestSupport {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final AtomicReference<String> fourthRequestBody = new
AtomicReference<>();
+
+ @RegisterExtension
+ public OpenAIMock openAIMock = new OpenAIMock().builder()
+ .when("turn-1")
+ .replyWith("reply-1")
+ .end()
+ .when("turn-2")
+ .replyWith("reply-2")
+ .end()
+ .when("turn-3")
+ .replyWith("reply-3")
+ .end()
+ .when("turn-4")
+ .assertRequest(fourthRequestBody::set)
+ .replyWith("reply-4")
+ .end()
+ .build();
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ String endpoint =
"openai:chat-completion?model=gpt-5&apiKey=dummy&conversationMemory=true"
+ + "&maxHistoryMessages=2&baseUrl=" +
openAIMock.getBaseUrl() + "/v1";
+
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:multi-turn")
+ .to(endpoint)
+ .setBody(constant("turn-2"))
+ .to(endpoint)
+ .setBody(constant("turn-3"))
+ .to(endpoint)
+ .setBody(constant("turn-4"))
+ .to(endpoint);
+ }
+ };
+ }
+
+ @Test
+ void
maxHistoryMessagesShouldRetainOnlyRecentHistoryInRequestsAndExchangeProperty()
throws Exception {
+ Exchange result = template.request("direct:multi-turn", e ->
e.getIn().setBody("turn-1"));
+
+ assertThat(result.getException()).isNull();
+
assertThat(result.getMessage().getBody(String.class)).isEqualTo("reply-4");
+
+ @SuppressWarnings("unchecked")
+ List<Object> history =
result.getProperty("CamelOpenAIConversationHistory", List.class);
+ assertThat(history).hasSize(2);
+
+ String request = fourthRequestBody.get();
+ assertThat(request).isNotNull();
+
+ JsonNode messages = MAPPER.readTree(request).get("messages");
+ assertThat(messages).isNotNull();
+ assertThat(messages)
+ .noneMatch(message ->
"turn-1".equals(message.path("content").asText())
+ || "reply-1".equals(message.path("content").asText())
+ || "turn-2".equals(message.path("content").asText())
+ || "reply-2".equals(message.path("content").asText()));
+ assertThat(messages)
+ .anyMatch(message ->
"turn-3".equals(message.path("content").asText()));
+ assertThat(messages)
+ .anyMatch(message ->
"reply-3".equals(message.path("content").asText()));
+ assertThat(messages)
+ .anyMatch(message ->
"turn-4".equals(message.path("content").asText()));
+ }
+}
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistoryTokenLimitTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistoryTokenLimitTest.java
new file mode 100644
index 000000000000..207227183703
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistoryTokenLimitTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.openai.models.chat.completions.ChatCompletionMessageParam;
+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;
+
+class OpenAIConversationHistoryTokenLimitTest extends CamelTestSupport {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final String TURN_1 = "turn-1" + "a".repeat(34);
+ private static final String TURN_2 = "turn-2" + "b".repeat(34);
+ private static final String TURN_3 = "turn-3" + "c".repeat(34);
+ private static final String TURN_4 = "turn-4" + "d".repeat(34);
+
+ private final AtomicReference<String> fourthRequestBody = new
AtomicReference<>();
+
+ @RegisterExtension
+ public OpenAIMock openAIMock = new OpenAIMock().builder()
+ .when(TURN_1)
+ .replyWith("reply-1")
+ .end()
+ .when(TURN_2)
+ .replyWith("reply-2")
+ .end()
+ .when(TURN_3)
+ .replyWith("reply-3")
+ .end()
+ .when(TURN_4)
+ .assertRequest(fourthRequestBody::set)
+ .replyWith("reply-4")
+ .end()
+ .build();
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ String endpoint =
"openai:chat-completion?model=gpt-5&apiKey=dummy&conversationMemory=true"
+ + "&maxHistoryTokens=15&baseUrl=" +
openAIMock.getBaseUrl() + "/v1";
+
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:token-limited")
+ .to(endpoint)
+ .setBody(constant(TURN_2))
+ .to(endpoint)
+ .setBody(constant(TURN_3))
+ .to(endpoint)
+ .setBody(constant(TURN_4))
+ .to(endpoint);
+ }
+ };
+ }
+
+ @Test
+ void maxHistoryTokensShouldTrimHistoryAndAllowSubsequentCalls() throws
Exception {
+ Exchange result = template.request("direct:token-limited", e ->
e.getIn().setBody(TURN_1));
+
+ assertThat(result.getException()).isNull();
+
assertThat(result.getMessage().getBody(String.class)).isEqualTo("reply-4");
+
+ @SuppressWarnings("unchecked")
+ List<ChatCompletionMessageParam> history
+ = result.getProperty("CamelOpenAIConversationHistory",
List.class);
+ assertThat(history).isNotEmpty();
+
assertThat(OpenAIConversationHistoryTrimmer.estimateTokens(history)).isLessThanOrEqualTo(15);
+
+ String request = fourthRequestBody.get();
+ assertThat(request).isNotNull();
+
+ JsonNode messages = MAPPER.readTree(request).get("messages");
+ assertThat(messages).isNotNull();
+ assertThat(messages)
+ .noneMatch(message ->
TURN_1.equals(message.path("content").asText())
+ || "reply-1".equals(message.path("content").asText())
+ || TURN_2.equals(message.path("content").asText()));
+ assertThat(messages)
+ .anyMatch(message ->
TURN_3.equals(message.path("content").asText()));
+ assertThat(messages)
+ .anyMatch(message ->
"reply-3".equals(message.path("content").asText()));
+ assertThat(messages)
+ .anyMatch(message ->
TURN_4.equals(message.path("content").asText()));
+ }
+}
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistoryTrimmerTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistoryTrimmerTest.java
new file mode 100644
index 000000000000..78f5a64ebbe4
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIConversationHistoryTrimmerTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import com.openai.models.chat.completions.ChatCompletionAssistantMessageParam;
+import com.openai.models.chat.completions.ChatCompletionContentPart;
+import com.openai.models.chat.completions.ChatCompletionContentPartImage;
+import com.openai.models.chat.completions.ChatCompletionContentPartText;
+import
com.openai.models.chat.completions.ChatCompletionMessageFunctionToolCall;
+import com.openai.models.chat.completions.ChatCompletionMessageParam;
+import com.openai.models.chat.completions.ChatCompletionMessageToolCall;
+import com.openai.models.chat.completions.ChatCompletionToolMessageParam;
+import com.openai.models.chat.completions.ChatCompletionUserMessageParam;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class OpenAIConversationHistoryTrimmerTest {
+
+ @Test
+ void trimShouldReturnSameListWhenNoLimitsConfigured() {
+ List<ChatCompletionMessageParam> history = List.of(
+ userMessage("one"),
+ assistantMessage("two"));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(0, 0));
+
+ assertThat(trimmed).isSameAs(history);
+ }
+
+ @Test
+ void trimShouldRetainMostRecentMessagesWhenMaxHistoryMessagesIsSet() {
+ List<ChatCompletionMessageParam> history = new ArrayList<>();
+ history.add(userMessage("msg-1"));
+ history.add(assistantMessage("reply-1"));
+ history.add(userMessage("msg-2"));
+ history.add(assistantMessage("reply-2"));
+ history.add(userMessage("msg-3"));
+ history.add(assistantMessage("reply-3"));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(2, 0));
+
+ assertThat(trimmed).hasSize(2);
+ assertThat(extractText(trimmed.get(0))).isEqualTo("msg-3");
+ assertThat(extractText(trimmed.get(1))).isEqualTo("reply-3");
+ }
+
+ @Test
+ void trimShouldDropOldestMessagesWhenMaxHistoryTokensIsExceeded() {
+ List<ChatCompletionMessageParam> history = List.of(
+ userMessage(repeat('a', 40)),
+ assistantMessage(repeat('b', 40)),
+ userMessage(repeat('c', 40)));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(0, 15));
+
+
assertThat(OpenAIConversationHistoryTrimmer.estimateTokens(trimmed)).isLessThanOrEqualTo(15);
+ assertThat(trimmed).hasSize(1);
+ assertThat(extractText(trimmed.get(0))).isEqualTo(repeat('c', 40));
+ }
+
+ @Test
+ void trimShouldApplyBothMessageAndTokenLimits() {
+ List<ChatCompletionMessageParam> history = new ArrayList<>();
+ for (int i = 1; i <= 6; i++) {
+ history.add(userMessage("user-" + i + repeat('x', 36)));
+ history.add(assistantMessage("assistant-" + i));
+ }
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(4, 20));
+
+ assertThat(trimmed.size()).isLessThanOrEqualTo(4);
+
assertThat(OpenAIConversationHistoryTrimmer.estimateTokens(trimmed)).isLessThanOrEqualTo(20);
+
assertThat(trimmed.stream().map(OpenAIConversationHistoryTrimmerTest::extractText))
+ .noneMatch(text -> text.contains("user-1") ||
text.contains("assistant-1"));
+
assertThat(trimmed.stream().map(OpenAIConversationHistoryTrimmerTest::extractText))
+ .anyMatch(text -> text.contains("user-6"));
+ }
+
+ @Test
+ void trimShouldCountToolCallArgumentsTowardTokenEstimate() {
+ ChatCompletionMessageParam assistantWithToolCall =
ChatCompletionMessageParam.ofAssistant(
+ ChatCompletionAssistantMessageParam.builder()
+ .toolCalls(List.of(
+ ChatCompletionMessageToolCall.ofFunction(
+
ChatCompletionMessageFunctionToolCall.builder()
+ .id("call-1")
+
.function(ChatCompletionMessageFunctionToolCall.Function.builder()
+ .name("lookup")
+
.arguments("{\"query\":\"" + repeat('z', 80) + "\"}")
+ .build())
+ .build())))
+ .build());
+
+ List<ChatCompletionMessageParam> history = List.of(
+ userMessage("short"),
+ assistantWithToolCall,
+ toolMessage("tool-result"));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(0, 10));
+
+ assertThat(trimmed).hasSize(2);
+ assertThat(trimmed.get(0).assistant()).isPresent();
+ assertThat(trimmed.get(1).tool()).isPresent();
+ }
+
+ @Test
+ void trimShouldReturnMutableListThatSupportsSubsequentAdds() {
+ List<ChatCompletionMessageParam> history = List.of(
+ userMessage(repeat('a', 40)),
+ assistantMessage(repeat('b', 40)),
+ userMessage(repeat('c', 40)));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(0, 15));
+
+ trimmed.add(userMessage("next"));
+
+ assertThat(trimmed).hasSize(2);
+ assertThat(extractText(trimmed.get(0))).isEqualTo(repeat('c', 40));
+ assertThat(extractText(trimmed.get(1))).isEqualTo("next");
+ }
+
+ @Test
+ void trimShouldReturnMutableEmptyListForEmptyHistory() {
+ List<ChatCompletionMessageParam> trimmed
+ = OpenAIConversationHistoryTrimmer.trim(new ArrayList<>(),
config(0, 10));
+
+ trimmed.add(userMessage("one"));
+
+ assertThat(trimmed).hasSize(1);
+ }
+
+ @Test
+ void trimShouldRetainLastSegmentWhenItAloneExceedsTokenLimit() {
+ List<ChatCompletionMessageParam> history = List.of(
+ userMessage("older"),
+ assistantMessage("older-reply"),
+ userMessage(repeat('x', 80)));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(0, 10));
+
+ assertThat(trimmed).hasSize(1);
+ assertThat(extractText(trimmed.get(0))).isEqualTo(repeat('x', 80));
+ }
+
+ @Test
+ void
trimShouldKeepAssistantToolCallBlockIntactWhenMessageLimitWouldSplitIt() {
+ List<ChatCompletionMessageParam> history = List.of(
+ userMessage("older"),
+ assistantMessage("older-reply"),
+ userMessage("latest"),
+ assistantWithToolCall("call-1"),
+ toolMessage("tool-result"),
+ assistantMessage("final-reply"));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(2, 0));
+
+ assertThat(trimmed).hasSize(1);
+ assertThat(trimmed.get(0).assistant()).isPresent();
+ assertThat(extractText(trimmed.get(0))).isEqualTo("final-reply");
+ assertThat(trimmed.stream().noneMatch(message ->
message.tool().isPresent())).isTrue();
+ }
+
+ @Test
+ void trimShouldRetainAssistantAndToolResultsAsOneSegment() {
+ List<ChatCompletionMessageParam> history = List.of(
+ assistantWithToolCall("call-1"),
+ toolMessage("tool-result"));
+
+ List<ChatCompletionMessageParam> trimmed =
OpenAIConversationHistoryTrimmer.trim(history, config(2, 0));
+
+ assertThat(trimmed).hasSize(2);
+ assertThat(trimmed.get(0).assistant()).isPresent();
+ assertThat(trimmed.get(1).tool()).isPresent();
+ }
+
+ @Test
+ void estimateTokensShouldIncludeImagePayloadSize() {
+ String imageUrl = "data:image/png;base64," + repeat('x', 400);
+ ChatCompletionMessageParam imageMessage =
ChatCompletionMessageParam.ofUser(
+ ChatCompletionUserMessageParam.builder()
+
.content(ChatCompletionUserMessageParam.Content.ofArrayOfContentParts(List.of(
+ ChatCompletionContentPart.ofText(
+
ChatCompletionContentPartText.builder().text("describe").build()),
+ ChatCompletionContentPart.ofImageUrl(
+
ChatCompletionContentPartImage.builder()
+
.imageUrl(ChatCompletionContentPartImage.ImageUrl.builder()
+ .url(imageUrl)
+ .build())
+ .build()))))
+ .build());
+
+ int tokens =
OpenAIConversationHistoryTrimmer.estimateTokens(List.of(imageMessage));
+
+ assertThat(tokens).isGreaterThan(100);
+ }
+
+ private static OpenAIConfiguration config(int maxHistoryMessages, int
maxHistoryTokens) {
+ OpenAIConfiguration configuration = new OpenAIConfiguration();
+ configuration.setMaxHistoryMessages(maxHistoryMessages);
+ configuration.setMaxHistoryTokens(maxHistoryTokens);
+ return configuration;
+ }
+
+ private static ChatCompletionMessageParam userMessage(String text) {
+ return ChatCompletionMessageParam.ofUser(
+ ChatCompletionUserMessageParam.builder()
+
.content(ChatCompletionUserMessageParam.Content.ofText(text))
+ .build());
+ }
+
+ private static ChatCompletionMessageParam assistantMessage(String text) {
+ return ChatCompletionMessageParam.ofAssistant(
+ ChatCompletionAssistantMessageParam.builder()
+
.content(ChatCompletionAssistantMessageParam.Content.ofText(text))
+ .build());
+ }
+
+ private static ChatCompletionMessageParam assistantWithToolCall(String
toolCallId) {
+ return ChatCompletionMessageParam.ofAssistant(
+ ChatCompletionAssistantMessageParam.builder()
+ .toolCalls(List.of(
+ ChatCompletionMessageToolCall.ofFunction(
+
ChatCompletionMessageFunctionToolCall.builder()
+ .id(toolCallId)
+
.function(ChatCompletionMessageFunctionToolCall.Function.builder()
+ .name("lookup")
+ .arguments("{}")
+ .build())
+ .build())))
+ .build());
+ }
+
+ private static ChatCompletionMessageParam toolMessage(String text) {
+ return ChatCompletionMessageParam.ofTool(
+ ChatCompletionToolMessageParam.builder()
+ .toolCallId("call-1")
+ .content(text)
+ .build());
+ }
+
+ private static String extractText(ChatCompletionMessageParam message) {
+ return message.user()
+ .map(user -> user.content().isText() ? user.content().asText()
: "")
+ .orElseGet(() -> message.assistant()
+ .flatMap(assistant -> assistant.content()
+
.filter(ChatCompletionAssistantMessageParam.Content::isText)
+
.map(ChatCompletionAssistantMessageParam.Content::asText))
+ .orElseGet(() -> message.tool()
+ .map(tool -> tool.content().isText() ?
tool.content().asText() : "")
+ .orElse("")));
+ }
+
+ private static String repeat(char value, int count) {
+ return String.valueOf(value).repeat(count);
+ }
+}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
index fc175c550c1b..901192e02a69 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
@@ -480,6 +480,88 @@ public interface OpenAIEndpointBuilderFactory {
doSetProperty("jsonSchema", jsonSchema);
return this;
}
+ /**
+ * When conversationMemory is enabled, retain at most this many
messages
+ * in the exchange conversation history. System and developer messages
+ * are prepended separately and are not stored in history. Assistant
+ * tool-call blocks are kept intact and may retain slightly more than
+ * this limit to preserve tool result pairing. When 0, no message limit
+ * is applied.
+ *
+ * The option is a: <code>int</code> type.
+ *
+ * Default: 0
+ * Group: producer
+ *
+ * @param maxHistoryMessages the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder maxHistoryMessages(int
maxHistoryMessages) {
+ doSetProperty("maxHistoryMessages", maxHistoryMessages);
+ return this;
+ }
+ /**
+ * When conversationMemory is enabled, retain at most this many
messages
+ * in the exchange conversation history. System and developer messages
+ * are prepended separately and are not stored in history. Assistant
+ * tool-call blocks are kept intact and may retain slightly more than
+ * this limit to preserve tool result pairing. When 0, no message limit
+ * is applied.
+ *
+ * The option will be converted to a <code>int</code> type.
+ *
+ * Default: 0
+ * Group: producer
+ *
+ * @param maxHistoryMessages the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder maxHistoryMessages(String
maxHistoryMessages) {
+ doSetProperty("maxHistoryMessages", maxHistoryMessages);
+ return this;
+ }
+ /**
+ * When conversationMemory is enabled, trim conversation history using
a
+ * token estimate (character count / 4, including image payload size
for
+ * multi-modal user messages). Oldest segments are dropped first until
+ * the estimated tokens are within this limit. Assistant tool-call
+ * blocks are removed as a unit with their tool results. The most
recent
+ * segment is always retained, even when it alone exceeds this limit.
+ * When 0, no token limit is applied.
+ *
+ * The option is a: <code>int</code> type.
+ *
+ * Default: 0
+ * Group: producer
+ *
+ * @param maxHistoryTokens the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder maxHistoryTokens(int maxHistoryTokens) {
+ doSetProperty("maxHistoryTokens", maxHistoryTokens);
+ return this;
+ }
+ /**
+ * When conversationMemory is enabled, trim conversation history using
a
+ * token estimate (character count / 4, including image payload size
for
+ * multi-modal user messages). Oldest segments are dropped first until
+ * the estimated tokens are within this limit. Assistant tool-call
+ * blocks are removed as a unit with their tool results. The most
recent
+ * segment is always retained, even when it alone exceeds this limit.
+ * When 0, no token limit is applied.
+ *
+ * The option will be converted to a <code>int</code> type.
+ *
+ * Default: 0
+ * Group: producer
+ *
+ * @param maxHistoryTokens the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder maxHistoryTokens(String
maxHistoryTokens) {
+ doSetProperty("maxHistoryTokens", maxHistoryTokens);
+ return this;
+ }
/**
* Maximum number of times the OpenAI SDK client retries failed
* requests. The SDK retry is rate-limit aware (honors Retry-After on