This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 0757d655cf9e CAMEL-24534: camel-qdrant - honour metadata value types
instead of casting them to String
0757d655cf9e is described below
commit 0757d655cf9e12213b87117bb9dea69b103315fb
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 28 06:53:48 2026 +0200
CAMEL-24534: camel-qdrant - honour metadata value types instead of casting
them to String
QdrantEmbeddingsDataTypeTransformer mapped every document metadata entry
into the
Qdrant payload with ValueFactory.value((String) value). The unchecked cast
throws
ClassCastException as soon as a metadata value is not a String, which
happens
routinely because LangChain4j document splitters add numeric metadata such
as the
chunk index and page number - so a normal ingestion pipeline that stores
metadata
fails at runtime.
Convert each value to the matching Qdrant Value type (boolean, integer,
double,
otherwise String), mirroring how the pgvector sibling handles
Number/Boolean.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../QdrantEmbeddingsDataTypeTransformer.java | 24 +++++++-
.../QdrantEmbeddingsDataTypeTransformerTest.java | 66 ++++++++++++++++++++++
2 files changed, 89 insertions(+), 1 deletion(-)
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);
+ }
+ }
+}