@tiens.nguyen/gonext-local-worker 1.0.148 → 1.0.150
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.
- package/gonext-local-worker.mjs +45 -0
- package/gonext_agent_chat.py +16 -7
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -139,6 +139,10 @@ const OCR_TIMEOUT_MS = 180_000;
|
|
|
139
139
|
const OCR_DEBUG = String(process.env.GONEXT_OCR_DEBUG ?? "").trim() === "1";
|
|
140
140
|
const OCR_SKIP_CORRECTION = String(process.env.GONEXT_OCR_SKIP_CORRECTION ?? "").trim() === "1";
|
|
141
141
|
const OCR_CORRECT_TIMEOUT_MS = 120_000;
|
|
142
|
+
// Fire-and-forget warm-up of the agent's Ollama coding model. Generous because a
|
|
143
|
+
// cold load of a big/multimodal model into VRAM can take ~20s+; the request isn't
|
|
144
|
+
// awaited, so a long ceiling just avoids aborting an in-progress load.
|
|
145
|
+
const AGENT_WARMUP_TIMEOUT_MS = 180_000;
|
|
142
146
|
// OCR translation/correction is hardcoded to a managed Ollama service + model.
|
|
143
147
|
// (The user-configurable "OCR translation model" chooser was removed.)
|
|
144
148
|
const OCR_TRANSLATE_OLLAMA_URL = "https://ollama1.gomarsic.cc";
|
|
@@ -1432,9 +1436,47 @@ async function runTranscribeJob(job) {
|
|
|
1432
1436
|
}
|
|
1433
1437
|
}
|
|
1434
1438
|
|
|
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, "");
|
|
1455
|
+
const url = `${root}/api/generate`;
|
|
1456
|
+
fetch(url, {
|
|
1457
|
+
method: "POST",
|
|
1458
|
+
headers: { "Content-Type": "application/json" },
|
|
1459
|
+
body: JSON.stringify({ model: codingModelId, keep_alive: -1 }),
|
|
1460
|
+
signal: AbortSignal.timeout(AGENT_WARMUP_TIMEOUT_MS),
|
|
1461
|
+
})
|
|
1462
|
+
.then((res) => {
|
|
1463
|
+
console.log(
|
|
1464
|
+
`[gonext-worker] agent warm-up ${res.ok ? "ok (model loading/resident)" : `HTTP ${res.status}`} → ${url} model=${codingModelId}`
|
|
1465
|
+
);
|
|
1466
|
+
})
|
|
1467
|
+
.catch((err) => {
|
|
1468
|
+
const reason =
|
|
1469
|
+
err?.name === "TimeoutError" ? "still loading in background" : err?.message || "unreachable";
|
|
1470
|
+
console.log(`[gonext-worker] agent warm-up skipped (${reason}) → ${url}`);
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1435
1474
|
async function runAgentChatJob(job) {
|
|
1436
1475
|
const { jobId, payload } = job;
|
|
1437
1476
|
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 ?? "");
|
|
1438
1480
|
const runRes = await workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
1439
1481
|
method: "PATCH",
|
|
1440
1482
|
body: JSON.stringify({ jobStatus: "running" }),
|
|
@@ -1507,6 +1549,9 @@ async function runAgentChatJob(job) {
|
|
|
1507
1549
|
codingModelId: payload?.codingModelId ?? "",
|
|
1508
1550
|
tools: payload?.tools ?? ["http_request"],
|
|
1509
1551
|
maxSteps: payload?.maxSteps ?? 5,
|
|
1552
|
+
// Max web_search + fetch_url calls the agent may make per task (user-configurable
|
|
1553
|
+
// in web Settings → Agent). Default 10; past it the retrieval tools refuse.
|
|
1554
|
+
researchBudget: payload?.researchBudget ?? 10,
|
|
1510
1555
|
// Tool-invocation mode for the agent: "code" (default, python CodeAgent) or
|
|
1511
1556
|
// "toolcall" (structured JSON tool calls). Passed through only; the API/web can
|
|
1512
1557
|
// set payload.agentToolMode to A/B — default keeps current behavior.
|
package/gonext_agent_chat.py
CHANGED
|
@@ -1133,6 +1133,15 @@ def run_agent_chat(cfg):
|
|
|
1133
1133
|
if max_steps < 1:
|
|
1134
1134
|
max_steps = 8
|
|
1135
1135
|
|
|
1136
|
+
# Max web_search + fetch_url calls per task (user-configurable in web Settings →
|
|
1137
|
+
# Agent). Default 10; past it the retrieval tools refuse and steer to finish.
|
|
1138
|
+
try:
|
|
1139
|
+
research_budget = int(cfg.get("researchBudget") or 10)
|
|
1140
|
+
except (TypeError, ValueError):
|
|
1141
|
+
research_budget = 10
|
|
1142
|
+
if research_budget < 1:
|
|
1143
|
+
research_budget = 10
|
|
1144
|
+
|
|
1136
1145
|
_log(
|
|
1137
1146
|
f"start model={agent_model_id!r} base={agent_base_url!r} "
|
|
1138
1147
|
f"codeModel={coding_model_id!r} codeBase={coding_base_url!r} maxSteps={max_steps}"
|
|
@@ -1440,9 +1449,9 @@ def run_agent_chat(cfg):
|
|
|
1440
1449
|
"clean one often does not exist. After 1-2 searches/fetches, COMPILE what you have and "
|
|
1441
1450
|
"finish: if a PDF was asked for, call create_pdf(text=<your compiled text>, title=...); "
|
|
1442
1451
|
"otherwise call final_answer. NEVER repeat a web_search/fetch_url you already ran.\n"
|
|
1443
|
-
"- RESEARCH BUDGET — you may make at most
|
|
1444
|
-
"(rewording a query still counts). Reserve your remaining steps for
|
|
1445
|
-
"final_answer.\n"
|
|
1452
|
+
f"- RESEARCH BUDGET — you may make at most {research_budget} web_search/fetch_url calls "
|
|
1453
|
+
"TOTAL per task (rewording a query still counts). Reserve your remaining steps for "
|
|
1454
|
+
"create_pdf / final_answer.\n"
|
|
1446
1455
|
"- Do NOT put final_answer outside the code block.\n\n"
|
|
1447
1456
|
)
|
|
1448
1457
|
# Lead with the TASK so the weak model anchors on what's actually being asked —
|
|
@@ -1486,10 +1495,10 @@ def run_agent_chat(cfg):
|
|
|
1486
1495
|
# Hard research budget: at most N retrieval calls (web_search + fetch_url) per run.
|
|
1487
1496
|
# Past it, retrieval tools refuse and steer to create_pdf/final_answer. Keeps steps
|
|
1488
1497
|
# free to actually FINISH — the live failure mode was burning ALL steps on
|
|
1489
|
-
# slightly-different searches and never producing the asked-for PDF.
|
|
1490
|
-
#
|
|
1491
|
-
#
|
|
1492
|
-
_research = {"used": 0, "max":
|
|
1498
|
+
# slightly-different searches and never producing the asked-for PDF. The ceiling is
|
|
1499
|
+
# user-configurable (web Settings → Agent, default 10) and applies to both PDF and
|
|
1500
|
+
# plain tasks.
|
|
1501
|
+
_research = {"used": 0, "max": research_budget}
|
|
1493
1502
|
|
|
1494
1503
|
def _research_spend(tool_name: str) -> str:
|
|
1495
1504
|
"""Return '' if under budget (and spend 1), else a refusal steering to finish."""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.150",
|
|
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",
|