@tiens.nguyen/gonext-local-worker 1.0.124 → 1.0.125

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.
@@ -1574,22 +1574,58 @@ def run_agent_chat(cfg):
1574
1574
  # literal messages array sent to /v1/chat/completions.
1575
1575
  class _LoggingModel(OpenAIServerModel):
1576
1576
  def generate(self, *args, **kwargs):
1577
- # Strip Qwen3 <think>…</think> reasoning traces from each step's reply
1578
- # BEFORE smolagents parses it for the code block / final_answer. Thinking
1579
- # stays on (better reasoning), but the trace never confuses the parser or
1580
- # leaks to the user.
1577
+ # Two safeguards on each step's reply BEFORE smolagents parses it for the
1578
+ # code block / final_answer:
1579
+ # 1. content=None guard — Qwen3 (and reasoning models generally) can return
1580
+ # message.content=None on a turn (e.g. it spent the whole completion on a
1581
+ # <think> trace and never emitted a code block). smolagents has NO null
1582
+ # guard (models.py: `content = response.choices[0].message.content`) and
1583
+ # feeds it straight into parse_code_blobs → `re` gets None →
1584
+ # "expected string or bytes-like object, got 'NoneType'" → the whole
1585
+ # loop spirals on parse errors and trips the WebSocket timeout. Coercing
1586
+ # None→"" turns that fatal crash into a normal "no code block" retry.
1587
+ # 2. strip any leftover Qwen3 <think>…</think> trace (belt-and-suspenders;
1588
+ # thinking is disabled via /no_think in _prepare_completion_kwargs, but a
1589
+ # stray trace must never reach the parser or leak to the user).
1581
1590
  msg = super().generate(*args, **kwargs)
1582
1591
  try:
1583
- if isinstance(getattr(msg, "content", None), str) and "</think>" in msg.content.lower():
1584
- cleaned = _strip_think(msg.content)
1585
- _log(f"stripped <think> trace ({len(msg.content) - len(cleaned)} chars)")
1592
+ content = getattr(msg, "content", None)
1593
+ if content is None:
1594
+ _log("model returned content=None (empty/thinking-only turn) → coercing to ''")
1595
+ msg.content = ""
1596
+ elif isinstance(content, str) and "</think>" in content.lower():
1597
+ cleaned = _strip_think(content)
1598
+ _log(f"stripped <think> trace ({len(content) - len(cleaned)} chars)")
1586
1599
  msg.content = cleaned
1587
1600
  except Exception as e: # noqa: BLE001
1588
- _log(f"think-strip error: {e}")
1601
+ _log(f"content-normalize error: {e}")
1589
1602
  return msg
1590
1603
 
1591
1604
  def _prepare_completion_kwargs(self, *args, **kwargs):
1592
1605
  ck = super()._prepare_completion_kwargs(*args, **kwargs)
1606
+ # Disable Qwen3 "thinking" for the agent loop. Live runs showed thinking-on
1607
+ # spends the whole completion budget on a <think> trace and returns
1608
+ # content=None (no code block) → parse-error spiral → WS timeout. The
1609
+ # `/no_think` soft switch (recognized by the Qwen3 chat template) forces a
1610
+ # direct Thought+code reply every step. Injected into the SYSTEM message so it
1611
+ # applies to EVERY step's request, not just the first user turn.
1612
+ try:
1613
+ msgs = ck.get("messages", []) or []
1614
+ for m in msgs:
1615
+ role = m.get("role") if isinstance(m, dict) else getattr(m, "role", None)
1616
+ if str(role).endswith("system") or role == "system":
1617
+ c = m.get("content") if isinstance(m, dict) else getattr(m, "content", None)
1618
+ if isinstance(c, str) and "/no_think" not in c:
1619
+ m["content"] = c + "\n\n/no_think"
1620
+ elif isinstance(c, list):
1621
+ for part in c:
1622
+ if isinstance(part, dict) and isinstance(part.get("text"), str) \
1623
+ and "/no_think" not in part["text"]:
1624
+ part["text"] += "\n\n/no_think"
1625
+ break
1626
+ break
1627
+ except Exception as e: # noqa: BLE001
1628
+ _log(f"/no_think inject error: {e}")
1593
1629
  try:
1594
1630
  msgs = ck.get("messages", []) or []
1595
1631
  _log(f"=== MODEL REQUEST: {len(msgs)} message(s) sent to the model ===")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.124",
3
+ "version": "1.0.125",
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",