@tiens.nguyen/gonext-local-worker 1.0.153 → 1.0.155

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,6 +143,8 @@ const OCR_CORRECT_TIMEOUT_MS = 120_000;
143
143
  // cold load of a big/multimodal model into VRAM can take ~20s+; the request isn't
144
144
  // awaited, so a long ceiling just avoids aborting an in-progress load.
145
145
  const AGENT_WARMUP_TIMEOUT_MS = 180_000;
146
+ // How often the background keep-warm re-asserts the coding model's keep_alive:-1.
147
+ const AGENT_WARMUP_INTERVAL_MS = 240_000; // 4 min — under Ollama's 5-min default unload
146
148
  // OCR translation/correction is hardcoded to a managed Ollama service + model.
147
149
  // (The user-configurable "OCR translation model" chooser was removed.)
148
150
  const OCR_TRANSLATE_OLLAMA_URL = "https://ollama1.gomarsic.cc";
@@ -1436,33 +1438,37 @@ async function runTranscribeJob(job) {
1436
1438
  }
1437
1439
  }
1438
1440
 
1439
- /**
1440
- * Fire-and-forget pre-load of the agent's Ollama coding model. Ollama evicts idle
1441
- * models (default OLLAMA_KEEP_ALIVE=5m), so the first agent step otherwise blocks
1442
- * ~20s+ while llama-server spins up and pages the weights into VRAM. The agent
1443
- * talks to Ollama over the OpenAI-compat /v1 endpoint, which IGNORES keep_alive
1444
- * so we hit the NATIVE /api/generate here (no prompt = load-only, returns as soon
1445
- * as the model is resident) with keep_alive:-1 to trigger the load AND pin it in
1446
- * memory. Kicked off the moment the job is claimed so the load overlaps python
1447
- * startup + prompt assembly. Best-effort: a non-Ollama coding server just 404s and
1448
- * the failure is swallowed — the job proceeds regardless.
1449
- */
1450
- function warmUpOllamaCodingModel(codingBaseURL, codingModelId) {
1451
- if (!codingBaseURL || !codingModelId) return;
1452
- // codingBaseURL is normalized to …/v1 for OpenAI-compat; strip it to reach the
1453
- // Ollama root where /api/generate lives.
1454
- const root = String(codingBaseURL).replace(/\/+$/, "").replace(/\/v1$/i, "");
1441
+ // —— Ollama coding-model keep-warm ——————————————————————————————————————————————
1442
+ // Ollama evicts idle models (default OLLAMA_KEEP_ALIVE=5m), so a cold agent step
1443
+ // blocks ~20s+ while llama-server pages the weights into VRAM. Instead of warming on
1444
+ // EVERY question (noisy, redundant), we remember the coding model from agent jobs and
1445
+ // re-assert a keep_alive:-1 load on a BACKGROUND INTERVAL so it stays resident between
1446
+ // questions. The agent talks to Ollama over /v1 (which ignores keep_alive), so we hit
1447
+ // the NATIVE /api/generate (no prompt = load-only, returns once the model is resident).
1448
+ // Best-effort throughout: a non-Ollama coding server just 404s and it's swallowed.
1449
+ let _warmCoding = null; // { root, model }
1450
+ let _warmTimer = null;
1451
+ let _warmLoggedOk = false;
1452
+
1453
+ function _warmupPing(quiet) {
1454
+ if (!_warmCoding) return;
1455
+ const { root, model } = _warmCoding;
1455
1456
  const url = `${root}/api/generate`;
1456
1457
  fetch(url, {
1457
1458
  method: "POST",
1458
1459
  headers: { "Content-Type": "application/json" },
1459
- body: JSON.stringify({ model: codingModelId, keep_alive: -1 }),
1460
+ body: JSON.stringify({ model, keep_alive: -1 }),
1460
1461
  signal: AbortSignal.timeout(AGENT_WARMUP_TIMEOUT_MS),
1461
1462
  })
1462
1463
  .then((res) => {
1463
- console.log(
1464
- `[gonext-worker] agent warm-up ${res.ok ? "ok (model loading/resident)" : `HTTP ${res.status}`} → ${url} model=${codingModelId}`
1465
- );
1464
+ if (!res.ok) {
1465
+ console.log(`[gonext-worker] agent warm-up HTTP ${res.status} → ${url} model=${model}`);
1466
+ } else if (!quiet || !_warmLoggedOk) {
1467
+ // Quiet interval pings only log the first success per model; a fresh/changed
1468
+ // model always logs so you can see it warm.
1469
+ console.log(`[gonext-worker] agent warm-up ok (model loading/resident) → ${url} model=${model}`);
1470
+ _warmLoggedOk = true;
1471
+ }
1466
1472
  })
1467
1473
  .catch((err) => {
1468
1474
  const reason =
@@ -1471,12 +1477,37 @@ function warmUpOllamaCodingModel(codingBaseURL, codingModelId) {
1471
1477
  });
1472
1478
  }
1473
1479
 
1480
+ /**
1481
+ * Remember the agent's Ollama coding model (from a claimed agent_chat job) and keep it
1482
+ * resident via a background interval. Warms IMMEDIATELY only when the model is first
1483
+ * seen or CHANGES — not on every question — so repeat questions don't re-warm/re-log.
1484
+ */
1485
+ function trackCodingModelForWarmup(codingBaseURL, codingModelId) {
1486
+ if (!codingBaseURL || !codingModelId) return;
1487
+ // codingBaseURL is normalized to …/v1 for OpenAI-compat; strip it to reach the
1488
+ // Ollama root where /api/generate lives.
1489
+ const root = String(codingBaseURL).replace(/\/+$/, "").replace(/\/v1$/i, "");
1490
+ const changed = !_warmCoding || _warmCoding.root !== root || _warmCoding.model !== codingModelId;
1491
+ _warmCoding = { root, model: codingModelId };
1492
+ if (changed) {
1493
+ _warmLoggedOk = false;
1494
+ _warmupPing(false); // warm the newly-seen/changed model now so the next question isn't cold
1495
+ }
1496
+ if (!_warmTimer) {
1497
+ // 4 min — under Ollama's 5-min default unload, so the model stays resident even
1498
+ // if a proxy strips keep_alive. unref() so this timer never blocks worker exit.
1499
+ _warmTimer = setInterval(() => _warmupPing(true), AGENT_WARMUP_INTERVAL_MS);
1500
+ if (typeof _warmTimer.unref === "function") _warmTimer.unref();
1501
+ }
1502
+ }
1503
+
1474
1504
  async function runAgentChatJob(job) {
1475
1505
  const { jobId, payload } = job;
1476
1506
  const start = Date.now();
1477
- // Kick the Ollama coding model's cold load off immediately (non-blocking) so it
1478
- // overlaps prompt assembly instead of stalling the first step. See helper above.
1479
- warmUpOllamaCodingModel(payload?.codingBaseURL ?? "", payload?.codingModelId ?? "");
1507
+ // Remember this job's Ollama coding model and keep it resident via a background
1508
+ // interval. Only warms now if the model is new/changed repeat questions rely on
1509
+ // the interval, so we don't re-warm (or re-log) on every question.
1510
+ trackCodingModelForWarmup(payload?.codingBaseURL ?? "", payload?.codingModelId ?? "");
1480
1511
  const runRes = await workerFetch(`/api/worker/jobs/${jobId}`, {
1481
1512
  method: "PATCH",
1482
1513
  body: JSON.stringify({ jobStatus: "running" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.153",
3
+ "version": "1.0.155",
4
4
  "description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
5
5
  "type": "module",
6
6
  "license": "MIT",