@pentatonic-ai/ai-agent-sdk 0.9.3 → 0.9.5
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/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/packages/memory/package-lock.json +3 -3
- package/packages/memory-engine/compat/server.py +45 -67
- package/packages/memory-engine/docker-compose.test.yml +0 -7
- package/packages/memory-engine/docker-compose.yml +10 -36
- package/packages/memory-engine/engine/services/l2/Dockerfile +7 -0
- package/packages/memory-engine/engine/services/l2/l2-hybridrag-proxy.py +233 -60
- package/packages/memory-engine/tests/test_l2_qmd_vec_search.py +280 -0
- package/packages/memory-engine/engine/services/l4/Dockerfile +0 -19
- package/packages/memory-engine/engine/services/l4/server.py +0 -315
package/dist/index.cjs
CHANGED
|
@@ -906,7 +906,7 @@ function fireAndForgetEmit(clientConfig, sessionOpts, messages, result, model) {
|
|
|
906
906
|
}
|
|
907
907
|
|
|
908
908
|
// src/telemetry.js
|
|
909
|
-
var VERSION = "0.9.
|
|
909
|
+
var VERSION = "0.9.5";
|
|
910
910
|
var TELEMETRY_URL = "https://sdk-telemetry.philip-134.workers.dev";
|
|
911
911
|
function machineId() {
|
|
912
912
|
const raw = typeof process !== "undefined" ? `${process.env?.USER || process.env?.USERNAME || "u"}:${process.platform || "x"}:${process.arch || "x"}` : "browser";
|
package/dist/index.js
CHANGED
|
@@ -875,7 +875,7 @@ function fireAndForgetEmit(clientConfig, sessionOpts, messages, result, model) {
|
|
|
875
875
|
}
|
|
876
876
|
|
|
877
877
|
// src/telemetry.js
|
|
878
|
-
var VERSION = "0.9.
|
|
878
|
+
var VERSION = "0.9.5";
|
|
879
879
|
var TELEMETRY_URL = "https://sdk-telemetry.philip-134.workers.dev";
|
|
880
880
|
function machineId() {
|
|
881
881
|
const raw = typeof process !== "undefined" ? `${process.env?.USER || process.env?.USERNAME || "u"}:${process.platform || "x"}:${process.arch || "x"}` : "browser";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pentatonic-ai/ai-agent-sdk",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "TES SDK — LLM observability and lifecycle tracking via Pentatonic Thing Event System. Track token usage, tool calls, and conversations. Manage things through event-sourced lifecycle stages with AI enrichment and vector search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -568,9 +568,9 @@
|
|
|
568
568
|
}
|
|
569
569
|
},
|
|
570
570
|
"node_modules/hono": {
|
|
571
|
-
"version": "4.12.
|
|
572
|
-
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.
|
|
573
|
-
"integrity": "sha512-
|
|
571
|
+
"version": "4.12.18",
|
|
572
|
+
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
|
|
573
|
+
"integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
|
|
574
574
|
"license": "MIT",
|
|
575
575
|
"engines": {
|
|
576
576
|
"node": ">=16.9.0"
|
|
@@ -25,7 +25,6 @@ Environment:
|
|
|
25
25
|
L0_URL default http://l0:8030
|
|
26
26
|
L2_PROXY_URL default http://l2:8031
|
|
27
27
|
L3_KG_URL default http://l3:8047
|
|
28
|
-
L4_VEC_URL default http://l4:8042
|
|
29
28
|
L5_MILVUS_URL default http://l5:8035
|
|
30
29
|
L6_DOC_URL default http://l6:8037
|
|
31
30
|
NV_EMBED_URL default http://nv-embed:8041/v1/embeddings
|
|
@@ -61,7 +60,6 @@ from _shared.embed_provider import EmbedClient # noqa: E402
|
|
|
61
60
|
L0_URL = os.environ.get("L0_URL", "http://l0:8030")
|
|
62
61
|
L2_PROXY_URL = os.environ.get("L2_PROXY_URL", "http://l2:8031")
|
|
63
62
|
L3_KG_URL = os.environ.get("L3_KG_URL", "http://l3:8047")
|
|
64
|
-
L4_VEC_URL = os.environ.get("L4_VEC_URL", "http://l4:8042")
|
|
65
63
|
L5_MILVUS_URL = os.environ.get("L5_MILVUS_URL", "http://l5:8035")
|
|
66
64
|
L6_DOC_URL = os.environ.get("L6_DOC_URL", "http://l6:8037")
|
|
67
65
|
NV_EMBED_URL = os.environ.get("NV_EMBED_URL", "http://nv-embed:8041/v1/embeddings")
|
|
@@ -288,32 +286,6 @@ async def _embed_batch(texts: list[str]) -> list[list[float]]:
|
|
|
288
286
|
return [d["embedding"] for d in resp.json()["data"]]
|
|
289
287
|
|
|
290
288
|
|
|
291
|
-
async def _index_l4(
|
|
292
|
-
records: list[dict[str, Any]],
|
|
293
|
-
embeddings: list[list[float]] | None = None,
|
|
294
|
-
) -> int:
|
|
295
|
-
"""Index records into the L4 sqlite-vec layer.
|
|
296
|
-
|
|
297
|
-
When `embeddings` is supplied (parallel to records), L4's /index-batch
|
|
298
|
-
skips its own embed call and uses ours — eliminates the redundant
|
|
299
|
-
embed work that previously cost ~850ms per drain alarm. When None,
|
|
300
|
-
L4 embeds itself (backwards-compatible path for older callers / tests
|
|
301
|
-
that don't share embeddings)."""
|
|
302
|
-
payload: dict[str, Any] = {"records": [
|
|
303
|
-
{"id": r.get("id") or hashlib.sha1(r["content"].encode()).hexdigest()[:32],
|
|
304
|
-
"text": r["content"]} for r in records
|
|
305
|
-
]}
|
|
306
|
-
if embeddings is not None:
|
|
307
|
-
payload["embeddings"] = embeddings
|
|
308
|
-
try:
|
|
309
|
-
resp = await _client().post(f"{L4_VEC_URL}/index-batch", json=payload, timeout=120.0)
|
|
310
|
-
resp.raise_for_status()
|
|
311
|
-
return resp.json().get("inserted", 0)
|
|
312
|
-
except Exception as exc:
|
|
313
|
-
print(f"[shim] L4 index-batch failed: {exc}")
|
|
314
|
-
return 0
|
|
315
|
-
|
|
316
|
-
|
|
317
289
|
async def _index_l5(
|
|
318
290
|
records: list[dict[str, Any]],
|
|
319
291
|
arena: str = "general",
|
|
@@ -325,7 +297,10 @@ async def _index_l5(
|
|
|
325
297
|
by arena natively (vs the shim's defence-in-depth post-filter).
|
|
326
298
|
|
|
327
299
|
When `embeddings` is supplied (parallel to records), L5 skips its
|
|
328
|
-
own embed call —
|
|
300
|
+
own embed call — the shim pre-computes vectors once at /store-batch
|
|
301
|
+
level and threads them through each layer to avoid 3× redundant
|
|
302
|
+
embed RPCs (L5 + L6 + L2-internal otherwise each re-embed the same
|
|
303
|
+
texts in parallel).
|
|
329
304
|
"""
|
|
330
305
|
payload: dict[str, Any] = {
|
|
331
306
|
"collection": "chats",
|
|
@@ -348,9 +323,9 @@ async def _index_l5(
|
|
|
348
323
|
resp.raise_for_status()
|
|
349
324
|
return resp.json().get("inserted", 0)
|
|
350
325
|
except Exception as exc:
|
|
351
|
-
# Best-effort: L5 is one of
|
|
352
|
-
# mean the record is unsearchable. L0 BM25 + L4
|
|
353
|
-
# all carry it independently.
|
|
326
|
+
# Best-effort: L5 is one of five redundant layers; failure here
|
|
327
|
+
# doesn't mean the record is unsearchable. L0 BM25 + L4 QMD +
|
|
328
|
+
# L6 doc-store all carry it independently.
|
|
354
329
|
print(f"[shim] L5 index-batch failed: {exc}")
|
|
355
330
|
return 0
|
|
356
331
|
|
|
@@ -363,7 +338,8 @@ async def _index_l6(
|
|
|
363
338
|
"""Index records into the L6 document store.
|
|
364
339
|
|
|
365
340
|
When `embeddings` is supplied (parallel to records), L6 skips its
|
|
366
|
-
own embed call —
|
|
341
|
+
own embed call — the shim pre-computes vectors once at /store-batch
|
|
342
|
+
level and threads them through each layer.
|
|
367
343
|
"""
|
|
368
344
|
payload: dict[str, Any] = {
|
|
369
345
|
"arena": arena,
|
|
@@ -401,8 +377,9 @@ async def _index_l2_internal(
|
|
|
401
377
|
/index-internal-batch which writes to all three in one round-trip.
|
|
402
378
|
|
|
403
379
|
When `embeddings` is supplied (parallel to records), L2's internal
|
|
404
|
-
embed call (used for L4-QMD population) is skipped —
|
|
405
|
-
|
|
380
|
+
embed call (used for L4-QMD population) is skipped — the shim
|
|
381
|
+
pre-computes vectors once at /store-batch level and threads them
|
|
382
|
+
through to L4_QMD via this endpoint.
|
|
406
383
|
"""
|
|
407
384
|
payload: dict[str, Any] = {
|
|
408
385
|
"arena": arena,
|
|
@@ -530,25 +507,25 @@ async def health():
|
|
|
530
507
|
nv_embed_health = urlunparse((_u.scheme, _u.netloc, "/health", "", "", ""))
|
|
531
508
|
|
|
532
509
|
import asyncio
|
|
533
|
-
l2_v,
|
|
510
|
+
l2_v, l5_v, l6_v, nv_v, l3_v = await asyncio.gather(
|
|
534
511
|
_probe(f"{L2_PROXY_URL}/health"),
|
|
535
|
-
_probe(f"{L4_VEC_URL}/health"),
|
|
536
512
|
_probe(f"{L5_MILVUS_URL}/health"),
|
|
537
513
|
_probe(f"{L6_DOC_URL}/health"),
|
|
538
514
|
_probe(nv_embed_health),
|
|
539
515
|
_probe_l3(),
|
|
540
516
|
)
|
|
541
517
|
|
|
542
|
-
# L0 BM25 (FTS5)
|
|
543
|
-
# inside the L2 proxy
|
|
544
|
-
#
|
|
518
|
+
# L0 BM25 (FTS5), L1 (always-loaded core files) and L4 QMD vec are
|
|
519
|
+
# all in-process inside the L2 proxy — L0+L1 in workspace.db / core
|
|
520
|
+
# files; L4 in qmd.sqlite which L2 opens directly. No separate runtime;
|
|
521
|
+
# if L2 is healthy, all three layers are usable. Tie their status to L2.
|
|
545
522
|
l2_ok = l2_v == "ok"
|
|
546
523
|
out["layers"] = {
|
|
547
524
|
"l0": "ok" if l2_ok else l2_v,
|
|
548
525
|
"l1": "ok" if l2_ok else l2_v,
|
|
549
526
|
"l2": l2_v,
|
|
550
527
|
"l3": l3_v,
|
|
551
|
-
"l4":
|
|
528
|
+
"l4": "ok" if l2_ok else l2_v,
|
|
552
529
|
"l5": l5_v,
|
|
553
530
|
"l6": l6_v,
|
|
554
531
|
"nv_embed": nv_v,
|
|
@@ -569,19 +546,15 @@ async def health():
|
|
|
569
546
|
"l6_vector_chunks": None,
|
|
570
547
|
"l6_fts_chunks": None,
|
|
571
548
|
}
|
|
572
|
-
# L0
|
|
549
|
+
# L0 and L4 both live inside L2 (workspace.db + qmd.sqlite directly
|
|
550
|
+
# opened by the L2 proxy). L2 exposes /index-internal-stats with both
|
|
551
|
+
# counts in one round-trip.
|
|
573
552
|
try:
|
|
574
553
|
r = await _client().get(f"{L2_PROXY_URL}/index-internal-stats", timeout=3.0)
|
|
575
554
|
if r.status_code == 200:
|
|
576
555
|
stats = r.json()
|
|
577
556
|
memories["l0_bm25_chunks"] = int(stats.get("l0_chunks") or 0)
|
|
578
|
-
|
|
579
|
-
pass
|
|
580
|
-
# L4 reports n_vectors on its own /health.
|
|
581
|
-
try:
|
|
582
|
-
r = await _client().get(f"{L4_VEC_URL}/health", timeout=3.0)
|
|
583
|
-
if r.status_code == 200:
|
|
584
|
-
memories["l4_vectors"] = int(r.json().get("n_vectors") or 0)
|
|
557
|
+
memories["l4_vectors"] = int(stats.get("l4_qmd_chunks") or 0)
|
|
585
558
|
except Exception:
|
|
586
559
|
pass
|
|
587
560
|
# L5 reports per-collection counts on /health. We surface chats —
|
|
@@ -634,8 +607,9 @@ async def health_deep():
|
|
|
634
607
|
except Exception as exc:
|
|
635
608
|
return name, {"ok": False, "status": f"unreachable: {type(exc).__name__}"}
|
|
636
609
|
|
|
610
|
+
# L4 is in-process inside L2 (qmd.sqlite direct-read) — its deep
|
|
611
|
+
# round-trip is covered by L2's /health/deep, no separate probe needed.
|
|
637
612
|
results = await asyncio.gather(
|
|
638
|
-
_probe_deep("l4", f"{L4_VEC_URL}/health/deep"),
|
|
639
613
|
_probe_deep("l5", f"{L5_MILVUS_URL}/health/deep"),
|
|
640
614
|
_probe_deep("l6", f"{L6_DOC_URL}/health/deep"),
|
|
641
615
|
)
|
|
@@ -675,15 +649,15 @@ async def store(req: StoreRequest):
|
|
|
675
649
|
# depending on which one was supplied).
|
|
676
650
|
_stash_all_keys(rid, req.metadata or {}, arena)
|
|
677
651
|
|
|
678
|
-
# Fan out to
|
|
652
|
+
# Fan out to L5 + L6 + L2-internal (L0+L4qmd+L3) in parallel.
|
|
679
653
|
import asyncio
|
|
680
|
-
|
|
681
|
-
_index_l4([record]),
|
|
654
|
+
l5_count, l6_count, l2_internal = await asyncio.gather(
|
|
682
655
|
_index_l5([record], arena=arena),
|
|
683
656
|
_index_l6([record], arena=arena),
|
|
684
657
|
_index_l2_internal([record], arena=arena),
|
|
685
658
|
)
|
|
686
659
|
|
|
660
|
+
l4_qmd_count = l2_internal.get("l4_qmd", 0)
|
|
687
661
|
return {
|
|
688
662
|
"id": rid,
|
|
689
663
|
"content": req.content,
|
|
@@ -692,8 +666,11 @@ async def store(req: StoreRequest):
|
|
|
692
666
|
"l0": l2_internal.get("l0", 0),
|
|
693
667
|
"l3_chunks": l2_internal.get("l3_chunks", 0),
|
|
694
668
|
"l3_entities": l2_internal.get("l3_entities", 0),
|
|
695
|
-
"l4_qmd":
|
|
696
|
-
|
|
669
|
+
"l4_qmd": l4_qmd_count,
|
|
670
|
+
# `l4` is aliased to L4_QMD now that the standalone L4 sqlite-vec
|
|
671
|
+
# sidecar has been dropped. Kept in the response for wire-format
|
|
672
|
+
# back-compat with callers that read engine.l4.
|
|
673
|
+
"l4": l4_qmd_count,
|
|
697
674
|
"l5": l5_count,
|
|
698
675
|
"l6": l6_count,
|
|
699
676
|
},
|
|
@@ -724,13 +701,13 @@ async def store_batch(req: StoreBatchRequest):
|
|
|
724
701
|
import asyncio
|
|
725
702
|
|
|
726
703
|
# Shared-embed mode: compute embeddings ONCE here, pass them down to
|
|
727
|
-
# every layer so they skip their own embed call. Previously
|
|
728
|
-
# +
|
|
729
|
-
#
|
|
730
|
-
#
|
|
731
|
-
#
|
|
732
|
-
#
|
|
733
|
-
#
|
|
704
|
+
# every layer so they skip their own embed call. Previously L5 + L6
|
|
705
|
+
# + L2-internal each re-embedded the same texts in parallel, which
|
|
706
|
+
# fanned 3× the gateway RPCs. The gateway throttles at K≈10 concurrent
|
|
707
|
+
# requests, so 30-way fan-out serialised into ~3 rounds of ~850ms
|
|
708
|
+
# each = ~2.5s of pure embed time per /store-batch. With shared
|
|
709
|
+
# embeddings we issue one chunked embed pass (10 sub-calls for N=50
|
|
710
|
+
# records) and skip the per-layer redundant work entirely.
|
|
734
711
|
# Disabled via PME_SHARE_EMBEDDINGS=false for operators wiring up
|
|
735
712
|
# per-layer differentiated embedders.
|
|
736
713
|
shared_embeddings: list[list[float]] | None = None
|
|
@@ -748,24 +725,25 @@ async def store_batch(req: StoreBatchRequest):
|
|
|
748
725
|
shared_embeddings = None
|
|
749
726
|
embed_ms = (time.perf_counter() - embed_t0) * 1000.0
|
|
750
727
|
|
|
751
|
-
|
|
752
|
-
_index_l4(normalised, embeddings=shared_embeddings),
|
|
728
|
+
l5_count, l6_count, l2_internal = await asyncio.gather(
|
|
753
729
|
_index_l5(normalised, arena=req.arena or "general", embeddings=shared_embeddings),
|
|
754
730
|
_index_l6(normalised, arena=req.arena or "general", embeddings=shared_embeddings),
|
|
755
731
|
_index_l2_internal(normalised, arena=req.arena or "general", embeddings=shared_embeddings),
|
|
756
732
|
)
|
|
757
733
|
dur_ms = (time.perf_counter() - t0) * 1000.0
|
|
758
734
|
|
|
735
|
+
l4_qmd_count = l2_internal.get("l4_qmd", 0)
|
|
759
736
|
return {
|
|
760
737
|
"status": "ok",
|
|
761
|
-
"inserted": max(
|
|
738
|
+
"inserted": max(l4_qmd_count, l5_count, l6_count),
|
|
762
739
|
"ids": [r["id"] for r in normalised],
|
|
763
740
|
"engine": {
|
|
764
741
|
"l0": l2_internal.get("l0", 0),
|
|
765
742
|
"l3_chunks": l2_internal.get("l3_chunks", 0),
|
|
766
743
|
"l3_entities": l2_internal.get("l3_entities", 0),
|
|
767
|
-
"l4_qmd":
|
|
768
|
-
|
|
744
|
+
"l4_qmd": l4_qmd_count,
|
|
745
|
+
# `l4` aliased to L4_QMD — sidecar dropped, see /store handler.
|
|
746
|
+
"l4": l4_qmd_count,
|
|
769
747
|
"l5": l5_count,
|
|
770
748
|
"l6": l6_count,
|
|
771
749
|
},
|
|
@@ -32,12 +32,6 @@ services:
|
|
|
32
32
|
# Pin the embedding dim explicitly across layers, independent of any
|
|
33
33
|
# developer-local .env (which may set EMBED_DIM=768 for Ollama-based
|
|
34
34
|
# local dev). The stub returns 4096; layers must agree.
|
|
35
|
-
l4:
|
|
36
|
-
environment:
|
|
37
|
-
L4_NV_EMBED_URL: http://embed-stub:8041/v1/embeddings
|
|
38
|
-
L4_EMBED_API_KEY: ""
|
|
39
|
-
L4_EMBED_DIM: "4096"
|
|
40
|
-
|
|
41
35
|
l5:
|
|
42
36
|
environment:
|
|
43
37
|
L5_NV_EMBED_URL: http://embed-stub:8041/v1/embeddings
|
|
@@ -61,6 +55,5 @@ services:
|
|
|
61
55
|
embed-stub:
|
|
62
56
|
condition: service_healthy
|
|
63
57
|
l2: { condition: service_started }
|
|
64
|
-
l4: { condition: service_started }
|
|
65
58
|
l5: { condition: service_started }
|
|
66
59
|
l6: { condition: service_started }
|
|
@@ -82,36 +82,6 @@ services:
|
|
|
82
82
|
retries: 30
|
|
83
83
|
start_period: 30s
|
|
84
84
|
|
|
85
|
-
# --------------------------------------------------------------------
|
|
86
|
-
# L4 — sqlite-vec sidecar
|
|
87
|
-
# --------------------------------------------------------------------
|
|
88
|
-
l4:
|
|
89
|
-
<<: *engine-base
|
|
90
|
-
build:
|
|
91
|
-
context: ./engine/services
|
|
92
|
-
dockerfile: l4/Dockerfile
|
|
93
|
-
container_name: pme-l4
|
|
94
|
-
# Default 18042 to avoid port collisions on 8042.
|
|
95
|
-
# Override via PME_L4_PORT for bench setups that intentionally replace it.
|
|
96
|
-
ports: ["127.0.0.1:${PME_L4_PORT:-18042}:8042"]
|
|
97
|
-
environment:
|
|
98
|
-
L4_NV_EMBED_URL: ${NV_EMBED_URL:-http://host.docker.internal:8041/v1/embeddings}
|
|
99
|
-
L4_EMBED_MODEL: ${EMBED_MODEL_NAME:-nv-embed-v2}
|
|
100
|
-
L4_EMBED_API_KEY: ${EMBED_API_KEY:-}
|
|
101
|
-
L4_EMBED_PROVIDER: ${EMBED_PROVIDER:-openai}
|
|
102
|
-
L4_EMBED_AUTODETECT: ${EMBED_AUTODETECT:-true}
|
|
103
|
-
L4_EMBED_DIM: ${EMBED_DIM:-4096}
|
|
104
|
-
L4_DB_PATH: /data/vec.db
|
|
105
|
-
extra_hosts:
|
|
106
|
-
- "host.docker.internal:host-gateway"
|
|
107
|
-
volumes:
|
|
108
|
-
- pme-l4-data:/data
|
|
109
|
-
healthcheck:
|
|
110
|
-
test: ["CMD", "python", "-c", "import urllib.request,sys; urllib.request.urlopen('http://localhost:8042/health',timeout=3)"]
|
|
111
|
-
interval: 10s
|
|
112
|
-
timeout: 5s
|
|
113
|
-
retries: 30
|
|
114
|
-
|
|
115
85
|
# --------------------------------------------------------------------
|
|
116
86
|
# L5 — Qdrant comms layer
|
|
117
87
|
# --------------------------------------------------------------------
|
|
@@ -224,13 +194,12 @@ services:
|
|
|
224
194
|
L0_URL: http://l2:8031
|
|
225
195
|
L2_PROXY_URL: http://l2:8031
|
|
226
196
|
L3_KG_URL: http://l3:7474
|
|
227
|
-
L4_VEC_URL: http://l4:8042
|
|
228
197
|
L5_MILVUS_URL: http://l5:8034
|
|
229
198
|
L6_DOC_URL: http://l6:8037
|
|
230
199
|
NV_EMBED_URL: ${NV_EMBED_URL:-http://host.docker.internal:8041/v1/embeddings}
|
|
231
200
|
# PME_ prefix vars feed the shim's EmbedClient for shared-embed
|
|
232
|
-
# mode on /store-batch (one embed call across all
|
|
233
|
-
#
|
|
201
|
+
# mode on /store-batch (one embed call across all 3 indexers vs
|
|
202
|
+
# 3 redundant calls). Match the L2 config block so both clients
|
|
234
203
|
# hit the same gateway with the same model. Set
|
|
235
204
|
# PME_SHARE_EMBEDDINGS=false to revert to per-layer embedding.
|
|
236
205
|
PME_NV_EMBED_URL: ${NV_EMBED_URL:-http://host.docker.internal:8041/v1/embeddings}
|
|
@@ -244,7 +213,6 @@ services:
|
|
|
244
213
|
- "host.docker.internal:host-gateway"
|
|
245
214
|
depends_on:
|
|
246
215
|
l2: { condition: service_started }
|
|
247
|
-
l4: { condition: service_started }
|
|
248
216
|
l5: { condition: service_started }
|
|
249
217
|
l6: { condition: service_started }
|
|
250
218
|
healthcheck:
|
|
@@ -252,7 +220,14 @@ services:
|
|
|
252
220
|
interval: 10s
|
|
253
221
|
timeout: 5s
|
|
254
222
|
retries: 30
|
|
255
|
-
|
|
223
|
+
# 180s gives L2 enough time to finish Neo4j schema + index creation
|
|
224
|
+
# on a cold start before compat's healthcheck starts counting failures.
|
|
225
|
+
# Observed concretely on the v0.9.4 deploy (2026-05-14): L2 took
|
|
226
|
+
# ~90s to warm up; with start_period: 60s, compat went unhealthy
|
|
227
|
+
# mid-startup, cloudflared's `depends_on: condition: service_healthy`
|
|
228
|
+
# failed, and `docker compose up` errored out before wait_for_health
|
|
229
|
+
# could observe the eventual recovery.
|
|
230
|
+
start_period: 180s
|
|
256
231
|
|
|
257
232
|
networks:
|
|
258
233
|
engine-net:
|
|
@@ -261,6 +236,5 @@ volumes:
|
|
|
261
236
|
pme-nv-embed-cache:
|
|
262
237
|
pme-l2-data:
|
|
263
238
|
pme-l3-data:
|
|
264
|
-
pme-l4-data:
|
|
265
239
|
pme-l5-data:
|
|
266
240
|
pme-l6-data:
|
|
@@ -9,9 +9,16 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
9
9
|
|
|
10
10
|
# Reranker = sentence-transformers MiniLM cross-encoder.
|
|
11
11
|
# Torch CPU wheels are fine — reranker is small enough to be CPU-bound.
|
|
12
|
+
#
|
|
13
|
+
# sqlite-vec 0.1.9: native KNN over packed-f32 vectors stored in a vec0
|
|
14
|
+
# virtual table. Replaces the legacy hand-rolled Python cosine loop over
|
|
15
|
+
# JSON-serialised embeddings in search_qmd_informed (~15s timeout at 450k
|
|
16
|
+
# rows → ~50ms native MATCH). Pin to 0.1.9 — that's the version probed
|
|
17
|
+
# against L4 QMD's wire format (struct.pack f32 + cosine distance_metric).
|
|
12
18
|
RUN pip install --no-cache-dir \
|
|
13
19
|
fastapi "uvicorn[standard]" httpx requests pydantic \
|
|
14
20
|
neo4j \
|
|
21
|
+
sqlite-vec==0.1.9 \
|
|
15
22
|
"sentence-transformers" \
|
|
16
23
|
"torch" --extra-index-url https://download.pytorch.org/whl/cpu
|
|
17
24
|
|