@tiens.nguyen/gonext-local-worker 1.0.130 → 1.0.132
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_agent_chat.py +83 -3
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -1099,6 +1099,10 @@ def run_agent_chat(cfg):
|
|
|
1099
1099
|
_log("router: agent (HTTP tool use needed)")
|
|
1100
1100
|
_emit({"type": "step", "text": "Choosing a tool…"})
|
|
1101
1101
|
|
|
1102
|
+
# True when the user wants a PDF but its content must first be researched by the
|
|
1103
|
+
# agent loop (set below); read by the max-steps fallback to force-deliver a PDF.
|
|
1104
|
+
agent_pdf_requested = False
|
|
1105
|
+
|
|
1102
1106
|
# ---- Deterministic PDF fast-path -------------------------------------------------
|
|
1103
1107
|
# A small model cannot reliably re-emit a long, emoji/quote-heavy document as a
|
|
1104
1108
|
# Python string literal for create_pdf(text="…"), so the CodeAgent call fails before
|
|
@@ -1137,6 +1141,9 @@ def run_agent_chat(cfg):
|
|
|
1137
1141
|
and _PDF_RESEARCH_INTENT.search(latest_user_text)):
|
|
1138
1142
|
_log("PDF fast-path SKIPPED: content needs research (no provided body) "
|
|
1139
1143
|
"→ agent loop (web_search → create_pdf)")
|
|
1144
|
+
# Remember the user wants a PDF: if the agent loop exhausts its steps without
|
|
1145
|
+
# calling create_pdf, the max-steps fallback renders the gathered facts anyway.
|
|
1146
|
+
agent_pdf_requested = True
|
|
1140
1147
|
else:
|
|
1141
1148
|
doc_title = _derive_pdf_title(doc_text)
|
|
1142
1149
|
_log(
|
|
@@ -1280,6 +1287,9 @@ def run_agent_chat(cfg):
|
|
|
1280
1287
|
"clean one often does not exist. After 1-2 searches/fetches, COMPILE what you have and "
|
|
1281
1288
|
"finish: if a PDF was asked for, call create_pdf(text=<your compiled text>, title=...); "
|
|
1282
1289
|
"otherwise call final_answer. NEVER repeat a web_search/fetch_url you already ran.\n"
|
|
1290
|
+
"- RESEARCH BUDGET — you may make at most 3 web_search/fetch_url calls TOTAL per task "
|
|
1291
|
+
"(rewording a query still counts). Reserve your remaining steps for create_pdf / "
|
|
1292
|
+
"final_answer.\n"
|
|
1283
1293
|
"- Do NOT put final_answer outside the code block.\n\n"
|
|
1284
1294
|
)
|
|
1285
1295
|
# Lead with the TASK so the weak model anchors on what's actually being asked —
|
|
@@ -1306,15 +1316,54 @@ def run_agent_chat(cfg):
|
|
|
1306
1316
|
# / final_answer). Keyed to retrieval tools where the loop actually happens.
|
|
1307
1317
|
_seen_calls: dict = {}
|
|
1308
1318
|
|
|
1319
|
+
# Search queries get near-duplicated with filler tweaks ("schedule" → "FULL schedule"
|
|
1320
|
+
# → "2026 FIFA full schedule"), dodging exact-match dedup — seen live. Normalize:
|
|
1321
|
+
# drop filler words, sort tokens, so all variants share one signature.
|
|
1322
|
+
_QUERY_FILLER = {
|
|
1323
|
+
"full", "complete", "comprehensive", "detailed", "entire", "all", "latest",
|
|
1324
|
+
"current", "the", "a", "an", "of", "with", "and", "for", "to", "in", "on",
|
|
1325
|
+
"more", "about", "info", "information",
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
def _norm_query(q: str) -> str:
|
|
1329
|
+
tokens = [t for t in re.findall(r"[a-z0-9]+", (q or "").lower())
|
|
1330
|
+
if t not in _QUERY_FILLER]
|
|
1331
|
+
return " ".join(sorted(tokens))
|
|
1332
|
+
|
|
1333
|
+
# Hard research budget: at most N retrieval calls (web_search + fetch_url) per run.
|
|
1334
|
+
# Past it, retrieval tools refuse and steer to create_pdf/final_answer. Keeps ≥2 of
|
|
1335
|
+
# the 5 steps free to actually FINISH — the live failure mode was burning ALL steps
|
|
1336
|
+
# on slightly-different searches and never producing the asked-for PDF.
|
|
1337
|
+
_research = {"used": 0, "max": 3}
|
|
1338
|
+
|
|
1339
|
+
def _research_spend(tool_name: str) -> str:
|
|
1340
|
+
"""Return '' if under budget (and spend 1), else a refusal steering to finish."""
|
|
1341
|
+
if _research["used"] >= _research["max"]:
|
|
1342
|
+
_log(f"{tool_name} BLOCKED: research budget ({_research['max']}) exhausted")
|
|
1343
|
+
return (
|
|
1344
|
+
f"Research budget exhausted ({_research['max']} web_search/fetch_url calls "
|
|
1345
|
+
"used). Do NOT search or fetch again. FINISH NOW with what you have: if the "
|
|
1346
|
+
"task asked for a PDF call create_pdf(text=<compile the gathered facts>, "
|
|
1347
|
+
"title=...), otherwise call final_answer(<your best answer from the "
|
|
1348
|
+
"observations above>)."
|
|
1349
|
+
)
|
|
1350
|
+
_research["used"] += 1
|
|
1351
|
+
return ""
|
|
1352
|
+
|
|
1353
|
+
# Everything useful the retrieval tools returned this run, so an exhausted loop can
|
|
1354
|
+
# still deterministically deliver (e.g. render a partial PDF) instead of dead-ending.
|
|
1355
|
+
_gathered: list = []
|
|
1356
|
+
|
|
1309
1357
|
def _dedup(tool_name: str, arg: str, next_hint: str):
|
|
1310
1358
|
"""Return (is_repeat, message). On a repeat, message steers the model forward."""
|
|
1311
|
-
|
|
1359
|
+
key = _norm_query(arg) if tool_name == "web_search" else (arg or "").strip().lower()
|
|
1360
|
+
sig = f"{tool_name}::{key}"
|
|
1312
1361
|
prior = _seen_calls.get(sig)
|
|
1313
1362
|
if prior is not None:
|
|
1314
1363
|
_log(f"{tool_name} REPEAT suppressed ({arg[:60]!r}) → nudging model forward")
|
|
1315
1364
|
return True, (
|
|
1316
|
-
f"You already ran {tool_name}({arg!r})
|
|
1317
|
-
f"above. Do NOT call it again. {next_hint}"
|
|
1365
|
+
f"You already ran {tool_name}({arg!r}) (or an equivalent query) — the "
|
|
1366
|
+
f"result would be the same as shown above. Do NOT call it again. {next_hint}"
|
|
1318
1367
|
)
|
|
1319
1368
|
_seen_calls[sig] = True
|
|
1320
1369
|
return False, ""
|
|
@@ -1418,10 +1467,15 @@ def run_agent_chat(cfg):
|
|
|
1418
1467
|
"'complete' source — one may not exist.")
|
|
1419
1468
|
if dup:
|
|
1420
1469
|
return nudge
|
|
1470
|
+
blocked = _research_spend("web_search")
|
|
1471
|
+
if blocked:
|
|
1472
|
+
return blocked
|
|
1421
1473
|
_emit({"type": "step", "text": f"Searching the web → {query[:80]}"})
|
|
1422
1474
|
result = _web_search_impl(query)
|
|
1423
1475
|
_log(f"web_search {query[:60]!r} → {result[:80]}")
|
|
1424
1476
|
_last_obs["text"] = result
|
|
1477
|
+
if result and not result.startswith("Error"):
|
|
1478
|
+
_gathered.append(result)
|
|
1425
1479
|
return result
|
|
1426
1480
|
|
|
1427
1481
|
@tool
|
|
@@ -1441,6 +1495,9 @@ def run_agent_chat(cfg):
|
|
|
1441
1495
|
"final_answer with what you found.")
|
|
1442
1496
|
if dup:
|
|
1443
1497
|
return nudge
|
|
1498
|
+
blocked = _research_spend("fetch_url")
|
|
1499
|
+
if blocked:
|
|
1500
|
+
return blocked
|
|
1444
1501
|
_emit({"type": "step", "text": f"Reading page → {url[:80]}"})
|
|
1445
1502
|
status, ctype, raw = _fetch_page_impl(url)
|
|
1446
1503
|
if status is None:
|
|
@@ -1464,6 +1521,7 @@ def run_agent_chat(cfg):
|
|
|
1464
1521
|
out = f"{url}\n{text}"
|
|
1465
1522
|
_log(f"fetch_url {url} → {len(text)} chars")
|
|
1466
1523
|
_last_obs["text"] = out
|
|
1524
|
+
_gathered.append(out)
|
|
1467
1525
|
return out
|
|
1468
1526
|
|
|
1469
1527
|
@tool
|
|
@@ -1773,6 +1831,28 @@ def run_agent_chat(cfg):
|
|
|
1773
1831
|
class _ToolAgent(_AgentBase):
|
|
1774
1832
|
def provide_final_answer(self, task, *args, **kwargs):
|
|
1775
1833
|
from smolagents.models import ChatMessage, MessageRole
|
|
1834
|
+
# PDF-on-exhaustion guarantee: the user asked for a PDF and the loop burned
|
|
1835
|
+
# all steps researching without ever calling create_pdf (seen live: 5 steps
|
|
1836
|
+
# of search/fetch, zero deliverable). Render whatever WAS gathered into a
|
|
1837
|
+
# (possibly partial) PDF instead of returning raw page text.
|
|
1838
|
+
if (agent_pdf_requested and _gathered
|
|
1839
|
+
and not (_last_obs.get("text") or "").startswith("✅")):
|
|
1840
|
+
_log(f"max-steps fallback → forcing create_pdf from {len(_gathered)} "
|
|
1841
|
+
"gathered observation(s)")
|
|
1842
|
+
try:
|
|
1843
|
+
compiled = "\n\n---\n\n".join(_gathered)[:12000]
|
|
1844
|
+
out = create_pdf(
|
|
1845
|
+
text=compiled, title=_derive_pdf_title(latest_user_text)
|
|
1846
|
+
)
|
|
1847
|
+
if isinstance(out, str) and out.startswith("✅"):
|
|
1848
|
+
out += (
|
|
1849
|
+
"\n\n(Note: I hit my research step limit, so this PDF contains "
|
|
1850
|
+
"what I could gather — it may be partial.)"
|
|
1851
|
+
)
|
|
1852
|
+
return ChatMessage(role=MessageRole.ASSISTANT, content=out)
|
|
1853
|
+
_log(f"forced create_pdf did not succeed: {str(out)[:120]}")
|
|
1854
|
+
except Exception as e: # noqa: BLE001
|
|
1855
|
+
_log(f"forced create_pdf error: {type(e).__name__}: {e}")
|
|
1776
1856
|
text = (_last_obs.get("text") or "").strip()
|
|
1777
1857
|
if text:
|
|
1778
1858
|
_log(f"max-steps fallback (last tool obs) → {text[:80]}")
|
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.132",
|
|
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",
|