From 6b1d6acefb64154b329f7aa9a8df8be03c2c28e0 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Sat, 22 Jun 2024 16:22:27 +0900 Subject: [PATCH] add search helper --- README.md | 37 +++++ pom.xml | 4 +- .../fess/multimodal/MultiModalConstants.java | 14 +- .../fess/multimodal/client/CasClient.java | 4 +- .../helper/MultiModalSearchHelper.java | 127 ++++++++++++++++++ .../multimodal/ingest/EmbeddingIngester.java | 23 ++-- .../query/MultiModalPhraseQueryCommand.java | 6 +- .../query/MultiModalQueryBuilder.java | 19 ++- .../query/MultiModalTermQueryCommand.java | 6 +- .../rank/fusion/MultiModalSearcher.java | 36 +++-- src/main/resources/app++.xml | 3 + src/main/resources/crawler/extractor++.xml | 2 +- .../ingest/EmbeddingIngesterTest.java | 13 +- 13 files changed, 247 insertions(+), 47 deletions(-) create mode 100644 src/main/java/org/codelibs/fess/multimodal/helper/MultiModalSearchHelper.java diff --git a/README.md b/README.md index 4b08490..1938621 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,43 @@ See [Maven Repository](https://repo1.maven.org/maven2/org/codelibs/fess/fess-web See [Plugin](https://fess.codelibs.org/14.15/admin/plugin-guide.html) of Administration guide. +## Usage + +After installing the plugin, follow these steps to configure and use it: + +1. **Start Fess**: Launch Fess and log in to the administration console. + +2. **Configure System Properties**: Add the following properties to the general settings under system properties: + ``` + fess.multimodal.content.field=content_vector + fess.multimodal.content.dimension=512 + fess.multimodal.content.method=hnsw + fess.multimodal.content.engine=lucene + fess.multimodal.content.space_type=cosinesimil + fess.multimodal.min_score=0.5 + ``` + +3. **Update Settings**: Navigate to the scheduler page and execu te Config Reloader. + +4. **Re-indexing**: Navigate to the maintenance page and execute re-indexing. + +5. **Setup CLIP as Service**: For image embedding, use CLIP as Service. Start the CLIP API using Docker: + ```sh + git clone https://github.com/codelibs/fess-webapp-multimodal.git + cd fess-webapp-multimodal/docker + docker compose up -d + ``` + This will make the CLIP API accessible at `localhost:51000`. + +6. **Crawl Directories**: Crawl directories containing image files. + +7. **Test Data**: If you need test data, you can download the Open Images Dataset. For example: + ```sh + pip install fiftyone + fiftyone zoo datasets load open-images-v7 --split validation --kwargs max_samples=1000 -d fiftyone + ``` + This will download 1000 images into the `fiftyone` directory. + ## Contributing We welcome contributions to enhance the functionality of this plugin. Please fork the repository and submit pull requests. diff --git a/pom.xml b/pom.xml index a215a14..f02ad2a 100644 --- a/pom.xml +++ b/pom.xml @@ -42,8 +42,8 @@ - - org.codelibs.fess.crawler.multimodal + org.codelibs.fess.multimodal + true diff --git a/src/main/java/org/codelibs/fess/multimodal/MultiModalConstants.java b/src/main/java/org/codelibs/fess/multimodal/MultiModalConstants.java index 7c94335..8ee89aa 100644 --- a/src/main/java/org/codelibs/fess/multimodal/MultiModalConstants.java +++ b/src/main/java/org/codelibs/fess/multimodal/MultiModalConstants.java @@ -19,9 +19,19 @@ public class MultiModalConstants { private static final String PREFIX = "fess.multimodal."; + public static final String CONTENT_DIMENSION = PREFIX + "content.dimension"; + + public static final String CONTENT_ENGINE = PREFIX + "content.engine"; + + public static final String CONTENT_METHOD = PREFIX + "content.method"; + + public static final String CONTENT_SPACE_TYPE = PREFIX + "content.space_type"; + + public static final String CONTENT_FIELD = PREFIX + "content.field"; + public static final String MIN_SCORE = PREFIX + "min_score"; - public static final String CONTENT_VECTOR_FIELD = System.getProperty(PREFIX + "content.field", "content_vector"); + public static final String DEFAULT_CONTENT_FIELD = PREFIX + "content_vector"; public static final String X_FESS_EMBEDDING = "X-FESS-Embedding"; @@ -29,6 +39,8 @@ public class MultiModalConstants { public static final String CAS_CLIENT = "casClient"; + public static final String HELPER = "multiModalSearchHelper"; + private MultiModalConstants() { // nothing } diff --git a/src/main/java/org/codelibs/fess/multimodal/client/CasClient.java b/src/main/java/org/codelibs/fess/multimodal/client/CasClient.java index 209098b..62c81f1 100644 --- a/src/main/java/org/codelibs/fess/multimodal/client/CasClient.java +++ b/src/main/java/org/codelibs/fess/multimodal/client/CasClient.java @@ -71,8 +71,8 @@ public class CasClient { public void init() { imageWidth = Integer.getInteger("clip.image.width", 224); imageHeight = Integer.getInteger("clip.image.height", 224); - maxImageWidth = Integer.getInteger("clip.image.max_width", 1000); - maxImageHeight = Integer.getInteger("clip.image.max_height", 1000); + maxImageWidth = Integer.getInteger("clip.image.max_width", 3000); + maxImageHeight = Integer.getInteger("clip.image.max_height", 2000); imageFormat = System.getProperty("clip.image.format", "png"); clipEndpoint = System.getProperty("clip.server.endpoint", "http://localhost:51000"); diff --git a/src/main/java/org/codelibs/fess/multimodal/helper/MultiModalSearchHelper.java b/src/main/java/org/codelibs/fess/multimodal/helper/MultiModalSearchHelper.java new file mode 100644 index 0000000..e763122 --- /dev/null +++ b/src/main/java/org/codelibs/fess/multimodal/helper/MultiModalSearchHelper.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2024 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.multimodal.helper; + +import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_DIMENSION; +import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_ENGINE; +import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_FIELD; +import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_METHOD; +import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_SPACE_TYPE; +import static org.codelibs.fess.multimodal.MultiModalConstants.DEFAULT_CONTENT_FIELD; +import static org.codelibs.fess.multimodal.MultiModalConstants.MIN_SCORE; + +import javax.annotation.PostConstruct; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.es.client.SearchEngineClient; +import org.codelibs.fess.query.parser.QueryParser; +import org.codelibs.fess.util.ComponentUtil; + +import com.google.common.base.CharMatcher; + +public class MultiModalSearchHelper { + private static final Logger logger = LogManager.getLogger(MultiModalSearchHelper.class); + + protected Float minScore; + + private String vectorField; + + @PostConstruct + public void init() { + final SearchEngineClient client = ComponentUtil.getSearchEngineClient(); + client.addDocumentSettingRewriteRule(s -> s.replace("\"codec\":", "\"knn\": true,\"codec\":")); + client.addDocumentMappingRewriteRule(s -> { + final String dimension = System.getProperty(CONTENT_DIMENSION); // ex. 512 + final String method = System.getProperty(CONTENT_METHOD); // ex. hnsw + final String engine = System.getProperty(CONTENT_ENGINE); // ex. lucene + final String spaceType = System.getProperty(CONTENT_SPACE_TYPE, "l2"); // ex. l2 + if (logger.isDebugEnabled()) { + logger.debug("field: {}, dimension: {}, method: {}, engine: {}, spaceType: {}", vectorField, dimension, method, engine, + spaceType); + } + if (StringUtil.isBlank(dimension) || StringUtil.isBlank(vectorField) || StringUtil.isBlank(method) + || StringUtil.isBlank(engine)) { + return s; + } + return s.replace("\"content\":", "\"" + vectorField + "\": {\n" // + + " \"type\": \"knn_vector\",\n" // + + " \"dimension\": " + dimension + ",\n" // + + " \"method\": {\n" // + + " \"name\": \"" + method + "\",\n" // + + " \"engine\": \"" + engine + "\",\n" // + + " \"space_type\": \"" + spaceType + "\"\n" // + + " }\n" // + + "},\n" // + + "\"content\":"); + }); + + if (ComponentUtil.hasQueryParser()) { + final QueryParser queryParser = ComponentUtil.getQueryParser(); + queryParser.addFilter((query, chain) -> chain.parse(rewriteQuery(query))); + } + + load(); + ComponentUtil.getSystemHelper().addUpdateConfigListener("MultiModalSearch", this::load); + } + + protected String load() { + final StringBuilder buf = new StringBuilder(); + + buf.append("vector_field="); + vectorField = System.getProperty(CONTENT_FIELD, DEFAULT_CONTENT_FIELD).trim(); // ex. content_vector + buf.append(vectorField); + + buf.append(", min_score="); + final String minScoreValue = System.getProperty(MIN_SCORE); + if (StringUtil.isNotBlank(minScoreValue)) { + try { + minScore = Float.valueOf(minScoreValue); + buf.append(minScore); + } catch (final NumberFormatException e) { + logger.debug("Failed to parse {}.", minScoreValue, e); + minScore = null; + } + } else { + minScore = null; + } + + return buf.toString(); + } + + protected String rewriteQuery(final String query) { + if (StringUtil.isBlank(query) || (query.indexOf('"') != -1) || !CharMatcher.whitespace().matchesAnyOf(query)) { + return query; + } + + for (final String field : ComponentUtil.getQueryFieldConfig().getSearchFields()) { + if (query.indexOf(field + ":") != -1) { + return query; + } + } + + return "\"" + query + "\""; + } + + public Float getMinScore() { + return minScore; + } + + public String getVectorField() { + return vectorField; + } +} diff --git a/src/main/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngester.java b/src/main/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngester.java index 6ef3cd7..29f8a3f 100644 --- a/src/main/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngester.java +++ b/src/main/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngester.java @@ -17,7 +17,6 @@ import static org.codelibs.core.lang.StringUtil.EMPTY; import static org.codelibs.fess.Constants.MAPPING_TYPE_ARRAY; -import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_VECTOR_FIELD; import static org.codelibs.fess.multimodal.MultiModalConstants.X_FESS_EMBEDDING; import java.util.Map; @@ -27,28 +26,36 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codelibs.fess.ingest.Ingester; +import org.codelibs.fess.multimodal.MultiModalConstants; +import org.codelibs.fess.multimodal.helper.MultiModalSearchHelper; import org.codelibs.fess.multimodal.util.EmbeddingUtil; import org.codelibs.fess.util.ComponentUtil; public class EmbeddingIngester extends Ingester { private static final Logger logger = LogManager.getLogger(EmbeddingIngester.class); + protected String vectorField; + @PostConstruct public void init() { - - ComponentUtil.getFessConfig().addCrawlerMetadataNameMapping(X_FESS_EMBEDDING, CONTENT_VECTOR_FIELD, MAPPING_TYPE_ARRAY, EMPTY); + final MultiModalSearchHelper helper = ComponentUtil.getComponent(MultiModalConstants.HELPER); + vectorField = helper.getVectorField(); + ComponentUtil.getFessConfig().addCrawlerMetadataNameMapping(X_FESS_EMBEDDING, vectorField, MAPPING_TYPE_ARRAY, EMPTY); + if (logger.isDebugEnabled()) { + logger.debug("vector field: {}", vectorField); + } } @Override protected Map process(final Map target) { - if (target.containsKey(CONTENT_VECTOR_FIELD)) { - logger.debug("[{}] : {}", CONTENT_VECTOR_FIELD, target); - if (target.get(CONTENT_VECTOR_FIELD) instanceof final String[] encodedEmbeddings) { + if (target.containsKey(vectorField)) { + logger.debug("[{}] : {}", vectorField, target); + if (target.get(vectorField) instanceof final String[] encodedEmbeddings) { final float[] embedding = EmbeddingUtil.decodeFloatArray(encodedEmbeddings[0]); logger.debug("embedding:{}", embedding); - target.put(CONTENT_VECTOR_FIELD, embedding); + target.put(vectorField, embedding); } else { - logger.warn("{} is not an array.", CONTENT_VECTOR_FIELD); + logger.warn("{} is not an array.", vectorField); } } return target; diff --git a/src/main/java/org/codelibs/fess/multimodal/query/MultiModalPhraseQueryCommand.java b/src/main/java/org/codelibs/fess/multimodal/query/MultiModalPhraseQueryCommand.java index 6d64b8c..c5ab16d 100644 --- a/src/main/java/org/codelibs/fess/multimodal/query/MultiModalPhraseQueryCommand.java +++ b/src/main/java/org/codelibs/fess/multimodal/query/MultiModalPhraseQueryCommand.java @@ -22,6 +22,7 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.search.PhraseQuery; import org.codelibs.fess.entity.QueryContext; +import org.codelibs.fess.entity.SearchRequestParams; import org.codelibs.fess.multimodal.rank.fusion.MultiModalSearcher; import org.codelibs.fess.multimodal.rank.fusion.MultiModalSearcher.SearchContext; import org.codelibs.fess.mylasta.direction.FessConfig; @@ -43,8 +44,9 @@ protected QueryBuilder convertPhraseQuery(final FessConfig fessConfig, final Que } final String text = String.join(" ", texts); - final QueryBuilder queryBuilder = - new MultiModalQueryBuilder.Builder().query(text).minScore(searchContext.getParams().getMinScore()).build().toQueryBuilder(); + final SearchRequestParams params = searchContext.getParams(); + final QueryBuilder queryBuilder = new MultiModalQueryBuilder.Builder().field(searchContext.getVectorField()).query(text) + .k(params.getPageSize()).build().toQueryBuilder(); context.addFieldLog(field, text); context.addHighlightedQuery(text); if (logger.isDebugEnabled()) { diff --git a/src/main/java/org/codelibs/fess/multimodal/query/MultiModalQueryBuilder.java b/src/main/java/org/codelibs/fess/multimodal/query/MultiModalQueryBuilder.java index f65d226..47654cb 100644 --- a/src/main/java/org/codelibs/fess/multimodal/query/MultiModalQueryBuilder.java +++ b/src/main/java/org/codelibs/fess/multimodal/query/MultiModalQueryBuilder.java @@ -16,7 +16,6 @@ package org.codelibs.fess.multimodal.query; import static org.codelibs.fess.multimodal.MultiModalConstants.CAS_CLIENT; -import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_VECTOR_FIELD; import org.codelibs.fess.multimodal.client.CasClient; import org.codelibs.fess.multimodal.index.query.KNNQueryBuilder; @@ -25,7 +24,9 @@ public class MultiModalQueryBuilder { + protected String field; protected String query; + protected int k; protected Float minScore; private MultiModalQueryBuilder() { @@ -34,14 +35,26 @@ private MultiModalQueryBuilder() { public static class Builder { + private String field; private String query; + private int k = 10; private Float minScore; + public Builder field(final String field) { + this.field = field; + return this; + } + public Builder query(final String query) { this.query = query; return this; } + public Builder k(final int k) { + this.k = k; + return this; + } + public Builder minScore(final Float minScore) { this.minScore = minScore; return this; @@ -49,7 +62,9 @@ public Builder minScore(final Float minScore) { public MultiModalQueryBuilder build() { final MultiModalQueryBuilder builder = new MultiModalQueryBuilder(); + builder.field = field; builder.query = query; + builder.k = k; builder.minScore = minScore; return builder; } @@ -58,7 +73,7 @@ public MultiModalQueryBuilder build() { public QueryBuilder toQueryBuilder() { final CasClient client = ComponentUtil.getComponent(CAS_CLIENT); final float[] embedding = client.getTextEmbedding(query); - return new KNNQueryBuilder.Builder().field(CONTENT_VECTOR_FIELD).vector(embedding).minScore(minScore).build(); + return new KNNQueryBuilder.Builder().field(field).vector(embedding).minScore(minScore).k(k).build(); } } diff --git a/src/main/java/org/codelibs/fess/multimodal/query/MultiModalTermQueryCommand.java b/src/main/java/org/codelibs/fess/multimodal/query/MultiModalTermQueryCommand.java index 882ee29..e9daebe 100644 --- a/src/main/java/org/codelibs/fess/multimodal/query/MultiModalTermQueryCommand.java +++ b/src/main/java/org/codelibs/fess/multimodal/query/MultiModalTermQueryCommand.java @@ -22,6 +22,7 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.search.TermQuery; import org.codelibs.fess.entity.QueryContext; +import org.codelibs.fess.entity.SearchRequestParams; import org.codelibs.fess.multimodal.rank.fusion.MultiModalSearcher; import org.codelibs.fess.multimodal.rank.fusion.MultiModalSearcher.SearchContext; import org.codelibs.fess.mylasta.direction.FessConfig; @@ -42,8 +43,9 @@ protected QueryBuilder convertDefaultTermQuery(final FessConfig fessConfig, fina return super.convertDefaultTermQuery(fessConfig, context, termQuery, boost, field, text); } - final QueryBuilder queryBuilder = - new MultiModalQueryBuilder.Builder().query(text).minScore(searchContext.getParams().getMinScore()).build().toQueryBuilder(); + final SearchRequestParams params = searchContext.getParams(); + final QueryBuilder queryBuilder = new MultiModalQueryBuilder.Builder().field(searchContext.getVectorField()).query(text) + .k(params.getPageSize()).build().toQueryBuilder(); context.addFieldLog(field, text); context.addHighlightedQuery(text); if (logger.isDebugEnabled()) { diff --git a/src/main/java/org/codelibs/fess/multimodal/rank/fusion/MultiModalSearcher.java b/src/main/java/org/codelibs/fess/multimodal/rank/fusion/MultiModalSearcher.java index 420ab0e..5e1b53a 100644 --- a/src/main/java/org/codelibs/fess/multimodal/rank/fusion/MultiModalSearcher.java +++ b/src/main/java/org/codelibs/fess/multimodal/rank/fusion/MultiModalSearcher.java @@ -15,7 +15,7 @@ */ package org.codelibs.fess.multimodal.rank.fusion; -import static org.codelibs.fess.multimodal.MultiModalConstants.MIN_SCORE; +import static org.codelibs.fess.multimodal.MultiModalConstants.HELPER; import java.util.Locale; import java.util.Map; @@ -24,11 +24,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.codelibs.core.lang.StringUtil; import org.codelibs.fess.entity.FacetInfo; import org.codelibs.fess.entity.GeoInfo; import org.codelibs.fess.entity.HighlightInfo; import org.codelibs.fess.entity.SearchRequestParams; +import org.codelibs.fess.multimodal.helper.MultiModalSearchHelper; import org.codelibs.fess.mylasta.action.FessUserBean; import org.codelibs.fess.rank.fusion.DefaultSearcher; import org.codelibs.fess.rank.fusion.SearchResult; @@ -40,35 +40,20 @@ public class MultiModalSearcher extends DefaultSearcher { protected ThreadLocal contextLocal = new ThreadLocal<>(); - protected Float minScore; - @PostConstruct public void register() { if (logger.isInfoEnabled()) { logger.info("Load {}", this.getClass().getSimpleName()); } - final String minScoreValue = System.getProperty(MIN_SCORE); - if (StringUtil.isNotBlank(minScoreValue)) { - try { - minScore = Float.valueOf(minScoreValue); - } catch (final NumberFormatException e) { - logger.debug("Failed to parse {}.", minScoreValue, e); - minScore = null; - } - } else { - minScore = null; - } - ComponentUtil.getRankFusionProcessor().register(this); } @Override protected SearchResult search(final String query, final SearchRequestParams params, final OptionalThing userBean) { try { - final SearchRequestParams reqParams = new SearchRequestParamsWrapper(params, minScore); - createContext(query, reqParams, userBean); - return super.search(query, reqParams, userBean); + final SearchContext searchContext = createContext(query, params, userBean); + return super.search(query, searchContext.getParams(), userBean); } finally { closeContext(); } @@ -79,7 +64,9 @@ public SearchContext createContext(final String query, final SearchRequestParams logger.warn("The context exists: {}", contextLocal.get()); contextLocal.remove(); } - final SearchContext context = new SearchContext(query, params, userBean); + final MultiModalSearchHelper multiModalSearchHelper = ComponentUtil.getComponent(HELPER); + final SearchRequestParams reqParams = new SearchRequestParamsWrapper(params, multiModalSearchHelper.getMinScore()); + final SearchContext context = new SearchContext(multiModalSearchHelper.getVectorField(), query, reqParams, userBean); contextLocal.set(context); return context; } @@ -98,16 +85,23 @@ public SearchContext getContext() { public static class SearchContext { + private final String vectorField; private final String query; private final SearchRequestParams params; private final OptionalThing userBean; - public SearchContext(final String query, final SearchRequestParams params, final OptionalThing userBean) { + public SearchContext(final String vectorField, final String query, final SearchRequestParams params, + final OptionalThing userBean) { + this.vectorField = vectorField; this.query = query; this.params = params; this.userBean = userBean; } + public String getVectorField() { + return vectorField; + } + public String getQuery() { return query; } diff --git a/src/main/resources/app++.xml b/src/main/resources/app++.xml index f36e101..448d3ff 100644 --- a/src/main/resources/app++.xml +++ b/src/main/resources/app++.xml @@ -5,4 +5,7 @@ + + diff --git a/src/main/resources/crawler/extractor++.xml b/src/main/resources/crawler/extractor++.xml index b6cd598..5045d1a 100644 --- a/src/main/resources/crawler/extractor++.xml +++ b/src/main/resources/crawler/extractor++.xml @@ -6,7 +6,7 @@ - + [ "image/gif", "image/jpeg", diff --git a/src/test/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngesterTest.java b/src/test/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngesterTest.java index 3c694e2..7cb357d 100644 --- a/src/test/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngesterTest.java +++ b/src/test/java/org/codelibs/fess/multimodal/ingest/EmbeddingIngesterTest.java @@ -15,8 +15,6 @@ */ package org.codelibs.fess.multimodal.ingest; -import static org.codelibs.fess.multimodal.MultiModalConstants.CONTENT_VECTOR_FIELD; - import java.util.HashMap; import java.util.Map; @@ -24,27 +22,30 @@ public class EmbeddingIngesterTest extends PlainTestCase { + private static final String VECTOR_FIELD = "vector_field"; + public void test_process() { final EmbeddingIngester ingester = new EmbeddingIngester(); + ingester.vectorField = VECTOR_FIELD; final Map target = new HashMap<>(); Map result = ingester.process(target); assertEquals(0, result.size()); target.clear(); - target.put(CONTENT_VECTOR_FIELD, new String[] { "P4AAAEAAAABAQAAA" }); + target.put(VECTOR_FIELD, new String[] { "P4AAAEAAAABAQAAA" }); result = ingester.process(target); assertEquals(1, result.size()); - final float[] array = (float[]) result.get(CONTENT_VECTOR_FIELD); + final float[] array = (float[]) result.get(VECTOR_FIELD); assertEquals(3, array.length); assertEquals(1.0f, array[0]); assertEquals(2.0f, array[1]); assertEquals(3.0f, array[2]); target.clear(); - target.put(CONTENT_VECTOR_FIELD, "P4AAAEAAAABAQAAA"); + target.put(VECTOR_FIELD, "P4AAAEAAAABAQAAA"); result = ingester.process(target); assertEquals(1, result.size()); - assertEquals("P4AAAEAAAABAQAAA", result.get(CONTENT_VECTOR_FIELD)); + assertEquals("P4AAAEAAAABAQAAA", result.get(VECTOR_FIELD)); } }