This is an automated email from the ASF dual-hosted git repository.

Croway 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 2e66cc2330c5 CAMEL-24550: Document AI component exception types for 
onException handling
2e66cc2330c5 is described below

commit 2e66cc2330c57ffc56590a63884b1d88ae94ae31
Author: Karol <[email protected]>
AuthorDate: Sat Aug 29 15:51:18 2026 +0200

    CAMEL-24550: Document AI component exception types for onException handling
    
    Document the SDK exception types that camel-openai and 
camel-langchain4j-chat
    propagate unchanged, so routes can retry rate limits and 5xx while failing 
fast
    on validation and auth errors. Adds the interaction between the SDK's own 
retry
    layer (maxRetries) and Camel redelivery, which multiply rather than replace 
each
    other, and a cross-component overview in the LLM Integration Guide.
    
    Co-authored-by: Claude <[email protected]>
---
 .../catalog/docs/langchain4j-chat-component.adoc   | 58 +++++++++++++++
 .../camel/catalog/docs/openai-component.adoc       | 87 +++++++++++++++++++++-
 .../src/main/docs/langchain4j-chat-component.adoc  | 58 +++++++++++++++
 .../src/main/docs/openai-component.adoc            | 87 +++++++++++++++++++++-
 .../src/main/docs/ai-llm-integration-guide.adoc    | 41 ++++++++++
 5 files changed, 329 insertions(+), 2 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-chat-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-chat-component.adoc
index c71b3d6e9d5d..cbe442b39008 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-chat-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-chat-component.adoc
@@ -325,3 +325,61 @@ List<Content> contents = List.of(augmentedContent);
 
 String response = template.requestBodyAndHeader("direct:send-multiple", 
messages, LangChain4jChatHeaders.AUGMENTED_DATA, contents, String.class);
 ----
+
+== Error Handling
+
+The component does not wrap what the chat model throws. LangChain4j maps 
provider HTTP errors onto its own hierarchy in `dev.langchain4j.exception`, and 
that exception reaches the exchange unchanged, so `onException` can match it by 
type.
+
+The hierarchy already splits along the axis that matters for retries:
+
+[cols="2,3"]
+|===
+| Exception | Meaning
+
+| `RetriableException` | Base type for failures worth another attempt
+| `RateLimitException` | Provider rejected the call with a rate limit
+| `InternalServerException` | Provider-side failure
+| `TimeoutException` | The call did not complete within the model's timeout
+| `NonRetriableException` | Base type for failures that will fail the same way 
again
+| `InvalidRequestException` | Malformed request, including context-length 
errors
+| `ContentFilteredException` | Content rejected by the provider's moderation, 
a subclass of `InvalidRequestException`
+| `AuthenticationException` | Bad or missing API key
+| `ModelNotFoundException` | Unknown model name
+| `UnresolvedModelServerException` | The model server address could not be 
resolved
+|===
+
+Because the two base classes carry the decision, a route rarely has to 
enumerate the concrete types:
+
+[source, java]
+----
+onException(RetriableException.class)
+    .maximumRedeliveries(3)
+    .redeliveryDelay(2000)
+    .backOffMultiplier(2)
+    .useExponentialBackOff();
+
+onException(NonRetriableException.class)
+    .maximumRedeliveries(0)
+    .handled(true)
+    .to("direct:rejected");
+
+from("direct:chat")
+    .to("langchain4j-chat:my-chat");
+----
+
+IMPORTANT: The mapping is performed by the LangChain4j model implementation, 
not by Camel. `OpenAiChatModel` and `OllamaChatModel` route their calls through 
LangChain4j's exception mapper, but a model that does not will propagate the 
raw exception of its own HTTP client instead. Verify what your provider throws 
before relying on the type.
+
+=== Model retries vs Camel redelivery
+
+Most LangChain4j builders expose `maxRetries`, defaulting to `2`, which means 
one call already makes up to three attempts before any exception reaches Camel. 
Adding `maximumRedeliveries(3)` on top of that multiplies out to twelve 
attempts against a provider that is already rate limiting you.
+
+Configure one layer. Either leave the model retries alone and skip Camel 
redelivery, or build the model with `maxRetries(0)` and drive retries from 
`onException` so they are visible to the Camel error handler:
+
+[source, java]
+----
+ChatModel model = OpenAiChatModel.builder()
+        .apiKey(System.getenv("OPENAI_API_KEY"))
+        .modelName("gpt-4o-mini")
+        .maxRetries(0)
+        .build();
+----
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
index 5d7462a31161..eea6c204ad13 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
@@ -1277,4 +1277,89 @@ The component may throw the following exceptions:
 * `CamelExchangeException`:
   ** When moderation returns a number of results that does not match the 
number of inputs (moderation)
   ** When the image response contains no images, or an image with neither 
`b64_json` nor `url` (image-generation, image-edit)
-* API-specific exceptions from the OpenAI SDK for network errors, 
authentication failures, rate limiting, etc.
+* API-specific exceptions from the OpenAI SDK for network errors, 
authentication failures, rate limiting, etc. These are propagated unchanged, so 
routes can match them by type. See 
<<_targeting_sdk_exceptions_with_onexception>>.
+
+=== Targeting SDK Exceptions with onException
+
+The producer does not wrap or reclassify what the OpenAI SDK throws. The SDK 
exception reaches the exchange as-is, so `onException` can match the concrete 
type and treat a transient failure differently from a terminal one.
+
+Every HTTP error response maps to a subclass of 
`com.openai.errors.OpenAIServiceException`:
+
+[cols="2,1,3"]
+|===
+| Exception | Status | Worth retrying
+
+| `BadRequestException` | 400 | No, the request is malformed
+| `UnauthorizedException` | 401 | No, fix the API key
+| `PermissionDeniedException` | 403 | No
+| `NotFoundException` | 404 | No, usually an unknown model or a wrong `baseUrl`
+| `UnprocessableEntityException` | 422 | No, typically a context-length or 
validation error
+| `RateLimitException` | 429 | Yes, with backoff
+| `InternalServerException` | 5xx | Yes, with backoff
+| `UnexpectedStatusCodeException` | other | Depends on the provider
+|===
+
+Three further exceptions carry no status code and extend 
`com.openai.errors.OpenAIException` directly: `OpenAIIoException` for connect 
and read failures, `OpenAIRetryableException` when the SDK gave up after 
exhausting its own retries, and `OpenAIInvalidDataException` when the response 
body cannot be parsed.
+
+Retrying the transient cases while failing fast on the rest:
+
+[source,java]
+----
+onException(RateLimitException.class, InternalServerException.class, 
OpenAIIoException.class)
+    .maximumRedeliveries(3)
+    .redeliveryDelay(2000)
+    .backOffMultiplier(2)
+    .useExponentialBackOff();
+
+onException(BadRequestException.class, UnprocessableEntityException.class,
+            UnauthorizedException.class)
+    .maximumRedeliveries(0)
+    .handled(true)
+    .to("direct:rejected");
+
+from("direct:ask")
+    .to("openai:chat-completion?model=gpt-4o-mini");
+----
+
+`OpenAIServiceException` carries the details of the failed call, which is what 
you want for logging or for routing on the provider's own error code:
+
+[source,java]
+----
+onException(OpenAIServiceException.class)
+    .handled(true)
+    .process(exchange -> {
+        OpenAIServiceException cause
+                = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, 
OpenAIServiceException.class);
+        exchange.getIn().setHeader("FailureStatus", cause.statusCode());
+        exchange.getIn().setHeader("FailureCode", cause.code().orElse(null));
+        exchange.getIn().setHeader("FailureType", cause.type().orElse(null));
+    })
+    .to("direct:failed-calls");
+----
+
+`headers()` on the same exception exposes the response headers, including 
`Retry-After` on a 429.
+
+=== SDK Retry vs Camel Redelivery
+
+The SDK client retries on its own before the exception ever reaches Camel. The 
`maxRetries` option controls this and defaults to `2`, so one exchange already 
issues up to three HTTP requests. The SDK retries 408, 409, 429 and 5xx 
responses as well as connection failures, backing off exponentially and 
honoring the `Retry-After` and `Retry-After-Ms` response headers.
+
+Camel redelivery multiplies with that layer rather than replacing it. With the 
default `maxRetries=2` and `maximumRedeliveries(3)`, a single message can 
produce twelve HTTP requests, which is rarely what you want against a 
rate-limited endpoint.
+
+Pick one layer and keep the other out of the way:
+
+* Leave `maxRetries` at its default and do not add Camel redelivery for rate 
limits. The SDK backoff already reads `Retry-After`, and this is the simplest 
choice for a plain request/response route.
+* Set `maxRetries=0` and drive retries from `onException` when the retry has 
to be visible to Camel, for example to reach a dead letter channel, apply a 
per-route policy, or show up in error handler metrics.
+
+[source,java]
+----
+from("direct:ask")
+    .to("openai:chat-completion?model=gpt-4o-mini&maxRetries=0");
+----
+
+NOTE: `requestTimeout` bounds a single HTTP request, not the whole retry 
sequence. At the default `maxRetries=2`, worst-case wall time is roughly three 
times `requestTimeout` plus the backoff between attempts.
+
+=== Errors in Streaming Mode
+
+With `streaming=true` the producer issues the initial HTTP request and then 
hands Camel an `Iterator<ChatCompletionChunk>`. Failures on that first request 
(authentication, rate limiting, connection errors) are thrown by the producer 
and behave like any other producer exception.
+
+A failure that occurs after the first chunk is different: it surfaces while 
the iterator is consumed, so it is reported against the step doing the 
consuming, usually a Split EIP, and not against the `to("openai:...")` step. 
Route-scoped `onException` still applies, but an error handler attached to the 
producer step alone will not see it.
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
 
b/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
index c71b3d6e9d5d..cbe442b39008 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
@@ -325,3 +325,61 @@ List<Content> contents = List.of(augmentedContent);
 
 String response = template.requestBodyAndHeader("direct:send-multiple", 
messages, LangChain4jChatHeaders.AUGMENTED_DATA, contents, String.class);
 ----
+
+== Error Handling
+
+The component does not wrap what the chat model throws. LangChain4j maps 
provider HTTP errors onto its own hierarchy in `dev.langchain4j.exception`, and 
that exception reaches the exchange unchanged, so `onException` can match it by 
type.
+
+The hierarchy already splits along the axis that matters for retries:
+
+[cols="2,3"]
+|===
+| Exception | Meaning
+
+| `RetriableException` | Base type for failures worth another attempt
+| `RateLimitException` | Provider rejected the call with a rate limit
+| `InternalServerException` | Provider-side failure
+| `TimeoutException` | The call did not complete within the model's timeout
+| `NonRetriableException` | Base type for failures that will fail the same way 
again
+| `InvalidRequestException` | Malformed request, including context-length 
errors
+| `ContentFilteredException` | Content rejected by the provider's moderation, 
a subclass of `InvalidRequestException`
+| `AuthenticationException` | Bad or missing API key
+| `ModelNotFoundException` | Unknown model name
+| `UnresolvedModelServerException` | The model server address could not be 
resolved
+|===
+
+Because the two base classes carry the decision, a route rarely has to 
enumerate the concrete types:
+
+[source, java]
+----
+onException(RetriableException.class)
+    .maximumRedeliveries(3)
+    .redeliveryDelay(2000)
+    .backOffMultiplier(2)
+    .useExponentialBackOff();
+
+onException(NonRetriableException.class)
+    .maximumRedeliveries(0)
+    .handled(true)
+    .to("direct:rejected");
+
+from("direct:chat")
+    .to("langchain4j-chat:my-chat");
+----
+
+IMPORTANT: The mapping is performed by the LangChain4j model implementation, 
not by Camel. `OpenAiChatModel` and `OllamaChatModel` route their calls through 
LangChain4j's exception mapper, but a model that does not will propagate the 
raw exception of its own HTTP client instead. Verify what your provider throws 
before relying on the type.
+
+=== Model retries vs Camel redelivery
+
+Most LangChain4j builders expose `maxRetries`, defaulting to `2`, which means 
one call already makes up to three attempts before any exception reaches Camel. 
Adding `maximumRedeliveries(3)` on top of that multiplies out to twelve 
attempts against a provider that is already rate limiting you.
+
+Configure one layer. Either leave the model retries alone and skip Camel 
redelivery, or build the model with `maxRetries(0)` and drive retries from 
`onException` so they are visible to the Camel error handler:
+
+[source, java]
+----
+ChatModel model = OpenAiChatModel.builder()
+        .apiKey(System.getenv("OPENAI_API_KEY"))
+        .modelName("gpt-4o-mini")
+        .maxRetries(0)
+        .build();
+----
diff --git 
a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc 
b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
index 5d7462a31161..eea6c204ad13 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
@@ -1277,4 +1277,89 @@ The component may throw the following exceptions:
 * `CamelExchangeException`:
   ** When moderation returns a number of results that does not match the 
number of inputs (moderation)
   ** When the image response contains no images, or an image with neither 
`b64_json` nor `url` (image-generation, image-edit)
-* API-specific exceptions from the OpenAI SDK for network errors, 
authentication failures, rate limiting, etc.
+* API-specific exceptions from the OpenAI SDK for network errors, 
authentication failures, rate limiting, etc. These are propagated unchanged, so 
routes can match them by type. See 
<<_targeting_sdk_exceptions_with_onexception>>.
+
+=== Targeting SDK Exceptions with onException
+
+The producer does not wrap or reclassify what the OpenAI SDK throws. The SDK 
exception reaches the exchange as-is, so `onException` can match the concrete 
type and treat a transient failure differently from a terminal one.
+
+Every HTTP error response maps to a subclass of 
`com.openai.errors.OpenAIServiceException`:
+
+[cols="2,1,3"]
+|===
+| Exception | Status | Worth retrying
+
+| `BadRequestException` | 400 | No, the request is malformed
+| `UnauthorizedException` | 401 | No, fix the API key
+| `PermissionDeniedException` | 403 | No
+| `NotFoundException` | 404 | No, usually an unknown model or a wrong `baseUrl`
+| `UnprocessableEntityException` | 422 | No, typically a context-length or 
validation error
+| `RateLimitException` | 429 | Yes, with backoff
+| `InternalServerException` | 5xx | Yes, with backoff
+| `UnexpectedStatusCodeException` | other | Depends on the provider
+|===
+
+Three further exceptions carry no status code and extend 
`com.openai.errors.OpenAIException` directly: `OpenAIIoException` for connect 
and read failures, `OpenAIRetryableException` when the SDK gave up after 
exhausting its own retries, and `OpenAIInvalidDataException` when the response 
body cannot be parsed.
+
+Retrying the transient cases while failing fast on the rest:
+
+[source,java]
+----
+onException(RateLimitException.class, InternalServerException.class, 
OpenAIIoException.class)
+    .maximumRedeliveries(3)
+    .redeliveryDelay(2000)
+    .backOffMultiplier(2)
+    .useExponentialBackOff();
+
+onException(BadRequestException.class, UnprocessableEntityException.class,
+            UnauthorizedException.class)
+    .maximumRedeliveries(0)
+    .handled(true)
+    .to("direct:rejected");
+
+from("direct:ask")
+    .to("openai:chat-completion?model=gpt-4o-mini");
+----
+
+`OpenAIServiceException` carries the details of the failed call, which is what 
you want for logging or for routing on the provider's own error code:
+
+[source,java]
+----
+onException(OpenAIServiceException.class)
+    .handled(true)
+    .process(exchange -> {
+        OpenAIServiceException cause
+                = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, 
OpenAIServiceException.class);
+        exchange.getIn().setHeader("FailureStatus", cause.statusCode());
+        exchange.getIn().setHeader("FailureCode", cause.code().orElse(null));
+        exchange.getIn().setHeader("FailureType", cause.type().orElse(null));
+    })
+    .to("direct:failed-calls");
+----
+
+`headers()` on the same exception exposes the response headers, including 
`Retry-After` on a 429.
+
+=== SDK Retry vs Camel Redelivery
+
+The SDK client retries on its own before the exception ever reaches Camel. The 
`maxRetries` option controls this and defaults to `2`, so one exchange already 
issues up to three HTTP requests. The SDK retries 408, 409, 429 and 5xx 
responses as well as connection failures, backing off exponentially and 
honoring the `Retry-After` and `Retry-After-Ms` response headers.
+
+Camel redelivery multiplies with that layer rather than replacing it. With the 
default `maxRetries=2` and `maximumRedeliveries(3)`, a single message can 
produce twelve HTTP requests, which is rarely what you want against a 
rate-limited endpoint.
+
+Pick one layer and keep the other out of the way:
+
+* Leave `maxRetries` at its default and do not add Camel redelivery for rate 
limits. The SDK backoff already reads `Retry-After`, and this is the simplest 
choice for a plain request/response route.
+* Set `maxRetries=0` and drive retries from `onException` when the retry has 
to be visible to Camel, for example to reach a dead letter channel, apply a 
per-route policy, or show up in error handler metrics.
+
+[source,java]
+----
+from("direct:ask")
+    .to("openai:chat-completion?model=gpt-4o-mini&maxRetries=0");
+----
+
+NOTE: `requestTimeout` bounds a single HTTP request, not the whole retry 
sequence. At the default `maxRetries=2`, worst-case wall time is roughly three 
times `requestTimeout` plus the backoff between attempts.
+
+=== Errors in Streaming Mode
+
+With `streaming=true` the producer issues the initial HTTP request and then 
hands Camel an `Iterator<ChatCompletionChunk>`. Failures on that first request 
(authentication, rate limiting, connection errors) are thrown by the producer 
and behave like any other producer exception.
+
+A failure that occurs after the first chunk is different: it surfaces while 
the iterator is consumed, so it is reported against the step doing the 
consuming, usually a Split EIP, and not against the `to("openai:...")` step. 
Route-scoped `onException` still applies, but an error handler attached to the 
producer step alone will not see it.
diff --git a/components/camel-ai/src/main/docs/ai-llm-integration-guide.adoc 
b/components/camel-ai/src/main/docs/ai-llm-integration-guide.adoc
index 74581e3a29b4..1c793a0bbcd8 100644
--- a/components/camel-ai/src/main/docs/ai-llm-integration-guide.adoc
+++ b/components/camel-ai/src/main/docs/ai-llm-integration-guide.adoc
@@ -302,6 +302,47 @@ Enable `useStreaming=true` on the platform-http endpoint 
when passing large stre
 | High-concurrency fan-out where Camel adds little value between HTTP and the 
LLM SDK
 |===
 
+== Error handling and retries
+
+None of the AI components wrap what the underlying SDK throws. Whatever the 
model client raises lands on the exchange unchanged, so `onException` can 
separate a rate limit worth retrying from a validation error that will fail 
identically on every attempt. Which types you match depends on the component.
+
+[cols="2,2,4",options="header"]
+|===
+| Component | Exception package | Retryable vs terminal
+
+| xref:openai-component.adoc[OpenAI]
+| `com.openai.errors`
+| No base type per category. Treat `RateLimitException`, 
`InternalServerException` and `OpenAIIoException` as retryable, and 
`BadRequestException`, `UnprocessableEntityException`, `UnauthorizedException` 
as terminal. All HTTP errors share `OpenAIServiceException`, which exposes 
`statusCode()`, `code()` and `headers()`
+
+| xref:langchain4j-chat-component.adoc[LangChain4j Chat], Agent, Embeddings
+| `dev.langchain4j.exception`
+| The hierarchy encodes it. `RetriableException` and `NonRetriableException` 
are the only two types most routes need
+
+| xref:spring-ai-chat-component.adoc[Spring AI Chat]
+| Depends on the `ChatModel` bean
+| Spring AI 2.0 builds its OpenAI model on the same `openai-java` SDK, so an 
OpenAI-backed `ChatModel` throws `com.openai.errors.*`. Other model 
implementations throw their own types, so check before matching
+|===
+
+A minimum viable policy, written against the LangChain4j hierarchy because it 
is the clearest:
+
+[source,java]
+----
+onException(RetriableException.class)
+    .maximumRedeliveries(3)
+    .redeliveryDelay(2000)
+    .backOffMultiplier(2)
+    .useExponentialBackOff();
+
+onException(NonRetriableException.class)
+    .maximumRedeliveries(0)
+    .handled(true)
+    .to("direct:dead-letter");
+----
+
+WARNING: These SDKs retry internally before Camel sees the failure, twice by 
default, and Camel redelivery multiplies with that layer instead of replacing 
it. Three SDK attempts under `maximumRedeliveries(3)` means twelve requests to 
a provider that is already rate limiting you. Either turn the SDK layer off 
(`maxRetries=0` on the OpenAI component, `maxRetries(0)` on a LangChain4j model 
builder) so the retry is visible to the Camel error handler, or leave it on and 
skip Camel redelivery  [...]
+
+For the per-component detail see 
xref:openai-component.adoc#_targeting_sdk_exceptions_with_onexception[OpenAI 
error handling] and 
xref:langchain4j-chat-component.adoc#_error_handling[LangChain4j Chat error 
handling].
+
 == End-to-end example: document extraction pipeline
 
 A typical resume-processing pipeline (the use case from community feedback):

Reply via email to