@tiens.nguyen/gonext-local-worker 1.0.89 → 1.0.91

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.
@@ -215,8 +215,8 @@ def _summarise_step(step_log):
215
215
  else:
216
216
  parts.append(f"→ Error: {err[:120]}")
217
217
 
218
- label = f"Step {step_num}: " if step_num is not None else ""
219
- return label + (" | ".join(parts) if parts else "thinking…")
218
+ # No numeric "Step N:" prefix show only the semantic action.
219
+ return (" | ".join(parts) if parts else "thinking…")
220
220
 
221
221
 
222
222
  # Keywords that strongly indicate the user wants to make an HTTP/network request,
@@ -241,9 +241,13 @@ def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
241
241
  Fast-path: if the user explicitly mentions network/request keywords → agent.
242
242
  Otherwise: ask the model to classify.
243
243
  """
244
+ # Show the routing stage in the web Thinking panel.
245
+ _emit({"type": "step", "text": "Routing your request…"})
246
+
244
247
  # Fast-path: explicit HTTP/network intent overrides the model classifier.
245
248
  if _AGENT_KEYWORDS.search(task_text):
246
249
  _log(f"router → YES (keyword match)")
250
+ _emit({"type": "step", "text": "→ Agent mode (needs tools)"})
247
251
  return True
248
252
 
249
253
  try:
@@ -271,9 +275,12 @@ def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
271
275
  )
272
276
  answer = (resp.choices[0].message.content or "").strip().upper()
273
277
  _log(f"router → {answer!r} (model)")
274
- return answer.startswith("Y")
278
+ is_agent = answer.startswith("Y")
279
+ _emit({"type": "step", "text": "→ Agent mode (needs tools)" if is_agent else "→ Chat reply"})
280
+ return is_agent
275
281
  except Exception as e: # noqa: BLE001
276
282
  _log(f"router error: {e} — defaulting to agent")
283
+ _emit({"type": "step", "text": "→ Agent mode (needs tools)"})
277
284
  return True
278
285
 
279
286
 
@@ -339,6 +346,18 @@ def _plain_reply(messages: list, base_url: str, api_key: str, model_id: str) ->
339
346
  return f"[Error: {e}]"
340
347
 
341
348
 
349
+ def _strip_tool_tags(text: str) -> str:
350
+ """Remove the internal hint tags we append to tool output (e.g. '[SUCCESS …]',
351
+ '[NOTE: …]', 'Note: This URL failed …') so they never leak into the user reply."""
352
+ out = []
353
+ for ln in (text or "").splitlines():
354
+ s = ln.strip()
355
+ if s.startswith("[SUCCESS") or s.startswith("[NOTE:") or s.startswith("Note: This URL failed"):
356
+ continue
357
+ out.append(ln)
358
+ return "\n".join(out).strip()
359
+
360
+
342
361
  def run_agent_chat(cfg):
343
362
  try:
344
363
  from smolagents import CodeAgent, OpenAIServerModel, tool
@@ -373,7 +392,11 @@ def run_agent_chat(cfg):
373
392
  else:
374
393
  coding_base_url = agent_base_url
375
394
  coding_model_id = agent_model_id
376
- max_steps = int(cfg.get("maxSteps") or 5)
395
+ # Strict single-shot: exactly ONE agent model call per message. The single code
396
+ # block must call a tool AND final_answer together — no multi-step ReAct loop.
397
+ # If the model fails to call final_answer, the max-steps fallback below returns
398
+ # the last tool observation deterministically (no extra model call).
399
+ max_steps = 1
377
400
 
378
401
  _log(
379
402
  f"start model={agent_model_id!r} base={agent_base_url!r} "
@@ -451,6 +474,7 @@ def run_agent_chat(cfg):
451
474
 
452
475
  if not needs_agent:
453
476
  _log("router: plain chat (no HTTP needed)")
477
+ _emit({"type": "step", "text": "Composing a reply…"})
454
478
  answer = _plain_reply(messages, agent_base_url, agent_api_key, agent_model_id)
455
479
  _log(f"plain reply: {len(answer)} chars")
456
480
  _emit({"type": "final", "text": answer})
@@ -466,6 +490,8 @@ def run_agent_chat(cfg):
466
490
  now_str = _dt_now.now().astimezone().strftime("%A, %d %B %Y, %H:%M %Z")
467
491
  tool_hint = (
468
492
  f"Current date/time: {now_str}.\n\n"
493
+ "YOU HAVE EXACTLY ONE TURN. In a single code block, call the ONE right tool "
494
+ "and then pass its result to final_answer(). Do not plan multiple steps.\n\n"
469
495
  "You have THREE tools:\n"
470
496
  " 1. http_request(method, url, headers='', body='', username='', password='') — "
471
497
  "call a SPECIFIC known API/URL.\n"
@@ -504,6 +530,10 @@ def run_agent_chat(cfg):
504
530
  # Track URLs that have already failed so we don't retry dead endpoints across steps.
505
531
  _failed_urls: set = set()
506
532
 
533
+ # Remember the last tool output so the single-shot fallback + the deterministic
534
+ # final formatting can report exactly what a tool returned (no extra model call).
535
+ _last_obs: dict = {"text": ""}
536
+
507
537
  @tool
508
538
  def http_request(method: str, url: str, headers: str = "", body: str = "",
509
539
  username: str = "", password: str = "") -> str:
@@ -561,6 +591,7 @@ def run_agent_chat(cfg):
561
591
  result = result + "\n[SUCCESS — call final_answer(response) now, do not parse or retry]"
562
592
  _emit({"type": "step", "text": f"HTTP {method.upper()} {url} → {status_line}"})
563
593
  _log(f"http_request {method.upper()} {url} → {result[:80]}")
594
+ _last_obs["text"] = result
564
595
  return result
565
596
 
566
597
  @tool
@@ -582,6 +613,7 @@ def run_agent_chat(cfg):
582
613
  out = now.strftime("%A, %d %B %Y, %H:%M:%S %Z")
583
614
  _emit({"type": "step", "text": f"Current date/time → {out}"})
584
615
  _log(f"get_current_datetime({timezone!r}) → {out}")
616
+ _last_obs["text"] = out
585
617
  return out
586
618
 
587
619
  @tool
@@ -597,6 +629,7 @@ def run_agent_chat(cfg):
597
629
  _emit({"type": "step", "text": f"Searching the web → {query[:80]}"})
598
630
  result = _web_search_impl(query)
599
631
  _log(f"web_search {query[:60]!r} → {result[:80]}")
632
+ _last_obs["text"] = result
600
633
  return result
601
634
 
602
635
  def step_callback(step_log):
@@ -658,13 +691,28 @@ def run_agent_chat(cfg):
658
691
  _log(f"MODEL REQUEST log error: {e}")
659
692
  return ck
660
693
 
694
+ # Single-shot agent: if the one model call doesn't end in final_answer(),
695
+ # smolagents would normally make an EXTRA model call (provide_final_answer) to
696
+ # synthesize one. We override that to return the last tool observation
697
+ # deterministically — keeping the agent to EXACTLY ONE model call, and never
698
+ # corrupting exact tool output (dates/numbers) the way a weak model would.
699
+ class _SingleShotAgent(CodeAgent):
700
+ def provide_final_answer(self, task, *args, **kwargs):
701
+ from smolagents.models import ChatMessage, MessageRole
702
+ text = (_last_obs.get("text") or "").strip()
703
+ if not text:
704
+ text = ("I couldn't complete that in one step. Please rephrase, or give "
705
+ "a specific URL/API to call.")
706
+ _log(f"single-shot fallback (no model call) → {text[:80]}")
707
+ return ChatMessage(role=MessageRole.ASSISTANT, content=text)
708
+
661
709
  try:
662
710
  model = _LoggingModel(
663
711
  model_id=coding_model_id,
664
712
  api_base=coding_base_url,
665
713
  api_key=agent_api_key,
666
714
  )
667
- agent = CodeAgent(
715
+ agent = _SingleShotAgent(
668
716
  tools=[http_request, web_search, get_current_datetime],
669
717
  model=model,
670
718
  max_steps=max_steps,
@@ -674,11 +722,14 @@ def run_agent_chat(cfg):
674
722
  )
675
723
  with contextlib.redirect_stdout(sys.stderr):
676
724
  result = agent.run(task_with_hint)
677
- final_text = _summarize_result(
678
- task_text, str(result).strip(),
679
- agent_base_url, agent_api_key, agent_model_id
680
- )
681
- _log(f"done: {len(final_text)} chars")
725
+ # Deterministic final formatting — NO summarizer model call. The agent's
726
+ # final_answer (or the single-shot fallback above) already holds exact tool
727
+ # output; we just strip the internal hint tags we appended to tool results so
728
+ # they don't leak to the user. This permanently fixes the date-corruption a
729
+ # weak summarizer model used to introduce.
730
+ _emit({"type": "step", "text": "Composing answer…"})
731
+ final_text = _strip_tool_tags(str(result).strip()) or "[No result]"
732
+ _log(f"done (deterministic, no summarizer call): {len(final_text)} chars")
682
733
  _emit({"type": "final", "text": final_text})
683
734
  except Exception as e: # noqa: BLE001
684
735
  _log(f"agent error: {e}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.89",
3
+ "version": "1.0.91",
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",