@tiens.nguyen/gonext-local-worker 1.0.127 → 1.0.129
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 +1 -1
- package/gonext_agent_chat.py +61 -4
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1506,7 +1506,7 @@ async function runAgentChatJob(job) {
|
|
|
1506
1506
|
codingBaseURL: payload?.codingBaseURL ?? "",
|
|
1507
1507
|
codingModelId: payload?.codingModelId ?? "",
|
|
1508
1508
|
tools: payload?.tools ?? ["http_request"],
|
|
1509
|
-
maxSteps: payload?.maxSteps ??
|
|
1509
|
+
maxSteps: payload?.maxSteps ?? 5,
|
|
1510
1510
|
// Tool-invocation mode for the agent: "code" (default, python CodeAgent) or
|
|
1511
1511
|
// "toolcall" (structured JSON tool calls). Passed through only; the API/web can
|
|
1512
1512
|
// set payload.agentToolMode to A/B — default keeps current behavior.
|
package/gonext_agent_chat.py
CHANGED
|
@@ -11,7 +11,7 @@ Reads on stdin:
|
|
|
11
11
|
"codingBaseURL": str, # optional: dedicated coding/reasoning model for the
|
|
12
12
|
"codingModelId": str, # CodeAgent's tool-use loop; empty = reuse agentModelId
|
|
13
13
|
"tools": ["http_request"], # v1: only http_request
|
|
14
|
-
"maxSteps": int # multi-step ReAct budget; default
|
|
14
|
+
"maxSteps": int # multi-step ReAct budget; default 5
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
Emits NDJSON lines on stdout:
|
|
@@ -23,6 +23,7 @@ import contextlib
|
|
|
23
23
|
import json
|
|
24
24
|
import re
|
|
25
25
|
import sys
|
|
26
|
+
import time
|
|
26
27
|
import traceback
|
|
27
28
|
import urllib.request
|
|
28
29
|
import urllib.error
|
|
@@ -65,13 +66,18 @@ def _http_request_impl(method, url, headers=None, body=None, timeout=25):
|
|
|
65
66
|
return f"Error: {e}"
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
def _html_to_text(html_text, limit=
|
|
69
|
+
def _html_to_text(html_text, limit=3000):
|
|
69
70
|
"""Strip HTML to readable plain text (zero-dep). Used by fetch_url so the weak
|
|
70
71
|
model receives prose instead of raw tags it cannot parse."""
|
|
71
72
|
import html as _html
|
|
72
73
|
text = html_text or ""
|
|
73
74
|
# Drop script/style/head/svg noise wholesale (incl. their content).
|
|
74
75
|
text = re.sub(r"(?is)<(script|style|head|noscript|svg|template)\b.*?</\1>", " ", text)
|
|
76
|
+
# Drop page chrome wholesale too — nav menus, headers, footers, sidebars, forms.
|
|
77
|
+
# Without this, a Wikipedia fetch spends ~1KB of the budget on "Jump to content /
|
|
78
|
+
# Main menu / Donate / Create account…" before any article text, which both starves
|
|
79
|
+
# the model of real content and bloats the per-step context (OOM risk on local MLX).
|
|
80
|
+
text = re.sub(r"(?is)<(nav|header|footer|aside|form|button|menu)\b.*?</\1>", " ", text)
|
|
75
81
|
# Turn block-ending tags into newlines so document structure survives stripping.
|
|
76
82
|
text = re.sub(r"(?i)<(br|/p|/div|/li|/tr|/h[1-6]|/section|/article)\s*>", "\n", text)
|
|
77
83
|
# Remove all remaining tags.
|
|
@@ -968,7 +974,7 @@ def run_agent_chat(cfg):
|
|
|
968
974
|
# overridable via cfg.maxSteps. The provide_final_answer override below is now only
|
|
969
975
|
# the exhaustion fallback (loop ends without final_answer).
|
|
970
976
|
try:
|
|
971
|
-
max_steps = int(cfg.get("maxSteps") or
|
|
977
|
+
max_steps = int(cfg.get("maxSteps") or 5)
|
|
972
978
|
except (TypeError, ValueError):
|
|
973
979
|
max_steps = 8
|
|
974
980
|
if max_steps < 1:
|
|
@@ -1270,6 +1276,10 @@ def run_agent_chat(cfg):
|
|
|
1270
1276
|
"- If a response starts with 'HTTP 2' it SUCCEEDED — answer with it next step.\n"
|
|
1271
1277
|
"- NEVER pass an 'Error:' / HTTP 4xx / 5xx result to final_answer. On an error, try a "
|
|
1272
1278
|
"DIFFERENT approach next step (e.g. web_search); only give up after a few tries.\n"
|
|
1279
|
+
"- CONVERGE — do NOT keep searching for a 'more complete' or 'perfect' source; a single "
|
|
1280
|
+
"clean one often does not exist. After 1-2 searches/fetches, COMPILE what you have and "
|
|
1281
|
+
"finish: if a PDF was asked for, call create_pdf(text=<your compiled text>, title=...); "
|
|
1282
|
+
"otherwise call final_answer. NEVER repeat a web_search/fetch_url you already ran.\n"
|
|
1273
1283
|
"- Do NOT put final_answer outside the code block.\n\n"
|
|
1274
1284
|
)
|
|
1275
1285
|
# Lead with the TASK so the weak model anchors on what's actually being asked —
|
|
@@ -1289,6 +1299,26 @@ def run_agent_chat(cfg):
|
|
|
1289
1299
|
# tool returned (no extra model call) if the loop ends without final_answer.
|
|
1290
1300
|
_last_obs: dict = {"text": ""}
|
|
1291
1301
|
|
|
1302
|
+
# Deterministic loop-breaker. Weak models often repeat the SAME search/fetch over and
|
|
1303
|
+
# over (hoping for a "more complete" result) and never converge, burning the whole step
|
|
1304
|
+
# budget. We remember each (tool, arg) signature; on a repeat we DON'T re-run it —
|
|
1305
|
+
# instead we return the prior result plus a hard nudge to move on (compile / create_pdf
|
|
1306
|
+
# / final_answer). Keyed to retrieval tools where the loop actually happens.
|
|
1307
|
+
_seen_calls: dict = {}
|
|
1308
|
+
|
|
1309
|
+
def _dedup(tool_name: str, arg: str, next_hint: str):
|
|
1310
|
+
"""Return (is_repeat, message). On a repeat, message steers the model forward."""
|
|
1311
|
+
sig = f"{tool_name}::{(arg or '').strip().lower()}"
|
|
1312
|
+
prior = _seen_calls.get(sig)
|
|
1313
|
+
if prior is not None:
|
|
1314
|
+
_log(f"{tool_name} REPEAT suppressed ({arg[:60]!r}) → nudging model forward")
|
|
1315
|
+
return True, (
|
|
1316
|
+
f"You already ran {tool_name}({arg!r}) — its result is unchanged and shown "
|
|
1317
|
+
f"above. Do NOT call it again. {next_hint}"
|
|
1318
|
+
)
|
|
1319
|
+
_seen_calls[sig] = True
|
|
1320
|
+
return False, ""
|
|
1321
|
+
|
|
1292
1322
|
@tool
|
|
1293
1323
|
def http_request(method: str, url: str, headers: str = "", body: str = "",
|
|
1294
1324
|
username: str = "", password: str = "") -> str:
|
|
@@ -1381,6 +1411,13 @@ def run_agent_chat(cfg):
|
|
|
1381
1411
|
Args:
|
|
1382
1412
|
query: What to look up, e.g. 'capital of France' or 'productivity day-to-day method'.
|
|
1383
1413
|
"""
|
|
1414
|
+
dup, nudge = _dedup("web_search", query,
|
|
1415
|
+
"Use the facts you have ALREADY gathered: if the task asks for a "
|
|
1416
|
+
"PDF, call create_pdf(text=..., title=...) now; otherwise call "
|
|
1417
|
+
"final_answer with what you found. Do not keep searching for a "
|
|
1418
|
+
"'complete' source — one may not exist.")
|
|
1419
|
+
if dup:
|
|
1420
|
+
return nudge
|
|
1384
1421
|
_emit({"type": "step", "text": f"Searching the web → {query[:80]}"})
|
|
1385
1422
|
result = _web_search_impl(query)
|
|
1386
1423
|
_log(f"web_search {query[:60]!r} → {result[:80]}")
|
|
@@ -1398,6 +1435,12 @@ def run_agent_chat(cfg):
|
|
|
1398
1435
|
Args:
|
|
1399
1436
|
url: The full URL of the page to read, e.g. 'https://example.com/article'.
|
|
1400
1437
|
"""
|
|
1438
|
+
dup, nudge = _dedup("fetch_url", url,
|
|
1439
|
+
"You already have this page's text above. Use it: call "
|
|
1440
|
+
"create_pdf(text=..., title=...) if a PDF was requested, else "
|
|
1441
|
+
"final_answer with what you found.")
|
|
1442
|
+
if dup:
|
|
1443
|
+
return nudge
|
|
1401
1444
|
_emit({"type": "step", "text": f"Reading page → {url[:80]}"})
|
|
1402
1445
|
status, ctype, raw = _fetch_page_impl(url)
|
|
1403
1446
|
if status is None:
|
|
@@ -1627,7 +1670,21 @@ def run_agent_chat(cfg):
|
|
|
1627
1670
|
# 2. strip any leftover Qwen3 <think>…</think> trace (belt-and-suspenders;
|
|
1628
1671
|
# thinking is disabled via /no_think in _prepare_completion_kwargs, but a
|
|
1629
1672
|
# stray trace must never reach the parser or leak to the user).
|
|
1630
|
-
|
|
1673
|
+
# A transient "Connection error" to the local MLX server (seen mid-loop when the
|
|
1674
|
+
# context grows large) otherwise aborts the whole run — retry a couple of times
|
|
1675
|
+
# with a short backoff before giving up.
|
|
1676
|
+
last_err = None
|
|
1677
|
+
for attempt in range(3):
|
|
1678
|
+
try:
|
|
1679
|
+
msg = super().generate(*args, **kwargs)
|
|
1680
|
+
break
|
|
1681
|
+
except Exception as e: # noqa: BLE001
|
|
1682
|
+
last_err = e
|
|
1683
|
+
if attempt < 2:
|
|
1684
|
+
_log(f"generate transient error (attempt {attempt + 1}/3): {e} — retrying")
|
|
1685
|
+
time.sleep(1.5 * (attempt + 1))
|
|
1686
|
+
else:
|
|
1687
|
+
raise last_err
|
|
1631
1688
|
try:
|
|
1632
1689
|
content = getattr(msg, "content", None)
|
|
1633
1690
|
if content is None:
|
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.129",
|
|
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",
|