@tiens.nguyen/gonext-local-worker 1.0.157 → 1.0.158

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.
@@ -1610,6 +1610,7 @@ async function runAgentChatJob(job) {
1610
1610
  ragAwsAccessKeyId: payload?.ragAwsAccessKeyId ?? "",
1611
1611
  ragAwsSecretAccessKey: payload?.ragAwsSecretAccessKey ?? "",
1612
1612
  ragEmbedModel: payload?.ragEmbedModel ?? "",
1613
+ ragEmbedUrl: payload?.ragEmbedUrl ?? "",
1613
1614
  ragTopK: payload?.ragTopK ?? 6,
1614
1615
  });
1615
1616
  // 30 min max for an agent run: multi-step ReAct on a 14B/31B with a cold prompt
@@ -1265,44 +1265,57 @@ def _rag_chunk_text(text: str, rel_path: str, ext: str):
1265
1265
  return chunks
1266
1266
 
1267
1267
 
1268
- def _ollama_embed(ollama_root: str, model: str, texts: list) -> list:
1269
- """Embed a batch of texts via Ollama /api/embed (falls back to /api/embeddings)."""
1270
- root = (ollama_root or "").rstrip("/")
1268
+ def _embed(base: str, model: str, texts: list) -> list:
1269
+ """Embed a batch of texts. Auto-detects the endpoint from `base` (a bare host:port,
1270
+ an .../v1, or an Ollama host): tries OpenAI-compatible /v1/embeddings first (MLX
1271
+ servers, modern Ollama), then Ollama /api/embed, then legacy /api/embeddings."""
1272
+ # Normalize to a bare root (strip any /v1, /api, /api/embed(dings) suffix).
1273
+ root = re.sub(r"/(v1|api/embeddings|api/embed|api)/?$", "", (base or "").rstrip("/")).rstrip("/")
1271
1274
  if not root:
1272
- raise RuntimeError("No Ollama endpoint available for embeddings.")
1275
+ raise RuntimeError("No embedding endpoint configured (set the RAG embedding server URL).")
1273
1276
  ctx = _ssl_context()
1274
- # Preferred: batch /api/embed (recent Ollama).
1275
- try:
1277
+ last_err = None
1278
+
1279
+ def _post(url, payload, timeout):
1276
1280
  req = urllib.request.Request(
1277
- f"{root}/api/embed",
1278
- data=json.dumps({"model": model, "input": texts}).encode("utf-8"),
1279
- headers={"Content-Type": "application/json"},
1280
- method="POST",
1281
+ url, data=json.dumps(payload).encode("utf-8"),
1282
+ headers={"Content-Type": "application/json"}, method="POST",
1281
1283
  )
1282
- with urllib.request.urlopen(req, timeout=300, context=ctx) as resp:
1283
- data = json.loads(resp.read().decode("utf-8", "replace"))
1284
+ with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
1285
+ return json.loads(resp.read().decode("utf-8", "replace"))
1286
+
1287
+ # 1) OpenAI-compatible /v1/embeddings — MLX servers on a port, and modern Ollama.
1288
+ try:
1289
+ data = _post(f"{root}/v1/embeddings", {"model": model, "input": texts}, 300)
1290
+ items = data.get("data")
1291
+ if isinstance(items, list) and items and isinstance(items[0], dict) and "embedding" in items[0]:
1292
+ return [it["embedding"] for it in items]
1293
+ except urllib.error.HTTPError as e:
1294
+ if e.code not in (400, 404, 405, 501):
1295
+ raise RuntimeError(f"Embeddings failed (HTTP {e.code}): {e.read().decode('utf-8', 'replace')[:200]}")
1296
+ last_err = f"/v1/embeddings HTTP {e.code}"
1297
+ except urllib.error.URLError as e:
1298
+ raise RuntimeError(f"Embedding server unreachable at {root}: {getattr(e, 'reason', e)}")
1299
+
1300
+ # 2) Ollama batch /api/embed.
1301
+ try:
1302
+ data = _post(f"{root}/api/embed", {"model": model, "input": texts}, 300)
1284
1303
  embs = data.get("embeddings")
1285
1304
  if isinstance(embs, list) and embs and isinstance(embs[0], list):
1286
1305
  return embs
1287
1306
  except urllib.error.HTTPError as e:
1288
- if e.code != 404:
1289
- detail = e.read().decode("utf-8", "replace")[:200]
1290
- raise RuntimeError(f"Ollama embed failed (HTTP {e.code}): {detail}")
1291
- except urllib.error.URLError as e:
1292
- raise RuntimeError(f"Ollama embed unreachable: {getattr(e, 'reason', e)}")
1293
- # Fallback: per-text /api/embeddings (older Ollama).
1307
+ if e.code not in (400, 404, 405, 501):
1308
+ raise RuntimeError(f"Ollama embed failed (HTTP {e.code}): {e.read().decode('utf-8', 'replace')[:200]}")
1309
+ last_err = f"/api/embed HTTP {e.code}"
1310
+
1311
+ # 3) Legacy Ollama /api/embeddings (per text).
1294
1312
  out = []
1295
1313
  for t in texts:
1296
- req = urllib.request.Request(
1297
- f"{root}/api/embeddings",
1298
- data=json.dumps({"model": model, "prompt": t}).encode("utf-8"),
1299
- headers={"Content-Type": "application/json"},
1300
- method="POST",
1301
- )
1302
- with urllib.request.urlopen(req, timeout=120, context=ctx) as resp:
1303
- data = json.loads(resp.read().decode("utf-8", "replace"))
1314
+ data = _post(f"{root}/api/embeddings", {"model": model, "prompt": t}, 120)
1304
1315
  out.append(data.get("embedding") or [])
1305
- return out
1316
+ if any(out):
1317
+ return out
1318
+ raise RuntimeError(f"No embedding endpoint responded at {root} ({last_err or 'unknown'}).")
1306
1319
 
1307
1320
 
1308
1321
  def _cosine(a: list, b: list) -> float:
@@ -1509,20 +1522,22 @@ def run_agent_chat(cfg):
1509
1522
  rag_akid = (cfg.get("ragAwsAccessKeyId") or "").strip()
1510
1523
  rag_secret = (cfg.get("ragAwsSecretAccessKey") or "").strip()
1511
1524
  rag_embed_model = (cfg.get("ragEmbedModel") or "nomic-embed-text").strip()
1525
+ rag_embed_url = (cfg.get("ragEmbedUrl") or "").strip()
1512
1526
  try:
1513
1527
  rag_top_k = int(cfg.get("ragTopK") or 6)
1514
1528
  except (TypeError, ValueError):
1515
1529
  rag_top_k = 6
1516
1530
  rag_top_k = max(1, min(50, rag_top_k))
1517
- # Embeddings run on the Ollama coding server (same box as the coder). Strip the
1518
- # OpenAI-compat /v1 suffix to reach the native /api/embed root.
1519
- rag_ollama_root = re.sub(r"/v1/?$", "", (coding_base_url or agent_base_url or "").rstrip("/"))
1531
+ # Embedding server: an explicit MLX/Ollama endpoint from Settings if given, else the
1532
+ # agent's Ollama coding server. _embed() auto-detects OpenAI-compat (/v1/embeddings,
1533
+ # e.g. an MLX server on a port) vs Ollama (/api/embed) — so a bare host:port works.
1534
+ rag_embed_base = rag_embed_url or (coding_base_url or agent_base_url or "")
1520
1535
  try:
1521
1536
  import boto3 as _boto3_probe # noqa: F401,PLC0415
1522
1537
  _boto3_ok = True
1523
1538
  except Exception: # noqa: BLE001
1524
1539
  _boto3_ok = False
1525
- _RAG_AVAILABLE = bool(rag_enabled and rag_akid and rag_secret and rag_ollama_root and _boto3_ok)
1540
+ _RAG_AVAILABLE = bool(rag_enabled and rag_akid and rag_secret and rag_embed_base and _boto3_ok)
1526
1541
  if rag_enabled and not _boto3_ok:
1527
1542
  _log("RAG requested but boto3 is not installed in the worker python — RAG tools disabled")
1528
1543
 
@@ -2678,7 +2693,7 @@ def run_agent_chat(cfg):
2678
2693
  return "No indexable text files found at that path."
2679
2694
  for i in range(0, len(records), 64):
2680
2695
  batch = records[i:i + 64]
2681
- vecs = _ollama_embed(rag_ollama_root, rag_embed_model, [r["text"] for r in batch])
2696
+ vecs = _embed(rag_embed_base, rag_embed_model, [r["text"] for r in batch])
2682
2697
  for r, v in zip(batch, vecs):
2683
2698
  r["embedding"] = v
2684
2699
  _emit({"type": "step", "text": f"Embedded {min(i + 64, len(records))}/{len(records)} chunks…"})
@@ -2718,7 +2733,7 @@ def run_agent_chat(cfg):
2718
2733
  try:
2719
2734
  import time as _t
2720
2735
  records = _rag_chunk_text(text, "user-note", ".txt")
2721
- vecs = _ollama_embed(rag_ollama_root, rag_embed_model, [r["text"] for r in records])
2736
+ vecs = _embed(rag_embed_base, rag_embed_model, [r["text"] for r in records])
2722
2737
  for r, v in zip(records, vecs):
2723
2738
  r["embedding"] = v
2724
2739
  client, bucket, prefix = _rag_s3_and_loc()
@@ -2749,7 +2764,7 @@ def run_agent_chat(cfg):
2749
2764
  chunks = _rag_load_chunks(client, bucket, base)
2750
2765
  if not chunks:
2751
2766
  return f"No knowledge base found for {source_url}. Run rag_index first."
2752
- qvec = _ollama_embed(rag_ollama_root, rag_embed_model, [query])[0]
2767
+ qvec = _embed(rag_embed_base, rag_embed_model, [query])[0]
2753
2768
  scored = sorted(
2754
2769
  ((_cosine(qvec, c.get("embedding") or []), c) for c in chunks),
2755
2770
  key=lambda x: x[0], reverse=True,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.157",
3
+ "version": "1.0.158",
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",