This is an automated email from the ASF dual-hosted git repository.
hansva 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 12133f1b61 Fix null value for json output and languagemodelchat (#6919)
12133f1b61 is described below
commit 12133f1b6116e6910ac1f5a0f8c7a3016534c081
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Apr 1 18:04:07 2026 +0200
Fix null value for json output and languagemodelchat (#6919)
---
.../jsonoutputenhanced/JsonEOutputMeta.java | 2 +-
.../jsonoutputenhanced/JsonEOutputMetaTest.java | 53 +++++++++++++
plugins/transforms/languagemodelchat/pom.xml | 2 +-
.../languagemodelchat/LanguageModelChat.java | 63 ++++++++-------
.../LanguageModelChatDataTest.java | 33 ++++++++
.../internals/LanguageModelChatModelNameTest.java | 34 +++++++++
.../LanguageModelFacadeReadMessagesJsonTest.java | 47 ++++++++++++
.../internals/LanguageModelTest.java | 77 +++++++++++++++++++
.../languagemodelchat/internals/MessageTest.java | 89 ++++++++++++++++++++++
.../languagemodelchat/internals/ModelTypeTest.java | 55 +++++++++++++
10 files changed, 427 insertions(+), 28 deletions(-)
diff --git
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
index ea79f6fd74..0935df79ce 100644
---
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
+++
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
@@ -181,7 +181,7 @@ public class JsonEOutputMeta extends
BaseTransformMeta<JsonEOutput, JsonEOutputD
operationType = OperationType.WRITE_TO_FILE;
encoding = Const.XML_ENCODING;
outputValue = CONST_OUTPUT_VALUE;
- jsonBloc = "result";
+ jsonBloc = "";
}
public JsonEOutputMeta(JsonEOutputMeta m) {
diff --git
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
index 5b8db3cc44..bd1c79783a 100644
---
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
+++
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
@@ -18,6 +18,7 @@
package org.apache.hop.pipeline.transforms.jsonoutputenhanced;
+import static org.apache.hop.core.util.Utils.isEmpty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -35,6 +36,58 @@ import org.junit.jupiter.api.Test;
class JsonEOutputMetaTest {
+ @Test
+ void newMeta_defaultJsonBlocIsEmpty() {
+ assertTrue(isEmpty(new JsonEOutputMeta().getJsonBloc()));
+ }
+
+ @Test
+ void deserializeSelfClosingJsonBloc_overridesConstructor() throws Exception {
+ String xml =
+ "<transform>\n"
+ + " <outputValue>prompt</outputValue>\n"
+ + " <jsonBloc/>\n"
+ + " <operation_type>outputvalue</operation_type>\n"
+ + "
<use_arrays_with_single_instance>N</use_arrays_with_single_instance>\n"
+ + " <use_single_item_per_group>N</use_single_item_per_group>\n"
+ + " <json_prittified>N</json_prittified>\n"
+ + " <encoding>UTF-8</encoding>\n"
+ + " <addtoresult>N</addtoresult>\n"
+ + " <file>\n"
+ + " <name/>\n"
+ + " <split_output_after>0</split_output_after>\n"
+ + " <extention>json</extention>\n"
+ + " <append>N</append>\n"
+ + " <split>N</split>\n"
+ + " <haspartno>N</haspartno>\n"
+ + " <add_date>N</add_date>\n"
+ + " <add_time>N</add_time>\n"
+ + " <create_parent_folder>N</create_parent_folder>\n"
+ + " <doNotOpenNewFileInit>N</doNotOpenNewFileInit>\n"
+ + " </file>\n"
+ + " <additional_fields>\n"
+ + " <json_size_field/>\n"
+ + " </additional_fields>\n"
+ + " <key_fields/>\n"
+ + " <fields>\n"
+ + " <field>\n"
+ + " <name>role</name>\n"
+ + " <element>role</element>\n"
+ + " <json_fragment>N</json_fragment>\n"
+ + " <is_without_enclosing>N</is_without_enclosing>\n"
+ + " <remove_if_blank>Y</remove_if_blank>\n"
+ + " </field>\n"
+ + " </fields>\n"
+ + "</transform>\n";
+ JsonEOutputMeta meta = new JsonEOutputMeta();
+ XmlMetadataUtil.deSerializeFromXml(
+ XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
+ JsonEOutputMeta.class,
+ meta,
+ new MemoryMetadataProvider());
+ assertTrue(isEmpty(meta.getJsonBloc()));
+ }
+
@Test
void testLoadSave() throws Exception {
Path path =
diff --git a/plugins/transforms/languagemodelchat/pom.xml
b/plugins/transforms/languagemodelchat/pom.xml
index ec94aea0b6..dd015e7e80 100644
--- a/plugins/transforms/languagemodelchat/pom.xml
+++ b/plugins/transforms/languagemodelchat/pom.xml
@@ -49,7 +49,7 @@
<dependency>
<groupId>com.squareup.moshi</groupId>
<artifactId>moshi</artifactId>
- <version>1.3.1</version>
+ <version>1.15.2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
diff --git
a/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
b/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
index 0afdb842ec..d30b38f6ad 100644
---
a/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
+++
b/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
@@ -27,7 +27,6 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
-import static
org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;
import static org.apache.hop.core.util.Utils.isEmpty;
import static
org.apache.hop.pipeline.transforms.languagemodelchat.internals.ui.i18nUtil.i18n;
@@ -192,42 +191,40 @@ public class LanguageModelChat extends
BaseTransform<LanguageModelChatMeta, Lang
() -> {
try {
processSync(inputRow, message);
- jobs.decrementAndGet();
- synchronized (lock) {
- lock.notifyAll();
- }
} catch (Exception e) {
try {
- processError(inputRow, e);
+ if (getTransformMeta().isDoingErrorHandling()) {
+ String errorMessage = e.toString();
+ putError(
+ getInputRowMeta(),
+ inputRow,
+ 1L,
+ errorMessage,
+ meta.getInputField(),
+ "LanguageModelChat001");
+ } else {
+ logError(i18n("LanguageModelChat.ErrorInTransformRunning") +
e.getMessage(), e);
+ setErrors(1L);
+ stopAll();
+ }
} catch (HopTransformException ex) {
logError(e.getMessage(), e);
logError(ex.getMessage(), ex);
+ if (!getTransformMeta().isDoingErrorHandling()) {
+ setErrors(1L);
+ stopAll();
+ }
+ }
+ } finally {
+ jobs.decrementAndGet();
+ synchronized (lock) {
+ lock.notifyAll();
}
}
});
jobs.incrementAndGet();
}
- private void processError(Object[] inputRow, Exception e) throws
HopTransformException {
- logError(e.getMessage(), e);
- int newFields = 8;
- Object[] outputRow = new Object[inputRow.length + newFields +
OVER_ALLOCATE_SIZE];
- arraycopy(inputRow, 0, outputRow, 0, inputRow.length);
-
- LanguageModel model = new LanguageModel(meta);
-
- outputRow[data.indexOfModelType] = model.getType().code();
- outputRow[data.indexOfModelName] = model.getName();
- outputRow[data.indexOfFinishReason] = "error";
- outputRow[data.indexOfInputTokenCount] = null;
- outputRow[data.indexOfOutputTokenCount] = null;
- outputRow[data.indexOfTotalTokenCount] = null;
- outputRow[data.indexOfInferenceTime] = null;
- outputRow[data.indexOfOutput] = getRootCauseMessage(e);
-
- putRow(data.outputRowMeta, outputRow);
- }
-
private void processSync(Object[] inputRow, String message)
throws HopTransformException, HopValueException {
@@ -387,4 +384,18 @@ public class LanguageModelChat extends
BaseTransform<LanguageModelChatMeta, Lang
super.putRow(rowMeta, row);
}
}
+
+ @Override
+ public void putError(
+ IRowMeta rowMeta,
+ Object[] row,
+ long nrErrors,
+ String errorDescriptions,
+ String fieldNames,
+ String errorCodes)
+ throws HopTransformException {
+ synchronized (this) {
+ super.putError(rowMeta, row, nrErrors, errorDescriptions, fieldNames,
errorCodes);
+ }
+ }
}
diff --git
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChatDataTest.java
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChatDataTest.java
new file mode 100644
index 0000000000..36ab9f3410
--- /dev/null
+++
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChatDataTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.hop.pipeline.transforms.languagemodelchat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class LanguageModelChatDataTest {
+
+ @Test
+ void defaultIndicesAreUnset() {
+ LanguageModelChatData d = new LanguageModelChatData();
+ assertEquals(-1, d.indexOfInputField);
+ assertEquals(-1, d.indexOfOutput);
+ assertEquals(0, d.nrPrevFields);
+ }
+}
diff --git
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelChatModelNameTest.java
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelChatModelNameTest.java
new file mode 100644
index 0000000000..7c21004d44
--- /dev/null
+++
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelChatModelNameTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.hop.pipeline.transforms.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+class LanguageModelChatModelNameTest {
+
+ @Test
+ void everyEnumHasModelId() {
+ for (LanguageModelChatModelName n : LanguageModelChatModelName.values()) {
+ assertNotNull(n.toString());
+ assertFalse(n.toString().isBlank());
+ }
+ }
+}
diff --git
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelFacadeReadMessagesJsonTest.java
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelFacadeReadMessagesJsonTest.java
new file mode 100644
index 0000000000..5c424df8db
--- /dev/null
+++
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelFacadeReadMessagesJsonTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.hop.pipeline.transforms.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import
org.apache.hop.pipeline.transforms.languagemodelchat.LanguageModelChatMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class LanguageModelFacadeReadMessagesJsonTest {
+
+ @RegisterExtension
+ static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
+
+ private LanguageModelFacade facade() {
+ return new LanguageModelFacade(new Variables(), new
LanguageModelChatMeta());
+ }
+
+ @Test
+ void readMessagesJson_topLevelArray() throws HopValueException {
+ List<Message> messages =
+ facade().readMessagesJson("[{\"role\":\"user\",\"content\":\"hello
from array\"}]");
+ assertEquals(1, messages.size());
+ assertEquals("user", messages.get(0).getRole());
+ assertEquals("hello from array", messages.get(0).getContent());
+ }
+}
diff --git
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelTest.java
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelTest.java
new file mode 100644
index 0000000000..7b77ae4fef
--- /dev/null
+++
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.hop.pipeline.transforms.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import
org.apache.hop.pipeline.transforms.languagemodelchat.LanguageModelChatMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class LanguageModelTest {
+
+ @RegisterExtension
+ static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
+
+ @Test
+ void openAiModel() {
+ LanguageModelChatMeta meta = new LanguageModelChatMeta();
+ meta.setModelType(ModelType.OPEN_AI.code());
+ meta.setOpenAiModelName("gpt-test");
+ LanguageModel lm = new LanguageModel(meta);
+ assertEquals(ModelType.OPEN_AI, lm.getType());
+ assertEquals("gpt-test", lm.getName());
+ }
+
+ @Test
+ void huggingFaceModel() {
+ LanguageModelChatMeta meta = new LanguageModelChatMeta();
+ meta.setModelType(ModelType.HUGGING_FACE.code());
+ meta.setHuggingFaceModelId("org/llama");
+ LanguageModel lm = new LanguageModel(meta);
+ assertEquals(ModelType.HUGGING_FACE, lm.getType());
+ assertEquals("org/llama", lm.getName());
+ }
+
+ @Test
+ void equalsAndHashCode() {
+ LanguageModelChatMeta m1 = new LanguageModelChatMeta();
+ m1.setModelType(ModelType.OLLAMA.code());
+ m1.setOllamaModelName("phi3");
+ LanguageModelChatMeta m2 = new LanguageModelChatMeta();
+ m2.setModelType(ModelType.OLLAMA.code());
+ m2.setOllamaModelName("phi3");
+ LanguageModel a = new LanguageModel(m1);
+ LanguageModel b = new LanguageModel(m2);
+ assertEquals(a, b);
+ assertEquals(a.hashCode(), b.hashCode());
+ }
+
+ @Test
+ void notEqualsWhenNameDiffers() {
+ LanguageModelChatMeta m1 = new LanguageModelChatMeta();
+ m1.setModelType(ModelType.MISTRAL.code());
+ m1.setMistralModelName("a");
+ LanguageModelChatMeta m2 = new LanguageModelChatMeta();
+ m2.setModelType(ModelType.MISTRAL.code());
+ m2.setMistralModelName("b");
+ assertNotEquals(new LanguageModel(m1), new LanguageModel(m2));
+ }
+}
diff --git
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/MessageTest.java
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/MessageTest.java
new file mode 100644
index 0000000000..1ecdff432a
--- /dev/null
+++
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/MessageTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.hop.pipeline.transforms.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import dev.langchain4j.data.message.ChatMessage;
+import dev.langchain4j.data.message.ChatMessageType;
+import java.util.List;
+import java.util.Optional;
+import org.apache.hop.core.exception.HopValueException;
+import org.junit.jupiter.api.Test;
+
+class MessageTest {
+
+ @Test
+ void toChatMessages_systemAndAssistant() throws HopValueException {
+ Message sys = new Message();
+ sys.setRole("system");
+ sys.setContent("s");
+ Message asst = new Message();
+ asst.setRole("assistant");
+ asst.setContent("a");
+ assertEquals(1, Message.toChatMessages(List.of(sys)).size());
+ assertEquals(1, Message.toChatMessages(List.of(asst)).size());
+ }
+
+ @Test
+ void toChatMessages_userText() throws HopValueException {
+ Message m = new Message();
+ m.setRole("user");
+ m.setContent("hello");
+ List<ChatMessage> out = Message.toChatMessages(List.of(m));
+ assertEquals(1, out.size());
+ assertEquals(ChatMessageType.USER, out.get(0).type());
+ }
+
+ @Test
+ void toChatMessages_missingRole_throws() {
+ Message m = new Message();
+ m.setContent("orphan content");
+ NullPointerException ex =
+ assertThrows(NullPointerException.class, () ->
Message.toChatMessages(List.of(m)));
+ assertTrue(ex.getMessage().contains("role"));
+ }
+
+ @Test
+ void toChatMessages_invalidRole() {
+ Message m = new Message();
+ m.setRole("unknown-role");
+ m.setContent("x");
+ assertThrows(HopValueException.class, () ->
Message.toChatMessages(List.of(m)));
+ }
+
+ @Test
+ void detectRoleNames() {
+ Message u = new Message();
+ u.setRole("user");
+ Message s = new Message();
+ s.setRole("system");
+ Message a = new Message();
+ a.setRole("assistant");
+ assertEquals(Optional.of("user"), Message.detectUserRoleName(List.of(u,
s)));
+ assertEquals(Optional.of("system"),
Message.detectSystemRoleName(List.of(u, s)));
+ assertEquals(Optional.of("assistant"),
Message.detectAssistantRoleName(List.of(u, a)));
+ }
+
+ @Test
+ void detectRoleNames_empty() {
+ assertTrue(Message.detectUserRoleName(List.of()).isEmpty());
+ }
+}
diff --git
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/ModelTypeTest.java
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/ModelTypeTest.java
new file mode 100644
index 0000000000..b081134674
--- /dev/null
+++
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/ModelTypeTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.hop.pipeline.transforms.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class ModelTypeTest {
+
+ @RegisterExtension
+ static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
+
+ @Test
+ void modelTypeDescriptions_hasEntryPerEnum() {
+ assertEquals(ModelType.values().length,
ModelType.modelTypeDescriptions().length);
+ }
+
+ @Test
+ void typeFromDescription_matchesEnumDescription() {
+ ModelType openAi = ModelType.OPEN_AI;
+ assertEquals(openAi, ModelType.typeFromDescription(openAi.description()));
+ }
+
+ @Test
+ void typeFromDescription_unknownFallsBackToOpenAi() {
+ assertEquals(ModelType.OPEN_AI,
ModelType.typeFromDescription("___not_a_real_model_label___"));
+ }
+
+ @Test
+ void codeAndDescriptionPresent() {
+ for (ModelType t : ModelType.values()) {
+ assertNotNull(t.code());
+ assertNotNull(t.description());
+ }
+ }
+}