@pentatonic-ai/ai-agent-sdk 0.7.8 → 0.7.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pentatonic-ai/ai-agent-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
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",
|
|
@@ -41,6 +41,18 @@ import { distill } from "./distill.js";
|
|
|
41
41
|
* ids) — pass the raw form here so retries of the same logical event
|
|
42
42
|
* match across runs whose prefixes differ by a few ms. Defaults to
|
|
43
43
|
* `content`.
|
|
44
|
+
* @param {"client" | "user"} [opts.dedupScope="client"] - Scope of the
|
|
45
|
+
* dedup match. Default `"client"`: byte-equal content for the tenant
|
|
46
|
+
* collapses to one row regardless of who emitted it (today's behaviour,
|
|
47
|
+
* appropriate when each row is single-owner). Set `"user"` to also
|
|
48
|
+
* require `user_id` equality, which lets multiple users legitimately
|
|
49
|
+
* own their own copy of the same shared content (private chat-channel
|
|
50
|
+
* members, group meeting attendees) — emit one ingest per member with
|
|
51
|
+
* the same content but different `userId`, all with
|
|
52
|
+
* `dedupScope: "user"`, and each user gets their own row with native
|
|
53
|
+
* per-user access counting / recency / decay. Requires `userId` to be
|
|
54
|
+
* set; when `userId` is null the option degrades to `"client"` scope
|
|
55
|
+
* (a global shared row still collapses cross-emit).
|
|
44
56
|
* @param {number} [opts.dedupLegacyWindowDays=7] - How far back the
|
|
45
57
|
* `[<iso>] <content>` legacy-form `LIKE` match scans. Default 7 days.
|
|
46
58
|
* The leading-wildcard `LIKE` can't use a btree index, so without a
|
|
@@ -94,6 +106,11 @@ export async function ingest(db, ai, llm, content, opts = {}) {
|
|
|
94
106
|
opts.dedupLegacyWindowDays === undefined
|
|
95
107
|
? 7
|
|
96
108
|
: Number(opts.dedupLegacyWindowDays);
|
|
109
|
+
// Per-user dedup requires `userId`; degrade to client-scope otherwise so
|
|
110
|
+
// a misuse can't accidentally relax the dedup boundary (we'd rather over-
|
|
111
|
+
// collapse than fragment the corpus on a missing userId).
|
|
112
|
+
const userScopedDedup = opts.dedupScope === "user" && !!opts.userId;
|
|
113
|
+
const userClause = userScopedDedup ? ` AND user_id = $4` : "";
|
|
97
114
|
try {
|
|
98
115
|
const sql =
|
|
99
116
|
legacyWindowDays > 0
|
|
@@ -107,17 +124,18 @@ export async function ingest(db, ai, llm, content, opts = {}) {
|
|
|
107
124
|
content LIKE '%] ' || $2
|
|
108
125
|
AND created_at > NOW() - ($3 || ' days')::interval
|
|
109
126
|
)
|
|
110
|
-
)
|
|
127
|
+
)${userScopedDedup ? "\n AND user_id = $4" : ""}
|
|
111
128
|
LIMIT 1`
|
|
112
129
|
: `SELECT id, 'exact' AS match_kind
|
|
113
130
|
FROM memory_nodes
|
|
114
131
|
WHERE client_id = $1
|
|
115
|
-
AND content = $2
|
|
132
|
+
AND content = $2${userClause}
|
|
116
133
|
LIMIT 1`;
|
|
117
|
-
const
|
|
134
|
+
const baseParams =
|
|
118
135
|
legacyWindowDays > 0
|
|
119
136
|
? [clientId, dedupKey, String(legacyWindowDays)]
|
|
120
137
|
: [clientId, dedupKey];
|
|
138
|
+
const params = userScopedDedup ? [...baseParams, opts.userId] : baseParams;
|
|
121
139
|
const dupCheck = await db(sql, params);
|
|
122
140
|
if (dupCheck.rows?.length) {
|
|
123
141
|
const matchKind = dupCheck.rows[0].match_kind || "exact";
|
|
@@ -30,7 +30,6 @@ Environment:
|
|
|
30
30
|
L6_DOC_URL default http://l6:8037
|
|
31
31
|
NV_EMBED_URL default http://nv-embed:8041/v1/embeddings
|
|
32
32
|
PORT default 8099 (matches pentatonic-memory v0.5)
|
|
33
|
-
CLIENT_ID default "default"
|
|
34
33
|
"""
|
|
35
34
|
|
|
36
35
|
import hashlib
|
|
@@ -63,7 +62,18 @@ NEO4J_AUTH = os.environ.get("NEO4J_AUTH", "neo4j/local-dev-pw")
|
|
|
63
62
|
NEO4J_DB = os.environ.get("NEO4J_DB", "neo4j")
|
|
64
63
|
|
|
65
64
|
PORT = int(os.environ.get("PORT", "8099"))
|
|
66
|
-
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Layer types we surface as the SDK 4-layer projection. Engine stores
|
|
68
|
+
# everything as chunks tagged with arena + layer_type metadata; this
|
|
69
|
+
# helper renders the legacy `ml_<arena>_<type>` layer-id from the
|
|
70
|
+
# per-row arena, so the response reflects the actual data not a
|
|
71
|
+
# deployment-wide constant. Falls back to "episodic" when arena or
|
|
72
|
+
# layer_type is missing.
|
|
73
|
+
def _layer_id(arena: Optional[str], layer_type: Optional[str] = None) -> str:
|
|
74
|
+
a = arena or "general"
|
|
75
|
+
t = layer_type or "episodic"
|
|
76
|
+
return f"ml_{a}_{t}"
|
|
67
77
|
|
|
68
78
|
# Test/isolated mode: bypass the L2 HybridRAG orchestrator and query L6 directly.
|
|
69
79
|
# Useful for bench harnesses where you want to validate the ingest+search
|
|
@@ -384,7 +394,6 @@ async def health():
|
|
|
384
394
|
"""
|
|
385
395
|
out = {
|
|
386
396
|
"status": "ok",
|
|
387
|
-
"client": CLIENT_ID,
|
|
388
397
|
"version": VERSION,
|
|
389
398
|
"engine": "pentatonic-memory-engine",
|
|
390
399
|
"layers": {},
|
|
@@ -476,7 +485,7 @@ async def store(req: StoreRequest):
|
|
|
476
485
|
return {
|
|
477
486
|
"id": rid,
|
|
478
487
|
"content": req.content,
|
|
479
|
-
"layerId":
|
|
488
|
+
"layerId": _layer_id(arena, (req.metadata or {}).get("layer_type")),
|
|
480
489
|
"engine": {
|
|
481
490
|
"l0": l2_internal.get("l0", 0),
|
|
482
491
|
"l3_chunks": l2_internal.get("l3_chunks", 0),
|
|
@@ -717,13 +726,14 @@ async def search(req: SearchRequest):
|
|
|
717
726
|
if item.get(k)
|
|
718
727
|
}
|
|
719
728
|
merged_meta = {**raw_top_level, **(attached_meta or item.get("metadata") or {})}
|
|
729
|
+
row_arena = merged_meta.get("arena")
|
|
730
|
+
row_layer_type = merged_meta.get("layer_type")
|
|
720
731
|
out_results.append({
|
|
721
732
|
"id": key,
|
|
722
733
|
"content": item.get("text") or item.get("content") or item.get("snippet") or "",
|
|
723
734
|
"metadata": merged_meta,
|
|
724
735
|
"similarity": float(rrf_scores[key]),
|
|
725
|
-
"layer_id":
|
|
726
|
-
"client_id": CLIENT_ID,
|
|
736
|
+
"layer_id": _layer_id(row_arena, row_layer_type),
|
|
727
737
|
"source": item.get("source_file") or item.get("path") or "",
|
|
728
738
|
"engine_layer": "+".join(sorted(set(layer_provenance.get(key, [])))),
|
|
729
739
|
})
|
|
@@ -819,13 +829,14 @@ async def search(req: SearchRequest):
|
|
|
819
829
|
if item.get(k)
|
|
820
830
|
}
|
|
821
831
|
merged_meta = {**raw_top_level, **(attached_meta or item.get("metadata") or {})}
|
|
832
|
+
row_arena = merged_meta.get("arena")
|
|
833
|
+
row_layer_type = merged_meta.get("layer_type")
|
|
822
834
|
out_results.append({
|
|
823
835
|
"id": chosen_id,
|
|
824
836
|
"content": item.get("text") or item.get("content") or item.get("snippet") or "",
|
|
825
837
|
"metadata": merged_meta,
|
|
826
838
|
"similarity": float(item.get("score") or item.get("similarity") or 0.0),
|
|
827
|
-
"layer_id":
|
|
828
|
-
"client_id": CLIENT_ID,
|
|
839
|
+
"layer_id": _layer_id(row_arena, row_layer_type),
|
|
829
840
|
"source": item.get("source", item.get("source_file", "")),
|
|
830
841
|
"engine_layer": item.get("layer", item.get("source_layer", "")),
|
|
831
842
|
})
|
|
@@ -215,7 +215,6 @@ services:
|
|
|
215
215
|
L5_MILVUS_URL: http://l5:8034
|
|
216
216
|
L6_DOC_URL: http://l6:8037
|
|
217
217
|
NV_EMBED_URL: ${NV_EMBED_URL:-http://host.docker.internal:8041/v1/embeddings}
|
|
218
|
-
CLIENT_ID: ${CLIENT_ID:-default}
|
|
219
218
|
BYPASS_L2_PROXY: ${BYPASS_L2_PROXY:-0}
|
|
220
219
|
extra_hosts:
|
|
221
220
|
- "host.docker.internal:host-gateway"
|