From: Svjatoslav Agejenko Date: Sun, 18 Jan 2026 21:35:28 +0000 (+0200) Subject: Add timeout support with a hierarchical configuration (skill, model, global) and... X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=b1cb2dbc09fae670333ed3333095db0e93bdefeb;p=alyverkko-cli.git Add timeout support with a hierarchical configuration (skill, model, global) and process termination for exceeded limits --- diff --git a/doc/index.org b/doc/index.org index 795e9e9..982fc89 100644 --- a/doc/index.org +++ b/doc/index.org @@ -711,6 +711,42 @@ multi-document summarization all require sufficient context size. Always verify your model's actual supported context - exceeding it causes unpredictable or significantly degraded model output. +*** Timeout + +/Timeout/ is a parameter that specifies the maximum time (in +milliseconds) that the AI is allowed to run for a task. If this time +is exceeded, the process is terminated, and the response is marked +with "TERMINATED BY TIMEOUT". + +The timeout parameter can be set at three levels: +1. *Skill-specific*: Defined in the skill YAML file. +2. *Model-specific*: Defined in the model configuration. +3. *Global default*: Set in the main configuration file. + +The priority hierarchy is: *skill* > *model* > *global default*. + +For example, to set a 5-minute timeout (300,000 milliseconds) for a specific skill, add: +: timeout_millis: 300000 +in the skill's YAML file. + +In the model configuration: +#+begin_src yaml +models: + - alias: "mistral" + timeout_millis: 600000 + # ... other parameters +#+end_src + +In the main configuration: +#+begin_src yaml +default_timeout_millis: 120000 +#+end_src + +Setting a timeout of 0 means no timeout. + +This feature helps prevent stuck AI processes and ensures predictable +task completion times. + *** Parameter Precedence Hierarchy :PROPERTIES: :ID: 456dd42e-a474-4464-a14e-384c68713537 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 2e80c53..1cd2865 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 @@ -128,5 +128,30 @@ public class Task { else return configuration.getDefaultMinP(); } + /** + * Calculates the effective timeout in milliseconds using the following hierarchy: + *
    + *
  1. Skill-specific timeout (highest priority)
  2. + *
  3. Model-specific timeout
  4. + *
  5. Global default timeout (lowest priority)
  6. + *
+ * + * @return the effective timeout in milliseconds, or null if no timeout is configured. + */ + public Long getEffectiveTimeoutMillis() { + // Skill-specific has the highest priority + if (skill != null && skill.getTimeoutMillis() != null) { + return skill.getTimeoutMillis(); + } + + // Model-specific next + if (model.getTimeoutMillis() != null) { + return model.getTimeoutMillis(); + } + + // Global default as fallback + return configuration.getDefaultTimeoutMillis(); + } + } 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 ad6f68a..32f18b9 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 @@ -5,6 +5,7 @@ import eu.svjatoslav.alyverkko_cli.Utils; import java.io.*; import java.nio.file.Files; import java.util.ArrayList; +import java.util.concurrent.TimeUnit; import static eu.svjatoslav.alyverkko_cli.Main.configuration; import static java.lang.String.join; @@ -63,6 +64,7 @@ public class TaskProcess { return task.systemPrompt.replace("", task.userPrompt); } + /** * Runs the AI query by constructing the prompt, writing it to a temp file, * invoking llama.cpp, collecting output, and performing any final cleanup. @@ -94,8 +96,24 @@ public class TaskProcess { StringBuilder result = new StringBuilder(); Thread outputThread = handleResultThread(process, result); - // Wait for the process to finish - process.waitFor(); + // Get effective timeout value + Long timeoutMillis = task.getEffectiveTimeoutMillis(); + boolean isTimedOut = false; + + if (timeoutMillis != null && timeoutMillis > 0) { + try { + if (!process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS)) { + process.destroyForcibly(); + isTimedOut = true; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + process.destroyForcibly(); + isTimedOut = true; + } + } else { + process.waitFor(); + } // Wait for the output thread to finish reading outputThread.join(); @@ -104,7 +122,12 @@ public class TaskProcess { task.endTimeMillis = System.currentTimeMillis(); // Clean up the AI response: remove partial prompt text, end-of-text marker, etc. - return cleanupAiResponse(result.toString()); + String cleanedResponse = cleanupAiResponse(result.toString()); + if (isTimedOut) { + cleanedResponse += "\nTERMINATED BY TIMEOUT"; + } + + return cleanedResponse; } finally { deleteTemporaryFile(); } diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java index 1e425f9..018109f 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java @@ -97,6 +97,14 @@ public class Configuration { @JsonProperty("skills_directory") private File skillsDirectory; + /** + * The default timeout in milliseconds for AI processing tasks. A value of 0 or null means no timeout. + * This serves as the lowest-priority fallback when no skill-specific or model-specific timeout is set. + */ + @JsonProperty("default_timeout_millis") + private Long defaultTimeoutMillis; + + /** * The list of models defined in this configuration. */ diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Model.java b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Model.java index 26f4406..33eadfd 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Model.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Model.java @@ -67,6 +67,13 @@ public class Model { @JsonProperty("final_answer_indicator") private String finalAnswerIndicator; + /** + * Maximum time in milliseconds allowed for AI processing for this model. If null, no timeout is set for this model. + * This value overrides the global default timeout but is overridden by skill-specific timeouts. + */ + @JsonProperty("timeout_millis") + private Long timeoutMillis; + /** *

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 diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/SkillConfig.java b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/SkillConfig.java index ec242e0..f260938 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/SkillConfig.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/SkillConfig.java @@ -41,4 +41,11 @@ public class SkillConfig { @JsonProperty("model_alias") private String modelAlias; + /** + * Maximum time in milliseconds allowed for AI processing when this skill is used. If null, no timeout is set for this skill. + * This is the highest-priority timeout value in the hierarchy, overriding model-specific and global default timeouts. + */ + @JsonProperty("timeout_millis") + private Long timeoutMillis; + } \ No newline at end of file