@tiens.nguyen/gonext-local-worker 1.0.180 → 1.0.182
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-repl.mjs +77 -24
- package/gonext_agent_chat.py +161 -18
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Commands: /exit /revert [runId] /help Ctrl-C stops following the current run.
|
|
20
20
|
*/
|
|
21
21
|
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
|
22
|
-
import { existsSync, statSync } from "node:fs";
|
|
22
|
+
import { existsSync, statSync, readFileSync } from "node:fs";
|
|
23
23
|
import { spawn } from "node:child_process";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
25
|
import { dirname, join, resolve, basename } from "node:path";
|
|
@@ -38,11 +38,26 @@ const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
|
38
38
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
39
39
|
const CLEAR_LINE = "\r\x1b[K"; // carriage-return + erase-to-end-of-line
|
|
40
40
|
|
|
41
|
-
// The worker's
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// and that gets erased once real output arrives, so they never pile up in the scrollback.
|
|
41
|
+
// The worker's "still thinking" heartbeat lines look like "Caffeinating… (90s)" — we
|
|
42
|
+
// SWALLOW them from the terminal (the local ticker below drives progress instead), and
|
|
43
|
+
// also drop the worker's ~~~ stream fences (they only matter to the web renderer).
|
|
45
44
|
const isHeartbeatLine = (line) => /…\s?\(\d+s\)\s*$/.test(line);
|
|
45
|
+
const isFenceLine = (line) => line.trim() === "~~~";
|
|
46
|
+
|
|
47
|
+
// Playful words for the local "thinking…" ticker, reused verbatim from the worker's
|
|
48
|
+
// thinking_words.txt sibling (falls back to a tiny built-in list if it's missing).
|
|
49
|
+
const THINKING_WORDS = (() => {
|
|
50
|
+
try {
|
|
51
|
+
return readFileSync(join(DIR, "thinking_words.txt"), "utf8")
|
|
52
|
+
.split("\n")
|
|
53
|
+
.map((l) => l.trim())
|
|
54
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
55
|
+
} catch {
|
|
56
|
+
return ["Thinking", "Percolating", "Cogitating", "Noodling", "Ruminating"];
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
const pickWord = () =>
|
|
60
|
+
THINKING_WORDS[Math.floor(Math.random() * THINKING_WORDS.length)] || "Thinking";
|
|
46
61
|
|
|
47
62
|
// ---------- flags ----------
|
|
48
63
|
const argv = process.argv.slice(2);
|
|
@@ -227,38 +242,74 @@ async function runAgentTurn(history) {
|
|
|
227
242
|
followAborted = false;
|
|
228
243
|
const startedAt = Date.now();
|
|
229
244
|
let shownChars = 0;
|
|
230
|
-
let carry = ""; // trailing partial line held until its newline arrives
|
|
231
|
-
let statusShown = false; // a transient
|
|
245
|
+
let carry = ""; // trailing partial content line held until its newline arrives
|
|
246
|
+
let statusShown = false; // a transient status line is currently on screen
|
|
232
247
|
let warnedPending = false;
|
|
248
|
+
let jobRunning = false; // only tick "thinking…" once the worker has claimed the job
|
|
249
|
+
|
|
250
|
+
// --- Live "thinking…" ticker ---------------------------------------------------
|
|
251
|
+
// Between visible tokens (prompt-eval / awaiting the first token) we show ONE in-place
|
|
252
|
+
// status line that counts UP every second — "Percolating thinking… (7s)" — so the wait
|
|
253
|
+
// feels alive. When output resumes we stamp a dim "✔ completed thought (Ns)" and let the
|
|
254
|
+
// tokens stream. The worker's own 45s heartbeat lines (and ~~~ fences) are swallowed;
|
|
255
|
+
// this local 1-second ticker owns the progress display.
|
|
256
|
+
const QUIET_MS = 700; // stream idle this long ⇒ we're waiting on the model
|
|
257
|
+
let lastContentAt = Date.now();
|
|
258
|
+
let thinking = false;
|
|
259
|
+
let thinkingSince = 0;
|
|
260
|
+
let thinkWord = pickWord();
|
|
261
|
+
const thinkSecs = () => Math.max(1, Math.round((Date.now() - thinkingSince) / 1000));
|
|
233
262
|
|
|
234
|
-
// Erase the transient heartbeat status line (if any) before printing real output.
|
|
235
263
|
const clearStatus = () => {
|
|
236
|
-
if (statusShown) {
|
|
237
|
-
|
|
264
|
+
if (statusShown) { process.stdout.write(CLEAR_LINE); statusShown = false; }
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const tick = () => {
|
|
268
|
+
if (!following || followAborted || !jobRunning) return;
|
|
269
|
+
if (Date.now() - lastContentAt < QUIET_MS) return; // tokens still flowing
|
|
270
|
+
// Count from when the stream actually went quiet, so the seconds reflect the true
|
|
271
|
+
// wait for the first token (1s, 2s, 3s…) rather than from the first tick.
|
|
272
|
+
if (!thinking) { thinking = true; thinkingSince = lastContentAt; thinkWord = pickWord(); }
|
|
273
|
+
const secs = thinkSecs();
|
|
274
|
+
if (secs % 12 === 0) thinkWord = pickWord(); // rotate the word on long waits
|
|
275
|
+
process.stdout.write(CLEAR_LINE + yellow(`${thinkWord} thinking… (${secs}s)`));
|
|
276
|
+
statusShown = true;
|
|
277
|
+
};
|
|
278
|
+
const ticker = setInterval(tick, 1000);
|
|
279
|
+
|
|
280
|
+
// Close out a "thinking…" phase right before real output prints.
|
|
281
|
+
const onContent = () => {
|
|
282
|
+
if (thinking) {
|
|
283
|
+
process.stdout.write(CLEAR_LINE + dim(`✔ completed thought (${thinkSecs()}s)`) + "\n");
|
|
284
|
+
thinking = false;
|
|
238
285
|
statusShown = false;
|
|
286
|
+
} else {
|
|
287
|
+
clearStatus();
|
|
239
288
|
}
|
|
289
|
+
lastContentAt = Date.now();
|
|
240
290
|
};
|
|
241
|
-
|
|
242
|
-
//
|
|
291
|
+
|
|
292
|
+
// Split newly-arrived text into lines: heartbeats + ~~~ fences are swallowed (the ticker
|
|
293
|
+
// shows progress); blank lines keep spacing; everything else commits dim to scrollback.
|
|
243
294
|
const consume = (chunk) => {
|
|
244
295
|
carry += stripThinkTags(chunk);
|
|
245
296
|
let nl;
|
|
246
297
|
while ((nl = carry.indexOf("\n")) >= 0) {
|
|
247
298
|
const line = carry.slice(0, nl);
|
|
248
299
|
carry = carry.slice(nl + 1);
|
|
249
|
-
if (isHeartbeatLine(line))
|
|
250
|
-
|
|
251
|
-
process.stdout.write(
|
|
252
|
-
|
|
253
|
-
} else if (line.trim() === "") {
|
|
254
|
-
// Swallow the blank line the worker appends after a heartbeat; keep real spacing.
|
|
255
|
-
if (!statusShown) process.stdout.write("\n");
|
|
256
|
-
} else {
|
|
257
|
-
clearStatus();
|
|
258
|
-
process.stdout.write(dim(line) + "\n");
|
|
300
|
+
if (isHeartbeatLine(line) || isFenceLine(line)) continue; // swallow
|
|
301
|
+
if (line.trim() === "") {
|
|
302
|
+
if (!thinking && !statusShown) process.stdout.write("\n");
|
|
303
|
+
continue;
|
|
259
304
|
}
|
|
305
|
+
onContent();
|
|
306
|
+
process.stdout.write(dim(line) + "\n");
|
|
260
307
|
}
|
|
308
|
+
// A non-empty remainder is a partial CONTENT line (heartbeats always arrive whole with
|
|
309
|
+
// a newline), so tokens are actively flowing — keep the ticker asleep.
|
|
310
|
+
if (carry.trim() !== "") lastContentAt = Date.now();
|
|
261
311
|
};
|
|
312
|
+
|
|
262
313
|
try {
|
|
263
314
|
for (;;) {
|
|
264
315
|
if (followAborted) {
|
|
@@ -273,10 +324,11 @@ async function runAgentTurn(history) {
|
|
|
273
324
|
});
|
|
274
325
|
const job = await jr.json().catch(() => ({}));
|
|
275
326
|
if (!jr.ok) throw new Error(job?.error || `job poll failed (HTTP ${jr.status})`);
|
|
327
|
+
jobRunning = job.jobStatus === "running";
|
|
276
328
|
const text = typeof job.resultText === "string" ? job.resultText : "";
|
|
277
329
|
if (job.jobStatus === "completed") {
|
|
278
|
-
// Drop the transient
|
|
279
|
-
//
|
|
330
|
+
// Drop the transient status line and any partial reasoning tail; the bright answer
|
|
331
|
+
// prints right after via the caller.
|
|
280
332
|
clearStatus();
|
|
281
333
|
return answerFrom(text);
|
|
282
334
|
}
|
|
@@ -302,6 +354,7 @@ async function runAgentTurn(history) {
|
|
|
302
354
|
await sleep(700);
|
|
303
355
|
}
|
|
304
356
|
} finally {
|
|
357
|
+
clearInterval(ticker);
|
|
305
358
|
clearStatus();
|
|
306
359
|
following = false;
|
|
307
360
|
}
|
package/gonext_agent_chat.py
CHANGED
|
@@ -558,6 +558,96 @@ _AGENT_KEYWORDS = re.compile(
|
|
|
558
558
|
re.IGNORECASE,
|
|
559
559
|
)
|
|
560
560
|
|
|
561
|
+
# Pure conversational openers/closers that never need a tool — a greeting, a thank-you, a
|
|
562
|
+
# "who are you". Matching the WHOLE message (anchored) means "hi, fetch https://…" won't
|
|
563
|
+
# match. These skip BOTH the model router call AND the heavy tool preamble → an instant
|
|
564
|
+
# small-prompt plain reply, instead of a multi-minute prompt-eval on the coding model.
|
|
565
|
+
_TRIVIAL_CHAT = re.compile(
|
|
566
|
+
r"^\s*(?:"
|
|
567
|
+
r"h(?:i+|ello+|ey+|iya|owdy)|yo|sup|wass?up|"
|
|
568
|
+
r"good\s*(?:morning|afternoon|evening|night|day)|greetings|"
|
|
569
|
+
r"thanks?(?:\s*you)?|thank\s*you|thx|ty|cheers|much\s*appreciated|"
|
|
570
|
+
r"bye+|goodbye|see\s*(?:ya|you)|cya|later|good\s*night|"
|
|
571
|
+
r"ok(?:ay)?|k|cool|nice|great|awesome|perfect|sounds\s*good|got\s*it|"
|
|
572
|
+
r"how\s*(?:are|r)\s*(?:you|u|ya)(?:\s*doing)?|how'?s\s*it\s*going|what'?s\s*up|"
|
|
573
|
+
r"who\s*(?:are|r)\s*(?:you|u)|what\s*(?:can|do)\s*you\s*do|what\s*are\s*you|"
|
|
574
|
+
r"test(?:ing)?|ping"
|
|
575
|
+
r")"
|
|
576
|
+
r"(?:\s+(?:there|everyone|all|bot|assistant|gonext|man|dude|buddy))?"
|
|
577
|
+
r"[\s!.?,'\"]*$",
|
|
578
|
+
re.IGNORECASE,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _is_trivial_chat(text: str) -> bool:
|
|
583
|
+
"""True for a short, pure-conversational message (greeting, thanks, 'who are you')
|
|
584
|
+
that clearly needs no tools — so we can answer directly and fast."""
|
|
585
|
+
t = (text or "").strip()
|
|
586
|
+
if not t or len(t) > 60:
|
|
587
|
+
return False
|
|
588
|
+
return bool(_TRIVIAL_CHAT.match(t))
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
# Compact replacement for smolagents' default CodeAgent system prompt. The stock template
|
|
592
|
+
# is ~9.9k chars, dominated by ~6k chars of GENERIC few-shot examples (image captions,
|
|
593
|
+
# Wikipedia, etc.) that are irrelevant to our HTTP/file tools and cost real prompt-eval
|
|
594
|
+
# time every step on a slow coding model. This keeps everything FUNCTIONALLY required —
|
|
595
|
+
# the Thought→Code→Observation loop, the code-blob format + closing tag, the {{tools}}
|
|
596
|
+
# rendering (so exact signatures survive), authorized imports, custom_instructions — plus
|
|
597
|
+
# ONE short worked example, and trims the rest. Jinja vars must match the stock template.
|
|
598
|
+
_COMPACT_CODE_SYSTEM_PROMPT = (
|
|
599
|
+
"You are an expert assistant who solves the task by writing Python code that calls "
|
|
600
|
+
"tools. You work in a loop of Thought → Code → Observation.\n\n"
|
|
601
|
+
"At EACH step:\n"
|
|
602
|
+
"- Write one 'Thought:' line: what you'll do and which tool.\n"
|
|
603
|
+
"- Then a code block that OPENS with {{code_block_opening_tag}} and CLOSES with "
|
|
604
|
+
"{{code_block_closing_tag}}, containing simple Python that calls ONE tool and print()s "
|
|
605
|
+
"anything you need next.\n"
|
|
606
|
+
"- You then receive that tool's 'Observation:'. Use it to decide the next step. When "
|
|
607
|
+
"you have the answer, call final_answer(answer) inside a code block.\n\n"
|
|
608
|
+
"Example:\n"
|
|
609
|
+
"Thought: I'll read the page the user gave.\n"
|
|
610
|
+
"{{code_block_opening_tag}}\n"
|
|
611
|
+
'text = fetch_url("https://example.com")\n'
|
|
612
|
+
"print(text)\n"
|
|
613
|
+
"{{code_block_closing_tag}}\n"
|
|
614
|
+
"Observation: \"Example Domain … \"\n"
|
|
615
|
+
"Thought: I have the content, so I'll answer.\n"
|
|
616
|
+
"{{code_block_opening_tag}}\n"
|
|
617
|
+
'final_answer("The page is Example Domain, a placeholder site.")\n'
|
|
618
|
+
"{{code_block_closing_tag}}\n\n"
|
|
619
|
+
"You have access to these tools — call them as plain Python functions with the exact "
|
|
620
|
+
"signatures shown:\n"
|
|
621
|
+
"{{code_block_opening_tag}}\n"
|
|
622
|
+
"{%- for tool in tools.values() %}\n"
|
|
623
|
+
"{{ tool.to_code_prompt() }}\n"
|
|
624
|
+
"{% endfor %}\n"
|
|
625
|
+
"{{code_block_closing_tag}}\n"
|
|
626
|
+
"{%- if managed_agents and managed_agents.values() | list %}\n"
|
|
627
|
+
"You can also delegate to team members by calling them like a tool with a 'task' "
|
|
628
|
+
"string argument:\n"
|
|
629
|
+
"{{code_block_opening_tag}}\n"
|
|
630
|
+
"{%- for agent in managed_agents.values() %}\n"
|
|
631
|
+
"def {{ agent.name }}(task: str) -> str:\n"
|
|
632
|
+
' """{{ agent.description }}"""\n'
|
|
633
|
+
"{% endfor %}\n"
|
|
634
|
+
"{{code_block_closing_tag}}\n"
|
|
635
|
+
"{%- endif %}\n\n"
|
|
636
|
+
"Rules:\n"
|
|
637
|
+
"1. ALWAYS write a 'Thought:' line, then a code block opening with "
|
|
638
|
+
"{{code_block_opening_tag}} and closing with {{code_block_closing_tag}} — or you fail.\n"
|
|
639
|
+
"2. Pass tool arguments directly: calculate(expression=\"2+2\"), NOT as a dict.\n"
|
|
640
|
+
"3. Use only variables you have defined; state persists between steps.\n"
|
|
641
|
+
"4. Never re-run a tool call with the exact same parameters.\n"
|
|
642
|
+
"5. Don't name a variable after a tool (e.g. 'final_answer').\n"
|
|
643
|
+
"6. Imports are allowed ONLY from: {{authorized_imports}}\n"
|
|
644
|
+
"7. Don't give up — you are in charge of solving the task.\n"
|
|
645
|
+
"{%- if custom_instructions %}\n"
|
|
646
|
+
"{{custom_instructions}}\n"
|
|
647
|
+
"{%- endif %}\n\n"
|
|
648
|
+
"Now Begin!"
|
|
649
|
+
)
|
|
650
|
+
|
|
561
651
|
|
|
562
652
|
def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
|
|
563
653
|
"""Decide if the task needs the HTTP agent (True) or a plain chat reply (False).
|
|
@@ -603,6 +693,16 @@ def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
|
|
|
603
693
|
_emit({"type": "step", "text": "→ Agent mode (needs tools)" if is_agent else "→ Chat reply"})
|
|
604
694
|
return is_agent
|
|
605
695
|
except Exception as e: # noqa: BLE001
|
|
696
|
+
# Classifier unreachable (e.g. the local chat server is down). No agent keyword
|
|
697
|
+
# matched above, so a SHORT message is almost certainly plain conversation — send
|
|
698
|
+
# it to a fast small-prompt reply instead of the heavy tool loop (which would
|
|
699
|
+
# prompt-eval a huge preamble for minutes). Only longer/ambiguous tasks fall back
|
|
700
|
+
# to the agent.
|
|
701
|
+
short = len((task_text or "").split()) <= 12
|
|
702
|
+
if short:
|
|
703
|
+
_log(f"router error: {e} — short message, defaulting to plain chat reply")
|
|
704
|
+
_emit({"type": "step", "text": "→ Chat reply"})
|
|
705
|
+
return False
|
|
606
706
|
_log(f"router error: {e} — defaulting to agent")
|
|
607
707
|
_emit({"type": "step", "text": "→ Agent mode (needs tools)"})
|
|
608
708
|
return True
|
|
@@ -641,8 +741,12 @@ def _summarize_result(task_text: str, agent_output: str,
|
|
|
641
741
|
return agent_output
|
|
642
742
|
|
|
643
743
|
|
|
644
|
-
def _plain_reply(messages: list, base_url: str, api_key: str, model_id: str
|
|
645
|
-
|
|
744
|
+
def _plain_reply(messages: list, base_url: str, api_key: str, model_id: str,
|
|
745
|
+
fallback_base_url: str = "", fallback_model_id: str = "") -> str:
|
|
746
|
+
"""Plain chat completion using the full conversation history — a SMALL prompt (no tool
|
|
747
|
+
preamble), so it returns fast. If the primary model is unreachable (e.g. the local MLX
|
|
748
|
+
server is down) and a distinct fallback model is given (the coding model), retry there
|
|
749
|
+
so a greeting still gets answered instead of erroring."""
|
|
646
750
|
_THINK_RE_LOCAL = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
|
|
647
751
|
chat_messages = [{"role": "system", "content": "You are a helpful assistant."}]
|
|
648
752
|
for m in messages:
|
|
@@ -655,19 +759,33 @@ def _plain_reply(messages: list, base_url: str, api_key: str, model_id: str) ->
|
|
|
655
759
|
if not content:
|
|
656
760
|
continue
|
|
657
761
|
chat_messages.append({"role": role, "content": content})
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
762
|
+
|
|
763
|
+
targets = [(base_url, model_id)]
|
|
764
|
+
if fallback_base_url and fallback_model_id and (
|
|
765
|
+
fallback_base_url.rstrip("/") != (base_url or "").rstrip("/")
|
|
766
|
+
or fallback_model_id != model_id
|
|
767
|
+
):
|
|
768
|
+
targets.append((fallback_base_url, fallback_model_id))
|
|
769
|
+
|
|
770
|
+
last_err = None
|
|
771
|
+
for i, (b, mid) in enumerate(targets):
|
|
772
|
+
try:
|
|
773
|
+
from openai import OpenAI
|
|
774
|
+
client = OpenAI(base_url=b, api_key=api_key or "local",
|
|
775
|
+
max_retries=0, timeout=60)
|
|
776
|
+
resp = client.chat.completions.create(
|
|
777
|
+
model=mid,
|
|
778
|
+
messages=chat_messages,
|
|
779
|
+
temperature=0.7,
|
|
780
|
+
max_tokens=512,
|
|
781
|
+
)
|
|
782
|
+
return (resp.choices[0].message.content or "").strip()
|
|
783
|
+
except Exception as e: # noqa: BLE001
|
|
784
|
+
last_err = e
|
|
785
|
+
if i + 1 < len(targets):
|
|
786
|
+
_log(f"plain reply: primary model failed ({e}); retrying on fallback "
|
|
787
|
+
f"{mid} @ {b}")
|
|
788
|
+
return f"[Error: {last_err}]"
|
|
671
789
|
|
|
672
790
|
|
|
673
791
|
def _synthesize_document(gathered: list, task: str, base_url: str, api_key: str,
|
|
@@ -1903,8 +2021,16 @@ def run_agent_chat(cfg):
|
|
|
1903
2021
|
return bool(_EMAIL_AVAILABLE and _EMAIL_CONFIRM.search(latest_user_text or "")
|
|
1904
2022
|
and _prior_email_preview())
|
|
1905
2023
|
|
|
1906
|
-
# Route:
|
|
1907
|
-
|
|
2024
|
+
# Route: does this task need HTTP tool use? A trivial greeting/thank-you/"who are you"
|
|
2025
|
+
# is caught LOCALLY (no model classifier call, no tool preamble) → straight to a fast
|
|
2026
|
+
# small-prompt reply. Otherwise ask the model classifier.
|
|
2027
|
+
if _is_trivial_chat(latest_user_text) and not _AGENT_KEYWORDS.search(latest_user_text or ""):
|
|
2028
|
+
_log("router → NO (trivial greeting/smalltalk — local fast-path, no tool preamble)")
|
|
2029
|
+
_emit({"type": "step", "text": "Routing your request…"})
|
|
2030
|
+
_emit({"type": "step", "text": "→ Chat reply"})
|
|
2031
|
+
needs_agent = False
|
|
2032
|
+
else:
|
|
2033
|
+
needs_agent = _route(latest_user_text, agent_base_url, agent_api_key, agent_model_id)
|
|
1908
2034
|
# A pending email awaiting 'confirm' must reach the agent even though a bare
|
|
1909
2035
|
# 'confirm' / 'send it' is not a network keyword — otherwise the send never fires.
|
|
1910
2036
|
if not needs_agent and _email_confirm_pending():
|
|
@@ -1915,7 +2041,9 @@ def run_agent_chat(cfg):
|
|
|
1915
2041
|
if not needs_agent:
|
|
1916
2042
|
_log("router: plain chat (no HTTP needed)")
|
|
1917
2043
|
_emit({"type": "step", "text": "Composing a reply…"})
|
|
1918
|
-
|
|
2044
|
+
# Small prompt → fast. Fall back to the coding model if the chat model is down.
|
|
2045
|
+
answer = _plain_reply(messages, agent_base_url, agent_api_key, agent_model_id,
|
|
2046
|
+
coding_base_url, coding_model_id)
|
|
1919
2047
|
_log(f"plain reply: {len(answer)} chars")
|
|
1920
2048
|
_emit({"type": "final", "text": answer})
|
|
1921
2049
|
return
|
|
@@ -3478,6 +3606,21 @@ def run_agent_chat(cfg):
|
|
|
3478
3606
|
agent_kwargs["additional_authorized_imports"] = [
|
|
3479
3607
|
"json", "base64", "urllib", "urllib.request", "urllib.error"
|
|
3480
3608
|
]
|
|
3609
|
+
# Trim smolagents' ~6k-char generic few-shot examples from the system prompt
|
|
3610
|
+
# (they dominate prompt-eval on a slow coding model) while keeping the tool
|
|
3611
|
+
# rendering + format rules intact. Load the stock templates and swap only the
|
|
3612
|
+
# system_prompt; fall back to the default on any error.
|
|
3613
|
+
try:
|
|
3614
|
+
import yaml as _yaml
|
|
3615
|
+
import os as _os
|
|
3616
|
+
import smolagents as _sm
|
|
3617
|
+
_tpl = _yaml.safe_load(open(_os.path.join(
|
|
3618
|
+
_os.path.dirname(_sm.__file__), "prompts", "code_agent.yaml")))
|
|
3619
|
+
_tpl["system_prompt"] = _COMPACT_CODE_SYSTEM_PROMPT
|
|
3620
|
+
agent_kwargs["prompt_templates"] = _tpl
|
|
3621
|
+
_log("using compact CodeAgent system prompt (trimmed few-shot examples)")
|
|
3622
|
+
except Exception as _e: # noqa: BLE001
|
|
3623
|
+
_log(f"compact system prompt unavailable ({_e}); using smolagents default")
|
|
3481
3624
|
agent = _ToolAgent(**agent_kwargs)
|
|
3482
3625
|
with contextlib.redirect_stdout(sys.stderr):
|
|
3483
3626
|
result = agent.run(task_with_hint)
|
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.182",
|
|
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",
|