@konneal/engine 0.2.44 → 0.2.46
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/{ask-JOX2D7KH.js → ask-6QPD3QA5.js} +1 -1
- package/dist/ask.d.ts +3 -0
- package/dist/certificates.d.ts +4 -0
- package/dist/{chunk-HDUSO5S5.js → chunk-SE67PTJ6.js} +12 -76
- package/dist/worker_public/src/index.js +2 -2
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +3 -92
- package/workers/worker_public/src/certificates.ts +15 -1
package/dist/ask.d.ts
CHANGED
|
@@ -2,4 +2,7 @@ import type { Env } from "./env";
|
|
|
2
2
|
export type { Env };
|
|
3
3
|
import { type ApiKey } from "./lib/http";
|
|
4
4
|
declare function handleAsk(env: Env, ctx: ExecutionContext, req: Request, tier: "anon" | "key" | "member", key: ApiKey | null): Promise<Response>;
|
|
5
|
+
/** RAGAS-style metric battery (G13): judge an (question, answer, passages)
|
|
6
|
+
* triple — faithfulness, answer relevancy, context precision. Driven by
|
|
7
|
+
* tests/eval-suite.mjs; prompts are data; refuses nothing, judges only. */
|
|
5
8
|
export { handleAsk };
|
package/dist/certificates.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export declare function isRegisterShaped(query: string): boolean;
|
|
|
15
15
|
* 2+ characters that are not register question words, plus bare model
|
|
16
16
|
* numbers ("190", "HM14H1"). Upper-cased tokens match case-insensitively
|
|
17
17
|
* in SQL LIKE. */
|
|
18
|
+
/** The family the question names ("R 60", "R60", "D 31") — the register
|
|
19
|
+
* search filters to it, so a question about R 60 never presents R 76
|
|
20
|
+
* rows. */
|
|
21
|
+
export declare function queryFamily(query: string): string | null;
|
|
18
22
|
export declare function registerTokens(query: string): string[];
|
|
19
23
|
/** The register query for a token set. Numeric tokens (a model number
|
|
20
24
|
* like "190") must match as a STANDALONE value — `LIKE '%190%'` also
|
|
@@ -1743,14 +1743,15 @@ function cacheKeyMaterial(query, lang, salt) {
|
|
|
1743
1743
|
function exactCacheKey(indexVersion, gen, ns, queryHash) {
|
|
1744
1744
|
return `a:${indexVersion}:g${gen}:${ns}:${queryHash}`;
|
|
1745
1745
|
}
|
|
1746
|
-
function semanticCacheKey(indexVersion, gen, signature) {
|
|
1747
|
-
return `sc:${indexVersion}:g${gen}:${signature}`;
|
|
1748
|
-
}
|
|
1749
1746
|
|
|
1750
1747
|
// workers/worker_public/src/certificates.ts
|
|
1751
1748
|
function isRegisterShaped(query) {
|
|
1752
1749
|
return /\bcertif(ied|icates?|ication)s?\b/i.test(query) && /\b(is|are|still|currently|valid|status|suspended|revoked|was|were)\b/i.test(query);
|
|
1753
1750
|
}
|
|
1751
|
+
function queryFamily(query) {
|
|
1752
|
+
const m = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(query);
|
|
1753
|
+
return m ? `${m[1].toUpperCase()}${m[2].padStart(2, "0")}` : null;
|
|
1754
|
+
}
|
|
1754
1755
|
function registerTokens(query) {
|
|
1755
1756
|
const stop = /* @__PURE__ */ new Set([
|
|
1756
1757
|
"the",
|
|
@@ -1833,7 +1834,13 @@ async function searchRegister(db, query) {
|
|
|
1833
1834
|
if (!isRegisterShaped(query)) return null;
|
|
1834
1835
|
const tokens2 = registerTokens(query);
|
|
1835
1836
|
if (!tokens2.length) return null;
|
|
1836
|
-
const
|
|
1837
|
+
const built = buildRegisterQuery(tokens2);
|
|
1838
|
+
let { sql, params } = built;
|
|
1839
|
+
const family = queryFamily(query);
|
|
1840
|
+
if (family) {
|
|
1841
|
+
sql = sql.replace(" WHERE ", " WHERE family = ?0 AND ");
|
|
1842
|
+
params.unshift(family);
|
|
1843
|
+
}
|
|
1837
1844
|
try {
|
|
1838
1845
|
const res = await db.prepare(sql).bind(...params).all();
|
|
1839
1846
|
return { rows: res.results ?? [], tokens: tokens2 };
|
|
@@ -2083,22 +2090,6 @@ async function handleAsk(env, ctx, req, tier, key) {
|
|
|
2083
2090
|
}
|
|
2084
2091
|
}
|
|
2085
2092
|
let understanding = null;
|
|
2086
|
-
const nodeScoped = !!modelNodeRefIn(q.query) || !!modelNodeRefIn(declaredCtx?.label);
|
|
2087
|
-
if (!cached && !nodeScoped && !contextual && !declaredCtx && !draftAct && !apiCallIntent && !q.lang && !userImage && !fresh) {
|
|
2088
|
-
const wv0 = await warmEmbed ?? null;
|
|
2089
|
-
if (wv0) {
|
|
2090
|
-
const sc0 = await semanticCacheGet(env, gen, wv0, salt);
|
|
2091
|
-
if (sc0) {
|
|
2092
|
-
console.log("semantic cache hit (pre-understanding)");
|
|
2093
|
-
telemetry(env, ctx, tier, "ask", null, true, sc0.answer.length, sc0.query_hash, q.lang, "semantic", telemetryMeta());
|
|
2094
|
-
const cctx0 = sc0.context_applied ?? NO_CONTEXT;
|
|
2095
|
-
if (wantsStream) {
|
|
2096
|
-
return sseResponse([{ type: "citations", citations: sc0.citations ?? [], context_applied: cctx0 }, { type: "token", v: sc0.answer }, { type: "done", model: sc0.model, query_hash: sc0.query_hash, similar: true, served_from: "similar", context_applied: cctx0 }], corsHeaders(req));
|
|
2097
|
-
}
|
|
2098
|
-
return json({ ...sc0, similar: true, context_applied: cctx0, quota });
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
2093
|
let optimisticVec = null;
|
|
2103
2094
|
let optimisticHits = [];
|
|
2104
2095
|
const t0 = Date.now();
|
|
@@ -2183,27 +2174,8 @@ async function handleAsk(env, ctx, req, tier, key) {
|
|
|
2183
2174
|
const graphDocNumbers = await graphExpand(env, understanding);
|
|
2184
2175
|
const eNote = await editionNote(env, understanding);
|
|
2185
2176
|
const register = await searchRegister(env.DB, q.query);
|
|
2177
|
+
console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
|
|
2186
2178
|
const regNote = register ? registerNote(register.rows) : void 0;
|
|
2187
|
-
if (understanding?.intent !== "conversational" && !nodeScoped && !contextual && !declaredCtx && !draftAct && !apiCallIntent && !userImage && !fresh) {
|
|
2188
|
-
const warmVec = await warmEmbed ?? null;
|
|
2189
|
-
if (warmVec) {
|
|
2190
|
-
const sc = await semanticCacheGet(env, gen, warmVec, salt);
|
|
2191
|
-
if (sc) {
|
|
2192
|
-
console.log("semantic cache hit");
|
|
2193
|
-
telemetry(env, ctx, tier, "ask", null, true, sc.answer.length, sc.query_hash, q.lang, "semantic", telemetryMeta());
|
|
2194
|
-
const cctx = sc.context_applied ?? NO_CONTEXT;
|
|
2195
|
-
if (wantsStream) {
|
|
2196
|
-
return sseResponse([
|
|
2197
|
-
...readAs() ? [{ type: "read", read: readAs() }] : [],
|
|
2198
|
-
{ type: "citations", citations: sc.citations ?? [], context_applied: cctx },
|
|
2199
|
-
{ type: "token", v: sc.answer },
|
|
2200
|
-
{ type: "done", model: sc.model, query_hash: sc.query_hash, similar: true, served_from: "similar", context_applied: cctx, read: readAs() }
|
|
2201
|
-
], corsHeaders(req));
|
|
2202
|
-
}
|
|
2203
|
-
return json({ ...sc, similar: true, context_applied: cctx, read: readAs(), quota });
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
2179
|
if (understanding?.intent === "conversational") {
|
|
2208
2180
|
const queryHash2 = await sha256Hex(q.query);
|
|
2209
2181
|
const messages2 = [
|
|
@@ -2617,8 +2589,6 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2617
2589
|
console.log("anchors:", streamed.violations.length, "of", streamed.total, "unverified \u2014 not caching");
|
|
2618
2590
|
}
|
|
2619
2591
|
if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && !canonical.includes(refusalAnswer())) {
|
|
2620
|
-
const wv = await warmEmbed ?? null;
|
|
2621
|
-
if (wv) semanticCachePut(env, ctx, gen, wv, salt, { answer: canonical, citations: cites, model, query_hash: queryHash });
|
|
2622
2592
|
ctx.waitUntil(
|
|
2623
2593
|
env.CACHE.put(exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt))), JSON.stringify({ answer: canonical, citations: cites, model, query_hash: queryHash }), { expirationTtl: LIMITS.cacheTtlSec })
|
|
2624
2594
|
);
|
|
@@ -2720,10 +2690,6 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2720
2690
|
const jsonQuality = answerQuality(finalCites.map((c) => c.quality));
|
|
2721
2691
|
const out = { answer, citations: finalCites, ...jsonQuality ? { source_quality: jsonQuality, confidence_note: qualityNote(jsonQuality), ...jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {} } : {}, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {} };
|
|
2722
2692
|
const cacheable = !contextual && !declaredCtx && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
|
|
2723
|
-
if (cacheable) {
|
|
2724
|
-
const warmVec = await warmEmbed ?? null;
|
|
2725
|
-
if (warmVec) semanticCachePut(env, ctx, gen, warmVec, salt, out);
|
|
2726
|
-
}
|
|
2727
2693
|
if (cacheable) {
|
|
2728
2694
|
const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
|
|
2729
2695
|
ctx.waitUntil(env.CACHE.put(ck, JSON.stringify(out), { expirationTtl: LIMITS.cacheTtlSec }));
|
|
@@ -2751,36 +2717,6 @@ function sseResponse(events, cors) {
|
|
|
2751
2717
|
}
|
|
2752
2718
|
});
|
|
2753
2719
|
}
|
|
2754
|
-
function cosine(a, b) {
|
|
2755
|
-
let dot = 0;
|
|
2756
|
-
let na = 0;
|
|
2757
|
-
let nb = 0;
|
|
2758
|
-
for (let i = 0; i < a.length; i++) {
|
|
2759
|
-
dot += a[i] * b[i];
|
|
2760
|
-
na += a[i] * a[i];
|
|
2761
|
-
nb += b[i] * b[i];
|
|
2762
|
-
}
|
|
2763
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb) || 1);
|
|
2764
|
-
}
|
|
2765
|
-
function scSignature(v, salt) {
|
|
2766
|
-
return v.slice(0, 16).map((x) => x.toFixed(2)).join(",") + (salt ? `|s:${salt.length}:${salt.slice(0, 64)}` : "");
|
|
2767
|
-
}
|
|
2768
|
-
async function semanticCacheGet(env, gen, vec, salt) {
|
|
2769
|
-
try {
|
|
2770
|
-
const raw = await env.CACHE.get(semanticCacheKey(env.INDEX_VERSION, gen, scSignature(vec, salt)), "json");
|
|
2771
|
-
if (!raw?.v || !Array.isArray(raw.v) || raw.v.length !== vec.length) return null;
|
|
2772
|
-
if (cosine(raw.v, vec) < 0.97) return null;
|
|
2773
|
-
return raw;
|
|
2774
|
-
} catch {
|
|
2775
|
-
return null;
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
function semanticCachePut(env, ctx, gen, vec, salt, payload) {
|
|
2779
|
-
const v = vec.map((x) => Number(x.toFixed(3)));
|
|
2780
|
-
ctx.waitUntil(
|
|
2781
|
-
env.CACHE.put(semanticCacheKey(env.INDEX_VERSION, gen, scSignature(vec, salt)), JSON.stringify({ v, ...payload }), { expirationTtl: LIMITS.cacheTtlSec })
|
|
2782
|
-
);
|
|
2783
|
-
}
|
|
2784
2720
|
|
|
2785
2721
|
export {
|
|
2786
2722
|
handleMemories,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
modelGroundingBlock,
|
|
10
10
|
scoreJudge,
|
|
11
11
|
standardForDocNumber
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-SE67PTJ6.js";
|
|
13
13
|
import {
|
|
14
14
|
buildMessages,
|
|
15
15
|
citations,
|
|
@@ -999,7 +999,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
999
999
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
1000
1000
|
body: JSON.stringify({ ...args, stream: false })
|
|
1001
1001
|
});
|
|
1002
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
1002
|
+
const res = name === "ask" ? await (await import("../../ask-6QPD3QA5.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-QZDQVB57.js")).handleSearch(env, ctx, inner, tier, key);
|
|
1003
1003
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
1004
1004
|
});
|
|
1005
1005
|
if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"playwright": "^1.62.1",
|
|
28
28
|
"yaml": "^2.9.1"
|
|
29
29
|
},
|
|
30
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.46",
|
|
31
31
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
32
32
|
"license": "BSD-3-Clause",
|
|
33
33
|
"type": "module",
|
|
@@ -20,7 +20,7 @@ import { contractV2, tableRetyped } from "./refs";
|
|
|
20
20
|
import { completeTables, completeFigures } from "./completion";
|
|
21
21
|
import { NO_CONTEXT, appliedContext, contextNote, namedDocumentIn, parseContext, resolveDocScope, syntheticUnderstanding } from "./context";
|
|
22
22
|
import { liveDataConfig, liveTokenFor, opCfg, opTokenMember, resolveLiveAccount, type LiveRecord } from "./livedata";
|
|
23
|
-
import { bindModelNode, licenseBoundaryNote, licenseBoundaryRefusal, licensedEntryForPackage, modelCitation, modelEcho, modelGroundingBlock,
|
|
23
|
+
import { bindModelNode, licenseBoundaryNote, licenseBoundaryRefusal, licensedEntryForPackage, modelCitation, modelEcho, modelGroundingBlock, standardForDocNumber } from "./modelplane";
|
|
24
24
|
import { evaluate as machineEvaluate, verdictNote } from "./verdict";
|
|
25
25
|
import { evaluateConditionSets, quantitiesIn, type ConditionVerdict } from "./conditions";
|
|
26
26
|
import { evaluateAggregation, type AggregationVerdict } from "./aggregation";
|
|
@@ -31,7 +31,7 @@ import { detectApiCallIntent, prepareApiCall } from "./apicalls";
|
|
|
31
31
|
import { memoryNote } from "./memories";
|
|
32
32
|
import { entitlementScope, resolveRequestScope, requestSalt, standardKeysFrom } from "./requestScope";
|
|
33
33
|
import { rawSessionToken, type SessionClaims } from "./session";
|
|
34
|
-
import { cacheKeyMaterial, corpusGen, exactCacheKey, freshRequested
|
|
34
|
+
import { cacheKeyMaterial, corpusGen, exactCacheKey, freshRequested } from "./answercache";
|
|
35
35
|
import type { Env } from "./env";
|
|
36
36
|
export type { Env };
|
|
37
37
|
import { json, err, corsHeaders, readJson, validateQuery, type ApiKey } from "./lib/http";
|
|
@@ -435,26 +435,6 @@ async function handleAsk(
|
|
|
435
435
|
// fresh already bypassed the exact cache above and bypasses this one.
|
|
436
436
|
let understanding: any = null;
|
|
437
437
|
// a query naming a model node (/req/…, /term/…) is node-SCOPED: its
|
|
438
|
-
// embedding sits near every other node-scoped ask about the same
|
|
439
|
-
// standard, and the single-entry semantic bucket then serves one
|
|
440
|
-
// node's answer for another (observed run-to-run across the golden
|
|
441
|
-
// model legs). Node-scoped queries use the exact cache only.
|
|
442
|
-
const nodeScoped = !!modelNodeRefIn(q.query) || !!modelNodeRefIn(declaredCtx?.label);
|
|
443
|
-
if (!cached && !nodeScoped && !contextual && !declaredCtx && !draftAct && !apiCallIntent && !q.lang && !userImage && !fresh) {
|
|
444
|
-
const wv0 = (await warmEmbed) ?? null;
|
|
445
|
-
if (wv0) {
|
|
446
|
-
const sc0 = await semanticCacheGet(env, gen, wv0, salt);
|
|
447
|
-
if (sc0) {
|
|
448
|
-
console.log("semantic cache hit (pre-understanding)");
|
|
449
|
-
telemetry(env, ctx, tier, "ask", null, true, sc0.answer.length, sc0.query_hash, q.lang, "semantic", telemetryMeta());
|
|
450
|
-
const cctx0 = sc0.context_applied ?? NO_CONTEXT;
|
|
451
|
-
if (wantsStream) {
|
|
452
|
-
return sseResponse([{ type: "citations", citations: sc0.citations ?? [], context_applied: cctx0 }, { type: "token", v: sc0.answer }, { type: "done", model: sc0.model, query_hash: sc0.query_hash, similar: true, served_from: "similar", context_applied: cctx0 }], corsHeaders(req));
|
|
453
|
-
}
|
|
454
|
-
return json({ ...sc0, similar: true, context_applied: cctx0, quota, });
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
438
|
// ── Option C: optimistic parallel retrieval ──
|
|
459
439
|
// The dense lane (folded-query embed + unfiltered Vectorize query) runs
|
|
460
440
|
// CONCURRENTLY with understanding instead of after it — the serial
|
|
@@ -583,34 +563,9 @@ async function handleAsk(
|
|
|
583
563
|
// join the prompt as an authoritative note — exact matches, never a
|
|
584
564
|
// similarity guess about certification standing
|
|
585
565
|
const register = await searchRegister(env.DB, q.query);
|
|
566
|
+
console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
|
|
586
567
|
const regNote = register ? registerNote(register.rows) : undefined;
|
|
587
568
|
|
|
588
|
-
// semantic cache: near-duplicate of a recently answered question —
|
|
589
|
-
// serves the stored answer with a `similar: true` marker (checked only
|
|
590
|
-
// for standalone knowledge questions; contextual turns, declared-context
|
|
591
|
-
// asks and image asks always run live; fresh regenerates, bypassing
|
|
592
|
-
// this cache too)
|
|
593
|
-
if (understanding?.intent !== "conversational" && !nodeScoped && !contextual && !declaredCtx && !draftAct && !apiCallIntent && !userImage && !fresh) {
|
|
594
|
-
const warmVec = (await warmEmbed) ?? null;
|
|
595
|
-
if (warmVec) {
|
|
596
|
-
const sc = await semanticCacheGet(env, gen, warmVec, salt);
|
|
597
|
-
if (sc) {
|
|
598
|
-
console.log("semantic cache hit");
|
|
599
|
-
telemetry(env, ctx, tier, "ask", null, true, sc.answer.length, sc.query_hash, q.lang, "semantic", telemetryMeta());
|
|
600
|
-
const cctx = sc.context_applied ?? NO_CONTEXT;
|
|
601
|
-
if (wantsStream) {
|
|
602
|
-
return sseResponse([
|
|
603
|
-
...(readAs() ? [{ type: "read", read: readAs() }] : []),
|
|
604
|
-
{ type: "citations", citations: sc.citations ?? [], context_applied: cctx },
|
|
605
|
-
{ type: "token", v: sc.answer },
|
|
606
|
-
{ type: "done", model: sc.model, query_hash: sc.query_hash, similar: true, served_from: "similar", context_applied: cctx, read: readAs() },
|
|
607
|
-
], corsHeaders(req));
|
|
608
|
-
}
|
|
609
|
-
return json({ ...sc, similar: true, context_applied: cctx, read: readAs(), quota, });
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
569
|
// Conversational route, decided by query UNDERSTANDING (any language, any
|
|
615
570
|
// phrasing) — not string matching. No retrieval: nothing in the corpus
|
|
616
571
|
// answers "who are you". The model speaks for itself from the service
|
|
@@ -1192,8 +1147,6 @@ async function handleAsk(
|
|
|
1192
1147
|
console.log("anchors:", streamed.violations.length, "of", streamed.total, "unverified — not caching");
|
|
1193
1148
|
}
|
|
1194
1149
|
if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && !canonical.includes(refusalAnswer())) {
|
|
1195
|
-
const wv = (await warmEmbed) ?? null;
|
|
1196
|
-
if (wv) semanticCachePut(env, ctx, gen, wv, salt, { answer: canonical, citations: cites, model, query_hash: queryHash });
|
|
1197
1150
|
ctx.waitUntil(
|
|
1198
1151
|
env.CACHE.put(exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt))), JSON.stringify({ answer: canonical, citations: cites, model, query_hash: queryHash }), { expirationTtl: LIMITS.cacheTtlSec }),
|
|
1199
1152
|
);
|
|
@@ -1358,10 +1311,6 @@ async function handleAsk(
|
|
|
1358
1311
|
const jsonQuality = answerQuality(finalCites.map((c: any) => c.quality));
|
|
1359
1312
|
const out = { answer, citations: finalCites, ...(jsonQuality ? { source_quality: jsonQuality, confidence_note: qualityNote(jsonQuality), ...(jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {}) } : {}), model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}) };
|
|
1360
1313
|
const cacheable = !contextual && !declaredCtx && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
|
|
1361
|
-
if (cacheable) {
|
|
1362
|
-
const warmVec = (await warmEmbed) ?? null;
|
|
1363
|
-
if (warmVec) semanticCachePut(env, ctx, gen, warmVec, salt, out);
|
|
1364
|
-
}
|
|
1365
1314
|
if (cacheable) {
|
|
1366
1315
|
const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
|
|
1367
1316
|
ctx.waitUntil(env.CACHE.put(ck, JSON.stringify(out), { expirationTtl: LIMITS.cacheTtlSec }));
|
|
@@ -1403,44 +1352,6 @@ function sseResponse(events: unknown[], cors: Record<string, string>): Response
|
|
|
1403
1352
|
|
|
1404
1353
|
|
|
1405
1354
|
// ── Semantic answer cache (G6) ──
|
|
1406
|
-
// Near-duplicate queries re-pay the whole pipeline. Bucket KV by a
|
|
1407
|
-
// leading-dimension signature of the query embedding; confirm with full
|
|
1408
|
-
// cosine >= 0.97 before serving. Same INDEX_VERSION + corpus-generation
|
|
1409
|
-
// namespace as the answer cache (deploys and corpus surgery invalidate
|
|
1410
|
-
// both). Single entry per bucket (v1): collisions overwrite, never mix.
|
|
1411
|
-
function cosine(a: number[], b: number[]): number {
|
|
1412
|
-
let dot = 0;
|
|
1413
|
-
let na = 0;
|
|
1414
|
-
let nb = 0;
|
|
1415
|
-
for (let i = 0; i < a.length; i++) {
|
|
1416
|
-
dot += a[i] * b[i];
|
|
1417
|
-
na += a[i] * a[i];
|
|
1418
|
-
nb += b[i] * b[i];
|
|
1419
|
-
}
|
|
1420
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb) || 1);
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
function scSignature(v: number[], salt?: string | null): string {
|
|
1424
|
-
return v.slice(0, 16).map((x) => x.toFixed(2)).join(",") + (salt ? `|s:${salt.length}:${salt.slice(0, 64)}` : "");
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
async function semanticCacheGet(env: Env, gen: string, vec: number[], salt?: string | null): Promise<{ answer: string; citations: unknown[]; model: string; query_hash: string; context_applied?: unknown } | null> {
|
|
1428
|
-
try {
|
|
1429
|
-
const raw = await env.CACHE.get(semanticCacheKey(env.INDEX_VERSION, gen, scSignature(vec, salt)), "json") as any;
|
|
1430
|
-
if (!raw?.v || !Array.isArray(raw.v) || raw.v.length !== vec.length) return null;
|
|
1431
|
-
if (cosine(raw.v, vec) < 0.97) return null;
|
|
1432
|
-
return raw;
|
|
1433
|
-
} catch {
|
|
1434
|
-
return null;
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
function semanticCachePut(env: Env, ctx: ExecutionContext, gen: string, vec: number[], salt: string | null | undefined, payload: { answer: string; citations: unknown[]; model: string; query_hash: string }): void {
|
|
1439
|
-
const v = vec.map((x) => Number(x.toFixed(3)));
|
|
1440
|
-
ctx.waitUntil(
|
|
1441
|
-
env.CACHE.put(semanticCacheKey(env.INDEX_VERSION, gen, scSignature(vec, salt)), JSON.stringify({ v, ...payload }), { expirationTtl: LIMITS.cacheTtlSec }),
|
|
1442
|
-
);
|
|
1443
|
-
}
|
|
1444
1355
|
|
|
1445
1356
|
// ── the route table (TODO.impl/03) ───────────────────────────────────────
|
|
1446
1357
|
|
|
@@ -29,6 +29,14 @@ export function isRegisterShaped(query: string): boolean {
|
|
|
29
29
|
* 2+ characters that are not register question words, plus bare model
|
|
30
30
|
* numbers ("190", "HM14H1"). Upper-cased tokens match case-insensitively
|
|
31
31
|
* in SQL LIKE. */
|
|
32
|
+
/** The family the question names ("R 60", "R60", "D 31") — the register
|
|
33
|
+
* search filters to it, so a question about R 60 never presents R 76
|
|
34
|
+
* rows. */
|
|
35
|
+
export function queryFamily(query: string): string | null {
|
|
36
|
+
const m = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(query);
|
|
37
|
+
return m ? `${m[1].toUpperCase()}${m[2].padStart(2, "0")}` : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
export function registerTokens(query: string): string[] {
|
|
33
41
|
const stop = new Set([
|
|
34
42
|
"the", "a", "an", "is", "are", "was", "were", "still", "currently", "in", "on", "for", "of", "and", "or",
|
|
@@ -84,7 +92,13 @@ export async function searchRegister(db: any, query: string): Promise<{ rows: Re
|
|
|
84
92
|
if (!isRegisterShaped(query)) return null;
|
|
85
93
|
const tokens = registerTokens(query);
|
|
86
94
|
if (!tokens.length) return null;
|
|
87
|
-
const
|
|
95
|
+
const built = buildRegisterQuery(tokens);
|
|
96
|
+
let { sql, params } = built;
|
|
97
|
+
const family = queryFamily(query);
|
|
98
|
+
if (family) {
|
|
99
|
+
sql = sql.replace(" WHERE ", " WHERE family = ?0 AND ");
|
|
100
|
+
params.unshift(family);
|
|
101
|
+
}
|
|
88
102
|
try {
|
|
89
103
|
const res = await db.prepare(sql).bind(...params).all();
|
|
90
104
|
return { rows: (res.results ?? []) as RegisterRow[], tokens };
|