@tiens.nguyen/gonext-local-worker 1.0.123 → 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.
- package/gonext_agent_chat.py +81 -29
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -904,20 +904,33 @@ def run_agent_chat(cfg):
|
|
|
904
904
|
raw_coding_base = (cfg.get("codingBaseURL") or "").strip()
|
|
905
905
|
raw_coding_model = (cfg.get("codingModelId") or "").strip()
|
|
906
906
|
if raw_coding_base:
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
coding_model_id = detected
|
|
914
|
-
else:
|
|
907
|
+
same_server = raw_coding_base.rstrip("/") == (agent_base_url or "").rstrip("/")
|
|
908
|
+
if same_server and not raw_coding_model:
|
|
909
|
+
# The coding URL points at the SAME server as the chat model. Do NOT
|
|
910
|
+
# auto-detect: /v1/models can list several cached models and return a
|
|
911
|
+
# SMALLER one first (e.g. a 3B), silently downgrading the agent's reasoning.
|
|
912
|
+
# Reuse the chat model id directly — it's the model actually loaded here.
|
|
915
913
|
_log(
|
|
916
|
-
f"coding
|
|
917
|
-
"
|
|
914
|
+
f"coding base == chat base ({raw_coding_base}); "
|
|
915
|
+
f"reusing chat model {agent_model_id!r} (skipping auto-detect)"
|
|
918
916
|
)
|
|
919
917
|
coding_base_url = agent_base_url
|
|
920
918
|
coding_model_id = agent_model_id
|
|
919
|
+
else:
|
|
920
|
+
# A DIFFERENT dedicated coding server. If no model name was given, ask the
|
|
921
|
+
# server which model it serves (mlx_lm.server otherwise tries to download a
|
|
922
|
+
# mismatched name from HF and 404s).
|
|
923
|
+
detected = raw_coding_model or _detect_model_id(raw_coding_base, agent_api_key)
|
|
924
|
+
if detected:
|
|
925
|
+
coding_base_url = raw_coding_base
|
|
926
|
+
coding_model_id = detected
|
|
927
|
+
else:
|
|
928
|
+
_log(
|
|
929
|
+
f"coding model id unresolved for {raw_coding_base!r}; "
|
|
930
|
+
"falling back to chat model"
|
|
931
|
+
)
|
|
932
|
+
coding_base_url = agent_base_url
|
|
933
|
+
coding_model_id = agent_model_id
|
|
921
934
|
else:
|
|
922
935
|
coding_base_url = agent_base_url
|
|
923
936
|
coding_model_id = agent_model_id
|
|
@@ -1187,15 +1200,13 @@ def run_agent_chat(cfg):
|
|
|
1187
1200
|
"\n"
|
|
1188
1201
|
"http_request RETURN FORMAT: 'HTTP 200\\n{body}' — first line is 'HTTP <code>', body follows.\n"
|
|
1189
1202
|
"\n"
|
|
1190
|
-
"
|
|
1191
|
-
"
|
|
1192
|
-
"
|
|
1193
|
-
"
|
|
1194
|
-
"
|
|
1195
|
-
"
|
|
1196
|
-
"
|
|
1197
|
-
" response = http_request('GET', url, headers='{\"Authorization\": \"Bearer TOKEN\"}')\n"
|
|
1198
|
-
" final_answer(response)\n"
|
|
1203
|
+
"AUTH — ONLY when the TASK itself provides credentials or a token. If it does "
|
|
1204
|
+
"NOT, call http_request with NO username/password/headers. NEVER invent or copy "
|
|
1205
|
+
"the placeholder values below.\n"
|
|
1206
|
+
" BASIC AUTH — pass username= and password= (auto base64; never build a 'Basic ' header):\n"
|
|
1207
|
+
" http_request('GET', url, username=<username from the task>, password=<password from the task>)\n"
|
|
1208
|
+
" BEARER TOKEN — pass it in headers:\n"
|
|
1209
|
+
" http_request('GET', url, headers='{\"Authorization\": \"Bearer <token from the task>\"}')\n"
|
|
1199
1210
|
"\n"
|
|
1200
1211
|
"CHOOSING A TOOL (match the TASK, not these examples):\n"
|
|
1201
1212
|
"- ONLY a date/time question (e.g. 'what is the date today') -> get_current_datetime().\n"
|
|
@@ -1203,6 +1214,8 @@ def run_agent_chat(cfg):
|
|
|
1203
1214
|
"knowledge -> web_search(query).\n"
|
|
1204
1215
|
"- 'read this page' / 'open this link' / 'what does <URL> say' -> fetch_url(url).\n"
|
|
1205
1216
|
"- any arithmetic, percentage, or 'how much is' math -> calculate(expression).\n"
|
|
1217
|
+
"- a live/current NUMBER with no URL given (price, exchange rate, weather, score, "
|
|
1218
|
+
"stock/crypto) -> web_search(query). Do NOT guess an API URL for these.\n"
|
|
1206
1219
|
"- 'create/make/generate/export a PDF' of some text/data -> create_pdf(text, title).\n"
|
|
1207
1220
|
+ _pdf_read_choose_line + _email_choose_line +
|
|
1208
1221
|
"- A specific known API/URL was given -> http_request().\n"
|
|
@@ -1212,8 +1225,11 @@ def run_agent_chat(cfg):
|
|
|
1212
1225
|
"If nothing works, call final_answer explaining what you need — do NOT make up an answer.\n"
|
|
1213
1226
|
"- Only report what a tool ACTUALLY returned. Never fabricate a response, body, or status code.\n"
|
|
1214
1227
|
"- Pass an http_request response DIRECTLY to final_answer — do NOT split, parse, or index it.\n"
|
|
1215
|
-
"-
|
|
1216
|
-
"
|
|
1228
|
+
"- When a call's result is UNCERTAIN (any network call), do NOT call final_answer "
|
|
1229
|
+
"in the SAME block. Call the tool alone, READ the Observation, THEN answer next step.\n"
|
|
1230
|
+
"- If a response starts with 'HTTP 2' it SUCCEEDED — answer with it next step.\n"
|
|
1231
|
+
"- NEVER pass an 'Error:' / HTTP 4xx / 5xx result to final_answer. On an error, try a "
|
|
1232
|
+
"DIFFERENT approach next step (e.g. web_search); only give up after a few tries.\n"
|
|
1217
1233
|
"- Do NOT put final_answer outside the code block.\n\n"
|
|
1218
1234
|
)
|
|
1219
1235
|
# Lead with the TASK so the weak model anchors on what's actually being asked —
|
|
@@ -1558,22 +1574,58 @@ def run_agent_chat(cfg):
|
|
|
1558
1574
|
# literal messages array sent to /v1/chat/completions.
|
|
1559
1575
|
class _LoggingModel(OpenAIServerModel):
|
|
1560
1576
|
def generate(self, *args, **kwargs):
|
|
1561
|
-
#
|
|
1562
|
-
#
|
|
1563
|
-
#
|
|
1564
|
-
#
|
|
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).
|
|
1565
1590
|
msg = super().generate(*args, **kwargs)
|
|
1566
1591
|
try:
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
_log(
|
|
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)")
|
|
1570
1599
|
msg.content = cleaned
|
|
1571
1600
|
except Exception as e: # noqa: BLE001
|
|
1572
|
-
_log(f"
|
|
1601
|
+
_log(f"content-normalize error: {e}")
|
|
1573
1602
|
return msg
|
|
1574
1603
|
|
|
1575
1604
|
def _prepare_completion_kwargs(self, *args, **kwargs):
|
|
1576
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}")
|
|
1577
1629
|
try:
|
|
1578
1630
|
msgs = ck.get("messages", []) or []
|
|
1579
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.
|
|
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",
|