Convert spaces to tabs

This commit is contained in:
extio-de
2025-12-13 15:26:38 +01:00
parent a41e5cf7b5
commit d2f5c46847
18 changed files with 1479 additions and 1479 deletions

View File

@@ -9,89 +9,89 @@ import de.extio.lmlib.profile.ModelCategory;
import de.extio.lmlib.profile.ModelProfile; import de.extio.lmlib.profile.ModelProfile;
public record Agent(String name, public record Agent(String name,
AgentType agentType, AgentType agentType,
ModelCategory modelCategory, ModelCategory modelCategory,
ModelProfile modelProfile, ModelProfile modelProfile,
String systemPrompt, String systemPrompt,
String textTemplate, String textTemplate,
AgentResponseHandler responseHandler, AgentResponseHandler responseHandler,
Consumer<AgentContext> preProcessor, Consumer<AgentContext> preProcessor,
Consumer<AgentContext> postProcessor, Consumer<AgentContext> postProcessor,
Function<List<AgentContext>, Function<List<AgentContext>,
List<AgentContext>> merger, List<AgentContext>> merger,
Function<AgentContext, AgentNext> chooseNext) implements BaseAgent { Function<AgentContext, AgentNext> chooseNext) implements BaseAgent {
public Agent { public Agent {
if (name == null) { if (name == null) {
throw new IllegalArgumentException("Name cannot be null"); throw new IllegalArgumentException("Name cannot be null");
} }
if (agentType == null) { if (agentType == null) {
throw new IllegalArgumentException("Agent type cannot be null"); throw new IllegalArgumentException("Agent type cannot be null");
} }
} }
@Override @Override
public AgentType agentType(final AgentContext context) { public AgentType agentType(final AgentContext context) {
if (agentType != null) { if (agentType != null) {
return agentType; return agentType;
} }
return BaseAgent.super.agentType(context); return BaseAgent.super.agentType(context);
} }
@Override @Override
public ModelCategory modelCategory(final AgentContext context) { public ModelCategory modelCategory(final AgentContext context) {
if (modelCategory != null) { if (modelCategory != null) {
return modelCategory; return modelCategory;
} }
return BaseAgent.super.modelCategory(context); return BaseAgent.super.modelCategory(context);
} }
@Override @Override
public ModelProfile modelProfile(final AgentContext context) { public ModelProfile modelProfile(final AgentContext context) {
if (modelProfile != null) { if (modelProfile != null) {
return modelProfile; return modelProfile;
} }
return BaseAgent.super.modelProfile(context); return BaseAgent.super.modelProfile(context);
} }
@Override @Override
public AgentResponseHandler responseHandler(final AgentContext context) { public AgentResponseHandler responseHandler(final AgentContext context) {
if (responseHandler != null) { if (responseHandler != null) {
return responseHandler; return responseHandler;
} }
return BaseAgent.super.responseHandler(context); return BaseAgent.super.responseHandler(context);
} }
@Override @Override
public void preProcess(final AgentContext context) { public void preProcess(final AgentContext context) {
if (preProcessor != null) { if (preProcessor != null) {
preProcessor.accept(context); preProcessor.accept(context);
} }
BaseAgent.super.preProcess(context); BaseAgent.super.preProcess(context);
} }
@Override @Override
public void postProcess(final AgentContext context) { public void postProcess(final AgentContext context) {
if (postProcessor != null) { if (postProcessor != null) {
postProcessor.accept(context); postProcessor.accept(context);
} }
BaseAgent.super.postProcess(context); BaseAgent.super.postProcess(context);
} }
@Override @Override
public List<AgentContext> merge(final List<AgentContext> contexts) { public List<AgentContext> merge(final List<AgentContext> contexts) {
if (merger != null) { if (merger != null) {
return merger.apply(contexts); return merger.apply(contexts);
} }
return BaseAgent.super.merge(contexts); return BaseAgent.super.merge(contexts);
} }
@Override @Override
public AgentNext chooseNextAgent(final AgentContext context) { public AgentNext chooseNextAgent(final AgentContext context) {
if (chooseNext != null) { if (chooseNext != null) {
return chooseNext.apply(context); return chooseNext.apply(context);
} }
return BaseAgent.super.chooseNextAgent(context); return BaseAgent.super.chooseNextAgent(context);
} }
} }

View File

@@ -2,9 +2,9 @@ package de.extio.lmlib.agent;
public final record AgentNext(String name, Boolean gradingPassed) { public final record AgentNext(String name, Boolean gradingPassed) {
public static final AgentNext END = new AgentNext(null, null); public static final AgentNext END = new AgentNext(null, null);
public static AgentNext end(final AgentContext context) { public static AgentNext end(final AgentContext context) {
return END; return END;
} }
} }

View File

@@ -13,164 +13,164 @@ import de.extio.lmlib.client.Conversation;
public class CsvAgentResponseHandler implements AgentResponseHandler { public class CsvAgentResponseHandler implements AgentResponseHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CsvAgentResponseHandler.class); private static final Logger LOGGER = LoggerFactory.getLogger(CsvAgentResponseHandler.class);
private final List<String> headings; private final List<String> headings;
private final String keyPrefix; private final String keyPrefix;
private final String fieldSeparator; private final String fieldSeparator;
private final String fieldSeparatorQuoted; private final String fieldSeparatorQuoted;
private final String errorPromptSuffix; private final String errorPromptSuffix;
public CsvAgentResponseHandler(final List<String> headings, final String keyPrefix) { public CsvAgentResponseHandler(final List<String> headings, final String keyPrefix) {
this(headings, keyPrefix, ";"); this(headings, keyPrefix, ";");
} }
public CsvAgentResponseHandler(final List<String> headings, final String keyPrefix, final String fieldSeparator) { public CsvAgentResponseHandler(final List<String> headings, final String keyPrefix, final String fieldSeparator) {
if (headings == null || headings.isEmpty()) { if (headings == null || headings.isEmpty()) {
throw new IllegalArgumentException("Headings list cannot be null or empty"); throw new IllegalArgumentException("Headings list cannot be null or empty");
} }
this.headings = List.copyOf(headings); this.headings = List.copyOf(headings);
this.keyPrefix = keyPrefix != null ? keyPrefix : ""; this.keyPrefix = keyPrefix != null ? keyPrefix : "";
this.fieldSeparator = fieldSeparator; this.fieldSeparator = fieldSeparator;
this.fieldSeparatorQuoted = Pattern.quote(fieldSeparator); this.fieldSeparatorQuoted = Pattern.quote(fieldSeparator);
this.errorPromptSuffix = "\n\nThe previous response could not be fully processed or validated. " + this.errorPromptSuffix = "\n\nThe previous response could not be fully processed or validated. " +
"Please format the response as CSV with '" + fieldSeparator + "' as field separator. " + "Please format the response as CSV with '" + fieldSeparator + "' as field separator. " +
"Expected columns: " + String.join(", ", headings) + ". One row per line."; "Expected columns: " + String.join(", ", headings) + ". One row per line.";
} }
@Override @Override
public boolean handle(final Completion completion, final AgentContext context) { public boolean handle(final Completion completion, final AgentContext context) {
final String resp = completion.response(); final String resp = completion.response();
if (resp == null || resp.isBlank()) { if (resp == null || resp.isBlank()) {
LOGGER.warn("Empty completion response for CSV parsing"); LOGGER.warn("Empty completion response for CSV parsing");
return false; return false;
} }
try { try {
final boolean success = this.parseCsv(resp, context); final boolean success = this.parseCsv(resp, context);
if (!success) { if (!success) {
LOGGER.warn("No valid CSV data parsed from response"); LOGGER.warn("No valid CSV data parsed from response");
this.addCsvResponseErrorPrompt(context); this.addCsvResponseErrorPrompt(context);
return false; return false;
} }
return true; return true;
} }
catch (final Exception ex) { catch (final Exception ex) {
LOGGER.warn("Failed to parse CSV response", ex); LOGGER.warn("Failed to parse CSV response", ex);
this.addCsvResponseErrorPrompt(context); this.addCsvResponseErrorPrompt(context);
return false; return false;
} }
} }
private boolean parseCsv(final String csvText, final AgentContext context) { private boolean parseCsv(final String csvText, final AgentContext context) {
final String[] lines = csvText.split("\n"); final String[] lines = csvText.split("\n");
int startLine = 0; int startLine = 0;
for (int i = 0; i < lines.length; i++) { for (int i = 0; i < lines.length; i++) {
final String line = lines[i].trim(); final String line = lines[i].trim();
if (line.isEmpty()) { if (line.isEmpty()) {
continue; continue;
} }
if (!line.contains(this.fieldSeparator)) { if (!line.contains(this.fieldSeparator)) {
startLine = i + 1; startLine = i + 1;
continue; continue;
} }
final String lowerLine = line.toLowerCase(); final String lowerLine = line.toLowerCase();
boolean isHeader = false; boolean isHeader = false;
for (final String heading : this.headings) { for (final String heading : this.headings) {
if (lowerLine.contains(heading.toLowerCase())) { if (lowerLine.contains(heading.toLowerCase())) {
isHeader = true; isHeader = true;
break; break;
} }
} }
if (isHeader) { if (isHeader) {
startLine = i + 1; startLine = i + 1;
continue; continue;
} }
break; break;
} }
final List<List<String>> columnData = new ArrayList<>(); final List<List<String>> columnData = new ArrayList<>();
for (int i = 0; i < this.headings.size(); i++) { for (int i = 0; i < this.headings.size(); i++) {
columnData.add(new ArrayList<>()); columnData.add(new ArrayList<>());
} }
int rowCount = 0; int rowCount = 0;
for (int i = startLine; i < lines.length; i++) { for (int i = startLine; i < lines.length; i++) {
final String line = lines[i].trim(); final String line = lines[i].trim();
if (line.isEmpty()) { if (line.isEmpty()) {
continue; continue;
} }
if (!line.contains(this.fieldSeparator)) { if (!line.contains(this.fieldSeparator)) {
continue; continue;
} }
try { try {
final String[] parts = line.split(this.fieldSeparatorQuoted, -1); final String[] parts = line.split(this.fieldSeparatorQuoted, -1);
if (parts.length != this.headings.size()) { if (parts.length != this.headings.size()) {
LOGGER.debug("Column count mismatch: expected {}, got {} for line '{}'", LOGGER.debug("Column count mismatch: expected {}, got {} for line '{}'",
this.headings.size(), parts.length, line); this.headings.size(), parts.length, line);
continue; continue;
} }
for (int col = 0; col < parts.length; col++) { for (int col = 0; col < parts.length; col++) {
final String value = this.stripQuotes(parts[col].trim()); final String value = this.stripQuotes(parts[col].trim());
columnData.get(col).add(value); columnData.get(col).add(value);
} }
rowCount++; rowCount++;
} }
catch (final Exception ex) { catch (final Exception ex) {
LOGGER.debug("Failed to parse line '{}': {}", line, ex.getMessage()); LOGGER.debug("Failed to parse line '{}': {}", line, ex.getMessage());
} }
} }
if (rowCount == 0) { if (rowCount == 0) {
return false; return false;
} }
for (int i = 0; i < this.headings.size(); i++) { for (int i = 0; i < this.headings.size(); i++) {
final String key = this.keyPrefix + this.headings.get(i); final String key = this.keyPrefix + this.headings.get(i);
context.setStringValues(key, columnData.get(i)); context.setStringValues(key, columnData.get(i));
LOGGER.debug("Stored {} values in context key '{}'", columnData.get(i).size(), key); LOGGER.debug("Stored {} values in context key '{}'", columnData.get(i).size(), key);
} }
LOGGER.debug("Parsed {} CSV rows into {} columns", rowCount, this.headings.size()); LOGGER.debug("Parsed {} CSV rows into {} columns", rowCount, this.headings.size());
return true; return true;
} }
private void addCsvResponseErrorPrompt(final AgentContext context) { private void addCsvResponseErrorPrompt(final AgentContext context) {
final var turn = context.getConversation().getConversation().getLast(); final var turn = context.getConversation().getConversation().getLast();
context.getConversation().replaceTurn(new Conversation.Turn( context.getConversation().replaceTurn(new Conversation.Turn(
turn.type(), turn.type(),
turn.text() + this.errorPromptSuffix)); turn.text() + this.errorPromptSuffix));
} }
private String stripQuotes(final String value) { private String stripQuotes(final String value) {
if (value == null || value.length() < 2) { if (value == null || value.length() < 2) {
return value; return value;
} }
if ((value.startsWith("\"") if ((value.startsWith("\"")
&& value.endsWith("\"")) && value.endsWith("\""))
|| (value.startsWith("'") || (value.startsWith("'")
&& value.endsWith("'"))) { && value.endsWith("'"))) {
return value.substring(1, value.length() - 1); return value.substring(1, value.length() - 1);
} }
return value; return value;
} }
} }

View File

@@ -18,259 +18,259 @@ import de.extio.lmlib.client.Conversation;
public class CsvUnmarshallingAgentResponseHandler implements AgentResponseHandler { public class CsvUnmarshallingAgentResponseHandler implements AgentResponseHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CsvUnmarshallingAgentResponseHandler.class); private static final Logger LOGGER = LoggerFactory.getLogger(CsvUnmarshallingAgentResponseHandler.class);
private final Class<?> targetClass; private final Class<?> targetClass;
private final String contextKey; private final String contextKey;
private final String fieldSeparator; private final String fieldSeparator;
private final String listSeparator; private final String listSeparator;
private final Field[] fields; private final Field[] fields;
private final List<String> fieldNames; private final List<String> fieldNames;
private final Constructor<?> constructor; private final Constructor<?> constructor;
private final String fieldSeparatorQuoted; private final String fieldSeparatorQuoted;
private final String listSeparatorQuoted; private final String listSeparatorQuoted;
private final String errorPromptSuffix; private final String errorPromptSuffix;
public CsvUnmarshallingAgentResponseHandler(final Class<?> targetClass, final String contextKey) { public CsvUnmarshallingAgentResponseHandler(final Class<?> targetClass, final String contextKey) {
this(targetClass, contextKey, ";", ","); this(targetClass, contextKey, ";", ",");
} }
public CsvUnmarshallingAgentResponseHandler(final Class<?> targetClass, final String contextKey, public CsvUnmarshallingAgentResponseHandler(final Class<?> targetClass, final String contextKey,
final String fieldSeparator, final String listSeparator) { final String fieldSeparator, final String listSeparator) {
this.targetClass = targetClass; this.targetClass = targetClass;
this.contextKey = contextKey; this.contextKey = contextKey;
this.fieldSeparator = fieldSeparator; this.fieldSeparator = fieldSeparator;
this.listSeparator = listSeparator; this.listSeparator = listSeparator;
// Pre-compute static data // Pre-compute static data
this.fields = targetClass.getDeclaredFields(); this.fields = targetClass.getDeclaredFields();
this.fieldNames = Arrays.stream(this.fields) this.fieldNames = Arrays.stream(this.fields)
.map(Field::getName) .map(Field::getName)
.toList(); .toList();
this.constructor = this.findRecordConstructor(); this.constructor = this.findRecordConstructor();
if (this.constructor == null) { if (this.constructor == null) {
throw new IllegalStateException("No suitable constructor found for " + targetClass.getName()); throw new IllegalStateException("No suitable constructor found for " + targetClass.getName());
} }
this.fieldSeparatorQuoted = Pattern.quote(fieldSeparator); this.fieldSeparatorQuoted = Pattern.quote(fieldSeparator);
this.listSeparatorQuoted = (listSeparator != null && !listSeparator.isEmpty()) this.listSeparatorQuoted = (listSeparator != null && !listSeparator.isEmpty())
? Pattern.quote(listSeparator) ? Pattern.quote(listSeparator)
: null; : null;
// Pre-compute error message suffix // Pre-compute error message suffix
final String listInfo = (listSeparator != null && !listSeparator.isEmpty()) final String listInfo = (listSeparator != null && !listSeparator.isEmpty())
? " and '" + listSeparator + "' for list items" ? " and '" + listSeparator + "' for list items"
: ""; : "";
this.errorPromptSuffix = "\n\nThe previous response could not be fully processed or validated. " + this.errorPromptSuffix = "\n\nThe previous response could not be fully processed or validated. " +
"Please format the response as CSV with '" + fieldSeparator + "' as field separator" + "Please format the response as CSV with '" + fieldSeparator + "' as field separator" +
listInfo + ". One object per line."; listInfo + ". One object per line.";
} }
private Constructor<?> findRecordConstructor() { private Constructor<?> findRecordConstructor() {
final Field[] targetFields = this.targetClass.getDeclaredFields(); final Field[] targetFields = this.targetClass.getDeclaredFields();
for (final Constructor<?> ctor : this.targetClass.getDeclaredConstructors()) { for (final Constructor<?> ctor : this.targetClass.getDeclaredConstructors()) {
if (ctor.getParameterCount() == targetFields.length) { if (ctor.getParameterCount() == targetFields.length) {
return ctor; return ctor;
} }
} }
return null; return null;
} }
@Override @Override
public boolean handle(final Completion completion, final AgentContext context) { public boolean handle(final Completion completion, final AgentContext context) {
final String resp = completion.response(); final String resp = completion.response();
if (resp == null || resp.isBlank()) { if (resp == null || resp.isBlank()) {
LOGGER.warn("Empty completion response for CSV parsing"); LOGGER.warn("Empty completion response for CSV parsing");
return false; return false;
} }
try { try {
final List<Object> parsedObjects = this.parseCsv(resp); final List<Object> parsedObjects = this.parseCsv(resp);
if (parsedObjects.isEmpty()) { if (parsedObjects.isEmpty()) {
LOGGER.warn("No valid CSV data parsed from response"); LOGGER.warn("No valid CSV data parsed from response");
this.addCsvResponseErrorPrompt(context); this.addCsvResponseErrorPrompt(context);
return false; return false;
} }
context.setValues(this.contextKey, parsedObjects); context.setValues(this.contextKey, parsedObjects);
LOGGER.debug("Parsed {} CSV objects", parsedObjects.size()); LOGGER.debug("Parsed {} CSV objects", parsedObjects.size());
return true; return true;
} }
catch (final Exception ex) { catch (final Exception ex) {
LOGGER.warn("Failed to parse CSV response", ex); LOGGER.warn("Failed to parse CSV response", ex);
this.addCsvResponseErrorPrompt(context); this.addCsvResponseErrorPrompt(context);
return false; return false;
} }
} }
private List<Object> parseCsv(final String csvText) throws Exception { private List<Object> parseCsv(final String csvText) throws Exception {
final List<Object> result = new ArrayList<>(); final List<Object> result = new ArrayList<>();
final String[] lines = csvText.split("\n"); final String[] lines = csvText.split("\n");
int startLine = 0; int startLine = 0;
for (int i = 0; i < lines.length; i++) { for (int i = 0; i < lines.length; i++) {
final String line = lines[i].trim(); final String line = lines[i].trim();
if (line.isEmpty()) { if (line.isEmpty()) {
continue; continue;
} }
if (!line.contains(this.fieldSeparator)) { if (!line.contains(this.fieldSeparator)) {
startLine = i + 1; startLine = i + 1;
continue; continue;
} }
final String lowerLine = line.toLowerCase(); final String lowerLine = line.toLowerCase();
boolean isHeader = false; boolean isHeader = false;
for (final String fieldName : this.fieldNames) { for (final String fieldName : this.fieldNames) {
if (lowerLine.contains(fieldName.toLowerCase())) { if (lowerLine.contains(fieldName.toLowerCase())) {
isHeader = true; isHeader = true;
break; break;
} }
} }
if (isHeader) { if (isHeader) {
startLine = i + 1; startLine = i + 1;
continue; continue;
} }
break; break;
} }
for (int i = startLine; i < lines.length; i++) { for (int i = startLine; i < lines.length; i++) {
final String line = lines[i].trim(); final String line = lines[i].trim();
if (line.isEmpty()) { if (line.isEmpty()) {
continue; continue;
} }
if (!line.contains(this.fieldSeparator)) { if (!line.contains(this.fieldSeparator)) {
continue; continue;
} }
try { try {
final Object obj = this.parseLine(line); final Object obj = this.parseLine(line);
if (obj != null) { if (obj != null) {
result.add(obj); result.add(obj);
LOGGER.debug("Parsed CSV object: {}", obj); LOGGER.debug("Parsed CSV object: {}", obj);
} }
} }
catch (final Exception ex) { catch (final Exception ex) {
LOGGER.debug("Failed to parse line '{}': {}", line, ex.getMessage()); LOGGER.debug("Failed to parse line '{}': {}", line, ex.getMessage());
} }
} }
return result; return result;
} }
private Object parseLine(final String line) throws Exception { private Object parseLine(final String line) throws Exception {
final String[] parts = line.split(this.fieldSeparatorQuoted, -1); final String[] parts = line.split(this.fieldSeparatorQuoted, -1);
if (parts.length != this.fields.length) { if (parts.length != this.fields.length) {
LOGGER.debug("Field count mismatch: expected {}, got {} for line '{}'", LOGGER.debug("Field count mismatch: expected {}, got {} for line '{}'",
this.fields.length, parts.length, line); this.fields.length, parts.length, line);
return null; return null;
} }
final Object[] args = new Object[this.fields.length]; final Object[] args = new Object[this.fields.length];
for (int i = 0; i < this.fields.length; i++) { for (int i = 0; i < this.fields.length; i++) {
final String value = parts[i].trim(); final String value = parts[i].trim();
args[i] = this.parseValue(value, this.fields[i]); args[i] = this.parseValue(value, this.fields[i]);
} }
return this.constructor.newInstance(args); return this.constructor.newInstance(args);
} }
private Object parseValue(final String value, final Field field) { private Object parseValue(final String value, final Field field) {
final String cleanValue = this.stripQuotes(value); final String cleanValue = this.stripQuotes(value);
final Class<?> fieldType = field.getType(); final Class<?> fieldType = field.getType();
if (cleanValue.isEmpty() || cleanValue.equalsIgnoreCase("null")) { if (cleanValue.isEmpty() || cleanValue.equalsIgnoreCase("null")) {
if (fieldType == List.class) { if (fieldType == List.class) {
return new ArrayList<>(); return new ArrayList<>();
} }
return null; return null;
} }
if (fieldType == String.class) { if (fieldType == String.class) {
return cleanValue; return cleanValue;
} }
if (fieldType == Integer.class || fieldType == int.class) { if (fieldType == Integer.class || fieldType == int.class) {
return Integer.parseInt(cleanValue); return Integer.parseInt(cleanValue);
} }
if (fieldType == Long.class || fieldType == long.class) { if (fieldType == Long.class || fieldType == long.class) {
return Long.parseLong(cleanValue); return Long.parseLong(cleanValue);
} }
if (fieldType == Double.class || fieldType == double.class) { if (fieldType == Double.class || fieldType == double.class) {
return Double.parseDouble(cleanValue); return Double.parseDouble(cleanValue);
} }
if (fieldType == Boolean.class || fieldType == boolean.class) { if (fieldType == Boolean.class || fieldType == boolean.class) {
return Boolean.parseBoolean(cleanValue); return Boolean.parseBoolean(cleanValue);
} }
if (fieldType == List.class) { if (fieldType == List.class) {
if (this.listSeparatorQuoted == null) { if (this.listSeparatorQuoted == null) {
return new ArrayList<>(); return new ArrayList<>();
} }
final Type genericType = field.getGenericType(); final Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) { if (genericType instanceof ParameterizedType) {
final ParameterizedType paramType = (ParameterizedType) genericType; final ParameterizedType paramType = (ParameterizedType) genericType;
final Type[] typeArgs = paramType.getActualTypeArguments(); final Type[] typeArgs = paramType.getActualTypeArguments();
if (typeArgs.length > 0 && typeArgs[0] == String.class) { if (typeArgs.length > 0 && typeArgs[0] == String.class) {
if (cleanValue.isEmpty()) { if (cleanValue.isEmpty()) {
return new ArrayList<String>(); return new ArrayList<String>();
} }
final String[] items = cleanValue.split(this.listSeparatorQuoted); final String[] items = cleanValue.split(this.listSeparatorQuoted);
final List<String> list = new ArrayList<>(); final List<String> list = new ArrayList<>();
for (final String item : items) { for (final String item : items) {
final String trimmed = item.trim(); final String trimmed = item.trim();
if (!trimmed.isEmpty()) { if (!trimmed.isEmpty()) {
list.add(trimmed); list.add(trimmed);
} }
} }
return list; return list;
} }
} }
} }
throw new IllegalArgumentException("Unsupported field type: " + fieldType.getName()); throw new IllegalArgumentException("Unsupported field type: " + fieldType.getName());
} }
private void addCsvResponseErrorPrompt(final AgentContext context) { private void addCsvResponseErrorPrompt(final AgentContext context) {
final var turn = context.getConversation().getConversation().getLast(); final var turn = context.getConversation().getConversation().getLast();
context.getConversation().replaceTurn(new Conversation.Turn( context.getConversation().replaceTurn(new Conversation.Turn(
turn.type(), turn.type(),
turn.text() + this.errorPromptSuffix)); turn.text() + this.errorPromptSuffix));
} }
private String stripQuotes(final String value) { private String stripQuotes(final String value) {
if (value == null || value.length() < 2) { if (value == null || value.length() < 2) {
return value; return value;
} }
if ((value.startsWith("\"") if ((value.startsWith("\"")
&& value.endsWith("\"")) && value.endsWith("\""))
|| (value.startsWith("'") || (value.startsWith("'")
&& value.endsWith("'"))) { && value.endsWith("'"))) {
return value.substring(1, value.length() - 1); return value.substring(1, value.length() - 1);
} }
return value; return value;
} }
} }

View File

@@ -5,8 +5,8 @@ import de.extio.lmlib.client.Chunk;
public interface StreamedAgentResponseHandler extends AgentResponseHandler { public interface StreamedAgentResponseHandler extends AgentResponseHandler {
void beforeStream(AgentContext context); void beforeStream(AgentContext context);
boolean handleChunk(Chunk chunk, AgentContext context); boolean handleChunk(Chunk chunk, AgentContext context);
} }

View File

@@ -2,19 +2,19 @@ package de.extio.lmlib.client;
public enum CompletionFinishReason { public enum CompletionFinishReason {
/** /**
* Regular completion * Regular completion
*/ */
DONE, DONE,
/** /**
* Completion was stopped because the token limit was reached. You may continue the conversation. * Completion was stopped because the token limit was reached. You may continue the conversation.
*/ */
TOKEN_LIMIT_REACHED, TOKEN_LIMIT_REACHED,
/** /**
* Completion was stopped because the content was filtered. * Completion was stopped because the content was filtered.
*/ */
CONTENT_FILTERED CONTENT_FILTERED
} }

View File

@@ -2,7 +2,7 @@ package de.extio.lmlib.client.oai.completion;
public final class FinishReasons { public final class FinishReasons {
public static final String FINISH_REASON_STOP = "stop"; public static final String FINISH_REASON_STOP = "stop";
public static final String FINISH_REASON_LENGTH = "length"; public static final String FINISH_REASON_LENGTH = "length";

View File

@@ -4,14 +4,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class StreamOptions { public class StreamOptions {
protected boolean includeUsage; protected boolean includeUsage;
@JsonProperty("include_usage") @JsonProperty("include_usage")
public boolean isIncludeUsage() { public boolean isIncludeUsage() {
return includeUsage; return includeUsage;
} }
public void setIncludeUsage(final boolean includeUsage) { public void setIncludeUsage(final boolean includeUsage) {
this.includeUsage = includeUsage; this.includeUsage = includeUsage;
} }
} }

View File

@@ -4,108 +4,108 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public final class Timings { public final class Timings {
private int cacheN; private int cacheN;
private int predictedN; private int predictedN;
private double predictedMs; private double predictedMs;
private double predictedPerTokenMs; private double predictedPerTokenMs;
private double predictedPerSecond; private double predictedPerSecond;
private int promptN; private int promptN;
private double promptMs; private double promptMs;
private double promptPerTokenMs; private double promptPerTokenMs;
private double promptPerSecond; private double promptPerSecond;
@JsonProperty("cache_n") @JsonProperty("cache_n")
public int getCacheN() { public int getCacheN() {
return this.cacheN; return this.cacheN;
} }
public void setCacheN(final int cacheN) { public void setCacheN(final int cacheN) {
this.cacheN = cacheN; this.cacheN = cacheN;
} }
@JsonProperty("predicted_n") @JsonProperty("predicted_n")
public int getPredictedN() { public int getPredictedN() {
return this.predictedN; return this.predictedN;
} }
public void setPredictedN(final int predictedN) { public void setPredictedN(final int predictedN) {
this.predictedN = predictedN; this.predictedN = predictedN;
} }
@JsonProperty("predicted_ms") @JsonProperty("predicted_ms")
public double getPredictedMs() { public double getPredictedMs() {
return this.predictedMs; return this.predictedMs;
} }
public void setPredictedMs(final double predictedMs) { public void setPredictedMs(final double predictedMs) {
this.predictedMs = predictedMs; this.predictedMs = predictedMs;
} }
@JsonProperty("predicted_per_token_ms") @JsonProperty("predicted_per_token_ms")
public double getPredictedPerTokenMs() { public double getPredictedPerTokenMs() {
return this.predictedPerTokenMs; return this.predictedPerTokenMs;
} }
public void setPredictedPerTokenMs(final double predictedPerTokenMs) { public void setPredictedPerTokenMs(final double predictedPerTokenMs) {
this.predictedPerTokenMs = predictedPerTokenMs; this.predictedPerTokenMs = predictedPerTokenMs;
} }
@JsonProperty("predicted_per_second") @JsonProperty("predicted_per_second")
public double getPredictedPerSecond() { public double getPredictedPerSecond() {
return this.predictedPerSecond; return this.predictedPerSecond;
} }
public void setPredictedPerSecond(final double predictedPerSecond) { public void setPredictedPerSecond(final double predictedPerSecond) {
this.predictedPerSecond = predictedPerSecond; this.predictedPerSecond = predictedPerSecond;
} }
@JsonProperty("prompt_n") @JsonProperty("prompt_n")
public int getPromptN() { public int getPromptN() {
return this.promptN; return this.promptN;
} }
public void setPromptN(final int promptN) { public void setPromptN(final int promptN) {
this.promptN = promptN; this.promptN = promptN;
} }
@JsonProperty("prompt_ms") @JsonProperty("prompt_ms")
public double getPromptMs() { public double getPromptMs() {
return this.promptMs; return this.promptMs;
} }
public void setPromptMs(final double promptMs) { public void setPromptMs(final double promptMs) {
this.promptMs = promptMs; this.promptMs = promptMs;
} }
@JsonProperty("prompt_per_token_ms") @JsonProperty("prompt_per_token_ms")
public double getPromptPerTokenMs() { public double getPromptPerTokenMs() {
return this.promptPerTokenMs; return this.promptPerTokenMs;
} }
public void setPromptPerTokenMs(final double promptPerTokenMs) { public void setPromptPerTokenMs(final double promptPerTokenMs) {
this.promptPerTokenMs = promptPerTokenMs; this.promptPerTokenMs = promptPerTokenMs;
} }
@JsonProperty("prompt_per_second") @JsonProperty("prompt_per_second")
public double getPromptPerSecond() { public double getPromptPerSecond() {
return this.promptPerSecond; return this.promptPerSecond;
} }
public void setPromptPerSecond(final double promptPerSecond) { public void setPromptPerSecond(final double promptPerSecond) {
this.promptPerSecond = promptPerSecond; this.promptPerSecond = promptPerSecond;
} }
@Override @Override
public String toString() { public String toString() {
return "Timings [cacheN=" + this.cacheN + ", predictedN=" + this.predictedN + ", predictedMs=" + this.predictedMs + ", predictedPerTokenMs=" + this.predictedPerTokenMs + ", predictedPerSecond=" + this.predictedPerSecond + ", promptN=" + this.promptN + ", promptMs=" + this.promptMs + ", promptPerTokenMs=" + this.promptPerTokenMs + ", promptPerSecond=" + this.promptPerSecond + "]"; return "Timings [cacheN=" + this.cacheN + ", predictedN=" + this.predictedN + ", predictedMs=" + this.predictedMs + ", predictedPerTokenMs=" + this.predictedPerTokenMs + ", predictedPerSecond=" + this.predictedPerSecond + ", promptN=" + this.promptN + ", promptMs=" + this.promptMs + ", promptPerTokenMs=" + this.promptPerTokenMs + ", promptPerSecond=" + this.promptPerSecond + "]";
} }
} }

View File

@@ -4,120 +4,120 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public final class Usage { public final class Usage {
private int completionTokens; private int completionTokens;
private int promptTokens; private int promptTokens;
private int totalTokens; private int totalTokens;
private InputTokensDetails inputTokensDetails; private InputTokensDetails inputTokensDetails;
private OutputTokensDetails outputTokensDetails; private OutputTokensDetails outputTokensDetails;
@JsonProperty("completion_tokens") @JsonProperty("completion_tokens")
public int getCompletionTokens() { public int getCompletionTokens() {
return this.completionTokens; return this.completionTokens;
} }
public void setCompletionTokens(final int completionTokens) { public void setCompletionTokens(final int completionTokens) {
this.completionTokens = completionTokens; this.completionTokens = completionTokens;
} }
@JsonProperty("output_tokens") @JsonProperty("output_tokens")
public void setOutputTokens(final int outputTokens) { public void setOutputTokens(final int outputTokens) {
this.completionTokens = outputTokens; this.completionTokens = outputTokens;
} }
@JsonProperty("prompt_tokens") @JsonProperty("prompt_tokens")
public int getPromptTokens() { public int getPromptTokens() {
return this.promptTokens; return this.promptTokens;
} }
public void setPromptTokens(final int promptTokens) { public void setPromptTokens(final int promptTokens) {
this.promptTokens = promptTokens; this.promptTokens = promptTokens;
} }
@JsonProperty("input_tokens") @JsonProperty("input_tokens")
public void setInputTokens(final int inputTokens) { public void setInputTokens(final int inputTokens) {
this.promptTokens = inputTokens; this.promptTokens = inputTokens;
} }
@JsonProperty("total_tokens") @JsonProperty("total_tokens")
public int getTotalTokens() { public int getTotalTokens() {
return this.totalTokens; return this.totalTokens;
} }
public void setTotalTokens(final int totalTokens) { public void setTotalTokens(final int totalTokens) {
this.totalTokens = totalTokens; this.totalTokens = totalTokens;
} }
@JsonProperty("input_tokens_details") @JsonProperty("input_tokens_details")
public InputTokensDetails getInputTokensDetails() { public InputTokensDetails getInputTokensDetails() {
return this.inputTokensDetails; return this.inputTokensDetails;
} }
public void setInputTokensDetails(final InputTokensDetails inputTokensDetails) { public void setInputTokensDetails(final InputTokensDetails inputTokensDetails) {
this.inputTokensDetails = inputTokensDetails; this.inputTokensDetails = inputTokensDetails;
} }
@JsonProperty("output_tokens_details") @JsonProperty("output_tokens_details")
public OutputTokensDetails getOutputTokensDetails() { public OutputTokensDetails getOutputTokensDetails() {
return this.outputTokensDetails; return this.outputTokensDetails;
} }
public void setOutputTokensDetails(final OutputTokensDetails outputTokensDetails) { public void setOutputTokensDetails(final OutputTokensDetails outputTokensDetails) {
this.outputTokensDetails = outputTokensDetails; this.outputTokensDetails = outputTokensDetails;
} }
public int getCachedTokens() { public int getCachedTokens() {
return this.inputTokensDetails != null ? this.inputTokensDetails.getCachedTokens() : 0; return this.inputTokensDetails != null ? this.inputTokensDetails.getCachedTokens() : 0;
} }
public int getReasoningTokens() { public int getReasoningTokens() {
return this.outputTokensDetails != null ? this.outputTokensDetails.getReasoningTokens() : 0; return this.outputTokensDetails != null ? this.outputTokensDetails.getReasoningTokens() : 0;
} }
@Override @Override
public String toString() { public String toString() {
return "Usage [completionTokens=" + this.completionTokens + ", promptTokens=" + this.promptTokens + ", totalTokens=" + this.totalTokens + ", inputTokensDetails=" + this.inputTokensDetails + ", outputTokensDetails=" + this.outputTokensDetails + "]"; return "Usage [completionTokens=" + this.completionTokens + ", promptTokens=" + this.promptTokens + ", totalTokens=" + this.totalTokens + ", inputTokensDetails=" + this.inputTokensDetails + ", outputTokensDetails=" + this.outputTokensDetails + "]";
} }
public static final class InputTokensDetails { public static final class InputTokensDetails {
private int cachedTokens; private int cachedTokens;
@JsonProperty("cached_tokens") @JsonProperty("cached_tokens")
public int getCachedTokens() { public int getCachedTokens() {
return this.cachedTokens; return this.cachedTokens;
} }
public void setCachedTokens(final int cachedTokens) { public void setCachedTokens(final int cachedTokens) {
this.cachedTokens = cachedTokens; this.cachedTokens = cachedTokens;
} }
@Override @Override
public String toString() { public String toString() {
return "InputTokensDetails [cachedTokens=" + this.cachedTokens + "]"; return "InputTokensDetails [cachedTokens=" + this.cachedTokens + "]";
} }
} }
public static final class OutputTokensDetails { public static final class OutputTokensDetails {
private int reasoningTokens; private int reasoningTokens;
@JsonProperty("reasoning_tokens") @JsonProperty("reasoning_tokens")
public int getReasoningTokens() { public int getReasoningTokens() {
return this.reasoningTokens; return this.reasoningTokens;
} }
public void setReasoningTokens(final int reasoningTokens) { public void setReasoningTokens(final int reasoningTokens) {
this.reasoningTokens = reasoningTokens; this.reasoningTokens = reasoningTokens;
} }
@Override @Override
public String toString() { public String toString() {
return "OutputTokensDetails [reasoningTokens=" + this.reasoningTokens + "]"; return "OutputTokensDetails [reasoningTokens=" + this.reasoningTokens + "]";
} }
} }
} }

View File

@@ -4,45 +4,45 @@ import com.fasterxml.jackson.annotation.JsonProperty;
final class ChatMessage { final class ChatMessage {
private String role; private String role;
private String content; private String content;
private String reasoningContent; private String reasoningContent;
public ChatMessage() { public ChatMessage() {
} }
public ChatMessage(final String role, final String content, final String reasoningContent) { public ChatMessage(final String role, final String content, final String reasoningContent) {
this.role = role; this.role = role;
this.content = content; this.content = content;
this.reasoningContent = reasoningContent; this.reasoningContent = reasoningContent;
} }
@JsonProperty("role") @JsonProperty("role")
public String getRole() { public String getRole() {
return role; return role;
} }
public void setRole(final String role) { public void setRole(final String role) {
this.role = role; this.role = role;
} }
@JsonProperty("content") @JsonProperty("content")
public String getContent() { public String getContent() {
return content; return content;
} }
public void setContent(final String content) { public void setContent(final String content) {
this.content = content; this.content = content;
} }
@JsonProperty("reasoning_content") @JsonProperty("reasoning_content")
public String getReasoningContent() { public String getReasoningContent() {
return reasoningContent; return reasoningContent;
} }
public void setReasoningContent(final String reasoningContent) { public void setReasoningContent(final String reasoningContent) {
this.reasoningContent = reasoningContent; this.reasoningContent = reasoningContent;
} }
} }

View File

@@ -51,8 +51,8 @@ public class AnswerGrader2 {
else { else {
context.setStringValue("qaOutputFormat", "Provide no preamble. Return the response in JSON format with the following field only: {\"qaRating\": rating}"); context.setStringValue("qaOutputFormat", "Provide no preamble. Return the response in JSON format with the following field only: {\"qaRating\": rating}");
} }
context.getContext().remove("qaRating"); context.getContext().remove("qaRating");
context.getContext().remove("qaExplanation"); context.getContext().remove("qaExplanation");
}, },
context -> { context -> {
if (!context.getContext().containsKey("qaRating")) { if (!context.getContext().containsKey("qaRating")) {

View File

@@ -42,8 +42,8 @@ public class AnswerGraderBinary {
else { else {
context.setStringValue("qaOutputFormat", "Return a JSON object with exactly this key: {\"qaPassed\": boolean}"); context.setStringValue("qaOutputFormat", "Return a JSON object with exactly this key: {\"qaPassed\": boolean}");
} }
context.getContext().remove("qaPassed"); context.getContext().remove("qaPassed");
context.getContext().remove("qaExplanation"); context.getContext().remove("qaExplanation");
}, },
context -> { context -> {
if (!context.getContext().containsKey("qaPassed")) { if (!context.getContext().containsKey("qaPassed")) {
@@ -70,7 +70,7 @@ public class AnswerGraderBinary {
context.getContext().put("qaPassed", List.of(qaPassed)); context.getContext().put("qaPassed", List.of(qaPassed));
if (!qaPassed && (int) context.getContext().get("qaIteration").get(0) < 2) { if (!qaPassed && (int) context.getContext().get("qaIteration").get(0) < 2) {
context.setStringValue("qaFeedback", "Feedback from the previous attempt to consider: " + context.getStringValue("qaExplanation")); context.setStringValue("qaFeedback", "Feedback from the previous attempt to consider: " + context.getStringValue("qaExplanation"));
return new AgentNext(context.getStringValue("qaFeedbackLoopDestination"), Boolean.FALSE); return new AgentNext(context.getStringValue("qaFeedbackLoopDestination"), Boolean.FALSE);
} }

View File

@@ -5,106 +5,106 @@ import org.springframework.stereotype.Component;
@Component @Component
public class GptOssPromptStrategy implements PromptStrategy { public class GptOssPromptStrategy implements PromptStrategy {
private static final String finalMarker = "<|channel|>final<|message|>"; private static final String finalMarker = "<|channel|>final<|message|>";
private static final String startMarker = "<|start|>"; private static final String startMarker = "<|start|>";
private static final String assistantStart = startMarker + "assistant"; private static final String assistantStart = startMarker + "assistant";
private static final String analysisMarker = "<|channel|>analysis<|message|>"; private static final String analysisMarker = "<|channel|>analysis<|message|>";
@Override @Override
public StringBuilder start(final String system, final String user) { public StringBuilder start(final String system, final String user) {
final StringBuilder prompt = new StringBuilder(); final StringBuilder prompt = new StringBuilder();
if (!system.isEmpty()) { if (!system.isEmpty()) {
prompt.append("<|start|>system<|message|>"); prompt.append("<|start|>system<|message|>");
prompt.append(system); prompt.append(system);
prompt.append("<|end|>"); prompt.append("<|end|>");
} }
prompt.append("<|start|>user<|message|>"); prompt.append("<|start|>user<|message|>");
prompt.append(user); prompt.append(user);
prompt.append("<|end|>"); prompt.append("<|end|>");
prompt.append(assistantStart); prompt.append(assistantStart);
return prompt; return prompt;
} }
@Override @Override
public void next(final StringBuilder prompt, final String assistant, final String user) { public void next(final StringBuilder prompt, final String assistant, final String user) {
final String assistantContent = assistant == null ? "" : assistant; final String assistantContent = assistant == null ? "" : assistant;
if (assistantContent.startsWith(assistantStart)) { if (assistantContent.startsWith(assistantStart)) {
final int assistantStartLength = assistantStart.length(); final int assistantStartLength = assistantStart.length();
if (prompt.length() >= assistantStartLength) { if (prompt.length() >= assistantStartLength) {
boolean endsWithAssistantStart = true; boolean endsWithAssistantStart = true;
for (int i = 0; i < assistantStartLength; i++) { for (int i = 0; i < assistantStartLength; i++) {
if (prompt.charAt(prompt.length() - assistantStartLength + i) != assistantStart.charAt(i)) { if (prompt.charAt(prompt.length() - assistantStartLength + i) != assistantStart.charAt(i)) {
endsWithAssistantStart = false; endsWithAssistantStart = false;
break; break;
} }
} }
if (endsWithAssistantStart) { if (endsWithAssistantStart) {
prompt.setLength(prompt.length() - assistantStartLength); prompt.setLength(prompt.length() - assistantStartLength);
} }
} }
} }
if (assistantContent.contains(finalMarker)) { if (assistantContent.contains(finalMarker)) {
prompt.append(assistantContent); prompt.append(assistantContent);
if (!assistantContent.endsWith("<|end|>")) { if (!assistantContent.endsWith("<|end|>")) {
prompt.append("<|end|>"); prompt.append("<|end|>");
} }
} }
else { else {
prompt.append("<|channel|>final<|message|>"); prompt.append("<|channel|>final<|message|>");
prompt.append(assistantContent); prompt.append(assistantContent);
prompt.append("<|end|>"); prompt.append("<|end|>");
} }
prompt.append("<|start|>user<|message|>"); prompt.append("<|start|>user<|message|>");
prompt.append(user == null ? "" : user); prompt.append(user == null ? "" : user);
prompt.append("<|end|>"); prompt.append("<|end|>");
prompt.append(assistantStart); prompt.append(assistantStart);
} }
@Override @Override
public String removeEOT(final String prompt) { public String removeEOT(final String prompt) {
return prompt.strip().replace("<|end|>", "").replace("<|return|>", ""); return prompt.strip().replace("<|end|>", "").replace("<|return|>", "");
} }
@Override @Override
public String getPromptName() { public String getPromptName() {
return "gpt-oss"; return "gpt-oss";
} }
@Override @Override
public String getResponse(final String prompt) { public String getResponse(final String prompt) {
if (prompt == null) { if (prompt == null) {
return null; return null;
} }
final int markerIndex = prompt.indexOf(finalMarker); final int markerIndex = prompt.indexOf(finalMarker);
if (markerIndex == -1) { if (markerIndex == -1) {
return prompt; return prompt;
} }
final int start = markerIndex + finalMarker.length(); final int start = markerIndex + finalMarker.length();
int end = prompt.indexOf(startMarker, start); int end = prompt.indexOf(startMarker, start);
if (end == -1) { if (end == -1) {
end = prompt.length(); end = prompt.length();
} }
return prompt.substring(start, end).strip(); return prompt.substring(start, end).strip();
} }
@Override @Override
public String getReasoning(final String prompt) { public String getReasoning(final String prompt) {
if (prompt == null) { if (prompt == null) {
return null; return null;
} }
final int markerIndex = prompt.indexOf(analysisMarker); final int markerIndex = prompt.indexOf(analysisMarker);
if (markerIndex == -1) { if (markerIndex == -1) {
return null; return null;
} }
final int start = markerIndex + analysisMarker.length(); final int start = markerIndex + analysisMarker.length();
int end = prompt.indexOf(startMarker, start); int end = prompt.indexOf(startMarker, start);
if (end == -1) { if (end == -1) {
end = prompt.length(); end = prompt.length();
} }
return prompt.substring(start, end).strip(); return prompt.substring(start, end).strip();
} }
} }

View File

@@ -5,31 +5,31 @@ import org.springframework.stereotype.Component;
@Component @Component
public class NoPromptStrategy implements PromptStrategy { public class NoPromptStrategy implements PromptStrategy {
@Override @Override
public String getPromptName() { public String getPromptName() {
return "noop"; return "noop";
} }
@Override @Override
public StringBuilder start(final String system, final String user) { public StringBuilder start(final String system, final String user) {
final var sb = new StringBuilder(); final var sb = new StringBuilder();
if (system != null && !system.isBlank()) { if (system != null && !system.isBlank()) {
sb.append(system); sb.append(system);
} }
if (system != null && !system.isBlank() && user != null && !user.isBlank()) { if (system != null && !system.isBlank() && user != null && !user.isBlank()) {
sb.append("\n"); sb.append("\n");
} }
if (user != null && !user.isBlank()) { if (user != null && !user.isBlank()) {
sb.append(user); sb.append(user);
} }
return sb; return sb;
} }
@Override @Override
public void next(final StringBuilder prompt, final String assistant, final String user) { public void next(final StringBuilder prompt, final String assistant, final String user) {
prompt.append(assistant); prompt.append(assistant);
prompt.append("\n"); prompt.append("\n");
prompt.append(user); prompt.append(user);
} }
} }

View File

@@ -16,307 +16,307 @@ import de.extio.lmlib.client.Conversation;
public class CsvAgentResponseHandlerTest { public class CsvAgentResponseHandlerTest {
@Test @Test
public void testSimpleCsvParsing() { public void testSimpleCsvParsing() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "xy_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "xy_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
1;2;3 1;2;3
4;5;6 4;5;6
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<String> aValues = context.getStringValues("xy_a"); final List<String> aValues = context.getStringValues("xy_a");
final List<String> bValues = context.getStringValues("xy_b"); final List<String> bValues = context.getStringValues("xy_b");
final List<String> cValues = context.getStringValues("xy_c"); final List<String> cValues = context.getStringValues("xy_c");
assertNotNull(aValues); assertNotNull(aValues);
assertNotNull(bValues); assertNotNull(bValues);
assertNotNull(cValues); assertNotNull(cValues);
assertEquals(List.of("1", "4"), aValues); assertEquals(List.of("1", "4"), aValues);
assertEquals(List.of("2", "5"), bValues); assertEquals(List.of("2", "5"), bValues);
assertEquals(List.of("3", "6"), cValues); assertEquals(List.of("3", "6"), cValues);
} }
@Test @Test
public void testWithHeader() { public void testWithHeader() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("name", "age", "city"), "person_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("name", "age", "city"), "person_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
name;age;city name;age;city
Alice;30;Berlin Alice;30;Berlin
Bob;25;Munich Bob;25;Munich
Charlie;35;Hamburg Charlie;35;Hamburg
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<String> names = context.getStringValues("person_name"); final List<String> names = context.getStringValues("person_name");
final List<String> ages = context.getStringValues("person_age"); final List<String> ages = context.getStringValues("person_age");
final List<String> cities = context.getStringValues("person_city"); final List<String> cities = context.getStringValues("person_city");
assertEquals(List.of("Alice", "Bob", "Charlie"), names); assertEquals(List.of("Alice", "Bob", "Charlie"), names);
assertEquals(List.of("30", "25", "35"), ages); assertEquals(List.of("30", "25", "35"), ages);
assertEquals(List.of("Berlin", "Munich", "Hamburg"), cities); assertEquals(List.of("Berlin", "Munich", "Hamburg"), cities);
} }
@Test @Test
public void testWithEmptyPrefix() { public void testWithEmptyPrefix() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("x", "y"), ""); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("x", "y"), "");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
10;20 10;20
30;40 30;40
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("10", "30"), context.getStringValues("x")); assertEquals(List.of("10", "30"), context.getStringValues("x"));
assertEquals(List.of("20", "40"), context.getStringValues("y")); assertEquals(List.of("20", "40"), context.getStringValues("y"));
} }
@Test @Test
public void testWithNullPrefix() { public void testWithNullPrefix() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("col1", "col2"), null); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("col1", "col2"), null);
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
a;b a;b
c;d c;d
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("a", "c"), context.getStringValues("col1")); assertEquals(List.of("a", "c"), context.getStringValues("col1"));
assertEquals(List.of("b", "d"), context.getStringValues("col2")); assertEquals(List.of("b", "d"), context.getStringValues("col2"));
} }
@Test @Test
public void testWithExtraTextBeforeData() { public void testWithExtraTextBeforeData() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "test_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
Here is the CSV data: Here is the CSV data:
a;b;c a;b;c
1;2;3 1;2;3
4;5;6 4;5;6
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("1", "4"), context.getStringValues("test_a")); assertEquals(List.of("1", "4"), context.getStringValues("test_a"));
assertEquals(List.of("2", "5"), context.getStringValues("test_b")); assertEquals(List.of("2", "5"), context.getStringValues("test_b"));
assertEquals(List.of("3", "6"), context.getStringValues("test_c")); assertEquals(List.of("3", "6"), context.getStringValues("test_c"));
} }
@Test @Test
public void testWithEmptyLines() { public void testWithEmptyLines() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "data_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "data_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
1;2 1;2
3;4 3;4
5;6 5;6
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("1", "3", "5"), context.getStringValues("data_a")); assertEquals(List.of("1", "3", "5"), context.getStringValues("data_a"));
assertEquals(List.of("2", "4", "6"), context.getStringValues("data_b")); assertEquals(List.of("2", "4", "6"), context.getStringValues("data_b"));
} }
@Test @Test
public void testWithWhitespace() { public void testWithWhitespace() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "ws_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "ws_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
1 ; 2 ; 3 1 ; 2 ; 3
4 ; 5 ; 6 4 ; 5 ; 6
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("1", "4"), context.getStringValues("ws_a")); assertEquals(List.of("1", "4"), context.getStringValues("ws_a"));
assertEquals(List.of("2", "5"), context.getStringValues("ws_b")); assertEquals(List.of("2", "5"), context.getStringValues("ws_b"));
assertEquals(List.of("3", "6"), context.getStringValues("ws_c")); assertEquals(List.of("3", "6"), context.getStringValues("ws_c"));
} }
@Test @Test
public void testWithCustomSeparator() { public void testWithCustomSeparator() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "custom_", ","); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "custom_", ",");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
1,2,3 1,2,3
4,5,6 4,5,6
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("1", "4"), context.getStringValues("custom_a")); assertEquals(List.of("1", "4"), context.getStringValues("custom_a"));
assertEquals(List.of("2", "5"), context.getStringValues("custom_b")); assertEquals(List.of("2", "5"), context.getStringValues("custom_b"));
assertEquals(List.of("3", "6"), context.getStringValues("custom_c")); assertEquals(List.of("3", "6"), context.getStringValues("custom_c"));
} }
@Test @Test
public void testColumnCountMismatch() { public void testColumnCountMismatch() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "mismatch_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b", "c"), "mismatch_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
1;2;3 1;2;3
4;5 4;5
7;8;9 7;8;9
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
// Only rows with correct column count should be included // Only rows with correct column count should be included
assertEquals(List.of("1", "7"), context.getStringValues("mismatch_a")); assertEquals(List.of("1", "7"), context.getStringValues("mismatch_a"));
assertEquals(List.of("2", "8"), context.getStringValues("mismatch_b")); assertEquals(List.of("2", "8"), context.getStringValues("mismatch_b"));
assertEquals(List.of("3", "9"), context.getStringValues("mismatch_c")); assertEquals(List.of("3", "9"), context.getStringValues("mismatch_c"));
} }
@Test @Test
public void testEmptyResponse() { public void testEmptyResponse() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "empty_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "empty_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final Completion completion = new Completion("", null, null, null); final Completion completion = new Completion("", null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertFalse(result); assertFalse(result);
} }
@Test @Test
public void testNullResponse() { public void testNullResponse() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "null_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "null_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final Completion completion = new Completion(null, null, null, null); final Completion completion = new Completion(null, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertFalse(result); assertFalse(result);
} }
@Test @Test
public void testNoValidData() { public void testNoValidData() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "nodata_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("a", "b"), "nodata_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
This is just text This is just text
without any valid CSV data without any valid CSV data
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertFalse(result); assertFalse(result);
} }
@Test @Test
public void testSingleRow() { public void testSingleRow() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("x", "y", "z"), "single_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("x", "y", "z"), "single_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = "100;200;300"; final String csvResponse = "100;200;300";
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
assertEquals(List.of("100"), context.getStringValues("single_x")); assertEquals(List.of("100"), context.getStringValues("single_x"));
assertEquals(List.of("200"), context.getStringValues("single_y")); assertEquals(List.of("200"), context.getStringValues("single_y"));
assertEquals(List.of("300"), context.getStringValues("single_z")); assertEquals(List.of("300"), context.getStringValues("single_z"));
} }
@Test @Test
public void testMultipleHeaders() { public void testMultipleHeaders() {
final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("id", "value"), "multi_"); final CsvAgentResponseHandler handler = new CsvAgentResponseHandler(List.of("id", "value"), "multi_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
id;value id;value
ID;VALUE ID;VALUE
1;100 1;100
2;200 2;200
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
// Should skip both header rows // Should skip both header rows
assertEquals(List.of("1", "2"), context.getStringValues("multi_id")); assertEquals(List.of("1", "2"), context.getStringValues("multi_id"));
assertEquals(List.of("100", "200"), context.getStringValues("multi_value")); assertEquals(List.of("100", "200"), context.getStringValues("multi_value"));
} }
@Test @Test
public void testNullHeadingsList() { public void testNullHeadingsList() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
new CsvAgentResponseHandler(null, "prefix_"); new CsvAgentResponseHandler(null, "prefix_");
}); });
} }
@Test @Test
public void testEmptyHeadingsList() { public void testEmptyHeadingsList() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
new CsvAgentResponseHandler(List.of(), "prefix_"); new CsvAgentResponseHandler(List.of(), "prefix_");
}); });
} }
} }

View File

@@ -14,250 +14,250 @@ import de.extio.lmlib.client.Conversation;
public class CsvUnmarshallingAgentResponseHandlerTest { public class CsvUnmarshallingAgentResponseHandlerTest {
public static record TestObject(String name, List<String> tags, Integer value) { public static record TestObject(String name, List<String> tags, Integer value) {
} }
@Test @Test
public void testSimpleCsvParsing() { public void testSimpleCsvParsing() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
item1;tag1,tag2;42 item1;tag1,tag2;42
item2;tag3;100 item2;tag3;100
item3;;50 item3;;50
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_test_", TestObject.class); final List<TestObject> objects = context.getValues("_test_", TestObject.class);
assertEquals(3, objects.size()); assertEquals(3, objects.size());
assertEquals("item1", objects.get(0).name()); assertEquals("item1", objects.get(0).name());
assertEquals(List.of("tag1", "tag2"), objects.get(0).tags()); assertEquals(List.of("tag1", "tag2"), objects.get(0).tags());
assertEquals(42, objects.get(0).value()); assertEquals(42, objects.get(0).value());
assertEquals("item2", objects.get(1).name()); assertEquals("item2", objects.get(1).name());
assertEquals(List.of("tag3"), objects.get(1).tags()); assertEquals(List.of("tag3"), objects.get(1).tags());
assertEquals(100, objects.get(1).value()); assertEquals(100, objects.get(1).value());
assertEquals("item3", objects.get(2).name()); assertEquals("item3", objects.get(2).name());
assertTrue(objects.get(2).tags().isEmpty()); assertTrue(objects.get(2).tags().isEmpty());
assertEquals(50, objects.get(2).value()); assertEquals(50, objects.get(2).value());
} }
@Test @Test
public void testCsvWithHeader() { public void testCsvWithHeader() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
name;tags;value name;tags;value
item1;tag1,tag2;42 item1;tag1,tag2;42
item2;tag3;100 item2;tag3;100
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_test_", TestObject.class); final List<TestObject> objects = context.getValues("_test_", TestObject.class);
assertEquals(2, objects.size()); assertEquals(2, objects.size());
assertEquals("item1", objects.get(0).name()); assertEquals("item1", objects.get(0).name());
assertEquals("item2", objects.get(1).name()); assertEquals("item2", objects.get(1).name());
} }
@Test @Test
public void testCsvWithPreamble() { public void testCsvWithPreamble() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
Here is the result: Here is the result:
item1;tag1,tag2;42 item1;tag1,tag2;42
item2;tag3;100 item2;tag3;100
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_test_", TestObject.class); final List<TestObject> objects = context.getValues("_test_", TestObject.class);
assertEquals(2, objects.size()); assertEquals(2, objects.size());
} }
@Test @Test
public void testCsvWithHeaderAndPreamble() { public void testCsvWithHeaderAndPreamble() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
Here are the results in CSV format: Here are the results in CSV format:
name;tags;value name;tags;value
item1;tag1,tag2;42 item1;tag1,tag2;42
item2;tag3;100 item2;tag3;100
item3;;50 item3;;50
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_test_", TestObject.class); final List<TestObject> objects = context.getValues("_test_", TestObject.class);
assertEquals(3, objects.size()); assertEquals(3, objects.size());
assertEquals("item1", objects.get(0).name()); assertEquals("item1", objects.get(0).name());
assertEquals(List.of("tag1", "tag2"), objects.get(0).tags()); assertEquals(List.of("tag1", "tag2"), objects.get(0).tags());
assertEquals(42, objects.get(0).value()); assertEquals(42, objects.get(0).value());
} }
@Test @Test
public void testCsvWithEmptyLines() { public void testCsvWithEmptyLines() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
item1;tag1,tag2;42 item1;tag1,tag2;42
item2;tag3;100 item2;tag3;100
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_test_", TestObject.class); final List<TestObject> objects = context.getValues("_test_", TestObject.class);
assertEquals(2, objects.size()); assertEquals(2, objects.size());
} }
@Test @Test
public void testCsvWithNullValues() { public void testCsvWithNullValues() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TestObject.class, "_test_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
item1;tag1,tag2; item1;tag1,tag2;
item2;;100 item2;;100
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_test_", TestObject.class); final List<TestObject> objects = context.getValues("_test_", TestObject.class);
assertEquals(2, objects.size()); assertEquals(2, objects.size());
assertEquals("item1", objects.get(0).name()); assertEquals("item1", objects.get(0).name());
assertNotNull(objects.get(0).tags()); assertNotNull(objects.get(0).tags());
assertEquals(null, objects.get(0).value()); assertEquals(null, objects.get(0).value());
assertEquals("item2", objects.get(1).name()); assertEquals("item2", objects.get(1).name());
assertTrue(objects.get(1).tags().isEmpty()); assertTrue(objects.get(1).tags().isEmpty());
assertEquals(100, objects.get(1).value()); assertEquals(100, objects.get(1).value());
} }
public static record TagObject(String tag, List<String> aliases, Integer importance) { public static record TagObject(String tag, List<String> aliases, Integer importance) {
} }
@Test @Test
public void testTagObjectExample() { public void testTagObjectExample() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TagObject.class, "_tag_"); final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(TagObject.class, "_tag_");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
location_dark_forest;location_forest,location_shadow_woods;25 location_dark_forest;location_forest,location_shadow_woods;25
location_ancient_shrine;location_shrine,location_vine_shrine;65 location_ancient_shrine;location_shrine,location_vine_shrine;65
combat_dragon;combat_wyrm,combat_ancient_dragon;65 combat_dragon;combat_wyrm,combat_ancient_dragon;65
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TagObject> objects = context.getValues("_tag_", TagObject.class); final List<TagObject> objects = context.getValues("_tag_", TagObject.class);
assertEquals(3, objects.size()); assertEquals(3, objects.size());
assertEquals("location_dark_forest", objects.get(0).tag()); assertEquals("location_dark_forest", objects.get(0).tag());
assertEquals(List.of("location_forest", "location_shadow_woods"), objects.get(0).aliases()); assertEquals(List.of("location_forest", "location_shadow_woods"), objects.get(0).aliases());
assertEquals(25, objects.get(0).importance()); assertEquals(25, objects.get(0).importance());
assertEquals("combat_dragon", objects.get(2).tag()); assertEquals("combat_dragon", objects.get(2).tag());
assertEquals(List.of("combat_wyrm", "combat_ancient_dragon"), objects.get(2).aliases()); assertEquals(List.of("combat_wyrm", "combat_ancient_dragon"), objects.get(2).aliases());
assertEquals(65, objects.get(2).importance()); assertEquals(65, objects.get(2).importance());
} }
@Test @Test
public void testCustomSeparators() { public void testCustomSeparators() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler( final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(
TagObject.class, "_custom_", "|", ":"); TagObject.class, "_custom_", "|", ":");
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
location_dark_forest|location_forest:location_shadow_woods|25 location_dark_forest|location_forest:location_shadow_woods|25
location_ancient_shrine|location_shrine:location_vine_shrine|65 location_ancient_shrine|location_shrine:location_vine_shrine|65
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TagObject> objects = context.getValues("_custom_", TagObject.class); final List<TagObject> objects = context.getValues("_custom_", TagObject.class);
assertEquals(2, objects.size()); assertEquals(2, objects.size());
assertEquals("location_dark_forest", objects.get(0).tag()); assertEquals("location_dark_forest", objects.get(0).tag());
assertEquals(List.of("location_forest", "location_shadow_woods"), objects.get(0).aliases()); assertEquals(List.of("location_forest", "location_shadow_woods"), objects.get(0).aliases());
assertEquals(25, objects.get(0).importance()); assertEquals(25, objects.get(0).importance());
} }
@Test @Test
public void testNoListSeparator() { public void testNoListSeparator() {
final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler( final CsvUnmarshallingAgentResponseHandler handler = new CsvUnmarshallingAgentResponseHandler(
TestObject.class, "_nolist_", ";", null); TestObject.class, "_nolist_", ";", null);
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();
context.setConversation(Conversation.create("test")); context.setConversation(Conversation.create("test"));
final String csvResponse = """ final String csvResponse = """
item1;tag1,tag2,tag3;42 item1;tag1,tag2,tag3;42
item2;ignored;100 item2;ignored;100
"""; """;
final Completion completion = new Completion(csvResponse, null, null, null); final Completion completion = new Completion(csvResponse, null, null, null);
final boolean result = handler.handle(completion, context); final boolean result = handler.handle(completion, context);
assertTrue(result); assertTrue(result);
final List<TestObject> objects = context.getValues("_nolist_", TestObject.class); final List<TestObject> objects = context.getValues("_nolist_", TestObject.class);
assertEquals(2, objects.size()); assertEquals(2, objects.size());
assertEquals("item1", objects.get(0).name()); assertEquals("item1", objects.get(0).name());
assertTrue(objects.get(0).tags().isEmpty()); assertTrue(objects.get(0).tags().isEmpty());
assertEquals(42, objects.get(0).value()); assertEquals(42, objects.get(0).value());
assertEquals("item2", objects.get(1).name()); assertEquals("item2", objects.get(1).name());
assertTrue(objects.get(1).tags().isEmpty()); assertTrue(objects.get(1).tags().isEmpty());
assertEquals(100, objects.get(1).value()); assertEquals(100, objects.get(1).value());
} }
} }

View File

@@ -204,7 +204,7 @@ public class JsonAgentResponseHandlerTest {
assertTrue(value.contains("clown")); assertTrue(value.contains("clown"));
} }
@Test @Test
public void testSanitizeUnescapedQuotesInString2() { public void testSanitizeUnescapedQuotesInString2() {
final JsonAgentResponseHandler handler = new JsonAgentResponseHandler(); final JsonAgentResponseHandler handler = new JsonAgentResponseHandler();
final AgentContext context = new AgentContext(); final AgentContext context = new AgentContext();