@tiens.nguyen/gonext-local-worker 1.0.237 → 1.0.238
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 +77 -4
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -1392,8 +1392,14 @@ def _format_text_for_pdf(text: str, title: str, base_url: str, api_key: str,
|
|
|
1392
1392
|
fallback = f"# {title}\n\n{fallback}"
|
|
1393
1393
|
try:
|
|
1394
1394
|
from openai import OpenAI
|
|
1395
|
+
# timeout=20, NOT 60 (task #74): this formatting pass runs INSIDE a create_pdf
|
|
1396
|
+
# tool call, which itself runs under the agent's python-executor wall clock. A
|
|
1397
|
+
# 60s wait here could eat the whole step budget — the PDF then rendered+uploaded
|
|
1398
|
+
# fine but the model was told the step timed out, so it re-created the PDF from
|
|
1399
|
+
# scratch (3 duplicate PDFs, ~10 wasted minutes). Formatting is optional polish:
|
|
1400
|
+
# give it 20s, then fall back to the raw text and leave room for render+upload.
|
|
1395
1401
|
client = OpenAI(base_url=base_url, api_key=api_key or "local",
|
|
1396
|
-
max_retries=0, timeout=
|
|
1402
|
+
max_retries=0, timeout=20)
|
|
1397
1403
|
system_content = (
|
|
1398
1404
|
"You format raw text/data into clean Markdown for a PDF document. "
|
|
1399
1405
|
"Add a single top-level '# Title', sensible headings, bullet lists, "
|
|
@@ -2961,8 +2967,15 @@ def run_agent_chat(cfg):
|
|
|
2961
2967
|
)
|
|
2962
2968
|
|
|
2963
2969
|
_emit({"type": "step", "text": "Formatting document…"})
|
|
2970
|
+
# CHAT model, not the coder (task #74 review): formatting raw text into
|
|
2971
|
+
# Markdown is a language task, and the chat model (local MLX) answers in
|
|
2972
|
+
# seconds where the remote coder can take minutes to first token — inside
|
|
2973
|
+
# the 20s format timeout that difference is "formatted" vs "always falls
|
|
2974
|
+
# back to raw text". Same split as routing/plain replies/summarization.
|
|
2964
2975
|
markdown_text = _format_text_for_pdf(
|
|
2965
|
-
doc_text, doc_title,
|
|
2976
|
+
doc_text, doc_title,
|
|
2977
|
+
agent_base_url or coding_base_url, agent_api_key,
|
|
2978
|
+
agent_model_id or coding_model_id,
|
|
2966
2979
|
instruction=edit_instruction,
|
|
2967
2980
|
)
|
|
2968
2981
|
|
|
@@ -3223,6 +3236,8 @@ def run_agent_chat(cfg):
|
|
|
3223
3236
|
f"- RESEARCH BUDGET — you may make at most {research_budget} web_search/fetch_url calls "
|
|
3224
3237
|
"TOTAL per task (rewording a query still counts). Reserve your remaining steps for "
|
|
3225
3238
|
"create_pdf / final_answer.\n"
|
|
3239
|
+
"- If an earlier Observation already says '✅ Your PDF … is ready', the PDF EXISTS — "
|
|
3240
|
+
"do NOT call create_pdf again. Pass that download link to final_answer.\n"
|
|
3226
3241
|
"- Do NOT put final_answer outside the code block.\n\n"
|
|
3227
3242
|
)
|
|
3228
3243
|
# Recover an interrupted prior turn's step trace, if one survives on disk (crash/
|
|
@@ -3595,6 +3610,22 @@ def run_agent_chat(cfg):
|
|
|
3595
3610
|
_last_obs["text"] = result
|
|
3596
3611
|
return result
|
|
3597
3612
|
|
|
3613
|
+
# Task #74 retry guard. When a create_pdf call outlives the executor's wall clock,
|
|
3614
|
+
# the tool still finishes (render + S3 upload succeed, "PDF ready" is emitted) but
|
|
3615
|
+
# the model is told the step FAILED — so it calls create_pdf again for the same
|
|
3616
|
+
# document. Remember the last success here; `timeout_pending` is flipped by
|
|
3617
|
+
# step_callback whenever a step's result was an executor-timeout error, meaning
|
|
3618
|
+
# whatever succeeded during that step was never reported to the model.
|
|
3619
|
+
_pdf_state: dict = {"last": None, "timeout_pending": False}
|
|
3620
|
+
|
|
3621
|
+
def _pdf_sig(title: str, text: str) -> str:
|
|
3622
|
+
return (title.strip().lower() + "||"
|
|
3623
|
+
+ re.sub(r"\s+", " ", (text or "").strip().lower())[:1500])
|
|
3624
|
+
|
|
3625
|
+
def _pdf_titles_similar(a: str, b: str) -> bool:
|
|
3626
|
+
a, b = a.strip().lower(), b.strip().lower()
|
|
3627
|
+
return bool(a and b) and (a == b or a in b or b in a)
|
|
3628
|
+
|
|
3598
3629
|
@tool
|
|
3599
3630
|
def create_pdf(text: str, title: str = "") -> str:
|
|
3600
3631
|
"""Create a PDF document from text/data and return a download link.
|
|
@@ -3608,10 +3639,28 @@ def run_agent_chat(cfg):
|
|
|
3608
3639
|
title: Optional document title shown at the top and used in the file name.
|
|
3609
3640
|
"""
|
|
3610
3641
|
doc_title = (title or "").strip() or "Document"
|
|
3642
|
+
# Retry guard (task #74): identical content is ALWAYS a retry; a merely similar
|
|
3643
|
+
# title counts only when the previous success was swallowed by an executor
|
|
3644
|
+
# timeout (the model rewords/shortens on retry, so the text rarely matches).
|
|
3645
|
+
prev = _pdf_state.get("last")
|
|
3646
|
+
if prev and (
|
|
3647
|
+
prev["sig"] == _pdf_sig(doc_title, text)
|
|
3648
|
+
or (_pdf_state.get("timeout_pending")
|
|
3649
|
+
and _pdf_titles_similar(prev["title"], doc_title))
|
|
3650
|
+
):
|
|
3651
|
+
_log(f"create_pdf retry detected (prev title={prev['title']!r}) "
|
|
3652
|
+
"→ returning the existing PDF instead of re-rendering")
|
|
3653
|
+
_emit({"type": "step",
|
|
3654
|
+
"text": f"PDF already created → {prev['title']}.pdf (reusing link)"})
|
|
3655
|
+
_last_obs["text"] = prev["msg"]
|
|
3656
|
+
return prev["msg"]
|
|
3611
3657
|
# 1) Format the raw text into clean Markdown (extra model call, intentional).
|
|
3658
|
+
# CHAT model, not the coder — see the fast-path call site for why (task #74).
|
|
3612
3659
|
_emit({"type": "step", "text": "Formatting document…"})
|
|
3613
3660
|
markdown_text = _format_text_for_pdf(
|
|
3614
|
-
text or "", doc_title,
|
|
3661
|
+
text or "", doc_title,
|
|
3662
|
+
agent_base_url or coding_base_url, agent_api_key,
|
|
3663
|
+
agent_model_id or coding_model_id,
|
|
3615
3664
|
)
|
|
3616
3665
|
|
|
3617
3666
|
# 2) Render the Markdown to PDF bytes locally (pure-Python xhtml2pdf).
|
|
@@ -3655,6 +3704,9 @@ def run_agent_chat(cfg):
|
|
|
3655
3704
|
)
|
|
3656
3705
|
_emit({"type": "step", "text": f"PDF ready → {doc_title}.pdf"})
|
|
3657
3706
|
_log(f"create_pdf ok title={doc_title!r} bytes={len(pdf_bytes)}")
|
|
3707
|
+
_pdf_state["last"] = {
|
|
3708
|
+
"sig": _pdf_sig(doc_title, text), "title": doc_title, "msg": out,
|
|
3709
|
+
}
|
|
3658
3710
|
_last_obs["text"] = out
|
|
3659
3711
|
return out
|
|
3660
3712
|
|
|
@@ -3670,6 +3722,19 @@ def run_agent_chat(cfg):
|
|
|
3670
3722
|
def step_callback(step_log):
|
|
3671
3723
|
step_num = getattr(step_log, "step_number", "?")
|
|
3672
3724
|
|
|
3725
|
+
# Task #74: an executor-timeout error means whatever the step's tool call
|
|
3726
|
+
# achieved was NEVER reported to the model (a create_pdf may have finished —
|
|
3727
|
+
# or may still be finishing — in the background). Flag it so a repeat
|
|
3728
|
+
# create_pdf with a similar title is treated as the retry it is; any step
|
|
3729
|
+
# that ends with a real (non-timeout) result clears the flag.
|
|
3730
|
+
try:
|
|
3731
|
+
_err = getattr(step_log, "error", None)
|
|
3732
|
+
_pdf_state["timeout_pending"] = bool(
|
|
3733
|
+
_err and "maximum execution time" in str(_err)
|
|
3734
|
+
)
|
|
3735
|
+
except Exception: # noqa: BLE001
|
|
3736
|
+
pass
|
|
3737
|
+
|
|
3673
3738
|
# (B) Trim OLD, LARGE tool observations out of the CodeAgent's step memory so
|
|
3674
3739
|
# they aren't re-sent to the model on every subsequent step (the #63 O(n²) token
|
|
3675
3740
|
# blow-up: an uncapped read_file_lines dump cost ~5-6k tokens PER remaining step).
|
|
@@ -5122,7 +5187,15 @@ def run_agent_chat(cfg):
|
|
|
5122
5187
|
)
|
|
5123
5188
|
if _AgentBase is CodeAgent:
|
|
5124
5189
|
# Only the CodeAgent runs a Python executor; ToolCallingAgent takes neither.
|
|
5125
|
-
|
|
5190
|
+
# 660s, NOT 60 (task #74): the wall clock must exceed the LARGEST tool-internal
|
|
5191
|
+
# timeout (run_command caps at 600s; create_pdf's format+render+upload can pass
|
|
5192
|
+
# 60s), otherwise a tool that eventually SUCCEEDS gets reported to the model as
|
|
5193
|
+
# 'Code execution exceeded the maximum execution time' — the model then retries
|
|
5194
|
+
# work that already finished (the duplicate-PDF bug). Every tool enforces its
|
|
5195
|
+
# own tighter timeout and returns a clean message; runaway pure-python is still
|
|
5196
|
+
# caught by smolagents' op-count caps (MAX_OPERATIONS/MAX_WHILE_ITERATIONS),
|
|
5197
|
+
# so this wall clock is a last-resort backstop, not the primary guard.
|
|
5198
|
+
agent_kwargs["executor_kwargs"] = {"timeout_seconds": 660}
|
|
5126
5199
|
agent_kwargs["additional_authorized_imports"] = [
|
|
5127
5200
|
"json", "base64", "urllib", "urllib.request", "urllib.error"
|
|
5128
5201
|
]
|
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.238",
|
|
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",
|