This is an automated email from the ASF dual-hosted git repository. fmariani pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 2e2a90713d8a43b37a6775f1c1fbc172589ae77b Author: Croway <[email protected]> AuthorDate: Wed Mar 11 13:50:27 2026 +0100 Camel Spring AI Image component - integration with SpringAI ImageModel API --- bom/camel-bom/pom.xml | 5 + catalog/camel-allcomponents/pom.xml | 5 + .../camel/catalog/components/spring-ai-chat.json | 57 +++-- .../camel-spring-ai/camel-spring-ai-image/pom.xml | 77 +++++++ .../image/SpringAiImageComponentConfigurer.java | 99 ++++++++ .../SpringAiImageConfigurationConfigurer.java | 72 ++++++ .../image/SpringAiImageConverterLoader.java | 76 +++++++ .../image/SpringAiImageEndpointConfigurer.java | 83 +++++++ .../image/SpringAiImageEndpointUriFactory.java | 78 +++++++ .../component/springai/image/spring-ai-image.json | 60 +++++ .../services/org/apache/camel/TypeConverterLoader | 2 + .../services/org/apache/camel/component.properties | 7 + .../org/apache/camel/component/spring-ai-image | 2 + ...onent.springai.image.SpringAiImageConfiguration | 2 + .../camel/configurer/spring-ai-image-component | 2 + .../camel/configurer/spring-ai-image-endpoint | 2 + .../camel/urifactory/spring-ai-image-endpoint | 2 + .../src/main/docs/spring-ai-image-component.adoc | 197 ++++++++++++++++ .../component/springai/image/SpringAiImage.java | 24 ++ .../springai/image/SpringAiImageComponent.java | 62 +++++ .../springai/image/SpringAiImageConfiguration.java | 136 +++++++++++ .../springai/image/SpringAiImageConverter.java | 70 ++++++ .../springai/image/SpringAiImageEndpoint.java | 74 ++++++ .../springai/image/SpringAiImageHeaders.java | 55 +++++ .../springai/image/SpringAiImageProducer.java | 126 ++++++++++ .../springai/image/SpringAiImageOllamaIT.java | 247 ++++++++++++++++++++ .../src/test/resources/log4j2.properties | 50 ++++ .../camel-spring-parent/camel-spring-ai/pom.xml | 1 + .../ROOT/examples/json/spring-ai-image.json | 1 + docs/components/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/spring-ai-image-component.adoc | 1 + .../dsl/SpringAiChatEndpointBuilderFactory.java | 253 +++++++++++++++++++++ parent/pom.xml | 5 + 33 files changed, 1910 insertions(+), 24 deletions(-) diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index e9d137156544..b1c365b6a55a 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -2162,6 +2162,11 @@ <artifactId>camel-spring-ai-embeddings</artifactId> <version>4.19.0-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-ai-image</artifactId> + <version>4.19.0-SNAPSHOT</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring-ai-tools</artifactId> diff --git a/catalog/camel-allcomponents/pom.xml b/catalog/camel-allcomponents/pom.xml index 97359d22feca..2974d4124386 100644 --- a/catalog/camel-allcomponents/pom.xml +++ b/catalog/camel-allcomponents/pom.xml @@ -1947,6 +1947,11 @@ <artifactId>camel-spring-ai-embeddings</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-ai-image</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring-ai-tools</artifactId> diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ai-chat.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ai-chat.json index 590077c9cdf5..0608321cf737 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ai-chat.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ai-chat.json @@ -58,7 +58,9 @@ "CamelSpringAiChatFinishReason": { "index": 26, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The reason why the chat response generation stopped (e.g., STOP, LENGTH, TOOL_CALLS)", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#FINISH_REASON" }, "CamelSpringAiChatModelName": { "index": 27, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the AI model used to generate the response", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#MODEL_NAME" }, "CamelSpringAiChatResponseId": { "index": 28, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The unique ID of the chat response", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#RESPONSE_ID" }, - "CamelSpringAiChatResponseMetadata": { "index": 29, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Full response metadata as a Map containing all available metadata fields", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#RESPONSE_METADATA" } + "CamelSpringAiChatResponseMetadata": { "index": 29, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Full response metadata as a Map containing all available metadata fields", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#RESPONSE_METADATA" }, + "CamelSpringAiChatToolNames": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated tool names for selecting tools by name via ToolCallbackResolver", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#TOOL_NAMES" }, + "CamelSpringAiChatToolContext": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Context map to pass to tools during execution", "constantName": "org.apache.camel.component.springai.chat.SpringAiChatConstants#TOOL_CONTEXT" } }, "properties": { "chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The ID of the chat endpoint" }, @@ -67,28 +69,35 @@ "chatOperation": { "index": 3, "kind": "parameter", "displayName": "Chat Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.springai.chat.SpringAiChatOperations", "enum": [ "CHAT_SINGLE_MESSAGE", "CHAT_SINGLE_MESSAGE_WITH_PROMPT", "CHAT_MULTIPLE_MESSAGES" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "CHAT_SINGLE_MESSAGE", "configurationClass": "org.apache.camel. [...] "systemMessage": { "index": 4, "kind": "parameter", "displayName": "System Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Default system message to set context for the conversation. Can be overridden by the CamelSprin [...] "tags": { "index": 5, "kind": "parameter", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, - "userMessage": { "index": 6, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Default user message text for multimodal requests. Can be combined with media data in the message b [...] - "lazyStartProducer": { "index": 7, "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 produc [...] - "advisors": { "index": 8, "kind": "parameter", "displayName": "Advisors", "group": "advanced", "label": "advanced", "required": false, "type": "array", "javaType": "java.util.List<org.springframework.ai.chat.client.advisor.api.Advisor>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "List of custom advisor [...] - "chatMemory": { "index": 9, "kind": "parameter", "displayName": "Chat Memory", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.springframework.ai.chat.memory.ChatMemory", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "ChatMemory instance for maintaining conver [...] - "chatMemoryVectorStore": { "index": 10, "kind": "parameter", "displayName": "Chat Memory Vector Store", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.springframework.ai.vectorstore.VectorStore", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "VectorStore instance for maintaining c [...] - "entityClass": { "index": 11, "kind": "parameter", "displayName": "Entity Class", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.Class<java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "The Java class to use for entity response conversion using ChatClient.ent [...] - "maxFileSize": { "index": 12, "kind": "parameter", "displayName": "Max File Size", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1048576, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Maximum file size in bytes for multimodal content (images, audio, PDFs, etc [...] - "maxTokens": { "index": 13, "kind": "parameter", "displayName": "Max Tokens", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Maximum tokens in the response." }, - "outputClass": { "index": 14, "kind": "parameter", "displayName": "Output Class", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.Class<java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "The Java class to use for BEAN output format conversion. Required when ou [...] - "outputFormat": { "index": 15, "kind": "parameter", "displayName": "Output Format", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "The output format for structured output conversion (BEAN, MAP, LIST). Used in conjunctio [...] - "structuredOutputConverter": { "index": 16, "kind": "parameter", "displayName": "Structured Output Converter", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.springframework.ai.converter.StructuredOutputConverter<java.lang.Object>", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configurati [...] - "systemMetadata": { "index": 17, "kind": "parameter", "displayName": "System Metadata", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Metadata to attach to system messages. This metadat [...] - "temperature": { "index": 18, "kind": "parameter", "displayName": "Temperature", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Temperature parameter for response randomness (0.0-2.0)." }, - "topKSampling": { "index": 19, "kind": "parameter", "displayName": "Top KSampling", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Top K parameter for sampling." }, - "topP": { "index": 20, "kind": "parameter", "displayName": "Top P", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Top P parameter for nucleus sampling." }, - "userMetadata": { "index": 21, "kind": "parameter", "displayName": "User Metadata", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Metadata to attach to user messages. This metadata can [...] - "ragTemplate": { "index": 22, "kind": "parameter", "displayName": "Rag Template", "group": "rag", "label": "rag", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "Context:\\n\\{context}\\n\\nQuestion: \\{question}", "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Template for formatting RAG (R [...] - "similarityThreshold": { "index": 23, "kind": "parameter", "displayName": "Similarity Threshold", "group": "rag", "label": "rag", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.7, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Similarity threshold for RAG retrieval (default: 0.7)." }, - "topK": { "index": 24, "kind": "parameter", "displayName": "Top K", "group": "rag", "label": "rag", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Number of top documents to retrieve for RAG (default: 5)." }, - "vectorStore": { "index": 25, "kind": "parameter", "displayName": "Vector Store", "group": "rag", "label": "rag", "required": false, "type": "object", "javaType": "org.springframework.ai.vectorstore.VectorStore", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "VectorStore for automatic RAG retrieval." }, - "safeguardFailureResponse": { "index": 26, "kind": "parameter", "displayName": "Safeguard Failure Response", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Failure response message for SafeGuard advisor when sensitive c [...] - "safeguardSensitiveWords": { "index": 27, "kind": "parameter", "displayName": "Safeguard Sensitive Words", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Comma-separated list of sensitive words for SafeGuard advisor. Wh [...] - "safeguardOrder": { "index": 28, "kind": "parameter", "displayName": "Safeguard Order", "group": "security (advanced)", "label": "security,advanced", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Order of execution for SafeGuard advisor. Lower numbers execut [...] + "toolNames": { "index": 6, "kind": "parameter", "displayName": "Tool Names", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Comma-separated tool names for selecting tools by name via Spring AI's ToolCallbackResolver. This enabl [...] + "userMessage": { "index": 7, "kind": "parameter", "displayName": "User Message", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Default user message text for multimodal requests. Can be combined with media data in the message b [...] + "lazyStartProducer": { "index": 8, "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 produc [...] + "advisors": { "index": 9, "kind": "parameter", "displayName": "Advisors", "group": "advanced", "label": "advanced", "required": false, "type": "array", "javaType": "java.util.List<org.springframework.ai.chat.client.advisor.api.Advisor>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "List of custom advisor [...] + "chatMemory": { "index": 10, "kind": "parameter", "displayName": "Chat Memory", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.springframework.ai.chat.memory.ChatMemory", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "ChatMemory instance for maintaining conve [...] + "chatMemoryVectorStore": { "index": 11, "kind": "parameter", "displayName": "Chat Memory Vector Store", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.springframework.ai.vectorstore.VectorStore", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "VectorStore instance for maintaining c [...] + "entityClass": { "index": 12, "kind": "parameter", "displayName": "Entity Class", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.Class<java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "The Java class to use for entity response conversion using ChatClient.ent [...] + "maxFileSize": { "index": 13, "kind": "parameter", "displayName": "Max File Size", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1048576, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Maximum file size in bytes for multimodal content (images, audio, PDFs, etc [...] + "maxTokens": { "index": 14, "kind": "parameter", "displayName": "Max Tokens", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Maximum tokens in the response." }, + "mcpServer": { "index": 15, "kind": "parameter", "displayName": "Mcp Server", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "prefix": "mcpServer.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "MCP server config [...] + "mcpTimeout": { "index": 16, "kind": "parameter", "displayName": "Mcp Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 20, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Timeout in seconds for MCP operations including tool execution and initialization" }, + "outputClass": { "index": 17, "kind": "parameter", "displayName": "Output Class", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.Class<java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "The Java class to use for BEAN output format conversion. Required when ou [...] + "outputFormat": { "index": 18, "kind": "parameter", "displayName": "Output Format", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "The output format for structured output conversion (BEAN, MAP, LIST). Used in conjunctio [...] + "structuredOutputConverter": { "index": 19, "kind": "parameter", "displayName": "Structured Output Converter", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.springframework.ai.converter.StructuredOutputConverter<java.lang.Object>", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configurati [...] + "structuredOutputValidation": { "index": 20, "kind": "parameter", "displayName": "Structured Output Validation", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Enable structured output validation with aut [...] + "structuredOutputValidationMaxAttempts": { "index": 21, "kind": "parameter", "displayName": "Structured Output Validation Max Attempts", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Maximum number of retry atte [...] + "systemMetadata": { "index": 22, "kind": "parameter", "displayName": "System Metadata", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Metadata to attach to system messages. This metadat [...] + "temperature": { "index": 23, "kind": "parameter", "displayName": "Temperature", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Temperature parameter for response randomness (0.0-2.0)." }, + "toolCallbacks": { "index": 24, "kind": "parameter", "displayName": "Tool Callbacks", "group": "advanced", "label": "advanced", "required": false, "type": "array", "javaType": "java.util.List<org.springframework.ai.tool.ToolCallback>", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "List of ToolCallback inst [...] + "toolContext": { "index": 25, "kind": "parameter", "displayName": "Tool Context", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Context map to pass to tools during execution. Tool metho [...] + "topKSampling": { "index": 26, "kind": "parameter", "displayName": "Top KSampling", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Top K parameter for sampling." }, + "topP": { "index": 27, "kind": "parameter", "displayName": "Top P", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "java.lang.Double", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Top P parameter for nucleus sampling." }, + "userMetadata": { "index": 28, "kind": "parameter", "displayName": "User Metadata", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Metadata to attach to user messages. This metadata can [...] + "ragTemplate": { "index": 29, "kind": "parameter", "displayName": "Rag Template", "group": "rag", "label": "rag", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "Context:\\n\\{context}\\n\\nQuestion: \\{question}", "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Template for formatting RAG (R [...] + "similarityThreshold": { "index": 30, "kind": "parameter", "displayName": "Similarity Threshold", "group": "rag", "label": "rag", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.7, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Similarity threshold for RAG retrieval (default: 0.7)." }, + "topK": { "index": 31, "kind": "parameter", "displayName": "Top K", "group": "rag", "label": "rag", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Number of top documents to retrieve for RAG (default: 5)." }, + "vectorStore": { "index": 32, "kind": "parameter", "displayName": "Vector Store", "group": "rag", "label": "rag", "required": false, "type": "object", "javaType": "org.springframework.ai.vectorstore.VectorStore", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "VectorStore for automatic RAG retrieval." }, + "safeguardFailureResponse": { "index": 33, "kind": "parameter", "displayName": "Safeguard Failure Response", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Failure response message for SafeGuard advisor when sensitive c [...] + "safeguardSensitiveWords": { "index": 34, "kind": "parameter", "displayName": "Safeguard Sensitive Words", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Comma-separated list of sensitive words for SafeGuard advisor. Wh [...] + "safeguardOrder": { "index": 35, "kind": "parameter", "displayName": "Safeguard Order", "group": "security (advanced)", "label": "security,advanced", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.chat.SpringAiChatConfiguration", "configurationField": "configuration", "description": "Order of execution for SafeGuard advisor. Lower numbers execut [...] } } diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/pom.xml b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/pom.xml new file mode 100644 index 000000000000..c72aec09b897 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/pom.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>camel-spring-ai-parent</artifactId> + <groupId>org.apache.camel</groupId> + <version>4.19.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-spring-ai-image</artifactId> + <packaging>jar</packaging> + <name>Camel :: Spring AI :: Image</name> + <description>Camel Spring AI Image Generation component</description> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-model</artifactId> + </dependency> + + <!-- test dependencies --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-junit6</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <!-- OpenAI Spring AI for integration tests (Ollama via OpenAI base URL override) --> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-openai</artifactId> + <scope>test</scope> + </dependency> + <!-- Ollama test infrastructure --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-infra-ollama</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageComponentConfigurer.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageComponentConfigurer.java new file mode 100644 index 000000000000..5ac1157db5f3 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageComponentConfigurer.java @@ -0,0 +1,99 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.springai.image; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo") +@SuppressWarnings("unchecked") +public class SpringAiImageComponentConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + private org.apache.camel.component.springai.image.SpringAiImageConfiguration getOrCreateConfiguration(SpringAiImageComponent target) { + if (target.getConfiguration() == null) { + target.setConfiguration(new org.apache.camel.component.springai.image.SpringAiImageConfiguration()); + } + return target.getConfiguration(); + } + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + SpringAiImageComponent target = (SpringAiImageComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "autowiredenabled": + case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; + case "configuration": target.setConfiguration(property(camelContext, org.apache.camel.component.springai.image.SpringAiImageConfiguration.class, value)); return true; + case "height": getOrCreateConfiguration(target).setHeight(property(camelContext, java.lang.Integer.class, value)); return true; + case "imagemodel": + case "imageModel": getOrCreateConfiguration(target).setImageModel(property(camelContext, org.springframework.ai.image.ImageModel.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + case "model": getOrCreateConfiguration(target).setModel(property(camelContext, java.lang.String.class, value)); return true; + case "n": getOrCreateConfiguration(target).setN(property(camelContext, java.lang.Integer.class, value)); return true; + case "responseformat": + case "responseFormat": getOrCreateConfiguration(target).setResponseFormat(property(camelContext, java.lang.String.class, value)); return true; + case "style": getOrCreateConfiguration(target).setStyle(property(camelContext, java.lang.String.class, value)); return true; + case "width": getOrCreateConfiguration(target).setWidth(property(camelContext, java.lang.Integer.class, value)); return true; + default: return false; + } + } + + @Override + public String[] getAutowiredNames() { + return new String[]{"imageModel"}; + } + + @Override + public Class<?> getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "autowiredenabled": + case "autowiredEnabled": return boolean.class; + case "configuration": return org.apache.camel.component.springai.image.SpringAiImageConfiguration.class; + case "height": return java.lang.Integer.class; + case "imagemodel": + case "imageModel": return org.springframework.ai.image.ImageModel.class; + case "lazystartproducer": + case "lazyStartProducer": return boolean.class; + case "model": return java.lang.String.class; + case "n": return java.lang.Integer.class; + case "responseformat": + case "responseFormat": return java.lang.String.class; + case "style": return java.lang.String.class; + case "width": return java.lang.Integer.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + SpringAiImageComponent target = (SpringAiImageComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "autowiredenabled": + case "autowiredEnabled": return target.isAutowiredEnabled(); + case "configuration": return target.getConfiguration(); + case "height": return getOrCreateConfiguration(target).getHeight(); + case "imagemodel": + case "imageModel": return getOrCreateConfiguration(target).getImageModel(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + case "model": return getOrCreateConfiguration(target).getModel(); + case "n": return getOrCreateConfiguration(target).getN(); + case "responseformat": + case "responseFormat": return getOrCreateConfiguration(target).getResponseFormat(); + case "style": return getOrCreateConfiguration(target).getStyle(); + case "width": return getOrCreateConfiguration(target).getWidth(); + default: return null; + } + } +} + diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageConfigurationConfigurer.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageConfigurationConfigurer.java new file mode 100644 index 000000000000..299553610c7f --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageConfigurationConfigurer.java @@ -0,0 +1,72 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.springai.image; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.component.springai.image.SpringAiImageConfiguration; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo") +@SuppressWarnings("unchecked") +public class SpringAiImageConfigurationConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + org.apache.camel.component.springai.image.SpringAiImageConfiguration target = (org.apache.camel.component.springai.image.SpringAiImageConfiguration) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "height": target.setHeight(property(camelContext, java.lang.Integer.class, value)); return true; + case "imagemodel": + case "imageModel": target.setImageModel(property(camelContext, org.springframework.ai.image.ImageModel.class, value)); return true; + case "model": target.setModel(property(camelContext, java.lang.String.class, value)); return true; + case "n": target.setN(property(camelContext, java.lang.Integer.class, value)); return true; + case "responseformat": + case "responseFormat": target.setResponseFormat(property(camelContext, java.lang.String.class, value)); return true; + case "style": target.setStyle(property(camelContext, java.lang.String.class, value)); return true; + case "width": target.setWidth(property(camelContext, java.lang.Integer.class, value)); return true; + default: return false; + } + } + + @Override + public Class<?> getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "height": return java.lang.Integer.class; + case "imagemodel": + case "imageModel": return org.springframework.ai.image.ImageModel.class; + case "model": return java.lang.String.class; + case "n": return java.lang.Integer.class; + case "responseformat": + case "responseFormat": return java.lang.String.class; + case "style": return java.lang.String.class; + case "width": return java.lang.Integer.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + org.apache.camel.component.springai.image.SpringAiImageConfiguration target = (org.apache.camel.component.springai.image.SpringAiImageConfiguration) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "height": return target.getHeight(); + case "imagemodel": + case "imageModel": return target.getImageModel(); + case "model": return target.getModel(); + case "n": return target.getN(); + case "responseformat": + case "responseFormat": return target.getResponseFormat(); + case "style": return target.getStyle(); + case "width": return target.getWidth(); + default: return null; + } + } +} + diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageConverterLoader.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageConverterLoader.java new file mode 100644 index 000000000000..363ec99340ad --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageConverterLoader.java @@ -0,0 +1,76 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.springai.image; + +import javax.annotation.processing.Generated; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.DeferredContextBinding; +import org.apache.camel.Exchange; +import org.apache.camel.TypeConversionException; +import org.apache.camel.TypeConverterLoaderException; +import org.apache.camel.spi.TypeConverterLoader; +import org.apache.camel.spi.TypeConverterRegistry; +import org.apache.camel.support.SimpleTypeConverter; +import org.apache.camel.support.TypeConverterSupport; +import org.apache.camel.util.DoubleMap; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.TypeConverterLoaderGeneratorMojo") +@SuppressWarnings("unchecked") +@DeferredContextBinding +public final class SpringAiImageConverterLoader implements TypeConverterLoader, CamelContextAware { + + private CamelContext camelContext; + + public SpringAiImageConverterLoader() { + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException { + registerConverters(registry); + } + + private void registerConverters(TypeConverterRegistry registry) { + addTypeConverter(registry, byte[].class, org.springframework.ai.image.Image.class, false, + (type, exchange, value) -> { + Object answer = org.apache.camel.component.springai.image.SpringAiImageConverter.toBytes((org.springframework.ai.image.Image) value); + if (false && answer == null) { + answer = Void.class; + } + return answer; + }); + addTypeConverter(registry, java.io.InputStream.class, org.springframework.ai.image.Image.class, false, + (type, exchange, value) -> { + Object answer = org.apache.camel.component.springai.image.SpringAiImageConverter.toInputStream((org.springframework.ai.image.Image) value); + if (false && answer == null) { + answer = Void.class; + } + return answer; + }); + addTypeConverter(registry, java.lang.String.class, org.springframework.ai.image.Image.class, false, + (type, exchange, value) -> { + Object answer = org.apache.camel.component.springai.image.SpringAiImageConverter.toString((org.springframework.ai.image.Image) value); + if (false && answer == null) { + answer = Void.class; + } + return answer; + }); + } + + private static void addTypeConverter(TypeConverterRegistry registry, Class<?> toType, Class<?> fromType, boolean allowNull, SimpleTypeConverter.ConversionMethod method) { + registry.addTypeConverter(toType, fromType, new SimpleTypeConverter(allowNull, method)); + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageEndpointConfigurer.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageEndpointConfigurer.java new file mode 100644 index 000000000000..bd740b9e1545 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageEndpointConfigurer.java @@ -0,0 +1,83 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.springai.image; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo") +@SuppressWarnings("unchecked") +public class SpringAiImageEndpointConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + SpringAiImageEndpoint target = (SpringAiImageEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "height": target.getConfiguration().setHeight(property(camelContext, java.lang.Integer.class, value)); return true; + case "imagemodel": + case "imageModel": target.getConfiguration().setImageModel(property(camelContext, org.springframework.ai.image.ImageModel.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + case "model": target.getConfiguration().setModel(property(camelContext, java.lang.String.class, value)); return true; + case "n": target.getConfiguration().setN(property(camelContext, java.lang.Integer.class, value)); return true; + case "responseformat": + case "responseFormat": target.getConfiguration().setResponseFormat(property(camelContext, java.lang.String.class, value)); return true; + case "style": target.getConfiguration().setStyle(property(camelContext, java.lang.String.class, value)); return true; + case "width": target.getConfiguration().setWidth(property(camelContext, java.lang.Integer.class, value)); return true; + default: return false; + } + } + + @Override + public String[] getAutowiredNames() { + return new String[]{"imageModel"}; + } + + @Override + public Class<?> getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "height": return java.lang.Integer.class; + case "imagemodel": + case "imageModel": return org.springframework.ai.image.ImageModel.class; + case "lazystartproducer": + case "lazyStartProducer": return boolean.class; + case "model": return java.lang.String.class; + case "n": return java.lang.Integer.class; + case "responseformat": + case "responseFormat": return java.lang.String.class; + case "style": return java.lang.String.class; + case "width": return java.lang.Integer.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + SpringAiImageEndpoint target = (SpringAiImageEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "height": return target.getConfiguration().getHeight(); + case "imagemodel": + case "imageModel": return target.getConfiguration().getImageModel(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + case "model": return target.getConfiguration().getModel(); + case "n": return target.getConfiguration().getN(); + case "responseformat": + case "responseFormat": return target.getConfiguration().getResponseFormat(); + case "style": return target.getConfiguration().getStyle(); + case "width": return target.getConfiguration().getWidth(); + default: return null; + } + } +} + diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageEndpointUriFactory.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageEndpointUriFactory.java new file mode 100644 index 000000000000..65912ce4b8d4 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/java/org/apache/camel/component/springai/image/SpringAiImageEndpointUriFactory.java @@ -0,0 +1,78 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.springai.image; + +import javax.annotation.processing.Generated; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.spi.EndpointUriFactory; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.GenerateEndpointUriFactoryMojo") +public class SpringAiImageEndpointUriFactory extends org.apache.camel.support.component.EndpointUriFactorySupport implements EndpointUriFactory { + + private static final String BASE = ":imageId"; + + private static final Set<String> PROPERTY_NAMES; + private static final Set<String> SECRET_PROPERTY_NAMES; + private static final Map<String, String> MULTI_VALUE_PREFIXES; + static { + Set<String> props = new HashSet<>(9); + props.add("height"); + props.add("imageId"); + props.add("imageModel"); + props.add("lazyStartProducer"); + props.add("model"); + props.add("n"); + props.add("responseFormat"); + props.add("style"); + props.add("width"); + PROPERTY_NAMES = Collections.unmodifiableSet(props); + SECRET_PROPERTY_NAMES = Collections.emptySet(); + MULTI_VALUE_PREFIXES = Collections.emptyMap(); + } + + @Override + public boolean isEnabled(String scheme) { + return "spring-ai-image".equals(scheme); + } + + @Override + public String buildUri(String scheme, Map<String, Object> properties, boolean encode) throws URISyntaxException { + String syntax = scheme + BASE; + String uri = syntax; + + Map<String, Object> copy = new HashMap<>(properties); + + uri = buildPathParameter(syntax, uri, "imageId", null, true, copy); + uri = buildQueryParameters(uri, copy, encode); + return uri; + } + + @Override + public Set<String> propertyNames() { + return PROPERTY_NAMES; + } + + @Override + public Set<String> secretPropertyNames() { + return SECRET_PROPERTY_NAMES; + } + + @Override + public Map<String, String> multiValuePrefixes() { + return MULTI_VALUE_PREFIXES; + } + + @Override + public boolean isLenientProperties() { + return false; + } +} + diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/org/apache/camel/component/springai/image/spring-ai-image.json b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/org/apache/camel/component/springai/image/spring-ai-image.json new file mode 100644 index 000000000000..54f289ed3034 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/org/apache/camel/component/springai/image/spring-ai-image.json @@ -0,0 +1,60 @@ +{ + "component": { + "kind": "component", + "name": "spring-ai-image", + "title": "Spring AI Image", + "description": "Spring AI Image Generation", + "deprecated": false, + "firstVersion": "4.19.0", + "label": "ai", + "javaType": "org.apache.camel.component.springai.image.SpringAiImageComponent", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-spring-ai-image", + "version": "4.19.0-SNAPSHOT", + "scheme": "spring-ai-image", + "extendsScheme": "", + "syntax": "spring-ai-image:imageId", + "async": false, + "api": false, + "consumerOnly": false, + "producerOnly": true, + "lenientProperties": false, + "browsable": false, + "remote": true + }, + "componentProperties": { + "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration." }, + "height": { "index": 1, "kind": "property", "displayName": "Height", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Image height in pixels" }, + "imageModel": { "index": 2, "kind": "property", "displayName": "Image Model", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.springframework.ai.image.ImageModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "The ImageModel to use for generating images." }, + "lazyStartProducer": { "index": 3, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "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 [...] + "model": { "index": 4, "kind": "property", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "The model to use for image generation" }, + "n": { "index": 5, "kind": "property", "displayName": "N", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Number of images to generate" }, + "responseFormat": { "index": 6, "kind": "property", "displayName": "Response Format", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Response format: url or b64_json" }, + "style": { "index": 7, "kind": "property", "displayName": "Style", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Image style (e.g., vivid, natural)" }, + "width": { "index": 8, "kind": "property", "displayName": "Width", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Image width in pixels" }, + "autowiredEnabled": { "index": 9, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] + }, + "headers": { + "CamelSpringAiImageN": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of images to generate", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#N" }, + "CamelSpringAiImageWidth": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Image width in pixels", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#WIDTH" }, + "CamelSpringAiImageHeight": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Image height in pixels", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#HEIGHT" }, + "CamelSpringAiImageModel": { "index": 3, "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", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#MODEL" }, + "CamelSpringAiImageResponseFormat": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Response format: url or b64_json", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#RESPONSE_FORMAT" }, + "CamelSpringAiImageStyle": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Image style (e.g., vivid, natural)", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#STYLE" }, + "CamelSpringAiImageResponseMetadata": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.springframework.ai.image.ImageResponseMetadata", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The image response metadata", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#RESPONSE_METADATA" }, + "CamelSpringAiImageGeneration": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.springframework.ai.image.ImageGeneration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ImageGeneration object for single image results", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#IMAGE_GENERATION" }, + "CamelSpringAiImageGenerations": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List<org.springframework.ai.image.ImageGeneration>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "List of ImageGeneration objects for multiple image results", "constantName": "org.apache.camel.component.springai.image.SpringAiImageHeaders#IMAGE_GENERATIONS" } + }, + "properties": { + "imageId": { "index": 0, "kind": "path", "displayName": "Image Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The id" }, + "height": { "index": 1, "kind": "parameter", "displayName": "Height", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Image height in pixels" }, + "imageModel": { "index": 2, "kind": "parameter", "displayName": "Image Model", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.springframework.ai.image.ImageModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "The ImageModel to use for generating images." }, + "model": { "index": 3, "kind": "parameter", "displayName": "Model", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "The model to use for image generation" }, + "n": { "index": 4, "kind": "parameter", "displayName": "N", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Number of images to generate" }, + "responseFormat": { "index": 5, "kind": "parameter", "displayName": "Response Format", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Response format: url or b64_json" }, + "style": { "index": 6, "kind": "parameter", "displayName": "Style", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Image style (e.g., vivid, natural)" }, + "width": { "index": 7, "kind": "parameter", "displayName": "Width", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.springai.image.SpringAiImageConfiguration", "configurationField": "configuration", "description": "Image width in pixels" }, + "lazyStartProducer": { "index": 8, "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 produc [...] + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader new file mode 100644 index 000000000000..90f54936af55 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +org.apache.camel.component.springai.image.SpringAiImageConverterLoader diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/component.properties new file mode 100644 index 000000000000..efdca0adf4df --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/component.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +components=spring-ai-image +groupId=org.apache.camel +artifactId=camel-spring-ai-image +version=4.19.0-SNAPSHOT +projectName=Camel :: Spring AI :: Image +projectDescription=Camel Spring AI Image Generation component diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/component/spring-ai-image b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/component/spring-ai-image new file mode 100644 index 000000000000..c442a9dfa7d6 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/component/spring-ai-image @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.springai.image.SpringAiImageComponent diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.springai.image.SpringAiImageConfiguration b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.springai.image.SpringAiImageConfiguration new file mode 100644 index 000000000000..0aca32537821 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.springai.image.SpringAiImageConfiguration @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.springai.image.SpringAiImageConfigurationConfigurer diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/spring-ai-image-component b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/spring-ai-image-component new file mode 100644 index 000000000000..a31a767cb600 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/spring-ai-image-component @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.springai.image.SpringAiImageComponentConfigurer diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/spring-ai-image-endpoint b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/spring-ai-image-endpoint new file mode 100644 index 000000000000..137720de9d56 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/configurer/spring-ai-image-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.springai.image.SpringAiImageEndpointConfigurer diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/urifactory/spring-ai-image-endpoint b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/urifactory/spring-ai-image-endpoint new file mode 100644 index 000000000000..2112355383d9 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/services/org/apache/camel/urifactory/spring-ai-image-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.springai.image.SpringAiImageEndpointUriFactory diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/docs/spring-ai-image-component.adoc b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/docs/spring-ai-image-component.adoc new file mode 100644 index 000000000000..13d151476efc --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/docs/spring-ai-image-component.adoc @@ -0,0 +1,197 @@ += Spring AI Image Component +:doctitle: Spring AI Image +:shortname: spring-ai-image +:artifactid: camel-spring-ai-image +:description: Generate images using Spring AI. +:since: 4.19 +:supportlevel: Preview +:tabs-sync-option: +:component-header: Only producer is supported +//Manually maintained attributes +:group: AI +:camel-spring-boot-name: spring-ai-image + +*Since Camel {since}* + +*{component-header}* + +The Spring AI Image component generates images from text prompts using any image model +supported by https://docs.spring.io/spring-ai/reference/[Spring AI], including OpenAI DALL-E, +Stability AI, and local models via Ollama. + +Maven users will need to add the following dependency to their `pom.xml`: + +[source,xml] +---- +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-ai-image</artifactId> + <version>x.x.x</version> +</dependency> +---- + +== URI format + +---- +spring-ai-image:imageId[?options] +---- + +Where *imageId* can be any string to uniquely identify the endpoint. + +// component options: START +include::partial$component-configure-options.adoc[] +include::partial$component-endpoint-options.adoc[] +include::partial$component-endpoint-headers.adoc[] +// component options: END + +== Usage + +=== Configuring the ImageModel + +The component requires a Spring AI `ImageModel` bean. When using Spring Boot with a Spring AI starter +(e.g., `spring-ai-starter-model-openai`), the `ImageModel` bean is auto-configured. + +=== Basic Image Generation + +Send a text prompt as the message body to generate an image: + +[source,java] +---- +from("direct:generate") + .setBody(constant("A camel walking through the desert at sunset")) + .to("spring-ai-image:gen?imageModel=#imageModel") + .log("Generated image: ${body.getClass().getSimpleName()}") + .process(exchange -> { + Image image = exchange.getIn().getBody(Image.class); + String base64Data = image.getB64Json(); // Base64-encoded image bytes + String imageUrl = image.getUrl(); // URL (provider-dependent) + log.info("Image base64 length: {}, URL: {}", + base64Data != null ? base64Data.length() : "N/A", imageUrl); + }); +---- + +The response body is a `org.springframework.ai.image.Image` object containing either base64-encoded +image data (`b64Json`) or a URL (`url`), depending on the provider and `responseFormat` setting. + +The component includes built-in type converters that automatically convert `Image` objects to `byte[]` +(by decoding the base64 data), `InputStream`, or `String`. This means downstream components like +`file:` can consume the image directly without any intermediate processing. + +=== Image Options + +Control image generation parameters via endpoint configuration or headers. +Headers override endpoint configuration at runtime. + +==== Via Endpoint Configuration + +[source,java] +---- +from("direct:generate") + .to("spring-ai-image:gen?imageModel=#imageModel" + + "&model=dall-e-3" + + "&width=512&height=512" + + "&responseFormat=b64_json" + + "&style=vivid"); +---- + +==== Via Headers + +[source,java] +---- +Exchange exchange = template.request("direct:generate", e -> { + e.getIn().setBody("A red apple on a white table"); + e.getIn().setHeader(SpringAiImageHeaders.WIDTH, 256); + e.getIn().setHeader(SpringAiImageHeaders.HEIGHT, 256); + e.getIn().setHeader(SpringAiImageHeaders.MODEL, "dall-e-3"); + e.getIn().setHeader(SpringAiImageHeaders.RESPONSE_FORMAT, "b64_json"); +}); +---- + +=== Saving Images to Files + +The component includes a built-in `TypeConverter` that automatically converts `Image` objects +to `byte[]`. This means you can pipe generated images directly to the `file:` component +without any intermediate processing: + +[source,java] +---- +// Generate and save — no processor needed +from("direct:generate") + .to("spring-ai-image:gen?imageModel=#imageModel&width=512&height=512") + .to("file:output?fileName=generated.png"); +---- + +The `Image` → `byte[]` conversion decodes the base64 data automatically. + +=== Using with Ollama (Local Image Generation) + +Ollama supports image generation models via its OpenAI-compatible API. Configure the +Spring AI OpenAI `ImageModel` to point at Ollama: + +[source,java] +---- +// Ollama returns application/x-ndjson — register a converter that handles it +MappingJackson2HttpMessageConverter ndjsonConverter = new MappingJackson2HttpMessageConverter(); +ndjsonConverter.setSupportedMediaTypes( + List.of(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-ndjson"))); + +RestClient.Builder restClientBuilder = RestClient.builder() + .messageConverters(converters -> converters.addFirst(ndjsonConverter)); + +OpenAiImageApi imageApi = OpenAiImageApi.builder() + .baseUrl("http://localhost:11434") + .apiKey("unused") + .restClientBuilder(restClientBuilder) + .build(); + +OpenAiImageOptions defaultOptions = OpenAiImageOptions.builder() + .model("x/flux2-klein:4b") + .build(); + +ImageModel imageModel = new OpenAiImageModel(imageApi, defaultOptions, + RetryUtils.DEFAULT_RETRY_TEMPLATE); +---- + +Then use the Camel endpoint to control resolution: + +[source,java] +---- +from("direct:generate") + .to("spring-ai-image:gen?imageModel=#imageModel&width=512&height=512") + .to("file:output?fileName=camel-image.png"); +---- + +=== Multiple Image Generation + +Set the `n` parameter to generate multiple images. When `n > 1`, the body contains a +`List<Image>` instead of a single `Image`: + +[source,java] +---- +from("direct:batch") + .to("spring-ai-image:gen?imageModel=#imageModel&n=3"); + +// Usage: +List<Image> images = template.requestBody("direct:batch", + "A camel in different art styles", List.class); +---- + +=== Type Converters + +The component registers automatic type converters for `Image` objects: + +[cols="1,1,2"] +|=== +| From | To | Behavior + +| `Image` | `byte[]` | Decodes base64 data +| `Image` | `InputStream` | Wraps decoded bytes in ByteArrayInputStream +| `Image` | `String` | Returns base64 string or URL +|=== + +== See Also + +* https://docs.spring.io/spring-ai/reference/api/image.html[Spring AI Image API] +* xref:spring-ai-chat-component.adoc[Spring AI Chat Component] + +include::spring-boot:partial$starter.adoc[] diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImage.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImage.java new file mode 100644 index 000000000000..6edae5967fae --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImage.java @@ -0,0 +1,24 @@ +/* + * 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.springai.image; + +public final class SpringAiImage { + public static final String SCHEME = "spring-ai-image"; + + private SpringAiImage() { + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageComponent.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageComponent.java new file mode 100644 index 000000000000..0bacfcc96324 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageComponent.java @@ -0,0 +1,62 @@ +/* + * 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.springai.image; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.annotations.Component; +import org.apache.camel.support.DefaultComponent; + +@Component(SpringAiImage.SCHEME) +public class SpringAiImageComponent extends DefaultComponent { + @Metadata + private SpringAiImageConfiguration configuration; + + public SpringAiImageComponent() { + this(null); + } + + public SpringAiImageComponent(CamelContext context) { + super(context); + + this.configuration = new SpringAiImageConfiguration(); + } + + public SpringAiImageConfiguration getConfiguration() { + return configuration; + } + + /** + * The configuration. + */ + public void setConfiguration(SpringAiImageConfiguration configuration) { + this.configuration = configuration; + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + SpringAiImageConfiguration configuration = this.configuration.copy(); + + SpringAiImageEndpoint endpoint = new SpringAiImageEndpoint(uri, this, remaining, configuration); + setProperties(endpoint, parameters); + + return endpoint; + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageConfiguration.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageConfiguration.java new file mode 100644 index 000000000000..f8b1b2d23cb1 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageConfiguration.java @@ -0,0 +1,136 @@ +/* + * 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.springai.image; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.Configurer; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; +import org.springframework.ai.image.ImageModel; + +@Configurer +@UriParams +public class SpringAiImageConfiguration implements Cloneable { + + @Metadata(required = true, autowired = true) + @UriParam + private ImageModel imageModel; + + @UriParam(description = "Number of images to generate") + private Integer n; + + @UriParam(description = "Image width in pixels") + private Integer width; + + @UriParam(description = "Image height in pixels") + private Integer height; + + @UriParam(description = "The model to use for image generation") + private String model; + + @UriParam(description = "Response format: url or b64_json") + private String responseFormat; + + @UriParam(description = "Image style (e.g., vivid, natural)") + private String style; + + public ImageModel getImageModel() { + return imageModel; + } + + /** + * The {@link ImageModel} to use for generating images. + */ + public void setImageModel(ImageModel imageModel) { + this.imageModel = imageModel; + } + + public Integer getN() { + return n; + } + + /** + * Number of images to generate. Default depends on the model provider. + */ + public void setN(Integer n) { + this.n = n; + } + + public Integer getWidth() { + return width; + } + + /** + * Image width in pixels. + */ + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + /** + * Image height in pixels. + */ + public void setHeight(Integer height) { + this.height = height; + } + + public String getModel() { + return model; + } + + /** + * The model to use for image generation (e.g., dall-e-3). + */ + public void setModel(String model) { + this.model = model; + } + + public String getResponseFormat() { + return responseFormat; + } + + /** + * Response format: url or b64_json. + */ + public void setResponseFormat(String responseFormat) { + this.responseFormat = responseFormat; + } + + public String getStyle() { + return style; + } + + /** + * Image style (e.g., vivid, natural). Support depends on the model provider. + */ + public void setStyle(String style) { + this.style = style; + } + + public SpringAiImageConfiguration copy() { + try { + return (SpringAiImageConfiguration) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageConverter.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageConverter.java new file mode 100644 index 000000000000..aa31837075de --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageConverter.java @@ -0,0 +1,70 @@ +/* + * 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.springai.image; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Base64; + +import org.apache.camel.Converter; +import org.springframework.ai.image.Image; + +/** + * Type converters for Spring AI {@link Image} objects. + * + * Enables automatic conversion of generated images to byte arrays or input streams, so they can be directly consumed by + * components like the file component without an intermediate processor. + * + * <pre> + * from("direct:generate") + * .to("spring-ai-image:gen?imageModel=#imageModel") + * .to("file:/tmp?fileName=image.png"); + * </pre> + */ +@Converter(generateLoader = true) +public final class SpringAiImageConverter { + + private SpringAiImageConverter() { + } + + @Converter + public static byte[] toBytes(Image image) { + if (image.getB64Json() != null) { + return Base64.getDecoder().decode(image.getB64Json()); + } + if (image.getUrl() != null) { + return image.getUrl().getBytes(); + } + return new byte[0]; + } + + @Converter + public static InputStream toInputStream(Image image) { + return new ByteArrayInputStream(toBytes(image)); + } + + @Converter + public static String toString(Image image) { + if (image.getB64Json() != null) { + return image.getB64Json(); + } + if (image.getUrl() != null) { + return image.getUrl(); + } + return ""; + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageEndpoint.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageEndpoint.java new file mode 100644 index 000000000000..7558d279c75d --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageEndpoint.java @@ -0,0 +1,74 @@ +/* + * 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.springai.image; + +import org.apache.camel.Category; +import org.apache.camel.Component; +import org.apache.camel.Consumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.apache.camel.support.DefaultEndpoint; + +/** + * Spring AI Image Generation + */ +@UriEndpoint(firstVersion = "4.19.0", scheme = SpringAiImage.SCHEME, title = "Spring AI Image", + syntax = "spring-ai-image:imageId", producerOnly = true, category = { + Category.AI + }, headersClass = SpringAiImageHeaders.class) +public class SpringAiImageEndpoint extends DefaultEndpoint { + @Metadata(required = true) + @UriPath(description = "The id") + private final String imageId; + + @UriParam + private SpringAiImageConfiguration configuration; + + public SpringAiImageEndpoint( + String endpointUri, + Component component, + String imageId, + SpringAiImageConfiguration configuration) { + + super(endpointUri, component); + + this.imageId = imageId; + this.configuration = configuration; + } + + public SpringAiImageConfiguration getConfiguration() { + return this.configuration; + } + + public String getImageId() { + return this.imageId; + } + + @Override + public Producer createProducer() throws Exception { + return new SpringAiImageProducer(this); + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + throw new UnsupportedOperationException("Consumer is not implemented for this component"); + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageHeaders.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageHeaders.java new file mode 100644 index 000000000000..3531571811f8 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageHeaders.java @@ -0,0 +1,55 @@ +/* + * 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.springai.image; + +import org.apache.camel.spi.Metadata; + +public final class SpringAiImageHeaders { + + @Metadata(description = "Number of images to generate", javaType = "Integer") + public static final String N = "CamelSpringAiImageN"; + + @Metadata(description = "Image width in pixels", javaType = "Integer") + public static final String WIDTH = "CamelSpringAiImageWidth"; + + @Metadata(description = "Image height in pixels", javaType = "Integer") + public static final String HEIGHT = "CamelSpringAiImageHeight"; + + @Metadata(description = "The model to use for image generation", javaType = "String") + public static final String MODEL = "CamelSpringAiImageModel"; + + @Metadata(description = "Response format: url or b64_json", javaType = "String") + public static final String RESPONSE_FORMAT = "CamelSpringAiImageResponseFormat"; + + @Metadata(description = "Image style (e.g., vivid, natural)", javaType = "String") + public static final String STYLE = "CamelSpringAiImageStyle"; + + @Metadata(description = "The image response metadata", + javaType = "org.springframework.ai.image.ImageResponseMetadata") + public static final String RESPONSE_METADATA = "CamelSpringAiImageResponseMetadata"; + + @Metadata(description = "The ImageGeneration object for single image results", + javaType = "org.springframework.ai.image.ImageGeneration") + public static final String IMAGE_GENERATION = "CamelSpringAiImageGeneration"; + + @Metadata(description = "List of ImageGeneration objects for multiple image results", + javaType = "java.util.List<org.springframework.ai.image.ImageGeneration>") + public static final String IMAGE_GENERATIONS = "CamelSpringAiImageGenerations"; + + private SpringAiImageHeaders() { + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageProducer.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageProducer.java new file mode 100644 index 000000000000..bf46bea6cdf5 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/java/org/apache/camel/component/springai/image/SpringAiImageProducer.java @@ -0,0 +1,126 @@ +/* + * 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.springai.image; + +import java.util.List; + +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.support.DefaultProducer; +import org.springframework.ai.image.Image; +import org.springframework.ai.image.ImageGeneration; +import org.springframework.ai.image.ImageModel; +import org.springframework.ai.image.ImageOptions; +import org.springframework.ai.image.ImageOptionsBuilder; +import org.springframework.ai.image.ImagePrompt; +import org.springframework.ai.image.ImageResponse; + +public class SpringAiImageProducer extends DefaultProducer { + + public SpringAiImageProducer(SpringAiImageEndpoint endpoint) { + super(endpoint); + } + + @Override + public SpringAiImageEndpoint getEndpoint() { + return (SpringAiImageEndpoint) super.getEndpoint(); + } + + @Override + public void process(Exchange exchange) throws Exception { + final Message message = exchange.getMessage(); + final ImageModel model = getEndpoint().getConfiguration().getImageModel(); + final Object body = message.getBody(); + + ImagePrompt imagePrompt; + + if (body instanceof ImagePrompt prompt) { + imagePrompt = prompt; + } else if (body instanceof String text) { + ImageOptions options = buildImageOptions(exchange); + imagePrompt = new ImagePrompt(text, options); + } else { + throw new IllegalArgumentException( + "Message body must be a String or ImagePrompt, but was: " + + (body != null ? body.getClass().getName() : "null")); + } + + ImageResponse response = model.call(imagePrompt); + + if (response.getResults() != null && !response.getResults().isEmpty()) { + if (response.getResults().size() == 1) { + ImageGeneration generation = response.getResults().get(0); + message.setBody(generation.getOutput()); + message.setHeader(SpringAiImageHeaders.IMAGE_GENERATION, generation); + } else { + List<Image> images = response.getResults().stream() + .map(ImageGeneration::getOutput) + .toList(); + message.setBody(images); + message.setHeader(SpringAiImageHeaders.IMAGE_GENERATIONS, response.getResults()); + } + } + + if (response.getMetadata() != null) { + message.setHeader(SpringAiImageHeaders.RESPONSE_METADATA, response.getMetadata()); + } + } + + private ImageOptions buildImageOptions(Exchange exchange) { + SpringAiImageConfiguration config = getEndpoint().getConfiguration(); + ImageOptionsBuilder builder = ImageOptionsBuilder.builder(); + + // Headers override configuration + Integer n = getOption(exchange, SpringAiImageHeaders.N, Integer.class, config.getN()); + if (n != null) { + builder.N(n); + } + + Integer width = getOption(exchange, SpringAiImageHeaders.WIDTH, Integer.class, config.getWidth()); + if (width != null) { + builder.width(width); + } + + Integer height = getOption(exchange, SpringAiImageHeaders.HEIGHT, Integer.class, config.getHeight()); + if (height != null) { + builder.height(height); + } + + String model = getOption(exchange, SpringAiImageHeaders.MODEL, String.class, config.getModel()); + if (model != null) { + builder.model(model); + } + + String responseFormat = getOption(exchange, SpringAiImageHeaders.RESPONSE_FORMAT, String.class, + config.getResponseFormat()); + if (responseFormat != null) { + builder.responseFormat(responseFormat); + } + + String style = getOption(exchange, SpringAiImageHeaders.STYLE, String.class, config.getStyle()); + if (style != null) { + builder.style(style); + } + + return builder.build(); + } + + private <T> T getOption(Exchange exchange, String headerName, Class<T> type, T configValue) { + T headerValue = exchange.getIn().getHeader(headerName, type); + return headerValue != null ? headerValue : configValue; + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/java/org/apache/camel/component/springai/image/SpringAiImageOllamaIT.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/java/org/apache/camel/component/springai/image/SpringAiImageOllamaIT.java new file mode 100644 index 000000000000..bdbbb6621687 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/java/org/apache/camel/component/springai/image/SpringAiImageOllamaIT.java @@ -0,0 +1,247 @@ +/* + * 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.springai.image; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.infra.ollama.services.OllamaService; +import org.apache.camel.test.infra.ollama.services.OllamaServiceFactory; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.image.Image; +import org.springframework.ai.image.ImageModel; +import org.springframework.ai.openai.OpenAiImageModel; +import org.springframework.ai.openai.OpenAiImageOptions; +import org.springframework.ai.openai.api.OpenAiImageApi; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for the Spring AI Image component using Ollama's OpenAI-compatible API. + * + * Uses x/flux2-klein:4b model running on Ollama, accessed via OpenAI base URL override. The model must be pre-pulled in + * Ollama. + * + * Ollama returns application/x-ndjson content type, so a custom RestClient.Builder is used to register a Jackson + * converter that supports this media type. + */ +@DisabledIfSystemProperty(named = "ci.env.name", matches = ".*", disabledReason = "Disabled unless running in CI") +public class SpringAiImageOllamaIT extends CamelTestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(SpringAiImageOllamaIT.class); + + private static final String IMAGE_MODEL_NAME = "x/flux2-klein:4b"; + + @TempDir( + /* cleanup = CleanupMode.NEVER */ // Uncomment to double check the image + ) + Path tempDir; + + @RegisterExtension + static OllamaService OLLAMA = OllamaServiceFactory.createSingletonService(); + + private ImageModel imageModel; + + @Override + protected void setupResources() throws Exception { + super.setupResources(); + + LOG.info("*******************************************************************************"); + LOG.info("* Generated images directory: {}", tempDir.toAbsolutePath()); + LOG.info("*******************************************************************************"); + LOG.info("Setting up ImageModel with Ollama at {} using model '{}'", OLLAMA.baseUrl(), IMAGE_MODEL_NAME); + + // Ollama returns application/x-ndjson which Spring's default RestClient doesn't handle. + // Add a Jackson converter that supports this media type. + MappingJackson2HttpMessageConverter ndjsonConverter = new MappingJackson2HttpMessageConverter(); + ndjsonConverter.setSupportedMediaTypes( + java.util.List.of(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-ndjson"))); + + RestClient.Builder restClientBuilder = RestClient.builder() + .messageConverters(converters -> converters.addFirst(ndjsonConverter)); + + OpenAiImageApi imageApi = OpenAiImageApi.builder() + .baseUrl(OLLAMA.baseUrl()) + .apiKey("unused") + .restClientBuilder(restClientBuilder) + .build(); + + // Only set the model name here; width/height are configured on the Camel endpoint or via headers + OpenAiImageOptions defaultOptions = OpenAiImageOptions.builder() + .model(IMAGE_MODEL_NAME) + .build(); + + imageModel = new OpenAiImageModel(imageApi, defaultOptions, RetryUtils.DEFAULT_RETRY_TEMPLATE); + LOG.info("ImageModel initialized successfully"); + } + + @Test + public void testImageGeneration() { + String prompt = "A camel walking through a beautiful sunset over the ocean"; + LOG.info("testImageGeneration - Sending prompt: '{}'", prompt); + + Exchange exchange = template().request("direct:generate", e -> { + e.getIn().setBody(prompt); + }); + + assertThat(exchange).isNotNull(); + assertThat(exchange.getException()).isNull(); + + Object body = exchange.getMessage().getBody(); + assertThat(body).isInstanceOf(Image.class); + + Image image = (Image) body; + logImageResult("testImageGeneration", image); + assertThat(image.getUrl() != null || image.getB64Json() != null) + .as("Image should have URL or base64 data") + .isTrue(); + } + + @Test + public void testImageGenerationWithOptions() { + String prompt = "A camel standing next to a red apple on a white table"; + LOG.info("testImageGenerationWithOptions - Sending prompt: '{}' with model={}", prompt, IMAGE_MODEL_NAME); + + Exchange exchange = template().request("direct:generateWithOptions", e -> { + e.getIn().setBody(prompt); + }); + + assertThat(exchange).isNotNull(); + assertThat(exchange.getException()).isNull(); + + Object body = exchange.getMessage().getBody(); + assertThat(body).isInstanceOf(Image.class); + logImageResult("testImageGenerationWithOptions", (Image) body); + } + + @Test + public void testImageGenerationWithHeaderOverrides() { + String prompt = "A camel riding through the desert under a blue sky with white clouds"; + LOG.info("testImageGenerationWithHeaderOverrides - Sending prompt: '{}' with headers: model={}, width=256, height=256", + prompt, IMAGE_MODEL_NAME); + + Exchange exchange = template().request("direct:generate", e -> { + e.getIn().setBody(prompt); + e.getIn().setHeader(SpringAiImageHeaders.MODEL, IMAGE_MODEL_NAME); + // Override width/height via headers (smaller than endpoint's 512x512) + e.getIn().setHeader(SpringAiImageHeaders.WIDTH, 256); + e.getIn().setHeader(SpringAiImageHeaders.HEIGHT, 256); + }); + + assertThat(exchange).isNotNull(); + assertThat(exchange.getException()).isNull(); + + Object body = exchange.getMessage().getBody(); + assertThat(body).isInstanceOf(Image.class); + logImageResult("testImageGenerationWithHeaderOverrides", (Image) body); + } + + @Test + public void testImageGenerationSaveToFile() throws Exception { + String prompt = "A camel resting under a green tree in a field"; + LOG.info("testImageGenerationSaveToFile - Sending prompt: '{}', saving via file component to {}", + prompt, tempDir.toAbsolutePath()); + + Exchange exchange = template().request("direct:generateAndSave", e -> { + e.getIn().setBody(prompt); + }); + + assertThat(exchange).isNotNull(); + assertThat(exchange.getException()).isNull(); + + // Verify the file was written by the Camel file component (via Image -> byte[] type converter) + File outputFile = tempDir.resolve("camel-file-saved.png").toFile(); + assertThat(outputFile).exists(); + assertThat(outputFile.length()).isGreaterThan(0); + LOG.info("testImageGenerationSaveToFile - Image saved via file component to {} ({} bytes)", + outputFile.getAbsolutePath(), outputFile.length()); + } + + private void logImageResult(String testName, Image image) { + if (image.getUrl() != null) { + LOG.info("{} - Image URL: {}", testName, image.getUrl()); + } + if (image.getB64Json() != null) { + // Save the image to target/generated-images/ for inspection + saveImage(testName, image); + } + if (image.getUrl() == null && image.getB64Json() == null) { + LOG.warn("{} - Image has neither URL nor base64 data!", testName); + } + } + + private void saveImage(String testName, Image image) { + if (image.getB64Json() == null) { + return; + } + try { + byte[] imageBytes = Base64.getDecoder().decode(image.getB64Json()); + Path outputPath = tempDir.resolve(testName + ".png"); + Files.write(outputPath, imageBytes); + LOG.info("{} - Image saved to: {} ({} bytes)", testName, outputPath.toAbsolutePath(), imageBytes.length); + } catch (Exception e) { + LOG.warn("{} - Failed to save image: {}", testName, e.getMessage()); + } + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + getCamelContext().getRegistry().bind("imageModel", imageModel); + + // Width and height set on the Camel endpoint to reduce image size (~500KB) + from("direct:generate") + .log("Generating image with prompt: ${body}") + .to("spring-ai-image:generate?imageModel=#imageModel&width=512&height=512") + .log("Image generated: ${body.getClass().getSimpleName()}"); + + // Model, width, height and responseFormat all set via Camel endpoint options + from("direct:generateWithOptions") + .log("Generating image with options, prompt: ${body}") + .to("spring-ai-image:generateWithOptions?imageModel=#imageModel" + + "&model=" + IMAGE_MODEL_NAME + + "&width=512&height=512") + .log("Image generated with options: ${body.getClass().getSimpleName()}"); + + // Generate an image and save it to a file. + // The Image -> byte[] conversion happens automatically via SpringAiImageConverter. + from("direct:generateAndSave") + .log("Generating image for file save, prompt: ${body}") + .to("spring-ai-image:generateAndSave?imageModel=#imageModel&width=512&height=512") + .log("Image generated, saving to file (type conversion: Image -> byte[])") + .toF("file:%s?fileName=camel-file-saved.png", tempDir.toAbsolutePath()) + .log("Image saved to file"); + } + }; + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/resources/log4j2.properties b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/resources/log4j2.properties new file mode 100644 index 000000000000..554a2011875f --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/resources/log4j2.properties @@ -0,0 +1,50 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-spring-ai-image-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n + +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n + +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file + +# Spring AI Image component +logger.springai-image.name = org.apache.camel.component.springai.image +logger.springai-image.level = DEBUG + +# Spring AI OpenAI image model (request/response) +logger.openai-image.name = org.springframework.ai.openai.OpenAiImageModel +logger.openai-image.level = DEBUG + +# Spring AI OpenAI image API (HTTP calls) +logger.openai-api.name = org.springframework.ai.openai.api.OpenAiImageApi +logger.openai-api.level = DEBUG + +# Camel route processing +logger.camel.name = org.apache.camel +logger.camel.level = INFO + +# Test class +logger.test.name = org.apache.camel.component.springai.image.SpringAiImageOllamaIT +logger.test.level = DEBUG diff --git a/components/camel-spring-parent/camel-spring-ai/pom.xml b/components/camel-spring-parent/camel-spring-ai/pom.xml index 21b2d6eb5a93..70f4c232e55e 100644 --- a/components/camel-spring-parent/camel-spring-ai/pom.xml +++ b/components/camel-spring-parent/camel-spring-ai/pom.xml @@ -55,6 +55,7 @@ <module>camel-spring-ai-embeddings</module> <module>camel-spring-ai-vector-store</module> <module>camel-spring-ai-tools</module> + <module>camel-spring-ai-image</module> </modules> </project> diff --git a/docs/components/modules/ROOT/examples/json/spring-ai-image.json b/docs/components/modules/ROOT/examples/json/spring-ai-image.json new file mode 120000 index 000000000000..87f812a01aac --- /dev/null +++ b/docs/components/modules/ROOT/examples/json/spring-ai-image.json @@ -0,0 +1 @@ +../../../../../../components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/generated/resources/META-INF/org/apache/camel/component/springai/image/spring-ai-image.json \ No newline at end of file diff --git a/docs/components/modules/ROOT/nav.adoc b/docs/components/modules/ROOT/nav.adoc index 8a0d98049306..db785be79049 100644 --- a/docs/components/modules/ROOT/nav.adoc +++ b/docs/components/modules/ROOT/nav.adoc @@ -23,6 +23,7 @@ *** xref:qdrant-component.adoc[Qdrant] *** xref:spring-ai-chat-component.adoc[Spring AI Chat] *** xref:spring-ai-embeddings-component.adoc[Spring AI Embeddings] +*** xref:spring-ai-image-component.adoc[Spring AI Image] *** xref:spring-ai-tools-component.adoc[Spring AI Tools] *** xref:spring-ai-vector-store-component.adoc[Spring AI Vector Store] *** xref:tensorflow-serving-component.adoc[TensorFlow Serving] diff --git a/docs/components/modules/ROOT/pages/spring-ai-image-component.adoc b/docs/components/modules/ROOT/pages/spring-ai-image-component.adoc new file mode 120000 index 000000000000..7753650854a4 --- /dev/null +++ b/docs/components/modules/ROOT/pages/spring-ai-image-component.adoc @@ -0,0 +1 @@ +../../../../../components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/main/docs/spring-ai-image-component.adoc \ No newline at end of file diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringAiChatEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringAiChatEndpointBuilderFactory.java index f371390d764d..2b4f40aec09f 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringAiChatEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringAiChatEndpointBuilderFactory.java @@ -173,6 +173,22 @@ public interface SpringAiChatEndpointBuilderFactory { doSetProperty("tags", tags); return this; } + /** + * Comma-separated tool names for selecting tools by name via Spring + * AI's ToolCallbackResolver. This enables selecting Spring Tool + * annotated beans or any registered ToolCallback by name. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: producer + * + * @param toolNames the value to set + * @return the dsl builder + */ + default SpringAiChatEndpointBuilder toolNames(String toolNames) { + doSetProperty("toolNames", toolNames); + return this; + } /** * Default user message text for multimodal requests. Can be combined * with media data in the message body. @@ -593,6 +609,83 @@ public interface SpringAiChatEndpointBuilderFactory { doSetProperty("maxTokens", maxTokens); return this; } + /** + * MCP server configurations. Define servers using prefix notation: + * mcpServer..transportType=stdiosse, mcpServer..command= (stdio), + * mcpServer..args= (stdio), mcpServer..url= (sse), + * mcpServer..oauthProfile= (OAuth profile for HTTP auth, requires + * camel-oauth). This is a multi-value option with prefix: mcpServer. + * + * The option is a: <code>java.util.Map<java.lang.String, + * java.lang.Object></code> type. + * The option is multivalued, and you can use the mcpServer(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param key the option key + * @param value the option value + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder mcpServer(String key, Object value) { + doSetMultiValueProperty("mcpServer", "mcpServer." + key, value); + return this; + } + /** + * MCP server configurations. Define servers using prefix notation: + * mcpServer..transportType=stdiosse, mcpServer..command= (stdio), + * mcpServer..args= (stdio), mcpServer..url= (sse), + * mcpServer..oauthProfile= (OAuth profile for HTTP auth, requires + * camel-oauth). This is a multi-value option with prefix: mcpServer. + * + * The option is a: <code>java.util.Map<java.lang.String, + * java.lang.Object></code> type. + * The option is multivalued, and you can use the mcpServer(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param values the values + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder mcpServer(Map values) { + doSetMultiValueProperties("mcpServer", "mcpServer.", values); + return this; + } + /** + * Timeout in seconds for MCP operations including tool execution and + * initialization. + * + * The option is a: <code>int</code> type. + * + * Default: 20 + * Group: advanced + * + * @param mcpTimeout the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder mcpTimeout(int mcpTimeout) { + doSetProperty("mcpTimeout", mcpTimeout); + return this; + } + /** + * Timeout in seconds for MCP operations including tool execution and + * initialization. + * + * The option will be converted to a <code>int</code> type. + * + * Default: 20 + * Group: advanced + * + * @param mcpTimeout the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder mcpTimeout(String mcpTimeout) { + doSetProperty("mcpTimeout", mcpTimeout); + return this; + } /** * The Java class to use for BEAN output format conversion. Required * when outputFormat is BEAN. @@ -676,6 +769,74 @@ public interface SpringAiChatEndpointBuilderFactory { doSetProperty("structuredOutputConverter", structuredOutputConverter); return this; } + /** + * Enable structured output validation with automatic retry on + * validation failure. When enabled, the + * StructuredOutputValidationAdvisor validates the response against a + * JSON Schema and re-prompts the LLM with validation errors if the + * output is invalid. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: advanced + * + * @param structuredOutputValidation the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder structuredOutputValidation(boolean structuredOutputValidation) { + doSetProperty("structuredOutputValidation", structuredOutputValidation); + return this; + } + /** + * Enable structured output validation with automatic retry on + * validation failure. When enabled, the + * StructuredOutputValidationAdvisor validates the response against a + * JSON Schema and re-prompts the LLM with validation errors if the + * output is invalid. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: false + * Group: advanced + * + * @param structuredOutputValidation the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder structuredOutputValidation(String structuredOutputValidation) { + doSetProperty("structuredOutputValidation", structuredOutputValidation); + return this; + } + /** + * Maximum number of retry attempts for structured output validation. + * + * The option is a: <code>int</code> type. + * + * Default: 3 + * Group: advanced + * + * @param structuredOutputValidationMaxAttempts the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder structuredOutputValidationMaxAttempts(int structuredOutputValidationMaxAttempts) { + doSetProperty("structuredOutputValidationMaxAttempts", structuredOutputValidationMaxAttempts); + return this; + } + /** + * Maximum number of retry attempts for structured output validation. + * + * The option will be converted to a <code>int</code> type. + * + * Default: 3 + * Group: advanced + * + * @param structuredOutputValidationMaxAttempts the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder structuredOutputValidationMaxAttempts(String structuredOutputValidationMaxAttempts) { + doSetProperty("structuredOutputValidationMaxAttempts", structuredOutputValidationMaxAttempts); + return this; + } /** * Metadata to attach to system messages. This metadata can be used for * tracking system prompt versions, model configurations, or other @@ -739,6 +900,73 @@ public interface SpringAiChatEndpointBuilderFactory { doSetProperty("temperature", temperature); return this; } + /** + * List of ToolCallback instances to register alongside Camel route + * tools. Use + * MethodToolCallbackProvider.builder().toolObjects(bean).build().getToolCallbacks() to resolve callbacks from Tool-annotated beans. + * + * The option is a: + * <code>java.util.List<org.springframework.ai.tool.ToolCallback></code> type. + * + * Group: advanced + * + * @param toolCallbacks the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder toolCallbacks(List<org.springframework.ai.tool.ToolCallback> toolCallbacks) { + doSetProperty("toolCallbacks", toolCallbacks); + return this; + } + /** + * List of ToolCallback instances to register alongside Camel route + * tools. Use + * MethodToolCallbackProvider.builder().toolObjects(bean).build().getToolCallbacks() to resolve callbacks from Tool-annotated beans. + * + * The option will be converted to a + * <code>java.util.List<org.springframework.ai.tool.ToolCallback></code> type. + * + * Group: advanced + * + * @param toolCallbacks the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder toolCallbacks(String toolCallbacks) { + doSetProperty("toolCallbacks", toolCallbacks); + return this; + } + /** + * Context map to pass to tools during execution. Tool methods accepting + * a ToolContext parameter will receive these values. + * + * The option is a: <code>java.util.Map<java.lang.String, + * java.lang.Object></code> type. + * + * Group: advanced + * + * @param toolContext the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder toolContext(Map<java.lang.String, java.lang.Object> toolContext) { + doSetProperty("toolContext", toolContext); + return this; + } + /** + * Context map to pass to tools during execution. Tool methods accepting + * a ToolContext parameter will receive these values. + * + * The option will be converted to a + * <code>java.util.Map<java.lang.String, java.lang.Object></code> + * type. + * + * Group: advanced + * + * @param toolContext the value to set + * @return the dsl builder + */ + default AdvancedSpringAiChatEndpointBuilder toolContext(String toolContext) { + doSetProperty("toolContext", toolContext); + return this; + } /** * Top K parameter for sampling. * @@ -1301,6 +1529,31 @@ public interface SpringAiChatEndpointBuilderFactory { public String springAiChatResponseMetadata() { return "CamelSpringAiChatResponseMetadata"; } + /** + * Comma-separated tool names for selecting tools by name via + * ToolCallbackResolver. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code SpringAiChatToolNames}. + */ + public String springAiChatToolNames() { + return "CamelSpringAiChatToolNames"; + } + /** + * Context map to pass to tools during execution. + * + * The option is a: {@code java.util.Map<String, Object>} type. + * + * Group: producer + * + * @return the name of the header {@code SpringAiChatToolContext}. + */ + public String springAiChatToolContext() { + return "CamelSpringAiChatToolContext"; + } } static SpringAiChatEndpointBuilder endpointBuilder(String componentName, String path) { class SpringAiChatEndpointBuilderImpl extends AbstractEndpointBuilder implements SpringAiChatEndpointBuilder, AdvancedSpringAiChatEndpointBuilder { diff --git a/parent/pom.xml b/parent/pom.xml index be26675beba0..44f007a87d53 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -2625,6 +2625,11 @@ <artifactId>camel-spring-ai-embeddings</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-ai-image</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring-ai-tools</artifactId>
