This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 db2e22647dc3 CAMEL-26214: camel-weaviate - take the vector as a List
of any numbers, such as the List of Double a JSON array gives, or a float or
double array (#29061)
db2e22647dc3 is described below
commit db2e22647dc335a1e80c2378eef1db279087ce3f
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:13:17 2026 +0100
CAMEL-26214: camel-weaviate - take the vector as a List of any numbers,
such as the List of Double a JSON array gives, or a float or double array
(#29061)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/catalog/components/weaviate.json | 2 +-
.../camel/catalog/docs/weaviate-component.adoc | 2 +-
.../apache/camel/component/weaviate/weaviate.json | 2 +-
.../src/main/docs/weaviate-component.adoc | 2 +-
.../weaviate/WeaviateVectorDbEndpoint.java | 14 +-
.../weaviate/WeaviateVectorDbProducer.java | 56 ++++--
.../component/weaviate/WeaviateVectorBodyTest.java | 212 +++++++++++++++++++++
7 files changed, 259 insertions(+), 31 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/weaviate.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/weaviate.json
index 38bed054a8ac..be4b2cac11df 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/weaviate.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/weaviate.json
@@ -49,7 +49,7 @@
"CamelWeaviateQueryVector": { "index": 11, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "List<Float>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Optional query vector for
hybrid search (overrides server-side vectorizer)", "constantName":
"org.apache.camel.component.weaviate.WeaviateVectorDbHeaders#QUERY_VECTOR" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "list", "text", "null" ], "byOption": [ {
"option": "action", "value": "CREATE", "carriers": [ "list" ] }, { "option":
"action", "value": "BATCH_CREATE", "carriers": [ "list" ], "javaType":
"java.util.List<io.weaviate.client6.v1.api.collections.WeaviateObject<java.util.Map<java.lang.String,
java.lang.Object>>>" }, { "option": "action", "value": "UPDATE_BY_ID",
"carriers": [ "list" ] }, { "option": "action", "value": "QUERY", "carriers": [
"list" ] }, { " [...]
+ "producerIn": { "carriers": [ "list", "text", "null" ], "byOption": [ {
"option": "action", "value": "CREATE", "carriers": [ "list" ] }, { "option":
"action", "value": "BATCH_CREATE", "carriers": [ "list" ], "javaType":
"java.util.List<io.weaviate.client6.v1.api.collections.WeaviateObject<java.util.Map<java.lang.String,
java.lang.Object>>>" }, { "option": "action", "value": "UPDATE_BY_ID",
"carriers": [ "list" ] }, { "option": "action", "value": "QUERY", "carriers": [
"list" ] }, { " [...]
"producerOut": { "carriers": [ "pojo", "value" ], "byOption": [ {
"option": "action", "value": "CREATE", "carriers": [ "pojo" ], "javaType":
"io.weaviate.client6.v1.api.collections.WeaviateObject<java.util.Map<java.lang.String,
java.lang.Object>>" }, { "option": "action", "value": "BATCH_CREATE",
"carriers": [ "pojo" ], "javaType":
"io.weaviate.client6.v1.api.collections.data.InsertManyResponse" }, { "option":
"action", "value": "QUERY", "carriers": [ "pojo" ], "javaType": "io.weavia [...]
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/weaviate-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/weaviate-component.adoc
index 124f6b47315a..5971c240fe21 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/weaviate-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/weaviate-component.adoc
@@ -94,4 +94,4 @@ The `action` option of the endpoint, such as
`weaviate:myCollection?action=BM25_
message: "${body}"
----
-
+The vector of CREATE, UPDATE_BY_ID and QUERY is the message body, and the
optional vector of HYBRID_QUERY is the `CamelWeaviateQueryVector` header.
Either is a List of numbers, such as the List of Double a JSON array unmarshals
to, or a `float[]` or a `double[]`; each number is converted to a float.
Another body fails, and so does a List with an element that is not a number.
diff --git
a/components/camel-ai/camel-weaviate/src/generated/resources/META-INF/org/apache/camel/component/weaviate/weaviate.json
b/components/camel-ai/camel-weaviate/src/generated/resources/META-INF/org/apache/camel/component/weaviate/weaviate.json
index 38bed054a8ac..be4b2cac11df 100644
---
a/components/camel-ai/camel-weaviate/src/generated/resources/META-INF/org/apache/camel/component/weaviate/weaviate.json
+++
b/components/camel-ai/camel-weaviate/src/generated/resources/META-INF/org/apache/camel/component/weaviate/weaviate.json
@@ -49,7 +49,7 @@
"CamelWeaviateQueryVector": { "index": 11, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "List<Float>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Optional query vector for
hybrid search (overrides server-side vectorizer)", "constantName":
"org.apache.camel.component.weaviate.WeaviateVectorDbHeaders#QUERY_VECTOR" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "list", "text", "null" ], "byOption": [ {
"option": "action", "value": "CREATE", "carriers": [ "list" ] }, { "option":
"action", "value": "BATCH_CREATE", "carriers": [ "list" ], "javaType":
"java.util.List<io.weaviate.client6.v1.api.collections.WeaviateObject<java.util.Map<java.lang.String,
java.lang.Object>>>" }, { "option": "action", "value": "UPDATE_BY_ID",
"carriers": [ "list" ] }, { "option": "action", "value": "QUERY", "carriers": [
"list" ] }, { " [...]
+ "producerIn": { "carriers": [ "list", "text", "null" ], "byOption": [ {
"option": "action", "value": "CREATE", "carriers": [ "list" ] }, { "option":
"action", "value": "BATCH_CREATE", "carriers": [ "list" ], "javaType":
"java.util.List<io.weaviate.client6.v1.api.collections.WeaviateObject<java.util.Map<java.lang.String,
java.lang.Object>>>" }, { "option": "action", "value": "UPDATE_BY_ID",
"carriers": [ "list" ] }, { "option": "action", "value": "QUERY", "carriers": [
"list" ] }, { " [...]
"producerOut": { "carriers": [ "pojo", "value" ], "byOption": [ {
"option": "action", "value": "CREATE", "carriers": [ "pojo" ], "javaType":
"io.weaviate.client6.v1.api.collections.WeaviateObject<java.util.Map<java.lang.String,
java.lang.Object>>" }, { "option": "action", "value": "BATCH_CREATE",
"carriers": [ "pojo" ], "javaType":
"io.weaviate.client6.v1.api.collections.data.InsertManyResponse" }, { "option":
"action", "value": "QUERY", "carriers": [ "pojo" ], "javaType": "io.weavia [...]
},
"properties": {
diff --git
a/components/camel-ai/camel-weaviate/src/main/docs/weaviate-component.adoc
b/components/camel-ai/camel-weaviate/src/main/docs/weaviate-component.adoc
index 124f6b47315a..5971c240fe21 100644
--- a/components/camel-ai/camel-weaviate/src/main/docs/weaviate-component.adoc
+++ b/components/camel-ai/camel-weaviate/src/main/docs/weaviate-component.adoc
@@ -94,4 +94,4 @@ The `action` option of the endpoint, such as
`weaviate:myCollection?action=BM25_
message: "${body}"
----
-
+The vector of CREATE, UPDATE_BY_ID and QUERY is the message body, and the
optional vector of HYBRID_QUERY is the `CamelWeaviateQueryVector` header.
Either is a List of numbers, such as the List of Double a JSON array unmarshals
to, or a `float[]` or a `double[]`; each number is converted to a float.
Another body fails, and so does a List with an element that is not a number.
diff --git
a/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbEndpoint.java
b/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbEndpoint.java
index 7ab6f42a11fe..85eecf41fb8a 100644
---
a/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbEndpoint.java
+++
b/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbEndpoint.java
@@ -66,13 +66,13 @@ import org.apache.camel.support.DefaultEndpoint;
@BodyType.When(option = "action",
value = "DELETE_BY_ID", carriers = Carrier.ANY),
@BodyType.When(option = "action",
value = "AGGREGATE", carriers = Carrier.ANY) },
description = "By the action in the action
option or the CamelWeaviateAction header: the vector as a"
- + " List of Float or a float
array for CREATE, UPDATE_BY_ID and QUERY, with the properties"
- + " in the
CamelWeaviateProperties header, a List of WeaviateObject for BATCH_CREATE, and"
- + " the query text for
HYBRID_QUERY and BM25_QUERY. CREATE_COLLECTION, DELETE_COLLECTION,"
- + " QUERY_BY_ID, DELETE_BY_ID
and AGGREGATE do not read the body, the id is in the"
- + " CamelWeaviateIndexId
header. Another body fails, a List of Double too: the"
- + " weaviate:embeddings data
type transformer builds the vector from the output of"
- + " langchain4j-embeddings"),
+ + " List of numbers, such as
the List of Double a JSON array unmarshals to, or a float or"
+ + " double array for CREATE,
UPDATE_BY_ID and QUERY, with the properties in the"
+ + " CamelWeaviateProperties
header, a List of WeaviateObject for BATCH_CREATE, and the query"
+ + " text for HYBRID_QUERY and
BM25_QUERY. CREATE_COLLECTION, DELETE_COLLECTION, QUERY_BY_ID,"
+ + " DELETE_BY_ID and AGGREGATE
do not read the body, the id is in the CamelWeaviateIndexId"
+ + " header. Another body
fails: the weaviate:embeddings data type transformer builds the"
+ + " vector from the output of
langchain4j-embeddings"),
producerOut = @BodyType(value = { Carrier.POJO, Carrier.VALUE },
byOption = {
@BodyType.When(option = "action",
value = "CREATE", carriers = Carrier.POJO,
diff --git
a/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbProducer.java
b/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbProducer.java
index 6ef5e3137c66..7eb1854a8d15 100644
---
a/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbProducer.java
+++
b/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/WeaviateVectorDbProducer.java
@@ -30,12 +30,15 @@ import
io.weaviate.client6.v1.api.collections.data.InsertManyResponse;
import io.weaviate.client6.v1.api.collections.query.NearVector;
import io.weaviate.client6.v1.api.collections.query.QueryResponse;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.NoSuchHeaderException;
import org.apache.camel.support.DefaultProducer;
import org.apache.camel.support.ExchangeHelper;
public class WeaviateVectorDbProducer extends DefaultProducer {
+ private static final String VECTOR_TYPES = "a List of numbers, such as
Float or Double, a float[] or a double[]";
+
private WeaviateClient client;
public WeaviateVectorDbProducer(WeaviateVectorDbEndpoint endpoint) {
@@ -128,15 +131,12 @@ public class WeaviateVectorDbProducer extends
DefaultProducer {
private void create(Exchange exchange) throws Exception {
final Message in = exchange.getMessage();
- List elements = in.getMandatoryBody(List.class);
+ float[] primitiveVectors = toVector(exchange);
String collectionName = resolveCollectionName(in);
HashMap<String, Object> props =
in.getHeader(WeaviateVectorDbHeaders.PROPERTIES, HashMap.class);
- Float[] vectors = (Float[]) elements.toArray(new Float[0]);
- float[] primitiveVectors = toPrimitiveFloatArray(vectors);
-
CollectionHandle<Map<String, Object>> collection =
client.collections.use(collectionName);
WeaviateObject<Map<String, Object>> result;
@@ -151,13 +151,11 @@ public class WeaviateVectorDbProducer extends
DefaultProducer {
private void updateById(Exchange exchange) throws Exception {
final Message in = exchange.getMessage();
- List elements = in.getMandatoryBody(List.class);
+ float[] primitiveVectors = toVector(exchange);
String indexId = ExchangeHelper.getMandatoryHeader(exchange,
WeaviateVectorDbHeaders.INDEX_ID, String.class);
String collectionName = resolveCollectionName(in);
- Float[] vectors = (Float[]) elements.toArray(new Float[0]);
- float[] primitiveVectors = toPrimitiveFloatArray(vectors);
HashMap<String, Object> props =
in.getHeader(WeaviateVectorDbHeaders.PROPERTIES, HashMap.class);
CollectionHandle<Map<String, Object>> collection =
client.collections.use(collectionName);
@@ -196,15 +194,12 @@ public class WeaviateVectorDbProducer extends
DefaultProducer {
private void query(Exchange exchange) throws Exception {
final Message in = exchange.getMessage();
- List elements = in.getMandatoryBody(List.class);
+ float[] primitiveVectors = toVector(exchange);
int topK = in.getHeader(WeaviateVectorDbHeaders.QUERY_TOP_K, 10,
Integer.class);
String collectionName = resolveCollectionName(in);
- Float[] vectors = (Float[]) elements.toArray(new Float[0]);
- float[] primitiveVectors = toPrimitiveFloatArray(vectors);
-
CollectionHandle<Map<String, Object>> collection =
client.collections.use(collectionName);
final int limit = topK;
@@ -260,7 +255,8 @@ public class WeaviateVectorDbProducer extends
DefaultProducer {
int topK = in.getHeader(WeaviateVectorDbHeaders.QUERY_TOP_K, 10,
Integer.class);
Float alpha = in.getHeader(WeaviateVectorDbHeaders.HYBRID_ALPHA,
Float.class);
- List<Float> queryVector =
in.getHeader(WeaviateVectorDbHeaders.QUERY_VECTOR, List.class);
+ List<?> queryVectorElements =
in.getHeader(WeaviateVectorDbHeaders.QUERY_VECTOR, List.class);
+ float[] queryVector = queryVectorElements != null ?
toPrimitiveFloatArray(queryVectorElements) : null;
String collectionName = resolveCollectionName(in);
@@ -275,8 +271,7 @@ public class WeaviateVectorDbProducer extends
DefaultProducer {
h.alpha(alpha);
}
if (queryVector != null) {
- float[] primitiveVector =
toPrimitiveFloatArray(queryVector.toArray(new Float[0]));
- h.nearVector(NearVector.of(primitiveVector));
+ h.nearVector(NearVector.of(queryVector));
}
if (fieldToSearch != null) {
h.returnProperties(fieldToSearch.keySet().toArray(new
String[0]));
@@ -344,13 +339,34 @@ public class WeaviateVectorDbProducer extends
DefaultProducer {
out.setBody(r);
}
- private static float[] toPrimitiveFloatArray(Float[] boxed) {
- float[] result = new float[boxed.length];
- for (int i = 0; i < boxed.length; i++) {
- if (boxed[i] == null) {
- throw new IllegalArgumentException("Vector element at index "
+ i + " is null");
+ /**
+ * The vector in the body of CREATE, UPDATE_BY_ID and QUERY: a float[], or
a List of numbers, such as the List of
+ * Double a JSON array unmarshals to, or another body Camel converts to a
List, such as a double[] or a Float[].
+ */
+ private static float[] toVector(Exchange exchange) throws
InvalidPayloadException {
+ final Message in = exchange.getMessage();
+ if (in.getBody() instanceof float[] vector) {
+ return vector;
+ }
+ List<?> elements = in.getBody(List.class);
+ if (elements == null) {
+ throw new InvalidPayloadException(exchange, List.class, in, "the
vector must be " + VECTOR_TYPES);
+ }
+ return toPrimitiveFloatArray(elements);
+ }
+
+ private static float[] toPrimitiveFloatArray(List<?> elements) {
+ float[] result = new float[elements.size()];
+ int i = 0;
+ for (Object element : elements) {
+ if (!(element instanceof Number number)) {
+ // the type only, not the value, which comes from the message
+ throw new IllegalArgumentException(
+ "Vector element at index " + i + " is "
+ + (element == null ? "null"
: "a " + element.getClass().getName())
+ + ": the vector must be " +
VECTOR_TYPES);
}
- result[i] = boxed[i];
+ result[i++] = number.floatValue();
}
return result;
}
diff --git
a/components/camel-ai/camel-weaviate/src/test/java/org/apache/camel/component/weaviate/WeaviateVectorBodyTest.java
b/components/camel-ai/camel-weaviate/src/test/java/org/apache/camel/component/weaviate/WeaviateVectorBodyTest.java
new file mode 100644
index 000000000000..0fa71cea40c3
--- /dev/null
+++
b/components/camel-ai/camel-weaviate/src/test/java/org/apache/camel/component/weaviate/WeaviateVectorBodyTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.weaviate;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Stream;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.weaviate.client6.v1.internal.grpc.ByteStringUtil;
+import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateGrpc;
+import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateProtoBase.Vectors;
+import
io.weaviate.client6.v1.internal.grpc.protocol.WeaviateProtoSearchGet.SearchReply;
+import
io.weaviate.client6.v1.internal.grpc.protocol.WeaviateProtoSearchGet.SearchRequest;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-26214: the vector of CREATE, UPDATE_BY_ID and QUERY, and the
CamelWeaviateQueryVector header of HYBRID_QUERY,
+ * is a List of numbers, such as the List of Double a JSON array unmarshals
to, or a float or double array. A fake
+ * Weaviate, its REST API on a JDK HttpServer and its gRPC API, records the
requests of the client, so no Weaviate is
+ * needed.
+ */
+class WeaviateVectorBodyTest extends CamelTestSupport {
+
+ private static final float[] VECTOR = { 0.1f, 0.2f, 0.3f };
+ // the vector as the client writes it in the JSON of an object
+ private static final String VECTOR_JSON =
"\"vectors\":{\"default\":[0.1,0.2,0.3]}";
+ private static final String ID = "6f0b5a6e-4b5e-4b8e-9d5e-0a8c7a2a9a11";
+
+ private final List<String> restRequests = new CopyOnWriteArrayList<>();
+ private final List<SearchRequest> searches = new CopyOnWriteArrayList<>();
+ private HttpServer rest;
+ private Server grpc;
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ rest = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
+ rest.createContext("/", this::handle);
+ rest.start();
+ grpc = ServerBuilder.forPort(0).addService(new
WeaviateGrpc.WeaviateImplBase() {
+ @Override
+ public void search(SearchRequest request,
StreamObserver<SearchReply> reply) {
+ searches.add(request);
+ reply.onNext(SearchReply.getDefaultInstance());
+ reply.onCompleted();
+ }
+ }).build().start();
+
+ CamelContext context = super.createCamelContext();
+ WeaviateVectorDbConfiguration configuration
+ = context.getComponent("weaviate",
WeaviateVectorDbComponent.class).getConfiguration();
+ configuration.setHost("localhost:" + rest.getAddress().getPort());
+ configuration.setGrpcHost("localhost");
+ configuration.setGrpcPort(grpc.getPort());
+ return context;
+ }
+
+ @AfterEach
+ void stopFakeWeaviate() {
+ rest.stop(0);
+ grpc.shutdownNow();
+ }
+
+ static Stream<Named<Object>> vectorBodies() {
+ return Stream.of(
+ Named.of("List of Float", List.of(0.1f, 0.2f, 0.3f)),
+ Named.of("List of Double", List.of(0.1d, 0.2d, 0.3d)),
+ Named.of("List of BigDecimal", List.of(new BigDecimal("0.1"),
new BigDecimal("0.2"), new BigDecimal("0.3"))),
+ Named.of("float[]", new float[] { 0.1f, 0.2f, 0.3f }),
+ Named.of("double[]", new double[] { 0.1d, 0.2d, 0.3d }),
+ Named.of("Double[]", new Double[] { 0.1d, 0.2d, 0.3d }));
+ }
+
+ @ParameterizedTest
+ @MethodSource("vectorBodies")
+ void createTakesTheVector(Object body) {
+ Exchange result = fluentTemplate.to("weaviate:Articles?action=CREATE")
+ .withBody(body)
+ .request(Exchange.class);
+
+ assertThat(result.getException()).isNull();
+ assertThat(restRequests).filteredOn(request ->
request.startsWith("POST /v1/objects"))
+ .singleElement().asString().contains(VECTOR_JSON);
+ }
+
+ @ParameterizedTest
+ @MethodSource("vectorBodies")
+ void updateByIdTakesTheVector(Object body) {
+ Exchange result =
fluentTemplate.to("weaviate:Articles?action=UPDATE_BY_ID")
+ .withHeader(WeaviateVectorDbHeaders.INDEX_ID, ID)
+ .withBody(body)
+ .request(Exchange.class);
+
+ assertThat(result.getException()).isNull();
+ assertThat(restRequests).filteredOn(request ->
request.startsWith("PATCH /v1/objects/Articles/" + ID))
+ .singleElement().asString().contains(VECTOR_JSON);
+ }
+
+ @ParameterizedTest
+ @MethodSource("vectorBodies")
+ void queryTakesTheVector(Object body) {
+ Exchange result = fluentTemplate.to("weaviate:Articles?action=QUERY")
+ .withBody(body)
+ .request(Exchange.class);
+
+ assertThat(result.getException()).isNull();
+ assertThat(searches).singleElement()
+ .satisfies(search ->
assertThat(vectorOf(search.getNearVector().getVectorsList())).containsExactly(VECTOR));
+ }
+
+ @Test
+ void hybridQueryTakesTheQueryVectorHeader() {
+ Exchange result =
fluentTemplate.to("weaviate:Articles?action=HYBRID_QUERY")
+ .withHeader(WeaviateVectorDbHeaders.QUERY_VECTOR,
List.of(0.1d, 0.2d, 0.3d))
+ .withBody("camel")
+ .request(Exchange.class);
+
+ assertThat(result.getException()).isNull();
+ assertThat(searches).singleElement().satisfies(search -> {
+ assertThat(search.getHybridSearch().getQuery()).isEqualTo("camel");
+
assertThat(vectorOf(search.getHybridSearch().getNearVector().getVectorsList())).containsExactly(VECTOR);
+ });
+ }
+
+ @Test
+ void aBodyThatIsNotAVectorFails() {
+ Exchange text = fluentTemplate.to("weaviate:Articles?action=CREATE")
+ .withBody("0.1, 0.2, 0.3")
+ .request(Exchange.class);
+
assertThat(text.getException()).isInstanceOfSatisfying(InvalidPayloadException.class,
+ e -> assertThat(e.getType()).isEqualTo(List.class))
+ .hasMessageContaining("the vector must be a List of numbers,
such as Float or Double, a float[] or a double[]");
+
+ // the message names the type of the element, not its value
+ Exchange listOfText =
fluentTemplate.to("weaviate:Articles?action=QUERY")
+ .withBody(List.of("secret-0.1", "0.2"))
+ .request(Exchange.class);
+
assertThat(listOfText.getException()).isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("Vector element at index 0 is a
java.lang.String")
+ .hasMessageNotContaining("secret");
+
+ Exchange nullElement =
fluentTemplate.to("weaviate:Articles?action=UPDATE_BY_ID")
+ .withHeader(WeaviateVectorDbHeaders.INDEX_ID, ID)
+ .withBody(Arrays.asList(0.1d, null, 0.3d))
+ .request(Exchange.class);
+
assertThat(nullElement.getException()).isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("Vector element at index 1 is null");
+
+ assertThat(restRequests).noneMatch(request ->
request.contains("/objects"));
+ assertThat(searches).isEmpty();
+ }
+
+ private static float[] vectorOf(List<Vectors> vectors) {
+ assertThat(vectors).hasSize(1);
+ return
ByteStringUtil.decodeVectorSingle(vectors.get(0).getVectorBytes());
+ }
+
+ private void handle(HttpExchange http) throws IOException {
+ String body = new String(http.getRequestBody().readAllBytes(),
StandardCharsets.UTF_8);
+ String path = http.getRequestURI().getPath();
+ restRequests.add(http.getRequestMethod() + " " + path + " " + body);
+ String response;
+ if (path.endsWith("/.well-known/live")) {
+ response = "";
+ } else if (path.endsWith("/meta")) {
+ response =
"{\"hostname\":\"http://[::]:8080\",\"version\":\"1.37.4\",\"modules\":{}}";
+ } else {
+ // the object, as Weaviate gives it back
+ response = body;
+ }
+ byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
+ http.getResponseHeaders().add("Content-Type", "application/json");
+ http.sendResponseHeaders(200, bytes.length == 0 ? -1 : bytes.length);
+ if (bytes.length > 0) {
+ http.getResponseBody().write(bytes);
+ }
+ http.close();
+ }
+}