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

davsclaus pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new ae7d4581f3fc CAMEL-24526/24529/24530/24534: camel-ai fixes backport to 
camel-4.18.x (#25896)
ae7d4581f3fc is described below

commit ae7d4581f3fcba8ce83b7d834ada41679980568a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Sun Aug 30 13:15:25 2026 +0200

    CAMEL-24526/24529/24530/24534: camel-ai fixes backport to camel-4.18.x 
(#25896)
    
    Straight cherry-pick backport to camel-4.18.x of four camel-ai fixes 
already reviewed and merged on main:
    
    - CAMEL-24526: camel-djl - reject the zoo tabular applications instead of 
returning no-op predictors (silent wrong result)
    - CAMEL-24529: camel-djl - close the loaded ZooModel when the producer 
stops (native-memory leak)
    - CAMEL-24530: camel-djl - close the InputStream opened from File/Path 
bodies in DJLConverter (FD leak)
    - CAMEL-24534: camel-qdrant - honour metadata value types instead of 
casting them to String (runtime ClassCastException on numeric metadata)
    
    CAMEL-24528 (camel-huggingface) is not included: the camel-huggingface 
module does not exist on camel-4.18.x.
    
    Each commit is a clean cherry-pick of the merged main commit with no manual 
conflict resolution.
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../apache/camel/component/djl/DJLConverter.java   | 16 ++++--
 .../apache/camel/component/djl/DJLProducer.java    |  6 ++
 .../component/djl/model/AbstractPredictor.java     |  9 +++
 .../djl/model/ModelPredictorProducer.java          | 15 ++---
 .../djl/model/audio/ZooAudioPredictor.java         |  7 +++
 .../djl/model/cv/AbstractCvZooPredictor.java       |  7 +++
 .../djl/model/cv/ZooImageGenerationPredictor.java  |  7 +++
 .../djl/model/nlp/AbstractNlpZooPredictor.java     |  7 +++
 .../djl/model/nlp/ZooQuestionAnswerPredictor.java  |  7 +++
 .../tabular/ZooLinearRegressionPredictor.java      | 32 -----------
 .../tabular/ZooSoftmaxRegressionPredictor.java     | 32 -----------
 .../model/timeseries/ZooForecastingPredictor.java  |  7 +++
 .../camel/component/djl/DJLConverterTest.java      | 49 ++++++++++++++++
 .../camel/component/djl/DJLProducerTest.java       | 43 ++++++++++++++
 .../djl/model/ModelPredictorProducerTest.java      | 20 ++++++-
 .../QdrantEmbeddingsDataTypeTransformer.java       | 24 +++++++-
 .../QdrantEmbeddingsDataTypeTransformerTest.java   | 66 ++++++++++++++++++++++
 17 files changed, 275 insertions(+), 79 deletions(-)

diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLConverter.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLConverter.java
index 1a841cde1745..8bcd6c895916 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLConverter.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLConverter.java
@@ -48,12 +48,16 @@ public class DJLConverter {
 
     @Converter
     public static Image toImage(File file) throws IOException {
-        return toImage(new FileInputStream(file));
+        try (InputStream inputStream = new FileInputStream(file)) {
+            return toImage(inputStream);
+        }
     }
 
     @Converter
     public static Image toImage(Path path) throws IOException {
-        return toImage(Files.newInputStream(path));
+        try (InputStream inputStream = Files.newInputStream(path)) {
+            return toImage(inputStream);
+        }
     }
 
     @Converter
@@ -105,12 +109,16 @@ public class DJLConverter {
 
     @Converter
     public static Audio toAudio(File file) throws IOException {
-        return toAudio(new FileInputStream(file));
+        try (InputStream inputStream = new FileInputStream(file)) {
+            return toAudio(inputStream);
+        }
     }
 
     @Converter
     public static Audio toAudio(Path path) throws IOException {
-        return toAudio(Files.newInputStream(path));
+        try (InputStream inputStream = Files.newInputStream(path)) {
+            return toAudio(inputStream);
+        }
     }
 
     @Converter
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLProducer.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLProducer.java
index d82c57fac13f..e0552a868850 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLProducer.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/DJLProducer.java
@@ -36,4 +36,10 @@ public class DJLProducer extends DefaultProducer {
     public void process(Exchange exchange) throws Exception {
         this.predictor.process(exchange);
     }
+
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+        this.predictor.close();
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/AbstractPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/AbstractPredictor.java
index 9cc43f34a673..c98bbb5eb551 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/AbstractPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/AbstractPredictor.java
@@ -29,6 +29,15 @@ public abstract class AbstractPredictor {
 
     public abstract void process(Exchange exchange) throws Exception;
 
+    /**
+     * Releases any resources held by this predictor, such as a model loaded 
from the DJL model zoo. Called when the
+     * owning producer is stopped. The default implementation does nothing; 
predictors that keep a long-lived model
+     * override this method to close it.
+     */
+    public void close() {
+        // no-op by default
+    }
+
     protected DJLEndpoint getEndpoint() {
         return endpoint;
     }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/ModelPredictorProducer.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/ModelPredictorProducer.java
index 3cf86bbcf719..775d66e85d00 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/ModelPredictorProducer.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/ModelPredictorProducer.java
@@ -56,8 +56,6 @@ import 
org.apache.camel.component.djl.model.nlp.ZooTextGenerationPredictor;
 import 
org.apache.camel.component.djl.model.nlp.ZooTokenClassificationPredictor;
 import org.apache.camel.component.djl.model.nlp.ZooWordEmbeddingPredictor;
 import org.apache.camel.component.djl.model.tabular.CustomTabularPredictor;
-import 
org.apache.camel.component.djl.model.tabular.ZooLinearRegressionPredictor;
-import 
org.apache.camel.component.djl.model.tabular.ZooSoftmaxRegressionPredictor;
 import 
org.apache.camel.component.djl.model.timeseries.CustomForecastingPredictor;
 import org.apache.camel.component.djl.model.timeseries.ZooForecastingPredictor;
 
@@ -138,11 +136,14 @@ public final class ModelPredictorProducer {
             return new ZooTextEmbeddingPredictor(endpoint);
         }
 
-        // Tabular
-        if (LINEAR_REGRESSION.getPath().equals(applicationPath)) {
-            return new ZooLinearRegressionPredictor(endpoint);
-        } else if (SOFTMAX_REGRESSION.getPath().equals(applicationPath)) {
-            return new ZooSoftmaxRegressionPredictor(endpoint);
+        // Tabular: the DJL model zoo does not publish tabular regression 
models, and the input and
+        // output types of a tabular model are specific to the user's data, so 
there is no generic zoo
+        // predictor for these applications. Users must supply their own model 
and translator and use
+        // the custom variant (see getCustomPredictor / 
CustomTabularPredictor) instead.
+        if (LINEAR_REGRESSION.getPath().equals(applicationPath) || 
SOFTMAX_REGRESSION.getPath().equals(applicationPath)) {
+            throw new RuntimeCamelException(
+                    "Zoo models are not available for tabular application: " + 
applicationPath
+                                            + ". Provide your own model and 
translator and use the custom predictor instead.");
         }
 
         // Audio
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/audio/ZooAudioPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/audio/ZooAudioPredictor.java
index 9d369c922087..e35048a22a99 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/audio/ZooAudioPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/audio/ZooAudioPredictor.java
@@ -74,4 +74,11 @@ public class ZooAudioPredictor extends AbstractPredictor {
             throw new RuntimeCamelException("Could not process input or 
output", e);
         }
     }
+
+    @Override
+    public void close() {
+        if (model != null) {
+            model.close();
+        }
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/AbstractCvZooPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/AbstractCvZooPredictor.java
index cfdef6708439..e1a37318bebf 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/AbstractCvZooPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/AbstractCvZooPredictor.java
@@ -55,4 +55,11 @@ public abstract class AbstractCvZooPredictor<T> extends 
AbstractPredictor {
             throw new RuntimeCamelException("Could not process input or 
output", e);
         }
     }
+
+    @Override
+    public void close() {
+        if (model != null) {
+            model.close();
+        }
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/ZooImageGenerationPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/ZooImageGenerationPredictor.java
index 9101e7fd22b4..bafc76db3a5e 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/ZooImageGenerationPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/cv/ZooImageGenerationPredictor.java
@@ -73,4 +73,11 @@ public class ZooImageGenerationPredictor extends 
AbstractPredictor {
             throw new RuntimeCamelException("Could not process input or 
output", e);
         }
     }
+
+    @Override
+    public void close() {
+        if (model != null) {
+            model.close();
+        }
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/AbstractNlpZooPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/AbstractNlpZooPredictor.java
index 9feeae72ca98..5ccb2a621033 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/AbstractNlpZooPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/AbstractNlpZooPredictor.java
@@ -52,4 +52,11 @@ public abstract class AbstractNlpZooPredictor<T> extends 
AbstractPredictor {
             throw new RuntimeCamelException("Could not process input or 
output", e);
         }
     }
+
+    @Override
+    public void close() {
+        if (model != null) {
+            model.close();
+        }
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/ZooQuestionAnswerPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/ZooQuestionAnswerPredictor.java
index 259d2916496e..f4ad767291bb 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/ZooQuestionAnswerPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/nlp/ZooQuestionAnswerPredictor.java
@@ -82,4 +82,11 @@ public class ZooQuestionAnswerPredictor extends 
AbstractPredictor {
             throw new RuntimeCamelException("Could not process input or 
output", e);
         }
     }
+
+    @Override
+    public void close() {
+        if (model != null) {
+            model.close();
+        }
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/tabular/ZooLinearRegressionPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/tabular/ZooLinearRegressionPredictor.java
deleted file mode 100644
index 43961f8a28bf..000000000000
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/tabular/ZooLinearRegressionPredictor.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.djl.model.tabular;
-
-import org.apache.camel.Exchange;
-import org.apache.camel.component.djl.DJLEndpoint;
-import org.apache.camel.component.djl.model.AbstractPredictor;
-
-public class ZooLinearRegressionPredictor extends AbstractPredictor {
-    public ZooLinearRegressionPredictor(DJLEndpoint endpoint) {
-        super(endpoint);
-    }
-
-    @Override
-    public void process(Exchange exchange) throws Exception {
-        // TODO: impl
-    }
-}
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/tabular/ZooSoftmaxRegressionPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/tabular/ZooSoftmaxRegressionPredictor.java
deleted file mode 100644
index ee2f684e5c58..000000000000
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/tabular/ZooSoftmaxRegressionPredictor.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.djl.model.tabular;
-
-import org.apache.camel.Exchange;
-import org.apache.camel.component.djl.DJLEndpoint;
-import org.apache.camel.component.djl.model.AbstractPredictor;
-
-public class ZooSoftmaxRegressionPredictor extends AbstractPredictor {
-    public ZooSoftmaxRegressionPredictor(DJLEndpoint endpoint) {
-        super(endpoint);
-    }
-
-    @Override
-    public void process(Exchange exchange) throws Exception {
-        // TODO: impl
-    }
-}
diff --git 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/timeseries/ZooForecastingPredictor.java
 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/timeseries/ZooForecastingPredictor.java
index c79fa3f0a9d7..6c5d04ce09ed 100644
--- 
a/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/timeseries/ZooForecastingPredictor.java
+++ 
b/components/camel-ai/camel-djl/src/main/java/org/apache/camel/component/djl/model/timeseries/ZooForecastingPredictor.java
@@ -74,4 +74,11 @@ public class ZooForecastingPredictor extends 
AbstractPredictor {
             throw new RuntimeCamelException("Could not process input or 
output", e);
         }
     }
+
+    @Override
+    public void close() {
+        if (model != null) {
+            model.close();
+        }
+    }
 }
diff --git 
a/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/DJLConverterTest.java
 
b/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/DJLConverterTest.java
new file mode 100644
index 000000000000..19e74069b0ef
--- /dev/null
+++ 
b/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/DJLConverterTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.djl;
+
+import java.io.File;
+import java.nio.file.Path;
+
+import ai.djl.modality.cv.Image;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DJLConverterTest {
+
+    private static final String IMAGE_RESOURCE = "/data/detect/kitten.jpg";
+
+    // Converting from a File opens a stream internally; it must be closed 
(try-with-resources) so the
+    // file descriptor is not leaked per conversion. The conversion must still 
return a valid Image.
+    @Test
+    void toImageFromFileReturnsImageWithoutLeakingStream() throws Exception {
+        File file = new 
File(DJLConverterTest.class.getResource(IMAGE_RESOURCE).toURI());
+        Image image = DJLConverter.toImage(file);
+        assertNotNull(image);
+        assertTrue(image.getWidth() > 0 && image.getHeight() > 0);
+    }
+
+    @Test
+    void toImageFromPathReturnsImageWithoutLeakingStream() throws Exception {
+        Path path = 
Path.of(DJLConverterTest.class.getResource(IMAGE_RESOURCE).toURI());
+        Image image = DJLConverter.toImage(path);
+        assertNotNull(image);
+        assertTrue(image.getWidth() > 0 && image.getHeight() > 0);
+    }
+}
diff --git 
a/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/DJLProducerTest.java
 
b/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/DJLProducerTest.java
new file mode 100644
index 000000000000..a3e5bf58861e
--- /dev/null
+++ 
b/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/DJLProducerTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.djl;
+
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+class DJLProducerTest {
+
+    // Stopping the producer must release the predictor so a zoo model does 
not leak native memory across
+    // restart/redeploy (DJLProducer.doStop -> AbstractPredictor.close). For 
the custom (model-less) predictor
+    // path close() is the inherited no-op, so stopping must complete without 
error. The zoo predictors close
+    // their loaded model in their own close() overrides.
+    @Test
+    void stoppingProducerReleasesPredictor() {
+        DJLEndpoint endpoint = new 
DJLEndpoint("djl:tabular/linear_regression", null, "tabular/linear_regression");
+        endpoint.setCamelContext(new DefaultCamelContext());
+        endpoint.setModel("MyModel");
+        endpoint.setTranslator("MyTranslator");
+
+        assertDoesNotThrow(() -> {
+            DJLProducer producer = new DJLProducer(endpoint);
+            producer.start();
+            producer.stop();
+        });
+    }
+}
diff --git 
a/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/model/ModelPredictorProducerTest.java
 
b/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/model/ModelPredictorProducerTest.java
index 05f40e04f9e9..9beac5758b32 100644
--- 
a/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/model/ModelPredictorProducerTest.java
+++ 
b/components/camel-ai/camel-djl/src/test/java/org/apache/camel/component/djl/model/ModelPredictorProducerTest.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 
 import ai.djl.MalformedModelException;
 import ai.djl.repository.zoo.ModelNotFoundException;
+import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.component.djl.DJLEndpoint;
 import org.apache.camel.component.djl.model.audio.CustomAudioPredictor;
 import org.apache.camel.component.djl.model.cv.CustomCvPredictor;
@@ -46,6 +47,8 @@ import org.junit.jupiter.api.Test;
 import static 
org.apache.camel.component.djl.model.ModelPredictorProducer.getCustomPredictor;
 import static 
org.apache.camel.component.djl.model.ModelPredictorProducer.getZooPredictor;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class ModelPredictorProducerTest {
 
@@ -90,9 +93,7 @@ class ModelPredictorProducerTest {
         // No builtin zoo model available for "nlp/multiple_choice"
         // No builtin zoo model available for "nlp/text_embedding"
 
-        // Tabular
-        // No builtin zoo model available for "tabular/linear_regression"
-        // No builtin zoo model available for "tabular/softmax_regression"
+        // Tabular: no zoo predictor exists (see 
testGetZooPredictorRejectsTabularApplications)
 
         // Audio
         // No builtin zoo model available for "audio"
@@ -102,6 +103,19 @@ class ModelPredictorProducerTest {
                 getZooPredictor(zooEndpoint("timeseries/forecasting", 
"ai.djl.pytorch:deepar:0.0.1")));
     }
 
+    @Test
+    void testGetZooPredictorRejectsTabularApplications() {
+        // The DJL model zoo publishes no tabular regression models and 
tabular I/O types are
+        // model-specific, so the zoo predictor factory must reject these 
applications with a clear
+        // error rather than returning a no-op predictor that echoes the input 
as the prediction.
+        RuntimeCamelException linear = 
assertThrows(RuntimeCamelException.class,
+                () -> getZooPredictor(zooEndpoint("tabular/linear_regression", 
"any:artifact:0.0.1")));
+        assertTrue(linear.getMessage().contains("tabular/linear_regression"));
+        RuntimeCamelException softmax = 
assertThrows(RuntimeCamelException.class,
+                () -> 
getZooPredictor(zooEndpoint("tabular/softmax_regression", 
"any:artifact:0.0.1")));
+        
assertTrue(softmax.getMessage().contains("tabular/softmax_regression"));
+    }
+
     @Test
     void testGetCustomPredictor() {
         var modelName = "MyModel";
diff --git 
a/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformer.java
 
b/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformer.java
index 3046fe09238e..6207c3b43087 100644
--- 
a/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformer.java
+++ 
b/components/camel-ai/camel-qdrant/src/main/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformer.java
@@ -25,6 +25,7 @@ import io.qdrant.client.PointIdFactory;
 import io.qdrant.client.ValueFactory;
 import io.qdrant.client.VectorsFactory;
 import io.qdrant.client.grpc.Common;
+import io.qdrant.client.grpc.JsonWithInt.Value;
 import io.qdrant.client.grpc.Points;
 import org.apache.camel.Message;
 import org.apache.camel.ai.CamelLangchain4jAttributes;
@@ -55,10 +56,31 @@ public class QdrantEmbeddingsDataTypeTransformer extends 
Transformer {
             builder.putPayload("text_segment", 
ValueFactory.value(text.text()));
             Metadata metadata = text.metadata();
             metadata.toMap()
-                    .forEach((key, value) -> builder.putPayload(key, 
ValueFactory.value((String) value)));
+                    .forEach((key, value) -> builder.putPayload(key, 
toValue(value)));
 
         }
 
         message.setBody(builder.build());
     }
+
+    /**
+     * Converts a LangChain4j metadata value to a Qdrant payload value. 
Metadata is not always a String - document
+     * splitters routinely add numeric entries such as the chunk index or page 
number - so the value type must be
+     * honoured instead of being blindly cast to String.
+     */
+    private static Value toValue(Object value) {
+        if (value == null) {
+            return ValueFactory.nullValue();
+        }
+        if (value instanceof Boolean booleanValue) {
+            return ValueFactory.value(booleanValue);
+        }
+        if (value instanceof Integer || value instanceof Long) {
+            return ValueFactory.value(((Number) value).longValue());
+        }
+        if (value instanceof Number number) {
+            return ValueFactory.value(number.doubleValue());
+        }
+        return ValueFactory.value(String.valueOf(value));
+    }
 }
diff --git 
a/components/camel-ai/camel-qdrant/src/test/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformerTest.java
 
b/components/camel-ai/camel-qdrant/src/test/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformerTest.java
new file mode 100644
index 000000000000..f6b1f34da15b
--- /dev/null
+++ 
b/components/camel-ai/camel-qdrant/src/test/java/org/apache/camel/component/qdrant/transform/QdrantEmbeddingsDataTypeTransformerTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.qdrant.transform;
+
+import java.util.Map;
+
+import dev.langchain4j.data.document.Metadata;
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.segment.TextSegment;
+import io.qdrant.client.grpc.JsonWithInt.Value;
+import io.qdrant.client.grpc.Points;
+import org.apache.camel.Exchange;
+import org.apache.camel.ai.CamelLangchain4jAttributes;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.DataType;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class QdrantEmbeddingsDataTypeTransformerTest {
+
+    @Test
+    void mapsMixedMetadataTypesToTypedPayload() throws Exception {
+        // A TextSegment carrying String and numeric metadata, as document 
splitters routinely produce
+        // (chunk index, page number, ...). The transformer must not blindly 
cast every value to String.
+        Metadata metadata = new Metadata()
+                .put("source", "doc.txt")
+                .put("index", 3)
+                .put("score", 0.75);
+        TextSegment segment = TextSegment.from("hello world", metadata);
+        Embedding embedding = new Embedding(new float[] { 0.1f, 0.2f, 0.3f });
+
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            Exchange exchange = new DefaultExchange(context);
+            
exchange.getMessage().setHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR,
 embedding);
+            exchange.getMessage().setBody(segment);
+
+            new 
QdrantEmbeddingsDataTypeTransformer().transform(exchange.getMessage(), 
DataType.ANY, DataType.ANY);
+
+            Points.PointStruct point = 
exchange.getMessage().getBody(Points.PointStruct.class);
+            assertThat(point).isNotNull();
+
+            Map<String, Value> payload = point.getPayloadMap();
+            
assertThat(payload.get("text_segment").getStringValue()).isEqualTo("hello 
world");
+            
assertThat(payload.get("source").getStringValue()).isEqualTo("doc.txt");
+            assertThat(payload.get("index").getIntegerValue()).isEqualTo(3L);
+            assertThat(payload.get("score").getDoubleValue()).isEqualTo(0.75);
+        }
+    }
+}

Reply via email to