@tiens.nguyen/gonext-local-worker 1.0.61 → 1.0.63

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.
@@ -117,6 +117,55 @@ def _summarise_step(step_log):
117
117
  return label + (" | ".join(parts) if parts else "thinking…")
118
118
 
119
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
+
120
169
  def run_agent_chat(cfg):
121
170
  try:
122
171
  from smolagents import CodeAgent, OpenAIServerModel, tool
@@ -132,29 +181,31 @@ def run_agent_chat(cfg):
132
181
 
133
182
  _log(f"start model={agent_model_id!r} base={agent_base_url!r} maxSteps={max_steps}")
134
183
 
135
- # Build task from the last user message; prepend prior assistant turns as context.
136
- # Strip <think>...</think> blocks from assistant messages — those are internal
137
- # reasoning steps and must not be fed back to the agent as conversation context.
138
- _THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
139
-
184
+ # Use only the latest user message as the agent task.
140
185
  task_text = ""
141
- context_lines = []
142
186
  for m in messages:
143
- role = m.get("role", "")
144
- content = m.get("content", "")
145
- if role == "user":
146
- task_text = content
147
- elif role == "assistant":
148
- clean = _THINK_RE.sub("", content).strip()
149
- if clean:
150
- context_lines.append(f"Assistant previously said: {clean[:500]}")
187
+ if m.get("role") == "user":
188
+ task_text = m.get("content", "")
151
189
 
152
190
  if not task_text:
153
191
  _emit({"type": "final", "text": "[No user message found in history]"})
154
192
  return
155
193
 
156
- if context_lines:
157
- task_text = "\n".join(context_lines) + "\n\nNow answer: " + task_text
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)")
158
209
 
159
210
  # Prepend explicit tool instructions so small models use http_request correctly
160
211
  # and always terminate with final_answer() rather than looping forever.
@@ -162,12 +213,9 @@ def run_agent_chat(cfg):
162
213
  "You have ONE built-in function: `http_request(method, url, headers='', body='')`. "
163
214
  "Do NOT import requests, urllib, or any library — call http_request() directly.\n"
164
215
  "When you have the answer, ALWAYS end with `final_answer(your_answer)` — this stops the agent.\n"
165
- "Example:\n"
166
- "result = http_request(method='GET', url='https://example.com')\n"
167
- "final_answer(result)\n\n"
216
+ "Call http_request with the URL from the task, then call final_answer with the result.\n\n"
168
217
  )
169
218
  task_with_hint = tool_hint + "Task: " + task_text
170
- _log(f"task={task_text[:120]!r}")
171
219
 
172
220
  @tool
173
221
  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.61",
3
+ "version": "1.0.63",
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",