Skip to content

CAMEL-24961: camel-ai - the body types of openai, langchain4j-chat, a2a, docling, milvus, qdrant, ai-tool and ibm-watson-language - #28250

Merged
davsclaus merged 26 commits into
mainfrom
feature/CAMEL-24961-catalog-body-types-ai
Oct 24, 2026
Merged

davsclaus merged 26 commits into
mainfrom
feature/CAMEL-24961-catalog-body-types-ai

Conversation

@davsclaus

Copy link
Copy Markdown
Contributor

Stacked on #28246. This is the AI group of the catalog body types (CAMEL-24961). The group is openai, langchain4j-chat, a2a, docling, milvus, qdrant, ai-tool and ibm-watson-language: of the components the human-written routes use, these are the ones used most that had no body types yet.

What the catalog says now

Component Producer input Producer output
openai any: the prompt, which userMessage can replace. audio, image-edit, batch and decisions take their own body text. With streaming=true, an iterator. With autoToolExecution=false, the tool calls. Per operation: embeddings gives a list, audio-speech bytes, batch-results an iterator, decisions a Decision, and batch and moderation keep the body
langchain4j-chat any. CHAT_SINGLE_MESSAGE_WITH_PROMPT takes a map, CHAT_MULTIPLE_MESSAGES a list text
a2a text, bytes, stream, file or pojo (a Message). The task and push config operations read headers text. Per operation and dataFormat: an iterator of events, a list of tasks, a config
docling any, as CamelDoclingInputFilePath can replace the body. The batch operations take a list text. A DoclingDocument, a status, metadata, or the batch and chunk results
milvus pojo: the request of the SDK pojo: the result of the SDK
qdrant pojo, list or null, by the action header list, pojo or unchanged
ibm-watson-language any, as a header can give the text pojo: AnalysisResults

The consumers:

  • the openai webhook gives the event (UnwrapWebhookEvent) and answers without a body;
  • a2a gives the text of the message (the Message with dataFormat=POJO) and replies with text or a Message;
  • docling gives the converted document as text;
  • ai-tool gives no body, as the arguments are headers, and sends the body back as text.

Checked at runtime against llama3.2 on a local Ollama, through its OpenAI API:

  • chat-completion gives a String, also with jsonSchema;
  • with streaming=true, an Iterator of ChatCompletionChunk;
  • responses gives a String;
  • with autoToolExecution=false, a java.util.Optional of the tool calls; when the tools run, a String;
  • the ai-tool route gets no body, and the argument as a header;
  • the three operations of langchain4j-chat give a String.

The local server was not started for embeddings, so I took that body from the SDK, where Embedding.embedding() returns a List<Float>. The other components were read from their code.

Two changes, for openai

  • The validator reads the options in the path of an endpoint, as the catalog parses them. The operation of openai is in the path (openai:embeddings). A path the catalog cannot read, such as a toD with an expression, says nothing about its options.
  • The build rejects a byOption value that its option cannot have: it must be a value of the enum, or true or false for a boolean, ignoring case. A misspelled embedding would otherwise never hold. The 166 values in the catalog pass.

When a header can replace the body. Tooling cannot see headers. The prompt of openai, the text of Watson and the file of docling can each come from a header, and then the body is not used. So those inputs are any, and the description says what the body must be without the header. This way the validator never reports a body that a header replaces.

Measured. I ran the validator over the 1204 human-written and 1209 model-written route files: the reports are the same as with #28246. The human files use openai 7 times, a2a 5, langchain4j-chat 2, ibm-watson-language 2, and milvus, qdrant and ai-tool once each. None reads a field of the text answer without an unmarshal: the milvus example splits the text with ${body.trim().split(';')[0]}.

Tests

  • TextBodyFlowTest.testTheOptionsInThePathOfTheEndpoint:
    • a field read on the JSON answer of chat-completion is reported;
    • llm:embeddings gives a List;
    • a toD with an expression in the path says nothing.
  • EndpointSchemaGeneratorMojoTest: a value the option does not have.
  • These pass:
    • the 280 tests of camel-yaml-dsl-validator;
    • the 187 validator tests of camel-jbang-core;
    • the 102 tests of camel-package-maven-plugin.

Docs: the validation page has the openai example, and the catalog page lists the new build check.

Claude Code on behalf of davsclaus

🤖 Generated with Claude Code

davsclaus and others added 10 commits October 21, 2026 13:58
…s, and the body it leaves after a step

A @BodyTypes annotation on the endpoint, data format or language class says the
body per side (consumerOut, consumerReply, producerIn, producerOut, marshal,
unmarshal and expression in and out): its carriers (null, text, bytes, stream,
file, value, map, list, pojo, unchanged, any, unknown), the Java type when it is
always the same, the format, the options it depends on, the body for other
option values, and a description. The package plugin writes it as a bodyTypes
section of the catalog JSON and checks that each side fits the kind and that
the options it names exist. A subclass declares its own.

The first slice: file, direct, seda, timer, cron, kamelet, http, https,
rest-openapi, xslt, sql and the json data format (jackson), read from the code
that sets the body and checked against the body types the message history
records when routes run. camel_catalog_doc returns them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…tp, jms, kafka, aws2-sqs, the csv, jacksonXml and yaml data formats, and the languages

The second slice of the catalog body types:
- components: platform-http, rest (and the rest-openapi consumer), ftp, ftps,
  sftp, jms, activemq, activemq6, amqp, kafka, aws2-sqs, log, mock, scheduler,
  quartz;
- data formats: csv, jacksonXml, snakeYaml;
- languages: simple, constant, jsonpath, xpath, groovy, jq.

Each value is read from the code that sets the body, and checked against the
body types the message history recorded when the tests of the modules and
reference routes ran (the ftp integration tests included).

camel_catalog_doc gives the body types a line per side, and its answer is cut
at 18000 characters instead of 16000, so the four sides of the jms components
leave the options a limit asks for room.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
paho-mqtt5: the consumer gives the payload as a byte array, the producer sends
what converts to one and leaves the body unchanged.

mongodb: the consumer gives a Document (or null for a change without its full
document with changeStreams); the producer takes a document or a query, and
leaves a body by operation: a Document or null for a find of one, a List for
findAll, findDistinct and aggregate, a Long for count, the result of the driver
for the other writes.

Read from the code that sets the body, and checked against a mosquitto and a
mongodb container started with camel infra.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…ts an option value without carriers

From the review of PR 28241:

- An iterator is read once and has no size and no index, so tooling that
  reads list as a List accepts ${body.size()} or ${body[0]} on it, which fail
  at runtime. The new iterator carrier names it: sql with outputType=StreamList
  gives an iterator, not a list.
- A @BodyType.When with no carriers (carrier = {}) wrote "carriers": [] into
  the catalog. The build now rejects it, as it does a side without carriers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…MEL-24961-catalog-body-types-2

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
… camel_catalog_doc says unknown for no carriers

From the review of PR 28242:

- The csv data format with lazyLoad=true gives an iterator, with the iterator
  carrier of PR 28241, not a list.
- camel_catalog_doc says unknown for a body without carriers, instead of
  failing. The build rejects it now, but a catalog read from elsewhere may
  have one.
- Sqs2Endpoint imports org.apache.camel.spi.* already: only the import of the
  nested BodyType.Carrier stays.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…types of the catalog instead of their own tables

The YAML validator's body walks read what a component or a data format does
with the body from the catalog, instead of from tables in their source:

- the consumer a route starts with (consumerOut): the file as read, text,
  bytes or data, instead of the file consumer only;
- the body after a to: (producerOut): unchanged for log and mock and now for a
  file write, the response of the http producer as bytes or a stream;
- the producers that need text (producerIn), instead of file, ftp, ftps and
  sftp only;
- the body after an unmarshal (unmarshalOut), instead of the json, jacksonXml,
  yaml and csv table and the options that make something else;
- the consumers that reply with text (consumerReply), instead of platform-http;
- the consumers that give no body, instead of timer, quartz, scheduler and cron.

A side holds for a step only when the step decides every option it depends
on: an option the component can also be configured with (the valueDeserializer
of kafka) counts only when the endpoint sets it, and a placeholder value is not
known. The catalog of an older Camel version has no body types, so the
validator's own catalog stands in for it. After a step that fails at runtime
the walk stops, so a file write that keeps the body is reported once.

The rules about HTTP methods, REST bindings and EIPs stay in the validator; the
EIPs get body types with CAMEL-25928.

The same reports as before on 1204 human-written route files (the Camel
repository, the examples, kamelets, camel-k and camel-kit) and on 1209
model-written ones (the r17 benchmark series), and the doc examples of the
validator tests still validate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…docling, milvus, qdrant, ai-tool and ibm-watson-language

The AI components declare the body they need and the body they leave, per
operation where the operation decides it. The answer of openai and
langchain4j-chat is text, also when it is asked for JSON, so the YAML
validator now reports a field read on it that has no unmarshal before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
… endpoint count for its body types

The operation of openai is in the path (openai:embeddings), so the validator
reads the path options with the syntax of the component, as the catalog
parses an endpoint. A path the catalog cannot read, such as a toD with an
expression, says nothing about its options.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…pe for a value its option cannot have

A byOption value must be one of the enum of its option, or true or false for
a boolean, ignoring case as tooling compares it with the endpoint: a
misspelled operation such as embedding for embeddings would never hold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
@davsclaus davsclaus added this to the 4.24.0 milestone Oct 21, 2026

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. No issues found.

Clean enhancement: @BodyTypes annotations on 8 AI component endpoints, readPath() correctly handles null URIs, dynamic URIs (falls through to configurable), and static path options. checkValue() NPE safety is guaranteed by checkOption() running first. The new test covers all three cases (static path → error, list indexing → ok, dynamic path → silent). Enum values in the annotations were verified against the component models.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

davsclaus and others added 6 commits October 21, 2026 20:46
The attribute holds an array, and the catalog JSON and BodyTypeModel call
the same field carriers. The Javadoc of BodyTypeModel.isUnknown() says that
a side with no carriers is unknown too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…AMEL-24961-catalog-body-types-ai

# Conflicts:
#	tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…e data format and language schemas

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my earlier review — previous findings are all addressed.

The new commit (1ef9d76) is a clean mechanical rename of the carrier field to carriers in every @BodyType.When annotation across A2AEndpoint, DoclingEndpoint, and LangChain4jChatEndpoint. The rename is applied consistently across all occurrences in all three files. No logic change, no runtime behavior change — this is a pure API alignment with the updated @BodyType.When annotation definition in the parent stack. LGTM.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

…atalog-body-types-3

Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
@davsclaus
davsclaus changed the base branch from feature/CAMEL-24961-catalog-body-types-3 to main October 24, 2026 06:46
…atalog-body-types-ai

# Conflicts:
#	docs/user-manual/modules/ROOT/pages/camel-jbang-validation.adoc
#	dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
#	dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
@davsclaus davsclaus added the enhancement New feature or request label Oct 24, 2026
@davsclaus
davsclaus merged commit 2d084ca into main Oct 24, 2026
5 of 7 checks passed
@davsclaus
davsclaus deleted the feature/CAMEL-24961-catalog-body-types-ai branch October 24, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants