summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2026-07-04 17:08:36 +0200
committerMichael Peter Christen <mc@yacy.net>2026-07-04 17:08:36 +0200
commite5d9efe6539f0f2ae46058da21bc7b810f7661b1 (patch)
tree47413589b8b8af688b2997ca7a6e8db0f10a7d88
parent8f2f9c3ce4ef3f3ee56a1a5dc4810ad4dbad96b9 (diff)
fix for timeout
-rw-r--r--source/net/yacy/ai/LLM.java31
-rw-r--r--source/net/yacy/ai/LogReportService.java10
2 files changed, 36 insertions, 5 deletions
diff --git a/source/net/yacy/ai/LLM.java b/source/net/yacy/ai/LLM.java
index e22020f8d..5af3cd276 100644
--- a/source/net/yacy/ai/LLM.java
+++ b/source/net/yacy/ai/LLM.java
@@ -227,6 +227,11 @@ public class LLM {
private static String sendPostRequest(final String urls, final JSONObject data, final String apiKey) throws IOException, URISyntaxException {
final URL url = new URI(urls).toURL();
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ // Batch calls like the log report generation run large models on CPU and may
+ // take many minutes for a single response. Nothing on our side is allowed to
+ // abort such a call: connecting must fail fast, but reading must never time out.
+ conn.setConnectTimeout(10000);
+ conn.setReadTimeout(0);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
if (apiKey != null && !apiKey.isEmpty()) {
@@ -387,11 +392,35 @@ public class LLM {
final JSONObject choice = choices.getJSONObject(0);
final JSONObject message = choice.getJSONObject("message");
final String content = message.optString("content", "");
- return content;
+ // A truncated answer is not an abort on our side but a generation limit;
+ // make the cause visible because the caller only sees a fragment.
+ final String finishReason = choice.optString("finish_reason", "");
+ if ("length".equals(finishReason)) {
+ log.warn("chat response was truncated by the max_tokens limit (" + max_tokens
+ + "), model=" + LogRedaction.redact(model)
+ + ", contentChars=" + content.length()
+ + ". Configure a higher max_tokens for this model if complete outputs are required.");
+ }
+ return stripThinkBlocks(content);
} catch (JSONException | URISyntaxException e) {
throw new IOException(e.getMessage());
}
}
+
+ /**
+ * Remove reasoning blocks from a chat answer. Some backends deliver the chain of
+ * thought of thinking models inline as &lt;think&gt;...&lt;/think&gt; in the content;
+ * a report or answer must only contain the text after the reasoning. An unclosed
+ * think block (e.g. because the response was cut by the token limit) is removed
+ * up to the end of the content.
+ */
+ protected static String stripThinkBlocks(final String content) {
+ if (content == null || content.indexOf("<think>") < 0) return content;
+ final String withoutClosed = content.replaceAll("(?s)<think>.*?</think>", "");
+ final int unclosed = withoutClosed.indexOf("<think>");
+ final String result = unclosed < 0 ? withoutClosed : withoutClosed.substring(0, unclosed);
+ return result.trim();
+ }
public String chat(final String model, final String systemPrompt, final String userPrompt, final int max_tokens) throws IOException {
try {
diff --git a/source/net/yacy/ai/LogReportService.java b/source/net/yacy/ai/LogReportService.java
index 23662984e..3e0792223 100644
--- a/source/net/yacy/ai/LogReportService.java
+++ b/source/net/yacy/ai/LogReportService.java
@@ -236,10 +236,12 @@ public class LogReportService {
log.info("Log report scheduler is disabled by " + CONFIG_ENABLED + ".");
return null;
}
- if (!hasConfiguredLogReportModel()) {
- log.info("Log report scheduler is inactive because no production model is configured for logreport usage.");
- return null;
- }
+ // Note: the scheduler is started even when no logreport model is configured
+ // yet. The model assignment can happen at any time on /LLMSelection_p.html
+ // and must not require a restart; the tick below skips (with a single log
+ // line) as long as the model is missing and picks up work automatically once
+ // it is configured. Do not add an early return here for a missing model -
+ // that would silently disable report generation until the next restart.
final LogReportService service = new LogReportService(sb);
final long initialDelay = Math.max(0L, sb.getConfigLong(CONFIG_INITIAL_DELAY_MINUTES, DEFAULT_INITIAL_DELAY_MINUTES));