@tiens.nguyen/gonext-local-worker 1.0.191 → 1.0.192
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 +84 -46
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -476,6 +476,28 @@ def _clip(text: str, limit: int = 280) -> str:
|
|
|
476
476
|
return f"{cut}…"
|
|
477
477
|
|
|
478
478
|
|
|
479
|
+
def _render_numbered_tool_list(tools) -> str:
|
|
480
|
+
"""Render ' N. name(args) — description' lines from the REAL registered smolagents
|
|
481
|
+
tool objects — their actual name/inputs/docstring — instead of a hand-maintained
|
|
482
|
+
duplicate of the same information. This is the single source of truth for what's
|
|
483
|
+
ACTUALLY callable, so the list can never drift out of sync with the tool set (e.g.
|
|
484
|
+
when RAG or a workspace is enabled/disabled) and the numbering is always exactly
|
|
485
|
+
correct, unlike the old manually-incremented counter it replaces. Kept intentionally
|
|
486
|
+
terse — first docstring line only, bare arg names — since the system prompt's own
|
|
487
|
+
`tool.to_code_prompt()` rendering (see _COMPACT_CODE_SYSTEM_PROMPT) already shows the
|
|
488
|
+
full typed signature on every model call; this is just a quick-reference index."""
|
|
489
|
+
lines = []
|
|
490
|
+
for i, t in enumerate(tools, 1):
|
|
491
|
+
args = []
|
|
492
|
+
for pname, pinfo in (getattr(t, "inputs", None) or {}).items():
|
|
493
|
+
optional = isinstance(pinfo, dict) and pinfo.get("nullable")
|
|
494
|
+
args.append(f"{pname}=''" if optional and pinfo.get("type") == "string" else pname)
|
|
495
|
+
desc_full = (getattr(t, "description", "") or "").strip()
|
|
496
|
+
desc = desc_full.splitlines()[0] if desc_full else ""
|
|
497
|
+
lines.append(f" {i}. {t.name}({', '.join(args)}) — {desc}")
|
|
498
|
+
return ("\n".join(lines) + "\n") if lines else ""
|
|
499
|
+
|
|
500
|
+
|
|
479
501
|
def _summarise_step(step_log):
|
|
480
502
|
"""Return a short human-readable description of an agent step."""
|
|
481
503
|
tool_calls = getattr(step_log, "tool_calls", None) or []
|
|
@@ -598,6 +620,31 @@ def _is_trivial_chat(text: str) -> bool:
|
|
|
598
620
|
return bool(_TRIVIAL_CHAT.match(t))
|
|
599
621
|
|
|
600
622
|
|
|
623
|
+
# A weak/confused coding model sometimes "thinks out loud" in plain prose instead of
|
|
624
|
+
# emitting the next tool call — e.g. a rambling, self-correcting scratchpad ("Wait,
|
|
625
|
+
# actually... Let's try... Actually, let's just...") that can run thousands of chars.
|
|
626
|
+
# The prose-final-answer heuristic below (long text with list-like structure) can't tell
|
|
627
|
+
# this apart from a genuine, complete answer on structure alone — both use headings/
|
|
628
|
+
# numbered lists. These two signals catch the scratchpad case specifically: it either
|
|
629
|
+
# self-labels as "Thought"/"Thinking Process" up front (a real answer doesn't open by
|
|
630
|
+
# naming itself as the model's own reasoning), or it's dense with self-correction
|
|
631
|
+
# language a finished answer wouldn't contain.
|
|
632
|
+
_SCRATCHPAD_START_RE = re.compile(r"^\s*thought\b\s*[:\n]", re.I)
|
|
633
|
+
_DELIBERATION_RE = re.compile(
|
|
634
|
+
r"\b(wait,|actually,|let'?s (try|just|use|list|see|go)|"
|
|
635
|
+
r"i (?:need to|should) (?:check|verify|confirm))\b",
|
|
636
|
+
re.I,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _looks_like_scratchpad(stripped: str) -> bool:
|
|
641
|
+
"""True if `stripped` reads like unresolved internal deliberation rather than a
|
|
642
|
+
finished, user-facing answer — see module comment above for the two signals used."""
|
|
643
|
+
if _SCRATCHPAD_START_RE.match(stripped):
|
|
644
|
+
return True
|
|
645
|
+
return len(_DELIBERATION_RE.findall(stripped)) >= 3
|
|
646
|
+
|
|
647
|
+
|
|
601
648
|
# Compact replacement for smolagents' default CodeAgent system prompt. The stock template
|
|
602
649
|
# is ~9.9k chars, dominated by ~6k chars of GENERIC few-shot examples (image captions,
|
|
603
650
|
# Wikipedia, etc.) that are irrelevant to our HTTP/file tools and cost real prompt-eval
|
|
@@ -2267,25 +2314,12 @@ def run_agent_chat(cfg):
|
|
|
2267
2314
|
# fabricate URLs/responses, and always terminate with final_answer().
|
|
2268
2315
|
from datetime import datetime as _dt_now
|
|
2269
2316
|
now_str = _dt_now.now().astimezone().strftime("%A, %d %B %Y, %H:%M %Z")
|
|
2270
|
-
# The
|
|
2271
|
-
#
|
|
2272
|
-
#
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
_tool_num += 1
|
|
2277
|
-
_pdf_num = _tool_num
|
|
2278
|
-
_email_num = 0
|
|
2279
|
-
if _EMAIL_AVAILABLE:
|
|
2280
|
-
_tool_num += 1
|
|
2281
|
-
_email_num = _tool_num
|
|
2282
|
-
if _RAG_AVAILABLE:
|
|
2283
|
-
_tool_num += 7 # download_file, unzip_file, list_dir, read_text_file, rag_index/add/search
|
|
2284
|
-
if _WS_AVAILABLE:
|
|
2285
|
-
_tool_num += 6 # grep_repo, read_file_lines, edit_lines, edit_file, create_file, run_command
|
|
2286
|
-
if not _RAG_AVAILABLE:
|
|
2287
|
-
_tool_num += 2 # list_dir + read_text_file registered for workspace browsing
|
|
2288
|
-
_tool_count_word = {6: "SIX", 7: "SEVEN", 8: "EIGHT"}.get(_tool_num, str(_tool_num))
|
|
2317
|
+
# The numbered tool list itself is generated LATER from the real registered tool
|
|
2318
|
+
# objects (see _render_numbered_tool_list, spliced into task_with_hint below via the
|
|
2319
|
+
# {{TOOL_LIST}} placeholder) — it can never drift or miscount, unlike a hand-written
|
|
2320
|
+
# list with a manually-incremented number. The PDF reader / send_email / RAG /
|
|
2321
|
+
# workspace tools are only ever REGISTERED (further below) when available, so an
|
|
2322
|
+
# unavailable tool is never advertised.
|
|
2289
2323
|
_rag_tool_block = (
|
|
2290
2324
|
" FILES & KNOWLEDGE BASE — to SUMMARIZE or ANSWER questions about a ZIP of files at a URL:\n"
|
|
2291
2325
|
" - download_file(url) — download a file (e.g. a .zip) to this machine.\n"
|
|
@@ -2339,18 +2373,10 @@ def run_agent_chat(cfg):
|
|
|
2339
2373
|
"AFTER editing, re-read with grep_repo/read_file_lines (an S3 knowledge base may be "
|
|
2340
2374
|
"stale — do not trust rag_search for freshly edited code).\n"
|
|
2341
2375
|
) if _WS_AVAILABLE else ""
|
|
2342
|
-
_pdf_read_tool_line = (
|
|
2343
|
-
f" {_pdf_num}. extract_text_from_pdf(url) — READ/summarize an EXISTING PDF file "
|
|
2344
|
-
"at a URL. This does NOT create a PDF (use create_pdf for that).\n"
|
|
2345
|
-
) if _PDF_READ_AVAILABLE else ""
|
|
2346
2376
|
_pdf_read_choose_line = (
|
|
2347
2377
|
"- 'read'/'summarize'/'what does the PDF say' for an existing PDF URL -> "
|
|
2348
2378
|
"extract_text_from_pdf(url).\n"
|
|
2349
2379
|
) if _PDF_READ_AVAILABLE else ""
|
|
2350
|
-
_email_tool_line = (
|
|
2351
|
-
f" {_email_num}. send_email(to, subject, body) — send an email via the configured "
|
|
2352
|
-
"email API. It PREVIEWS first and only sends after the user replies 'confirm'.\n"
|
|
2353
|
-
) if _EMAIL_AVAILABLE else ""
|
|
2354
2380
|
_email_choose_line = (
|
|
2355
2381
|
"- 'email …' / 'send an email to …' -> send_email(to, subject, body) "
|
|
2356
2382
|
"(previews first, then sends on confirm).\n"
|
|
@@ -2362,20 +2388,10 @@ def run_agent_chat(cfg):
|
|
|
2362
2388
|
"next (e.g. web_search to find a URL, THEN fetch_url to read it). When you have "
|
|
2363
2389
|
"enough to answer, call final_answer(<your answer>) in a code block. Prefer "
|
|
2364
2390
|
"FEWER steps, and never repeat a tool call that already succeeded.\n\n"
|
|
2365
|
-
|
|
2366
|
-
"
|
|
2367
|
-
"
|
|
2368
|
-
|
|
2369
|
-
"when you do NOT already have a real URL. Returns a summary + source.\n"
|
|
2370
|
-
" 3. fetch_url(url) — READ the full text of a specific web page (e.g. a link the "
|
|
2371
|
-
"user gave, or a source URL from web_search). Returns clean text, not raw HTML.\n"
|
|
2372
|
-
" 4. calculate(expression) — do math: arithmetic, percentages, powers "
|
|
2373
|
-
"(e.g. '15% of 80', '(3+4)*2'). ALWAYS use this instead of computing yourself.\n"
|
|
2374
|
-
f" 5. get_current_datetime(timezone='') — current date/time ONLY (now: {now_str}). "
|
|
2375
|
-
"Use this ONLY when the task explicitly asks for the date or time.\n"
|
|
2376
|
-
" 6. create_pdf(text, title='') — make a PDF document from text/data and return a "
|
|
2377
|
-
"download link. Use ONLY when the task asks to create/make/generate/export a PDF.\n"
|
|
2378
|
-
+ _pdf_read_tool_line + _email_tool_line + _rag_tool_block + _ws_tool_block +
|
|
2391
|
+
"{{TOOL_LIST}}\n" # filled in below, once from the REAL registered tool objects
|
|
2392
|
+
f"(Current date/time: {now_str} — pass a timezone to get_current_datetime() only "
|
|
2393
|
+
"if the task needs a DIFFERENT one.)\n"
|
|
2394
|
+
+ _rag_tool_block + _ws_tool_block +
|
|
2379
2395
|
"\n"
|
|
2380
2396
|
"http_request RETURN FORMAT: 'HTTP 200\\n{body}' — first line is 'HTTP <code>', body follows.\n"
|
|
2381
2397
|
"\n"
|
|
@@ -3134,9 +3150,11 @@ def run_agent_chat(cfg):
|
|
|
3134
3150
|
msg.content = normalized
|
|
3135
3151
|
else:
|
|
3136
3152
|
stripped = content.strip()
|
|
3137
|
-
looks_final =
|
|
3138
|
-
|
|
3139
|
-
|
|
3153
|
+
looks_final = (
|
|
3154
|
+
len(stripped) >= 400 or re.search(
|
|
3155
|
+
r"(^|\n)#{1,4}\s|\n\d+\.\s|\n[-*]\s|\|.+\|", stripped
|
|
3156
|
+
) is not None
|
|
3157
|
+
) and not _looks_like_scratchpad(stripped)
|
|
3140
3158
|
if stripped and looks_final:
|
|
3141
3159
|
_log(
|
|
3142
3160
|
f"prose-only turn ({len(stripped)} chars, no tool call) "
|
|
@@ -3145,6 +3163,19 @@ def run_agent_chat(cfg):
|
|
|
3145
3163
|
msg.content = (
|
|
3146
3164
|
stripped + "\n<code>\nfinal_answer(" + repr(stripped) + ")\n</code>"
|
|
3147
3165
|
)
|
|
3166
|
+
elif stripped and _looks_like_scratchpad(stripped):
|
|
3167
|
+
# Looks like unresolved deliberation, not a finished answer — do
|
|
3168
|
+
# NOT wrap it as final_answer (that would dump this rambling
|
|
3169
|
+
# scratchpad into the user-facing reply AND permanently pollute
|
|
3170
|
+
# every future turn's conversation history with it, seen live:
|
|
3171
|
+
# a 6000+ char "Thinking Process:" dump derailed the next two
|
|
3172
|
+
# unrelated questions). Leave content as-is: smolagents' own
|
|
3173
|
+
# code-block parser will fail to find one and burn this step as
|
|
3174
|
+
# a normal parse-error retry, giving the model another attempt.
|
|
3175
|
+
_log(
|
|
3176
|
+
f"scratchpad-like prose ({len(stripped)} chars, no tool call) "
|
|
3177
|
+
"→ NOT auto-wrapping; letting the step fail normally"
|
|
3178
|
+
)
|
|
3148
3179
|
except Exception as e: # noqa: BLE001
|
|
3149
3180
|
_log(f"content-normalize error: {e}")
|
|
3150
3181
|
return msg
|
|
@@ -3701,8 +3732,9 @@ def run_agent_chat(cfg):
|
|
|
3701
3732
|
|
|
3702
3733
|
agent_tools = [http_request, web_search, fetch_url, calculate,
|
|
3703
3734
|
get_current_datetime, create_pdf]
|
|
3704
|
-
# Only register the PDF reader / email / RAG tools when available
|
|
3705
|
-
#
|
|
3735
|
+
# Only register the PDF reader / email / RAG tools when available — the model
|
|
3736
|
+
# never sees a tool it can't run, since the task-hint's tool list (filled in just
|
|
3737
|
+
# below) is generated FROM this exact list, so it's always in sync by construction.
|
|
3706
3738
|
if _PDF_READ_AVAILABLE:
|
|
3707
3739
|
agent_tools.append(extract_text_from_pdf)
|
|
3708
3740
|
if _EMAIL_AVAILABLE:
|
|
@@ -3717,6 +3749,12 @@ def run_agent_chat(cfg):
|
|
|
3717
3749
|
# (S3 indexing) isn't configured.
|
|
3718
3750
|
if not _RAG_AVAILABLE:
|
|
3719
3751
|
agent_tools += [list_dir, read_text_file]
|
|
3752
|
+
# Fill in the numbered tool list now that the REAL tool objects exist — see
|
|
3753
|
+
# _render_numbered_tool_list and the {{TOOL_LIST}} placeholder left in tool_hint.
|
|
3754
|
+
task_with_hint = task_with_hint.replace(
|
|
3755
|
+
"{{TOOL_LIST}}",
|
|
3756
|
+
f"You have {len(agent_tools)} tools:\n" + _render_numbered_tool_list(agent_tools),
|
|
3757
|
+
)
|
|
3720
3758
|
agent_kwargs = dict(
|
|
3721
3759
|
tools=agent_tools,
|
|
3722
3760
|
model=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.192",
|
|
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",
|