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 2d084caa00de CAMEL-24961: camel-ai - the body types of openai, 
langchain4j-chat, a2a, docling, milvus, qdrant, ai-tool and ibm-watson-language 
(#28250)
2d084caa00de is described below

commit 2d084caa00de1526290c3d0aafd032cbb3c53cde
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 24 08:59:07 2026 +0200

    CAMEL-24961: camel-ai - the body types of openai, langchain4j-chat, a2a, 
docling, milvus, qdrant, ai-tool and ibm-watson-language (#28250)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../org/apache/camel/catalog/components/a2a.json   |  6 ++
 .../apache/camel/catalog/components/ai-tool.json   |  4 ++
 .../apache/camel/catalog/components/docling.json   |  5 ++
 .../catalog/components/ibm-watson-language.json    |  4 ++
 .../camel/catalog/components/langchain4j-chat.json |  4 ++
 .../org/apache/camel/catalog/components/llm.json   |  6 ++
 .../apache/camel/catalog/components/milvus.json    |  4 ++
 .../apache/camel/catalog/components/openai.json    |  6 ++
 .../apache/camel/catalog/components/qdrant.json    |  4 ++
 .../org/apache/camel/component/a2a/a2a.json        |  6 ++
 .../apache/camel/component/a2a/A2AEndpoint.java    | 72 +++++++++++++++++++
 .../apache/camel/component/ai/tool/ai-tool.json    |  4 ++
 .../camel/component/ai/tool/AiToolEndpoint.java    | 13 ++++
 .../apache/camel/component/docling/docling.json    |  5 ++
 .../camel/component/docling/DoclingEndpoint.java   | 66 +++++++++++++++++
 .../langchain4j/chat/langchain4j-chat.json         |  4 ++
 .../langchain4j/chat/LangChain4jChatEndpoint.java  | 16 +++++
 .../org/apache/camel/component/milvus/milvus.json  |  4 ++
 .../camel/component/milvus/MilvusEndpoint.java     | 14 ++++
 .../org/apache/camel/component/openai/llm.json     |  6 ++
 .../org/apache/camel/component/openai/openai.json  |  6 ++
 .../camel/component/openai/OpenAIEndpoint.java     | 83 ++++++++++++++++++++++
 .../org/apache/camel/component/qdrant/qdrant.json  |  4 ++
 .../camel/component/qdrant/QdrantEndpoint.java     | 18 +++++
 .../ibm/watson/language/ibm-watson-language.json   |  4 ++
 .../watson/language/WatsonLanguageEndpoint.java    | 14 ++++
 .../modules/ROOT/pages/camel-catalog.adoc          |  3 +-
 .../modules/ROOT/pages/camel-jbang-validation.adoc |  5 +-
 .../camel/dsl/yaml/validator/CatalogBodyTypes.java | 35 +++++++++
 .../camel/dsl/yaml/validator/TextBodyFlowTest.java | 44 ++++++++++++
 .../camel/maven/packaging/BodyTypesHelper.java     | 43 +++++++----
 .../packaging/EndpointSchemaGeneratorMojoTest.java | 16 ++++-
 .../endpoint/SomeEndpointWithBadBodyTypes.java     |  6 ++
 33 files changed, 515 insertions(+), 19 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/a2a.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/a2a.json
index 4fb7bd58f981..2ac1c899e37a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/a2a.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/a2a.json
@@ -49,6 +49,12 @@
     "CamelA2AListStatus": { "index": 17, "kind": "header", "displayName": "", 
"group": "common", "label": "common", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Comma-separated status filter for task listing", 
"constantName": "org.apache.camel.component.a2a.A2AConstants#LIST_STATUS" },
     "CamelA2AExtensions": { "index": 18, "kind": "header", "displayName": "", 
"group": "consumer", "label": "consumer", "required": false, "javaType": 
"java.util.List", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Negotiated A2A extension URIs requested 
by the client", "constantName": 
"org.apache.camel.component.a2a.A2AConstants#EXTENSIONS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"dataFormat", "value": "POJO", "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.a2a.model.Message" } ], "description": "The message 
of the caller, for SendMessage and SendStreamingMessage (the component answers 
the task and push config requests itself): the text of its parts, one per line, 
a data part as JSON and a file part as its URL. With dataFormat POJO the 
Message, with RAW the Message as JSON" },
+    "consumerReply": { "carriers": [ "text", "bytes", "stream", "file", "pojo" 
], "description": "The reply of the agent: text, sent as one text part (bytes, 
a stream or a file as their text), or a Message sent as it is. Another body is 
sent as text: a Task as the text of its last message, a Map or a POJO as its 
toString(): marshal it first. The reply goes in a Task, completed unless the 
CamelA2ATaskState header says another state, or as a Message when the 
CamelA2AResponseType header is  [...]
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], 
"byOption": [ { "option": "operation", "value": "TASK_GET", "carriers": [ "any" 
] }, { "option": "operation", "value": "TASK_LIST", "carriers": [ "any" ] }, { 
"option": "operation", "value": "TASK_CANCEL", "carriers": [ "any" ] }, { 
"option": "operation", "value": "TASK_SUBSCRIBE", "carriers": [ "any" ] }, { 
"option": "operation", "value": "PUSH_CONFIG_CREATE", "carriers": [ "pojo" ], 
"javaType": "org.apache.c [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"operation", "value": "MESSAGE_STREAM", "carriers": [ "iterator" ], "javaType": 
"org.apache.camel.component.a2a.streaming.SseEventIterator" }, { "option": 
"operation", "value": "TASK_GET", "carriers": [ "text" ] }, { "option": 
"operation", "value": "TASK_LIST", "carriers": [ "list", "null" ] }, { 
"option": "operation", "value": "TASK_CANCEL", "carriers": [ "text" ] }, { 
"option": "operation", "value": "TASK_SUBSCRIBE" [...]
+  },
   "properties": {
     "agentCardSource": { "index": 0, "kind": "path", "displayName": "Agent 
Card Source", "group": "common", "label": "", "required": true, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "The 
agent card source (classpath:, file:, http:\/\/, https:\/\/, or plain name)" },
     "agentCard": { "index": 1, "kind": "parameter", "displayName": "Agent 
Card", "group": "common", "label": "", "required": false, "type": "object", 
"javaType": "org.apache.camel.component.a2a.model.AgentCard", "deprecated": 
false, "deprecationNote": "", "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.a2a.A2AConfiguration", 
"configurationField": "configuration", "description": "Agent card bean 
reference or inline configuration" },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
index 0ce16172cd96..deed749b292a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
@@ -46,6 +46,10 @@
     "CamelAiToolCallId": { "index": 0, "kind": "header", "displayName": "", 
"group": "consumer", "label": "consumer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The tool invocation identifier supplied by the 
calling framework. Absent when the framework does not supply one.", 
"constantName": 
"org.apache.camel.component.ai.tool.AiToolConstants#TOOL_CALL_ID" },
     "CamelAiToolConversationId": { "index": 1, "kind": "header", 
"displayName": "", "group": "consumer", "label": "consumer", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The conversation identifier supplied by 
the calling framework. Absent when the framework does not supply one.", 
"constantName": 
"org.apache.camel.component.ai.tool.AiToolConstants#CONVERSATION_ID" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "null" ], "description": "No body: each 
argument of the tool call is a header named as its parameter, a Map or a List 
for a nested JSON value. Undeclared arguments and names starting with camel are 
dropped" },
+    "consumerReply": { "carriers": [ "any" ], "dependsOn": [ "outputSchema", 
"outputParameters" ], "description": "The tool result, as text: the body 
converted to a String, so a Map, a List or a POJO is sent as its toString(): 
marshal it to JSON first. No body gives No result. With outputSchema or 
outputParameter.NAME options the body must be JSON: a JSON String, a Map, a 
List, a number or a boolean, and bytes, a stream or another object fail the 
call" }
+  },
   "properties": {
     "toolName": { "index": 0, "kind": "path", "displayName": "Tool Name", 
"group": "consumer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The tool name. This is the 
name the LLM sees and uses to invoke the tool." },
     "argSchema": { "index": 1, "kind": "parameter", "displayName": "Arg 
Schema", "group": "consumer", "label": "consumer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, 
"supportFileReference": true, "configurationClass": 
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField": 
"configuration", "description": "Raw JSON Schema for tool input parameters. 
Supports in [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
index 2929f7f68134..426611f97d2f 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
@@ -117,6 +117,11 @@
     "CamelDoclingChunkingMaxTokens": { "index": 30, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Maximum tokens per chunk for hybrid 
chunking", "constantName": 
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MAX_TOKENS" },
     "CamelDoclingChunkingMergePeers": { "index": 31, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Whether to merge peer chunks in hybrid 
chunking", "constantName": 
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MERGE_PEERS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "text" ], "description": "The document of 
an async conversion that completed (SUBMIT_ASYNC_CONVERSION on the same 
component), as text in the outputFormat it was submitted with: markdown by 
default, JSON text for json. The task id is in the CamelDoclingTaskId header, 
and a failed conversion goes to the error handler" },
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "BATCH_CONVERT_TO_MARKDOWN", "carriers": [ "list" ] }, { 
"option": "operation", "value": "BATCH_CONVERT_TO_HTML", "carriers": [ "list" ] 
}, { "option": "operation", "value": "BATCH_CONVERT_TO_JSON", "carriers": [ 
"list" ] }, { "option": "operation", "value": "BATCH_EXTRACT_TEXT", "carriers": 
[ "list" ] }, { "option": "operation", "value": 
"BATCH_EXTRACT_STRUCTURED_DATA", "carriers": [ "list" ] } ], [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"operation", "value": "CONVERT_TO_JSON", "carriers": [ "pojo" ], "javaType": 
"ai.docling.core.DoclingDocument" }, { "option": "operation", "value": 
"EXTRACT_STRUCTURED_DATA", "carriers": [ "pojo" ], "javaType": 
"ai.docling.core.DoclingDocument" }, { "option": "operation", "value": 
"CHECK_CONVERSION_STATUS", "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.docling.ConversionStatus" }, { "option": "operat 
[...]
+  },
   "properties": {
     "operationId": { "index": 0, "kind": "path", "displayName": "Operation 
Id", "group": "common", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The operation identifier" 
},
     "contentInBody": { "index": 1, "kind": "parameter", "displayName": 
"Content In Body", "group": "common", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.docling.DoclingConfiguration", 
"configurationField": "configuration", "description": "Include the content of 
the output file in the exchange body and delet [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ibm-watson-language.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ibm-watson-language.json
index ce47ac5e383e..d0b502549cf5 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ibm-watson-language.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ibm-watson-language.json
@@ -53,6 +53,10 @@
     "CamelIBMWatsonLanguageSentimentScore": { "index": 10, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The sentiment score", "constantName": 
"org.apache.camel.component.ibm.watson.language.WatsonLanguageConstants#SENTIMENT_SCORE"
 },
     "CamelIBMWatsonLanguageSentimentLabel": { "index": 11, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The sentiment label (positive, 
negative, neutral)", "constantName": 
"org.apache.camel.component.ibm.watson.language.WatsonLanguageConstants#SENTIMENT_LABEL"
 }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "any" ], "description": "The text to analyze 
for analyzeText, or the URL of the page for analyzeUrl, read as text, unless 
the CamelIBMWatsonLanguageText or CamelIBMWatsonLanguageUrl header gives it. 
Without the header, no body or a blank one fails, and a Map or a POJO is 
analyzed as its toString(): marshal it first" },
+    "producerOut": { "carriers": [ "pojo" ], "javaType": 
"com.ibm.watson.natural_language_understanding.v1.model.AnalysisResults", 
"description": "The results of the features enabled (sentiment, entities, 
keywords and so on), with the sentiment score and label and the language in 
headers too. Converted to text, it is the JSON of the results" }
+  },
   "properties": {
     "label": { "index": 0, "kind": "path", "displayName": "Label", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Logical name" },
     "serviceUrl": { "index": 1, "kind": "parameter", "displayName": "Service 
Url", "group": "common", "label": "common", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.ibm.watson.language.WatsonLanguageConfiguration", 
"configurationField": "configuration", "description": "The service endpoint 
URL. If not specified, the default URL will be used." },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
index 951f48a17cf5..f1aedc286daf 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
@@ -48,6 +48,10 @@
     "CamelLangChain4jChatRequestModel": { "index": 6, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The request model name.", 
"constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#REQUEST_MODEL"
 },
     "CamelLangChain4jChatResponseModel": { "index": 7, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The response model name.", 
"constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#RESPONSE_MODEL"
 }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"chatOperation", "value": "CHAT_SINGLE_MESSAGE_WITH_PROMPT", "carriers": [ 
"map" ] }, { "option": "chatOperation", "value": "CHAT_MULTIPLE_MESSAGES", 
"carriers": [ "list" ] } ], "description": "The prompt as text, or a 
ChatMessage; another body is sent as its toString(), and no body fails. 
CHAT_SINGLE_MESSAGE_WITH_PROMPT takes the variables of the template in the 
CamelLangChain4jChatPromptTemplate header as a Map, and C [...]
+    "producerOut": { "carriers": [ "text" ], "description": "The answer of the 
model as text, also when it is asked for JSON: unmarshal it to read its fields" 
}
+  },
   "properties": {
     "chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The id" },
     "chatOperation": { "index": 1, "kind": "parameter", "displayName": "Chat 
Operation", "group": "producer", "label": "", "required": true, "type": "enum", 
"javaType": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatOperations", 
"enum": [ "CHAT_SINGLE_MESSAGE", "CHAT_SINGLE_MESSAGE_WITH_PROMPT", 
"CHAT_MULTIPLE_MESSAGES" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "defaultValue": "CHAT_SINGLE_MESSAGE", 
"configurationClass": "org.apache. [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/llm.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/llm.json
index a2451cbd1b69..6870d839b3a6 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/llm.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/llm.json
@@ -146,6 +146,12 @@
     "CamelOpenAIImageOutputTokens": { "index": 107, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of output tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_TOKENS" },
     "CamelOpenAIImageTotalTokens": { "index": 108, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The total number of tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_TOTAL_TOKENS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.openai.models.webhooks.UnwrapWebhookEvent", "description": "The event of 
the webhook operation, verified with webhookSecret, such as a batch or a 
background response that completed. The event type and the id of the object it 
is about are in headers" },
+    "consumerReply": { "carriers": [ "any" ], "description": "The body at the 
end of the route is not sent: OpenAI gets 200 without a body, or 500 when the 
route fails, and then sends the event again" },
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "decisions", "carriers": [ "pojo" ], "javaType": 
"com.openai.models.decisions.DecisionCreateParams" }, { "option": "operation", 
"value": "batch", "carriers": [ "map", "list", "text", "bytes", "stream", 
"file" ] }, { "option": "operation", "value": "audio-transcription", 
"carriers": [ "file", "bytes", "stream" ] }, { "option": "operation", "value": 
"audio-translation", "carriers": [ "file", "bytes", [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"streaming", "value": "true", "carriers": [ "iterator" ] }, { "option": 
"autoToolExecution", "value": "false", "carriers": [ "text", "pojo", "list" ] 
}, { "option": "operation", "value": "decisions", "carriers": [ "pojo" ], 
"javaType": "com.openai.models.decisions.Decision" }, { "option": "operation", 
"value": "responses", "carriers": [ "text" ] }, { "option": "operation", 
"value": "responses-retrieve", "carriers": [  [...]
+  },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.openai.OpenAIOperations", "enum": [ 
"chat-completion", "decisions", "responses", "responses-retrieve", 
"responses-cancel", "batch", "batch-retrieve", "batch-cancel", "batch-results", 
"embeddings", "tool-execution", "audio-transcription", "audio-translation", 
"audio-speech", "moderation", "image-generation", "i [...]
     "additionalBodyProperty": { "index": 1, "kind": "parameter", 
"displayName": "Additional Body Property", "group": "common", "label": "", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": 
"additionalBodyProperty.", "multiValue": true, "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", 
"configurationField": "configurat [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milvus.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milvus.json
index 401fd632d4cc..c2c82cb38cd3 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milvus.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milvus.json
@@ -42,6 +42,10 @@
     "CamelMilvusKeyName": { "index": 6, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Key Name for Insert\/Upsert operation", "constantName": 
"org.apache.camel.component.milvus.MilvusHeaders#KEY_NAME" },
     "CamelMilvusKeyValue": { "index": 7, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Key Value for Insert\/Upsert operation", "constantName": 
"org.apache.camel.component.milvus.MilvusHeaders#KEY_VALUE" }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "pojo" ], "description": "The request of the 
Milvus SDK for the action in the CamelMilvusAction header: a 
CreateCollectionParam, CreateIndexParam, InsertParam, UpsertParam, 
SearchSimpleParam, QueryParam or DeleteParam. Another body fails, a vector as a 
List too: the milvus:embeddings data type transformer builds the InsertParam or 
UpsertParam from the output of langchain4j-embeddings" },
+    "producerOut": { "carriers": [ "pojo" ], "description": "The result of the 
Milvus SDK for the action: an RpcStatus for CREATE_COLLECTION and CREATE_INDEX, 
a MutationResult for INSERT, UPSERT and DELETE, a SearchResponse for SEARCH and 
a QueryResults for QUERY" }
+  },
   "properties": {
     "collection": { "index": 0, "kind": "path", "displayName": "Collection", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The collection Name" },
     "host": { "index": 1, "kind": "parameter", "displayName": "Host", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "defaultValue": "localhost", "configurationClass": 
"org.apache.camel.component.milvus.MilvusConfiguration", "configurationField": 
"configuration", "description": "The host to connect to." },
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 d212b19e520c..4d569fa9d943 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
@@ -146,6 +146,12 @@
     "CamelOpenAIImageOutputTokens": { "index": 107, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of output tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_TOKENS" },
     "CamelOpenAIImageTotalTokens": { "index": 108, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The total number of tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_TOTAL_TOKENS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.openai.models.webhooks.UnwrapWebhookEvent", "description": "The event of 
the webhook operation, verified with webhookSecret, such as a batch or a 
background response that completed. The event type and the id of the object it 
is about are in headers" },
+    "consumerReply": { "carriers": [ "any" ], "description": "The body at the 
end of the route is not sent: OpenAI gets 200 without a body, or 500 when the 
route fails, and then sends the event again" },
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "decisions", "carriers": [ "pojo" ], "javaType": 
"com.openai.models.decisions.DecisionCreateParams" }, { "option": "operation", 
"value": "batch", "carriers": [ "map", "list", "text", "bytes", "stream", 
"file" ] }, { "option": "operation", "value": "audio-transcription", 
"carriers": [ "file", "bytes", "stream" ] }, { "option": "operation", "value": 
"audio-translation", "carriers": [ "file", "bytes", [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"streaming", "value": "true", "carriers": [ "iterator" ] }, { "option": 
"autoToolExecution", "value": "false", "carriers": [ "text", "pojo", "list" ] 
}, { "option": "operation", "value": "decisions", "carriers": [ "pojo" ], 
"javaType": "com.openai.models.decisions.Decision" }, { "option": "operation", 
"value": "responses", "carriers": [ "text" ] }, { "option": "operation", 
"value": "responses-retrieve", "carriers": [  [...]
+  },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.openai.OpenAIOperations", "enum": [ 
"chat-completion", "decisions", "responses", "responses-retrieve", 
"responses-cancel", "batch", "batch-retrieve", "batch-cancel", "batch-results", 
"embeddings", "tool-execution", "audio-transcription", "audio-translation", 
"audio-speech", "moderation", "image-generation", "i [...]
     "additionalBodyProperty": { "index": 1, "kind": "parameter", 
"displayName": "Additional Body Property", "group": "common", "label": "", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": 
"additionalBodyProperty.", "multiValue": true, "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", 
"configurationField": "configurat [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/qdrant.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/qdrant.json
index 283a45b213cb..4370a6d81573 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/qdrant.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/qdrant.json
@@ -48,6 +48,10 @@
     "CamelQdrantPointId": { "index": 9, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "int", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The point id to use for operation.", "constantName": 
"org.apache.camel.component.qdrant.QdrantHeaders#POINT_ID" },
     "CamelQdrantMaxResults": { "index": 10, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "int", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The maximum number of results to return from a 
similarity search.", "constantName": 
"org.apache.camel.component.qdrant.QdrantHeaders#MAX_RESULTS" }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "pojo", "list", "null" ], "description": "By 
the action in the CamelQdrantAction header: a VectorParams for 
CREATE_COLLECTION, a PointStruct or a List of them for UPSERT, a PointId or a 
List of them for RETRIEVE, a PointsSelector, PointId, Filter or Condition for 
DELETE, and the query vector as a List of Float, a float array or a PointStruct 
for SIMILARITY_SEARCH. DELETE_COLLECTION and COLLECTION_INFO do not read the 
body. Another body fails, such as a n [...]
+    "producerOut": { "carriers": [ "list", "pojo", "unchanged" ], 
"description": "By the action: a List of RetrievedPoint for RETRIEVE, a List of 
ScoredPoint for SIMILARITY_SEARCH and a CollectionInfo for COLLECTION_INFO. The 
other actions keep the body, UPSERT and DELETE with the operation id and status 
in headers" }
+  },
   "properties": {
     "collection": { "index": 0, "kind": "path", "displayName": "Collection", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The collection Name" },
     "host": { "index": 1, "kind": "parameter", "displayName": "Host", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "defaultValue": "localhost", "configurationClass": 
"org.apache.camel.component.qdrant.QdrantConfiguration", "configurationField": 
"configuration", "description": "The host to connect to." },
diff --git 
a/components/camel-ai/camel-a2a/src/generated/resources/META-INF/org/apache/camel/component/a2a/a2a.json
 
b/components/camel-ai/camel-a2a/src/generated/resources/META-INF/org/apache/camel/component/a2a/a2a.json
index 4fb7bd58f981..2ac1c899e37a 100644
--- 
a/components/camel-ai/camel-a2a/src/generated/resources/META-INF/org/apache/camel/component/a2a/a2a.json
+++ 
b/components/camel-ai/camel-a2a/src/generated/resources/META-INF/org/apache/camel/component/a2a/a2a.json
@@ -49,6 +49,12 @@
     "CamelA2AListStatus": { "index": 17, "kind": "header", "displayName": "", 
"group": "common", "label": "common", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Comma-separated status filter for task listing", 
"constantName": "org.apache.camel.component.a2a.A2AConstants#LIST_STATUS" },
     "CamelA2AExtensions": { "index": 18, "kind": "header", "displayName": "", 
"group": "consumer", "label": "consumer", "required": false, "javaType": 
"java.util.List", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Negotiated A2A extension URIs requested 
by the client", "constantName": 
"org.apache.camel.component.a2a.A2AConstants#EXTENSIONS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"dataFormat", "value": "POJO", "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.a2a.model.Message" } ], "description": "The message 
of the caller, for SendMessage and SendStreamingMessage (the component answers 
the task and push config requests itself): the text of its parts, one per line, 
a data part as JSON and a file part as its URL. With dataFormat POJO the 
Message, with RAW the Message as JSON" },
+    "consumerReply": { "carriers": [ "text", "bytes", "stream", "file", "pojo" 
], "description": "The reply of the agent: text, sent as one text part (bytes, 
a stream or a file as their text), or a Message sent as it is. Another body is 
sent as text: a Task as the text of its last message, a Map or a POJO as its 
toString(): marshal it first. The reply goes in a Task, completed unless the 
CamelA2ATaskState header says another state, or as a Message when the 
CamelA2AResponseType header is  [...]
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], 
"byOption": [ { "option": "operation", "value": "TASK_GET", "carriers": [ "any" 
] }, { "option": "operation", "value": "TASK_LIST", "carriers": [ "any" ] }, { 
"option": "operation", "value": "TASK_CANCEL", "carriers": [ "any" ] }, { 
"option": "operation", "value": "TASK_SUBSCRIBE", "carriers": [ "any" ] }, { 
"option": "operation", "value": "PUSH_CONFIG_CREATE", "carriers": [ "pojo" ], 
"javaType": "org.apache.c [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"operation", "value": "MESSAGE_STREAM", "carriers": [ "iterator" ], "javaType": 
"org.apache.camel.component.a2a.streaming.SseEventIterator" }, { "option": 
"operation", "value": "TASK_GET", "carriers": [ "text" ] }, { "option": 
"operation", "value": "TASK_LIST", "carriers": [ "list", "null" ] }, { 
"option": "operation", "value": "TASK_CANCEL", "carriers": [ "text" ] }, { 
"option": "operation", "value": "TASK_SUBSCRIBE" [...]
+  },
   "properties": {
     "agentCardSource": { "index": 0, "kind": "path", "displayName": "Agent 
Card Source", "group": "common", "label": "", "required": true, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "The 
agent card source (classpath:, file:, http:\/\/, https:\/\/, or plain name)" },
     "agentCard": { "index": 1, "kind": "parameter", "displayName": "Agent 
Card", "group": "common", "label": "", "required": false, "type": "object", 
"javaType": "org.apache.camel.component.a2a.model.AgentCard", "deprecated": 
false, "deprecationNote": "", "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.a2a.A2AConfiguration", 
"configurationField": "configuration", "description": "Agent card bean 
reference or inline configuration" },
diff --git 
a/components/camel-ai/camel-a2a/src/main/java/org/apache/camel/component/a2a/A2AEndpoint.java
 
b/components/camel-ai/camel-a2a/src/main/java/org/apache/camel/component/a2a/A2AEndpoint.java
index c9868f484217..0f3a5649244d 100644
--- 
a/components/camel-ai/camel-a2a/src/main/java/org/apache/camel/component/a2a/A2AEndpoint.java
+++ 
b/components/camel-ai/camel-a2a/src/main/java/org/apache/camel/component/a2a/A2AEndpoint.java
@@ -47,6 +47,9 @@ import 
org.apache.camel.component.a2a.push.PushNotificationSubscriber;
 import org.apache.camel.component.a2a.state.A2ATaskStore;
 import org.apache.camel.component.a2a.state.GuardedTaskStore;
 import org.apache.camel.component.a2a.state.InMemoryTaskStore;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -70,6 +73,75 @@ import org.slf4j.LoggerFactory;
              syntax = "a2a:agentCardSource",
              category = { Category.AI },
              headersClass = A2AConstants.class)
+@BodyTypes(consumerOut = @BodyType(value = Carrier.TEXT,
+                                   byOption = @BodyType.When(option = 
"dataFormat", value = "POJO", carriers = Carrier.POJO,
+                                                             javaType = 
"org.apache.camel.component.a2a.model.Message"),
+                                   description = "The message of the caller, 
for SendMessage and SendStreamingMessage (the"
+                                                 + " component answers the 
task and push config requests itself): the text"
+                                                 + " of its parts, one per 
line, a data part as JSON and a file part as its"
+                                                 + " URL. With dataFormat POJO 
the Message, with RAW the Message as JSON"),
+           consumerReply = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
+                                     description = "The reply of the agent: 
text, sent as one text part (bytes, a stream or a"
+                                                   + " file as their text), or 
a Message sent as it is. Another body is sent"
+                                                   + " as text: a Task as the 
text of its last message, a Map or a POJO as its"
+                                                   + " toString(): marshal it 
first. The reply goes in a Task, completed"
+                                                   + " unless the 
CamelA2ATaskState header says another state, or as a Message"
+                                                   + " when the 
CamelA2AResponseType header is message"),
+           producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
+                                  byOption = {
+                                          @BodyType.When(option = "operation", 
value = "TASK_GET", carriers = Carrier.ANY),
+                                          @BodyType.When(option = "operation", 
value = "TASK_LIST", carriers = Carrier.ANY),
+                                          @BodyType.When(option = "operation", 
value = "TASK_CANCEL", carriers = Carrier.ANY),
+                                          @BodyType.When(option = "operation", 
value = "TASK_SUBSCRIBE",
+                                                         carriers = 
Carrier.ANY),
+                                          @BodyType.When(option = "operation", 
value = "PUSH_CONFIG_CREATE",
+                                                         carriers = 
Carrier.POJO,
+                                                         javaType = 
"org.apache.camel.component.a2a.model.TaskPushNotificationConfig"),
+                                          @BodyType.When(option = "operation", 
value = "PUSH_CONFIG_GET",
+                                                         carriers = 
Carrier.ANY),
+                                          @BodyType.When(option = "operation", 
value = "PUSH_CONFIG_LIST",
+                                                         carriers = 
Carrier.ANY),
+                                          @BodyType.When(option = "operation", 
value = "PUSH_CONFIG_DELETE",
+                                                         carriers = 
Carrier.ANY) },
+                                  description = "The message to send: text, 
sent as one text part (bytes, a stream or a"
+                                                + " file as their text), or a 
Message sent as it is; MESSAGE_STREAM also"
+                                                + " takes a 
SendMessageRequest. Another body is sent as its toString(), so a"
+                                                + " Map or a POJO needs 
marshalling first, and JSON text is sent as text."
+                                                + " PUSH_CONFIG_CREATE needs a 
TaskPushNotificationConfig, and the other"
+                                                + " operations read headers, 
not the body"),
+           producerOut = @BodyType(value = Carrier.TEXT,
+                                   byOption = {
+                                           @BodyType.When(option = 
"operation", value = "MESSAGE_STREAM",
+                                                          carriers = 
Carrier.ITERATOR,
+                                                          javaType = 
"org.apache.camel.component.a2a.streaming.SseEventIterator"),
+                                           @BodyType.When(option = 
"operation", value = "TASK_GET", carriers = Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "TASK_LIST",
+                                                          carriers = { 
Carrier.LIST, Carrier.NULL }),
+                                           @BodyType.When(option = 
"operation", value = "TASK_CANCEL", carriers = Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "TASK_SUBSCRIBE",
+                                                          carriers = 
Carrier.ITERATOR,
+                                                          javaType = 
"org.apache.camel.component.a2a.streaming.SseEventIterator"),
+                                           @BodyType.When(option = 
"operation", value = "PUSH_CONFIG_CREATE",
+                                                          carriers = 
Carrier.POJO,
+                                                          javaType = 
"org.apache.camel.component.a2a.model.TaskPushNotificationConfig"),
+                                           @BodyType.When(option = 
"operation", value = "PUSH_CONFIG_GET",
+                                                          carriers = 
Carrier.POJO,
+                                                          javaType = 
"org.apache.camel.component.a2a.model.TaskPushNotificationConfig"),
+                                           @BodyType.When(option = 
"operation", value = "PUSH_CONFIG_LIST",
+                                                          carriers = { 
Carrier.LIST, Carrier.NULL }),
+                                           @BodyType.When(option = 
"operation", value = "PUSH_CONFIG_DELETE",
+                                                          carriers = 
Carrier.MAP),
+                                           @BodyType.When(option = 
"dataFormat", value = "POJO", carriers = Carrier.POJO),
+                                           @BodyType.When(option = 
"dataFormat", value = "RAW", carriers = Carrier.TEXT) },
+                                   description = "The reply of the agent as 
text: the parts of the Message, or of the last"
+                                                 + " message in the history of 
the Task, one per line. TASK_GET and"
+                                                 + " TASK_CANCEL give the text 
of the Task, TASK_LIST a List of Task,"
+                                                 + " MESSAGE_STREAM and 
TASK_SUBSCRIBE an Iterator of the events as they"
+                                                 + " arrive (split it), and 
the push config operations a"
+                                                 + " 
TaskPushNotificationConfig, a List of them, or an empty Map for delete."
+                                                 + " With dataFormat POJO the 
Task or the Message, with RAW the JSON of the"
+                                                 + " response as text (a 
stream of the events for MESSAGE_STREAM and"
+                                                 + " TASK_SUBSCRIBE)"))
 public class A2AEndpoint extends DefaultEndpoint {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(A2AEndpoint.class);
diff --git 
a/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
 
b/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
index 0ce16172cd96..deed749b292a 100644
--- 
a/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
+++ 
b/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
@@ -46,6 +46,10 @@
     "CamelAiToolCallId": { "index": 0, "kind": "header", "displayName": "", 
"group": "consumer", "label": "consumer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The tool invocation identifier supplied by the 
calling framework. Absent when the framework does not supply one.", 
"constantName": 
"org.apache.camel.component.ai.tool.AiToolConstants#TOOL_CALL_ID" },
     "CamelAiToolConversationId": { "index": 1, "kind": "header", 
"displayName": "", "group": "consumer", "label": "consumer", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The conversation identifier supplied by 
the calling framework. Absent when the framework does not supply one.", 
"constantName": 
"org.apache.camel.component.ai.tool.AiToolConstants#CONVERSATION_ID" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "null" ], "description": "No body: each 
argument of the tool call is a header named as its parameter, a Map or a List 
for a nested JSON value. Undeclared arguments and names starting with camel are 
dropped" },
+    "consumerReply": { "carriers": [ "any" ], "dependsOn": [ "outputSchema", 
"outputParameters" ], "description": "The tool result, as text: the body 
converted to a String, so a Map, a List or a POJO is sent as its toString(): 
marshal it to JSON first. No body gives No result. With outputSchema or 
outputParameter.NAME options the body must be JSON: a JSON String, a Map, a 
List, a number or a boolean, and bytes, a stream or another object fail the 
call" }
+  },
   "properties": {
     "toolName": { "index": 0, "kind": "path", "displayName": "Tool Name", 
"group": "consumer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The tool name. This is the 
name the LLM sees and uses to invoke the tool." },
     "argSchema": { "index": 1, "kind": "parameter", "displayName": "Arg 
Schema", "group": "consumer", "label": "consumer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, 
"supportFileReference": true, "configurationClass": 
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField": 
"configuration", "description": "Raw JSON Schema for tool input parameters. 
Supports in [...]
diff --git 
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolEndpoint.java
 
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolEndpoint.java
index c6edab87148f..f85439260633 100644
--- 
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolEndpoint.java
+++ 
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolEndpoint.java
@@ -20,6 +20,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -43,6 +46,16 @@ import static 
org.apache.camel.component.ai.tool.AiTool.SCHEME;
              remote = false,
              headersClass = AiToolConstants.class,
              category = { Category.AI })
+@BodyTypes(consumerOut = @BodyType(value = Carrier.NULL,
+                                   description = "No body: each argument of 
the tool call is a header named as its"
+                                                 + " parameter, a Map or a 
List for a nested JSON value. Undeclared"
+                                                 + " arguments and names 
starting with camel are dropped"),
+           consumerReply = @BodyType(value = Carrier.ANY, dependsOn = { 
"outputSchema", "outputParameters" },
+                                     description = "The tool result, as text: 
the body converted to a String, so a Map, a"
+                                                   + " List or a POJO is sent 
as its toString(): marshal it to JSON first. No"
+                                                   + " body gives No result. 
With outputSchema or outputParameter.NAME"
+                                                   + " options the body must 
be JSON: a JSON String, a Map, a List, a number"
+                                                   + " or a boolean, and 
bytes, a stream or another object fail the call"))
 public class AiToolEndpoint extends DefaultEndpoint {
 
     @Metadata(required = true)
diff --git 
a/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
 
b/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
index 2929f7f68134..426611f97d2f 100644
--- 
a/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
+++ 
b/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
@@ -117,6 +117,11 @@
     "CamelDoclingChunkingMaxTokens": { "index": 30, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Maximum tokens per chunk for hybrid 
chunking", "constantName": 
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MAX_TOKENS" },
     "CamelDoclingChunkingMergePeers": { "index": 31, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Whether to merge peer chunks in hybrid 
chunking", "constantName": 
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MERGE_PEERS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "text" ], "description": "The document of 
an async conversion that completed (SUBMIT_ASYNC_CONVERSION on the same 
component), as text in the outputFormat it was submitted with: markdown by 
default, JSON text for json. The task id is in the CamelDoclingTaskId header, 
and a failed conversion goes to the error handler" },
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "BATCH_CONVERT_TO_MARKDOWN", "carriers": [ "list" ] }, { 
"option": "operation", "value": "BATCH_CONVERT_TO_HTML", "carriers": [ "list" ] 
}, { "option": "operation", "value": "BATCH_CONVERT_TO_JSON", "carriers": [ 
"list" ] }, { "option": "operation", "value": "BATCH_EXTRACT_TEXT", "carriers": 
[ "list" ] }, { "option": "operation", "value": 
"BATCH_EXTRACT_STRUCTURED_DATA", "carriers": [ "list" ] } ], [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"operation", "value": "CONVERT_TO_JSON", "carriers": [ "pojo" ], "javaType": 
"ai.docling.core.DoclingDocument" }, { "option": "operation", "value": 
"EXTRACT_STRUCTURED_DATA", "carriers": [ "pojo" ], "javaType": 
"ai.docling.core.DoclingDocument" }, { "option": "operation", "value": 
"CHECK_CONVERSION_STATUS", "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.docling.ConversionStatus" }, { "option": "operat 
[...]
+  },
   "properties": {
     "operationId": { "index": 0, "kind": "path", "displayName": "Operation 
Id", "group": "common", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The operation identifier" 
},
     "contentInBody": { "index": 1, "kind": "parameter", "displayName": 
"Content In Body", "group": "common", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.docling.DoclingConfiguration", 
"configurationField": "configuration", "description": "Include the content of 
the output file in the exchange body and delet [...]
diff --git 
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingEndpoint.java
 
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingEndpoint.java
index d7e152bfa55f..35fbad76dff3 100644
--- 
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingEndpoint.java
+++ 
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingEndpoint.java
@@ -20,6 +20,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -33,6 +36,69 @@ import org.apache.camel.support.ScheduledPollEndpoint;
              title = "Docling",
              syntax = "docling:operationId",
              category = { Category.TRANSFORMATION, Category.AI }, headersClass 
= DoclingHeaders.class)
+@BodyTypes(consumerOut = @BodyType(value = Carrier.TEXT,
+                                   description = "The document of an async 
conversion that completed"
+                                                 + " (SUBMIT_ASYNC_CONVERSION 
on the same component), as text in the"
+                                                 + " outputFormat it was 
submitted with: markdown by default, JSON text for"
+                                                 + " json. The task id is in 
the CamelDoclingTaskId header, and a failed"
+                                                 + " conversion goes to the 
error handler"),
+           producerIn = @BodyType(value = Carrier.ANY,
+                                  byOption = {
+                                          @BodyType.When(option = "operation", 
value = "BATCH_CONVERT_TO_MARKDOWN",
+                                                         carriers = 
Carrier.LIST),
+                                          @BodyType.When(option = "operation", 
value = "BATCH_CONVERT_TO_HTML",
+                                                         carriers = 
Carrier.LIST),
+                                          @BodyType.When(option = "operation", 
value = "BATCH_CONVERT_TO_JSON",
+                                                         carriers = 
Carrier.LIST),
+                                          @BodyType.When(option = "operation", 
value = "BATCH_EXTRACT_TEXT",
+                                                         carriers = 
Carrier.LIST),
+                                          @BodyType.When(option = "operation", 
value = "BATCH_EXTRACT_STRUCTURED_DATA",
+                                                         carriers = 
Carrier.LIST) },
+                                  description = "The document: its content as 
text or bytes, or a File (the GenericFile of"
+                                                + " the file consumers too). A 
stream or a Path fails: convert it to bytes"
+                                                + " first. Text that looks 
like a URL or a path (it starts with http://,";
+                                                + " https:// or /, or has a 
backslash) is read as one only with"
+                                                + " allowUrlSource or 
allowFilePathSource, and fails otherwise. With the"
+                                                + " CamelDoclingInputFilePath 
header the body is not read. The batch"
+                                                + " operations take a List or 
an array of paths or files (a directory path"
+                                                + " with allowFilePathSource), 
and CHECK_CONVERSION_STATUS the task id, unless"
+                                                + " the CamelDoclingTaskId 
header gives it"),
+           producerOut = @BodyType(value = Carrier.TEXT,
+                                   byOption = {
+                                           @BodyType.When(option = 
"operation", value = "CONVERT_TO_JSON",
+                                                          carriers = 
Carrier.POJO,
+                                                          javaType = 
"ai.docling.core.DoclingDocument"),
+                                           @BodyType.When(option = 
"operation", value = "EXTRACT_STRUCTURED_DATA",
+                                                          carriers = 
Carrier.POJO,
+                                                          javaType = 
"ai.docling.core.DoclingDocument"),
+                                           @BodyType.When(option = 
"operation", value = "CHECK_CONVERSION_STATUS",
+                                                          carriers = 
Carrier.POJO,
+                                                          javaType = 
"org.apache.camel.component.docling.ConversionStatus"),
+                                           @BodyType.When(option = 
"operation", value = "EXTRACT_METADATA",
+                                                          carriers = 
Carrier.POJO,
+                                                          javaType = 
"org.apache.camel.component.docling.DocumentMetadata"),
+                                           @BodyType.When(option = 
"operation", value = "BATCH_CONVERT_TO_MARKDOWN",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "BATCH_CONVERT_TO_HTML",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "BATCH_CONVERT_TO_JSON",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "BATCH_EXTRACT_TEXT",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "BATCH_EXTRACT_STRUCTURED_DATA",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "CHUNK_HYBRID",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "CHUNK_HIERARCHICAL",
+                                                          carriers = { 
Carrier.POJO, Carrier.LIST }) },
+                                   description = "By the operation: text for 
the conversions to markdown, HTML and text,"
+                                                 + " which with the docling 
CLI is the path of the output file unless"
+                                                 + " contentInBody 
(useDoclingServe gives the content); the task id for"
+                                                 + " SUBMIT_ASYNC_CONVERSION; 
a DoclingDocument for CONVERT_TO_JSON and"
+                                                 + " EXTRACT_STRUCTURED_DATA; 
a BatchProcessingResults for the batch"
+                                                 + " operations, a List of 
BatchConversionResult with splitBatchResults; and a"
+                                                 + " ChunkDocumentResponse for 
the chunking, a List of Chunk with"
+                                                 + " contentInBody"))
 public class DoclingEndpoint extends ScheduledPollEndpoint {
 
     @Metadata(required = true)
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
 
b/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
index 951f48a17cf5..f1aedc286daf 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
@@ -48,6 +48,10 @@
     "CamelLangChain4jChatRequestModel": { "index": 6, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The request model name.", 
"constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#REQUEST_MODEL"
 },
     "CamelLangChain4jChatResponseModel": { "index": 7, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The response model name.", 
"constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#RESPONSE_MODEL"
 }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"chatOperation", "value": "CHAT_SINGLE_MESSAGE_WITH_PROMPT", "carriers": [ 
"map" ] }, { "option": "chatOperation", "value": "CHAT_MULTIPLE_MESSAGES", 
"carriers": [ "list" ] } ], "description": "The prompt as text, or a 
ChatMessage; another body is sent as its toString(), and no body fails. 
CHAT_SINGLE_MESSAGE_WITH_PROMPT takes the variables of the template in the 
CamelLangChain4jChatPromptTemplate header as a Map, and C [...]
+    "producerOut": { "carriers": [ "text" ], "description": "The answer of the 
model as text, also when it is asked for JSON: unmarshal it to read its fields" 
}
+  },
   "properties": {
     "chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The id" },
     "chatOperation": { "index": 1, "kind": "parameter", "displayName": "Chat 
Operation", "group": "producer", "label": "", "required": true, "type": "enum", 
"javaType": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatOperations", 
"enum": [ "CHAT_SINGLE_MESSAGE", "CHAT_SINGLE_MESSAGE_WITH_PROMPT", 
"CHAT_MULTIPLE_MESSAGES" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "defaultValue": "CHAT_SINGLE_MESSAGE", 
"configurationClass": "org.apache. [...]
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java
 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java
index d333487bb13e..e70f27fa5e79 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java
@@ -20,6 +20,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -33,6 +36,19 @@ import static 
org.apache.camel.component.langchain4j.chat.LangChain4jChat.SCHEME
              syntax = "langchain4j-chat:chatId",
              producerOnly = true,
              category = { Category.AI }, headersClass = 
LangChain4jChatHeaders.class)
+@BodyTypes(producerIn = @BodyType(value = Carrier.ANY,
+                                  byOption = {
+                                          @BodyType.When(option = 
"chatOperation", value = "CHAT_SINGLE_MESSAGE_WITH_PROMPT",
+                                                         carriers = 
Carrier.MAP),
+                                          @BodyType.When(option = 
"chatOperation", value = "CHAT_MULTIPLE_MESSAGES",
+                                                         carriers = 
Carrier.LIST) },
+                                  description = "The prompt as text, or a 
ChatMessage; another body is sent as its"
+                                                + " toString(), and no body 
fails. CHAT_SINGLE_MESSAGE_WITH_PROMPT takes the"
+                                                + " variables of the template 
in the CamelLangChain4jChatPromptTemplate"
+                                                + " header as a Map, and 
CHAT_MULTIPLE_MESSAGES a List of ChatMessage"),
+           producerOut = @BodyType(value = Carrier.TEXT,
+                                   description = "The answer of the model as 
text, also when it is asked for JSON: unmarshal"
+                                                 + " it to read its fields"))
 public class LangChain4jChatEndpoint extends DefaultEndpoint {
 
     @Metadata(required = true)
diff --git 
a/components/camel-ai/camel-milvus/src/generated/resources/META-INF/org/apache/camel/component/milvus/milvus.json
 
b/components/camel-ai/camel-milvus/src/generated/resources/META-INF/org/apache/camel/component/milvus/milvus.json
index 401fd632d4cc..c2c82cb38cd3 100644
--- 
a/components/camel-ai/camel-milvus/src/generated/resources/META-INF/org/apache/camel/component/milvus/milvus.json
+++ 
b/components/camel-ai/camel-milvus/src/generated/resources/META-INF/org/apache/camel/component/milvus/milvus.json
@@ -42,6 +42,10 @@
     "CamelMilvusKeyName": { "index": 6, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Key Name for Insert\/Upsert operation", "constantName": 
"org.apache.camel.component.milvus.MilvusHeaders#KEY_NAME" },
     "CamelMilvusKeyValue": { "index": 7, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Key Value for Insert\/Upsert operation", "constantName": 
"org.apache.camel.component.milvus.MilvusHeaders#KEY_VALUE" }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "pojo" ], "description": "The request of the 
Milvus SDK for the action in the CamelMilvusAction header: a 
CreateCollectionParam, CreateIndexParam, InsertParam, UpsertParam, 
SearchSimpleParam, QueryParam or DeleteParam. Another body fails, a vector as a 
List too: the milvus:embeddings data type transformer builds the InsertParam or 
UpsertParam from the output of langchain4j-embeddings" },
+    "producerOut": { "carriers": [ "pojo" ], "description": "The result of the 
Milvus SDK for the action: an RpcStatus for CREATE_COLLECTION and CREATE_INDEX, 
a MutationResult for INSERT, UPSERT and DELETE, a SearchResponse for SEARCH and 
a QueryResults for QUERY" }
+  },
   "properties": {
     "collection": { "index": 0, "kind": "path", "displayName": "Collection", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The collection Name" },
     "host": { "index": 1, "kind": "parameter", "displayName": "Host", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "defaultValue": "localhost", "configurationClass": 
"org.apache.camel.component.milvus.MilvusConfiguration", "configurationField": 
"configuration", "description": "The host to connect to." },
diff --git 
a/components/camel-ai/camel-milvus/src/main/java/org/apache/camel/component/milvus/MilvusEndpoint.java
 
b/components/camel-ai/camel-milvus/src/main/java/org/apache/camel/component/milvus/MilvusEndpoint.java
index c803a3b4bfdc..bc647b2b11ea 100644
--- 
a/components/camel-ai/camel-milvus/src/main/java/org/apache/camel/component/milvus/MilvusEndpoint.java
+++ 
b/components/camel-ai/camel-milvus/src/main/java/org/apache/camel/component/milvus/MilvusEndpoint.java
@@ -26,6 +26,9 @@ import org.apache.camel.Component;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -47,6 +50,17 @@ import org.apache.camel.support.DefaultEndpoint;
                      Category.AI
              },
              headersClass = MilvusHeaders.class)
+@BodyTypes(producerIn = @BodyType(value = Carrier.POJO,
+                                  description = "The request of the Milvus SDK 
for the action in the CamelMilvusAction"
+                                                + " header: a 
CreateCollectionParam, CreateIndexParam, InsertParam,"
+                                                + " UpsertParam, 
SearchSimpleParam, QueryParam or DeleteParam. Another body"
+                                                + " fails, a vector as a List 
too: the milvus:embeddings data type"
+                                                + " transformer builds the 
InsertParam or UpsertParam from the output of"
+                                                + " langchain4j-embeddings"),
+           producerOut = @BodyType(value = Carrier.POJO,
+                                   description = "The result of the Milvus SDK 
for the action: an RpcStatus for"
+                                                 + " CREATE_COLLECTION and 
CREATE_INDEX, a MutationResult for INSERT, UPSERT"
+                                                 + " and DELETE, a 
SearchResponse for SEARCH and a QueryResults for QUERY"))
 public class MilvusEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
     @Metadata(required = true)
diff --git 
a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/llm.json
 
b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/llm.json
index a2451cbd1b69..6870d839b3a6 100644
--- 
a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/llm.json
+++ 
b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/llm.json
@@ -146,6 +146,12 @@
     "CamelOpenAIImageOutputTokens": { "index": 107, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of output tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_TOKENS" },
     "CamelOpenAIImageTotalTokens": { "index": 108, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The total number of tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_TOTAL_TOKENS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.openai.models.webhooks.UnwrapWebhookEvent", "description": "The event of 
the webhook operation, verified with webhookSecret, such as a batch or a 
background response that completed. The event type and the id of the object it 
is about are in headers" },
+    "consumerReply": { "carriers": [ "any" ], "description": "The body at the 
end of the route is not sent: OpenAI gets 200 without a body, or 500 when the 
route fails, and then sends the event again" },
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "decisions", "carriers": [ "pojo" ], "javaType": 
"com.openai.models.decisions.DecisionCreateParams" }, { "option": "operation", 
"value": "batch", "carriers": [ "map", "list", "text", "bytes", "stream", 
"file" ] }, { "option": "operation", "value": "audio-transcription", 
"carriers": [ "file", "bytes", "stream" ] }, { "option": "operation", "value": 
"audio-translation", "carriers": [ "file", "bytes", [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"streaming", "value": "true", "carriers": [ "iterator" ] }, { "option": 
"autoToolExecution", "value": "false", "carriers": [ "text", "pojo", "list" ] 
}, { "option": "operation", "value": "decisions", "carriers": [ "pojo" ], 
"javaType": "com.openai.models.decisions.Decision" }, { "option": "operation", 
"value": "responses", "carriers": [ "text" ] }, { "option": "operation", 
"value": "responses-retrieve", "carriers": [  [...]
+  },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.openai.OpenAIOperations", "enum": [ 
"chat-completion", "decisions", "responses", "responses-retrieve", 
"responses-cancel", "batch", "batch-retrieve", "batch-cancel", "batch-results", 
"embeddings", "tool-execution", "audio-transcription", "audio-translation", 
"audio-speech", "moderation", "image-generation", "i [...]
     "additionalBodyProperty": { "index": 1, "kind": "parameter", 
"displayName": "Additional Body Property", "group": "common", "label": "", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": 
"additionalBodyProperty.", "multiValue": true, "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", 
"configurationField": "configurat [...]
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 d212b19e520c..4d569fa9d943 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
@@ -146,6 +146,12 @@
     "CamelOpenAIImageOutputTokens": { "index": 107, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of output tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_TOKENS" },
     "CamelOpenAIImageTotalTokens": { "index": 108, "kind": "header", 
"displayName": "", "group": "common", "label": "", "required": false, 
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The total number of tokens billed for 
the image request. Only reported by GPT image models", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#IMAGE_TOTAL_TOKENS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.openai.models.webhooks.UnwrapWebhookEvent", "description": "The event of 
the webhook operation, verified with webhookSecret, such as a batch or a 
background response that completed. The event type and the id of the object it 
is about are in headers" },
+    "consumerReply": { "carriers": [ "any" ], "description": "The body at the 
end of the route is not sent: OpenAI gets 200 without a body, or 500 when the 
route fails, and then sends the event again" },
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "decisions", "carriers": [ "pojo" ], "javaType": 
"com.openai.models.decisions.DecisionCreateParams" }, { "option": "operation", 
"value": "batch", "carriers": [ "map", "list", "text", "bytes", "stream", 
"file" ] }, { "option": "operation", "value": "audio-transcription", 
"carriers": [ "file", "bytes", "stream" ] }, { "option": "operation", "value": 
"audio-translation", "carriers": [ "file", "bytes", [...]
+    "producerOut": { "carriers": [ "text" ], "byOption": [ { "option": 
"streaming", "value": "true", "carriers": [ "iterator" ] }, { "option": 
"autoToolExecution", "value": "false", "carriers": [ "text", "pojo", "list" ] 
}, { "option": "operation", "value": "decisions", "carriers": [ "pojo" ], 
"javaType": "com.openai.models.decisions.Decision" }, { "option": "operation", 
"value": "responses", "carriers": [ "text" ] }, { "option": "operation", 
"value": "responses-retrieve", "carriers": [  [...]
+  },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.openai.OpenAIOperations", "enum": [ 
"chat-completion", "decisions", "responses", "responses-retrieve", 
"responses-cancel", "batch", "batch-retrieve", "batch-cancel", "batch-results", 
"embeddings", "tool-execution", "audio-transcription", "audio-translation", 
"audio-speech", "moderation", "image-generation", "i [...]
     "additionalBodyProperty": { "index": 1, "kind": "parameter", 
"displayName": "Additional Body Property", "group": "common", "label": "", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": 
"additionalBodyProperty.", "multiValue": true, "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", 
"configurationField": "configurat [...]
diff --git 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
index 1b65e239cb5b..4fbad0d31b78 100644
--- 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
+++ 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
@@ -63,6 +63,9 @@ import 
org.apache.camel.component.ai.tool.AiToolParameterHelper;
 import org.apache.camel.component.ai.tool.AiToolRegistry;
 import org.apache.camel.component.ai.tool.AiToolRegistryListener;
 import org.apache.camel.component.ai.tool.AiToolSpec;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -85,6 +88,86 @@ import org.slf4j.LoggerFactory;
              syntax = "openai:operation",
              category = { Category.AI },
              headersClass = OpenAIConstants.class)
+@BodyTypes(consumerOut = @BodyType(value = Carrier.POJO, javaType = 
"com.openai.models.webhooks.UnwrapWebhookEvent",
+                                   description = "The event of the webhook 
operation, verified with webhookSecret, such as"
+                                                 + " a batch or a background 
response that completed. The event type and"
+                                                 + " the id of the object it 
is about are in headers"),
+           consumerReply = @BodyType(value = Carrier.ANY,
+                                     description = "The body at the end of the 
route is not sent: OpenAI gets 200 without a"
+                                                   + " body, or 500 when the 
route fails, and then sends the event again"),
+           producerIn = @BodyType(value = Carrier.ANY,
+                                  byOption = {
+                                          @BodyType.When(option = "operation", 
value = "decisions", carriers = Carrier.POJO,
+                                                         javaType = 
"com.openai.models.decisions.DecisionCreateParams"),
+                                          @BodyType.When(option = "operation", 
value = "batch",
+                                                         carriers = {
+                                                                 Carrier.MAP, 
Carrier.LIST, Carrier.TEXT, Carrier.BYTES,
+                                                                 
Carrier.STREAM, Carrier.FILE }),
+                                          @BodyType.When(option = "operation", 
value = "audio-transcription",
+                                                         carriers = { 
Carrier.FILE, Carrier.BYTES, Carrier.STREAM }),
+                                          @BodyType.When(option = "operation", 
value = "audio-translation",
+                                                         carriers = { 
Carrier.FILE, Carrier.BYTES, Carrier.STREAM }),
+                                          @BodyType.When(option = "operation", 
value = "image-edit",
+                                                         carriers = {
+                                                                 Carrier.FILE, 
Carrier.BYTES, Carrier.STREAM,
+                                                                 Carrier.LIST 
}) },
+                                  description = "The prompt of chat-completion 
and responses: text, or a file, bytes or a"
+                                                + " stream with an image or a 
PDF (chat-completion also audio), which"
+                                                + " userMessage then 
describes. Another body is sent as its toString(),"
+                                                + " and userMessage or the 
CamelOpenAIUserMessage header replaces a text"
+                                                + " body. embeddings and 
moderation take text or a List of texts"
+                                                + " (moderation also an 
image), audio-speech the text to speak, and"
+                                                + " image-generation the 
prompt unless imagePrompt is set. batch takes the"
+                                                + " requests as a Map keyed by 
custom_id, a List, or JSONL; audio takes the"
+                                                + " audio and image-edit the 
image, or a List of images. The operations"
+                                                + " that read a stored 
response, a batch or the tool calls of the last"
+                                                + " answer do not use the 
body"),
+           producerOut = @BodyType(value = Carrier.TEXT,
+                                   byOption = {
+                                           @BodyType.When(option = 
"streaming", value = "true", carriers = Carrier.ITERATOR),
+                                           @BodyType.When(option = 
"autoToolExecution", value = "false",
+                                                          carriers = { 
Carrier.TEXT, Carrier.POJO, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "decisions", carriers = Carrier.POJO,
+                                                          javaType = 
"com.openai.models.decisions.Decision"),
+                                           @BodyType.When(option = 
"operation", value = "responses", carriers = Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "responses-retrieve",
+                                                          carriers = 
Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "responses-cancel",
+                                                          carriers = 
Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "batch", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = 
"operation", value = "batch-retrieve",
+                                                          carriers = 
Carrier.UNCHANGED),
+                                           @BodyType.When(option = 
"operation", value = "batch-cancel",
+                                                          carriers = 
Carrier.UNCHANGED),
+                                           @BodyType.When(option = 
"operation", value = "batch-results",
+                                                          carriers = 
Carrier.ITERATOR),
+                                           @BodyType.When(option = 
"operation", value = "embeddings", carriers = Carrier.LIST),
+                                           @BodyType.When(option = 
"operation", value = "tool-execution",
+                                                          carriers = { 
Carrier.NULL, Carrier.UNCHANGED }),
+                                           @BodyType.When(option = 
"operation", value = "audio-transcription",
+                                                          carriers = 
Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "audio-translation",
+                                                          carriers = 
Carrier.TEXT),
+                                           @BodyType.When(option = 
"operation", value = "audio-speech",
+                                                          carriers = 
Carrier.BYTES,
+                                                          javaType = "byte[]"),
+                                           @BodyType.When(option = 
"operation", value = "moderation",
+                                                          carriers = 
Carrier.UNCHANGED),
+                                           @BodyType.When(option = 
"operation", value = "image-generation",
+                                                          carriers = { 
Carrier.BYTES, Carrier.TEXT, Carrier.LIST }),
+                                           @BodyType.When(option = 
"operation", value = "image-edit",
+                                                          carriers = { 
Carrier.BYTES, Carrier.TEXT, Carrier.LIST }) },
+                                   description = "The answer of 
chat-completion as text, also with outputClass or"
+                                                 + " jsonSchema, where it is 
JSON text: unmarshal it to read its fields."
+                                                 + " With streaming, an 
Iterator of the chunks (text when MCP tools run in"
+                                                 + " a loop). With 
autoToolExecution=false, the tool calls the model asks"
+                                                 + " for: an Optional of the 
List of tool calls, or a List of the function"
+                                                 + " calls for responses. 
embeddings gives a vector (a List of Float), or a"
+                                                 + " List of vectors for a 
List of texts; image-generation and image-edit"
+                                                 + " the image as bytes or its 
URL as text, a List for several; batch-results"
+                                                 + " an Iterator of the result 
lines as Maps. batch and moderation keep the"
+                                                 + " body and set headers, and 
tool-execution clears it when it runs tool"
+                                                 + " calls"))
 public class OpenAIEndpoint extends DefaultEndpoint {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(OpenAIEndpoint.class);
diff --git 
a/components/camel-ai/camel-qdrant/src/generated/resources/META-INF/org/apache/camel/component/qdrant/qdrant.json
 
b/components/camel-ai/camel-qdrant/src/generated/resources/META-INF/org/apache/camel/component/qdrant/qdrant.json
index 283a45b213cb..4370a6d81573 100644
--- 
a/components/camel-ai/camel-qdrant/src/generated/resources/META-INF/org/apache/camel/component/qdrant/qdrant.json
+++ 
b/components/camel-ai/camel-qdrant/src/generated/resources/META-INF/org/apache/camel/component/qdrant/qdrant.json
@@ -48,6 +48,10 @@
     "CamelQdrantPointId": { "index": 9, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "int", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The point id to use for operation.", "constantName": 
"org.apache.camel.component.qdrant.QdrantHeaders#POINT_ID" },
     "CamelQdrantMaxResults": { "index": 10, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "int", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The maximum number of results to return from a 
similarity search.", "constantName": 
"org.apache.camel.component.qdrant.QdrantHeaders#MAX_RESULTS" }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "pojo", "list", "null" ], "description": "By 
the action in the CamelQdrantAction header: a VectorParams for 
CREATE_COLLECTION, a PointStruct or a List of them for UPSERT, a PointId or a 
List of them for RETRIEVE, a PointsSelector, PointId, Filter or Condition for 
DELETE, and the query vector as a List of Float, a float array or a PointStruct 
for SIMILARITY_SEARCH. DELETE_COLLECTION and COLLECTION_INFO do not read the 
body. Another body fails, such as a n [...]
+    "producerOut": { "carriers": [ "list", "pojo", "unchanged" ], 
"description": "By the action: a List of RetrievedPoint for RETRIEVE, a List of 
ScoredPoint for SIMILARITY_SEARCH and a CollectionInfo for COLLECTION_INFO. The 
other actions keep the body, UPSERT and DELETE with the operation id and status 
in headers" }
+  },
   "properties": {
     "collection": { "index": 0, "kind": "path", "displayName": "Collection", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The collection Name" },
     "host": { "index": 1, "kind": "parameter", "displayName": "Host", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "defaultValue": "localhost", "configurationClass": 
"org.apache.camel.component.qdrant.QdrantConfiguration", "configurationField": 
"configuration", "description": "The host to connect to." },
diff --git 
a/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/QdrantEndpoint.java
 
b/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/QdrantEndpoint.java
index 3b7a1b845540..8dbb2ba6b487 100644
--- 
a/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/QdrantEndpoint.java
+++ 
b/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/QdrantEndpoint.java
@@ -25,6 +25,9 @@ import org.apache.camel.Component;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -46,6 +49,21 @@ import org.apache.camel.support.DefaultEndpoint;
                      Category.AI
              },
              headersClass = QdrantHeaders.class)
+@BodyTypes(producerIn = @BodyType(value = { Carrier.POJO, Carrier.LIST, 
Carrier.NULL },
+                                  description = "By the action in the 
CamelQdrantAction header: a VectorParams for"
+                                                + " CREATE_COLLECTION, a 
PointStruct or a List of them for UPSERT, a PointId"
+                                                + " or a List of them for 
RETRIEVE, a PointsSelector, PointId, Filter or"
+                                                + " Condition for DELETE, and 
the query vector as a List of Float, a float"
+                                                + " array or a PointStruct for 
SIMILARITY_SEARCH. DELETE_COLLECTION and"
+                                                + " COLLECTION_INFO do not 
read the body. Another body fails, such as a"
+                                                + " number as the id (use 
PointIdFactory.id) or a List of Double. The"
+                                                + " qdrant:embeddings data 
type transformer builds the PointStruct from the"
+                                                + " output of 
langchain4j-embeddings"),
+           producerOut = @BodyType(value = { Carrier.LIST, Carrier.POJO, 
Carrier.UNCHANGED },
+                                   description = "By the action: a List of 
RetrievedPoint for RETRIEVE, a List of"
+                                                 + " ScoredPoint for 
SIMILARITY_SEARCH and a CollectionInfo for"
+                                                 + " COLLECTION_INFO. The 
other actions keep the body, UPSERT and DELETE with"
+                                                 + " the operation id and 
status in headers"))
 public class QdrantEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
     @Metadata(required = true)
diff --git 
a/components/camel-ibm/camel-ibm-watson-language/src/generated/resources/META-INF/org/apache/camel/component/ibm/watson/language/ibm-watson-language.json
 
b/components/camel-ibm/camel-ibm-watson-language/src/generated/resources/META-INF/org/apache/camel/component/ibm/watson/language/ibm-watson-language.json
index ce47ac5e383e..d0b502549cf5 100644
--- 
a/components/camel-ibm/camel-ibm-watson-language/src/generated/resources/META-INF/org/apache/camel/component/ibm/watson/language/ibm-watson-language.json
+++ 
b/components/camel-ibm/camel-ibm-watson-language/src/generated/resources/META-INF/org/apache/camel/component/ibm/watson/language/ibm-watson-language.json
@@ -53,6 +53,10 @@
     "CamelIBMWatsonLanguageSentimentScore": { "index": 10, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The sentiment score", "constantName": 
"org.apache.camel.component.ibm.watson.language.WatsonLanguageConstants#SENTIMENT_SCORE"
 },
     "CamelIBMWatsonLanguageSentimentLabel": { "index": 11, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The sentiment label (positive, 
negative, neutral)", "constantName": 
"org.apache.camel.component.ibm.watson.language.WatsonLanguageConstants#SENTIMENT_LABEL"
 }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "any" ], "description": "The text to analyze 
for analyzeText, or the URL of the page for analyzeUrl, read as text, unless 
the CamelIBMWatsonLanguageText or CamelIBMWatsonLanguageUrl header gives it. 
Without the header, no body or a blank one fails, and a Map or a POJO is 
analyzed as its toString(): marshal it first" },
+    "producerOut": { "carriers": [ "pojo" ], "javaType": 
"com.ibm.watson.natural_language_understanding.v1.model.AnalysisResults", 
"description": "The results of the features enabled (sentiment, entities, 
keywords and so on), with the sentiment score and label and the language in 
headers too. Converted to text, it is the JSON of the results" }
+  },
   "properties": {
     "label": { "index": 0, "kind": "path", "displayName": "Label", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Logical name" },
     "serviceUrl": { "index": 1, "kind": "parameter", "displayName": "Service 
Url", "group": "common", "label": "common", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.ibm.watson.language.WatsonLanguageConfiguration", 
"configurationField": "configuration", "description": "The service endpoint 
URL. If not specified, the default URL will be used." },
diff --git 
a/components/camel-ibm/camel-ibm-watson-language/src/main/java/org/apache/camel/component/ibm/watson/language/WatsonLanguageEndpoint.java
 
b/components/camel-ibm/camel-ibm-watson-language/src/main/java/org/apache/camel/component/ibm/watson/language/WatsonLanguageEndpoint.java
index 801c07756df8..f8ff4572ffdb 100644
--- 
a/components/camel-ibm/camel-ibm-watson-language/src/main/java/org/apache/camel/component/ibm/watson/language/WatsonLanguageEndpoint.java
+++ 
b/components/camel-ibm/camel-ibm-watson-language/src/main/java/org/apache/camel/component/ibm/watson/language/WatsonLanguageEndpoint.java
@@ -22,6 +22,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -35,6 +38,17 @@ import org.apache.camel.support.DefaultEndpoint;
 @UriEndpoint(firstVersion = "4.16.0", scheme = "ibm-watson-language", title = 
"IBM Watson Language",
              syntax = "ibm-watson-language:label", producerOnly = true, 
category = { Category.AI, Category.CLOUD },
              headersClass = WatsonLanguageConstants.class)
+@BodyTypes(producerIn = @BodyType(value = Carrier.ANY,
+                                  description = "The text to analyze for 
analyzeText, or the URL of the page for"
+                                                + " analyzeUrl, read as text, 
unless the CamelIBMWatsonLanguageText or"
+                                                + " CamelIBMWatsonLanguageUrl 
header gives it. Without the header, no body or"
+                                                + " a blank one fails, and a 
Map or a POJO is analyzed as its toString():"
+                                                + " marshal it first"),
+           producerOut = @BodyType(value = Carrier.POJO,
+                                   javaType = 
"com.ibm.watson.natural_language_understanding.v1.model.AnalysisResults",
+                                   description = "The results of the features 
enabled (sentiment, entities, keywords and so"
+                                                 + " on), with the sentiment 
score and label and the language in headers too."
+                                                 + " Converted to text, it is 
the JSON of the results"))
 public class WatsonLanguageEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
     @UriPath(description = "Logical name")
diff --git a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc
index 573ada4c6125..454c8930fb64 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc
@@ -188,7 +188,8 @@ public class FileEndpoint extends GenericFileEndpoint<File> 
{
 ----
 
 The build checks that each side belongs to the kind (a consumer-only component 
has no producer sides), that the
-options a side names exist, that `unchanged` is only on the body after a step 
and `any` only on what a step needs.
+options a side names exist, that a value of an option is one the option can 
have (such as `chat-completion` for the
+`operation` of openai), and that `unchanged` is only on the body after a step 
and `any` only on what a step needs.
 The values are read from the code that sets the body, and checked against the 
body types the message history records
 when the routes run.
 
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-validation.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-validation.adoc
index 26c18f3af01c..54c39089b805 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-validation.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-validation.adoc
@@ -77,8 +77,9 @@ The YAML validator follows the message body through the steps 
of each route, and
 it: a field read such as `+${body.qty}+` on text that was never unmarshalled, 
or a Map that a `to: file` cannot write.
 What a component or a data format does with the body comes from the
 xref:camel-catalog.adoc#_body_types_in_the_catalog[body types in the catalog]: 
the file consumer gives the file it
-read, the `http` producer leaves the response as bytes or a stream, and 
`unmarshal: json` makes a Map or a List.
-Where they do not tell what the body is, the validator says nothing: after a 
`platform-http` consumer the body
+read, the `http` producer leaves the response as bytes or a stream, and 
`unmarshal: json` makes a Map or a List. The
+answer of `openai:chat-completion` is text, also when it is asked for JSON 
with `jsonSchema`, so a field read on it
+needs `unmarshal: json` first. Where they do not tell what the body is, the 
validator says nothing: after a `platform-http` consumer the body
 depends on the request, and the deserializer of a `kafka` consumer can be set 
on the component, which the route file
 does not show.
 
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
index 3259582da25f..897532a0a271 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
@@ -191,9 +191,44 @@ final class CatalogBodyTypes {
         String text = node != null && node.isTextual() ? node.asText()
                 : node != null && node.isObject() && node.has("uri") ? 
node.get("uri").asText() : null;
         set.putAll(query(text));
+        readPath(uri, model, set, configurable);
         return answer(model.getBodyType(side), set, configurable);
     }
 
+    /**
+     * Adds the options in the path of an endpoint uri, as the syntax of its 
component names them: the operation of
+     * openai:embeddings. A path the catalog cannot read says nothing about 
them, as an option the component may set.
+     */
+    private void readPath(String uri, ComponentModel model, Map<String, 
String> set, Set<String> configurable) {
+        if (uri == null || model.getEndpointPathOptions().isEmpty()) {
+            return;
+        }
+        // the query is read on its own, and may hold what the parser of the 
catalog does not take, such as a space
+        int q = uri.indexOf('?');
+        String path = q < 0 ? uri : uri.substring(0, q);
+        Optional<Map<String, String>> properties = endpointProperties(catalog, 
path);
+        if (properties.isEmpty() && fallback != null) {
+            properties = endpointProperties(fallback, path);
+        }
+        for (BaseOptionModel option : model.getEndpointPathOptions()) {
+            String name = option.getName();
+            if (properties.isEmpty()) {
+                configurable.add(name);
+            } else if (properties.get().containsKey(name)) {
+                set.putIfAbsent(name, properties.get().get(name));
+            }
+        }
+    }
+
+    private static Optional<Map<String, String>> 
endpointProperties(CamelCatalog catalog, String uri) {
+        try {
+            return Optional.ofNullable(catalog.endpointProperties(uri));
+        } catch (Exception e) {
+            // not an endpoint the catalog can read, such as one of a 
component it does not have
+            return Optional.empty();
+        }
+    }
+
     /**
      * What a side says for the options a step sets: the body for an option 
value it names, or its body when the options
      * it depends on keep their defaults; null when it is not known for the 
step.
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
index 5c0085afe5a8..0c2af877367d 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
@@ -1037,6 +1037,50 @@ public class TextBodyFlowTest {
         assertThat(lines(yaml)).isEmpty();
     }
 
+    @Test
+    public void testTheOptionsInThePathOfTheEndpoint() {
+        // the operation of openai is in the path: the answer of 
chat-completion is text, also when it is asked for as
+        // JSON, embeddings gives a List, and an operation only known when the 
route runs says nothing
+        String yaml = """
+                - route:
+                    id: city
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: Name a city in Denmark
+                        - to: 
openai:chat-completion?jsonSchema=resource:classpath:city.json
+                        - setHeader:
+                            name: city
+                            simple: ${body[name]}
+                - route:
+                    id: vector
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: Apache Camel
+                        - to: llm:embeddings
+                        - setHeader:
+                            name: first
+                            simple: ${body[0]}
+                - route:
+                    id: dynamic
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: Apache Camel
+                        - toD: openai:${header.operation}
+                        - setHeader:
+                            name: first
+                            simple: ${body[0]}
+                """;
+        assertThat(lines(yaml)).singleElement().satisfies(m -> assertThat(m)
+                .contains("route city: simple reads a field of the body 
(${body[name]})")
+                .contains("to: openai:chat-completion returns it as text: 
unmarshal it first"));
+    }
+
     @Test
     public void testOneReportForAStepThatFails() {
         // the first write fails at runtime, so the second does not run with 
this body
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
index fcb3789aa78a..8ce467e34661 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
@@ -18,7 +18,7 @@ package org.apache.camel.maven.packaging;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -28,6 +28,7 @@ import org.apache.camel.spi.BodyType;
 import org.apache.camel.spi.BodyType.Carrier;
 import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.tooling.model.BaseModel;
+import org.apache.camel.tooling.model.BaseOptionModel;
 import org.apache.camel.tooling.model.BodyTypeModel;
 import org.apache.camel.tooling.model.ComponentModel;
 
@@ -50,8 +51,8 @@ final class BodyTypesHelper {
      * @param  bodyTypes                the annotation, null when the class 
has none
      * @param  owner                    the class with the annotation, for the 
error message
      * @throws IllegalArgumentException when a side does not fit the model: a 
data format side on a component, a
-     *                                  producer side on a consumer-only 
component, unchanged on what a step needs or
-     *                                  any on what it leaves
+     *                                  producer side on a consumer-only 
component, unchanged on what a step needs, any
+     *                                  on what it leaves, or a value an 
option cannot have
      */
     static void addBodyTypes(BaseModel<?> model, BodyTypes bodyTypes, Class<?> 
owner) {
         if (bodyTypes == null) {
@@ -70,7 +71,7 @@ final class BodyTypesHelper {
         sides.put(BodyTypeModel.EXPRESSION_OUT, bodyTypes.expressionOut());
 
         Set<String> allowed = allowedSides(model);
-        Set<String> options = optionNames(model);
+        Map<String, BaseOptionModel> options = options(model);
         for (Map.Entry<String, BodyType> entry : sides.entrySet()) {
             String side = entry.getKey();
             BodyType bodyType = entry.getValue();
@@ -93,6 +94,7 @@ final class BodyTypesHelper {
             }
             for (BodyType.When when : bodyType.byOption()) {
                 checkOption(owner, side, when.option(), options);
+                checkValue(owner, side, when, options.get(when.option()));
                 check(owner, side + " " + when.option() + "=" + when.value(), 
when.carriers());
             }
             model.getBodyTypes().put(side, asModel(bodyType));
@@ -118,24 +120,39 @@ final class BodyTypesHelper {
         };
     }
 
-    /** The options of the model, which the body types can depend on. */
-    private static Set<String> optionNames(BaseModel<?> model) {
-        Set<String> names = new HashSet<>();
-        model.getOptions().forEach(o -> names.add(o.getName()));
+    /** The options of the model by name, which the body types can depend on. 
*/
+    private static Map<String, BaseOptionModel> options(BaseModel<?> model) {
+        Map<String, BaseOptionModel> answer = new HashMap<>();
+        model.getOptions().forEach(o -> answer.put(o.getName(), o));
         if (model instanceof ComponentModel component) {
-            component.getComponentOptions().forEach(o -> 
names.add(o.getName()));
-            component.getEndpointOptions().forEach(o -> 
names.add(o.getName()));
+            component.getComponentOptions().forEach(o -> 
answer.put(o.getName(), o));
+            component.getEndpointOptions().forEach(o -> 
answer.put(o.getName(), o));
         }
-        return names;
+        return answer;
     }
 
-    private static void checkOption(Class<?> owner, String side, String 
option, Set<String> options) {
-        if (!options.contains(option)) {
+    private static void checkOption(Class<?> owner, String side, String 
option, Map<String, BaseOptionModel> options) {
+        if (!options.containsKey(option)) {
             throw new IllegalArgumentException(
                     owner.getName() + ": the @BodyTypes " + side + " depends 
on " + option + ", which is not an option");
         }
     }
 
+    /**
+     * A body for a value the option cannot have would never hold: the value 
is one of an enum, or true or false for a
+     * boolean, in any case, as tooling compares it with what the endpoint 
sets.
+     */
+    private static void checkValue(Class<?> owner, String side, BodyType.When 
when, BaseOptionModel option) {
+        List<String> values = option.getEnums() != null && 
!option.getEnums().isEmpty()
+                ? option.getEnums()
+                : "boolean".equals(option.getType()) ? List.of("true", 
"false") : List.of();
+        if (!values.isEmpty() && values.stream().noneMatch(v -> 
v.equalsIgnoreCase(when.value()))) {
+            throw new IllegalArgumentException(
+                    owner.getName() + ": the @BodyTypes " + side + " has " + 
when.option() + "=" + when.value()
+                                               + ", which is not a value of 
the option: " + String.join(", ", values));
+        }
+    }
+
     /**
      * A body has a carrier: an option value without one would say nothing. 
UNCHANGED is for the body after a step, such
      * as a sql query that leaves it unchanged unless it is a select. ANY is 
for the bodies a step needs. ANY and
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
index 5fa2fe71426d..24d5b3d86b84 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
@@ -233,7 +233,8 @@ class EndpointSchemaGeneratorMojoTest {
     @Test
     void testEndpointWithBodyTypes() throws Exception {
         // CAMEL-24961: the body a step needs and the body it leaves, in the 
order of the annotation
-        addEndpointOptions("resultHandlerFactory", "output");
+        addEndpointOptions("resultHandlerFactory");
+        addEnumOption("output", "string", "bytes");
         mojo.addBodyTypes(model, SomeEndpointWithBodyTypes.class);
         assertEquals(List.of(BodyTypeModel.CONSUMER_OUT, 
BodyTypeModel.PRODUCER_IN, BodyTypeModel.PRODUCER_OUT),
                 List.copyOf(model.getBodyTypes().keySet()));
@@ -278,9 +279,10 @@ class EndpointSchemaGeneratorMojoTest {
             SomeEndpointWithBadBodyTypes.DataFormatSide.class, 
SomeEndpointWithBadBodyTypes.ProducerSideOfConsumer.class,
             SomeEndpointWithBadBodyTypes.UnchangedInput.class, 
SomeEndpointWithBadBodyTypes.AnyOutput.class,
             SomeEndpointWithBadBodyTypes.UnknownWithOthers.class, 
SomeEndpointWithBadBodyTypes.NoCarriers.class,
-            SomeEndpointWithBadBodyTypes.UnknownOption.class, 
SomeEndpointWithBadBodyTypes.OptionValueWithoutCarriers.class })
+            SomeEndpointWithBadBodyTypes.UnknownOption.class, 
SomeEndpointWithBadBodyTypes.OptionValueWithoutCarriers.class,
+            SomeEndpointWithBadBodyTypes.ValueTheOptionDoesNotHave.class })
     void testEndpointWithBadBodyTypes(Class<?> clazz) {
-        addEndpointOptions("output");
+        addEnumOption("output", "string", "bytes");
         
model.setConsumerOnly(clazz.getAnnotation(UriEndpoint.class).consumerOnly());
         MojoExecutionException e = assertThrows(MojoExecutionException.class, 
() -> mojo.addBodyTypes(model, clazz));
         assertTrue(e.getMessage().startsWith(clazz.getName() + ": the 
@BodyTypes "), e.getMessage());
@@ -294,4 +296,12 @@ class EndpointSchemaGeneratorMojoTest {
             model.addEndpointOption(option);
         }
     }
+
+    private void addEnumOption(String name, String... values) {
+        ComponentModel.EndpointOptionModel option = new 
ComponentModel.EndpointOptionModel();
+        option.setName(name);
+        option.setType("enum");
+        option.setEnums(List.of(values));
+        model.addEndpointOption(option);
+    }
 }
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java
 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java
index cccd18faa715..8c0f53728d6a 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java
@@ -69,4 +69,10 @@ public final class SomeEndpointWithBadBodyTypes {
                                        byOption = @BodyType.When(option = 
"output", value = "bytes", carriers = {})))
     public static class OptionValueWithoutCarriers {
     }
+
+    @UriEndpoint(scheme = "some", syntax = "some", title = "some")
+    @BodyTypes(producerOut = @BodyType(value = Carrier.TEXT,
+                                       byOption = @BodyType.When(option = 
"output", value = "DOM", carriers = Carrier.POJO)))
+    public static class ValueTheOptionDoesNotHave {
+    }
 }

Reply via email to