@tiens.nguyen/gonext-local-worker 1.0.54 → 1.0.56
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-local-worker.mjs +4 -2
- package/gonext_agent_chat.py +92 -36
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1237,7 +1237,7 @@ async function runAgentChatJob(job) {
|
|
|
1237
1237
|
agentApiKey: payload?.agentApiKey ?? "",
|
|
1238
1238
|
agentModelId: payload?.agentModelId ?? "",
|
|
1239
1239
|
tools: payload?.tools ?? ["http_request"],
|
|
1240
|
-
maxSteps: payload?.maxSteps ??
|
|
1240
|
+
maxSteps: payload?.maxSteps ?? 5,
|
|
1241
1241
|
});
|
|
1242
1242
|
const timeoutMs = 300_000; // 5 min max for an agent run
|
|
1243
1243
|
|
|
@@ -1245,7 +1245,9 @@ async function runAgentChatJob(job) {
|
|
|
1245
1245
|
let finalText = "";
|
|
1246
1246
|
|
|
1247
1247
|
await runProcessWithStreamingStdout(python, [scriptPath], input, timeoutMs, (event) => {
|
|
1248
|
-
if (event.type === "
|
|
1248
|
+
if (event.type === "log" && typeof event.text === "string") {
|
|
1249
|
+
console.log(`[gonext-agent] ${event.text}`);
|
|
1250
|
+
} else if (event.type === "step" && typeof event.text === "string") {
|
|
1249
1251
|
if (!inThink) {
|
|
1250
1252
|
inThink = true;
|
|
1251
1253
|
enqueueText("<think>");
|
package/gonext_agent_chat.py
CHANGED
|
@@ -12,17 +12,14 @@ Reads on stdin:
|
|
|
12
12
|
"maxSteps": int # default 10
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
Emits NDJSON lines on stdout
|
|
16
|
-
{"type": "
|
|
17
|
-
{"type": "
|
|
18
|
-
|
|
19
|
-
All smolagents/rich console output goes to stderr so stdout stays clean.
|
|
20
|
-
|
|
21
|
-
TLS: uses certifi CA bundle when available (macOS may lack system certs for
|
|
22
|
-
Python urllib), with fallback to the default bundle.
|
|
15
|
+
Emits NDJSON lines on stdout:
|
|
16
|
+
{"type": "log", "text": "..."} — worker logs to console, not shown in chat
|
|
17
|
+
{"type": "step", "text": "..."} — shown in <think> area
|
|
18
|
+
{"type": "final", "text": "..."} — assistant answer
|
|
23
19
|
"""
|
|
24
20
|
import contextlib
|
|
25
21
|
import json
|
|
22
|
+
import re
|
|
26
23
|
import sys
|
|
27
24
|
import urllib.request
|
|
28
25
|
import urllib.error
|
|
@@ -63,6 +60,61 @@ def _http_request_impl(method, url, headers=None, body=None, timeout=20):
|
|
|
63
60
|
return f"Error: {e}"
|
|
64
61
|
|
|
65
62
|
|
|
63
|
+
def _summarise_step(step_log):
|
|
64
|
+
"""Return a short human-readable description of an agent step."""
|
|
65
|
+
tool_calls = getattr(step_log, "tool_calls", None) or []
|
|
66
|
+
observations = getattr(step_log, "observations", None)
|
|
67
|
+
error = getattr(step_log, "error", None)
|
|
68
|
+
step_num = getattr(step_log, "step_number", None)
|
|
69
|
+
|
|
70
|
+
parts = []
|
|
71
|
+
for tc in tool_calls:
|
|
72
|
+
name = getattr(tc, "name", "")
|
|
73
|
+
args = getattr(tc, "arguments", None)
|
|
74
|
+
|
|
75
|
+
if name == "python_interpreter" and isinstance(args, dict):
|
|
76
|
+
code = args.get("code", "")
|
|
77
|
+
# Show the http_request call if present, else first meaningful line
|
|
78
|
+
m = re.search(r'http_request\s*\(\s*(?:method\s*=\s*)?[\'"]?(\w+)[\'"]?\s*,\s*(?:url\s*=\s*)?[\'"]([^\'"]+)', code)
|
|
79
|
+
if m:
|
|
80
|
+
parts.append(f"HTTP {m.group(1).upper()} {m.group(2)}")
|
|
81
|
+
else:
|
|
82
|
+
first = next(
|
|
83
|
+
(l.strip() for l in code.splitlines()
|
|
84
|
+
if l.strip() and not l.strip().startswith("#")),
|
|
85
|
+
code[:80],
|
|
86
|
+
)
|
|
87
|
+
parts.append(first[:120])
|
|
88
|
+
else:
|
|
89
|
+
if isinstance(args, dict):
|
|
90
|
+
method = args.get("method", "")
|
|
91
|
+
url = args.get("url", "")
|
|
92
|
+
if method and url:
|
|
93
|
+
parts.append(f"HTTP {method.upper()} {url}")
|
|
94
|
+
else:
|
|
95
|
+
parts.append(f"{name}()")
|
|
96
|
+
else:
|
|
97
|
+
parts.append(name or "tool call")
|
|
98
|
+
|
|
99
|
+
if observations:
|
|
100
|
+
obs = str(observations).strip()
|
|
101
|
+
# smolagents prefixes output with "Execution logs:" — skip that header line
|
|
102
|
+
# and show the first line of actual content.
|
|
103
|
+
lines = [l for l in obs.splitlines() if l.strip() and l.strip() != "Execution logs:"]
|
|
104
|
+
if lines:
|
|
105
|
+
parts.append(f"→ {lines[0][:200]}")
|
|
106
|
+
|
|
107
|
+
if error:
|
|
108
|
+
err = str(error)
|
|
109
|
+
if "Import of" in err and "not allowed" in err:
|
|
110
|
+
parts.append("→ (import blocked — using http_request tool instead)")
|
|
111
|
+
else:
|
|
112
|
+
parts.append(f"→ Error: {err[:120]}")
|
|
113
|
+
|
|
114
|
+
label = f"Step {step_num}: " if step_num is not None else ""
|
|
115
|
+
return label + (" | ".join(parts) if parts else "thinking…")
|
|
116
|
+
|
|
117
|
+
|
|
66
118
|
def run_agent_chat(cfg):
|
|
67
119
|
try:
|
|
68
120
|
from smolagents import CodeAgent, OpenAIServerModel, tool
|
|
@@ -74,9 +126,11 @@ def run_agent_chat(cfg):
|
|
|
74
126
|
agent_base_url = cfg.get("agentBaseURL") or ""
|
|
75
127
|
agent_api_key = cfg.get("agentApiKey") or "local"
|
|
76
128
|
agent_model_id = cfg.get("agentModelId") or ""
|
|
77
|
-
max_steps = int(cfg.get("maxSteps") or
|
|
129
|
+
max_steps = int(cfg.get("maxSteps") or 5)
|
|
130
|
+
|
|
131
|
+
_log(f"start model={agent_model_id!r} base={agent_base_url!r} maxSteps={max_steps}")
|
|
78
132
|
|
|
79
|
-
# Build
|
|
133
|
+
# Build task from the last user message; prepend prior assistant turns as context.
|
|
80
134
|
task_text = ""
|
|
81
135
|
context_lines = []
|
|
82
136
|
for m in messages:
|
|
@@ -94,9 +148,22 @@ def run_agent_chat(cfg):
|
|
|
94
148
|
if context_lines:
|
|
95
149
|
task_text = "\n".join(context_lines) + "\n\nNow answer: " + task_text
|
|
96
150
|
|
|
151
|
+
# Prepend explicit tool instructions so small models use http_request correctly
|
|
152
|
+
# and always terminate with final_answer() rather than looping forever.
|
|
153
|
+
tool_hint = (
|
|
154
|
+
"You have ONE built-in function: `http_request(method, url, headers='', body='')`. "
|
|
155
|
+
"Do NOT import requests, urllib, or any library — call http_request() directly.\n"
|
|
156
|
+
"When you have the answer, ALWAYS end with `final_answer(your_answer)` — this stops the agent.\n"
|
|
157
|
+
"Example:\n"
|
|
158
|
+
"result = http_request(method='GET', url='https://example.com')\n"
|
|
159
|
+
"final_answer(result)\n\n"
|
|
160
|
+
)
|
|
161
|
+
task_with_hint = tool_hint + "Task: " + task_text
|
|
162
|
+
_log(f"task={task_text[:120]!r}")
|
|
163
|
+
|
|
97
164
|
@tool
|
|
98
165
|
def http_request(method: str, url: str, headers: str = "", body: str = "") -> str:
|
|
99
|
-
"""Perform an HTTP request and return the status code and
|
|
166
|
+
"""Perform an HTTP request and return the status code and body preview.
|
|
100
167
|
|
|
101
168
|
Args:
|
|
102
169
|
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
@@ -110,30 +177,16 @@ def run_agent_chat(cfg):
|
|
|
110
177
|
parsed_headers = json.loads(headers)
|
|
111
178
|
except Exception: # noqa: BLE001
|
|
112
179
|
pass
|
|
113
|
-
|
|
180
|
+
result = _http_request_impl(method, url, parsed_headers, body or None)
|
|
181
|
+
_log(f"http_request {method.upper()} {url} → {result[:80]}")
|
|
182
|
+
return result
|
|
114
183
|
|
|
115
184
|
def step_callback(step_log):
|
|
116
185
|
try:
|
|
117
|
-
|
|
118
|
-
# tool_calls is a list of ToolCall(name, arguments, id)
|
|
119
|
-
tool_calls = getattr(step_log, "tool_calls", None) or []
|
|
120
|
-
for tc in tool_calls:
|
|
121
|
-
name = getattr(tc, "name", str(tc))
|
|
122
|
-
args = getattr(tc, "arguments", None)
|
|
123
|
-
arg_str = json.dumps(args) if args is not None else ""
|
|
124
|
-
parts.append(f"Tool: {name}({arg_str})")
|
|
125
|
-
observations = getattr(step_log, "observations", None)
|
|
126
|
-
if observations:
|
|
127
|
-
parts.append(f"→ {str(observations)[:400]}")
|
|
128
|
-
error = getattr(step_log, "error", None)
|
|
129
|
-
if error:
|
|
130
|
-
parts.append(f"Error: {error}")
|
|
131
|
-
if not parts:
|
|
132
|
-
parts.append(str(step_log)[:300])
|
|
133
|
-
text = " ".join(parts)
|
|
186
|
+
text = _summarise_step(step_log)
|
|
134
187
|
except Exception as e: # noqa: BLE001
|
|
135
188
|
text = f"Step: {e}"
|
|
136
|
-
|
|
189
|
+
_log(f"step: {text[:200]}")
|
|
137
190
|
_emit({"type": "step", "text": text})
|
|
138
191
|
|
|
139
192
|
try:
|
|
@@ -150,21 +203,24 @@ def run_agent_chat(cfg):
|
|
|
150
203
|
executor_kwargs={"timeout_seconds": 60},
|
|
151
204
|
additional_authorized_imports=["json", "urllib", "urllib.request", "urllib.error"],
|
|
152
205
|
)
|
|
153
|
-
# Emit before agent.run() so the web no-progress timer resets while the
|
|
154
|
-
# model loads its weights and generates its first reasoning step.
|
|
155
206
|
_emit({"type": "step", "text": f"Sending task to {agent_model_id}…"})
|
|
156
207
|
with contextlib.redirect_stdout(sys.stderr):
|
|
157
|
-
result = agent.run(
|
|
208
|
+
result = agent.run(task_with_hint)
|
|
158
209
|
final_text = str(result).strip()
|
|
210
|
+
_log(f"done: {len(final_text)} chars")
|
|
159
211
|
_emit({"type": "final", "text": final_text})
|
|
160
212
|
except Exception as e: # noqa: BLE001
|
|
213
|
+
_log(f"agent error: {e}")
|
|
161
214
|
_emit({"type": "final", "text": f"[Agent error: {e}]"})
|
|
162
215
|
|
|
163
216
|
|
|
217
|
+
def _log(text: str):
|
|
218
|
+
"""Emit a log event — worker prints it to console, not forwarded to chat."""
|
|
219
|
+
_emit({"type": "log", "text": text})
|
|
220
|
+
|
|
221
|
+
|
|
164
222
|
def _emit(obj):
|
|
165
|
-
"""Write one NDJSON line to the real stdout and flush immediately.
|
|
166
|
-
Uses _REAL_STDOUT (captured at import time) so redirect_stdout blocks
|
|
167
|
-
inside agent.run() don't accidentally route events to stderr."""
|
|
223
|
+
"""Write one NDJSON line to the real stdout and flush immediately."""
|
|
168
224
|
_REAL_STDOUT.write(json.dumps(obj) + "\n")
|
|
169
225
|
_REAL_STDOUT.flush()
|
|
170
226
|
|
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.56",
|
|
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",
|