@tiens.nguyen/gonext-local-worker 1.0.142 → 1.0.144
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 +51 -15
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -482,6 +482,12 @@ def _summarise_step(step_log):
|
|
|
482
482
|
err = str(error)
|
|
483
483
|
if "Import of" in err and "not allowed" in err:
|
|
484
484
|
parts.append("→ (import blocked — using http_request tool instead)")
|
|
485
|
+
elif "reached max steps" in err.lower() or "max steps" in err.lower():
|
|
486
|
+
# NOT a user-facing failure: hitting the step budget triggers our
|
|
487
|
+
# provide_final_answer override, which ALWAYS delivers a synthesized answer
|
|
488
|
+
# (a rendered PDF, the last tool result, or a plain reply). Surfacing the raw
|
|
489
|
+
# "Error: Reached max steps." made a successful run look failed (task #10).
|
|
490
|
+
parts.append("→ Wrapping up (reached step budget)…")
|
|
485
491
|
else:
|
|
486
492
|
parts.append(f"→ Error: {err[:120]}")
|
|
487
493
|
|
|
@@ -634,14 +640,23 @@ def _strip_tool_tags(text: str) -> str:
|
|
|
634
640
|
|
|
635
641
|
_THINK_BLOCK = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
|
|
636
642
|
|
|
637
|
-
#
|
|
638
|
-
# emit "<code >" (trailing space), "< code >", "</ code>",
|
|
639
|
-
#
|
|
640
|
-
#
|
|
641
|
-
#
|
|
642
|
-
#
|
|
643
|
-
|
|
643
|
+
# Malformed variants of smolagents' code-block tags. Weak coding models routinely
|
|
644
|
+
# emit "<code >" (trailing space), "< code >", "</ code>", "<CODE>", OR — worst —
|
|
645
|
+
# "<code print(x) </code>" where the OPENING tag is missing its ">" entirely. None
|
|
646
|
+
# match smolagents' exact "<code>(.*?)</code>" parser, so EVERY step fails with
|
|
647
|
+
# "regex pattern <code>(.*?)</code> was not found" and the run burns all its steps.
|
|
648
|
+
# Normalizing the tags to canonical form before the parser sees them turns that
|
|
649
|
+
# spiral into a normal, parseable step.
|
|
644
650
|
_CODE_CLOSE_TAG = re.compile(r"<\s*/\s*code\s*>", re.IGNORECASE)
|
|
651
|
+
# A well-formed-ISH open tag that DOES have a ">": "<code>", "<code >", "< code >",
|
|
652
|
+
# "<CODE>", "<code class=py>". Critically the attr span is [^<>] (NOT [^>]) so it can
|
|
653
|
+
# never swallow across the "<" of a following "</code>" — the bug that collapsed
|
|
654
|
+
# "<code print(x) </code>" to a bare "<code>" and destroyed the code.
|
|
655
|
+
_CODE_OPEN_TAG = re.compile(r"<\s*code\b[^<>]*>", re.IGNORECASE)
|
|
656
|
+
# An open tag MISSING its ">": "<code" not immediately followed by ">". \b keeps it
|
|
657
|
+
# from matching "<codeblock"/"<coder"; the lookahead skips the already-canonical
|
|
658
|
+
# "<code>" so this pass only rescues the no-">" case.
|
|
659
|
+
_CODE_OPEN_NO_GT = re.compile(r"<\s*code\b(?!>)", re.IGNORECASE)
|
|
645
660
|
# Fallback: a markdown code fence (```py / ```python / ```) when the model ignored
|
|
646
661
|
# the tag convention entirely. Captures the fenced body so we can re-wrap it.
|
|
647
662
|
_MD_FENCE_BLOCK = re.compile(
|
|
@@ -652,18 +667,25 @@ _MD_FENCE_BLOCK = re.compile(
|
|
|
652
667
|
def _normalize_code_tags(text: str) -> str:
|
|
653
668
|
"""Coerce a model reply's code-block delimiters to smolagents' exact <code>…</code>.
|
|
654
669
|
|
|
655
|
-
Handles the
|
|
656
|
-
1. tag
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
670
|
+
Handles the failure modes seen live with weak coding models:
|
|
671
|
+
1. close-tag variants: "</ code>", "< / code >" → "</code>" (done FIRST so the
|
|
672
|
+
open-tag pass has a canonical "<" boundary to stop at).
|
|
673
|
+
2. open-tag-with-">": "<code >", "< code >", "<CODE>", "<code attr>" → "<code>".
|
|
674
|
+
3. open-tag-missing-">": "<code print(x) </code>" → "<code> print(x) </code>".
|
|
675
|
+
4. markdown fences instead of tags: ```py … ``` → <code>…</code> (only when no
|
|
676
|
+
<code> tag is present, so we don't touch backticks inside a real <code> block).
|
|
677
|
+
5. opened-but-never-closed (exactly one "<code>", no "</code>"): append "</code>"
|
|
678
|
+
so the parser can still extract the code instead of failing the whole step.
|
|
679
|
+
Idempotent — already-correct "<code>…</code>" is returned unchanged."""
|
|
661
680
|
if not text:
|
|
662
681
|
return text
|
|
663
|
-
fixed =
|
|
664
|
-
fixed =
|
|
682
|
+
fixed = _CODE_CLOSE_TAG.sub("</code>", text)
|
|
683
|
+
fixed = _CODE_OPEN_TAG.sub("<code>", fixed)
|
|
684
|
+
fixed = _CODE_OPEN_NO_GT.sub("<code>", fixed)
|
|
665
685
|
if "<code>" not in fixed:
|
|
666
686
|
fixed = _MD_FENCE_BLOCK.sub(lambda m: f"<code>\n{m.group(1)}</code>", fixed, count=1)
|
|
687
|
+
if fixed.count("<code>") == 1 and "</code>" not in fixed:
|
|
688
|
+
fixed = fixed + "\n</code>"
|
|
667
689
|
return fixed
|
|
668
690
|
|
|
669
691
|
|
|
@@ -1850,10 +1872,13 @@ def run_agent_chat(cfg):
|
|
|
1850
1872
|
# Ollama honors it). Harmless if the server ignores it — usage stays 0.
|
|
1851
1873
|
completion_kwargs["stream_options"] = {"include_usage": True}
|
|
1852
1874
|
self._apply_rate_limit()
|
|
1875
|
+
t0 = time.monotonic()
|
|
1876
|
+
_log("streamed generate: request issued (stream=True), awaiting first token…")
|
|
1853
1877
|
stream = self.client.chat.completions.create(**completion_kwargs)
|
|
1854
1878
|
parts = []
|
|
1855
1879
|
role = "assistant"
|
|
1856
1880
|
in_tok = out_tok = 0
|
|
1881
|
+
first_token_at = None
|
|
1857
1882
|
for chunk in stream:
|
|
1858
1883
|
usage = getattr(chunk, "usage", None)
|
|
1859
1884
|
if usage is not None:
|
|
@@ -1869,6 +1894,17 @@ def run_agent_chat(cfg):
|
|
|
1869
1894
|
role = delta.role
|
|
1870
1895
|
piece = getattr(delta, "content", None)
|
|
1871
1896
|
if piece:
|
|
1897
|
+
if first_token_at is None:
|
|
1898
|
+
# First streamed byte. The gap t0→here is pure prompt-eval time
|
|
1899
|
+
# (server sent nothing before this); everything after is token
|
|
1900
|
+
# generation with bytes flowing, so the proxy idle timer resets.
|
|
1901
|
+
# If this line never appears in the log, the request died in
|
|
1902
|
+
# prompt-eval BEFORE any token — streaming can't help there
|
|
1903
|
+
# (shrink the model / warm the prompt cache instead).
|
|
1904
|
+
first_token_at = time.monotonic()
|
|
1905
|
+
_log(f"streamed generate: FIRST token after {first_token_at - t0:.1f}s "
|
|
1906
|
+
"(prompt-eval done; tokens now flowing)")
|
|
1907
|
+
_emit({"type": "step", "text": "Model is responding…"})
|
|
1872
1908
|
parts.append(piece)
|
|
1873
1909
|
content = "".join(parts)
|
|
1874
1910
|
if stop_sequences and not self.supports_stop_parameter:
|
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.144",
|
|
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",
|