@maximem/synap-js-sdk 0.5.8 → 0.5.9
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/README.md +5 -1
- package/dist/anticipation-cache-LPNKMRBH.cjs +20 -0
- package/dist/anticipation-cache-LPNKMRBH.cjs.map +1 -0
- package/dist/anticipation-cache-RWKF55J4.js +20 -0
- package/dist/{chunk-FXGLT6AT.js → chunk-I7TWR4S6.js} +59 -3
- package/dist/chunk-I7TWR4S6.js.map +1 -0
- package/dist/{chunk-T6U5PVAD.cjs → chunk-ICUFS3B5.cjs} +449 -12
- package/dist/chunk-ICUFS3B5.cjs.map +1 -0
- package/dist/{chunk-ODVOQOSO.js → chunk-R72YL6Q4.js} +445 -8
- package/dist/chunk-R72YL6Q4.js.map +1 -0
- package/dist/{chunk-HCULAVJC.cjs → chunk-RE4DDIWO.cjs} +66 -10
- package/dist/chunk-RE4DDIWO.cjs.map +1 -0
- package/dist/chunk-T372OL3Q.cjs.map +1 -1
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
- package/dist/env-55ULHNJZ.cjs.map +1 -1
- package/dist/{errors-Bl3vY1eb.d.cts → errors-Bdayqlzy.d.cts} +88 -2
- package/dist/{errors-Bl3vY1eb.d.ts → errors-Bdayqlzy.d.ts} +88 -2
- package/dist/grpc/index.cjs +4 -4
- package/dist/grpc/index.cjs.map +1 -1
- package/dist/grpc/index.d.cts +8 -3
- package/dist/grpc/index.d.ts +8 -3
- package/dist/grpc/index.js +3 -3
- package/dist/index.cjs +1300 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +258 -5
- package/dist/index.d.ts +258 -5
- package/dist/index.js +1253 -69
- package/dist/index.js.map +1 -1
- package/dist/onnx-encoder-4RSL6ID4.cjs +103 -0
- package/dist/onnx-encoder-4RSL6ID4.cjs.map +1 -0
- package/dist/onnx-encoder-NP35KH4H.js +103 -0
- package/dist/onnx-encoder-NP35KH4H.js.map +1 -0
- package/dist/{stream-client-VWKINE47.js → stream-client-3WHXGW4U.js} +2 -2
- package/dist/stream-client-L6OQPZN7.cjs +16 -0
- package/dist/stream-client-L6OQPZN7.cjs.map +1 -0
- package/package.json +4 -2
- package/dist/anticipation-cache-LRE3TLK4.cjs +0 -14
- package/dist/anticipation-cache-LRE3TLK4.cjs.map +0 -1
- package/dist/anticipation-cache-WLU4NMD5.js +0 -14
- package/dist/chunk-FXGLT6AT.js.map +0 -1
- package/dist/chunk-HCULAVJC.cjs.map +0 -1
- package/dist/chunk-ODVOQOSO.js.map +0 -1
- package/dist/chunk-T6U5PVAD.cjs.map +0 -1
- package/dist/stream-client-N46JBT5A.cjs +0 -16
- package/dist/stream-client-N46JBT5A.cjs.map +0 -1
- /package/dist/{anticipation-cache-WLU4NMD5.js.map → anticipation-cache-RWKF55J4.js.map} +0 -0
- /package/dist/{stream-client-VWKINE47.js.map → stream-client-3WHXGW4U.js.map} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
var _chunkZKOJDUN4cjs = require('./chunk-ZKOJDUN4.cjs');
|
|
5
6
|
|
|
6
7
|
// src/behavior/anticipation.json
|
|
@@ -352,6 +353,191 @@ var anticipation_default = {
|
|
|
352
353
|
note: "sorted by key, so `customer=ab` and this cannot collapse"
|
|
353
354
|
}
|
|
354
355
|
]
|
|
356
|
+
},
|
|
357
|
+
semantic_match: {
|
|
358
|
+
$comment: "Matching a fetch query to prefetched items by meaning, with the SDK's local embedding model (`model`, which history search uses too). An item's score is its best cosine to the query, over the item's own text and the questions it answers (its HQEs, sent with the bundle item; item_questions), with the query as asked and with the asker's pronouns in the third person (canonical_query). Three bands: at sure_min_cosine or above the cache answers; between min_cosine and sure_min_cosine the match is borderline and the SDK asks the server too, serving the server's answer if it arrives within race_deadline_ms and the cached one if not; below min_cosine the server answers. A hit also serves items at serve_min_cosine or above, and items from a bundle holding a hit at bundle_serve_min_cosine or above. asked_kind: a question that asks for a time of day, a duration or an amount is not answered by served items holding none (asked_details). Items not embedded yet, and every item without the model, are matched as before (BM25 with its novel-term gate). The thresholds hold for `model` only, one text per run. Measured on harnesses/anticipation_match (404 labelled queries, 4 languages, production-shaped bundles with production-prompt HQEs), held-out half, per 100 questions with the answer cached for 68: BM25 served 27 right and 13 wrong; MiniLM on the memory text alone 30 and 5.5; with all of the above (sure bar 0.74) 44-47 and 2.5-3, 21 Oct 2026.",
|
|
359
|
+
model: "onnx:Xenova/paraphrase-multilingual-MiniLM-L12-v2@2c4055b12046f11709e9df2c122e59ffbdc2f900/onnx/model_quantized.onnx",
|
|
360
|
+
mode: "semantic",
|
|
361
|
+
min_cosine: 0.66,
|
|
362
|
+
serve_min_cosine: 0.4,
|
|
363
|
+
asked_kind: true,
|
|
364
|
+
max_queries: 4,
|
|
365
|
+
item_questions: true,
|
|
366
|
+
canonical_query: "both",
|
|
367
|
+
self_pronouns: {
|
|
368
|
+
$comment: "canonical_query rewrites the asker's own pronouns into the third person memories are stored in ('my landlord' -> 'the user's landlord'). Applied in order, case-insensitive. 'canon' compares only the rewritten query, 'both' takes the better of the two (HQEs are written in the first person).",
|
|
369
|
+
en: [
|
|
370
|
+
["\\b(i am|i'm)\\b", "the user is"],
|
|
371
|
+
["\\b(i have|i've)\\b", "the user has"],
|
|
372
|
+
["\\b(i was)\\b", "the user was"],
|
|
373
|
+
["\\b(my|mine)\\b", "the user's"],
|
|
374
|
+
["\\b(i|me|myself)\\b", "the user"]
|
|
375
|
+
]
|
|
376
|
+
},
|
|
377
|
+
asked_details: {
|
|
378
|
+
$comment: "asked_kind: a question that asks for one of these kinds of value is not answered by served items that hold none (asked 'what time is my flight', served 'the flight is on 25 October'). Its own list, not history_search.asked_kinds, so history search's ranking is untouched. Query patterns are matched case-insensitively; value patterns are written to be language-neutral (digits, units, currency) and avoid \\\\b next to non-ASCII letters, which ECMAScript's \\\\b does not treat as word characters.",
|
|
379
|
+
time_of_day: {
|
|
380
|
+
query: "what time|at what time|which time|opening hours|open hours|what hours|when does .{0,40}(open|close|start|begin)|qu[e\xE9] hora|a qu[e\xE9] hora|que horas|a que horas|hor[a\xE1]rio|\u0915\u093F\u0938 \u0938\u092E\u092F|\u0915\u093F\u0924\u0928\u0947 \u092C\u091C\u0947|\u091F\u093E\u0907\u092E",
|
|
381
|
+
value: "\\b\\d{1,2}(:\\d{2})? ?([ap]\\.?m\\.?)(\\W|$)|\\b\\d{1,2}:\\d{2}\\b|\\b\\d{1,2}h(\\d{2})?\\b|noon|midnight|mediod[i\xED]a|meio-dia|\u092C\u091C\u0947"
|
|
382
|
+
},
|
|
383
|
+
duration: {
|
|
384
|
+
query: "how long|duration|how many (minutes|hours)|cu[a\xE1]nto dura|cu[a\xE1]nto tiempo|quanto tempo|dura[c\xE7][a\xE3]o|\u0915\u093F\u0924\u0928\u0940 \u0926\u0947\u0930|\u0915\u093F\u0924\u0928\u0947 \u0938\u092E\u092F",
|
|
385
|
+
value: "\\d+(\\.\\d+)? ?(minutes?|mins?|hours?|hrs?|days?|weeks?|months?|years?|minutos?|horas?|d[i\xED]as|semanas?|meses|a\xF1os|anos|\u092E\u093F\u0928\u091F|\u0918\u0902\u091F\u0947|\u0918\u0902\u091F\u093E|\u0926\u093F\u0928|\u0939\u092B\u094D\u0924\u0947|\u092E\u0939\u0940\u0928\u0947|\u0938\u093E\u0932)"
|
|
386
|
+
},
|
|
387
|
+
money: {
|
|
388
|
+
query: "how much|cost|price|fee|charge|budget|amount|quote|cu[a\xE1]nto cuesta|precio|costo|tarifa|quanto custa|pre[c\xE7]o|valor|custo|\u0915\u093F\u0924\u0928\u0947 \u092A\u0948\u0938\u0947|\u0915\u093F\u0924\u0928\u093E \u092A\u0948\u0938\u093E|\u0915\u0940\u092E\u0924|\u0916\u0930\u094D\u091A|\u0936\u0941\u0932\u094D\u0915|\u0915\u093F\u0924\u0928\u0947 \u0930\u0941\u092A\u092F\u0947",
|
|
389
|
+
value: "[$\u20AC\xA3\xA5\u20B9]\\s?\\d|\\d[\\d,.]* ?(usd|eur|gbp|inr|brl|mxn|dollars?|euros?|pounds?|rupees?|reais|real|pesos?|\u0930\u0941\u092A\u092F\u0947|\u0930\u0941\u092A\u090F)|\\b(usd|eur|gbp|inr|brl|rs\\.?|r\\$) ?\\d"
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
bundle_serve_min_cosine: 0.3,
|
|
393
|
+
sure_min_cosine: 0.74,
|
|
394
|
+
race_deadline_ms: 250
|
|
395
|
+
},
|
|
396
|
+
history_search: {
|
|
397
|
+
$comment: `Local search over the raw turns a compaction replaced (COMPACTION_HYBRID_PLAN.md P5). Keyword first (literal values, then BM25 with the bm25 section's tokenizer), embeddings only when installed and keywords come up short; results fused by reciprocal rank with a small recency prior, shown in conversation order. The heading reaches the LLM prompt: byte-identical in both SDKs. Each hit brings the turn before it (its last previous_turn_words words). Irregular past forms are mapped to the base form before stemming (English; history search only). A turn needs min_matched_terms query words, or one word found in at most rare_term_max_share of the turns. Passages (passages, passages_per_turn): a turn longer than max_words_per_excerpt is scored as passages (table rows with their header, a few lines of a log, a few sentences), ranks by its best passage, and shows its best passages; measured 20 Oct with multilingual MiniLM, answer text found 117 of 148 probes vs 93 by whole turns. Tool results the host agent reports are archived here too, never sent to the server, as "[tool result: <name>] <result>" cut to tool_result_max_chars: searchable like turns, not counted as server turns.`,
|
|
398
|
+
max_excerpts: 6,
|
|
399
|
+
max_words_per_excerpt: 60,
|
|
400
|
+
min_matched_terms: 2,
|
|
401
|
+
rrf_k: 60,
|
|
402
|
+
recency_weight: 0.5,
|
|
403
|
+
heading: "### From the earlier turns (verbatim excerpts matched to the latest message; later turns may have changed them)",
|
|
404
|
+
store_limits: {
|
|
405
|
+
max_conversations: 200,
|
|
406
|
+
max_turns_per_conversation: 5e3,
|
|
407
|
+
max_bytes_per_conversation: 8388608,
|
|
408
|
+
idle_ttl_seconds: 604800,
|
|
409
|
+
max_file_bytes: 209715200
|
|
410
|
+
},
|
|
411
|
+
previous_turns: 1,
|
|
412
|
+
previous_turn_words: 40,
|
|
413
|
+
rare_term_max_share: 0.02,
|
|
414
|
+
tool_result_max_chars: 16e3,
|
|
415
|
+
passages: true,
|
|
416
|
+
passages_per_turn: 2,
|
|
417
|
+
early_query: {
|
|
418
|
+
pattern: "\\b(original|originally|initial|initially|at first|at the start|used to|previous|previously|first (draft|version|proposal|quote|offer|reply|round|email|contract))\\b",
|
|
419
|
+
flags: "i",
|
|
420
|
+
note: "A question about how things started: no recency prior for it, or later turns on the same subject push the early one out (20 Oct, documents case)."
|
|
421
|
+
},
|
|
422
|
+
asked_kinds: {
|
|
423
|
+
$comment: "A question that asks for a kind of value (a number to call, an email, an address, an amount, a date, a percentage) also ranks the passages that hold one, among those sharing a query word: 'the best number to reach Renata' found her email but showed 'Best, Renata', not the line with the number (20 Oct).",
|
|
424
|
+
phone: { query: "\\b(phone|mobile|cell|telephone|call|reach|contact|direct line|best number)\\b", value: "(?:\\+\\d{1,3}[\\s.-]?)?\\(?\\d{2,4}\\)?[\\s.-]\\d{3,4}[\\s.-]\\d{3,4}\\b" },
|
|
425
|
+
email: { query: "\\b(e-?mail|email address|write to)\\b", value: "[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)+" },
|
|
426
|
+
address: { query: "\\b(address|street|located|postal|where (is|are|do|does|should))\\b", value: "\\b\\d{1,5}\\s+[A-Z][a-z]+(?:\\s[A-Z][a-z]+)*\\s(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Way|Court|Ct|Place|Pl|Rua|Avenida)\\b" },
|
|
427
|
+
money: { query: "\\b(how much|cost|costs|price|fee|fees|charge|charges|pay|paid|budget|amount|quote|quoted|spend)\\b", value: "(?:[$\u20AC\xA3\xA5\u20B9]|\\b(?:USD|EUR|GBP|CAD|INR|AUD)\\s?)\\d" },
|
|
428
|
+
percent: { query: "(percent|percentage|%)", value: "\\d(?:\\.\\d+)?\\s?%" },
|
|
429
|
+
date: { query: "\\b(when|what date|which day|deadline|due|by what date)\\b", value: "\\b(?:\\d{4}-\\d{2}-\\d{2}|\\d{1,2}(?:st|nd|rd|th)?\\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\s+\\d{1,2})\\b" }
|
|
430
|
+
},
|
|
431
|
+
irregular_forms: {
|
|
432
|
+
sold: "sell",
|
|
433
|
+
paid: "pay",
|
|
434
|
+
sent: "send",
|
|
435
|
+
bought: "buy",
|
|
436
|
+
made: "make",
|
|
437
|
+
got: "get",
|
|
438
|
+
gotten: "get",
|
|
439
|
+
took: "take",
|
|
440
|
+
taken: "take",
|
|
441
|
+
gave: "give",
|
|
442
|
+
given: "give",
|
|
443
|
+
went: "go",
|
|
444
|
+
gone: "go",
|
|
445
|
+
told: "tell",
|
|
446
|
+
left: "leave",
|
|
447
|
+
met: "meet",
|
|
448
|
+
kept: "keep",
|
|
449
|
+
found: "find",
|
|
450
|
+
held: "hold",
|
|
451
|
+
brought: "bring",
|
|
452
|
+
thought: "think",
|
|
453
|
+
flew: "fly",
|
|
454
|
+
flown: "fly",
|
|
455
|
+
wrote: "write",
|
|
456
|
+
written: "write",
|
|
457
|
+
chose: "choose",
|
|
458
|
+
chosen: "choose",
|
|
459
|
+
spoke: "speak",
|
|
460
|
+
spoken: "speak",
|
|
461
|
+
broke: "break",
|
|
462
|
+
broken: "break",
|
|
463
|
+
began: "begin",
|
|
464
|
+
begun: "begin",
|
|
465
|
+
ran: "run",
|
|
466
|
+
came: "come",
|
|
467
|
+
saw: "see",
|
|
468
|
+
seen: "see",
|
|
469
|
+
knew: "know",
|
|
470
|
+
known: "know",
|
|
471
|
+
drove: "drive",
|
|
472
|
+
driven: "drive",
|
|
473
|
+
built: "build",
|
|
474
|
+
spent: "spend",
|
|
475
|
+
lost: "lose",
|
|
476
|
+
won: "win",
|
|
477
|
+
lent: "lend",
|
|
478
|
+
felt: "feel",
|
|
479
|
+
heard: "hear",
|
|
480
|
+
understood: "understand",
|
|
481
|
+
forgot: "forget",
|
|
482
|
+
forgotten: "forget",
|
|
483
|
+
ate: "eat",
|
|
484
|
+
eaten: "eat",
|
|
485
|
+
fell: "fall",
|
|
486
|
+
fallen: "fall",
|
|
487
|
+
grew: "grow",
|
|
488
|
+
grown: "grow",
|
|
489
|
+
hid: "hide",
|
|
490
|
+
hidden: "hide",
|
|
491
|
+
rode: "ride",
|
|
492
|
+
rang: "ring",
|
|
493
|
+
rose: "rise",
|
|
494
|
+
sang: "sing",
|
|
495
|
+
sat: "sit",
|
|
496
|
+
slept: "sleep",
|
|
497
|
+
stood: "stand",
|
|
498
|
+
stole: "steal",
|
|
499
|
+
swam: "swim",
|
|
500
|
+
taught: "teach",
|
|
501
|
+
threw: "throw",
|
|
502
|
+
thrown: "throw",
|
|
503
|
+
woke: "wake",
|
|
504
|
+
wore: "wear",
|
|
505
|
+
worn: "wear",
|
|
506
|
+
led: "lead",
|
|
507
|
+
dealt: "deal",
|
|
508
|
+
meant: "mean",
|
|
509
|
+
sought: "seek",
|
|
510
|
+
caught: "catch",
|
|
511
|
+
fought: "fight",
|
|
512
|
+
bent: "bend",
|
|
513
|
+
bit: "bite",
|
|
514
|
+
bled: "bleed",
|
|
515
|
+
blew: "blow",
|
|
516
|
+
bred: "breed",
|
|
517
|
+
drew: "draw",
|
|
518
|
+
drawn: "draw",
|
|
519
|
+
drank: "drink",
|
|
520
|
+
fed: "feed",
|
|
521
|
+
forgave: "forgive",
|
|
522
|
+
froze: "freeze",
|
|
523
|
+
hung: "hang",
|
|
524
|
+
laid: "lay",
|
|
525
|
+
lit: "light",
|
|
526
|
+
overpaid: "overpay",
|
|
527
|
+
rebuilt: "rebuild",
|
|
528
|
+
resent: "resend",
|
|
529
|
+
shook: "shake",
|
|
530
|
+
shot: "shoot",
|
|
531
|
+
shut: "shut",
|
|
532
|
+
sank: "sink",
|
|
533
|
+
slid: "slide",
|
|
534
|
+
spun: "spin",
|
|
535
|
+
struck: "strike",
|
|
536
|
+
swore: "swear",
|
|
537
|
+
swept: "sweep",
|
|
538
|
+
tore: "tear",
|
|
539
|
+
withdrew: "withdraw"
|
|
540
|
+
}
|
|
355
541
|
}
|
|
356
542
|
};
|
|
357
543
|
|
|
@@ -384,6 +570,8 @@ var BM25_PARAMS = CONTRACT.bm25;
|
|
|
384
570
|
var STOP_WORDS = new Set(CONTRACT.bm25.stop_words);
|
|
385
571
|
var SUFFIXES = CONTRACT.bm25.suffixes;
|
|
386
572
|
var ALIAS_TOKEN_SOURCE = CONTRACT.bm25.alias_token.pattern;
|
|
573
|
+
var HISTORY_SEARCH = CONTRACT.history_search;
|
|
574
|
+
var SEMANTIC_MATCH = CONTRACT.semantic_match;
|
|
387
575
|
function isRecallQuery(searchQuery) {
|
|
388
576
|
for (const q of _nullishCoalesce(searchQuery, () => ( []))) {
|
|
389
577
|
if (!q) continue;
|
|
@@ -497,6 +685,46 @@ function rungKey(scopePath) {
|
|
|
497
685
|
function rungServes(bundleRung, requestRung) {
|
|
498
686
|
return (bundleRung || null) === (requestRung || null);
|
|
499
687
|
}
|
|
688
|
+
function unownedElsewhere(entryEntity, entryConversation, conversationId) {
|
|
689
|
+
return entryEntity === ANY_SCOPE && entryConversation !== null && entryConversation !== conversationId;
|
|
690
|
+
}
|
|
691
|
+
function canonicalQuery(query) {
|
|
692
|
+
let out = query;
|
|
693
|
+
for (const [lang, rules] of Object.entries(_nullishCoalesce(SEMANTIC_MATCH.self_pronouns, () => ( {})))) {
|
|
694
|
+
if (lang.startsWith("$") || typeof rules === "string") continue;
|
|
695
|
+
for (const [pattern, repl] of rules) out = out.replace(new RegExp(pattern, "gi"), repl);
|
|
696
|
+
}
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
function askedDetailPatterns(queries) {
|
|
700
|
+
const out = [];
|
|
701
|
+
for (const [name, spec] of Object.entries(_nullishCoalesce(SEMANTIC_MATCH.asked_details, () => ( {})))) {
|
|
702
|
+
if (name.startsWith("$") || typeof spec !== "object") continue;
|
|
703
|
+
const q = new RegExp(spec.query, "i");
|
|
704
|
+
if (queries.some((x) => q.test(x))) out.push(new RegExp(spec.value, "i"));
|
|
705
|
+
}
|
|
706
|
+
return out;
|
|
707
|
+
}
|
|
708
|
+
function itemQuestions(item) {
|
|
709
|
+
const qs = item.questions;
|
|
710
|
+
return Array.isArray(qs) ? qs.filter((q) => typeof q === "string" && q.trim() !== "").map((q) => q.trim()) : [];
|
|
711
|
+
}
|
|
712
|
+
var MATCHERS = ["bm25", "semantic"];
|
|
713
|
+
function matcherSetting() {
|
|
714
|
+
const raw = (_nullishCoalesce(_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_SDK_CACHE_MATCHER"), () => ( ""))).trim().toLowerCase();
|
|
715
|
+
return MATCHERS.includes(raw) ? raw : SEMANTIC_MATCH.mode;
|
|
716
|
+
}
|
|
717
|
+
function cosine(a, b) {
|
|
718
|
+
let dot = 0;
|
|
719
|
+
let na = 0;
|
|
720
|
+
let nb = 0;
|
|
721
|
+
for (let i = 0; i < a.length && i < b.length; i++) {
|
|
722
|
+
dot += a[i] * b[i];
|
|
723
|
+
na += a[i] * a[i];
|
|
724
|
+
nb += b[i] * b[i];
|
|
725
|
+
}
|
|
726
|
+
return na && nb ? dot / Math.sqrt(na * nb) : 0;
|
|
727
|
+
}
|
|
500
728
|
var DEFAULT_TTL_SECONDS = 1800;
|
|
501
729
|
var DEFAULT_MAX_BUNDLES = 50;
|
|
502
730
|
var AnticipationCache = (_class2 = class {
|
|
@@ -509,11 +737,81 @@ var AnticipationCache = (_class2 = class {
|
|
|
509
737
|
__init3() {this.corpusVocab = /* @__PURE__ */ new Set()}
|
|
510
738
|
__init4() {this.bm25 = null}
|
|
511
739
|
__init5() {this.bm25Dirty = true}
|
|
512
|
-
|
|
740
|
+
// Semantic matching (CONTRACT.semantic_match). Python: the same fields.
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
/** Embedding runs one batch after another; `whenEmbedded` awaits the queue. */
|
|
751
|
+
__init6() {this.embedding = Promise.resolve()}
|
|
752
|
+
constructor(options = {}) {;_class2.prototype.__init.call(this);_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);
|
|
513
753
|
this.ttlSeconds = _nullishCoalesce(options.ttlSeconds, () => ( DEFAULT_TTL_SECONDS));
|
|
514
754
|
this.maxBundles = _nullishCoalesce(options.maxBundles, () => ( DEFAULT_MAX_BUNDLES));
|
|
515
755
|
this.now = _nullishCoalesce(options.now, () => ( monotonicNow));
|
|
516
756
|
this.onLookup = options.onLookup;
|
|
757
|
+
this.encoder = _nullishCoalesce(options.encoder, () => ( null));
|
|
758
|
+
this.matcher = _nullishCoalesce(options.matcher, () => ( matcherSetting()));
|
|
759
|
+
this.minCosine = _nullishCoalesce(options.minCosine, () => ( SEMANTIC_MATCH.min_cosine));
|
|
760
|
+
this.serveMinCosine = _nullishCoalesce(options.serveMinCosine, () => ( SEMANTIC_MATCH.serve_min_cosine));
|
|
761
|
+
this.askedKind = _nullishCoalesce(options.askedKind, () => ( SEMANTIC_MATCH.asked_kind));
|
|
762
|
+
this.itemQuestions = _nullishCoalesce(options.itemQuestions, () => ( SEMANTIC_MATCH.item_questions));
|
|
763
|
+
this.canonical = _nullishCoalesce(options.canonical, () => ( SEMANTIC_MATCH.canonical_query));
|
|
764
|
+
this.bundleServeMinCosine = _nullishCoalesce(options.bundleServeMinCosine, () => ( SEMANTIC_MATCH.bundle_serve_min_cosine));
|
|
765
|
+
this.sureMinCosine = _nullishCoalesce(options.sureMinCosine, () => ( SEMANTIC_MATCH.sure_min_cosine));
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Hand over the local embedding model once it has loaded. Items already
|
|
769
|
+
* cached are embedded off the lookup path.
|
|
770
|
+
*/
|
|
771
|
+
setEncoder(encoder) {
|
|
772
|
+
this.encoder = encoder;
|
|
773
|
+
this.embedItems(this.items);
|
|
774
|
+
}
|
|
775
|
+
/** True when lookups match by meaning: matcher "semantic" and a model loaded. */
|
|
776
|
+
get semantic() {
|
|
777
|
+
return this.matcher === "semantic" && this.encoder !== null;
|
|
778
|
+
}
|
|
779
|
+
/** Resolves once every item stored so far has had its turn to be embedded. */
|
|
780
|
+
whenEmbedded() {
|
|
781
|
+
return this.embedding;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Give `items` their vectors, queued behind any earlier batch, so neither a
|
|
785
|
+
* bundle arriving on the stream nor a lookup waits for the model. A lookup
|
|
786
|
+
* before they are ready matches those items by BM25 alone.
|
|
787
|
+
*/
|
|
788
|
+
embedItems(items) {
|
|
789
|
+
const encoder = this.encoder;
|
|
790
|
+
if (encoder === null || this.matcher !== "semantic") return;
|
|
791
|
+
const todo = items.filter((i) => i.vecModel !== encoder.model);
|
|
792
|
+
if (todo.length === 0) return;
|
|
793
|
+
this.embedding = this.embedding.then(async () => {
|
|
794
|
+
try {
|
|
795
|
+
const texts = [];
|
|
796
|
+
for (const i of todo) {
|
|
797
|
+
texts.push(i.content);
|
|
798
|
+
if (this.itemQuestions) texts.push(...i.questions);
|
|
799
|
+
}
|
|
800
|
+
const vecs = await encoder.encode(texts);
|
|
801
|
+
let k = 0;
|
|
802
|
+
for (const item of todo) {
|
|
803
|
+
const n = this.itemQuestions ? item.questions.length : 0;
|
|
804
|
+
const v = vecs[k];
|
|
805
|
+
if (v) {
|
|
806
|
+
item.vec = Array.from(v);
|
|
807
|
+
item.qvecs = vecs.slice(k + 1, k + 1 + n).map((x) => Array.from(x));
|
|
808
|
+
item.vecModel = encoder.model;
|
|
809
|
+
}
|
|
810
|
+
k += 1 + n;
|
|
811
|
+
}
|
|
812
|
+
} catch (e2) {
|
|
813
|
+
}
|
|
814
|
+
});
|
|
517
815
|
}
|
|
518
816
|
get size() {
|
|
519
817
|
return this.entries.size;
|
|
@@ -542,11 +840,22 @@ var AnticipationCache = (_class2 = class {
|
|
|
542
840
|
for (const item of _nullishCoalesce(list, () => ( []))) {
|
|
543
841
|
const content = String(_nullishCoalesce(item.content, () => ( "")));
|
|
544
842
|
const tokens = tokenize(content);
|
|
545
|
-
this.items.push({
|
|
843
|
+
this.items.push({
|
|
844
|
+
bundleId: bundle.bundleId,
|
|
845
|
+
itemType,
|
|
846
|
+
content,
|
|
847
|
+
tokens,
|
|
848
|
+
itemDict: item,
|
|
849
|
+
vec: null,
|
|
850
|
+
vecModel: "",
|
|
851
|
+
questions: itemQuestions(item),
|
|
852
|
+
qvecs: []
|
|
853
|
+
});
|
|
546
854
|
for (const t of tokens) this.corpusVocab.add(t);
|
|
547
855
|
}
|
|
548
856
|
}
|
|
549
857
|
this.bm25Dirty = true;
|
|
858
|
+
this.embedItems(this.items.filter((i) => i.bundleId === bundle.bundleId));
|
|
550
859
|
while (this.entries.size > this.maxBundles) {
|
|
551
860
|
let oldest = null;
|
|
552
861
|
for (const e of this.entries.values()) {
|
|
@@ -556,6 +865,31 @@ var AnticipationCache = (_class2 = class {
|
|
|
556
865
|
this.dropBundle(oldest.bundleId);
|
|
557
866
|
}
|
|
558
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* `lookup`, matching by meaning when a model is loaded: embeds the queries,
|
|
870
|
+
* then looks up with their vectors. A query the model cannot embed is looked
|
|
871
|
+
* up by BM25, as without a model. Python does this inside `lookup`.
|
|
872
|
+
*/
|
|
873
|
+
async lookupAsync(params) {
|
|
874
|
+
const encoder = this.encoder;
|
|
875
|
+
let queries = (_nullishCoalesce(params.searchQuery, () => ( []))).filter((q) => typeof q === "string" && q.trim() !== "").map((q) => q.trim()).slice(0, SEMANTIC_MATCH.max_queries);
|
|
876
|
+
const recall = _chunkZKOJDUN4cjs.getEnvFlag.call(void 0, CONTRACT.recall_bypass.env_flag) && isRecallQuery(_nullishCoalesce(params.searchQuery, () => ( null)));
|
|
877
|
+
if (!this.semantic || encoder === null || queries.length === 0 || recall || this.items.length === 0) {
|
|
878
|
+
return this.lookup(params);
|
|
879
|
+
}
|
|
880
|
+
if (this.canonical === "canon" || this.canonical === "both") {
|
|
881
|
+
const canon = queries.map(canonicalQuery);
|
|
882
|
+
const changed = canon.filter((c, i) => c !== queries[i]);
|
|
883
|
+
queries = this.canonical === "both" ? [...queries, ...changed] : canon;
|
|
884
|
+
}
|
|
885
|
+
let queryVectors = null;
|
|
886
|
+
try {
|
|
887
|
+
queryVectors = await (_nullishCoalesce(encoder.encodeQuery, () => ( encoder.encode))).call(encoder, queries);
|
|
888
|
+
} catch (e3) {
|
|
889
|
+
queryVectors = null;
|
|
890
|
+
}
|
|
891
|
+
return this.lookup({ ...params, queryVectors });
|
|
892
|
+
}
|
|
559
893
|
lookup(params) {
|
|
560
894
|
const {
|
|
561
895
|
searchQuery = null,
|
|
@@ -564,7 +898,8 @@ var AnticipationCache = (_class2 = class {
|
|
|
564
898
|
clientId = null,
|
|
565
899
|
conversationId = null,
|
|
566
900
|
scopeRung = null,
|
|
567
|
-
maxItems = 10
|
|
901
|
+
maxItems = 10,
|
|
902
|
+
queryVectors = null
|
|
568
903
|
} = params;
|
|
569
904
|
this.evictExpired();
|
|
570
905
|
const accepted = this.buildAcceptedScope(entityId, customerId, clientId);
|
|
@@ -590,13 +925,22 @@ var AnticipationCache = (_class2 = class {
|
|
|
590
925
|
}
|
|
591
926
|
const hasQuery = (_nullishCoalesce(searchQuery, () => ( []))).some((q) => q && q.trim() !== "");
|
|
592
927
|
if (!hasQuery) {
|
|
593
|
-
return this.freshnessLookup(
|
|
928
|
+
return this.freshnessLookup(
|
|
929
|
+
_nullishCoalesce(entityId, () => ( customerId)),
|
|
930
|
+
conversationId,
|
|
931
|
+
_nullishCoalesce(scopeRung, () => ( null)),
|
|
932
|
+
maxItems,
|
|
933
|
+
telemetry
|
|
934
|
+
);
|
|
594
935
|
}
|
|
595
936
|
if (_chunkZKOJDUN4cjs.getEnvFlag.call(void 0, CONTRACT.recall_bypass.env_flag) && isRecallQuery(searchQuery)) {
|
|
596
937
|
return this.miss(telemetry, "recall_bypass");
|
|
597
938
|
}
|
|
598
939
|
const queryTokens = (_nullishCoalesce(searchQuery, () => ( []))).flatMap((q) => tokenize(q));
|
|
599
940
|
telemetry.bm25QueryTokens = queryTokens;
|
|
941
|
+
if (this.semantic && queryVectors !== null && queryVectors.length > 0) {
|
|
942
|
+
return this.semanticLookup(queryTokens, queryVectors, accepted, conversationId, _nullishCoalesce(scopeRung, () => ( null)), maxItems, telemetry);
|
|
943
|
+
}
|
|
600
944
|
if (queryTokens.length === 0) {
|
|
601
945
|
return this.miss(telemetry, "no_query_tokens");
|
|
602
946
|
}
|
|
@@ -663,6 +1007,92 @@ var AnticipationCache = (_class2 = class {
|
|
|
663
1007
|
if (coverageMin !== null && coverage < coverageMin) {
|
|
664
1008
|
return this.miss(telemetry, "coverage_gate");
|
|
665
1009
|
}
|
|
1010
|
+
return this.serve(top, telemetry, coverage, "bm25_score");
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Match by meaning: a lookup is a hit when an item's cosine to a query
|
|
1014
|
+
* reaches `minCosine`, and the hit also serves the items at `serveMinCosine`
|
|
1015
|
+
* or above. An item with no vector yet is matched by BM25 with its novel-term
|
|
1016
|
+
* gate, as before. Python: `_semantic_lookup`, which explains the choices.
|
|
1017
|
+
*/
|
|
1018
|
+
semanticLookup(queryTokens, queryVectors, accepted, conversationId, scopeRung, maxItems, telemetry) {
|
|
1019
|
+
const model = _nullishCoalesce(_optionalChain([this, 'access', _2 => _2.encoder, 'optionalAccess', _3 => _3.model]), () => ( ""));
|
|
1020
|
+
if (queryTokens.length > 0 && (this.bm25Dirty || this.bm25 === null) && this.items.length > 0) {
|
|
1021
|
+
this.bm25 = new BM25(this.items.map((i) => i.tokens));
|
|
1022
|
+
this.bm25Dirty = false;
|
|
1023
|
+
}
|
|
1024
|
+
const bm25Scores = queryTokens.length > 0 && this.bm25 ? this.bm25.scores(queryTokens) : [];
|
|
1025
|
+
const effectiveThreshold = Math.max(
|
|
1026
|
+
EFFECTIVE_FLOOR,
|
|
1027
|
+
Math.min(THRESHOLDS.bm25, QUERY_TOKEN_SCALE * Math.max(queryTokens.length, 1))
|
|
1028
|
+
);
|
|
1029
|
+
telemetry.bm25Threshold = round4(effectiveThreshold);
|
|
1030
|
+
const uniqueStems = new Set(queryTokens);
|
|
1031
|
+
let novel = 0;
|
|
1032
|
+
for (const s of uniqueStems) if (!this.corpusVocab.has(s)) novel++;
|
|
1033
|
+
const novelRatio = uniqueStems.size ? novel / uniqueStems.size : 1;
|
|
1034
|
+
telemetry.novelTermRatio = round4(novelRatio);
|
|
1035
|
+
const wordRoute = queryTokens.length > 0 && !(this.corpusVocab.size >= THRESHOLDS.minCorpusForNovelGate && novelRatio >= THRESHOLDS.novelTerm);
|
|
1036
|
+
const validBundles = this.getValidBundleIds(accepted, conversationId, scopeRung);
|
|
1037
|
+
const picked = [];
|
|
1038
|
+
const companions = [];
|
|
1039
|
+
const rejected = [];
|
|
1040
|
+
this.items.forEach((item, idx) => {
|
|
1041
|
+
const b = _nullishCoalesce(bm25Scores[idx], () => ( 0));
|
|
1042
|
+
const reject = (score, cos2, reason) => rejected.push({
|
|
1043
|
+
score,
|
|
1044
|
+
payload: {
|
|
1045
|
+
item_index: idx,
|
|
1046
|
+
bundle_id: item.bundleId,
|
|
1047
|
+
item_type: item.itemType,
|
|
1048
|
+
content_first_120: item.content.slice(0, PREVIEW_CHARS),
|
|
1049
|
+
bm25_score: round4(b),
|
|
1050
|
+
cosine: cos2 === null ? null : round4(cos2),
|
|
1051
|
+
reason
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
if (!validBundles.has(item.bundleId)) return reject(b, null, "scope_filter_excluded");
|
|
1055
|
+
let passes;
|
|
1056
|
+
let rank;
|
|
1057
|
+
let cos = null;
|
|
1058
|
+
if (item.vec !== null && item.vecModel === model) {
|
|
1059
|
+
const vecs = [item.vec, ...item.qvecs];
|
|
1060
|
+
cos = Math.max(...queryVectors.flatMap((q) => vecs.map((v) => cosine(q, v))));
|
|
1061
|
+
passes = cos >= this.minCosine;
|
|
1062
|
+
rank = cos;
|
|
1063
|
+
} else {
|
|
1064
|
+
passes = wordRoute && b >= effectiveThreshold;
|
|
1065
|
+
rank = this.minCosine;
|
|
1066
|
+
}
|
|
1067
|
+
if (!passes && cos !== null && cos >= Math.min(this.serveMinCosine, this.bundleServeMinCosine)) {
|
|
1068
|
+
companions.push({ score: cos, item });
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (!passes) return reject(rank, cos, "below_threshold");
|
|
1072
|
+
picked.push({ score: rank, item });
|
|
1073
|
+
});
|
|
1074
|
+
rejected.sort((a, b) => b.score - a.score);
|
|
1075
|
+
telemetry.itemsRejected = rejected.slice(0, REJECTED_CAP).map((r) => r.payload);
|
|
1076
|
+
if (picked.length === 0) return this.miss(telemetry, "no_items_above_threshold");
|
|
1077
|
+
picked.sort((a, b) => b.score - a.score);
|
|
1078
|
+
const hitBundles = new Set(picked.map((p) => p.item.bundleId));
|
|
1079
|
+
const served = companions.filter(
|
|
1080
|
+
(c) => c.score >= this.serveMinCosine || hitBundles.has(c.item.bundleId) && c.score >= this.bundleServeMinCosine
|
|
1081
|
+
);
|
|
1082
|
+
served.sort((a, b) => b.score - a.score);
|
|
1083
|
+
const top = [...picked, ...served].slice(0, maxItems);
|
|
1084
|
+
if (this.askedKind) {
|
|
1085
|
+
const patterns = askedDetailPatterns(telemetry.searchQuery);
|
|
1086
|
+
if (patterns.length > 0 && !top.some(({ item }) => patterns.some((p) => p.test(item.content)))) {
|
|
1087
|
+
return this.miss(telemetry, "asked_kind_missing");
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
const hit = this.serve(top, telemetry, null, "cosine");
|
|
1091
|
+
hit.band = picked[0].score >= this.sureMinCosine ? "sure" : "borderline";
|
|
1092
|
+
return hit;
|
|
1093
|
+
}
|
|
1094
|
+
/** A hit: renew the leases of the bundles used, report, merge the items. */
|
|
1095
|
+
serve(top, telemetry, coverage, scoreKey) {
|
|
666
1096
|
const itemsByType = {};
|
|
667
1097
|
const bundleIds = /* @__PURE__ */ new Set();
|
|
668
1098
|
for (const { item } of top) {
|
|
@@ -683,14 +1113,14 @@ var AnticipationCache = (_class2 = class {
|
|
|
683
1113
|
bundle_id: item.bundleId,
|
|
684
1114
|
item_type: item.itemType,
|
|
685
1115
|
content_first_120: item.content.slice(0, PREVIEW_CHARS),
|
|
686
|
-
|
|
1116
|
+
[scoreKey]: round4(score)
|
|
687
1117
|
}));
|
|
688
1118
|
this.fire(telemetry);
|
|
689
1119
|
return {
|
|
690
1120
|
itemsByType,
|
|
691
1121
|
bundleIds: [...bundleIds],
|
|
692
1122
|
score: top[0].score,
|
|
693
|
-
coverage
|
|
1123
|
+
coverage: _nullishCoalesce(coverage, () => ( 1))
|
|
694
1124
|
};
|
|
695
1125
|
}
|
|
696
1126
|
/**
|
|
@@ -796,6 +1226,7 @@ var AnticipationCache = (_class2 = class {
|
|
|
796
1226
|
if (entry.bundleType !== "user_summary") continue;
|
|
797
1227
|
if (!rungServes(entry.scopeRung, scopeRung)) continue;
|
|
798
1228
|
if (entry.entityId !== entityId && entry.entityId !== ANY_SCOPE) continue;
|
|
1229
|
+
if (unownedElsewhere(entry.entityId, entry.conversationId, null)) continue;
|
|
799
1230
|
if (freshest === null || entry.storedAt > freshest.storedAt) freshest = entry;
|
|
800
1231
|
}
|
|
801
1232
|
if (freshest === null) return null;
|
|
@@ -851,12 +1282,12 @@ var AnticipationCache = (_class2 = class {
|
|
|
851
1282
|
}
|
|
852
1283
|
fire(telemetry) {
|
|
853
1284
|
try {
|
|
854
|
-
_optionalChain([this, 'access',
|
|
855
|
-
} catch (
|
|
1285
|
+
_optionalChain([this, 'access', _4 => _4.onLookup, 'optionalCall', _5 => _5(telemetry)]);
|
|
1286
|
+
} catch (e4) {
|
|
856
1287
|
}
|
|
857
1288
|
}
|
|
858
|
-
freshnessLookup(
|
|
859
|
-
const candidates = [...this.entries.values()].filter((e) => rungServes(e.scopeRung, scopeRung)).filter((e) => entityId ===
|
|
1289
|
+
freshnessLookup(owner, conversationId, scopeRung, maxItems, telemetry) {
|
|
1290
|
+
const candidates = [...this.entries.values()].filter((e) => rungServes(e.scopeRung, scopeRung)).filter((e) => e.entityId === ANY_SCOPE || owner !== null && owner !== "" && e.entityId === owner).filter((e) => !unownedElsewhere(e.entityId, e.conversationId, conversationId)).sort((a, b) => b.storedAt - a.storedAt);
|
|
860
1291
|
if (candidates.length === 0) return this.miss(telemetry, "empty");
|
|
861
1292
|
const newest = candidates[0];
|
|
862
1293
|
const itemsByType = {};
|
|
@@ -900,6 +1331,7 @@ var AnticipationCache = (_class2 = class {
|
|
|
900
1331
|
if (!rungServes(entry.scopeRung, scopeRung)) continue;
|
|
901
1332
|
if (!accepted.has(entry.entityId)) continue;
|
|
902
1333
|
if (entry.conversationId && conversationId && entry.conversationId !== conversationId) continue;
|
|
1334
|
+
if (unownedElsewhere(entry.entityId, entry.conversationId, conversationId)) continue;
|
|
903
1335
|
valid.add(entry.bundleId);
|
|
904
1336
|
}
|
|
905
1337
|
return valid;
|
|
@@ -944,5 +1376,10 @@ function round4(n) {
|
|
|
944
1376
|
|
|
945
1377
|
|
|
946
1378
|
|
|
947
|
-
|
|
948
|
-
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
exports.CONTRACT = CONTRACT; exports.THRESHOLDS = THRESHOLDS; exports.HISTORY_SEARCH = HISTORY_SEARCH; exports.SEMANTIC_MATCH = SEMANTIC_MATCH; exports.isRecallQuery = isRecallQuery; exports.stem = stem; exports.tokenize = tokenize; exports.BM25 = BM25; exports.ANY_SCOPE = ANY_SCOPE; exports.rungKey = rungKey; exports.rungServes = rungServes; exports.unownedElsewhere = unownedElsewhere; exports.canonicalQuery = canonicalQuery; exports.MATCHERS = MATCHERS; exports.AnticipationCache = AnticipationCache;
|
|
1385
|
+
//# sourceMappingURL=chunk-ICUFS3B5.cjs.map
|