From: Svjatoslav Agejenko Date: Sun, 11 Jan 2026 03:52:54 +0000 (+0200) Subject: Remove `Model` class and refactor codebase to use `ConfigurationModel` for model... X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=4fb87e8f22fd53d1ee4131183f25cc837a8b7570;p=alyverkko-cli.git Remove `Model` class and refactor codebase to use `ConfigurationModel` for model representation. --- diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/ListModelsCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/ListModelsCommand.java index 2ad1d7c..6ffe1ba 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/ListModelsCommand.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/ListModelsCommand.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/Task.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/Task.java index 6c2a78f..1fed2e4 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/Task.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/Task.java @@ -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(); } diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcess.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcess.java index 9ae43a2..061991b 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcess.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcess.java @@ -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; * * *

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 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", } diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java index 57fd642..4f095d2 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java @@ -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 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 modelOptional = modelLibrary.findModelByAlias(modelAlias); + Optional modelOptional = modelLibrary.findModelByAlias(modelAlias); if (!modelOptional.isPresent()) { throw new IllegalArgumentException("Model with alias '" + modelAlias + "' not found."); } diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ConfigurationModel.java b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ConfigurationModel.java index f330839..5accc73 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ConfigurationModel.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ConfigurationModel.java @@ -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; + /** + *

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. + *

Typical output: + *

+     * Model: default
+     *   Path: /path/to/model.gguf
+     *   Context size: 32768
+     * 
+ */ + 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 index 0000000..a676bee --- /dev/null +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/ModelLibrary.java @@ -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 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 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 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 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 index bac0aad..0000000 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/model/Model.java +++ /dev/null @@ -1,118 +0,0 @@ -package eu.svjatoslav.alyverkko_cli.model; - -import java.io.File; - - -/** - *

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. - *

Models are typically discovered through configuration files and stored in the ModelLibrary for easy access. - *

Key fields include: - *

    - *
  • filesystemPath - Location of the model file
  • - *
  • contextSizeTokens - Maximum token capacity for this model
  • - *
  • alias - User-friendly identifier for the model
  • - *
  • endOfTextMarker - Optional response completion marker
  • - *
- */ -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; - } - - /** - *

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. - *

Typical output: - *

-     * Model: default
-     *   Path: /path/to/model.gguf
-     *   Context size: 32768
-     * 
- */ - 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 index eb1087d..0000000 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/model/ModelLibrary.java +++ /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 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 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 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 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 index 5d8b074..0000000 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/model/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - *

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. - *

Key features: - *

    - *
  • Model metadata storage and validation
  • - *
  • Default model selection and management
  • - *
  • File system integration for model discovery
  • - *
- */ - -package eu.svjatoslav.alyverkko_cli.model; \ No newline at end of file