@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.
- package/gonext-local-worker.mjs +1 -0
- package/gonext_agent_chat.py +49 -34
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -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
|
package/gonext_agent_chat.py
CHANGED
|
@@ -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
|
|
1269
|
-
"""Embed a batch of texts
|
|
1270
|
-
|
|
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
|
|
1275
|
+
raise RuntimeError("No embedding endpoint configured (set the RAG embedding server URL).")
|
|
1273
1276
|
ctx = _ssl_context()
|
|
1274
|
-
|
|
1275
|
-
|
|
1277
|
+
last_err = None
|
|
1278
|
+
|
|
1279
|
+
def _post(url, payload, timeout):
|
|
1276
1280
|
req = urllib.request.Request(
|
|
1277
|
-
|
|
1278
|
-
|
|
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=
|
|
1283
|
-
|
|
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
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
1518
|
-
#
|
|
1519
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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",
|