Skip to content

Commit cf9e6e1

Browse files
Add base64 string vector utility and test (#1132) (#1133)
* add base64 string vector utility and test * license header Co-authored-by: Laura Trotta <153528055+l-trotta@users.noreply.github.com>
1 parent 53eea0f commit cf9e6e1

5 files changed

Lines changed: 260 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.elasticsearch._helpers.vector;
21+
22+
import java.nio.ByteBuffer;
23+
import java.util.Base64;
24+
25+
/**
26+
* Utility class that can be extended to be used as vector type document,
27+
* which will translate float array vectors in a base64 format accepted by Elasticsearch version 9.3+,
28+
* with increased ingestion performance
29+
* <p>
30+
* Check {@link Base64VectorTest} for example usage
31+
*/
32+
public class AbstractBase64VectorDocument {
33+
private String vector;
34+
35+
public void setVector(float[] vec) {
36+
ByteBuffer buff = ByteBuffer.allocate(Float.BYTES * vec.length);
37+
for (int i = 0; i < vec.length; i++) {
38+
buff.putFloat(vec[i]);
39+
}
40+
this.vector = Base64.getEncoder().encodeToString(buff.array());
41+
}
42+
43+
public void setVector(String vec) {
44+
this.vector = vec;
45+
}
46+
47+
public String getVector() {
48+
return vector;
49+
}
50+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.elasticsearch._helpers.vector;
21+
22+
import co.elastic.clients.elasticsearch.ElasticsearchClient;
23+
import co.elastic.clients.elasticsearch.ElasticsearchTestClient;
24+
import co.elastic.clients.elasticsearch.ElasticsearchTestServer;
25+
import co.elastic.clients.elasticsearch.core.SearchResponse;
26+
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import org.junit.jupiter.api.BeforeAll;
29+
import org.junit.jupiter.api.Test;
30+
31+
import java.io.IOException;
32+
import java.io.InputStream;
33+
import java.io.StringReader;
34+
35+
import static org.junit.jupiter.api.Assertions.assertEquals;
36+
37+
public class Base64VectorTest {
38+
39+
static ElasticsearchClient elasticsearchClient;
40+
private static final String INDEX = "vec-64-ingest-test";
41+
42+
@BeforeAll
43+
public static void setup() {
44+
var server = ElasticsearchTestServer.global();
45+
elasticsearchClient = ElasticsearchTestClient.createClient(server.url(), new JacksonJsonpMapper(),
46+
server.sslContext(), null);
47+
}
48+
49+
@Test
50+
public void oneBase64VectorIngestTest() throws IOException {
51+
// Reading one example vector from file
52+
InputStream input = this.getClass()
53+
.getResourceAsStream("just-1-vector.json");
54+
55+
ObjectMapper mapper = new ObjectMapper();
56+
JsonVector vectorDoc = mapper.readerFor(JsonVector.class).readValue(input);
57+
58+
// Converting vector from array of floats to base64 using utility class
59+
CustomVectorDoc customVectorDoc = new CustomVectorDoc(vectorDoc.docid(),vectorDoc.title(), vectorDoc.text(), vectorDoc.emb());
60+
61+
// Or, alternatively, explicitly use the setVector() method provided by AbstractBase64VectorDocument
62+
// CustomVectorDoc customVectorDocAlt = new CustomVectorDoc(vectorDoc.docid(),vectorDoc.title(), vectorDoc.text());
63+
// customVectorDocAlt.setVector(vectorDoc.emb());
64+
65+
// Creating index explicitly indicating that the "vector" field will be of type dense vector
66+
// While usually elasticsearch is able to autodetect the field type when automatically creating an index,
67+
// in this specific case when using base64 string to represent vectors, it's necessary to manually map the
68+
// vector field as dense vector.
69+
if (elasticsearchClient.indices().exists(e -> e.index(INDEX)).value()) {
70+
elasticsearchClient.indices().delete(d -> d.index(INDEX));
71+
elasticsearchClient.indices().create(c -> c.index(INDEX)
72+
.withJson(new StringReader("{\n" +
73+
" \"mappings\": {\n" +
74+
" \"properties\": {\n" +
75+
" \"text\": {\n" +
76+
" \"type\": \"text\",\n" +
77+
" \"fields\": {\n" +
78+
" \"keyword\": {\n" +
79+
" \"type\": \"keyword\",\n" +
80+
" \"ignore_above\": 256\n" +
81+
" }\n" +
82+
" }\n" +
83+
" },\n" +
84+
" \"vector\": {\n" +
85+
" \"type\": \"dense_vector\",\n" +
86+
" \"dims\": 1536,\n" +
87+
" \"index\": true,\n" +
88+
" \"similarity\": \"cosine\",\n" +
89+
" \"index_options\": {\n" +
90+
" \"type\": \"flat\"\n" +
91+
" }\n" +
92+
" }\n" +
93+
" }\n" +
94+
" }\n" +
95+
"}")));
96+
elasticsearchClient.indices().refresh();
97+
}
98+
99+
// Indexing the vector
100+
elasticsearchClient.index(i -> i
101+
.index(INDEX)
102+
.document(customVectorDoc)
103+
);
104+
105+
// Refreshing to make sure we can query the vector
106+
elasticsearchClient.indices().refresh();
107+
108+
// Asserting exactly 1 vector has been ingested
109+
assertEquals(1L, elasticsearchClient.indices().stats().all().total().docs().count());
110+
111+
// Asserting vector can be deserialized
112+
SearchResponse<CustomVectorDoc> resp = elasticsearchClient.search(s -> s.index(INDEX),
113+
CustomVectorDoc.class);
114+
115+
assertEquals(customVectorDoc.getVector(),resp.hits().hits().get(0).source().getVector());
116+
117+
// Cleanup
118+
elasticsearchClient.indices().delete(d -> d.index(INDEX));
119+
}
120+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.elasticsearch._helpers.vector;
21+
22+
public final class CustomVectorDoc extends AbstractBase64VectorDocument {
23+
private String docid;
24+
private String title;
25+
private String text;
26+
27+
public CustomVectorDoc() {
28+
}
29+
30+
public CustomVectorDoc(String docid, String title, String text, float[] vector) {
31+
this.docid = docid;
32+
this.title = title;
33+
this.text = text;
34+
this.setVector(vector);
35+
}
36+
37+
public CustomVectorDoc(String docid, String title, String text) {
38+
this.docid = docid;
39+
this.title = title;
40+
this.text = text;
41+
}
42+
43+
public String getDocid() {
44+
return docid;
45+
}
46+
47+
public String getTitle() {
48+
return title;
49+
}
50+
51+
public String getText() {
52+
return text;
53+
}
54+
55+
public void setDocid(String docid) {
56+
this.docid = docid;
57+
}
58+
59+
public void setTitle(String title) {
60+
this.title = title;
61+
}
62+
63+
public void setText(String text) {
64+
this.text = text;
65+
}
66+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.elasticsearch._helpers.vector;
21+
22+
public record JsonVector(String docid, String title, String text, float[] emb) {
23+
}

java-client/src/test/resources/co/elastic/clients/elasticsearch/_helpers/vector/just-1-vector.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)