@tiens.nguyen/gonext-local-worker 1.0.60 → 1.0.62
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 -25
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -32,18 +32,20 @@ _REAL_STDOUT = sys.stdout
|
|
|
32
32
|
|
|
33
33
|
def _ssl_context():
|
|
34
34
|
import ssl
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
# Disable cert verification — this agent runs locally against dev tunnels
|
|
36
|
+
# (gorok, ngrok) whose certs may not chain correctly in Python's SSL store.
|
|
37
|
+
ctx = ssl.create_default_context()
|
|
38
|
+
ctx.check_hostname = False
|
|
39
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
40
|
+
return ctx
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
def _http_request_impl(method, url, headers=None, body=None, timeout=25):
|
|
43
|
-
|
|
44
|
+
# Merge caller headers on top of sensible defaults.
|
|
45
|
+
merged = {"User-Agent": "gonext-agent/1.0", "Accept": "*/*"}
|
|
44
46
|
if headers:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
merged.update(headers)
|
|
48
|
+
req = urllib.request.Request(url, method=method.upper(), headers=merged)
|
|
47
49
|
data = body.encode() if isinstance(body, str) and body else (body or None)
|
|
48
50
|
try:
|
|
49
51
|
ctx = _ssl_context()
|
|
@@ -115,6 +117,55 @@ def _summarise_step(step_log):
|
|
|
115
117
|
return label + (" | ".join(parts) if parts else "thinking…")
|
|
116
118
|
|
|
117
119
|
|
|
120
|
+
def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
|
|
121
|
+
"""Ask the local model if this task needs an HTTP request.
|
|
122
|
+
Returns True → run full agent; False → plain chat reply."""
|
|
123
|
+
try:
|
|
124
|
+
from openai import OpenAI
|
|
125
|
+
client = OpenAI(base_url=base_url, api_key=api_key or "local",
|
|
126
|
+
max_retries=0, timeout=20)
|
|
127
|
+
resp = client.chat.completions.create(
|
|
128
|
+
model=model_id,
|
|
129
|
+
messages=[
|
|
130
|
+
{"role": "system", "content": (
|
|
131
|
+
"You are a task classifier. Reply with YES or NO only, no punctuation."
|
|
132
|
+
)},
|
|
133
|
+
{"role": "user", "content": (
|
|
134
|
+
f"Does this task require making an HTTP request to a URL or API?\n\n"
|
|
135
|
+
f"Task: {task_text}\n\nYES or NO:"
|
|
136
|
+
)},
|
|
137
|
+
],
|
|
138
|
+
max_tokens=3,
|
|
139
|
+
temperature=0,
|
|
140
|
+
)
|
|
141
|
+
answer = (resp.choices[0].message.content or "").strip().upper()
|
|
142
|
+
_log(f"router → {answer!r}")
|
|
143
|
+
return answer.startswith("Y")
|
|
144
|
+
except Exception as e: # noqa: BLE001
|
|
145
|
+
_log(f"router error: {e} — defaulting to agent")
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _plain_reply(task_text: str, base_url: str, api_key: str, model_id: str) -> str:
|
|
150
|
+
"""Plain chat completion without any tools."""
|
|
151
|
+
try:
|
|
152
|
+
from openai import OpenAI
|
|
153
|
+
client = OpenAI(base_url=base_url, api_key=api_key or "local",
|
|
154
|
+
max_retries=0, timeout=60)
|
|
155
|
+
resp = client.chat.completions.create(
|
|
156
|
+
model=model_id,
|
|
157
|
+
messages=[
|
|
158
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
159
|
+
{"role": "user", "content": task_text},
|
|
160
|
+
],
|
|
161
|
+
temperature=0.7,
|
|
162
|
+
max_tokens=512,
|
|
163
|
+
)
|
|
164
|
+
return (resp.choices[0].message.content or "").strip()
|
|
165
|
+
except Exception as e: # noqa: BLE001
|
|
166
|
+
return f"[Error: {e}]"
|
|
167
|
+
|
|
168
|
+
|
|
118
169
|
def run_agent_chat(cfg):
|
|
119
170
|
try:
|
|
120
171
|
from smolagents import CodeAgent, OpenAIServerModel, tool
|
|
@@ -130,29 +181,31 @@ def run_agent_chat(cfg):
|
|
|
130
181
|
|
|
131
182
|
_log(f"start model={agent_model_id!r} base={agent_base_url!r} maxSteps={max_steps}")
|
|
132
183
|
|
|
133
|
-
#
|
|
134
|
-
# Strip <think>...</think> blocks from assistant messages — those are internal
|
|
135
|
-
# reasoning steps and must not be fed back to the agent as conversation context.
|
|
136
|
-
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
|
|
137
|
-
|
|
184
|
+
# Use only the latest user message as the agent task.
|
|
138
185
|
task_text = ""
|
|
139
|
-
context_lines = []
|
|
140
186
|
for m in messages:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if role == "user":
|
|
144
|
-
task_text = content
|
|
145
|
-
elif role == "assistant":
|
|
146
|
-
clean = _THINK_RE.sub("", content).strip()
|
|
147
|
-
if clean:
|
|
148
|
-
context_lines.append(f"Assistant previously said: {clean[:500]}")
|
|
187
|
+
if m.get("role") == "user":
|
|
188
|
+
task_text = m.get("content", "")
|
|
149
189
|
|
|
150
190
|
if not task_text:
|
|
151
191
|
_emit({"type": "final", "text": "[No user message found in history]"})
|
|
152
192
|
return
|
|
153
193
|
|
|
154
|
-
|
|
155
|
-
|
|
194
|
+
_log(f"task={task_text[:120]!r}")
|
|
195
|
+
|
|
196
|
+
# Route: ask the model if this task needs HTTP tool use.
|
|
197
|
+
_emit({"type": "step", "text": "Routing…"})
|
|
198
|
+
needs_agent = _route(task_text, agent_base_url, agent_api_key, agent_model_id)
|
|
199
|
+
|
|
200
|
+
if not needs_agent:
|
|
201
|
+
_log("router: plain chat (no HTTP needed)")
|
|
202
|
+
_emit({"type": "step", "text": "No HTTP request needed — answering directly…"})
|
|
203
|
+
answer = _plain_reply(task_text, agent_base_url, agent_api_key, agent_model_id)
|
|
204
|
+
_log(f"plain reply: {len(answer)} chars")
|
|
205
|
+
_emit({"type": "final", "text": answer})
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
_log("router: agent (HTTP tool use needed)")
|
|
156
209
|
|
|
157
210
|
# Prepend explicit tool instructions so small models use http_request correctly
|
|
158
211
|
# and always terminate with final_answer() rather than looping forever.
|
|
@@ -165,7 +218,6 @@ def run_agent_chat(cfg):
|
|
|
165
218
|
"final_answer(result)\n\n"
|
|
166
219
|
)
|
|
167
220
|
task_with_hint = tool_hint + "Task: " + task_text
|
|
168
|
-
_log(f"task={task_text[:120]!r}")
|
|
169
221
|
|
|
170
222
|
@tool
|
|
171
223
|
def http_request(method: str, url: str, headers: str = "", body: str = "") -> str:
|
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.62",
|
|
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",
|