@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.
- package/gonext_agent_chat.py +61 -10
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -215,8 +215,8 @@ def _summarise_step(step_log):
|
|
|
215
215
|
else:
|
|
216
216
|
parts.append(f"→ Error: {err[:120]}")
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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.
|
|
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",
|