From 584250a60858a9865b684c12f7bf3c44bf116335 Mon Sep 17 00:00:00 2001 From: Karol Date: Wed, 12 Aug 2026 17:43:02 +0200 Subject: [PATCH] CAMEL-23967: camel-openai - Add image generation and edit operations Adds the image-generation and image-edit operations backed by the images API of the openai-java SDK. The multipart parts of an image edit declare their content type. The API validates the upload on that content type, not on the file name, and the SDK leaves it as text/plain unless it is set, so an edit would otherwise be rejected. It is resolved from the usual MIME type detection, then from the extension of a File or Path body, and falls back to image/png for anything the API does not accept. imageResponseFormat has no default and is only sent when set explicitly. The GPT image models always return base64 and reject the parameter, and the OpenAI images endpoint now rejects it for every model, the DALL-E models that used to accept it no longer being offered. The option is kept for OpenAI-compatible providers that still implement the older images API. The body shape follows the response instead, decoding base64 into byte[] and leaving URLs as String, with a single image as the body and several images as a List. The image-edit body accepts File, Path, InputStream, byte[] or a List of those, since the GPT image models take up to 16 reference images, plus an optional mask header. camel-test-infra-openai-mock gains image generation and edit expectations. Image edit requests are multipart and are not parsed by the mock, as is already the case for transcription, so those expectations are matched in declaration order and the raw body is exposed to assertions. Co-authored-by: Claude --- .../camel/catalog/components/openai.json | 180 ++++--- .../camel/catalog/docs/openai-component.adoc | 12 +- .../camel/catalog/docs/openai-operations.adoc | 146 ++++- .../openai/OpenAIEndpointConfigurer.java | 72 +++ .../openai/OpenAIEndpointUriFactory.java | 14 +- .../apache/camel/component/openai/openai.json | 180 ++++--- .../src/main/docs/openai-component.adoc | 12 +- .../src/main/docs/openai-operations.adoc | 146 ++++- .../component/openai/OpenAIConfiguration.java | 168 +++++- .../component/openai/OpenAIConstants.java | 73 +++ .../component/openai/OpenAIEndpoint.java | 5 +- .../openai/OpenAIImageEditProducer.java | 155 ++++++ .../openai/OpenAIImageGenerationProducer.java | 127 +++++ .../component/openai/OpenAIImageSupport.java | 286 ++++++++++ .../component/openai/OpenAIOperations.java | 7 +- .../openai/OpenAIImageEditMockTest.java | 252 +++++++++ .../openai/OpenAIImageGenerationMockTest.java | 215 ++++++++ docs/components/modules/others/nav.adoc | 2 +- .../endpoint/StaticEndpointBuilders.java | 12 +- .../dsl/OpenAIEndpointBuilderFactory.java | 508 +++++++++++++++++- .../infra/openai/mock/ImageExpectation.java | 111 ++++ .../openai/mock/ImageRequestHandler.java | 111 ++++ .../openai/mock/ImageResponseBuilder.java | 91 ++++ .../test/infra/openai/mock/OpenAIMock.java | 10 +- .../infra/openai/mock/OpenAIMockBuilder.java | 142 ++++- .../openai/mock/OpenAIMockExpectations.java | 7 +- .../openai/mock/OpenAIMockServerHandler.java | 9 + 27 files changed, 2869 insertions(+), 184 deletions(-) create mode 100644 components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageEditProducer.java create mode 100644 components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageGenerationProducer.java create mode 100644 components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageSupport.java create mode 100644 components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageEditMockTest.java create mode 100644 components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageGenerationMockTest.java create mode 100644 test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageExpectation.java create mode 100644 test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageRequestHandler.java create mode 100644 test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageResponseBuilder.java 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 8f3a4643f5d6d..785d63a1495ee 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 @@ -64,36 +64,56 @@ "CamelOpenAIResponse": { "index": 27, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.chat.completions.ChatCompletion", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI chat completion response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSE" }, "CamelOpenAIResponsesResponse": { "index": 28, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.responses.Response", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI Responses API response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSES_RESPONSE" }, "CamelOpenAIModerationResponse": { "index": 29, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.moderations.ModerationCreateResponse", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI moderation response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESPONSE" }, - "CamelOpenAIEmbeddingModel": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" }, - "CamelOpenAIEmbeddingDimensions": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of output dimensions", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" }, - "CamelOpenAIEmbeddingResponseModel": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The embedding model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" }, - "CamelOpenAIEmbeddingCount": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of embeddings returned", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" }, - "CamelOpenAIEmbeddingVectorSize": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Vector dimensions of the embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" }, - "CamelOpenAIReferenceEmbedding": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Reference embedding vector for similarity comparison", "constantName": "org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" }, - "CamelOpenAISimilarityScore": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Calculated cosine similarity score (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" }, - "CamelOpenAIOriginalText": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String or List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Original text content when embeddings operation is used", "constantName": "org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }, - "CamelOpenAIModerationModel": { "index": 38, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for moderation (e.g., omni-moderation-latest)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_MODEL" }, - "CamelOpenAIModerationFlagged": { "index": 39, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the moderation API flagged the input as violating the usage policies. For a batch of inputs this is true when at least one input was flagged", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_FLAGGED" }, - "CamelOpenAIModerationResults": { "index": 40, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "One verdict per moderated input, in the order of the inputs. Each entry holds the keys 'input', 'flagged', 'categories' and 'categoryScores', so a batch can be split and routed per item", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESULTS" }, - "CamelOpenAIModerationCategories": { "index": 41, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation categories and whether each one was violated, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORIES" }, - "CamelOpenAIModerationCategoryScores": { "index": 42, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation confidence score per category, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORY_SCORES" }, - "CamelOpenAIModerationResponseModel": { "index": 43, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESPONSE_MODEL" }, - "CamelOpenAIAudioModel": { "index": 44, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for audio transcription", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_MODEL" }, - "CamelOpenAIAudioLanguage": { "index": 45, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The language of the input audio (ISO-639-1)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_LANGUAGE" }, - "CamelOpenAIAudioResponseFormat": { "index": 46, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format for audio transcription (json, text, srt, verbose_json, vtt)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_RESPONSE_FORMAT" }, - "CamelOpenAIAudioTemperature": { "index": 47, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sampling temperature for audio transcription (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TEMPERATURE" }, - "CamelOpenAIAudioPrompt": { "index": 48, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional text to guide the model's style or continue a previous audio segment", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_PROMPT" }, - "CamelOpenAIAudioTimestampGranularities": { "index": 49, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TIMESTAMP_GRANULARITIES" }, - "CamelOpenAIAudioDuration": { "index": 50, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" }, - "CamelOpenAIAudioDetectedLanguage": { "index": 51, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }, - "CamelOpenAISpeechModel": { "index": 52, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_MODEL" }, - "CamelOpenAISpeechVoice": { "index": 53, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The voice to use for the generated audio (e.g., alloy, echo, fable, onyx, nova, shimmer)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_VOICE" }, - "CamelOpenAISpeechResponseFormat": { "index": 54, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The audio format for text-to-speech output (mp3, opus, aac, flac, wav, pcm)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_RESPONSE_FORMAT" }, - "CamelOpenAISpeechSpeed": { "index": 55, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The speed of the generated audio (0.25 to 4.0, where 1.0 is normal speed)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_SPEED" }, - "CamelOpenAISpeechInstructions": { "index": 56, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional instructions to control the voice of the generated audio (does not work with tts-1 or tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_INSTRUCTIONS" } + "CamelOpenAIImageResponse": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.images.ImagesResponse", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI image generation or edit response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_RESPONSE" }, + "CamelOpenAIEmbeddingModel": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" }, + "CamelOpenAIEmbeddingDimensions": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of output dimensions", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" }, + "CamelOpenAIEmbeddingResponseModel": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The embedding model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" }, + "CamelOpenAIEmbeddingCount": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of embeddings returned", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" }, + "CamelOpenAIEmbeddingVectorSize": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Vector dimensions of the embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" }, + "CamelOpenAIReferenceEmbedding": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Reference embedding vector for similarity comparison", "constantName": "org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" }, + "CamelOpenAISimilarityScore": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Calculated cosine similarity score (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" }, + "CamelOpenAIOriginalText": { "index": 38, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String or List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Original text content when embeddings operation is used", "constantName": "org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }, + "CamelOpenAIModerationModel": { "index": 39, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for moderation (e.g., omni-moderation-latest)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_MODEL" }, + "CamelOpenAIModerationFlagged": { "index": 40, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the moderation API flagged the input as violating the usage policies. For a batch of inputs this is true when at least one input was flagged", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_FLAGGED" }, + "CamelOpenAIModerationResults": { "index": 41, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "One verdict per moderated input, in the order of the inputs. Each entry holds the keys 'input', 'flagged', 'categories' and 'categoryScores', so a batch can be split and routed per item", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESULTS" }, + "CamelOpenAIModerationCategories": { "index": 42, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation categories and whether each one was violated, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORIES" }, + "CamelOpenAIModerationCategoryScores": { "index": 43, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation confidence score per category, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORY_SCORES" }, + "CamelOpenAIModerationResponseModel": { "index": 44, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESPONSE_MODEL" }, + "CamelOpenAIAudioModel": { "index": 45, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for audio transcription", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_MODEL" }, + "CamelOpenAIAudioLanguage": { "index": 46, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The language of the input audio (ISO-639-1)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_LANGUAGE" }, + "CamelOpenAIAudioResponseFormat": { "index": 47, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format for audio transcription (json, text, srt, verbose_json, vtt)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_RESPONSE_FORMAT" }, + "CamelOpenAIAudioTemperature": { "index": 48, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sampling temperature for audio transcription (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TEMPERATURE" }, + "CamelOpenAIAudioPrompt": { "index": 49, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional text to guide the model's style or continue a previous audio segment", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_PROMPT" }, + "CamelOpenAIAudioTimestampGranularities": { "index": 50, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TIMESTAMP_GRANULARITIES" }, + "CamelOpenAIAudioDuration": { "index": 51, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" }, + "CamelOpenAIAudioDetectedLanguage": { "index": 52, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }, + "CamelOpenAISpeechModel": { "index": 53, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_MODEL" }, + "CamelOpenAISpeechVoice": { "index": 54, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The voice to use for the generated audio (e.g., alloy, echo, fable, onyx, nova, shimmer)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_VOICE" }, + "CamelOpenAISpeechResponseFormat": { "index": 55, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The audio format for text-to-speech output (mp3, opus, aac, flac, wav, pcm)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_RESPONSE_FORMAT" }, + "CamelOpenAISpeechSpeed": { "index": 56, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The speed of the generated audio (0.25 to 4.0, where 1.0 is normal speed)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_SPEED" }, + "CamelOpenAISpeechInstructions": { "index": 57, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional instructions to control the voice of the generated audio (does not work with tts-1 or tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_INSTRUCTIONS" }, + "CamelOpenAIImageModel": { "index": 58, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for image generation or editing (e.g., gpt-image-1, dall-e-3, dall-e-2)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_MODEL" }, + "CamelOpenAIImagePrompt": { "index": 59, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prompt describing the image to generate, or the edit to apply. Takes precedence over the imagePrompt endpoint option and, for image-generation, over the message body", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_PROMPT" }, + "CamelOpenAIImageSize": { "index": 60, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the generated image (e.g., 1024x1024, 1536x1024, auto)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_SIZE" }, + "CamelOpenAIImageQuality": { "index": 61, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The quality of the generated image (auto, high, medium, low for GPT image models; hd, standard for dall-e-3; standard for dall-e-2)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_QUALITY" }, + "CamelOpenAIImageResponseFormat": { "index": 62, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format of the generated image (url or b64_json). Only supported by dall-e-2 and dall-e-3; GPT image models always return base64", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_RESPONSE_FORMAT" }, + "CamelOpenAIImageCount": { "index": 63, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of images to generate", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_COUNT" }, + "CamelOpenAIImageBackground": { "index": 64, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The background of the generated image (transparent, opaque, auto). Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_BACKGROUND" }, + "CamelOpenAIImageOutputFormat": { "index": 65, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The output format of the generated image (png, jpeg, webp). Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_FORMAT" }, + "CamelOpenAIImageOutputCompression": { "index": 66, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The compression level (0-100) for the webp or jpeg output formats. Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_COMPRESSION" }, + "CamelOpenAIImageStyle": { "index": 67, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The style of the generated image (vivid or natural). Only supported by dall-e-3", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_STYLE" }, + "CamelOpenAIImageModeration": { "index": 68, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content moderation level for image generation (low or auto). Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_MODERATION" }, + "CamelOpenAIImageInputFidelity": { "index": 69, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How closely the edit must match the style and features of the input image (high or low). Only supported by the image-edit operation on gpt-image-1 and gpt-image-1.5", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_INPUT_FIDELITY" }, + "CamelOpenAIImageMask": { "index": 70, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "byte[], java.io.File, java.nio.file.Path or java.io.InputStream", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "An optional PNG mask for the image-edit operation, where the fully transparent areas indicate where the image should be edited", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_MASK" }, + "CamelOpenAIImageResultCount": { "index": 71, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of images returned in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_RESULT_COUNT" }, + "CamelOpenAIImageRevisedPrompt": { "index": 72, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prompt as revised by the model, when a single image is returned (dall-e-3)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_REVISED_PROMPT" }, + "CamelOpenAIImageRevisedPrompts": { "index": 73, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prompts as revised by the model, one entry per returned image (dall-e-3)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_REVISED_PROMPTS" }, + "CamelOpenAIImageInputTokens": { "index": 74, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of input tokens billed for the image request. Only reported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_INPUT_TOKENS" }, + "CamelOpenAIImageOutputTokens": { "index": 75, "kind": "header", "displayName": "", "group": "producer", "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": 76, "kind": "header", "displayName": "", "group": "producer", "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" } }, "properties": { - "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openai.OpenAIOperations", "enum": [ "chat-completion", "responses", "embeddings", "tool-execution", "audio-transcription", "audio-translation", "audio-speech", "moderation" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform: 'chat-completion', 'responses', 'embeddings', 'tool-execution', 'audio-transcription', 'audio-translation', 'audio-speech', or 'moderation'" }, + "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openai.OpenAIOperations", "enum": [ "chat-completion", "responses", "embeddings", "tool-execution", "audio-transcription", "audio-translation", "audio-speech", "moderation", "image-generation", "image-edit" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform: 'chat-completion', 'responses', 'embeddings', 'tool-execution', 'audio-transcription', 'audio-translation', 'audio-speech', 'moderation', 'image-generation', or 'image-edit'" }, "additionalBodyProperty": { "index": 1, "kind": "parameter", "displayName": "Additional Body Property", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "additionalBodyProperty.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Additional JSON properties to include in the request body (e.g. additionalBodyProperty.traceId=123). This is a multi-value option with prefix: additionalBodyProperty." }, "additionalHeader": { "index": 2, "kind": "parameter", "displayName": "Additional Header", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "additionalHeader.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Additional HTTP request headers to send with every API call (e.g. additionalHeader.OpenAI-Organization=my-org or additionalHeader.api-key=secret). Values may contain secrets. This is a multi-value option with prefix: additionalHeader." }, "additionalResponseHeader": { "index": 3, "kind": "parameter", "displayName": "Additional Response Header", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "additionalResponseHeader.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Map additional fields from the response message to Camel headers. The key is the field name in the API response, the value is the Camel header name (e.g. additionalResponseHeader.reasoning_content=CamelMyReasoningHeader). This is a multi-value option with prefix: additionalResponseHeader." }, @@ -116,51 +136,63 @@ "fileSearchVectorStoreIds": { "index": 20, "kind": "parameter", "displayName": "File Search Vector Store Ids", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated vector store ids required when builtinTools includes file_search" }, "hallucinatedToolNameStrategy": { "index": 21, "kind": "parameter", "displayName": "Hallucinated Tool Name Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.HallucinatedToolNameStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strategy for handling tool names hallucinated by the model (tool not found in any MCP server). 'failExchange' (default) throws an IllegalStateException, failing the exchange immediately. 'repromptModel' sends a corrective tool result listing the available tools so the model can self-correct and retry. The maxToolIterations option bounds retries." }, "hostedMcpTools": { "index": 22, "kind": "parameter", "displayName": "Hosted Mcp Tools", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON array of hosted MCP tool definitions (OpenAI Tool.Mcp) passed through to the Responses API" }, - "jsonSchema": { "index": 23, "kind": "parameter", "displayName": "Json Schema", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "supportFileReference": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON schema for structured output validation" }, - "maxAgenticTokens": { "index": 24, "kind": "parameter", "displayName": "Max Agentic Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum cumulative prompt plus completion tokens allowed across the MCP agentic loop. When 0 or negative, no token budget is enforced. Enforcement runs after each API call that requests further tool execution, so actual spend may exceed the configured budget by up to one call (typically the largest, as the prompt grows each iteration). A final text response is returned even when cumulative usage exceeds the budget." }, - "maxHistoryMessages": { "index": 25, "kind": "parameter", "displayName": "Max History Messages", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, retain at most this many messages in the exchange conversation history. System and developer messages are prepended separately and are not stored in history. Assistant tool-call blocks are kept intact and may retain slightly more than this limit to preserve tool result pairing. When 0, no message limit is applied." }, - "maxHistoryTokens": { "index": 26, "kind": "parameter", "displayName": "Max History Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, trim conversation history using a token estimate (character count \/ 4, including image payload size for multi-modal user messages). Oldest segments are dropped first until the estimated tokens are within this limit. Assistant tool-call blocks are removed as a unit with their tool results. The most recent segment is always retained, even when it alone exceeds this limit. When 0, no token limit is applied." }, - "maxRetries": { "index": 27, "kind": "parameter", "displayName": "Max Retries", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 2, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of times the OpenAI SDK client retries failed requests. The SDK retry is rate-limit aware (honors Retry-After on 429)." }, - "maxTokens": { "index": 28, "kind": "parameter", "displayName": "Max Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tokens to generate" }, - "maxToolIterations": { "index": 29, "kind": "parameter", "displayName": "Max Tool Iterations", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 50, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tool call loop iterations to prevent infinite loops" }, - "mcpProtocolVersions": { "index": 30, "kind": "parameter", "displayName": "Mcp Protocol Versions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated list of MCP protocol versions to advertise when connecting to MCP servers using Streamable HTTP transport. When not set, the SDK default is used. Example: 2024-11-05,2025-03-26,2025-06-18" }, - "mcpReconnect": { "index": 31, "kind": "parameter", "displayName": "Mcp Reconnect", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Automatically reconnect to MCP servers when a tool call fails due to a transport error, and retry the call once." }, - "mcpServer": { "index": 32, "kind": "parameter", "displayName": "Mcp Server", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "MCP (Model Context Protocol) server configurations. Define servers using prefix notation: mcpServer..transportType=stdiossestreamableHttp, (Note that sse is deprecated) mcpServer..command= (stdio), mcpServer..args= (stdio), mcpServer..url= (sse\/streamableHttp), mcpServer..oauthProfile= (OAuth profile for HTTP auth, requires camel-oauth), mcpServer..toolNames= (optional include list to restrict which tools are registered from this server). This is a multi-value option with prefix: mcpServer." }, - "mcpTimeout": { "index": 33, "kind": "parameter", "displayName": "Mcp Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP tool call requests. Applies to all MCP operations including tool execution and initialization." }, - "mcpToolRefresh": { "index": 34, "kind": "parameter", "displayName": "Mcp Tool Refresh", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Refresh the advertised tool list when an MCP server notifies that its tools changed. Set to false to keep the tool list fixed to what was listed when the endpoint started, for deployments that require a deterministic set of tools." }, - "model": { "index": 35, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for chat completion" }, - "moderationModel": { "index": 36, "kind": "parameter", "displayName": "Moderation Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "omni-moderation-latest", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for moderation" }, - "outputClass": { "index": 37, "kind": "parameter", "displayName": "Output Class", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Fully qualified class name for structured output using response format" }, - "parallelToolExecution": { "index": 38, "kind": "parameter", "displayName": "Parallel Tool Execution", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Execute the tool calls returned by the model in a single response concurrently instead of sequentially. Tool calls in the same batch are independent by design, so this reduces the latency of a batch to that of its slowest tool. Results are always fed back to the model in the original tool call order. Note that with toolExecutionErrorStrategy=failExchange the sibling tool calls already dispatched complete before the exchange fails." }, - "parallelToolTimeout": { "index": 39, "kind": "parameter", "displayName": "Parallel Tool Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in milliseconds for a batch of parallel tool calls, so that one slow tool cannot block the whole batch. The timeout applies to the batch as a whole, not per tool call. A tool call that exceeds it is cancelled and handled according to toolExecutionErrorStrategy. The default of 0 disables the batch timeout and relies on mcpTimeout, which already bounds each individual MCP request. Only used when parallelToolExecution=true." }, - "previousResponseId": { "index": 40, "kind": "parameter", "displayName": "Previous Response Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Previous response id for OpenAI server-side conversation state (Responses API only)" }, - "requestTimeout": { "index": 41, "kind": "parameter", "displayName": "Request Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "HTTP request timeout in milliseconds for the OpenAI SDK client. When 0 or negative, the SDK default (10 minutes) is used." }, - "speechInstructions": { "index": 42, "kind": "parameter", "displayName": "Speech Instructions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Optional instructions to control the voice of the generated audio. Does not work with tts-1 or tts-1-hd." }, - "speechModel": { "index": 43, "kind": "parameter", "displayName": "Speech Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)" }, - "speechResponseFormat": { "index": 44, "kind": "parameter", "displayName": "Speech Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "mp3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The audio format for text-to-speech output" }, - "speechSpeed": { "index": 45, "kind": "parameter", "displayName": "Speech Speed", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The speed of the generated audio, from 0.25 to 4.0 where 1.0 is normal speed" }, - "speechVoice": { "index": 46, "kind": "parameter", "displayName": "Speech Voice", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "alloy", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The voice to use for text-to-speech (e.g., alloy, echo, fable, onyx, nova, shimmer). See the OpenAI documentation for the full list of supported voices." }, - "storeFullResponse": { "index": 47, "kind": "parameter", "displayName": "Store Full Response", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Store the full SDK response in non-streaming mode: chat-completion uses exchange property 'CamelOpenAIResponse'; responses uses 'CamelOpenAIResponsesResponse'; moderation uses 'CamelOpenAIModerationResponse'" }, - "streaming": { "index": 48, "kind": "parameter", "displayName": "Streaming", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Enable streaming responses" }, - "stripThinking": { "index": 49, "kind": "parameter", "displayName": "Strip Thinking", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strip ... blocks from model responses (used by reasoning models like Qwen3, DeepSeek-R1). The thinking content is stored in the CamelOpenAIThinkingContent header." }, - "systemMessage": { "index": 50, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "System message to prepend. When set and conversationMemory is enabled, the conversation history is reset." }, - "temperature": { "index": 51, "kind": "parameter", "displayName": "Temperature", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Temperature for response generation (0.0 to 2.0)" }, - "toolExecutionErrorStrategy": { "index": 52, "kind": "parameter", "displayName": "Tool Execution Error Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.ToolExecutionErrorStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strategy for handling exceptions thrown during MCP tool execution. 'failExchange' (default) propagates the exception to the Camel exchange so that standard Camel error handling (onException, dead-letter channel) can process it. This is the safer default because 'repromptModel' sends raw exception messages (which may contain connection strings, hostnames, or internal paths) to a third-party LLM provider. 'repromptModel' catches the error and sends it back to the model as a tool result so the model can attempt to recover." }, - "topP": { "index": 53, "kind": "parameter", "displayName": "Top P", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Top P for response generation (0.0 to 1.0)" }, - "userMessage": { "index": 54, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Default user message text to use when no prompt is provided" }, - "lazyStartProducer": { "index": 55, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, - "oauthProfile": { "index": 56, "kind": "parameter", "displayName": "Oauth Profile", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "OAuth profile name for obtaining an access token via the OAuth 2.0 Client Credentials grant. When set, the token is acquired from the configured identity provider and used instead of apiKey. Requires camel-oauth on the classpath. The profile properties are resolved from camel.oauth..client-id, camel.oauth..client-secret, and camel.oauth..token-endpoint." }, - "sslContextParameters": { "index": 57, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "SSLContextParameters to use for configuring SSL\/TLS. When set, takes precedence over the individual sslTruststore, sslKeystore, and sslProtocol options." }, - "sslEndpointAlgorithm": { "index": 58, "kind": "parameter", "displayName": "Ssl Endpoint Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "security": "insecure:ssl", "insecureValue": "none", "defaultValue": "https", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The endpoint identification algorithm to validate the server hostname using the server certificate. Set to an empty string or 'none' to disable hostname verification" }, - "sslKeymanagerAlgorithm": { "index": 59, "kind": "parameter", "displayName": "Ssl Keymanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "SunX509", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the key manager factory for SSL connections" }, - "sslKeyPassword": { "index": 60, "kind": "parameter", "displayName": "Ssl Key Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password of the private key in the key store file" }, - "sslKeystoreLocation": { "index": 61, "kind": "parameter", "displayName": "Ssl Keystore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the key store file. This is optional and can be used for two-way authentication for the OpenAI API" }, - "sslKeystorePassword": { "index": 62, "kind": "parameter", "displayName": "Ssl Keystore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The store password for the key store file" }, - "sslKeystoreType": { "index": 63, "kind": "parameter", "displayName": "Ssl Keystore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the key store file" }, - "sslProtocol": { "index": 64, "kind": "parameter", "displayName": "Ssl Protocol", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "TLSv1.3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The SSL protocol used to generate the SSLContext" }, - "sslTrustmanagerAlgorithm": { "index": 65, "kind": "parameter", "displayName": "Ssl Trustmanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "PKIX", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the trust manager factory for SSL connections" }, - "sslTruststoreLocation": { "index": 66, "kind": "parameter", "displayName": "Ssl Truststore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the trust store file, used to validate the server's certificate" }, - "sslTruststorePassword": { "index": 67, "kind": "parameter", "displayName": "Ssl Truststore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password for the trust store file. If a password is not set, the configured trust store can still be used, but integrity checking is disabled" }, - "sslTruststoreType": { "index": 68, "kind": "parameter", "displayName": "Ssl Truststore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the trust store file" } + "imageBackground": { "index": 23, "kind": "parameter", "displayName": "Image Background", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "transparent", "opaque", "auto" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The background of the generated image. Only supported by the GPT image models, and a transparent background requires the png or webp output format." }, + "imageCount": { "index": 24, "kind": "parameter", "displayName": "Image Count", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The number of images to generate, between 1 and 10. dall-e-3 only supports 1." }, + "imageInputFidelity": { "index": 25, "kind": "parameter", "displayName": "Image Input Fidelity", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "high", "low" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "How closely the edit must match the style and features of the input image. Only supported by the image-edit operation on gpt-image-1 and gpt-image-1.5." }, + "imageModel": { "index": 26, "kind": "parameter", "displayName": "Image Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for image generation or editing (e.g., gpt-image-1, gpt-image-1-mini, gpt-image-1.5, gpt-image-2). Required for the image-generation and image-edit operations, because the model determines which of the other image options are accepted. The DALL-E models are no longer offered by OpenAI, but remain valid values for OpenAI-compatible providers." }, + "imageModeration": { "index": 27, "kind": "parameter", "displayName": "Image Moderation", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "low", "auto" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The content moderation level applied to image generation. Only supported by the GPT image models." }, + "imageOutputCompression": { "index": 28, "kind": "parameter", "displayName": "Image Output Compression", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The compression level from 0 to 100 for the webp and jpeg output formats. Only supported by the GPT image models." }, + "imageOutputFormat": { "index": 29, "kind": "parameter", "displayName": "Image Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "png", "jpeg", "webp" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The output format of the generated image. Only supported by the GPT image models, which default to png." }, + "imagePrompt": { "index": 30, "kind": "parameter", "displayName": "Image Prompt", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The prompt describing the image to generate, or the edit to apply. For image-generation the message body is used when this is not set; for image-edit the body carries the input image, so the prompt must come from this option or from the CamelOpenAIImagePrompt header." }, + "imageQuality": { "index": 31, "kind": "parameter", "displayName": "Image Quality", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "auto", "high", "medium", "low", "hd", "standard" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The quality of the generated image. GPT image models accept auto, high, medium and low; hd and standard are DALL-E values kept for OpenAI-compatible providers." }, + "imageResponseFormat": { "index": 32, "kind": "parameter", "displayName": "Image Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "url", "b64_json" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The response format of the generated image. The OpenAI images endpoint rejects this option: the GPT image models always return base64, and the DALL-E models that used to accept it are no longer offered. It is only sent when explicitly set, and is kept for OpenAI-compatible providers that still implement the older images API." }, + "imageSize": { "index": 33, "kind": "parameter", "displayName": "Image Size", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The size of the generated image (e.g., 1024x1024, 1536x1024, 1024x1536, auto). The accepted values depend on the model." }, + "imageStyle": { "index": 34, "kind": "parameter", "displayName": "Image Style", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "vivid", "natural" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The style of the generated image. A dall-e-3 option, so only useful with OpenAI-compatible providers." }, + "jsonSchema": { "index": 35, "kind": "parameter", "displayName": "Json Schema", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "supportFileReference": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON schema for structured output validation" }, + "maxAgenticTokens": { "index": 36, "kind": "parameter", "displayName": "Max Agentic Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum cumulative prompt plus completion tokens allowed across the MCP agentic loop. When 0 or negative, no token budget is enforced. Enforcement runs after each API call that requests further tool execution, so actual spend may exceed the configured budget by up to one call (typically the largest, as the prompt grows each iteration). A final text response is returned even when cumulative usage exceeds the budget." }, + "maxHistoryMessages": { "index": 37, "kind": "parameter", "displayName": "Max History Messages", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, retain at most this many messages in the exchange conversation history. System and developer messages are prepended separately and are not stored in history. Assistant tool-call blocks are kept intact and may retain slightly more than this limit to preserve tool result pairing. When 0, no message limit is applied." }, + "maxHistoryTokens": { "index": 38, "kind": "parameter", "displayName": "Max History Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, trim conversation history using a token estimate (character count \/ 4, including image payload size for multi-modal user messages). Oldest segments are dropped first until the estimated tokens are within this limit. Assistant tool-call blocks are removed as a unit with their tool results. The most recent segment is always retained, even when it alone exceeds this limit. When 0, no token limit is applied." }, + "maxRetries": { "index": 39, "kind": "parameter", "displayName": "Max Retries", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 2, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of times the OpenAI SDK client retries failed requests. The SDK retry is rate-limit aware (honors Retry-After on 429)." }, + "maxTokens": { "index": 40, "kind": "parameter", "displayName": "Max Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tokens to generate" }, + "maxToolIterations": { "index": 41, "kind": "parameter", "displayName": "Max Tool Iterations", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 50, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tool call loop iterations to prevent infinite loops" }, + "mcpProtocolVersions": { "index": 42, "kind": "parameter", "displayName": "Mcp Protocol Versions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated list of MCP protocol versions to advertise when connecting to MCP servers using Streamable HTTP transport. When not set, the SDK default is used. Example: 2024-11-05,2025-03-26,2025-06-18" }, + "mcpReconnect": { "index": 43, "kind": "parameter", "displayName": "Mcp Reconnect", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Automatically reconnect to MCP servers when a tool call fails due to a transport error, and retry the call once." }, + "mcpServer": { "index": 44, "kind": "parameter", "displayName": "Mcp Server", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "MCP (Model Context Protocol) server configurations. Define servers using prefix notation: mcpServer..transportType=stdiossestreamableHttp, (Note that sse is deprecated) mcpServer..command= (stdio), mcpServer..args= (stdio), mcpServer..url= (sse\/streamableHttp), mcpServer..oauthProfile= (OAuth profile for HTTP auth, requires camel-oauth), mcpServer..toolNames= (optional include list to restrict which tools are registered from this server). This is a multi-value option with prefix: mcpServer." }, + "mcpTimeout": { "index": 45, "kind": "parameter", "displayName": "Mcp Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP tool call requests. Applies to all MCP operations including tool execution and initialization." }, + "mcpToolRefresh": { "index": 46, "kind": "parameter", "displayName": "Mcp Tool Refresh", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Refresh the advertised tool list when an MCP server notifies that its tools changed. Set to false to keep the tool list fixed to what was listed when the endpoint started, for deployments that require a deterministic set of tools." }, + "model": { "index": 47, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for chat completion" }, + "moderationModel": { "index": 48, "kind": "parameter", "displayName": "Moderation Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "omni-moderation-latest", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for moderation" }, + "outputClass": { "index": 49, "kind": "parameter", "displayName": "Output Class", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Fully qualified class name for structured output using response format" }, + "parallelToolExecution": { "index": 50, "kind": "parameter", "displayName": "Parallel Tool Execution", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Execute the tool calls returned by the model in a single response concurrently instead of sequentially. Tool calls in the same batch are independent by design, so this reduces the latency of a batch to that of its slowest tool. Results are always fed back to the model in the original tool call order. Note that with toolExecutionErrorStrategy=failExchange the sibling tool calls already dispatched complete before the exchange fails." }, + "parallelToolTimeout": { "index": 51, "kind": "parameter", "displayName": "Parallel Tool Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in milliseconds for a batch of parallel tool calls, so that one slow tool cannot block the whole batch. The timeout applies to the batch as a whole, not per tool call. A tool call that exceeds it is cancelled and handled according to toolExecutionErrorStrategy. The default of 0 disables the batch timeout and relies on mcpTimeout, which already bounds each individual MCP request. Only used when parallelToolExecution=true." }, + "previousResponseId": { "index": 52, "kind": "parameter", "displayName": "Previous Response Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Previous response id for OpenAI server-side conversation state (Responses API only)" }, + "requestTimeout": { "index": 53, "kind": "parameter", "displayName": "Request Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "HTTP request timeout in milliseconds for the OpenAI SDK client. When 0 or negative, the SDK default (10 minutes) is used." }, + "speechInstructions": { "index": 54, "kind": "parameter", "displayName": "Speech Instructions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Optional instructions to control the voice of the generated audio. Does not work with tts-1 or tts-1-hd." }, + "speechModel": { "index": 55, "kind": "parameter", "displayName": "Speech Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)" }, + "speechResponseFormat": { "index": 56, "kind": "parameter", "displayName": "Speech Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "mp3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The audio format for text-to-speech output" }, + "speechSpeed": { "index": 57, "kind": "parameter", "displayName": "Speech Speed", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The speed of the generated audio, from 0.25 to 4.0 where 1.0 is normal speed" }, + "speechVoice": { "index": 58, "kind": "parameter", "displayName": "Speech Voice", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "alloy", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The voice to use for text-to-speech (e.g., alloy, echo, fable, onyx, nova, shimmer). See the OpenAI documentation for the full list of supported voices." }, + "storeFullResponse": { "index": 59, "kind": "parameter", "displayName": "Store Full Response", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Store the full SDK response in non-streaming mode: chat-completion uses exchange property 'CamelOpenAIResponse'; responses uses 'CamelOpenAIResponsesResponse'; moderation uses 'CamelOpenAIModerationResponse'; image-generation and image-edit use 'CamelOpenAIImageResponse'" }, + "streaming": { "index": 60, "kind": "parameter", "displayName": "Streaming", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Enable streaming responses" }, + "stripThinking": { "index": 61, "kind": "parameter", "displayName": "Strip Thinking", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strip ... blocks from model responses (used by reasoning models like Qwen3, DeepSeek-R1). The thinking content is stored in the CamelOpenAIThinkingContent header." }, + "systemMessage": { "index": 62, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "System message to prepend. When set and conversationMemory is enabled, the conversation history is reset." }, + "temperature": { "index": 63, "kind": "parameter", "displayName": "Temperature", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Temperature for response generation (0.0 to 2.0)" }, + "toolExecutionErrorStrategy": { "index": 64, "kind": "parameter", "displayName": "Tool Execution Error Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.ToolExecutionErrorStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strategy for handling exceptions thrown during MCP tool execution. 'failExchange' (default) propagates the exception to the Camel exchange so that standard Camel error handling (onException, dead-letter channel) can process it. This is the safer default because 'repromptModel' sends raw exception messages (which may contain connection strings, hostnames, or internal paths) to a third-party LLM provider. 'repromptModel' catches the error and sends it back to the model as a tool result so the model can attempt to recover." }, + "topP": { "index": 65, "kind": "parameter", "displayName": "Top P", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Top P for response generation (0.0 to 1.0)" }, + "userMessage": { "index": 66, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Default user message text to use when no prompt is provided" }, + "lazyStartProducer": { "index": 67, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "oauthProfile": { "index": 68, "kind": "parameter", "displayName": "Oauth Profile", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "OAuth profile name for obtaining an access token via the OAuth 2.0 Client Credentials grant. When set, the token is acquired from the configured identity provider and used instead of apiKey. Requires camel-oauth on the classpath. The profile properties are resolved from camel.oauth..client-id, camel.oauth..client-secret, and camel.oauth..token-endpoint." }, + "sslContextParameters": { "index": 69, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "SSLContextParameters to use for configuring SSL\/TLS. When set, takes precedence over the individual sslTruststore, sslKeystore, and sslProtocol options." }, + "sslEndpointAlgorithm": { "index": 70, "kind": "parameter", "displayName": "Ssl Endpoint Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "security": "insecure:ssl", "insecureValue": "none", "defaultValue": "https", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The endpoint identification algorithm to validate the server hostname using the server certificate. Set to an empty string or 'none' to disable hostname verification" }, + "sslKeymanagerAlgorithm": { "index": 71, "kind": "parameter", "displayName": "Ssl Keymanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "SunX509", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the key manager factory for SSL connections" }, + "sslKeyPassword": { "index": 72, "kind": "parameter", "displayName": "Ssl Key Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password of the private key in the key store file" }, + "sslKeystoreLocation": { "index": 73, "kind": "parameter", "displayName": "Ssl Keystore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the key store file. This is optional and can be used for two-way authentication for the OpenAI API" }, + "sslKeystorePassword": { "index": 74, "kind": "parameter", "displayName": "Ssl Keystore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The store password for the key store file" }, + "sslKeystoreType": { "index": 75, "kind": "parameter", "displayName": "Ssl Keystore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the key store file" }, + "sslProtocol": { "index": 76, "kind": "parameter", "displayName": "Ssl Protocol", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "TLSv1.3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The SSL protocol used to generate the SSLContext" }, + "sslTrustmanagerAlgorithm": { "index": 77, "kind": "parameter", "displayName": "Ssl Trustmanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "PKIX", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the trust manager factory for SSL connections" }, + "sslTruststoreLocation": { "index": 78, "kind": "parameter", "displayName": "Ssl Truststore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the trust store file, used to validate the server's certificate" }, + "sslTruststorePassword": { "index": 79, "kind": "parameter", "displayName": "Ssl Truststore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password for the trust store file. If a password is not set, the configured trust store can still be used, but integrity checking is disabled" }, + "sslTruststoreType": { "index": 80, "kind": "parameter", "displayName": "Ssl Truststore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the trust store file" } } } diff --git a/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 6d5637f0ddde4..5d7462a311615 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 @@ -14,7 +14,7 @@ *{component-header}* -The OpenAI component provides integration with OpenAI and OpenAI-compatible APIs for chat completion, text embeddings, content moderation, audio transcription, audio translation, and text-to-speech using the official openai-java SDK. +The OpenAI component provides integration with OpenAI and OpenAI-compatible APIs for chat completion, text embeddings, content moderation, audio transcription, audio translation, text-to-speech, and image generation and editing using the official openai-java SDK. Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -48,6 +48,8 @@ See xref:others:openai-responses.adoc[Responses API operation] for usage (`previ * `audio-translation` - Transcribe and translate audio files into English text (e.g., Whisper) * `audio-speech` - Synthesize spoken audio from text using text-to-speech models (e.g., gpt-4o-mini-tts, tts-1) * `moderation` - Check text against the OpenAI usage policies before it reaches a model +* `image-generation` - Generate images from a text prompt (e.g., gpt-image-1, gpt-image-1-mini) +* `image-edit` - Edit an existing image, optionally through a mask, from a text prompt // component options: START include::partial$component-configure-options.adoc[] @@ -1255,20 +1257,24 @@ For more details on specific features, see: * xref:others:openai-responses.adoc[Responses API operation] - OpenAI Responses API, hosted tools, and server-side conversation state * xref:others:openai-mcp.adoc[MCP Tool Calling] - Model Context Protocol server configuration, agentic loop, streaming, and connection recovery * xref:others:openai-providers.adoc[OpenAI-Compatible Providers] - Using Ollama, LM Studio, vLLM, and OpenRouter as alternative backends -* xref:others:openai-operations.adoc[Embeddings, Moderation and Audio Operations] - Text embeddings, vector database integration, content moderation, and audio transcription +* xref:others:openai-operations.adoc[Embeddings, Moderation, Audio and Image Operations] - Text embeddings, vector database integration, content moderation, audio transcription, and image generation == Error Handling The component may throw the following exceptions: * `IllegalArgumentException`: - ** When an invalid operation is specified (supported: `chat-completion`, `responses`, `embeddings`, `tool-execution`, `audio-transcription`, `audio-translation`, `audio-speech`, `moderation`) + ** When an invalid operation is specified (supported: `chat-completion`, `responses`, `embeddings`, `tool-execution`, `audio-transcription`, `audio-translation`, `audio-speech`, `moderation`, `image-generation`, `image-edit`) ** When message body or user message is missing ** When the audio model is missing (audio-transcription, audio-translation) or the speech model is missing (audio-speech) ** When image file is provided without userMessage (chat-completion) ** When unsupported file type is provided (only text and image files are supported) ** When invalid JSON schema string is provided ** When the moderation input list is empty or contains null elements (moderation) + ** When the image model is missing (image-generation, image-edit) + ** When the prompt is missing (image-generation, image-edit) or the image list is empty (image-edit) + ** When an unsupported image or mask body type is provided (image-edit) * `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. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-operations.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-operations.adoc index 99590c19cc364..10b05e4d7282c 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-operations.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-operations.adoc @@ -1,4 +1,4 @@ -= OpenAI - Embeddings, Moderation and Audio Operations += OpenAI - Embeddings, Moderation, Audio and Image Operations :tabs-sync-option: xref:ROOT:openai-component.adoc[Back to OpenAI Component] @@ -581,3 +581,147 @@ The message body is the generated audio as a `byte[]`. The `Content-Type` header ==== The generated audio is fully buffered into a `byte[]` in memory. This is fine for typical short text-to-speech responses, but be mindful of heap usage when synthesizing very large inputs (e.g., book-length narration). ==== + +== Image Generation and Edit Operations + +The `image-generation` operation creates images from a text prompt using OpenAI's `POST /v1/images/generations` endpoint. The message body is the prompt, and the produced body is the generated image as a `byte[]` together with a `Content-Type` header, so the result chains straight into `file:`, object storage, or messaging endpoints. + +=== Basic Image Generation + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:product-image") + .setBody(simple("Studio photo of ${header.productName} on a white background")) + .to("openai:image-generation?imageModel=gpt-image-1&imageSize=1024x1024") + .to("file:target/images?fileName=${header.productName}.png"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:product-image + steps: + - to: + uri: openai:image-generation + parameters: + imageModel: gpt-image-1 + imageSize: 1024x1024 + - to: + uri: file:target/images?fileName=${header.productName}.png +---- +==== + +The prompt can also come from the `imagePrompt` endpoint option or the `CamelOpenAIImagePrompt` header, which take precedence over the message body. That is useful when the body is already carrying something else, or when the prompt is a fixed part of the route. + +=== Image Generation Parameters + +[cols="1,1,3"] +|=== +| Parameter | Type | Description + +| `imageModel` | String | The model to use (e.g., `gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`). Required. The DALL-E models are no longer offered by OpenAI, but remain valid values for compatible providers. +| `imagePrompt` | String | The prompt, when it does not come from the message body. +| `imageSize` | String | Image size such as `1024x1024`, `1536x1024`, `1024x1536` or `auto`. The accepted values depend on the model. +| `imageQuality` | String | `auto`, `high`, `medium`, `low` for the GPT image models; `hd` and `standard` are DALL-E values kept for compatible providers. +| `imageResponseFormat` | String | `url` or `b64_json`. Rejected by the OpenAI images endpoint, see the note below. +| `imageCount` | Integer | Number of images to generate, from 1 to 10. `dall-e-3` only supports 1. +| `imageBackground` | String | `transparent`, `opaque` or `auto`. GPT image models only, and `transparent` requires the `png` or `webp` output format. +| `imageOutputFormat` | String | `png`, `jpeg` or `webp`. GPT image models only, which default to `png`. +| `imageOutputCompression` | Integer | Compression from 0 to 100 for the `webp` and `jpeg` output formats. GPT image models only. +| `imageStyle` | String | `vivid` or `natural`. A `dall-e-3` option, so only useful with compatible providers. +| `imageModeration` | String | `low` or `auto`. GPT image models only. +|=== + +[IMPORTANT] +==== +Leave `imageResponseFormat` unset against OpenAI. The GPT image models always return base64 and reject the parameter, and as of August 2026 the OpenAI images endpoint rejects it for every model with `Unknown parameter: 'response_format'` — the DALL-E models that used to accept it are no longer offered. The option is only sent when you set it explicitly, so routes are unaffected by default; it remains available for OpenAI-compatible providers that still implement the older images API, where `url` is often the default. +==== + +=== Image Edit + +The `image-edit` operation edits an existing image using OpenAI's `POST /v1/images/edits` endpoint. The message body carries the image, so the prompt must come from the `imagePrompt` endpoint option or the `CamelOpenAIImagePrompt` header. + +[source,java] +---- +from("aws2-s3:marketing-assets") + .setHeader(OpenAIConstants.IMAGE_PROMPT, constant("Add a red SALE banner in the top-right corner")) + .to("openai:image-edit?imageModel=gpt-image-1") + .to("aws2-s3:marketing-assets-processed"); +---- + +The body may be a `File`, `Path`, `InputStream`, `byte[]`, or a `List` of those. A list is sent as several reference images, which the GPT image models accept up to 16 of, each under 50 MB. + +An optional mask can be supplied through the `CamelOpenAIImageMask` header, using the same body types. The fully transparent areas of the mask mark where the image should be edited: + +[source,java] +---- +from("direct:edit") + .setHeader(OpenAIConstants.IMAGE_MASK, constant(maskBytes)) + .setHeader(OpenAIConstants.IMAGE_PROMPT, constant("Replace the masked area with a blue sky")) + .to("openai:image-edit?imageModel=gpt-image-1&imageInputFidelity=high"); +---- + +The image-edit operation accepts the same parameters as image-generation, except for `imageStyle` and `imageModeration`, and adds one of its own: + +[cols="1,1,3"] +|=== +| Parameter | Type | Description + +| `imageInputFidelity` | String | `high` or `low`, controlling how closely the edit preserves the style and features of the input image. Supported by `gpt-image-1` and `gpt-image-1.5`. +|=== + +=== Image Input Handling + +The API validates the upload on the content type of the multipart part and accepts only `image/png`, `image/jpeg` and `image/webp`. That content type is resolved in this order: + +1. the MIME type detected as described in xref:ROOT:openai-component.adoc[the component MIME type detection] — the `CamelOpenAIMediaType` header, then the content type headers set by the object storage components, then `Content-Type`, then `CamelFileContentType`, then the file name; +2. the extension of the body, when the body is a `File` or `Path`; +3. `image/png`. + +Anything that resolves to a type the API does not accept, such as the `text/plain` that a bare `byte[]` body would otherwise be sent as, falls back to `image/png` rather than being passed through. + +The multipart file name is kept consistent with that content type: the `CamelFileNameOnly` header when it has an extension, otherwise the name of a `File` or `Path` body, otherwise `image.`. + +That means an image arriving from `file:` or from an object storage component needs no extra configuration. A `byte[]` body of a JPEG or WebP image needs either `CamelFileNameOnly`, `Content-Type` or the `CamelOpenAIMediaType` header, or it will be uploaded as PNG. + +=== Image Output + +A single image becomes the message body directly, and several images become a `List`, so the common case does not force routes to unwrap a one-element list. Images returned as base64 are decoded into `byte[]`, and images returned as URLs stay as `String`. + +[cols="1,1,3"] +|=== +| Header | Type | Description + +| `CamelOpenAIImageResultCount` | Integer | The number of images returned. +| `CamelOpenAIImageRevisedPrompt` | String | The prompt as revised by the model, when a single image is returned (`dall-e-3`). +| `CamelOpenAIImageRevisedPrompts` | List | The revised prompts, one entry per returned image. +| `CamelOpenAIImageInputTokens` | Long | Input tokens billed. GPT image models only. +| `CamelOpenAIImageOutputTokens` | Long | Output tokens billed. GPT image models only. +| `CamelOpenAIImageTotalTokens` | Long | Total tokens billed. GPT image models only. +| `Content-Type` | String | Set only when the body is binary, from the output format reported by the response, falling back to the requested `imageOutputFormat` and then to `image/png`. +|=== + +Set `storeFullResponse=true` to keep the complete SDK response in the `CamelOpenAIImageResponse` exchange property. + +[NOTE] +==== +Generated images are fully buffered into memory as `byte[]`. Requesting several large, high-quality images in a single exchange multiplies that cost, so prefer the `url` response format on `dall-e-2` and `dall-e-3` when the route only needs to pass a reference along. +==== diff --git a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java index e37e21cabaa2f..0a233420ffa73 100644 --- a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java +++ b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java @@ -66,6 +66,30 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "hallucinatedToolNameStrategy": target.getConfiguration().setHallucinatedToolNameStrategy(property(camelContext, org.apache.camel.component.openai.HallucinatedToolNameStrategy.class, value)); return true; case "hostedmcptools": case "hostedMcpTools": target.getConfiguration().setHostedMcpTools(property(camelContext, java.lang.String.class, value)); return true; + case "imagebackground": + case "imageBackground": target.getConfiguration().setImageBackground(property(camelContext, java.lang.String.class, value)); return true; + case "imagecount": + case "imageCount": target.getConfiguration().setImageCount(property(camelContext, java.lang.Integer.class, value)); return true; + case "imageinputfidelity": + case "imageInputFidelity": target.getConfiguration().setImageInputFidelity(property(camelContext, java.lang.String.class, value)); return true; + case "imagemodel": + case "imageModel": target.getConfiguration().setImageModel(property(camelContext, java.lang.String.class, value)); return true; + case "imagemoderation": + case "imageModeration": target.getConfiguration().setImageModeration(property(camelContext, java.lang.String.class, value)); return true; + case "imageoutputcompression": + case "imageOutputCompression": target.getConfiguration().setImageOutputCompression(property(camelContext, java.lang.Integer.class, value)); return true; + case "imageoutputformat": + case "imageOutputFormat": target.getConfiguration().setImageOutputFormat(property(camelContext, java.lang.String.class, value)); return true; + case "imageprompt": + case "imagePrompt": target.getConfiguration().setImagePrompt(property(camelContext, java.lang.String.class, value)); return true; + case "imagequality": + case "imageQuality": target.getConfiguration().setImageQuality(property(camelContext, java.lang.String.class, value)); return true; + case "imageresponseformat": + case "imageResponseFormat": target.getConfiguration().setImageResponseFormat(property(camelContext, java.lang.String.class, value)); return true; + case "imagesize": + case "imageSize": target.getConfiguration().setImageSize(property(camelContext, java.lang.String.class, value)); return true; + case "imagestyle": + case "imageStyle": target.getConfiguration().setImageStyle(property(camelContext, java.lang.String.class, value)); return true; case "jsonschema": case "jsonSchema": target.getConfiguration().setJsonSchema(property(camelContext, java.lang.String.class, value)); return true; case "lazystartproducer": @@ -205,6 +229,30 @@ public Class getOptionType(String name, boolean ignoreCase) { case "hallucinatedToolNameStrategy": return org.apache.camel.component.openai.HallucinatedToolNameStrategy.class; case "hostedmcptools": case "hostedMcpTools": return java.lang.String.class; + case "imagebackground": + case "imageBackground": return java.lang.String.class; + case "imagecount": + case "imageCount": return java.lang.Integer.class; + case "imageinputfidelity": + case "imageInputFidelity": return java.lang.String.class; + case "imagemodel": + case "imageModel": return java.lang.String.class; + case "imagemoderation": + case "imageModeration": return java.lang.String.class; + case "imageoutputcompression": + case "imageOutputCompression": return java.lang.Integer.class; + case "imageoutputformat": + case "imageOutputFormat": return java.lang.String.class; + case "imageprompt": + case "imagePrompt": return java.lang.String.class; + case "imagequality": + case "imageQuality": return java.lang.String.class; + case "imageresponseformat": + case "imageResponseFormat": return java.lang.String.class; + case "imagesize": + case "imageSize": return java.lang.String.class; + case "imagestyle": + case "imageStyle": return java.lang.String.class; case "jsonschema": case "jsonSchema": return java.lang.String.class; case "lazystartproducer": @@ -345,6 +393,30 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "hallucinatedToolNameStrategy": return target.getConfiguration().getHallucinatedToolNameStrategy(); case "hostedmcptools": case "hostedMcpTools": return target.getConfiguration().getHostedMcpTools(); + case "imagebackground": + case "imageBackground": return target.getConfiguration().getImageBackground(); + case "imagecount": + case "imageCount": return target.getConfiguration().getImageCount(); + case "imageinputfidelity": + case "imageInputFidelity": return target.getConfiguration().getImageInputFidelity(); + case "imagemodel": + case "imageModel": return target.getConfiguration().getImageModel(); + case "imagemoderation": + case "imageModeration": return target.getConfiguration().getImageModeration(); + case "imageoutputcompression": + case "imageOutputCompression": return target.getConfiguration().getImageOutputCompression(); + case "imageoutputformat": + case "imageOutputFormat": return target.getConfiguration().getImageOutputFormat(); + case "imageprompt": + case "imagePrompt": return target.getConfiguration().getImagePrompt(); + case "imagequality": + case "imageQuality": return target.getConfiguration().getImageQuality(); + case "imageresponseformat": + case "imageResponseFormat": return target.getConfiguration().getImageResponseFormat(); + case "imagesize": + case "imageSize": return target.getConfiguration().getImageSize(); + case "imagestyle": + case "imageStyle": return target.getConfiguration().getImageStyle(); case "jsonschema": case "jsonSchema": return target.getConfiguration().getJsonSchema(); case "lazystartproducer": diff --git a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java index c8c8445903be3..7e6bcd1e2cbe1 100644 --- a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java +++ b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java @@ -24,7 +24,7 @@ public class OpenAIEndpointUriFactory extends org.apache.camel.support.component private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(69); + Set props = new HashSet<>(81); props.add("additionalBodyProperty"); props.add("additionalHeader"); props.add("additionalResponseHeader"); @@ -47,6 +47,18 @@ public class OpenAIEndpointUriFactory extends org.apache.camel.support.component props.add("fileSearchVectorStoreIds"); props.add("hallucinatedToolNameStrategy"); props.add("hostedMcpTools"); + props.add("imageBackground"); + props.add("imageCount"); + props.add("imageInputFidelity"); + props.add("imageModel"); + props.add("imageModeration"); + props.add("imageOutputCompression"); + props.add("imageOutputFormat"); + props.add("imagePrompt"); + props.add("imageQuality"); + props.add("imageResponseFormat"); + props.add("imageSize"); + props.add("imageStyle"); props.add("jsonSchema"); props.add("lazyStartProducer"); props.add("maxAgenticTokens"); 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 8f3a4643f5d6d..785d63a1495ee 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 @@ -64,36 +64,56 @@ "CamelOpenAIResponse": { "index": 27, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.chat.completions.ChatCompletion", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI chat completion response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSE" }, "CamelOpenAIResponsesResponse": { "index": 28, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.responses.Response", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI Responses API response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSES_RESPONSE" }, "CamelOpenAIModerationResponse": { "index": 29, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.moderations.ModerationCreateResponse", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI moderation response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESPONSE" }, - "CamelOpenAIEmbeddingModel": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" }, - "CamelOpenAIEmbeddingDimensions": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of output dimensions", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" }, - "CamelOpenAIEmbeddingResponseModel": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The embedding model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" }, - "CamelOpenAIEmbeddingCount": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of embeddings returned", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" }, - "CamelOpenAIEmbeddingVectorSize": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Vector dimensions of the embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" }, - "CamelOpenAIReferenceEmbedding": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Reference embedding vector for similarity comparison", "constantName": "org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" }, - "CamelOpenAISimilarityScore": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Calculated cosine similarity score (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" }, - "CamelOpenAIOriginalText": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String or List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Original text content when embeddings operation is used", "constantName": "org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }, - "CamelOpenAIModerationModel": { "index": 38, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for moderation (e.g., omni-moderation-latest)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_MODEL" }, - "CamelOpenAIModerationFlagged": { "index": 39, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the moderation API flagged the input as violating the usage policies. For a batch of inputs this is true when at least one input was flagged", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_FLAGGED" }, - "CamelOpenAIModerationResults": { "index": 40, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "One verdict per moderated input, in the order of the inputs. Each entry holds the keys 'input', 'flagged', 'categories' and 'categoryScores', so a batch can be split and routed per item", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESULTS" }, - "CamelOpenAIModerationCategories": { "index": 41, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation categories and whether each one was violated, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORIES" }, - "CamelOpenAIModerationCategoryScores": { "index": 42, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation confidence score per category, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORY_SCORES" }, - "CamelOpenAIModerationResponseModel": { "index": 43, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESPONSE_MODEL" }, - "CamelOpenAIAudioModel": { "index": 44, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for audio transcription", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_MODEL" }, - "CamelOpenAIAudioLanguage": { "index": 45, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The language of the input audio (ISO-639-1)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_LANGUAGE" }, - "CamelOpenAIAudioResponseFormat": { "index": 46, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format for audio transcription (json, text, srt, verbose_json, vtt)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_RESPONSE_FORMAT" }, - "CamelOpenAIAudioTemperature": { "index": 47, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sampling temperature for audio transcription (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TEMPERATURE" }, - "CamelOpenAIAudioPrompt": { "index": 48, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional text to guide the model's style or continue a previous audio segment", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_PROMPT" }, - "CamelOpenAIAudioTimestampGranularities": { "index": 49, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TIMESTAMP_GRANULARITIES" }, - "CamelOpenAIAudioDuration": { "index": 50, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" }, - "CamelOpenAIAudioDetectedLanguage": { "index": 51, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }, - "CamelOpenAISpeechModel": { "index": 52, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_MODEL" }, - "CamelOpenAISpeechVoice": { "index": 53, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The voice to use for the generated audio (e.g., alloy, echo, fable, onyx, nova, shimmer)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_VOICE" }, - "CamelOpenAISpeechResponseFormat": { "index": 54, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The audio format for text-to-speech output (mp3, opus, aac, flac, wav, pcm)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_RESPONSE_FORMAT" }, - "CamelOpenAISpeechSpeed": { "index": 55, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The speed of the generated audio (0.25 to 4.0, where 1.0 is normal speed)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_SPEED" }, - "CamelOpenAISpeechInstructions": { "index": 56, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional instructions to control the voice of the generated audio (does not work with tts-1 or tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_INSTRUCTIONS" } + "CamelOpenAIImageResponse": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "com.openai.models.images.ImagesResponse", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The complete OpenAI image generation or edit response object", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_RESPONSE" }, + "CamelOpenAIEmbeddingModel": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" }, + "CamelOpenAIEmbeddingDimensions": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of output dimensions", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" }, + "CamelOpenAIEmbeddingResponseModel": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The embedding model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" }, + "CamelOpenAIEmbeddingCount": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of embeddings returned", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" }, + "CamelOpenAIEmbeddingVectorSize": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Vector dimensions of the embeddings", "constantName": "org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" }, + "CamelOpenAIReferenceEmbedding": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Reference embedding vector for similarity comparison", "constantName": "org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" }, + "CamelOpenAISimilarityScore": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Calculated cosine similarity score (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" }, + "CamelOpenAIOriginalText": { "index": 38, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String or List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Original text content when embeddings operation is used", "constantName": "org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }, + "CamelOpenAIModerationModel": { "index": 39, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for moderation (e.g., omni-moderation-latest)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_MODEL" }, + "CamelOpenAIModerationFlagged": { "index": 40, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the moderation API flagged the input as violating the usage policies. For a batch of inputs this is true when at least one input was flagged", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_FLAGGED" }, + "CamelOpenAIModerationResults": { "index": 41, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "One verdict per moderated input, in the order of the inputs. Each entry holds the keys 'input', 'flagged', 'categories' and 'categoryScores', so a batch can be split and routed per item", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESULTS" }, + "CamelOpenAIModerationCategories": { "index": 42, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation categories and whether each one was violated, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORIES" }, + "CamelOpenAIModerationCategoryScores": { "index": 43, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation confidence score per category, for a single input. Not set for a list body, where 'CamelOpenAIModerationResults' carries the verdicts", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_CATEGORY_SCORES" }, + "CamelOpenAIModerationResponseModel": { "index": 44, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The moderation model used in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#MODERATION_RESPONSE_MODEL" }, + "CamelOpenAIAudioModel": { "index": 45, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for audio transcription", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_MODEL" }, + "CamelOpenAIAudioLanguage": { "index": 46, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The language of the input audio (ISO-639-1)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_LANGUAGE" }, + "CamelOpenAIAudioResponseFormat": { "index": 47, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format for audio transcription (json, text, srt, verbose_json, vtt)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_RESPONSE_FORMAT" }, + "CamelOpenAIAudioTemperature": { "index": 48, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sampling temperature for audio transcription (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TEMPERATURE" }, + "CamelOpenAIAudioPrompt": { "index": 49, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional text to guide the model's style or continue a previous audio segment", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_PROMPT" }, + "CamelOpenAIAudioTimestampGranularities": { "index": 50, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TIMESTAMP_GRANULARITIES" }, + "CamelOpenAIAudioDuration": { "index": 51, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" }, + "CamelOpenAIAudioDetectedLanguage": { "index": 52, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }, + "CamelOpenAISpeechModel": { "index": 53, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_MODEL" }, + "CamelOpenAISpeechVoice": { "index": 54, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The voice to use for the generated audio (e.g., alloy, echo, fable, onyx, nova, shimmer)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_VOICE" }, + "CamelOpenAISpeechResponseFormat": { "index": 55, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The audio format for text-to-speech output (mp3, opus, aac, flac, wav, pcm)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_RESPONSE_FORMAT" }, + "CamelOpenAISpeechSpeed": { "index": 56, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The speed of the generated audio (0.25 to 4.0, where 1.0 is normal speed)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_SPEED" }, + "CamelOpenAISpeechInstructions": { "index": 57, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional instructions to control the voice of the generated audio (does not work with tts-1 or tts-1-hd)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#SPEECH_INSTRUCTIONS" }, + "CamelOpenAIImageModel": { "index": 58, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for image generation or editing (e.g., gpt-image-1, dall-e-3, dall-e-2)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_MODEL" }, + "CamelOpenAIImagePrompt": { "index": 59, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prompt describing the image to generate, or the edit to apply. Takes precedence over the imagePrompt endpoint option and, for image-generation, over the message body", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_PROMPT" }, + "CamelOpenAIImageSize": { "index": 60, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the generated image (e.g., 1024x1024, 1536x1024, auto)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_SIZE" }, + "CamelOpenAIImageQuality": { "index": 61, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The quality of the generated image (auto, high, medium, low for GPT image models; hd, standard for dall-e-3; standard for dall-e-2)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_QUALITY" }, + "CamelOpenAIImageResponseFormat": { "index": 62, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format of the generated image (url or b64_json). Only supported by dall-e-2 and dall-e-3; GPT image models always return base64", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_RESPONSE_FORMAT" }, + "CamelOpenAIImageCount": { "index": 63, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of images to generate", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_COUNT" }, + "CamelOpenAIImageBackground": { "index": 64, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The background of the generated image (transparent, opaque, auto). Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_BACKGROUND" }, + "CamelOpenAIImageOutputFormat": { "index": 65, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The output format of the generated image (png, jpeg, webp). Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_FORMAT" }, + "CamelOpenAIImageOutputCompression": { "index": 66, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The compression level (0-100) for the webp or jpeg output formats. Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_OUTPUT_COMPRESSION" }, + "CamelOpenAIImageStyle": { "index": 67, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The style of the generated image (vivid or natural). Only supported by dall-e-3", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_STYLE" }, + "CamelOpenAIImageModeration": { "index": 68, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content moderation level for image generation (low or auto). Only supported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_MODERATION" }, + "CamelOpenAIImageInputFidelity": { "index": 69, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How closely the edit must match the style and features of the input image (high or low). Only supported by the image-edit operation on gpt-image-1 and gpt-image-1.5", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_INPUT_FIDELITY" }, + "CamelOpenAIImageMask": { "index": 70, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "byte[], java.io.File, java.nio.file.Path or java.io.InputStream", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "An optional PNG mask for the image-edit operation, where the fully transparent areas indicate where the image should be edited", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_MASK" }, + "CamelOpenAIImageResultCount": { "index": 71, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of images returned in the response", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_RESULT_COUNT" }, + "CamelOpenAIImageRevisedPrompt": { "index": 72, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prompt as revised by the model, when a single image is returned (dall-e-3)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_REVISED_PROMPT" }, + "CamelOpenAIImageRevisedPrompts": { "index": 73, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prompts as revised by the model, one entry per returned image (dall-e-3)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_REVISED_PROMPTS" }, + "CamelOpenAIImageInputTokens": { "index": 74, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of input tokens billed for the image request. Only reported by GPT image models", "constantName": "org.apache.camel.component.openai.OpenAIConstants#IMAGE_INPUT_TOKENS" }, + "CamelOpenAIImageOutputTokens": { "index": 75, "kind": "header", "displayName": "", "group": "producer", "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": 76, "kind": "header", "displayName": "", "group": "producer", "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" } }, "properties": { - "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openai.OpenAIOperations", "enum": [ "chat-completion", "responses", "embeddings", "tool-execution", "audio-transcription", "audio-translation", "audio-speech", "moderation" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform: 'chat-completion', 'responses', 'embeddings', 'tool-execution', 'audio-transcription', 'audio-translation', 'audio-speech', or 'moderation'" }, + "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openai.OpenAIOperations", "enum": [ "chat-completion", "responses", "embeddings", "tool-execution", "audio-transcription", "audio-translation", "audio-speech", "moderation", "image-generation", "image-edit" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform: 'chat-completion', 'responses', 'embeddings', 'tool-execution', 'audio-transcription', 'audio-translation', 'audio-speech', 'moderation', 'image-generation', or 'image-edit'" }, "additionalBodyProperty": { "index": 1, "kind": "parameter", "displayName": "Additional Body Property", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "additionalBodyProperty.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Additional JSON properties to include in the request body (e.g. additionalBodyProperty.traceId=123). This is a multi-value option with prefix: additionalBodyProperty." }, "additionalHeader": { "index": 2, "kind": "parameter", "displayName": "Additional Header", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "additionalHeader.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Additional HTTP request headers to send with every API call (e.g. additionalHeader.OpenAI-Organization=my-org or additionalHeader.api-key=secret). Values may contain secrets. This is a multi-value option with prefix: additionalHeader." }, "additionalResponseHeader": { "index": 3, "kind": "parameter", "displayName": "Additional Response Header", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "additionalResponseHeader.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Map additional fields from the response message to Camel headers. The key is the field name in the API response, the value is the Camel header name (e.g. additionalResponseHeader.reasoning_content=CamelMyReasoningHeader). This is a multi-value option with prefix: additionalResponseHeader." }, @@ -116,51 +136,63 @@ "fileSearchVectorStoreIds": { "index": 20, "kind": "parameter", "displayName": "File Search Vector Store Ids", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated vector store ids required when builtinTools includes file_search" }, "hallucinatedToolNameStrategy": { "index": 21, "kind": "parameter", "displayName": "Hallucinated Tool Name Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.HallucinatedToolNameStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strategy for handling tool names hallucinated by the model (tool not found in any MCP server). 'failExchange' (default) throws an IllegalStateException, failing the exchange immediately. 'repromptModel' sends a corrective tool result listing the available tools so the model can self-correct and retry. The maxToolIterations option bounds retries." }, "hostedMcpTools": { "index": 22, "kind": "parameter", "displayName": "Hosted Mcp Tools", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON array of hosted MCP tool definitions (OpenAI Tool.Mcp) passed through to the Responses API" }, - "jsonSchema": { "index": 23, "kind": "parameter", "displayName": "Json Schema", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "supportFileReference": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON schema for structured output validation" }, - "maxAgenticTokens": { "index": 24, "kind": "parameter", "displayName": "Max Agentic Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum cumulative prompt plus completion tokens allowed across the MCP agentic loop. When 0 or negative, no token budget is enforced. Enforcement runs after each API call that requests further tool execution, so actual spend may exceed the configured budget by up to one call (typically the largest, as the prompt grows each iteration). A final text response is returned even when cumulative usage exceeds the budget." }, - "maxHistoryMessages": { "index": 25, "kind": "parameter", "displayName": "Max History Messages", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, retain at most this many messages in the exchange conversation history. System and developer messages are prepended separately and are not stored in history. Assistant tool-call blocks are kept intact and may retain slightly more than this limit to preserve tool result pairing. When 0, no message limit is applied." }, - "maxHistoryTokens": { "index": 26, "kind": "parameter", "displayName": "Max History Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, trim conversation history using a token estimate (character count \/ 4, including image payload size for multi-modal user messages). Oldest segments are dropped first until the estimated tokens are within this limit. Assistant tool-call blocks are removed as a unit with their tool results. The most recent segment is always retained, even when it alone exceeds this limit. When 0, no token limit is applied." }, - "maxRetries": { "index": 27, "kind": "parameter", "displayName": "Max Retries", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 2, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of times the OpenAI SDK client retries failed requests. The SDK retry is rate-limit aware (honors Retry-After on 429)." }, - "maxTokens": { "index": 28, "kind": "parameter", "displayName": "Max Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tokens to generate" }, - "maxToolIterations": { "index": 29, "kind": "parameter", "displayName": "Max Tool Iterations", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 50, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tool call loop iterations to prevent infinite loops" }, - "mcpProtocolVersions": { "index": 30, "kind": "parameter", "displayName": "Mcp Protocol Versions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated list of MCP protocol versions to advertise when connecting to MCP servers using Streamable HTTP transport. When not set, the SDK default is used. Example: 2024-11-05,2025-03-26,2025-06-18" }, - "mcpReconnect": { "index": 31, "kind": "parameter", "displayName": "Mcp Reconnect", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Automatically reconnect to MCP servers when a tool call fails due to a transport error, and retry the call once." }, - "mcpServer": { "index": 32, "kind": "parameter", "displayName": "Mcp Server", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "MCP (Model Context Protocol) server configurations. Define servers using prefix notation: mcpServer..transportType=stdiossestreamableHttp, (Note that sse is deprecated) mcpServer..command= (stdio), mcpServer..args= (stdio), mcpServer..url= (sse\/streamableHttp), mcpServer..oauthProfile= (OAuth profile for HTTP auth, requires camel-oauth), mcpServer..toolNames= (optional include list to restrict which tools are registered from this server). This is a multi-value option with prefix: mcpServer." }, - "mcpTimeout": { "index": 33, "kind": "parameter", "displayName": "Mcp Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP tool call requests. Applies to all MCP operations including tool execution and initialization." }, - "mcpToolRefresh": { "index": 34, "kind": "parameter", "displayName": "Mcp Tool Refresh", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Refresh the advertised tool list when an MCP server notifies that its tools changed. Set to false to keep the tool list fixed to what was listed when the endpoint started, for deployments that require a deterministic set of tools." }, - "model": { "index": 35, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for chat completion" }, - "moderationModel": { "index": 36, "kind": "parameter", "displayName": "Moderation Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "omni-moderation-latest", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for moderation" }, - "outputClass": { "index": 37, "kind": "parameter", "displayName": "Output Class", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Fully qualified class name for structured output using response format" }, - "parallelToolExecution": { "index": 38, "kind": "parameter", "displayName": "Parallel Tool Execution", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Execute the tool calls returned by the model in a single response concurrently instead of sequentially. Tool calls in the same batch are independent by design, so this reduces the latency of a batch to that of its slowest tool. Results are always fed back to the model in the original tool call order. Note that with toolExecutionErrorStrategy=failExchange the sibling tool calls already dispatched complete before the exchange fails." }, - "parallelToolTimeout": { "index": 39, "kind": "parameter", "displayName": "Parallel Tool Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in milliseconds for a batch of parallel tool calls, so that one slow tool cannot block the whole batch. The timeout applies to the batch as a whole, not per tool call. A tool call that exceeds it is cancelled and handled according to toolExecutionErrorStrategy. The default of 0 disables the batch timeout and relies on mcpTimeout, which already bounds each individual MCP request. Only used when parallelToolExecution=true." }, - "previousResponseId": { "index": 40, "kind": "parameter", "displayName": "Previous Response Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Previous response id for OpenAI server-side conversation state (Responses API only)" }, - "requestTimeout": { "index": 41, "kind": "parameter", "displayName": "Request Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "HTTP request timeout in milliseconds for the OpenAI SDK client. When 0 or negative, the SDK default (10 minutes) is used." }, - "speechInstructions": { "index": 42, "kind": "parameter", "displayName": "Speech Instructions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Optional instructions to control the voice of the generated audio. Does not work with tts-1 or tts-1-hd." }, - "speechModel": { "index": 43, "kind": "parameter", "displayName": "Speech Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)" }, - "speechResponseFormat": { "index": 44, "kind": "parameter", "displayName": "Speech Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "mp3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The audio format for text-to-speech output" }, - "speechSpeed": { "index": 45, "kind": "parameter", "displayName": "Speech Speed", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The speed of the generated audio, from 0.25 to 4.0 where 1.0 is normal speed" }, - "speechVoice": { "index": 46, "kind": "parameter", "displayName": "Speech Voice", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "alloy", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The voice to use for text-to-speech (e.g., alloy, echo, fable, onyx, nova, shimmer). See the OpenAI documentation for the full list of supported voices." }, - "storeFullResponse": { "index": 47, "kind": "parameter", "displayName": "Store Full Response", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Store the full SDK response in non-streaming mode: chat-completion uses exchange property 'CamelOpenAIResponse'; responses uses 'CamelOpenAIResponsesResponse'; moderation uses 'CamelOpenAIModerationResponse'" }, - "streaming": { "index": 48, "kind": "parameter", "displayName": "Streaming", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Enable streaming responses" }, - "stripThinking": { "index": 49, "kind": "parameter", "displayName": "Strip Thinking", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strip ... blocks from model responses (used by reasoning models like Qwen3, DeepSeek-R1). The thinking content is stored in the CamelOpenAIThinkingContent header." }, - "systemMessage": { "index": 50, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "System message to prepend. When set and conversationMemory is enabled, the conversation history is reset." }, - "temperature": { "index": 51, "kind": "parameter", "displayName": "Temperature", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Temperature for response generation (0.0 to 2.0)" }, - "toolExecutionErrorStrategy": { "index": 52, "kind": "parameter", "displayName": "Tool Execution Error Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.ToolExecutionErrorStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strategy for handling exceptions thrown during MCP tool execution. 'failExchange' (default) propagates the exception to the Camel exchange so that standard Camel error handling (onException, dead-letter channel) can process it. This is the safer default because 'repromptModel' sends raw exception messages (which may contain connection strings, hostnames, or internal paths) to a third-party LLM provider. 'repromptModel' catches the error and sends it back to the model as a tool result so the model can attempt to recover." }, - "topP": { "index": 53, "kind": "parameter", "displayName": "Top P", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Top P for response generation (0.0 to 1.0)" }, - "userMessage": { "index": 54, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Default user message text to use when no prompt is provided" }, - "lazyStartProducer": { "index": 55, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, - "oauthProfile": { "index": 56, "kind": "parameter", "displayName": "Oauth Profile", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "OAuth profile name for obtaining an access token via the OAuth 2.0 Client Credentials grant. When set, the token is acquired from the configured identity provider and used instead of apiKey. Requires camel-oauth on the classpath. The profile properties are resolved from camel.oauth..client-id, camel.oauth..client-secret, and camel.oauth..token-endpoint." }, - "sslContextParameters": { "index": 57, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "SSLContextParameters to use for configuring SSL\/TLS. When set, takes precedence over the individual sslTruststore, sslKeystore, and sslProtocol options." }, - "sslEndpointAlgorithm": { "index": 58, "kind": "parameter", "displayName": "Ssl Endpoint Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "security": "insecure:ssl", "insecureValue": "none", "defaultValue": "https", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The endpoint identification algorithm to validate the server hostname using the server certificate. Set to an empty string or 'none' to disable hostname verification" }, - "sslKeymanagerAlgorithm": { "index": 59, "kind": "parameter", "displayName": "Ssl Keymanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "SunX509", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the key manager factory for SSL connections" }, - "sslKeyPassword": { "index": 60, "kind": "parameter", "displayName": "Ssl Key Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password of the private key in the key store file" }, - "sslKeystoreLocation": { "index": 61, "kind": "parameter", "displayName": "Ssl Keystore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the key store file. This is optional and can be used for two-way authentication for the OpenAI API" }, - "sslKeystorePassword": { "index": 62, "kind": "parameter", "displayName": "Ssl Keystore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The store password for the key store file" }, - "sslKeystoreType": { "index": 63, "kind": "parameter", "displayName": "Ssl Keystore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the key store file" }, - "sslProtocol": { "index": 64, "kind": "parameter", "displayName": "Ssl Protocol", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "TLSv1.3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The SSL protocol used to generate the SSLContext" }, - "sslTrustmanagerAlgorithm": { "index": 65, "kind": "parameter", "displayName": "Ssl Trustmanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "PKIX", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the trust manager factory for SSL connections" }, - "sslTruststoreLocation": { "index": 66, "kind": "parameter", "displayName": "Ssl Truststore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the trust store file, used to validate the server's certificate" }, - "sslTruststorePassword": { "index": 67, "kind": "parameter", "displayName": "Ssl Truststore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password for the trust store file. If a password is not set, the configured trust store can still be used, but integrity checking is disabled" }, - "sslTruststoreType": { "index": 68, "kind": "parameter", "displayName": "Ssl Truststore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the trust store file" } + "imageBackground": { "index": 23, "kind": "parameter", "displayName": "Image Background", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "transparent", "opaque", "auto" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The background of the generated image. Only supported by the GPT image models, and a transparent background requires the png or webp output format." }, + "imageCount": { "index": 24, "kind": "parameter", "displayName": "Image Count", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The number of images to generate, between 1 and 10. dall-e-3 only supports 1." }, + "imageInputFidelity": { "index": 25, "kind": "parameter", "displayName": "Image Input Fidelity", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "high", "low" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "How closely the edit must match the style and features of the input image. Only supported by the image-edit operation on gpt-image-1 and gpt-image-1.5." }, + "imageModel": { "index": 26, "kind": "parameter", "displayName": "Image Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for image generation or editing (e.g., gpt-image-1, gpt-image-1-mini, gpt-image-1.5, gpt-image-2). Required for the image-generation and image-edit operations, because the model determines which of the other image options are accepted. The DALL-E models are no longer offered by OpenAI, but remain valid values for OpenAI-compatible providers." }, + "imageModeration": { "index": 27, "kind": "parameter", "displayName": "Image Moderation", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "low", "auto" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The content moderation level applied to image generation. Only supported by the GPT image models." }, + "imageOutputCompression": { "index": 28, "kind": "parameter", "displayName": "Image Output Compression", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The compression level from 0 to 100 for the webp and jpeg output formats. Only supported by the GPT image models." }, + "imageOutputFormat": { "index": 29, "kind": "parameter", "displayName": "Image Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "png", "jpeg", "webp" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The output format of the generated image. Only supported by the GPT image models, which default to png." }, + "imagePrompt": { "index": 30, "kind": "parameter", "displayName": "Image Prompt", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The prompt describing the image to generate, or the edit to apply. For image-generation the message body is used when this is not set; for image-edit the body carries the input image, so the prompt must come from this option or from the CamelOpenAIImagePrompt header." }, + "imageQuality": { "index": 31, "kind": "parameter", "displayName": "Image Quality", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "auto", "high", "medium", "low", "hd", "standard" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The quality of the generated image. GPT image models accept auto, high, medium and low; hd and standard are DALL-E values kept for OpenAI-compatible providers." }, + "imageResponseFormat": { "index": 32, "kind": "parameter", "displayName": "Image Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "url", "b64_json" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The response format of the generated image. The OpenAI images endpoint rejects this option: the GPT image models always return base64, and the DALL-E models that used to accept it are no longer offered. It is only sent when explicitly set, and is kept for OpenAI-compatible providers that still implement the older images API." }, + "imageSize": { "index": 33, "kind": "parameter", "displayName": "Image Size", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The size of the generated image (e.g., 1024x1024, 1536x1024, 1024x1536, auto). The accepted values depend on the model." }, + "imageStyle": { "index": 34, "kind": "parameter", "displayName": "Image Style", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "vivid", "natural" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The style of the generated image. A dall-e-3 option, so only useful with OpenAI-compatible providers." }, + "jsonSchema": { "index": 35, "kind": "parameter", "displayName": "Json Schema", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "supportFileReference": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "JSON schema for structured output validation" }, + "maxAgenticTokens": { "index": 36, "kind": "parameter", "displayName": "Max Agentic Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum cumulative prompt plus completion tokens allowed across the MCP agentic loop. When 0 or negative, no token budget is enforced. Enforcement runs after each API call that requests further tool execution, so actual spend may exceed the configured budget by up to one call (typically the largest, as the prompt grows each iteration). A final text response is returned even when cumulative usage exceeds the budget." }, + "maxHistoryMessages": { "index": 37, "kind": "parameter", "displayName": "Max History Messages", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, retain at most this many messages in the exchange conversation history. System and developer messages are prepended separately and are not stored in history. Assistant tool-call blocks are kept intact and may retain slightly more than this limit to preserve tool result pairing. When 0, no message limit is applied." }, + "maxHistoryTokens": { "index": 38, "kind": "parameter", "displayName": "Max History Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "When conversationMemory is enabled, trim conversation history using a token estimate (character count \/ 4, including image payload size for multi-modal user messages). Oldest segments are dropped first until the estimated tokens are within this limit. Assistant tool-call blocks are removed as a unit with their tool results. The most recent segment is always retained, even when it alone exceeds this limit. When 0, no token limit is applied." }, + "maxRetries": { "index": 39, "kind": "parameter", "displayName": "Max Retries", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 2, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of times the OpenAI SDK client retries failed requests. The SDK retry is rate-limit aware (honors Retry-After on 429)." }, + "maxTokens": { "index": 40, "kind": "parameter", "displayName": "Max Tokens", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tokens to generate" }, + "maxToolIterations": { "index": 41, "kind": "parameter", "displayName": "Max Tool Iterations", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 50, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Maximum number of tool call loop iterations to prevent infinite loops" }, + "mcpProtocolVersions": { "index": 42, "kind": "parameter", "displayName": "Mcp Protocol Versions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Comma-separated list of MCP protocol versions to advertise when connecting to MCP servers using Streamable HTTP transport. When not set, the SDK default is used. Example: 2024-11-05,2025-03-26,2025-06-18" }, + "mcpReconnect": { "index": 43, "kind": "parameter", "displayName": "Mcp Reconnect", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Automatically reconnect to MCP servers when a tool call fails due to a transport error, and retry the call once." }, + "mcpServer": { "index": 44, "kind": "parameter", "displayName": "Mcp Server", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "MCP (Model Context Protocol) server configurations. Define servers using prefix notation: mcpServer..transportType=stdiossestreamableHttp, (Note that sse is deprecated) mcpServer..command= (stdio), mcpServer..args= (stdio), mcpServer..url= (sse\/streamableHttp), mcpServer..oauthProfile= (OAuth profile for HTTP auth, requires camel-oauth), mcpServer..toolNames= (optional include list to restrict which tools are registered from this server). This is a multi-value option with prefix: mcpServer." }, + "mcpTimeout": { "index": 45, "kind": "parameter", "displayName": "Mcp Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP tool call requests. Applies to all MCP operations including tool execution and initialization." }, + "mcpToolRefresh": { "index": 46, "kind": "parameter", "displayName": "Mcp Tool Refresh", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Refresh the advertised tool list when an MCP server notifies that its tools changed. Set to false to keep the tool list fixed to what was listed when the endpoint started, for deployments that require a deterministic set of tools." }, + "model": { "index": 47, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for chat completion" }, + "moderationModel": { "index": 48, "kind": "parameter", "displayName": "Moderation Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "omni-moderation-latest", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for moderation" }, + "outputClass": { "index": 49, "kind": "parameter", "displayName": "Output Class", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Fully qualified class name for structured output using response format" }, + "parallelToolExecution": { "index": 50, "kind": "parameter", "displayName": "Parallel Tool Execution", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Execute the tool calls returned by the model in a single response concurrently instead of sequentially. Tool calls in the same batch are independent by design, so this reduces the latency of a batch to that of its slowest tool. Results are always fed back to the model in the original tool call order. Note that with toolExecutionErrorStrategy=failExchange the sibling tool calls already dispatched complete before the exchange fails." }, + "parallelToolTimeout": { "index": 51, "kind": "parameter", "displayName": "Parallel Tool Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Timeout in milliseconds for a batch of parallel tool calls, so that one slow tool cannot block the whole batch. The timeout applies to the batch as a whole, not per tool call. A tool call that exceeds it is cancelled and handled according to toolExecutionErrorStrategy. The default of 0 disables the batch timeout and relies on mcpTimeout, which already bounds each individual MCP request. Only used when parallelToolExecution=true." }, + "previousResponseId": { "index": 52, "kind": "parameter", "displayName": "Previous Response Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Previous response id for OpenAI server-side conversation state (Responses API only)" }, + "requestTimeout": { "index": 53, "kind": "parameter", "displayName": "Request Timeout", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "HTTP request timeout in milliseconds for the OpenAI SDK client. When 0 or negative, the SDK default (10 minutes) is used." }, + "speechInstructions": { "index": 54, "kind": "parameter", "displayName": "Speech Instructions", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Optional instructions to control the voice of the generated audio. Does not work with tts-1 or tts-1-hd." }, + "speechModel": { "index": 55, "kind": "parameter", "displayName": "Speech Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The model to use for text-to-speech (e.g., gpt-4o-mini-tts, tts-1, tts-1-hd)" }, + "speechResponseFormat": { "index": 56, "kind": "parameter", "displayName": "Speech Response Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "mp3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The audio format for text-to-speech output" }, + "speechSpeed": { "index": 57, "kind": "parameter", "displayName": "Speech Speed", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The speed of the generated audio, from 0.25 to 4.0 where 1.0 is normal speed" }, + "speechVoice": { "index": 58, "kind": "parameter", "displayName": "Speech Voice", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "alloy", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The voice to use for text-to-speech (e.g., alloy, echo, fable, onyx, nova, shimmer). See the OpenAI documentation for the full list of supported voices." }, + "storeFullResponse": { "index": 59, "kind": "parameter", "displayName": "Store Full Response", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Store the full SDK response in non-streaming mode: chat-completion uses exchange property 'CamelOpenAIResponse'; responses uses 'CamelOpenAIResponsesResponse'; moderation uses 'CamelOpenAIModerationResponse'; image-generation and image-edit use 'CamelOpenAIImageResponse'" }, + "streaming": { "index": 60, "kind": "parameter", "displayName": "Streaming", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Enable streaming responses" }, + "stripThinking": { "index": 61, "kind": "parameter", "displayName": "Strip Thinking", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strip ... blocks from model responses (used by reasoning models like Qwen3, DeepSeek-R1). The thinking content is stored in the CamelOpenAIThinkingContent header." }, + "systemMessage": { "index": 62, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "System message to prepend. When set and conversationMemory is enabled, the conversation history is reset." }, + "temperature": { "index": 63, "kind": "parameter", "displayName": "Temperature", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Temperature for response generation (0.0 to 2.0)" }, + "toolExecutionErrorStrategy": { "index": 64, "kind": "parameter", "displayName": "Tool Execution Error Strategy", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "org.apache.camel.component.openai.ToolExecutionErrorStrategy", "enum": [ "failExchange", "repromptModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "failExchange", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Strategy for handling exceptions thrown during MCP tool execution. 'failExchange' (default) propagates the exception to the Camel exchange so that standard Camel error handling (onException, dead-letter channel) can process it. This is the safer default because 'repromptModel' sends raw exception messages (which may contain connection strings, hostnames, or internal paths) to a third-party LLM provider. 'repromptModel' catches the error and sends it back to the model as a tool result so the model can attempt to recover." }, + "topP": { "index": 65, "kind": "parameter", "displayName": "Top P", "group": "producer", "label": "", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Top P for response generation (0.0 to 1.0)" }, + "userMessage": { "index": 66, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "Default user message text to use when no prompt is provided" }, + "lazyStartProducer": { "index": 67, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "oauthProfile": { "index": 68, "kind": "parameter", "displayName": "Oauth Profile", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "OAuth profile name for obtaining an access token via the OAuth 2.0 Client Credentials grant. When set, the token is acquired from the configured identity provider and used instead of apiKey. Requires camel-oauth on the classpath. The profile properties are resolved from camel.oauth..client-id, camel.oauth..client-secret, and camel.oauth..token-endpoint." }, + "sslContextParameters": { "index": 69, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "SSLContextParameters to use for configuring SSL\/TLS. When set, takes precedence over the individual sslTruststore, sslKeystore, and sslProtocol options." }, + "sslEndpointAlgorithm": { "index": 70, "kind": "parameter", "displayName": "Ssl Endpoint Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "security": "insecure:ssl", "insecureValue": "none", "defaultValue": "https", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The endpoint identification algorithm to validate the server hostname using the server certificate. Set to an empty string or 'none' to disable hostname verification" }, + "sslKeymanagerAlgorithm": { "index": 71, "kind": "parameter", "displayName": "Ssl Keymanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "SunX509", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the key manager factory for SSL connections" }, + "sslKeyPassword": { "index": 72, "kind": "parameter", "displayName": "Ssl Key Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password of the private key in the key store file" }, + "sslKeystoreLocation": { "index": 73, "kind": "parameter", "displayName": "Ssl Keystore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the key store file. This is optional and can be used for two-way authentication for the OpenAI API" }, + "sslKeystorePassword": { "index": 74, "kind": "parameter", "displayName": "Ssl Keystore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The store password for the key store file" }, + "sslKeystoreType": { "index": 75, "kind": "parameter", "displayName": "Ssl Keystore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the key store file" }, + "sslProtocol": { "index": 76, "kind": "parameter", "displayName": "Ssl Protocol", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "TLSv1.3", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The SSL protocol used to generate the SSLContext" }, + "sslTrustmanagerAlgorithm": { "index": 77, "kind": "parameter", "displayName": "Ssl Trustmanager Algorithm", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "PKIX", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The algorithm used by the trust manager factory for SSL connections" }, + "sslTruststoreLocation": { "index": 78, "kind": "parameter", "displayName": "Ssl Truststore Location", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The location of the trust store file, used to validate the server's certificate" }, + "sslTruststorePassword": { "index": 79, "kind": "parameter", "displayName": "Ssl Truststore Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The password for the trust store file. If a password is not set, the configured trust store can still be used, but integrity checking is disabled" }, + "sslTruststoreType": { "index": 80, "kind": "parameter", "displayName": "Ssl Truststore Type", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "JKS", "configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration", "configurationField": "configuration", "description": "The file format of the trust store file" } } } diff --git a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc index 6d5637f0ddde4..5d7462a311615 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 @@ -14,7 +14,7 @@ *{component-header}* -The OpenAI component provides integration with OpenAI and OpenAI-compatible APIs for chat completion, text embeddings, content moderation, audio transcription, audio translation, and text-to-speech using the official openai-java SDK. +The OpenAI component provides integration with OpenAI and OpenAI-compatible APIs for chat completion, text embeddings, content moderation, audio transcription, audio translation, text-to-speech, and image generation and editing using the official openai-java SDK. Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -48,6 +48,8 @@ See xref:others:openai-responses.adoc[Responses API operation] for usage (`previ * `audio-translation` - Transcribe and translate audio files into English text (e.g., Whisper) * `audio-speech` - Synthesize spoken audio from text using text-to-speech models (e.g., gpt-4o-mini-tts, tts-1) * `moderation` - Check text against the OpenAI usage policies before it reaches a model +* `image-generation` - Generate images from a text prompt (e.g., gpt-image-1, gpt-image-1-mini) +* `image-edit` - Edit an existing image, optionally through a mask, from a text prompt // component options: START include::partial$component-configure-options.adoc[] @@ -1255,20 +1257,24 @@ For more details on specific features, see: * xref:others:openai-responses.adoc[Responses API operation] - OpenAI Responses API, hosted tools, and server-side conversation state * xref:others:openai-mcp.adoc[MCP Tool Calling] - Model Context Protocol server configuration, agentic loop, streaming, and connection recovery * xref:others:openai-providers.adoc[OpenAI-Compatible Providers] - Using Ollama, LM Studio, vLLM, and OpenRouter as alternative backends -* xref:others:openai-operations.adoc[Embeddings, Moderation and Audio Operations] - Text embeddings, vector database integration, content moderation, and audio transcription +* xref:others:openai-operations.adoc[Embeddings, Moderation, Audio and Image Operations] - Text embeddings, vector database integration, content moderation, audio transcription, and image generation == Error Handling The component may throw the following exceptions: * `IllegalArgumentException`: - ** When an invalid operation is specified (supported: `chat-completion`, `responses`, `embeddings`, `tool-execution`, `audio-transcription`, `audio-translation`, `audio-speech`, `moderation`) + ** When an invalid operation is specified (supported: `chat-completion`, `responses`, `embeddings`, `tool-execution`, `audio-transcription`, `audio-translation`, `audio-speech`, `moderation`, `image-generation`, `image-edit`) ** When message body or user message is missing ** When the audio model is missing (audio-transcription, audio-translation) or the speech model is missing (audio-speech) ** When image file is provided without userMessage (chat-completion) ** When unsupported file type is provided (only text and image files are supported) ** When invalid JSON schema string is provided ** When the moderation input list is empty or contains null elements (moderation) + ** When the image model is missing (image-generation, image-edit) + ** When the prompt is missing (image-generation, image-edit) or the image list is empty (image-edit) + ** When an unsupported image or mask body type is provided (image-edit) * `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. diff --git a/components/camel-ai/camel-openai/src/main/docs/openai-operations.adoc b/components/camel-ai/camel-openai/src/main/docs/openai-operations.adoc index 99590c19cc364..10b05e4d7282c 100644 --- a/components/camel-ai/camel-openai/src/main/docs/openai-operations.adoc +++ b/components/camel-ai/camel-openai/src/main/docs/openai-operations.adoc @@ -1,4 +1,4 @@ -= OpenAI - Embeddings, Moderation and Audio Operations += OpenAI - Embeddings, Moderation, Audio and Image Operations :tabs-sync-option: xref:ROOT:openai-component.adoc[Back to OpenAI Component] @@ -581,3 +581,147 @@ The message body is the generated audio as a `byte[]`. The `Content-Type` header ==== The generated audio is fully buffered into a `byte[]` in memory. This is fine for typical short text-to-speech responses, but be mindful of heap usage when synthesizing very large inputs (e.g., book-length narration). ==== + +== Image Generation and Edit Operations + +The `image-generation` operation creates images from a text prompt using OpenAI's `POST /v1/images/generations` endpoint. The message body is the prompt, and the produced body is the generated image as a `byte[]` together with a `Content-Type` header, so the result chains straight into `file:`, object storage, or messaging endpoints. + +=== Basic Image Generation + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:product-image") + .setBody(simple("Studio photo of ${header.productName} on a white background")) + .to("openai:image-generation?imageModel=gpt-image-1&imageSize=1024x1024") + .to("file:target/images?fileName=${header.productName}.png"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:product-image + steps: + - to: + uri: openai:image-generation + parameters: + imageModel: gpt-image-1 + imageSize: 1024x1024 + - to: + uri: file:target/images?fileName=${header.productName}.png +---- +==== + +The prompt can also come from the `imagePrompt` endpoint option or the `CamelOpenAIImagePrompt` header, which take precedence over the message body. That is useful when the body is already carrying something else, or when the prompt is a fixed part of the route. + +=== Image Generation Parameters + +[cols="1,1,3"] +|=== +| Parameter | Type | Description + +| `imageModel` | String | The model to use (e.g., `gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`). Required. The DALL-E models are no longer offered by OpenAI, but remain valid values for compatible providers. +| `imagePrompt` | String | The prompt, when it does not come from the message body. +| `imageSize` | String | Image size such as `1024x1024`, `1536x1024`, `1024x1536` or `auto`. The accepted values depend on the model. +| `imageQuality` | String | `auto`, `high`, `medium`, `low` for the GPT image models; `hd` and `standard` are DALL-E values kept for compatible providers. +| `imageResponseFormat` | String | `url` or `b64_json`. Rejected by the OpenAI images endpoint, see the note below. +| `imageCount` | Integer | Number of images to generate, from 1 to 10. `dall-e-3` only supports 1. +| `imageBackground` | String | `transparent`, `opaque` or `auto`. GPT image models only, and `transparent` requires the `png` or `webp` output format. +| `imageOutputFormat` | String | `png`, `jpeg` or `webp`. GPT image models only, which default to `png`. +| `imageOutputCompression` | Integer | Compression from 0 to 100 for the `webp` and `jpeg` output formats. GPT image models only. +| `imageStyle` | String | `vivid` or `natural`. A `dall-e-3` option, so only useful with compatible providers. +| `imageModeration` | String | `low` or `auto`. GPT image models only. +|=== + +[IMPORTANT] +==== +Leave `imageResponseFormat` unset against OpenAI. The GPT image models always return base64 and reject the parameter, and as of August 2026 the OpenAI images endpoint rejects it for every model with `Unknown parameter: 'response_format'` — the DALL-E models that used to accept it are no longer offered. The option is only sent when you set it explicitly, so routes are unaffected by default; it remains available for OpenAI-compatible providers that still implement the older images API, where `url` is often the default. +==== + +=== Image Edit + +The `image-edit` operation edits an existing image using OpenAI's `POST /v1/images/edits` endpoint. The message body carries the image, so the prompt must come from the `imagePrompt` endpoint option or the `CamelOpenAIImagePrompt` header. + +[source,java] +---- +from("aws2-s3:marketing-assets") + .setHeader(OpenAIConstants.IMAGE_PROMPT, constant("Add a red SALE banner in the top-right corner")) + .to("openai:image-edit?imageModel=gpt-image-1") + .to("aws2-s3:marketing-assets-processed"); +---- + +The body may be a `File`, `Path`, `InputStream`, `byte[]`, or a `List` of those. A list is sent as several reference images, which the GPT image models accept up to 16 of, each under 50 MB. + +An optional mask can be supplied through the `CamelOpenAIImageMask` header, using the same body types. The fully transparent areas of the mask mark where the image should be edited: + +[source,java] +---- +from("direct:edit") + .setHeader(OpenAIConstants.IMAGE_MASK, constant(maskBytes)) + .setHeader(OpenAIConstants.IMAGE_PROMPT, constant("Replace the masked area with a blue sky")) + .to("openai:image-edit?imageModel=gpt-image-1&imageInputFidelity=high"); +---- + +The image-edit operation accepts the same parameters as image-generation, except for `imageStyle` and `imageModeration`, and adds one of its own: + +[cols="1,1,3"] +|=== +| Parameter | Type | Description + +| `imageInputFidelity` | String | `high` or `low`, controlling how closely the edit preserves the style and features of the input image. Supported by `gpt-image-1` and `gpt-image-1.5`. +|=== + +=== Image Input Handling + +The API validates the upload on the content type of the multipart part and accepts only `image/png`, `image/jpeg` and `image/webp`. That content type is resolved in this order: + +1. the MIME type detected as described in xref:ROOT:openai-component.adoc[the component MIME type detection] — the `CamelOpenAIMediaType` header, then the content type headers set by the object storage components, then `Content-Type`, then `CamelFileContentType`, then the file name; +2. the extension of the body, when the body is a `File` or `Path`; +3. `image/png`. + +Anything that resolves to a type the API does not accept, such as the `text/plain` that a bare `byte[]` body would otherwise be sent as, falls back to `image/png` rather than being passed through. + +The multipart file name is kept consistent with that content type: the `CamelFileNameOnly` header when it has an extension, otherwise the name of a `File` or `Path` body, otherwise `image.`. + +That means an image arriving from `file:` or from an object storage component needs no extra configuration. A `byte[]` body of a JPEG or WebP image needs either `CamelFileNameOnly`, `Content-Type` or the `CamelOpenAIMediaType` header, or it will be uploaded as PNG. + +=== Image Output + +A single image becomes the message body directly, and several images become a `List`, so the common case does not force routes to unwrap a one-element list. Images returned as base64 are decoded into `byte[]`, and images returned as URLs stay as `String`. + +[cols="1,1,3"] +|=== +| Header | Type | Description + +| `CamelOpenAIImageResultCount` | Integer | The number of images returned. +| `CamelOpenAIImageRevisedPrompt` | String | The prompt as revised by the model, when a single image is returned (`dall-e-3`). +| `CamelOpenAIImageRevisedPrompts` | List | The revised prompts, one entry per returned image. +| `CamelOpenAIImageInputTokens` | Long | Input tokens billed. GPT image models only. +| `CamelOpenAIImageOutputTokens` | Long | Output tokens billed. GPT image models only. +| `CamelOpenAIImageTotalTokens` | Long | Total tokens billed. GPT image models only. +| `Content-Type` | String | Set only when the body is binary, from the output format reported by the response, falling back to the requested `imageOutputFormat` and then to `image/png`. +|=== + +Set `storeFullResponse=true` to keep the complete SDK response in the `CamelOpenAIImageResponse` exchange property. + +[NOTE] +==== +Generated images are fully buffered into memory as `byte[]`. Requesting several large, high-quality images in a single exchange multiplies that cost, so prefer the `url` response format on `dall-e-2` and `dall-e-3` when the route only needs to pass a reference along. +==== diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java index ad171fb7d24e0..0ce3b5d187193 100644 --- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java +++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java @@ -150,7 +150,8 @@ public class OpenAIConfiguration implements Cloneable { @UriParam(defaultValue = "false") @Metadata(description = "Store the full SDK response in non-streaming mode: chat-completion uses exchange property " + "'CamelOpenAIResponse'; responses uses 'CamelOpenAIResponsesResponse'; " - + "moderation uses 'CamelOpenAIModerationResponse'") + + "moderation uses 'CamelOpenAIModerationResponse'; " + + "image-generation and image-edit use 'CamelOpenAIImageResponse'") private boolean storeFullResponse = false; @UriParam(defaultValue = "false") @@ -321,6 +322,75 @@ public class OpenAIConfiguration implements Cloneable { + "Does not work with tts-1 or tts-1-hd.") private String speechInstructions; + // ========== IMAGE GENERATION/EDIT CONFIGURATION ========== + + @UriParam + @Metadata(description = "The model to use for image generation or editing (e.g., gpt-image-1, gpt-image-1-mini, " + + "gpt-image-1.5, gpt-image-2). Required for the image-generation and image-edit " + + "operations, because the model determines which of the other image options are " + + "accepted. The DALL-E models are no longer offered by OpenAI, but remain valid values " + + "for OpenAI-compatible providers.") + private String imageModel; + + @UriParam + @Metadata(description = "The prompt describing the image to generate, or the edit to apply. For image-generation " + + "the message body is used when this is not set; for image-edit the body carries the " + + "input image, so the prompt must come from this option or from the " + + "CamelOpenAIImagePrompt header.", + largeInput = true) + private String imagePrompt; + + @UriParam + @Metadata(description = "The size of the generated image (e.g., 1024x1024, 1536x1024, 1024x1536, auto). " + + "The accepted values depend on the model.") + private String imageSize; + + @UriParam(enums = "auto,high,medium,low,hd,standard") + @Metadata(description = "The quality of the generated image. GPT image models accept auto, high, medium and " + + "low; hd and standard are DALL-E values kept for OpenAI-compatible providers.") + private String imageQuality; + + @UriParam(enums = "url,b64_json") + @Metadata(description = "The response format of the generated image. The OpenAI images endpoint rejects this " + + "option: the GPT image models always return base64, and the DALL-E models that used to " + + "accept it are no longer offered. It is only sent when explicitly set, and is kept for " + + "OpenAI-compatible providers that still implement the older images API.") + private String imageResponseFormat; + + @UriParam + @Metadata(description = "The number of images to generate, between 1 and 10. dall-e-3 only supports 1.") + private Integer imageCount; + + @UriParam(enums = "transparent,opaque,auto") + @Metadata(description = "The background of the generated image. Only supported by the GPT image models, and a " + + "transparent background requires the png or webp output format.") + private String imageBackground; + + @UriParam(enums = "png,jpeg,webp") + @Metadata(description = "The output format of the generated image. Only supported by the GPT image models, " + + "which default to png.") + private String imageOutputFormat; + + @UriParam + @Metadata(description = "The compression level from 0 to 100 for the webp and jpeg output formats. " + + "Only supported by the GPT image models.") + private Integer imageOutputCompression; + + @UriParam(enums = "vivid,natural") + @Metadata(description = "The style of the generated image. A dall-e-3 option, so only useful with " + + "OpenAI-compatible providers.") + private String imageStyle; + + @UriParam(enums = "low,auto") + @Metadata(description = "The content moderation level applied to image generation. Only supported by the " + + "GPT image models.") + private String imageModeration; + + @UriParam(enums = "high,low") + @Metadata(description = "How closely the edit must match the style and features of the input image. " + + "Only supported by the image-edit operation on gpt-image-1 and gpt-image-1.5.") + private String imageInputFidelity; + // ========== SSL CONFIGURATION ========== @UriParam(label = "security") @@ -911,6 +981,102 @@ public void setSslEndpointAlgorithm(String sslEndpointAlgorithm) { this.sslEndpointAlgorithm = sslEndpointAlgorithm; } + public String getImageModel() { + return imageModel; + } + + public void setImageModel(String imageModel) { + this.imageModel = imageModel; + } + + public String getImagePrompt() { + return imagePrompt; + } + + public void setImagePrompt(String imagePrompt) { + this.imagePrompt = imagePrompt; + } + + public String getImageSize() { + return imageSize; + } + + public void setImageSize(String imageSize) { + this.imageSize = imageSize; + } + + public String getImageQuality() { + return imageQuality; + } + + public void setImageQuality(String imageQuality) { + this.imageQuality = imageQuality; + } + + public String getImageResponseFormat() { + return imageResponseFormat; + } + + public void setImageResponseFormat(String imageResponseFormat) { + this.imageResponseFormat = imageResponseFormat; + } + + public Integer getImageCount() { + return imageCount; + } + + public void setImageCount(Integer imageCount) { + this.imageCount = imageCount; + } + + public String getImageBackground() { + return imageBackground; + } + + public void setImageBackground(String imageBackground) { + this.imageBackground = imageBackground; + } + + public String getImageOutputFormat() { + return imageOutputFormat; + } + + public void setImageOutputFormat(String imageOutputFormat) { + this.imageOutputFormat = imageOutputFormat; + } + + public Integer getImageOutputCompression() { + return imageOutputCompression; + } + + public void setImageOutputCompression(Integer imageOutputCompression) { + this.imageOutputCompression = imageOutputCompression; + } + + public String getImageStyle() { + return imageStyle; + } + + public void setImageStyle(String imageStyle) { + this.imageStyle = imageStyle; + } + + public String getImageModeration() { + return imageModeration; + } + + public void setImageModeration(String imageModeration) { + this.imageModeration = imageModeration; + } + + public String getImageInputFidelity() { + return imageInputFidelity; + } + + public void setImageInputFidelity(String imageInputFidelity) { + this.imageInputFidelity = imageInputFidelity; + } + public OpenAIConfiguration copy() { try { return (OpenAIConfiguration) clone(); diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java index 3315c65be9e63..a672ec53d26f2 100644 --- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java +++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java @@ -104,6 +104,9 @@ public final class OpenAIConstants { @Metadata(description = "The complete OpenAI moderation response object", javaType = "com.openai.models.moderations.ModerationCreateResponse") public static final String MODERATION_RESPONSE = "CamelOpenAIModerationResponse"; + @Metadata(description = "The complete OpenAI image generation or edit response object", + javaType = "com.openai.models.images.ImagesResponse") + public static final String IMAGE_RESPONSE = "CamelOpenAIImageResponse"; // Embeddings Input Headers @Metadata(description = "The model to use for embeddings", javaType = "String") @@ -199,6 +202,76 @@ public final class OpenAIConstants { javaType = "String") public static final String SPEECH_INSTRUCTIONS = "CamelOpenAISpeechInstructions"; + // Image Generation/Edit Input Headers + @Metadata(description = "The model to use for image generation or editing (e.g., gpt-image-1, dall-e-3, dall-e-2)", + javaType = "String") + public static final String IMAGE_MODEL = "CamelOpenAIImageModel"; + @Metadata(description = "The prompt describing the image to generate, or the edit to apply. Takes precedence over " + + "the imagePrompt endpoint option and, for image-generation, over the message body", + javaType = "String") + public static final String IMAGE_PROMPT = "CamelOpenAIImagePrompt"; + @Metadata(description = "The size of the generated image (e.g., 1024x1024, 1536x1024, auto)", javaType = "String") + public static final String IMAGE_SIZE = "CamelOpenAIImageSize"; + @Metadata(description = "The quality of the generated image (auto, high, medium, low for GPT image models; " + + "hd, standard for dall-e-3; standard for dall-e-2)", + javaType = "String") + public static final String IMAGE_QUALITY = "CamelOpenAIImageQuality"; + @Metadata(description = "The response format of the generated image (url or b64_json). Only supported by " + + "dall-e-2 and dall-e-3; GPT image models always return base64", + javaType = "String") + public static final String IMAGE_RESPONSE_FORMAT = "CamelOpenAIImageResponseFormat"; + @Metadata(description = "The number of images to generate", javaType = "Integer") + public static final String IMAGE_COUNT = "CamelOpenAIImageCount"; + @Metadata(description = "The background of the generated image (transparent, opaque, auto). " + + "Only supported by GPT image models", + javaType = "String") + public static final String IMAGE_BACKGROUND = "CamelOpenAIImageBackground"; + @Metadata(description = "The output format of the generated image (png, jpeg, webp). " + + "Only supported by GPT image models", + javaType = "String") + public static final String IMAGE_OUTPUT_FORMAT = "CamelOpenAIImageOutputFormat"; + @Metadata(description = "The compression level (0-100) for the webp or jpeg output formats. " + + "Only supported by GPT image models", + javaType = "Integer") + public static final String IMAGE_OUTPUT_COMPRESSION = "CamelOpenAIImageOutputCompression"; + @Metadata(description = "The style of the generated image (vivid or natural). Only supported by dall-e-3", + javaType = "String") + public static final String IMAGE_STYLE = "CamelOpenAIImageStyle"; + @Metadata(description = "The content moderation level for image generation (low or auto). " + + "Only supported by GPT image models", + javaType = "String") + public static final String IMAGE_MODERATION = "CamelOpenAIImageModeration"; + @Metadata(description = "How closely the edit must match the style and features of the input image (high or low). " + + "Only supported by the image-edit operation on gpt-image-1 and gpt-image-1.5", + javaType = "String") + public static final String IMAGE_INPUT_FIDELITY = "CamelOpenAIImageInputFidelity"; + @Metadata(description = "An optional PNG mask for the image-edit operation, where the fully transparent areas " + + "indicate where the image should be edited", + javaType = "byte[], java.io.File, java.nio.file.Path or java.io.InputStream") + public static final String IMAGE_MASK = "CamelOpenAIImageMask"; + + // Image Generation/Edit Output Headers + @Metadata(description = "The number of images returned in the response", javaType = "Integer") + public static final String IMAGE_RESULT_COUNT = "CamelOpenAIImageResultCount"; + @Metadata(description = "The prompt as revised by the model, when a single image is returned (dall-e-3)", + javaType = "String") + public static final String IMAGE_REVISED_PROMPT = "CamelOpenAIImageRevisedPrompt"; + @Metadata(description = "The prompts as revised by the model, one entry per returned image (dall-e-3)", + javaType = "java.util.List") + public static final String IMAGE_REVISED_PROMPTS = "CamelOpenAIImageRevisedPrompts"; + @Metadata(description = "The number of input tokens billed for the image request. " + + "Only reported by GPT image models", + javaType = "Long") + public static final String IMAGE_INPUT_TOKENS = "CamelOpenAIImageInputTokens"; + @Metadata(description = "The number of output tokens billed for the image request. " + + "Only reported by GPT image models", + javaType = "Long") + public static final String IMAGE_OUTPUT_TOKENS = "CamelOpenAIImageOutputTokens"; + @Metadata(description = "The total number of tokens billed for the image request. " + + "Only reported by GPT image models", + javaType = "Long") + public static final String IMAGE_TOTAL_TOKENS = "CamelOpenAIImageTotalTokens"; + private OpenAIConstants() { // Utility class } 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 8210107c16329..7d05f83c47a37 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 @@ -85,7 +85,8 @@ public class OpenAIEndpoint extends DefaultEndpoint { @UriPath @Metadata(required = true, description = "The operation to perform: 'chat-completion', 'responses', 'embeddings', 'tool-execution', " - + "'audio-transcription', 'audio-translation', 'audio-speech', or 'moderation'") + + "'audio-transcription', 'audio-translation', 'audio-speech', 'moderation', " + + "'image-generation', or 'image-edit'") private OpenAIOperations operation; @UriParam @@ -126,6 +127,8 @@ public Producer createProducer() throws Exception { case audioTranslation -> new OpenAIAudioTranslationProducer(this); case audioSpeech -> new OpenAIAudioSpeechProducer(this); case moderation -> new OpenAIModerationProducer(this); + case imageGeneration -> new OpenAIImageGenerationProducer(this); + case imageEdit -> new OpenAIImageEditProducer(this); }; } diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageEditProducer.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageEditProducer.java new file mode 100644 index 0000000000000..a0f60bfa71bb9 --- /dev/null +++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageEditProducer.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.openai; + +import java.io.InputStream; +import java.util.List; + +import com.openai.core.MultipartField; +import com.openai.models.images.ImageEditParams; +import com.openai.models.images.ImagesResponse; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.support.DefaultProducer; +import org.apache.camel.util.ObjectHelper; + +/** + * OpenAI producer for image editing. The message body carries the image (or a {@link List} of images for the models + * that accept several reference images) and the prompt describes the edit. The produced body is the edited image as a + * {@code byte[]}, or its URL as a {@code String} when the model is asked for the {@code url} response format. + */ +public class OpenAIImageEditProducer extends DefaultProducer { + + public OpenAIImageEditProducer(OpenAIEndpoint endpoint) { + super(endpoint); + } + + @Override + public OpenAIEndpoint getEndpoint() { + return (OpenAIEndpoint) super.getEndpoint(); + } + + @Override + public void process(Exchange exchange) throws Exception { + OpenAIConfiguration config = getEndpoint().getConfiguration(); + Message in = exchange.getIn(); + + String model = OpenAIImageSupport.resolveModel(in, config, "image-edit"); + String prompt = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_PROMPT, config.getImagePrompt(), + String.class); + if (ObjectHelper.isEmpty(prompt)) { + // the body carries the image, so unlike image-generation there is no body to fall back on + throw new IllegalArgumentException( + "The edit prompt must be specified via the imagePrompt parameter or the " + + OpenAIConstants.IMAGE_PROMPT + + " header, because the message body carries the image to edit"); + } + + String outputFormat = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_OUTPUT_FORMAT, + config.getImageOutputFormat(), String.class); + + List images = OpenAIImageSupport.resolveImages(in); + InputStream mask = null; + + try { + mask = OpenAIImageSupport.resolveMask(in); + + ImageEditParams.Builder params = ImageEditParams.builder() + .model(model) + .prompt(prompt) + .image(imageField(in, images)); + + if (mask != null) { + // the API documents the mask as a PNG, so the part is always declared as one + params.mask(MultipartField. builder() + .value(mask) + .filename("mask.png") + .contentType("image/png") + .build()); + } + + String size = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_SIZE, config.getImageSize(), + String.class); + if (ObjectHelper.isNotEmpty(size)) { + params.size(size); + } + + String quality = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_QUALITY, + config.getImageQuality(), String.class); + if (ObjectHelper.isNotEmpty(quality)) { + params.quality(ImageEditParams.Quality.of(quality)); + } + + // only dall-e-2 accepts response_format on this endpoint, so it is only sent when the route asked for it + String responseFormat = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_RESPONSE_FORMAT, + config.getImageResponseFormat(), String.class); + if (ObjectHelper.isNotEmpty(responseFormat)) { + params.responseFormat(ImageEditParams.ResponseFormat.of(responseFormat)); + } + + Integer count = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_COUNT, config.getImageCount(), + Integer.class); + if (count != null) { + params.n(count.longValue()); + } + + String background = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_BACKGROUND, + config.getImageBackground(), String.class); + if (ObjectHelper.isNotEmpty(background)) { + params.background(ImageEditParams.Background.of(background)); + } + + if (ObjectHelper.isNotEmpty(outputFormat)) { + params.outputFormat(ImageEditParams.OutputFormat.of(outputFormat)); + } + + Integer outputCompression = OpenAIImageSupport.resolveParameter(in, + OpenAIConstants.IMAGE_OUTPUT_COMPRESSION, config.getImageOutputCompression(), Integer.class); + if (outputCompression != null) { + params.outputCompression(outputCompression.longValue()); + } + + String inputFidelity = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_INPUT_FIDELITY, + config.getImageInputFidelity(), String.class); + if (ObjectHelper.isNotEmpty(inputFidelity)) { + params.inputFidelity(ImageEditParams.InputFidelity.of(inputFidelity)); + } + + ImagesResponse response = getEndpoint().getClient().images().edit(params.build()); + + OpenAIImageSupport.applyResponse(exchange, response, outputFormat, config.isStoreFullResponse()); + } finally { + images.forEach(OpenAIImageSupport::closeQuietly); + OpenAIImageSupport.closeQuietly(mask); + } + } + + private static MultipartField imageField(Message in, List images) { + ImageEditParams.Image value = images.size() == 1 + ? ImageEditParams.Image.ofInputStream(images.get(0)) + : ImageEditParams.Image.ofInputStreams(images); + + // the API rejects the upload on the content type of the part, which the SDK leaves as text/plain unless + // it is declared here; a raw byte[] body carries neither that nor a file name + String mimeType = OpenAIImageSupport.resolveImageMimeType(in); + return MultipartField. builder() + .value(value) + .filename(OpenAIImageSupport.filenameFor(in, "image", mimeType)) + .contentType(mimeType) + .build(); + } +} diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageGenerationProducer.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageGenerationProducer.java new file mode 100644 index 0000000000000..bb72458c3c9a8 --- /dev/null +++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageGenerationProducer.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.openai; + +import com.openai.models.images.ImageGenerateParams; +import com.openai.models.images.ImagesResponse; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.support.DefaultProducer; +import org.apache.camel.util.ObjectHelper; + +/** + * OpenAI producer for image generation. The message body is the prompt and the produced body is the generated image as + * a {@code byte[]}, or its URL as a {@code String} when the model is asked for the {@code url} response format. + */ +public class OpenAIImageGenerationProducer extends DefaultProducer { + + public OpenAIImageGenerationProducer(OpenAIEndpoint endpoint) { + super(endpoint); + } + + @Override + public OpenAIEndpoint getEndpoint() { + return (OpenAIEndpoint) super.getEndpoint(); + } + + @Override + public void process(Exchange exchange) throws Exception { + OpenAIConfiguration config = getEndpoint().getConfiguration(); + Message in = exchange.getIn(); + + String model = OpenAIImageSupport.resolveModel(in, config, "image-generation"); + String prompt = resolvePrompt(in, config); + + ImageGenerateParams.Builder params = ImageGenerateParams.builder() + .model(model) + .prompt(prompt); + + String size = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_SIZE, config.getImageSize(), + String.class); + if (ObjectHelper.isNotEmpty(size)) { + params.size(size); + } + + String quality = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_QUALITY, config.getImageQuality(), + String.class); + if (ObjectHelper.isNotEmpty(quality)) { + params.quality(ImageGenerateParams.Quality.of(quality)); + } + + // the GPT image models always return base64 and reject response_format outright, + // so it is only sent when the route asked for it + String responseFormat = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_RESPONSE_FORMAT, + config.getImageResponseFormat(), String.class); + if (ObjectHelper.isNotEmpty(responseFormat)) { + params.responseFormat(ImageGenerateParams.ResponseFormat.of(responseFormat)); + } + + Integer count = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_COUNT, config.getImageCount(), + Integer.class); + if (count != null) { + params.n(count.longValue()); + } + + String background = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_BACKGROUND, + config.getImageBackground(), String.class); + if (ObjectHelper.isNotEmpty(background)) { + params.background(ImageGenerateParams.Background.of(background)); + } + + String outputFormat = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_OUTPUT_FORMAT, + config.getImageOutputFormat(), String.class); + if (ObjectHelper.isNotEmpty(outputFormat)) { + params.outputFormat(ImageGenerateParams.OutputFormat.of(outputFormat)); + } + + Integer outputCompression = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_OUTPUT_COMPRESSION, + config.getImageOutputCompression(), Integer.class); + if (outputCompression != null) { + params.outputCompression(outputCompression.longValue()); + } + + String style = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_STYLE, config.getImageStyle(), + String.class); + if (ObjectHelper.isNotEmpty(style)) { + params.style(ImageGenerateParams.Style.of(style)); + } + + String moderation = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_MODERATION, + config.getImageModeration(), String.class); + if (ObjectHelper.isNotEmpty(moderation)) { + params.moderation(ImageGenerateParams.Moderation.of(moderation)); + } + + ImagesResponse response = getEndpoint().getClient().images().generate(params.build()); + + OpenAIImageSupport.applyResponse(exchange, response, outputFormat, config.isStoreFullResponse()); + } + + private static String resolvePrompt(Message in, OpenAIConfiguration config) { + String prompt = OpenAIImageSupport.resolveParameter(in, OpenAIConstants.IMAGE_PROMPT, config.getImagePrompt(), + String.class); + if (ObjectHelper.isEmpty(prompt)) { + prompt = in.getBody(String.class); + } + if (ObjectHelper.isEmpty(prompt)) { + throw new IllegalArgumentException( + "The message body must contain the image prompt, or it must be set via the imagePrompt parameter " + + "or the " + OpenAIConstants.IMAGE_PROMPT + " header"); + } + return prompt; + } +} diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageSupport.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageSupport.java new file mode 100644 index 0000000000000..352ca5ecca2dc --- /dev/null +++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIImageSupport.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.openai; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Locale; + +import com.openai.models.images.Image; +import com.openai.models.images.ImagesResponse; +import org.apache.camel.CamelExchangeException; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.WrappedFile; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Shared helpers for the {@code image-generation} and {@code image-edit} operations. + */ +final class OpenAIImageSupport { + + private static final Logger LOG = LoggerFactory.getLogger(OpenAIImageSupport.class); + + private static final String DEFAULT_OUTPUT_FORMAT = "png"; + private static final String DEFAULT_IMAGE_MIME_TYPE = "image/png"; + + private OpenAIImageSupport() { + } + + /** + * Resolves a value from the message header, falling back to the endpoint configuration. + */ + static T resolveParameter(Message message, String headerName, T defaultValue, Class type) { + T headerValue = message.getHeader(headerName, type); + return ObjectHelper.isNotEmpty(headerValue) ? headerValue : defaultValue; + } + + static String resolveModel(Message in, OpenAIConfiguration config, String operation) { + String model = resolveParameter(in, OpenAIConstants.IMAGE_MODEL, config.getImageModel(), String.class); + if (ObjectHelper.isEmpty(model)) { + throw new IllegalArgumentException( + "Image model must be specified via the imageModel parameter or the " + + OpenAIConstants.IMAGE_MODEL + " header for the " + operation + + " operation. The model determines which of the other image options " + + "are accepted by the API."); + } + return model; + } + + /** + * Reads the image data carried by the message body of an {@code image-edit} exchange. A {@link List} body is sent + * as multiple reference images, which the GPT image models accept. + */ + static List resolveImages(Message in) throws Exception { + Object body = in.getBody(); + if (body instanceof List list) { + if (list.isEmpty()) { + throw new IllegalArgumentException("The message body for image-edit must not be an empty list"); + } + List streams = new ArrayList<>(list.size()); + for (Object item : list) { + try { + streams.add(toInputStream(in, item, "image")); + } catch (Exception e) { + // the caller only closes the streams it was handed, so a partial list must not leak + streams.forEach(OpenAIImageSupport::closeQuietly); + throw e; + } + } + return streams; + } + return List.of(toInputStream(in, body, "image")); + } + + static void closeQuietly(InputStream stream) { + if (stream == null) { + return; + } + try { + stream.close(); + } catch (IOException e) { + LOG.debug("Could not close the image input stream", e); + } + } + + static InputStream resolveMask(Message in) throws Exception { + Object mask = in.getHeader(OpenAIConstants.IMAGE_MASK); + if (mask == null) { + return null; + } + return toInputStream(in, mask, "mask"); + } + + private static InputStream toInputStream(Message in, Object value, String what) throws Exception { + Object source = value instanceof WrappedFile wrappedFile ? wrappedFile.getFile() : value; + + if (source instanceof File file) { + return Files.newInputStream(file.toPath()); + } else if (source instanceof Path path) { + return Files.newInputStream(path); + } else if (source instanceof byte[] bytes) { + return new ByteArrayInputStream(bytes); + } else if (source instanceof InputStream inputStream) { + return inputStream; + } + + InputStream converted = in.getExchange().getContext().getTypeConverter() + .tryConvertTo(InputStream.class, in.getExchange(), source); + if (converted == null) { + throw new IllegalArgumentException( + "Unsupported " + what + " type for the image-edit operation: " + + (source != null ? source.getClass().getName() : "null") + + ". Supported: File, Path, InputStream, byte[]"); + } + return converted; + } + + /** + * Resolves the MIME type of an uploaded image. The API rejects the upload on the content type of the multipart + * part, not on the file name, and only accepts {@code image/png}, {@code image/jpeg} and {@code image/webp}, so + * anything else falls back to PNG rather than being passed through. + */ + static String resolveImageMimeType(Message in) { + String mime = MimeTypeHelper.resolveForBinary(in); + if (mime == null) { + mime = mimeTypeOfFileName(fileNameOfBody(in.getBody())); + } + return isSupportedImageMimeType(mime) ? mime.toLowerCase(Locale.ROOT) : DEFAULT_IMAGE_MIME_TYPE; + } + + /** + * Builds the multipart file name for an uploaded image, keeping its extension consistent with the content type. + */ + static String filenameFor(Message in, String fallbackBaseName, String mimeType) { + String fileName = in.getHeader(Exchange.FILE_NAME_ONLY, String.class); + if (ObjectHelper.isEmpty(fileName)) { + fileName = fileNameOfBody(in.getBody()); + } + if (ObjectHelper.isNotEmpty(fileName) && fileName.indexOf('.') > 0) { + return fileName; + } + return fallbackBaseName + "." + extensionFor(mimeType); + } + + private static boolean isSupportedImageMimeType(String mimeType) { + if (mimeType == null) { + return false; + } + return switch (mimeType.toLowerCase(Locale.ROOT)) { + case "image/png", "image/jpeg", "image/webp" -> true; + default -> false; + }; + } + + private static String mimeTypeOfFileName(String fileName) { + if (fileName == null) { + return null; + } + String name = fileName.toLowerCase(Locale.ROOT); + if (name.endsWith(".jpg") || name.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (name.endsWith(".webp")) { + return "image/webp"; + } else if (name.endsWith(".png")) { + return "image/png"; + } + return null; + } + + private static String fileNameOfBody(Object body) { + Object source = body instanceof WrappedFile wrappedFile ? wrappedFile.getFile() : body; + if (source instanceof File file) { + return file.getName(); + } else if (source instanceof Path path) { + return path.getFileName().toString(); + } + return null; + } + + private static String extensionFor(String mimeType) { + if (mimeType == null) { + return DEFAULT_OUTPUT_FORMAT; + } + return switch (mimeType.toLowerCase(Locale.ROOT)) { + case "image/jpeg", "image/jpg" -> "jpg"; + case "image/webp" -> "webp"; + default -> DEFAULT_OUTPUT_FORMAT; + }; + } + + /** + * Maps the returned images onto the exchange. A single image becomes the body directly and multiple images become a + * {@link List}, so the common case does not force routes to unwrap a one-element list. + */ + static void applyResponse( + Exchange exchange, ImagesResponse response, String outputFormat, boolean storeFullResponse) + throws CamelExchangeException { + + List images = response.data().orElse(List.of()); + if (images.isEmpty()) { + throw new CamelExchangeException("The image response contained no images", exchange); + } + + List results = new ArrayList<>(images.size()); + List revisedPrompts = new ArrayList<>(images.size()); + boolean binary = false; + + for (Image image : images) { + String b64 = image.b64Json().orElse(null); + if (ObjectHelper.isNotEmpty(b64)) { + // a MIME decoder is used because some OpenAI-compatible servers wrap the payload in line breaks + results.add(Base64.getMimeDecoder().decode(b64)); + binary = true; + } else { + String url = image.url().orElse(null); + if (ObjectHelper.isEmpty(url)) { + throw new CamelExchangeException( + "The image response contained an entry with neither b64_json nor url", exchange); + } + results.add(url); + } + revisedPrompts.add(image.revisedPrompt().orElse(null)); + } + + if (storeFullResponse) { + exchange.setProperty(OpenAIConstants.IMAGE_RESPONSE, response); + } + + Message out = exchange.getMessage(); + out.setBody(results.size() == 1 ? results.get(0) : results); + out.setHeader(OpenAIConstants.IMAGE_RESULT_COUNT, results.size()); + + if (binary) { + out.setHeader(Exchange.CONTENT_TYPE, + contentTypeFor(response.outputFormat().map(ImagesResponse.OutputFormat::asString) + .orElse(outputFormat))); + } + + if (revisedPrompts.stream().anyMatch(ObjectHelper::isNotEmpty)) { + out.setHeader(OpenAIConstants.IMAGE_REVISED_PROMPTS, revisedPrompts); + if (revisedPrompts.size() == 1) { + out.setHeader(OpenAIConstants.IMAGE_REVISED_PROMPT, revisedPrompts.get(0)); + } + } + + response.usage().ifPresent(usage -> { + out.setHeader(OpenAIConstants.IMAGE_INPUT_TOKENS, usage.inputTokens()); + out.setHeader(OpenAIConstants.IMAGE_OUTPUT_TOKENS, usage.outputTokens()); + out.setHeader(OpenAIConstants.IMAGE_TOTAL_TOKENS, usage.totalTokens()); + }); + } + + private static String contentTypeFor(String outputFormat) { + if (ObjectHelper.isEmpty(outputFormat)) { + return "image/png"; + } + return switch (outputFormat.toLowerCase(Locale.ROOT)) { + case "jpeg", "jpg" -> "image/jpeg"; + case "webp" -> "image/webp"; + default -> "image/png"; + }; + } +} diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIOperations.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIOperations.java index 6679e355f6d1a..f6abfb5ee401a 100644 --- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIOperations.java +++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIOperations.java @@ -25,7 +25,9 @@ public enum OpenAIOperations { audioTranscription("audio-transcription"), audioTranslation("audio-translation"), audioSpeech("audio-speech"), - moderation("moderation"); + moderation("moderation"), + imageGeneration("image-generation"), + imageEdit("image-edit"); private final String value; @@ -42,7 +44,8 @@ public static OpenAIOperations fromValue(String value) { throw new IllegalArgumentException( "Unknown operation: " + value + ". Supported: chat-completion, responses, embeddings, tool-execution, " - + "audio-transcription, audio-translation, audio-speech, moderation"); + + "audio-transcription, audio-translation, audio-speech, moderation, " + + "image-generation, image-edit"); } public String getValue() { diff --git a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageEditMockTest.java b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageEditMockTest.java new file mode 100644 index 0000000000000..43ac640c333e0 --- /dev/null +++ b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageEditMockTest.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.openai; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.infra.openai.mock.OpenAIMock; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenAIImageEditMockTest extends CamelTestSupport { + + private static final byte[] SOURCE_IMAGE = "FAKE-PNG-SOURCE".getBytes(StandardCharsets.UTF_8); + private static final byte[] SECOND_SOURCE_IMAGE = "FAKE-PNG-SOURCE-TWO".getBytes(StandardCharsets.UTF_8); + private static final byte[] MASK_IMAGE = "FAKE-PNG-MASK".getBytes(StandardCharsets.UTF_8); + private static final byte[] EDITED_IMAGE = "FAKE-PNG-EDITED".getBytes(StandardCharsets.UTF_8); + + @TempDir + File tempDir; + + private final AtomicReference lastRequest = new AtomicReference<>(); + + @RegisterExtension + public OpenAIMock openAIMock = new OpenAIMock().builder() + .whenImageEdit() + .replyWithImage(EDITED_IMAGE) + .assertImageRequest(lastRequest::set) + .end() + .build(); + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:edit") + .to("openai:image-edit?imageModel=gpt-image-1&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:edit-with-prompt-option") + .to("openai:image-edit?imageModel=gpt-image-1&imagePrompt=Add a red SALE banner" + + "&imageInputFidelity=high&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:edit-no-model") + .to("openai:image-edit?apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + } + }; + } + + @Test + void testEditByteArrayBody() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(SOURCE_IMAGE); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(EDITED_IMAGE); + assertThat(result.getMessage().getHeader(Exchange.CONTENT_TYPE)).isEqualTo("image/png"); + assertThat(fileParts()).isEqualTo(1); + assertThat(requestBody()).contains("Add a red SALE banner"); + // the API rejects the upload on the content type of the part, not on the file name + assertThat(contentTypeParts()).containsExactly("image/png"); + } + + @Test + void testEditFileBody() throws Exception { + File source = new File(tempDir, "product.png"); + Files.write(source.toPath(), SOURCE_IMAGE); + + Exchange result = template.request("direct:edit-with-prompt-option", e -> e.getIn().setBody(source)); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(EDITED_IMAGE); + assertThat(fileParts()).isEqualTo(1); + assertThat(requestBody()).contains("product.png").contains("input_fidelity"); + assertThat(contentTypeParts()).containsExactly("image/png"); + } + + @Test + void testEditInputStreamBody() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(new ByteArrayInputStream(SOURCE_IMAGE)); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(EDITED_IMAGE); + assertThat(fileParts()).isEqualTo(1); + } + + @Test + void testEditWithSeveralReferenceImages() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(List.of(SOURCE_IMAGE, SECOND_SOURCE_IMAGE)); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Combine both products into one scene"); + }); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(EDITED_IMAGE); + assertThat(fileParts()).isEqualTo(2); + } + + @Test + void testEditWithMask() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(SOURCE_IMAGE); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Replace the masked area with a blue sky"); + e.getIn().setHeader(OpenAIConstants.IMAGE_MASK, MASK_IMAGE); + }); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(EDITED_IMAGE); + // one part for the image and one for the mask + assertThat(fileParts()).isEqualTo(2); + assertThat(requestBody()).contains("mask.png"); + assertThat(contentTypeParts()).containsExactly("image/png", "image/png"); + } + + @Test + void testFilenameComesFromTheFileNameHeader() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(SOURCE_IMAGE); + e.getIn().setHeader(Exchange.FILE_NAME_ONLY, "banner.webp"); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()).isNull(); + assertThat(requestBody()).contains("banner.webp"); + } + + @Test + void testFilenameExtensionComesFromTheContentType() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(SOURCE_IMAGE); + e.getIn().setHeader(OpenAIConstants.MEDIA_TYPE, "image/webp"); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()).isNull(); + assertThat(requestBody()).contains("image.webp"); + assertThat(contentTypeParts()).containsExactly("image/webp"); + } + + @Test + void testJpegFileBodyKeepsItsContentType() throws Exception { + File source = new File(tempDir, "product.jpg"); + Files.write(source.toPath(), SOURCE_IMAGE); + + Exchange result = template.request("direct:edit-with-prompt-option", e -> e.getIn().setBody(source)); + + assertThat(result.getException()).isNull(); + assertThat(contentTypeParts()).containsExactly("image/jpeg"); + } + + @Test + void testNonImageContentTypeFallsBackToPng() { + // the API only accepts png, jpeg and webp, so anything else must not be passed through + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(SOURCE_IMAGE); + e.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain; charset=utf-8"); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()).isNull(); + assertThat(contentTypeParts()).containsExactly("image/png"); + } + + @Test + void testMissingPrompt() { + Exchange result = template.request("direct:edit", e -> e.getIn().setBody(SOURCE_IMAGE)); + + assertThat(result.getException()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("edit prompt must be specified"); + } + + @Test + void testMissingModel() { + Exchange result = template.request("direct:edit-no-model", e -> { + e.getIn().setBody(SOURCE_IMAGE); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Image model must be specified"); + } + + @Test + void testEmptyImageList() { + Exchange result = template.request("direct:edit", e -> { + e.getIn().setBody(List.of()); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "Add a red SALE banner"); + }); + + assertThat(result.getException()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("must not be an empty list"); + } + + private String requestBody() { + assertThat(lastRequest.get()).isNotNull(); + return new String(lastRequest.get(), StandardCharsets.ISO_8859_1); + } + + private List contentTypeParts() { + return Pattern.compile("Content-Type: (image/[a-z]+)").matcher(requestBody()) + .results() + .map(match -> match.group(1)) + .toList(); + } + + private int fileParts() { + String body = requestBody(); + int count = 0; + int index = body.indexOf("filename="); + while (index >= 0) { + count++; + index = body.indexOf("filename=", index + 1); + } + return count; + } +} diff --git a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageGenerationMockTest.java b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageGenerationMockTest.java new file mode 100644 index 0000000000000..ae8f156f204fd --- /dev/null +++ b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIImageGenerationMockTest.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.openai; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import com.openai.models.images.ImagesResponse; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.infra.openai.mock.OpenAIMock; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenAIImageGenerationMockTest extends CamelTestSupport { + + private static final byte[] FIRST_IMAGE = "FAKE-PNG-IMAGE-ONE".getBytes(StandardCharsets.UTF_8); + private static final byte[] SECOND_IMAGE = "FAKE-PNG-IMAGE-TWO".getBytes(StandardCharsets.UTF_8); + + @RegisterExtension + public OpenAIMock openAIMock = new OpenAIMock().builder() + .whenImageGeneration("A red bicycle") + .replyWithImage(FIRST_IMAGE) + .withImageUsage(11, 22) + .end() + .whenImageGeneration("Two cats") + .replyWithImage(FIRST_IMAGE) + .replyWithImage(SECOND_IMAGE) + .end() + .whenImageGeneration("A hosted logo") + .replyWithImageUrl("https://example.org/generated.png") + .end() + .whenImageGeneration("A cat in a hat") + .replyWithImage(FIRST_IMAGE) + .withRevisedPrompt("A photorealistic cat wearing a top hat") + .end() + .whenImageGeneration("A webp banner") + .replyWithImage(FIRST_IMAGE) + .withImageOutputFormat("webp") + .end() + .whenImageGeneration("A jpeg banner") + .replyWithImage(FIRST_IMAGE) + .end() + .build(); + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:generate") + .to("openai:image-generation?imageModel=gpt-image-1&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:generate-no-model") + .to("openai:image-generation?apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:generate-url") + .to("openai:image-generation?imageModel=dall-e-3&imageResponseFormat=url&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:generate-jpeg") + .to("openai:image-generation?imageModel=gpt-image-1&imageOutputFormat=jpeg&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:generate-with-prompt-option") + .to("openai:image-generation?imageModel=gpt-image-1&imagePrompt=A red bicycle&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + + from("direct:generate-store-response") + .to("openai:image-generation?imageModel=gpt-image-1&storeFullResponse=true&apiKey=dummy&baseUrl=" + + openAIMock.getBaseUrl() + "/v1"); + } + }; + } + + @Test + void testSingleImageBecomesByteArrayBody() { + Exchange result = template.request("direct:generate", e -> e.getIn().setBody("A red bicycle")); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody()).isInstanceOf(byte[].class); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(FIRST_IMAGE); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_RESULT_COUNT)).isEqualTo(1); + assertThat(result.getMessage().getHeader(Exchange.CONTENT_TYPE)).isEqualTo("image/png"); + } + + @Test + void testUsageHeaders() { + Exchange result = template.request("direct:generate", e -> e.getIn().setBody("A red bicycle")); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_INPUT_TOKENS)).isEqualTo(11L); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_OUTPUT_TOKENS)).isEqualTo(22L); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_TOTAL_TOKENS)).isEqualTo(33L); + } + + @Test + void testMultipleImagesBecomeListBody() { + Exchange result = template.request("direct:generate", e -> { + e.getIn().setBody("Two cats"); + e.getIn().setHeader(OpenAIConstants.IMAGE_COUNT, 2); + }); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody()).isInstanceOf(List.class); + + List images = result.getMessage().getBody(List.class); + assertThat(images).hasSize(2); + assertThat((byte[]) images.get(0)).isEqualTo(FIRST_IMAGE); + assertThat((byte[]) images.get(1)).isEqualTo(SECOND_IMAGE); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_RESULT_COUNT)).isEqualTo(2); + } + + @Test + void testUrlResponseFormatBecomesStringBody() { + Exchange result = template.request("direct:generate-url", e -> e.getIn().setBody("A hosted logo")); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody()).isEqualTo("https://example.org/generated.png"); + // no bytes were returned, so the exchange must not claim an image content type + assertThat(result.getMessage().getHeader(Exchange.CONTENT_TYPE)).isNull(); + } + + @Test + void testRevisedPromptHeaders() { + Exchange result = template.request("direct:generate", e -> e.getIn().setBody("A cat in a hat")); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_REVISED_PROMPT)) + .isEqualTo("A photorealistic cat wearing a top hat"); + assertThat(result.getMessage().getHeader(OpenAIConstants.IMAGE_REVISED_PROMPTS, List.class)) + .containsExactly("A photorealistic cat wearing a top hat"); + } + + @Test + void testContentTypeFromResponseOutputFormat() { + Exchange result = template.request("direct:generate", e -> e.getIn().setBody("A webp banner")); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getHeader(Exchange.CONTENT_TYPE)).isEqualTo("image/webp"); + } + + @Test + void testContentTypeFallsBackToRequestedOutputFormat() { + // the reply carries no output_format, so the format asked for in the request is used instead + Exchange result = template.request("direct:generate-jpeg", e -> e.getIn().setBody("A jpeg banner")); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getHeader(Exchange.CONTENT_TYPE)).isEqualTo("image/jpeg"); + } + + @Test + void testPromptFromEndpointOption() { + Exchange result = template.request("direct:generate-with-prompt-option", e -> e.getIn().setBody(null)); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(FIRST_IMAGE); + } + + @Test + void testPromptHeaderTakesPrecedenceOverBody() { + Exchange result = template.request("direct:generate", e -> { + e.getIn().setBody("ignored body"); + e.getIn().setHeader(OpenAIConstants.IMAGE_PROMPT, "A red bicycle"); + }); + + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(byte[].class)).isEqualTo(FIRST_IMAGE); + } + + @Test + void testStoreFullResponse() { + Exchange result = template.request("direct:generate-store-response", e -> e.getIn().setBody("A red bicycle")); + + assertThat(result.getException()).isNull(); + assertThat(result.getProperty(OpenAIConstants.IMAGE_RESPONSE)).isInstanceOf(ImagesResponse.class); + } + + @Test + void testMissingModel() { + Exchange result = template.request("direct:generate-no-model", e -> e.getIn().setBody("A red bicycle")); + + assertThat(result.getException()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Image model must be specified"); + } + + @Test + void testMissingPrompt() { + Exchange result = template.request("direct:generate", e -> e.getIn().setBody(null)); + + assertThat(result.getException()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("must contain the image prompt"); + } +} diff --git a/docs/components/modules/others/nav.adoc b/docs/components/modules/others/nav.adoc index bbbfe1b6bf5b7..4c1349afa8423 100644 --- a/docs/components/modules/others/nav.adoc +++ b/docs/components/modules/others/nav.adoc @@ -64,7 +64,7 @@ ** xref:oauth.adoc[Oauth] ** xref:observability-services.adoc[Observability Services] ** xref:openai-providers.adoc[OpenAI - Compatible Providers] -** xref:openai-operations.adoc[OpenAI - Embeddings, Moderation and Audio Operations] +** xref:openai-operations.adoc[OpenAI - Embeddings, Moderation, Audio and Image Operations] ** xref:openai-mcp.adoc[OpenAI - MCP Tool Calling] ** xref:openai-responses.adoc[OpenAI - Responses API Operation] ** xref:openapi-java.adoc[Openapi Java] diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java index 2f6aa940af898..7fdb45ae9f8cf 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java @@ -12600,10 +12600,10 @@ public static OnceEndpointBuilderFactory.OnceEndpointBuilder once(String compone * Path parameter: operation (required) * The operation to perform: 'chat-completion', 'responses', 'embeddings', * 'tool-execution', 'audio-transcription', 'audio-translation', - * 'audio-speech', or 'moderation' - * There are 8 enums and the value can be one of: chat-completion, + * 'audio-speech', 'moderation', 'image-generation', or 'image-edit' + * There are 10 enums and the value can be one of: chat-completion, * responses, embeddings, tool-execution, audio-transcription, - * audio-translation, audio-speech, moderation + * audio-translation, audio-speech, moderation, image-generation, image-edit * * @param path operation * @return the dsl builder @@ -12625,10 +12625,10 @@ public static OpenAIEndpointBuilderFactory.OpenAIEndpointBuilder openai(String p * Path parameter: operation (required) * The operation to perform: 'chat-completion', 'responses', 'embeddings', * 'tool-execution', 'audio-transcription', 'audio-translation', - * 'audio-speech', or 'moderation' - * There are 8 enums and the value can be one of: chat-completion, + * 'audio-speech', 'moderation', 'image-generation', or 'image-edit' + * There are 10 enums and the value can be one of: chat-completion, * responses, embeddings, tool-execution, audio-transcription, - * audio-translation, audio-speech, moderation + * audio-translation, audio-speech, moderation, image-generation, image-edit * * @param componentName to use a custom component name for the endpoint * instead of the default name diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java index db4adc8e19b4a..a0229612e6d87 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java @@ -551,6 +551,231 @@ default OpenAIEndpointBuilder hostedMcpTools(String hostedMcpTools) { doSetProperty("hostedMcpTools", hostedMcpTools); return this; } + /** + * The background of the generated image. Only supported by the GPT + * image models, and a transparent background requires the png or webp + * output format. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageBackground the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageBackground(String imageBackground) { + doSetProperty("imageBackground", imageBackground); + return this; + } + /** + * The number of images to generate, between 1 and 10. dall-e-3 only + * supports 1. + * + * The option is a: java.lang.Integer type. + * + * Group: producer + * + * @param imageCount the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageCount(Integer imageCount) { + doSetProperty("imageCount", imageCount); + return this; + } + /** + * The number of images to generate, between 1 and 10. dall-e-3 only + * supports 1. + * + * The option will be converted to a java.lang.Integer + * type. + * + * Group: producer + * + * @param imageCount the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageCount(String imageCount) { + doSetProperty("imageCount", imageCount); + return this; + } + /** + * How closely the edit must match the style and features of the input + * image. Only supported by the image-edit operation on gpt-image-1 and + * gpt-image-1.5. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageInputFidelity the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageInputFidelity(String imageInputFidelity) { + doSetProperty("imageInputFidelity", imageInputFidelity); + return this; + } + /** + * The model to use for image generation or editing (e.g., gpt-image-1, + * gpt-image-1-mini, gpt-image-1.5, gpt-image-2). Required for the + * image-generation and image-edit operations, because the model + * determines which of the other image options are accepted. The DALL-E + * models are no longer offered by OpenAI, but remain valid values for + * OpenAI-compatible providers. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageModel the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageModel(String imageModel) { + doSetProperty("imageModel", imageModel); + return this; + } + /** + * The content moderation level applied to image generation. Only + * supported by the GPT image models. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageModeration the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageModeration(String imageModeration) { + doSetProperty("imageModeration", imageModeration); + return this; + } + /** + * The compression level from 0 to 100 for the webp and jpeg output + * formats. Only supported by the GPT image models. + * + * The option is a: java.lang.Integer type. + * + * Group: producer + * + * @param imageOutputCompression the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageOutputCompression(Integer imageOutputCompression) { + doSetProperty("imageOutputCompression", imageOutputCompression); + return this; + } + /** + * The compression level from 0 to 100 for the webp and jpeg output + * formats. Only supported by the GPT image models. + * + * The option will be converted to a java.lang.Integer + * type. + * + * Group: producer + * + * @param imageOutputCompression the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageOutputCompression(String imageOutputCompression) { + doSetProperty("imageOutputCompression", imageOutputCompression); + return this; + } + /** + * The output format of the generated image. Only supported by the GPT + * image models, which default to png. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageOutputFormat the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageOutputFormat(String imageOutputFormat) { + doSetProperty("imageOutputFormat", imageOutputFormat); + return this; + } + /** + * The prompt describing the image to generate, or the edit to apply. + * For image-generation the message body is used when this is not set; + * for image-edit the body carries the input image, so the prompt must + * come from this option or from the CamelOpenAIImagePrompt header. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imagePrompt the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imagePrompt(String imagePrompt) { + doSetProperty("imagePrompt", imagePrompt); + return this; + } + /** + * The quality of the generated image. GPT image models accept auto, + * high, medium and low; hd and standard are DALL-E values kept for + * OpenAI-compatible providers. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageQuality the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageQuality(String imageQuality) { + doSetProperty("imageQuality", imageQuality); + return this; + } + /** + * The response format of the generated image. The OpenAI images + * endpoint rejects this option: the GPT image models always return + * base64, and the DALL-E models that used to accept it are no longer + * offered. It is only sent when explicitly set, and is kept for + * OpenAI-compatible providers that still implement the older images + * API. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageResponseFormat the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageResponseFormat(String imageResponseFormat) { + doSetProperty("imageResponseFormat", imageResponseFormat); + return this; + } + /** + * The size of the generated image (e.g., 1024x1024, 1536x1024, + * 1024x1536, auto). The accepted values depend on the model. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageSize the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageSize(String imageSize) { + doSetProperty("imageSize", imageSize); + return this; + } + /** + * The style of the generated image. A dall-e-3 option, so only useful + * with OpenAI-compatible providers. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param imageStyle the value to set + * @return the dsl builder + */ + default OpenAIEndpointBuilder imageStyle(String imageStyle) { + doSetProperty("imageStyle", imageStyle); + return this; + } /** * JSON schema for structured output validation. * @@ -1226,7 +1451,8 @@ default OpenAIEndpointBuilder speechVoice(String speechVoice) { * Store the full SDK response in non-streaming mode: chat-completion * uses exchange property 'CamelOpenAIResponse'; responses uses * 'CamelOpenAIResponsesResponse'; moderation uses - * 'CamelOpenAIModerationResponse'. + * 'CamelOpenAIModerationResponse'; image-generation and image-edit use + * 'CamelOpenAIImageResponse'. * * The option is a: boolean type. * @@ -1244,7 +1470,8 @@ default OpenAIEndpointBuilder storeFullResponse(boolean storeFullResponse) { * Store the full SDK response in non-streaming mode: chat-completion * uses exchange property 'CamelOpenAIResponse'; responses uses * 'CamelOpenAIResponsesResponse'; moderation uses - * 'CamelOpenAIModerationResponse'. + * 'CamelOpenAIModerationResponse'; image-generation and image-edit use + * 'CamelOpenAIImageResponse'. * * The option will be converted to a boolean type. * @@ -1763,10 +1990,12 @@ default OpenAIHeaderNameBuilder openai() { * Path parameter: operation (required) * The operation to perform: 'chat-completion', 'responses', * 'embeddings', 'tool-execution', 'audio-transcription', - * 'audio-translation', 'audio-speech', or 'moderation' - * There are 8 enums and the value can be one of: chat-completion, + * 'audio-translation', 'audio-speech', 'moderation', + * 'image-generation', or 'image-edit' + * There are 10 enums and the value can be one of: chat-completion, * responses, embeddings, tool-execution, audio-transcription, - * audio-translation, audio-speech, moderation + * audio-translation, audio-speech, moderation, image-generation, + * image-edit * * @param path operation * @return the dsl builder @@ -1788,10 +2017,12 @@ default OpenAIEndpointBuilder openai(String path) { * Path parameter: operation (required) * The operation to perform: 'chat-completion', 'responses', * 'embeddings', 'tool-execution', 'audio-transcription', - * 'audio-translation', 'audio-speech', or 'moderation' - * There are 8 enums and the value can be one of: chat-completion, + * 'audio-translation', 'audio-speech', 'moderation', + * 'image-generation', or 'image-edit' + * There are 10 enums and the value can be one of: chat-completion, * responses, embeddings, tool-execution, audio-transcription, - * audio-translation, audio-speech, moderation + * audio-translation, audio-speech, moderation, image-generation, + * image-edit * * @param componentName to use a custom component name for the endpoint * instead of the default name @@ -2189,6 +2420,19 @@ public String openAIResponsesResponse() { public String openAIModerationResponse() { return "CamelOpenAIModerationResponse"; } + /** + * The complete OpenAI image generation or edit response object. + * + * The option is a: {@code com.openai.models.images.ImagesResponse} + * type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageResponse}. + */ + public String openAIImageResponse() { + return "CamelOpenAIImageResponse"; + } /** * The model to use for embeddings. * @@ -2532,6 +2776,254 @@ public String openAISpeechSpeed() { public String openAISpeechInstructions() { return "CamelOpenAISpeechInstructions"; } + /** + * The model to use for image generation or editing (e.g., gpt-image-1, + * dall-e-3, dall-e-2). + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageModel}. + */ + public String openAIImageModel() { + return "CamelOpenAIImageModel"; + } + /** + * The prompt describing the image to generate, or the edit to apply. + * Takes precedence over the imagePrompt endpoint option and, for + * image-generation, over the message body. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImagePrompt}. + */ + public String openAIImagePrompt() { + return "CamelOpenAIImagePrompt"; + } + /** + * The size of the generated image (e.g., 1024x1024, 1536x1024, auto). + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageSize}. + */ + public String openAIImageSize() { + return "CamelOpenAIImageSize"; + } + /** + * The quality of the generated image (auto, high, medium, low for GPT + * image models; hd, standard for dall-e-3; standard for dall-e-2). + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageQuality}. + */ + public String openAIImageQuality() { + return "CamelOpenAIImageQuality"; + } + /** + * The response format of the generated image (url or b64_json). Only + * supported by dall-e-2 and dall-e-3; GPT image models always return + * base64. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageResponseFormat}. + */ + public String openAIImageResponseFormat() { + return "CamelOpenAIImageResponseFormat"; + } + /** + * The number of images to generate. + * + * The option is a: {@code Integer} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageCount}. + */ + public String openAIImageCount() { + return "CamelOpenAIImageCount"; + } + /** + * The background of the generated image (transparent, opaque, auto). + * Only supported by GPT image models. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageBackground}. + */ + public String openAIImageBackground() { + return "CamelOpenAIImageBackground"; + } + /** + * The output format of the generated image (png, jpeg, webp). Only + * supported by GPT image models. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageOutputFormat}. + */ + public String openAIImageOutputFormat() { + return "CamelOpenAIImageOutputFormat"; + } + /** + * The compression level (0-100) for the webp or jpeg output formats. + * Only supported by GPT image models. + * + * The option is a: {@code Integer} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageOutputCompression}. + */ + public String openAIImageOutputCompression() { + return "CamelOpenAIImageOutputCompression"; + } + /** + * The style of the generated image (vivid or natural). Only supported + * by dall-e-3. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageStyle}. + */ + public String openAIImageStyle() { + return "CamelOpenAIImageStyle"; + } + /** + * The content moderation level for image generation (low or auto). Only + * supported by GPT image models. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageModeration}. + */ + public String openAIImageModeration() { + return "CamelOpenAIImageModeration"; + } + /** + * How closely the edit must match the style and features of the input + * image (high or low). Only supported by the image-edit operation on + * gpt-image-1 and gpt-image-1.5. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageInputFidelity}. + */ + public String openAIImageInputFidelity() { + return "CamelOpenAIImageInputFidelity"; + } + /** + * An optional PNG mask for the image-edit operation, where the fully + * transparent areas indicate where the image should be edited. + * + * The option is a: {@code byte[], java.io.File, java.nio.file.Path or + * java.io.InputStream} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageMask}. + */ + public String openAIImageMask() { + return "CamelOpenAIImageMask"; + } + /** + * The number of images returned in the response. + * + * The option is a: {@code Integer} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageResultCount}. + */ + public String openAIImageResultCount() { + return "CamelOpenAIImageResultCount"; + } + /** + * The prompt as revised by the model, when a single image is returned + * (dall-e-3). + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageRevisedPrompt}. + */ + public String openAIImageRevisedPrompt() { + return "CamelOpenAIImageRevisedPrompt"; + } + /** + * The prompts as revised by the model, one entry per returned image + * (dall-e-3). + * + * The option is a: {@code java.util.List} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageRevisedPrompts}. + */ + public String openAIImageRevisedPrompts() { + return "CamelOpenAIImageRevisedPrompts"; + } + /** + * The number of input tokens billed for the image request. Only + * reported by GPT image models. + * + * The option is a: {@code Long} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageInputTokens}. + */ + public String openAIImageInputTokens() { + return "CamelOpenAIImageInputTokens"; + } + /** + * The number of output tokens billed for the image request. Only + * reported by GPT image models. + * + * The option is a: {@code Long} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageOutputTokens}. + */ + public String openAIImageOutputTokens() { + return "CamelOpenAIImageOutputTokens"; + } + /** + * The total number of tokens billed for the image request. Only + * reported by GPT image models. + * + * The option is a: {@code Long} type. + * + * Group: producer + * + * @return the name of the header {@code OpenAIImageTotalTokens}. + */ + public String openAIImageTotalTokens() { + return "CamelOpenAIImageTotalTokens"; + } } static OpenAIEndpointBuilder endpointBuilder(String componentName, String path) { class OpenAIEndpointBuilderImpl extends AbstractEndpointBuilder implements OpenAIEndpointBuilder, AdvancedOpenAIEndpointBuilder { diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageExpectation.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageExpectation.java new file mode 100644 index 0000000000000..4ced89ddabbd3 --- /dev/null +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageExpectation.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.test.infra.openai.mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Expectation for the image generation and image edit endpoints. An expectation without an expected prompt matches any + * request, which is what the image edit endpoint needs because its request is multipart and is not parsed by the mock. + */ +public class ImageExpectation { + + private String expectedPrompt; + private final List base64Images = new ArrayList<>(); + private final List imageUrls = new ArrayList<>(); + private final List revisedPrompts = new ArrayList<>(); + private String outputFormat; + private String size; + private Consumer requestAssertion; + private Integer inputTokens; + private Integer outputTokens; + + public String getExpectedPrompt() { + return expectedPrompt; + } + + public void setExpectedPrompt(String expectedPrompt) { + this.expectedPrompt = expectedPrompt; + } + + public boolean matches(String prompt) { + return expectedPrompt == null || expectedPrompt.equals(prompt); + } + + public void addBase64Image(String base64Image) { + base64Images.add(base64Image); + } + + public List getBase64Images() { + return base64Images; + } + + public void addImageUrl(String imageUrl) { + imageUrls.add(imageUrl); + } + + public List getImageUrls() { + return imageUrls; + } + + public void addRevisedPrompt(String revisedPrompt) { + revisedPrompts.add(revisedPrompt); + } + + public List getRevisedPrompts() { + return revisedPrompts; + } + + public String getOutputFormat() { + return outputFormat; + } + + public void setOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public Consumer getRequestAssertion() { + return requestAssertion; + } + + public void setRequestAssertion(Consumer requestAssertion) { + this.requestAssertion = requestAssertion; + } + + public Integer getInputTokens() { + return inputTokens; + } + + public Integer getOutputTokens() { + return outputTokens; + } + + public void setUsage(int inputTokens, int outputTokens) { + this.inputTokens = inputTokens; + this.outputTokens = outputTokens; + } +} diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageRequestHandler.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageRequestHandler.java new file mode 100644 index 0000000000000..b1c1825f9d101 --- /dev/null +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageRequestHandler.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.test.infra.openai.mock; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.HttpExchange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles image generation and image edit requests. Image generation is a JSON request whose prompt is used to select + * an expectation, while image edit is a multipart request that is consumed without being parsed, so its expectations + * are matched in the order they were declared. + */ +public class ImageRequestHandler { + private static final Logger LOG = LoggerFactory.getLogger(ImageRequestHandler.class); + + private final List expectations; + private final ImageResponseBuilder responseBuilder; + private final ObjectMapper objectMapper; + private final boolean multipart; + private int callIndex; + + public ImageRequestHandler(List expectations, ObjectMapper objectMapper, boolean multipart) { + this.expectations = expectations; + this.objectMapper = objectMapper; + this.multipart = multipart; + this.responseBuilder = new ImageResponseBuilder(objectMapper); + } + + public String handleRequest(HttpExchange exchange) throws IOException { + try { + byte[] requestBody; + try (InputStream is = exchange.getRequestBody()) { + requestBody = is.readAllBytes(); + } + + if (expectations.isEmpty()) { + throw new IllegalStateException("No image expectations configured"); + } + + ImageExpectation expectation; + if (multipart) { + LOG.debug("Processing image edit request (call #{})", callIndex); + expectation = expectations.get(callIndex % expectations.size()); + callIndex++; + } else { + String body = new String(requestBody, StandardCharsets.UTF_8); + LOG.debug("Processing image generation request: {}", body); + expectation = findExpectationByPrompt(extractPrompt(body)); + } + + if (expectation.getRequestAssertion() != null) { + expectation.getRequestAssertion().accept(requestBody); + } + + return responseBuilder.createImageResponse(expectation); + } catch (Exception e) { + String errorMessage = "Error processing image request: " + e.getMessage(); + LOG.error(errorMessage, e); + return createErrorResponse(500, errorMessage, exchange); + } + } + + private String extractPrompt(String requestBody) throws IOException { + JsonNode promptNode = objectMapper.readTree(requestBody).get("prompt"); + if (promptNode == null) { + throw new IllegalArgumentException("Missing 'prompt' field in image generation request"); + } + return promptNode.asText(); + } + + private ImageExpectation findExpectationByPrompt(String prompt) { + return expectations.stream() + .filter(expectation -> expectation.matches(prompt)) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + String.format("No matching image expectation found for prompt: %s", prompt))); + } + + private String createErrorResponse(int statusCode, String errorMessage, HttpExchange exchange) { + String jsonErrorMessage = String.format("{\"error\": {\"message\": \"%s\", \"type\": \"invalid_request_error\"}}", + errorMessage); + try { + exchange.sendResponseHeaders(statusCode, jsonErrorMessage.length()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return jsonErrorMessage; + } +} diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageResponseBuilder.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageResponseBuilder.java new file mode 100644 index 0000000000000..293e6c4db6453 --- /dev/null +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/ImageResponseBuilder.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.test.infra.openai.mock; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Builds the JSON payload returned by the image generation and image edit endpoints. + */ +public class ImageResponseBuilder { + + private static final long FIXED_CREATED = 1750000000L; + + private final ObjectMapper objectMapper; + + public ImageResponseBuilder(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public String createImageResponse(ImageExpectation expectation) throws Exception { + List base64Images = expectation.getBase64Images(); + List imageUrls = expectation.getImageUrls(); + + if (base64Images.isEmpty() && imageUrls.isEmpty()) { + throw new IllegalStateException( + "No image data configured for the image expectation. " + + "Call replyWithImage() or replyWithImageUrl()"); + } + + ObjectNode response = objectMapper.createObjectNode(); + response.put("created", FIXED_CREATED); + + ArrayNode data = response.putArray("data"); + List revisedPrompts = expectation.getRevisedPrompts(); + + for (int i = 0; i < base64Images.size(); i++) { + ObjectNode entry = data.addObject(); + entry.put("b64_json", base64Images.get(i)); + addRevisedPrompt(entry, revisedPrompts, i); + } + for (int i = 0; i < imageUrls.size(); i++) { + ObjectNode entry = data.addObject(); + entry.put("url", imageUrls.get(i)); + addRevisedPrompt(entry, revisedPrompts, base64Images.size() + i); + } + + if (expectation.getOutputFormat() != null) { + response.put("output_format", expectation.getOutputFormat()); + } + if (expectation.getSize() != null) { + response.put("size", expectation.getSize()); + } + if (expectation.getInputTokens() != null && expectation.getOutputTokens() != null) { + int inputTokens = expectation.getInputTokens(); + int outputTokens = expectation.getOutputTokens(); + ObjectNode usage = response.putObject("usage"); + usage.put("input_tokens", inputTokens); + usage.put("output_tokens", outputTokens); + usage.put("total_tokens", inputTokens + outputTokens); + ObjectNode details = usage.putObject("input_tokens_details"); + details.put("image_tokens", 0); + details.put("text_tokens", inputTokens); + } + + return objectMapper.writeValueAsString(response); + } + + private void addRevisedPrompt(ObjectNode entry, List revisedPrompts, int index) { + if (index < revisedPrompts.size() && revisedPrompts.get(index) != null) { + entry.put("revised_prompt", revisedPrompts.get(index)); + } + } +} diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMock.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMock.java index 75b67fbf0f746..b771cfc76b6cd 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMock.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMock.java @@ -44,6 +44,8 @@ public class OpenAIMock implements BeforeEachCallback, AfterEachCallback { private final List audioTranslationExpectations; private final List speechExpectations; private final List moderationExpectations; + private final List imageGenerationExpectations; + private final List imageEditExpectations; private final OpenAIMockBuilder builder; private final ObjectMapper objectMapper; private ExecutorService executor; @@ -55,11 +57,14 @@ public OpenAIMock() { this.audioTranslationExpectations = new ArrayList<>(); this.speechExpectations = new ArrayList<>(); this.moderationExpectations = new ArrayList<>(); + this.imageGenerationExpectations = new ArrayList<>(); + this.imageEditExpectations = new ArrayList<>(); this.objectMapper = new ObjectMapper(); this.builder = new OpenAIMockBuilder( this, this.expectations, this.embeddingExpectations, this.audioTranscriptionExpectations, this.audioTranslationExpectations, - this.speechExpectations, this.moderationExpectations); + this.speechExpectations, this.moderationExpectations, + this.imageGenerationExpectations, this.imageEditExpectations); } public OpenAIMockBuilder builder() { @@ -80,7 +85,8 @@ public void beforeEach(ExtensionContext context) throws Exception { new OpenAIMockServerHandler( new OpenAIMockExpectations( expectations, embeddingExpectations, audioTranscriptionExpectations, - audioTranslationExpectations, speechExpectations, moderationExpectations), + audioTranslationExpectations, speechExpectations, moderationExpectations, + imageGenerationExpectations, imageEditExpectations), objectMapper)); executor = Executors.newSingleThreadExecutor(); diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockBuilder.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockBuilder.java index a204aae7e49de..cc8ea0b0d99ca 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockBuilder.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockBuilder.java @@ -17,6 +17,7 @@ package org.apache.camel.test.infra.openai.mock; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -38,19 +39,25 @@ public class OpenAIMockBuilder { private final List audioTranslationExpectations; private final List speechExpectations; private final List moderationExpectations; + private final List imageGenerationExpectations; + private final List imageEditExpectations; private MockExpectation currentExpectation; private EmbeddingExpectation currentEmbeddingExpectation; private AudioTranscriptionExpectation currentAudioTranscriptionExpectation; private AudioTranscriptionExpectation currentAudioTranslationExpectation; private SpeechExpectation currentSpeechExpectation; private ModerationExpectation currentModerationExpectation; + private ImageExpectation currentImageGenerationExpectation; + private ImageExpectation currentImageEditExpectation; public OpenAIMockBuilder(OpenAIMock mock, List expectations, List embeddingExpectations, List audioTranscriptionExpectations, List audioTranslationExpectations, List speechExpectations, - List moderationExpectations) { + List moderationExpectations, + List imageGenerationExpectations, + List imageEditExpectations) { this.mock = mock; this.expectations = expectations; this.embeddingExpectations = embeddingExpectations; @@ -58,6 +65,8 @@ public OpenAIMockBuilder(OpenAIMock mock, List expectations, this.audioTranslationExpectations = audioTranslationExpectations; this.speechExpectations = speechExpectations; this.moderationExpectations = moderationExpectations; + this.imageGenerationExpectations = imageGenerationExpectations; + this.imageEditExpectations = imageEditExpectations; } public OpenAIMockBuilder when(String expectedInput) { @@ -319,6 +328,101 @@ public OpenAIMockBuilder withContentType(String contentType) { return this; } + // Image Generation/Edit API methods + + /** + * Sets up an image generation expectation that matches any prompt. + */ + public OpenAIMockBuilder whenImageGeneration() { + return whenImageGeneration(null); + } + + /** + * Sets up an image generation expectation that only matches the given prompt. + */ + public OpenAIMockBuilder whenImageGeneration(String expectedPrompt) { + log.debug("Setting up image generation expectation for prompt: {}", expectedPrompt); + currentImageGenerationExpectation = new ImageExpectation(); + currentImageGenerationExpectation.setExpectedPrompt(expectedPrompt); + return this; + } + + /** + * Sets up an image edit expectation. Image edit requests are multipart and are not parsed by the mock, so + * expectations are matched in the order they were declared. + */ + public OpenAIMockBuilder whenImageEdit() { + log.debug("Setting up image edit expectation"); + currentImageEditExpectation = new ImageExpectation(); + return this; + } + + /** + * Adds an image to the reply, served as a base64 payload. Call more than once to reply with several images. + */ + public OpenAIMockBuilder replyWithImage(byte[] imageData) { + ImageExpectation expectation = validateCurrentImageExpectation("replyWithImage()"); + log.debug("Setting image data of size: {}", imageData.length); + expectation.addBase64Image(Base64.getEncoder().encodeToString(imageData)); + return this; + } + + /** + * Adds an image to the reply, served as a URL. Call more than once to reply with several images. + */ + public OpenAIMockBuilder replyWithImageUrl(String url) { + ImageExpectation expectation = validateCurrentImageExpectation("replyWithImageUrl()"); + log.debug("Setting image url: {}", url); + expectation.addImageUrl(url); + return this; + } + + /** + * Adds a revised prompt for the image at the same position in the reply. + */ + public OpenAIMockBuilder withRevisedPrompt(String revisedPrompt) { + ImageExpectation expectation = validateCurrentImageExpectation("withRevisedPrompt()"); + expectation.addRevisedPrompt(revisedPrompt); + return this; + } + + /** + * Sets the output format reported by the reply, which drives the content type set on the exchange. + */ + public OpenAIMockBuilder withImageOutputFormat(String outputFormat) { + ImageExpectation expectation = validateCurrentImageExpectation("withImageOutputFormat()"); + expectation.setOutputFormat(outputFormat); + return this; + } + + /** + * Sets the image size reported by the reply. + */ + public OpenAIMockBuilder withImageSize(String size) { + ImageExpectation expectation = validateCurrentImageExpectation("withImageSize()"); + expectation.setSize(size); + return this; + } + + /** + * Asserts on the raw image request body. Image edit requests are multipart, so the body is exposed as raw bytes + * rather than as parsed fields. + */ + public OpenAIMockBuilder assertImageRequest(Consumer requestAssertion) { + ImageExpectation expectation = validateCurrentImageExpectation("assertImageRequest()"); + expectation.setRequestAssertion(requestAssertion); + return this; + } + + /** + * Sets the token usage reported by the reply, as the GPT image models do. + */ + public OpenAIMockBuilder withImageUsage(int inputTokens, int outputTokens) { + ImageExpectation expectation = validateCurrentImageExpectation("withImageUsage()"); + expectation.setUsage(inputTokens, outputTokens); + return this; + } + public OpenAIMockBuilder end() { if (currentExpectation != null) { log.debug("Finalizing expectation for input: {}", currentExpectation.getExpectedInput()); @@ -344,10 +448,19 @@ public OpenAIMockBuilder end() { log.debug("Finalizing moderation expectation for input: {}", currentModerationExpectation.getExpectedInput()); moderationExpectations.add(currentModerationExpectation); currentModerationExpectation = null; + } else if (currentImageGenerationExpectation != null) { + log.debug("Finalizing image generation expectation"); + imageGenerationExpectations.add(currentImageGenerationExpectation); + currentImageGenerationExpectation = null; + } else if (currentImageEditExpectation != null) { + log.debug("Finalizing image edit expectation"); + imageEditExpectations.add(currentImageEditExpectation); + currentImageEditExpectation = null; } else { throw new IllegalStateException( "Call when(), whenEmbedding(), whenTranscription(), whenTranslation(), whenSpeech(), " - + "or whenModeration() before end()"); + + "whenModeration(), whenImageGeneration(), or whenImageEdit() " + + "before end()"); } return this; } @@ -383,13 +496,34 @@ public OpenAIMock build() { moderationExpectations.add(currentModerationExpectation); currentModerationExpectation = null; } + if (currentImageGenerationExpectation != null) { + log.debug("Auto-finalizing current image generation expectation during build"); + imageGenerationExpectations.add(currentImageGenerationExpectation); + currentImageGenerationExpectation = null; + } + if (currentImageEditExpectation != null) { + log.debug("Auto-finalizing current image edit expectation during build"); + imageEditExpectations.add(currentImageEditExpectation); + currentImageEditExpectation = null; + } log.info("Built OpenAIMock with {} chat, {} embedding, {} transcription, {} translation, " - + "{} speech, and {} moderation expectations", + + "{} speech, {} moderation, {} image generation, and {} image edit expectations", expectations.size(), embeddingExpectations.size(), audioTranscriptionExpectations.size(), - audioTranslationExpectations.size(), speechExpectations.size(), moderationExpectations.size()); + audioTranslationExpectations.size(), speechExpectations.size(), moderationExpectations.size(), + imageGenerationExpectations.size(), imageEditExpectations.size()); return mock; } + private ImageExpectation validateCurrentImageExpectation(String methodName) { + if (currentImageGenerationExpectation != null) { + return currentImageGenerationExpectation; + } + if (currentImageEditExpectation != null) { + return currentImageEditExpectation; + } + throw new IllegalStateException("Call whenImageGeneration() or whenImageEdit() before " + methodName); + } + private void validateCurrentExpectation(String methodName) { if (currentExpectation == null) { throw new IllegalStateException("Call when() before " + methodName); diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockExpectations.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockExpectations.java index ec2b7f036bfde..39127e59d6e02 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockExpectations.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockExpectations.java @@ -28,12 +28,15 @@ public record OpenAIMockExpectations( List transcriptions, List translations, List speeches, - List moderations) { + List moderations, + List imageGenerations, + List imageEdits) { /** * Convenience factory for callers that only need chat completion expectations (e.g. SSL/TLS tests). */ public static OpenAIMockExpectations ofChat(List chat) { - return new OpenAIMockExpectations(chat, List.of(), List.of(), List.of(), List.of(), List.of()); + return new OpenAIMockExpectations( + chat, List.of(), List.of(), List.of(), List.of(), List.of(), List.of(), List.of()); } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockServerHandler.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockServerHandler.java index 1fb544a8da131..92477ee80074e 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockServerHandler.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockServerHandler.java @@ -34,6 +34,8 @@ public class OpenAIMockServerHandler implements HttpHandler { private final AudioTranscriptionRequestHandler audioTranslationRequestHandler; private final SpeechRequestHandler speechRequestHandler; private final ModerationRequestHandler moderationRequestHandler; + private final ImageRequestHandler imageGenerationRequestHandler; + private final ImageRequestHandler imageEditRequestHandler; public OpenAIMockServerHandler(OpenAIMockExpectations expectations, ObjectMapper objectMapper) { this.chatRequestHandler = new RequestHandler(expectations.chat(), objectMapper); @@ -45,6 +47,9 @@ public OpenAIMockServerHandler(OpenAIMockExpectations expectations, ObjectMapper = new AudioTranscriptionRequestHandler(expectations.translations(), objectMapper); this.speechRequestHandler = new SpeechRequestHandler(expectations.speeches(), objectMapper); this.moderationRequestHandler = new ModerationRequestHandler(expectations.moderations(), objectMapper); + this.imageGenerationRequestHandler + = new ImageRequestHandler(expectations.imageGenerations(), objectMapper, false); + this.imageEditRequestHandler = new ImageRequestHandler(expectations.imageEdits(), objectMapper, true); } @Override @@ -66,6 +71,10 @@ public void handle(HttpExchange exchange) throws IOException { response = audioTranslationRequestHandler.handleRequest(exchange); } else if (path.endsWith("/embeddings")) { response = embeddingRequestHandler.handleRequest(exchange); + } else if (path.endsWith("/images/generations")) { + response = imageGenerationRequestHandler.handleRequest(exchange); + } else if (path.endsWith("/images/edits")) { + response = imageEditRequestHandler.handleRequest(exchange); } else if (path.endsWith("/moderations")) { response = moderationRequestHandler.handleRequest(exchange); } else if (path.endsWith("/responses")) {