@tiens.nguyen/gonext-local-worker 1.0.121 → 1.0.123
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 +74 -28
- 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 ?? 8,
|
|
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 # default
|
|
14
|
+
"maxSteps": int # multi-step ReAct budget; default 8
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
Emits NDJSON lines on stdout:
|
|
@@ -570,6 +570,27 @@ def _strip_tool_tags(text: str) -> str:
|
|
|
570
570
|
return "\n".join(out).strip()
|
|
571
571
|
|
|
572
572
|
|
|
573
|
+
_THINK_BLOCK = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _strip_think(text: str) -> str:
|
|
577
|
+
"""Remove Qwen3-style <think>…</think> reasoning traces from a model reply.
|
|
578
|
+
|
|
579
|
+
Reasoning models (e.g. Qwen3) emit their chain-of-thought inside <think> tags
|
|
580
|
+
before the actual answer/code. We keep thinking ON (it improves the output) but
|
|
581
|
+
strip the trace before smolagents parses the step, so the code/final_answer parser
|
|
582
|
+
only sees the real content — and the trace never leaks to the user. Handles a
|
|
583
|
+
dangling </think> too (some chat templates open <think> server-side)."""
|
|
584
|
+
if not text:
|
|
585
|
+
return text
|
|
586
|
+
text = _THINK_BLOCK.sub("", text)
|
|
587
|
+
low = text.lower()
|
|
588
|
+
if "</think>" in low and "<think>" not in low:
|
|
589
|
+
# Template opened the think tag for the model; drop everything up to the close.
|
|
590
|
+
text = text[low.find("</think>") + len("</think>"):]
|
|
591
|
+
return text.strip()
|
|
592
|
+
|
|
593
|
+
|
|
573
594
|
_PDF_INSTALL_HINT = (
|
|
574
595
|
"PDF engine not installed on the worker. On macOS:\n"
|
|
575
596
|
" brew install pango libffi\n"
|
|
@@ -900,11 +921,18 @@ def run_agent_chat(cfg):
|
|
|
900
921
|
else:
|
|
901
922
|
coding_base_url = agent_base_url
|
|
902
923
|
coding_model_id = agent_model_id
|
|
903
|
-
#
|
|
904
|
-
#
|
|
905
|
-
#
|
|
906
|
-
#
|
|
907
|
-
|
|
924
|
+
# Multi-step ReAct loop (thinking agent): the agent may take several
|
|
925
|
+
# Thought → tool → Observation steps and then call final_answer() itself. This
|
|
926
|
+
# replaced the old strict single-shot (max_steps=1) once a stronger coding model
|
|
927
|
+
# (Qwen3-14B class) made real multi-step reasoning reliable. Default budget 8,
|
|
928
|
+
# overridable via cfg.maxSteps. The provide_final_answer override below is now only
|
|
929
|
+
# the exhaustion fallback (loop ends without final_answer).
|
|
930
|
+
try:
|
|
931
|
+
max_steps = int(cfg.get("maxSteps") or 8)
|
|
932
|
+
except (TypeError, ValueError):
|
|
933
|
+
max_steps = 8
|
|
934
|
+
if max_steps < 1:
|
|
935
|
+
max_steps = 8
|
|
908
936
|
|
|
909
937
|
_log(
|
|
910
938
|
f"start model={agent_model_id!r} base={agent_base_url!r} "
|
|
@@ -1136,9 +1164,12 @@ def run_agent_chat(cfg):
|
|
|
1136
1164
|
"(previews first, then sends on confirm).\n"
|
|
1137
1165
|
) if _EMAIL_AVAILABLE else ""
|
|
1138
1166
|
tool_hint = (
|
|
1139
|
-
"
|
|
1140
|
-
"
|
|
1141
|
-
"
|
|
1167
|
+
f"Solve the TASK step by step (up to {max_steps} steps). At EACH step write a "
|
|
1168
|
+
"brief Thought, then ONE code block that calls a SINGLE tool. You will SEE that "
|
|
1169
|
+
"tool's result (Observation) before the next step — use it to decide what to do "
|
|
1170
|
+
"next (e.g. web_search to find a URL, THEN fetch_url to read it). When you have "
|
|
1171
|
+
"enough to answer, call final_answer(<your answer>) in a code block. Prefer "
|
|
1172
|
+
"FEWER steps, and never repeat a tool call that already succeeded.\n\n"
|
|
1142
1173
|
f"You have {_tool_count_word} tools:\n"
|
|
1143
1174
|
" 1. http_request(method, url, headers='', body='', username='', password='') — "
|
|
1144
1175
|
"call a SPECIFIC known API/URL.\n"
|
|
@@ -1198,8 +1229,8 @@ def run_agent_chat(cfg):
|
|
|
1198
1229
|
# Track URLs that have already failed so we don't retry dead endpoints across steps.
|
|
1199
1230
|
_failed_urls: set = set()
|
|
1200
1231
|
|
|
1201
|
-
# Remember the last tool output so the
|
|
1202
|
-
#
|
|
1232
|
+
# Remember the last tool output so the max-steps fallback can report exactly what a
|
|
1233
|
+
# tool returned (no extra model call) if the loop ends without final_answer.
|
|
1203
1234
|
_last_obs: dict = {"text": ""}
|
|
1204
1235
|
|
|
1205
1236
|
@tool
|
|
@@ -1450,7 +1481,7 @@ def run_agent_chat(cfg):
|
|
|
1450
1481
|
|
|
1451
1482
|
# 2) Render the Markdown to PDF bytes locally (pure-Python xhtml2pdf).
|
|
1452
1483
|
# Catch EVERYTHING (xhtml2pdf can raise arbitrary errors on exotic glyphs /
|
|
1453
|
-
# emoji), so the tool always returns a string and never breaks the
|
|
1484
|
+
# emoji), so the tool always returns a string and never breaks the agent loop.
|
|
1454
1485
|
_emit({"type": "step", "text": "Rendering PDF…"})
|
|
1455
1486
|
try:
|
|
1456
1487
|
pdf_bytes = _render_pdf_bytes(markdown_text, doc_title)
|
|
@@ -1526,6 +1557,21 @@ def run_agent_chat(cfg):
|
|
|
1526
1557
|
# and any step memory it accumulates. completion_kwargs["messages"] here is the
|
|
1527
1558
|
# literal messages array sent to /v1/chat/completions.
|
|
1528
1559
|
class _LoggingModel(OpenAIServerModel):
|
|
1560
|
+
def generate(self, *args, **kwargs):
|
|
1561
|
+
# Strip Qwen3 <think>…</think> reasoning traces from each step's reply
|
|
1562
|
+
# BEFORE smolagents parses it for the code block / final_answer. Thinking
|
|
1563
|
+
# stays on (better reasoning), but the trace never confuses the parser or
|
|
1564
|
+
# leaks to the user.
|
|
1565
|
+
msg = super().generate(*args, **kwargs)
|
|
1566
|
+
try:
|
|
1567
|
+
if isinstance(getattr(msg, "content", None), str) and "</think>" in msg.content.lower():
|
|
1568
|
+
cleaned = _strip_think(msg.content)
|
|
1569
|
+
_log(f"stripped <think> trace ({len(msg.content) - len(cleaned)} chars)")
|
|
1570
|
+
msg.content = cleaned
|
|
1571
|
+
except Exception as e: # noqa: BLE001
|
|
1572
|
+
_log(f"think-strip error: {e}")
|
|
1573
|
+
return msg
|
|
1574
|
+
|
|
1529
1575
|
def _prepare_completion_kwargs(self, *args, **kwargs):
|
|
1530
1576
|
ck = super()._prepare_completion_kwargs(*args, **kwargs)
|
|
1531
1577
|
try:
|
|
@@ -1569,21 +1615,22 @@ def run_agent_chat(cfg):
|
|
|
1569
1615
|
else:
|
|
1570
1616
|
_log("agent tool mode: code (python CodeAgent)")
|
|
1571
1617
|
|
|
1572
|
-
#
|
|
1573
|
-
#
|
|
1618
|
+
# Exhaustion fallback: normally the model calls final_answer() itself within the
|
|
1619
|
+
# multi-step loop and that answer is used. Only if it burns through all max_steps
|
|
1620
|
+
# WITHOUT calling final_answer does smolagents call provide_final_answer to
|
|
1574
1621
|
# synthesize one. We override that to return the last tool observation
|
|
1575
|
-
# deterministically
|
|
1576
|
-
# corrupting exact tool output (dates/numbers)
|
|
1577
|
-
class
|
|
1622
|
+
# deterministically (or a plain reply) rather than dead-ending — and without a weak
|
|
1623
|
+
# model corrupting exact tool output (dates/numbers).
|
|
1624
|
+
class _ToolAgent(_AgentBase):
|
|
1578
1625
|
def provide_final_answer(self, task, *args, **kwargs):
|
|
1579
1626
|
from smolagents.models import ChatMessage, MessageRole
|
|
1580
1627
|
text = (_last_obs.get("text") or "").strip()
|
|
1581
1628
|
if text:
|
|
1582
|
-
_log(f"
|
|
1629
|
+
_log(f"max-steps fallback (last tool obs) → {text[:80]}")
|
|
1583
1630
|
else:
|
|
1584
|
-
# No usable tool output (e.g.
|
|
1631
|
+
# No usable tool output (e.g. every step's code failed to parse).
|
|
1585
1632
|
# Don't dead-end on a canned apology — answer from the conversation.
|
|
1586
|
-
_log("
|
|
1633
|
+
_log("max-steps fallback → plain reply over conversation")
|
|
1587
1634
|
try:
|
|
1588
1635
|
text = _plain_reply(
|
|
1589
1636
|
messages, agent_base_url, agent_api_key, agent_model_id
|
|
@@ -1592,8 +1639,8 @@ def run_agent_chat(cfg):
|
|
|
1592
1639
|
_log(f"plain-reply fallback error: {e}")
|
|
1593
1640
|
text = ""
|
|
1594
1641
|
if not text:
|
|
1595
|
-
text = ("I couldn't complete that
|
|
1596
|
-
"give a specific URL/API to call.")
|
|
1642
|
+
text = ("I couldn't complete that within the step budget. Please "
|
|
1643
|
+
"rephrase, or give a specific URL/API to call.")
|
|
1597
1644
|
return ChatMessage(role=MessageRole.ASSISTANT, content=text)
|
|
1598
1645
|
|
|
1599
1646
|
try:
|
|
@@ -1622,14 +1669,13 @@ def run_agent_chat(cfg):
|
|
|
1622
1669
|
agent_kwargs["additional_authorized_imports"] = [
|
|
1623
1670
|
"json", "base64", "urllib", "urllib.request", "urllib.error"
|
|
1624
1671
|
]
|
|
1625
|
-
agent =
|
|
1672
|
+
agent = _ToolAgent(**agent_kwargs)
|
|
1626
1673
|
with contextlib.redirect_stdout(sys.stderr):
|
|
1627
1674
|
result = agent.run(task_with_hint)
|
|
1628
|
-
#
|
|
1629
|
-
# final_answer (or the
|
|
1630
|
-
#
|
|
1631
|
-
# they don't leak to the user.
|
|
1632
|
-
# weak summarizer model used to introduce.
|
|
1675
|
+
# Final formatting — NO extra summarizer model call. In multi-step mode the
|
|
1676
|
+
# agent's own final_answer() (or the max-steps fallback above) already holds the
|
|
1677
|
+
# synthesized answer; we just strip the internal hint tags we appended to tool
|
|
1678
|
+
# results so they don't leak to the user.
|
|
1633
1679
|
_emit({"type": "step", "text": "Composing answer…"})
|
|
1634
1680
|
final_text = _strip_tool_tags(str(result).strip()) or "[No result]"
|
|
1635
1681
|
_log(f"done (deterministic, no summarizer call): {len(final_text)} chars")
|
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.123",
|
|
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",
|