Remove `Model` class and refactor codebase to use `ConfigurationModel` for model...
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 11 Jan 2026 03:52:54 +0000 (05:52 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 11 Jan 2026 03:52:54 +0000 (05:52 +0200)
src/main/java/eu/svjatoslav/alyverkko_cli/commands/ListModelsCommand.java
src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/Task.java
src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcess.java
src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java
src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ConfigurationModel.java
src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ModelLibrary.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/alyverkko_cli/model/Model.java [deleted file]
src/main/java/eu/svjatoslav/alyverkko_cli/model/ModelLibrary.java [deleted file]
src/main/java/eu/svjatoslav/alyverkko_cli/model/package-info.java [deleted file]

index 2ad1d7c..6ffe1ba 100644 (file)
@@ -1,7 +1,7 @@
 package eu.svjatoslav.alyverkko_cli.commands;
 
 import eu.svjatoslav.alyverkko_cli.Command;
-import eu.svjatoslav.alyverkko_cli.model.ModelLibrary;
+import eu.svjatoslav.alyverkko_cli.configuration.ModelLibrary;
 
 import java.io.IOException;
 
index 6c2a78f..1fed2e4 100644 (file)
@@ -1,7 +1,7 @@
 package eu.svjatoslav.alyverkko_cli.commands.task_processor;
 
+import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationModel;
 import eu.svjatoslav.alyverkko_cli.configuration.SkillConfig;
-import eu.svjatoslav.alyverkko_cli.model.Model;
 
 import static eu.svjatoslav.alyverkko_cli.Main.configuration;
 
@@ -35,7 +35,7 @@ public class Task {
     /**
      * The AI model to be used for processing this query.
      */
-    public Model model;
+    public ConfigurationModel model;
 
     /**
      * The start time of the query (milliseconds since epoch).
@@ -71,11 +71,11 @@ public class Task {
      * Calculate effective temperature based on override hierarchy.
      */
     public Float getEffectiveTemperature() {
-        // 1. Skill specific temperature has priority
+        // 1. Skill-specific temperature has priority
         if (skill != null && skill.getTemperature() != null) return skill.getTemperature();
 
         // 2. If not in skill, check if model specifies it
-        if (model.temperature != null) return model.temperature;
+        if (model.getTemperature() != null) return model.getTemperature();
 
         // 3. Fall back to global default
         return configuration.getDefaultTemperature();
@@ -88,11 +88,11 @@ public class Task {
      * @return the applicable top-p value for this query
      */
     public Float getEffectiveTopP() {
-        // Skill-specific has highest priority
+        // Skill-specific has the highest priority
         if (skill != null && skill.getTopP() != null) return skill.getTopP();
 
         // Model-specific next
-        if (model.topP != null) return model.topP;
+        if (model.getTopP() != null) return model.getTopP();
 
         // Global default as fallback
         return configuration.getDefaultTopP();
@@ -105,11 +105,11 @@ public class Task {
      * @return the applicable repeat penalty for this query
      */
     public Float getEffectiveRepeatPenalty() {
-        // Skill-specific has highest priority
+        // Skill-specific has the highest priority
         if (skill != null && skill.getRepeatPenalty() != null) return skill.getRepeatPenalty();
 
         // Model-specific next
-        if (model.repeatPenalty != null) return model.repeatPenalty;
+        if (model.getRepeatPenalty() != null) return model.getRepeatPenalty();
 
         // Global default as fallback
         return configuration.getDefaultRepeatPenalty();
@@ -118,13 +118,13 @@ public class Task {
 
     public Float getEffectiveTopK() {
         if (skill != null && skill.getTopK() != null) return skill.getTopK();
-        else if (model.topK != null) return model.topK;
+        else if (model.getTopK() != null) return model.getTopK();
         else return configuration.getDefaultTopK();
     }
 
     public Float getEffectiveMinP() {
         if (skill != null && skill.getMinP() != null) return skill.getMinP();
-        else if (model.minP != null) return model.minP;
+        else if (model.getMinP() != null) return model.getMinP();
         else return configuration.getDefaultMinP();
     }
 
index 9ae43a2..061991b 100644 (file)
@@ -1,6 +1,7 @@
 package eu.svjatoslav.alyverkko_cli.commands.task_processor;
 
 import eu.svjatoslav.alyverkko_cli.Utils;
+import eu.svjatoslav.alyverkko_cli.configuration.ModelLibrary;
 
 import java.io.*;
 import java.nio.file.Files;
@@ -25,7 +26,7 @@ import static java.lang.String.join;
  * </ol>
  * 
  * <p>Temperature settings, context size, and thread counts are all derived from the current configuration.
- * The response is formatted to match org-mode conventions while preserving original conversation structure.
+ * The response is formatted to match org-mode conventions while preserving the original conversation structure.
  */
 public class TaskProcess {
 
@@ -38,6 +39,7 @@ public class TaskProcess {
      * The mail query defining system prompt, user prompt, and which model to use.
      */
     private final Task task;
+    private final ModelLibrary modelLibrary;
 
     /**
      * Temporary file used as input to the llama.cpp CLI.
@@ -47,10 +49,12 @@ public class TaskProcess {
     /**
      * Creates a new AI task with a given mail query.
      *
-     * @param task the mail query containing model and prompts.
+     * @param task         the mail query containing model and prompts.
+     * @param modelLibrary model library containing all available models.
      */
-    public TaskProcess(Task task) {
+    public TaskProcess(Task task, ModelLibrary modelLibrary) {
         this.task = task;
+        this.modelLibrary = modelLibrary;
     }
 
     /**
@@ -143,8 +147,8 @@ public class TaskProcess {
     private String cleanupAiResponse(String result) {
 
         // remove text after the end of text marker if it exists
-        if (task.model.endOfTextMarker != null) {
-            int endOfTextMarkerIndex = result.indexOf(task.model.endOfTextMarker);
+        if (task.model.getEndOfTextMarker() != null) {
+            int endOfTextMarkerIndex = result.indexOf(task.model.getEndOfTextMarker());
             if (endOfTextMarkerIndex != -1) {
                 result = result.substring(0, endOfTextMarkerIndex);
             }
@@ -165,7 +169,7 @@ public class TaskProcess {
         ArrayList <String> args = new ArrayList<>();
         args.add("nice -n " + niceValue);
         args.add(executablePath);
-        args.add("--model " + task.model.filesystemPath);
+        args.add("--model " +  modelLibrary.getModelFullFilesystemPath(task.model));
         args.add("--threads " + configuration.getThreadCount());
         args.add("--threads-batch " + configuration.getBatchThreadCount());
 
@@ -196,7 +200,7 @@ public class TaskProcess {
         Float temperature = task.getEffectiveTemperature();
         if (temperature != null) args.add("--temp " + task.getEffectiveTemperature());
 
-        args.add("--ctx-size " + task.model.contextSizeTokens);
+        args.add("--ctx-size " + task.model.getContextSizeTokens());
         args.add("--batch-size 512");
         args.add("--single-turn");
         args.add("-n -1");
@@ -207,7 +211,7 @@ public class TaskProcess {
         // "--cache-type-k q8_0",
         // might save RAM, need to test if precision loss is acceptable
 
-        // might save RAM, need to test if precision loss is acceptable
+        // might save RAM, need to test it if precision loss is acceptable
         // "--cache-type-v q8_0",
 
     }
index 57fd642..4f095d2 100644 (file)
@@ -2,9 +2,9 @@ package eu.svjatoslav.alyverkko_cli.commands.task_processor;
 
 import eu.svjatoslav.alyverkko_cli.*;
 import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationHelper;
+import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationModel;
 import eu.svjatoslav.alyverkko_cli.configuration.SkillConfig;
-import eu.svjatoslav.alyverkko_cli.model.Model;
-import eu.svjatoslav.alyverkko_cli.model.ModelLibrary;
+import eu.svjatoslav.alyverkko_cli.configuration.ModelLibrary;
 import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser;
 import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.FileOption;
 
@@ -70,15 +70,6 @@ public class TaskProcessorCommand implements Command {
      */
     File taskDirectory;
 
-    public TaskProcessorCommand() {
-        Comparator<TaskQueueEntry> comparator = (a, b) -> {
-            int priorityCompare = Integer.compare(b.priority, a.priority);
-            if (priorityCompare != 0) {
-                return priorityCompare;
-            }
-            return a.tiebreaker.compareTo(b.tiebreaker);
-        };
-    }
 
     /**
      * @return the name of this command, i.e., "process".
@@ -222,8 +213,8 @@ public class TaskProcessorCommand implements Command {
         // Append the original user prompt (after the first line)
         resultFileContent.append(task.userPrompt).append("\n");
 
-        // Check for final answer indicator in the model's configuration
-        String finalAnswerIndicator = task.model.finalAnswerIndicator;
+        // Check for the final answer indicator in the model's configuration
+        String finalAnswerIndicator = task.model.getFinalAnswerIndicator();
         if (finalAnswerIndicator != null && !finalAnswerIndicator.isEmpty()) {
             int index = aiResponse.indexOf(finalAnswerIndicator);
             if (index != -1) {
@@ -270,7 +261,7 @@ public class TaskProcessorCommand implements Command {
 
         try {
             Task task = buildTaskFromFile(file);
-            TaskProcess aiTask = new TaskProcess(task);
+            TaskProcess aiTask = new TaskProcess(task, modelLibrary);
             String aiGeneratedResponse = aiTask.runAiQuery();
 
             saveAiResponseToFile(file, task, aiGeneratedResponse);
@@ -289,7 +280,7 @@ public class TaskProcessorCommand implements Command {
      */
     private static String getDoneLine(Task task) {
         return "DONE: skill=" + task.skillName +
-                " model=" + task.model.alias +
+                " model=" + task.model.getAlias() +
                 " duration=" + getDuration(task.startTimeMillis, task.endTimeMillis) + "\n";
     }
 
@@ -346,7 +337,7 @@ public class TaskProcessorCommand implements Command {
         // Set AI model using hierarchy: TOCOMPUTE > skill config > default
         String modelAlias = fileProcessingSettings.getOrDefault("model",
                 skill.getModelAlias() != null ? skill.getModelAlias() : "default");
-        Optional<Model> modelOptional = modelLibrary.findModelByAlias(modelAlias);
+        Optional<ConfigurationModel> modelOptional = modelLibrary.findModelByAlias(modelAlias);
         if (!modelOptional.isPresent()) {
             throw new IllegalArgumentException("Model with alias '" + modelAlias + "' not found.");
         }
index f330839..5accc73 100644 (file)
@@ -1,5 +1,6 @@
 package eu.svjatoslav.alyverkko_cli.configuration;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
@@ -67,4 +68,21 @@ public class ConfigurationModel {
     @JsonProperty("final_answer_indicator")
     private String finalAnswerIndicator;
 
+    /**
+     * <p>Prints the model's metadata to standard output in a consistent format. This includes the model's alias,
+     * filesystem path, and context token capacity. The output format is designed to be both human-readable and
+     * machine-parsable when needed.
+     * <p>Typical output:
+     * <pre>
+     * Model: default
+     *   Path: /path/to/model.gguf
+     *   Context size: 32768
+     * </pre>
+     */
+    public void printModelDetails() {
+        System.out.println("Model: " + alias);
+        System.out.println("  Path: " + filesystemPath);
+        System.out.println("  Context size: " + contextSizeTokens);
+    }
+
 }
diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ModelLibrary.java b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ModelLibrary.java
new file mode 100644 (file)
index 0000000..a676bee
--- /dev/null
@@ -0,0 +1,108 @@
+package eu.svjatoslav.alyverkko_cli.configuration;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A container (library) for multiple AI models, providing
+ * functionality for adding and retrieving models by alias.
+ */
+public class ModelLibrary {
+
+    /**
+     * The list of all successfully loaded models in this library.
+     */
+    private final List<ConfigurationModel> models;
+
+    /**
+     * The default model for this library (e.g., the first successfully
+     * loaded model in the list).
+     */
+    private static ConfigurationModel defaultModel;
+
+    /**
+     * Base directory containing the model files.
+     */
+    private final File modelsBaseDirectory;
+
+    /**
+     * Constructs a library of AI models from the provided list of
+     * {@link ConfigurationModel}s, ignoring those whose paths do not exist.
+     *
+     * @param modelsBaseDirectory the root directory where model files are stored.
+     * @param configModels        a list of model configurations.
+     */
+    public ModelLibrary(File modelsBaseDirectory, List<ConfigurationModel> configModels) {
+        this.modelsBaseDirectory = modelsBaseDirectory;
+        this.models = new ArrayList<>();
+
+        for (ConfigurationModel configModel : configModels) {
+            addModel(configModel);
+        }
+
+        if (models.isEmpty()) {
+            throw new RuntimeException("No models are defined!");
+        }
+
+        defaultModel = models.get(0);
+    }
+
+
+    /**
+     * Adds a model to the library if no model with the same alias
+     * already exists.
+     *
+     * @param model the model to add.
+     * @throws RuntimeException if a model with the same alias already exists.
+     */
+    public void addModel(ConfigurationModel model) {
+        if (findModelByAlias(model.getAlias()).isPresent()) {
+            throw new RuntimeException("Model with alias \"" + model.getAlias() + "\" already exists!");
+        }
+        models.add(model);
+    }
+
+    /**
+     * @return the list of loaded models in this library.
+     */
+    public List<ConfigurationModel> getModels() {
+        return models;
+    }
+
+    /**
+     * Finds a model by its alias in this library.
+     *
+     * @param alias the model alias to look for.
+     * @return an {@link Optional} describing the found model, or empty if none match.
+     */
+    public Optional<ConfigurationModel> findModelByAlias(String alias) {
+        return models.stream()
+                .filter(model -> model.getAlias().equals(alias))
+                .findFirst();
+    }
+
+    /**
+     * @return the default model (first loaded model).
+     */
+    public ConfigurationModel getDefaultModel() {
+        return defaultModel;
+    }
+
+    /**
+     * Prints the details of each model in the library to standard output.
+     */
+    public void printModels() {
+        System.out.println("Available models:\n");
+        for (ConfigurationModel model : models) {
+            model.printModelDetails();
+            System.out.println();
+        }
+    }
+
+    public String getModelFullFilesystemPath(ConfigurationModel model) {
+        return new File(modelsBaseDirectory, model.getFilesystemPath()).getAbsolutePath();
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/model/Model.java b/src/main/java/eu/svjatoslav/alyverkko_cli/model/Model.java
deleted file mode 100644 (file)
index bac0aad..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-package eu.svjatoslav.alyverkko_cli.model;
-
-import java.io.File;
-
-
-/**
- * <p>Represents an AI model stored on the filesystem with metadata about its capabilities and identification.
- * This class serves as a lightweight container for model information, enabling quick lookup and validation.
- * <p>Models are typically discovered through configuration files and stored in the ModelLibrary for easy access.
- * <p>Key fields include:
- * <ul>
- *   <li>filesystemPath - Location of the model file</li>
- *   <li>contextSizeTokens - Maximum token capacity for this model</li>
- *   <li>alias - User-friendly identifier for the model</li>
- *   <li>endOfTextMarker - Optional response completion marker</li>
- * </ul>
- */
-public class Model {
-
-    /**
-     * The path to the model file on the filesystem.
-     */
-    public final File filesystemPath;
-
-    /**
-     * The size of the context (in tokens) that this model is able to handle.
-     */
-    public final int contextSizeTokens;
-
-    /**
-     * A user-friendly alias for the model, e.g. "default" or "mistral".
-     */
-    public final String alias;
-
-    /**
-     * An optional marker indicating end of the AI-generated text (e.g., "###").
-     * If non-null, it can be used to detect where the model has finished answering.
-     */
-    public final String endOfTextMarker;
-
-    /**
-     * Model-specific temperature value (null if not configured).
-     */
-    public Float temperature;
-
-    /**
-     * Model-specific top-p value (null if not configured).
-     */
-    public Float topP;
-
-    /**
-     * Model-specific Top-K parameter controlling token selection (null if not configured).
-     */
-    public Float topK;
-
-    /**
-     * Minimum probability threshold for candidate tokens (null if not configured).
-     */
-    public Float minP;
-
-    /**
-     * Model-specific repeat penalty value (null if not configured).
-     */
-    public Float repeatPenalty;
-
-    /**
-     * Optional string indicating where the final answer starts in the model's response.
-     * If present in the response, the output is split into ASSISTANT (before indicator) and FINAL ANSWER (after indicator) sections.
-     */
-    public String finalAnswerIndicator;
-
-    /**
-     * Constructs a {@link Model} instance with all hyperparameters.
-     *
-     * @param filesystemPath     The path to the model file on the filesystem.
-     * @param contextSizeTokens  Maximum token capacity of this model.
-     * @param modelAlias         User-friendly identifier for the model.
-     * @param endOfTextMarker    Optional response completion marker.
-     * @param temperature        Sampling temperature (higher = more creative)
-     * @param topP               Nucleus sampling threshold (0.0-1.0)
-     * @param repeatPenalty      Penalty for token repetition (>0.0)
-     * @param topK               Token selection cutoff for Top-K sampling (>=1)
-     * @param minP               Minimum relative probability threshold (0.0-1.0)
-     * @param finalAnswerIndicator Optional string indicating where the final answer starts in the response.
-     */
-    public Model(File filesystemPath, int contextSizeTokens,
-                 String modelAlias, String endOfTextMarker,
-                 Float temperature, Float topP, Float repeatPenalty,
-                 Float topK, Float minP, String finalAnswerIndicator) {
-        this.filesystemPath = filesystemPath;
-        this.contextSizeTokens = contextSizeTokens;
-        this.alias = modelAlias;
-        this.endOfTextMarker = endOfTextMarker;
-        this.temperature = temperature;
-        this.topP = topP;
-        this.repeatPenalty = repeatPenalty;
-        this.topK = topK;
-        this.minP = minP;
-        this.finalAnswerIndicator = finalAnswerIndicator;
-    }
-
-    /**
-     * <p>Prints the model's metadata to standard output in a consistent format. This includes the model's alias,
-     * filesystem path, and context token capacity. The output format is designed to be both human-readable and
-     * machine-parsable when needed.
-     * <p>Typical output:
-     * <pre>
-     * Model: default
-     *   Path: /path/to/model.gguf
-     *   Context size: 32768
-     * </pre>
-     */
-    public void printModelDetails() {
-        System.out.println("Model: " + alias);
-        System.out.println("  Path: " + filesystemPath);
-        System.out.println("  Context size: " + contextSizeTokens);
-    }
-}
diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/model/ModelLibrary.java b/src/main/java/eu/svjatoslav/alyverkko_cli/model/ModelLibrary.java
deleted file mode 100644 (file)
index eb1087d..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-package eu.svjatoslav.alyverkko_cli.model;
-
-import eu.svjatoslav.alyverkko_cli.Utils;
-import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationModel;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * A container (library) for multiple AI models, providing
- * functionality for adding and retrieving models by alias.
- */
-public class ModelLibrary {
-
-    /**
-     * The list of all successfully loaded models in this library.
-     */
-    private final List<Model> models;
-
-    /**
-     * The default model for this library (e.g., the first successfully
-     * loaded model in the list).
-     */
-    private static Model defaultModel;
-
-    /**
-     * Base directory containing the model files.
-     */
-    private final File modelsBaseDirectory;
-
-    /**
-     * Constructs a library of AI models from the provided list of
-     * {@link ConfigurationModel}s, ignoring those whose paths do not exist.
-     *
-     * @param modelsBaseDirectory the root directory where model files are stored.
-     * @param configModels        a list of model configurations.
-     */
-    public ModelLibrary(File modelsBaseDirectory, List<ConfigurationModel> configModels) {
-        this.modelsBaseDirectory = modelsBaseDirectory;
-        this.models = new ArrayList<>();
-
-        for (ConfigurationModel configModel : configModels) {
-            addModelFromConfig(configModel);
-        }
-
-        if (models.isEmpty()) {
-            throw new RuntimeException("No models are defined!");
-        }
-
-        defaultModel = models.get(0);
-    }
-
-    /**
-     * Attempts to construct a {@link Model} from configuration, verifying file existence.
-     */
-    private void addModelFromConfig(ConfigurationModel configModel) {
-        File modelFile = new File(modelsBaseDirectory, configModel.getFilesystemPath());
-        if (!modelFile.exists()) {
-            Utils.printRedMessageToConsole("WARN: Model file not found: " + modelFile.getAbsolutePath() + " . Skipping model.");
-            return;
-        }
-
-        addModel(new Model(
-                modelFile,
-                configModel.getContextSizeTokens(),
-                configModel.getAlias(),
-                configModel.getEndOfTextMarker(),
-
-                configModel.getTemperature(),
-                configModel.getTopP(),
-                configModel.getRepeatPenalty(),
-
-                configModel.getTopK(),
-                configModel.getMinP(),
-                configModel.getFinalAnswerIndicator()
-        ));
-
-    }
-
-    /**
-     * Adds a model to the library if no model with the same alias
-     * already exists.
-     *
-     * @param model the model to add.
-     * @throws RuntimeException if a model with the same alias already exists.
-     */
-    public void addModel(Model model) {
-        if (findModelByAlias(model.alias).isPresent()) {
-            throw new RuntimeException("Model with alias \"" + model.alias + "\" already exists!");
-        }
-        models.add(model);
-    }
-
-    /**
-     * @return the list of loaded models in this library.
-     */
-    public List<Model> getModels() {
-        return models;
-    }
-
-    /**
-     * Finds a model by its alias in this library.
-     *
-     * @param alias the model alias to look for.
-     * @return an {@link Optional} describing the found model, or empty if none match.
-     */
-    public Optional<Model> findModelByAlias(String alias) {
-        return models.stream()
-                .filter(model -> model.alias.equals(alias))
-                .findFirst();
-    }
-
-    /**
-     * @return the default model (first loaded model).
-     */
-    public Model getDefaultModel() {
-        return defaultModel;
-    }
-
-    /**
-     * Prints the details of each model in the library to standard output.
-     */
-    public void printModels() {
-        System.out.println("Available models:\n");
-        for (Model model : models) {
-            model.printModelDetails();
-            System.out.println();
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/model/package-info.java b/src/main/java/eu/svjatoslav/alyverkko_cli/model/package-info.java
deleted file mode 100644 (file)
index 5d8b074..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * <p>This package defines the model management system for the Ã„lyverkko CLI.
- * It includes classes for representing AI models and maintaining a library of available models.
- * <p>Key features:
- * <ul>
- *   <li>Model metadata storage and validation</li>
- *   <li>Default model selection and management</li>
- *   <li>File system integration for model discovery</li>
- * </ul>
- */
-
-package eu.svjatoslav.alyverkko_cli.model;
\ No newline at end of file