This is an automated email from the ASF dual-hosted git repository.
mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new d65582f901 Fixes #8750 : Embed text: skip whitespace-only text instead
of failing the batch (#8831)
d65582f901 is described below
commit d65582f901756f6ffd7b321a1217e084f6f9dd9a
Author: Bart Maertens <[email protected]>
AuthorDate: Fri Oct 9 22:53:40 2026 +0200
Fixes #8750 : Embed text: skip whitespace-only text instead of failing the
batch (#8831)
---
.../ROOT/pages/pipeline/transforms/embedtext.adoc | 2 +-
integration-tests/ai/0004-embed-blank-text.hpl | 412 +++++++++++++++++++++
integration-tests/ai/README.md | 1 +
.../ai/main-0004-embed-blank-text.hwf | 91 +++++
.../hop/ai/transforms/embedtext/EmbedText.java | 11 +-
.../hop/ai/transforms/embedtext/EmbedTextTest.java | 31 ++
6 files changed, 546 insertions(+), 2 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/embedtext.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/embedtext.adoc
index 8beb2d237a..0f36d147b8 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/embedtext.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/embedtext.adoc
@@ -70,7 +70,7 @@ Vector is the better choice when the Vector value type plugin
is installed. When
|Transform name|Name of the transform, unique within the pipeline.
|AI provider|The AI provider serving the embedding model.
|Model|Optional. Overrides the provider's EMBEDDING model for this transform
only. Accepts variables.
-|Input field|Field holding the text to embed. A row whose text is empty is
passed on with empty output fields rather than being dropped, and keeps its
place in the stream.
+|Input field|Field holding the text to embed. A row whose text is empty or
only whitespace is passed on with empty output fields rather than being
dropped, and keeps its place in the stream.
|Output field|Field the embedding is written to.
|Output format|`STRING` for a JSON array, `VECTOR` for a Vector field.
|Batch size|Rows buffered before the provider is called. Accepts variables.
Rows with empty text are buffered so they keep their place in the stream, but
are not sent, so a batch can send fewer texts than its size.
diff --git a/integration-tests/ai/0004-embed-blank-text.hpl
b/integration-tests/ai/0004-embed-blank-text.hpl
new file mode 100644
index 0000000000..faa5c9c899
--- /dev/null
+++ b/integration-tests/ai/0004-embed-blank-text.hpl
@@ -0,0 +1,412 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+ <info>
+ <name>0004-embed-blank-text</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Embed text in one batch that mixes text with empty and
whitespace-only values.
+ The blank values are made by a formula, because a Data Grid turns a
value of only spaces
+ into null before Embed text would see it.
+ The rows with text get an embedding, the others pass through without
one, and no row is
+ lost. Sending blank text used to stop the transform for the whole batch
(#8750).</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <pipeline_status>0</pipeline_status>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/10/09 12:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/09 12:00:00.000</modified_date>
+ <key_for_session_key/>
+ <is_key_private>N</is_key_private>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop> <from>chunks</from> <to>make blank text</to> <enabled>Y</enabled>
</hop>
+ <hop> <from>make blank text</from> <to>Embed text</to>
<enabled>Y</enabled> </hop>
+ <hop> <from>Embed text</from> <to>has text?</to> <enabled>Y</enabled>
</hop>
+ <hop> <from>has text?</from> <to>embedded</to> <enabled>Y</enabled> </hop>
+ <hop> <from>has text?</from> <to>not embedded</to> <enabled>Y</enabled>
</hop>
+ <hop> <from>embedded</from> <to>OUTPUT</to> <enabled>Y</enabled> </hop>
+ <hop> <from>embedded</from> <to>Abort</to> <enabled>Y</enabled> </hop>
+ <hop> <from>not embedded</from> <to>OUTPUT</to> <enabled>Y</enabled> </hop>
+ <hop> <from>not embedded</from> <to>Abort</to> <enabled>Y</enabled> </hop>
+ <hop> <from>OUTPUT</from> <to>count rows</to> <enabled>Y</enabled> </hop>
+ <hop> <from>count rows</from> <to>check row count</to>
<enabled>Y</enabled> </hop>
+ <hop> <from>check row count</from> <to>Done</to> <enabled>Y</enabled>
</hop>
+ <hop> <from>check row count</from> <to>Abort</to> <enabled>Y</enabled>
</hop>
+ </order>
+ <transform>
+ <name>chunks</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>Apache Hop is a data orchestration platform.</item>
+ <item/>
+ <item>Y</item>
+ </line>
+ <line>
+ <item/>
+ <item>spaces</item>
+ <item>N</item>
+ </line>
+ <line>
+ <item/>
+ <item>tab-newline</item>
+ <item>N</item>
+ </line>
+ <line>
+ <item/>
+ <item/>
+ <item>N</item>
+ </line>
+ <line>
+ <item>Embeddings turn text into vectors for retrieval.</item>
+ <item/>
+ <item>Y</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>text</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>blank</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>has_text</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>96</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>make blank text</name>
+ <type>Formula</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <formulas>
+ <formula>
+ <field_name>chunk_text</field_name>
+ <formula>IF([blank]="spaces",REPT("
",3),IF([blank]="tab-newline",CHAR(9)&"
"&CHAR(10),[text]&""))</formula>
+ <replace_field/>
+ <value_length>-1</value_length>
+ <value_precision>-1</value_precision>
+ <value_type>2</value_type>
+ </formula>
+ </formulas>
+ <attributes/>
+ <GUI>
+ <xloc>176</xloc>
+ <yloc>176</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Embed text</name>
+ <type>EmbedText</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <ai_provider>ollama-embeddings</ai_provider>
+ <model_name/>
+ <input_field>chunk_text</input_field>
+ <output_field>embedding</output_field>
+ <output_format>STRING</output_format>
+ <batch_size>16</batch_size>
+ <include_model_metadata>Y</include_model_metadata>
+ <model_field>embedding_model</model_field>
+ <dimensions_field>embedding_dimensions</dimensions_field>
+ <attributes/>
+ <GUI>
+ <xloc>256</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>has text?</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>embedded</send_true_to>
+ <send_false_to>not embedded</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>has_text</leftvalue>
+ <function>=</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>Y</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>416</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>embedded</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>OUTPUT</send_true_to>
+ <send_false_to>Abort</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>embedding</leftvalue>
+ <function>IS NOT NULL</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text></text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>48</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>not embedded</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>OUTPUT</send_true_to>
+ <send_false_to>Abort</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>embedding</leftvalue>
+ <function>IS NULL</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text></text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>144</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>OUTPUT</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>736</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>count rows</name>
+ <type>MemoryGroupBy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <give_back_row>N</give_back_row>
+ <group>
+ </group>
+ <fields>
+ <field>
+ <aggregate>row_count</aggregate>
+ <subject>has_text</subject>
+ <type>COUNT_ALL</type>
+ <valuefield/>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>896</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>check row count</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>Done</send_true_to>
+ <send_false_to>Abort</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>row_count</leftvalue>
+ <function>=</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>5</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>1056</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Done</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>1216</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Abort</name>
+ <type>Abort</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <row_threshold>0</row_threshold>
+ <message>Embed text gave a row with text no embedding, gave blank text an
embedding, or lost a row. The row logged above shows what came back.</message>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <attributes/>
+ <GUI>
+ <xloc>736</xloc>
+ <yloc>256</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/ai/README.md b/integration-tests/ai/README.md
index cf5c67f1ab..7f0e465ce9 100644
--- a/integration-tests/ai/README.md
+++ b/integration-tests/ai/README.md
@@ -42,3 +42,4 @@ standard suite. See `disabled.txt`.
| Test | Covers |
|---|---|
| `0001-embed-text` | The Embed text transform against `nomic-embed-text`: the
vector width, the model name on the row, and that an embedding comes back. The
vector values themselves vary per call, so they are not compared. |
+| `0004-embed-blank-text` | Embed text with empty and whitespace-only values
in the same batch as real text: those rows pass through without an embedding,
the others get one, and no row is lost. |
diff --git a/integration-tests/ai/main-0004-embed-blank-text.hwf
b/integration-tests/ai/main-0004-embed-blank-text.hwf
new file mode 100644
index 0000000000..e0be40dcad
--- /dev/null
+++ b/integration-tests/ai/main-0004-embed-blank-text.hwf
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+ <name>main-0004-embed-blank-text</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Embed text against a real Ollama embedding model, with empty
and whitespace-only text in the batch.</description>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/10/09 12:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/09 12:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+ <hour>12</hour>
+ <intervalMinutes>60</intervalMinutes>
+ <intervalSeconds>0</intervalSeconds>
+ <minutes>0</minutes>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <weekDay>1</weekDay>
+ <parallel>N</parallel>
+ <xloc>64</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Embed blank text</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <clear_files>N</clear_files>
+ <clear_rows>N</clear_rows>
+ <create_parent_folder>N</create_parent_folder>
+ <exec_per_row>N</exec_per_row>
+ <filename>${PROJECT_HOME}/0004-embed-blank-text.hpl</filename>
+ <loglevel>Basic</loglevel>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <params_from_previous>N</params_from_previous>
+ <run_configuration>local</run_configuration>
+ <set_append_logfile>N</set_append_logfile>
+ <set_logfile>N</set_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <parallel>N</parallel>
+ <xloc>256</xloc>
+ <yloc>80</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>Embed blank text</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git
a/plugins/tech/ai/src/main/java/org/apache/hop/ai/transforms/embedtext/EmbedText.java
b/plugins/tech/ai/src/main/java/org/apache/hop/ai/transforms/embedtext/EmbedText.java
index 709116845c..ef7c3e1fc6 100644
---
a/plugins/tech/ai/src/main/java/org/apache/hop/ai/transforms/embedtext/EmbedText.java
+++
b/plugins/tech/ai/src/main/java/org/apache/hop/ai/transforms/embedtext/EmbedText.java
@@ -84,7 +84,7 @@ public class EmbedText extends BaseTransform<EmbedTextMeta,
EmbedTextData> {
String text = data.inputRowMeta.getString(row, data.inputFieldIndex);
data.pendingRows.add(row);
// Null marks a row that needs no embedding. It still queues, so the
output keeps input order.
- data.pendingTexts.add(Utils.isEmpty(text) ? null : text);
+ data.pendingTexts.add(hasNothingToEmbed(text) ? null : text);
if (data.pendingRows.size() >= data.batchSize) {
flushBatch();
}
@@ -160,6 +160,15 @@ public class EmbedText extends
BaseTransform<EmbedTextMeta, EmbedTextData> {
}
}
+ /**
+ * True for text that has no content to embed. langchain4j refuses a text
segment that trims to
+ * nothing, which takes control characters as well as spaces, and would stop
the transform before
+ * the provider is called. Unicode whitespace has nothing to embed either.
+ */
+ static boolean hasNothingToEmbed(String text) {
+ return text == null || text.trim().isEmpty() || text.isBlank();
+ }
+
private void withEmbedding(Object[] output, float[] vector) {
int index = data.inputRowMeta.size();
output[index++] = data.emitVector ? vector : toJsonArray(vector);
diff --git
a/plugins/tech/ai/src/test/java/org/apache/hop/ai/transforms/embedtext/EmbedTextTest.java
b/plugins/tech/ai/src/test/java/org/apache/hop/ai/transforms/embedtext/EmbedTextTest.java
index ba2200e5c1..146e09eadb 100644
---
a/plugins/tech/ai/src/test/java/org/apache/hop/ai/transforms/embedtext/EmbedTextTest.java
+++
b/plugins/tech/ai/src/test/java/org/apache/hop/ai/transforms/embedtext/EmbedTextTest.java
@@ -17,6 +17,7 @@
package org.apache.hop.ai.transforms.embedtext;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -102,6 +103,30 @@ class EmbedTextTest {
assertEquals(2, passed.size(), "rows with nothing to embed are not
dropped");
}
+ @Test
+ void treatsWhitespaceOnlyTextAsEmpty() throws Exception {
+ // langchain4j refuses a blank text segment, which would stop the
transform outright.
+ run(Arrays.asList(new Object[] {"first"}, new Object[] {" \t\n "}, new
Object[] {"third"}));
+
+ assertEquals(3, passed.size());
+ assertEquals("[1.0,2.0]", passed.get(0)[1]);
+ assertEquals(" \t\n ", passed.get(1)[0], "the blank text itself is passed
on unchanged");
+ assertNull(passed.get(1)[1], "a row with blank text gets no embedding");
+ assertEquals("[1.0,2.0]", passed.get(2)[1]);
+ }
+
+ @Test
+ void findsNothingToEmbedInTextThatOnlyHoldsWhitespaceOrControlCharacters() {
+ assertTrue(EmbedText.hasNothingToEmbed(null));
+ assertTrue(EmbedText.hasNothingToEmbed(""));
+ assertTrue(EmbedText.hasNothingToEmbed(" \t\r\n"));
+ // langchain4j trims these away and then refuses the segment.
+ assertTrue(EmbedText.hasNothingToEmbed("\u0000\u0007"));
+ // Unicode spaces that trim keeps.
+ assertTrue(EmbedText.hasNothingToEmbed("\u2003\u3000"));
+ assertFalse(EmbedText.hasNothingToEmbed(" a "));
+ }
+
@Test
void divertsOnlyTheRowsThatWereActuallySent() throws Exception {
// A row with no text is not in the request, so a provider failure is not
its failure.
@@ -165,6 +190,12 @@ class EmbedTextTest {
throw new RuntimeException("provider is down");
}
List<TextSegment> segments = invocation.getArgument(0);
+ // Like the providers, reject blank input for the whole request.
+ for (TextSegment segment : segments) {
+ if (segment.text().isBlank()) {
+ throw new RuntimeException("input must not be blank");
+ }
+ }
List<Embedding> embeddings = new ArrayList<>();
for (int i = 0; i < segments.size(); i++) {
embeddings.add(Embedding.from(new float[] {1.0f, 2.0f}));