@konneal/engine 0.2.159 → 0.2.163
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-FUNBGZKM.js → ask-DP5HOH4K.js} +3 -3
- package/dist/{chunk-PBHMQZKA.js → chunk-6OD35FOY.js} +9 -5
- package/dist/{chunk-OL7KKGEO.js → chunk-IGAGXQHV.js} +1 -1
- package/dist/{chunk-CBSVUTFM.js → chunk-IT3MS476.js} +100 -2
- package/dist/{chunk-G2RE4XRV.js → chunk-MTIAHAOK.js} +4 -1
- package/dist/compress.d.ts +11 -0
- package/dist/crag.d.ts +3 -0
- package/dist/grader.d.ts +4 -0
- package/dist/prompts/crag-passage.md +7 -0
- package/dist/{search-GAU5SEZI.js → search-FW5O32BY.js} +2 -2
- package/dist/{tools-W5HGME7W.js → tools-GDHKPEZR.js} +1 -1
- package/dist/worker_public/src/index.js +54 -7
- package/package.json +1 -1
- package/workers/worker_public/prompts/crag-passage.md +7 -0
- package/workers/worker_public/src/ask.ts +82 -2
- package/workers/worker_public/src/compress.ts +74 -0
- package/workers/worker_public/src/crag.ts +52 -0
- package/workers/worker_public/src/grader.ts +27 -0
- package/workers/worker_public/src/pipeline.ts +7 -1
- package/workers/worker_public/src/refs.ts +19 -5
- package/workers/worker_public/src/research.ts +2 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleAsk
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IT3MS476.js";
|
|
4
4
|
import "./chunk-ECOYO7EX.js";
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-6OD35FOY.js";
|
|
6
|
+
import "./chunk-MTIAHAOK.js";
|
|
7
7
|
import "./chunk-NI4CIK6W.js";
|
|
8
8
|
import "./chunk-W7J5SFXJ.js";
|
|
9
9
|
import "./chunk-ETLSWSZ7.js";
|
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
} from "./chunk-LSZTJI7V.js";
|
|
19
19
|
|
|
20
20
|
// workers/worker_public/src/refs.ts
|
|
21
|
-
var REF = /\[\[(u:[A-Za-z0-9_-]+)\]\]/g;
|
|
21
|
+
var REF = /\[\[(u:(?:u:)?[A-Za-z0-9_-]+)\]\]/g;
|
|
22
|
+
function normalizeRef(id) {
|
|
23
|
+
return id.startsWith("u:u:") ? id.slice(2) : id;
|
|
24
|
+
}
|
|
22
25
|
function availableUnitIds(hits) {
|
|
23
26
|
const ids = /* @__PURE__ */ new Set();
|
|
24
27
|
for (const h of hits) {
|
|
@@ -28,13 +31,14 @@ function availableUnitIds(hits) {
|
|
|
28
31
|
return ids;
|
|
29
32
|
}
|
|
30
33
|
function parseRefs(text) {
|
|
31
|
-
return [...text.matchAll(REF)].map((m) => m[1]);
|
|
34
|
+
return [...text.matchAll(REF)].map((m) => normalizeRef(m[1]));
|
|
32
35
|
}
|
|
33
36
|
function sanitizeRefs(text, available) {
|
|
34
37
|
const dropped = [];
|
|
35
|
-
const out = text.replace(REF, (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const out = text.replace(REF, (_full, id) => {
|
|
39
|
+
const normalized = normalizeRef(id);
|
|
40
|
+
if (available.has(normalized)) return `[[${normalized}]]`;
|
|
41
|
+
dropped.push(normalized);
|
|
38
42
|
return "";
|
|
39
43
|
});
|
|
40
44
|
return { text: out, dropped };
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
tableRetyped,
|
|
33
33
|
toolNote,
|
|
34
34
|
verdictNote
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6OD35FOY.js";
|
|
36
36
|
import {
|
|
37
37
|
NO_CONTEXT,
|
|
38
38
|
STAGE_NAMES,
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
tokenBudget,
|
|
76
76
|
understandQuery,
|
|
77
77
|
usageTotal
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-MTIAHAOK.js";
|
|
79
79
|
import {
|
|
80
80
|
embed,
|
|
81
81
|
generateOnce
|
|
@@ -144,6 +144,38 @@ async function retrieveInternal(service, auth, query) {
|
|
|
144
144
|
// workers/worker_public/prompts/grader.md
|
|
145
145
|
var grader_default = 'You grade retrieval quality for a legal-metrology Q&A system.\nGiven the question and the retrieved passage summaries, reply with ONLY:\n{"grade": "good"} \u2014 passages clearly contain the material to answer\n{"grade": "weak"} \u2014 passages are on the right publication/topic but lack the specific material (a broader or differently-worded retrieval might find it)\n{"grade": "bad"} \u2014 passages are unrelated to the question\n';
|
|
146
146
|
|
|
147
|
+
// workers/worker_public/src/crag.ts
|
|
148
|
+
async function gradePassage(ai, model, systemPrompt, query, passage, timeoutMs = 6e3) {
|
|
149
|
+
try {
|
|
150
|
+
const timeout = new Promise((r) => setTimeout(() => r(null), timeoutMs));
|
|
151
|
+
const call = (async () => {
|
|
152
|
+
const res = await ai.run(model, {
|
|
153
|
+
messages: [
|
|
154
|
+
{ role: "system", content: systemPrompt },
|
|
155
|
+
{ role: "user", content: `Question: ${query}
|
|
156
|
+
|
|
157
|
+
Passage:
|
|
158
|
+
${passage.replace(/\s+/g, " ").slice(0, 1200)}` }
|
|
159
|
+
],
|
|
160
|
+
max_tokens: 6144,
|
|
161
|
+
reasoning_effort: "low",
|
|
162
|
+
// a measurement: greedy decoding, no sampling
|
|
163
|
+
temperature: 0,
|
|
164
|
+
top_p: 1
|
|
165
|
+
});
|
|
166
|
+
const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
|
|
167
|
+
const m = (text ?? "").match(/"grade"\s*:\s*"(correct|incorrect|ambiguous)"/);
|
|
168
|
+
return m ? m[1] : null;
|
|
169
|
+
})();
|
|
170
|
+
return await Promise.race([call, timeout]);
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// workers/worker_public/prompts/crag-passage.md
|
|
177
|
+
var crag_passage_default = 'You judge the RELEVANCE of one retrieved passage to a question, for a\nlegal-metrology retrieval system (the CRAG per-document evaluator).\nVerdicts:\n- "correct": the passage contains information useful for answering the question.\n- "incorrect": the passage is unrelated to the question or would mislead.\n- "ambiguous": partially related \u2014 relevant subject, unclear usefulness.\nReply with ONLY: {"grade": "correct" | "incorrect" | "ambiguous"}\n';
|
|
178
|
+
|
|
147
179
|
// workers/worker_public/src/grader.ts
|
|
148
180
|
async function gradeRetrieval(ai, model, query, passages) {
|
|
149
181
|
if (!passages.length) return "bad";
|
|
@@ -210,6 +242,12 @@ async function scoreJudge(ai, model, systemPrompt, userPrompt) {
|
|
|
210
242
|
return null;
|
|
211
243
|
}
|
|
212
244
|
}
|
|
245
|
+
function gradePassage2(ai, model, query, passage) {
|
|
246
|
+
return gradePassage(ai, model, crag_passage_default.trimEnd(), query, passage);
|
|
247
|
+
}
|
|
248
|
+
function gradePassages(ai, model, query, passages) {
|
|
249
|
+
return Promise.all(passages.map((p) => gradePassage2(ai, model, query, p)));
|
|
250
|
+
}
|
|
213
251
|
|
|
214
252
|
// workers/worker_public/prompts/summarize.md
|
|
215
253
|
var summarize_default = "Summarize this conversation so a Q&A assistant can continue it with full continuity. Capture: documents and editions discussed, questions asked, answers given (key values and definitions), terminology established, unresolved threads. Under 150 words, plain text, no preamble. The conversation may be any length and in any language \u2014 summarize it in English.\n";
|
|
@@ -1722,6 +1760,7 @@ async function attachFigureImages(env, messages, usedHits, query) {
|
|
|
1722
1760
|
const payload = row ? JSON.parse(String(row.payload)) : {};
|
|
1723
1761
|
const uri = typeof payload.uri === "string" ? payload.uri : "";
|
|
1724
1762
|
if (typeof payload.caption === "string" && payload.caption.trim()) figCaption = payload.caption.trim();
|
|
1763
|
+
else if (typeof payload.description === "string" && payload.description.trim()) figCaption = payload.description.trim();
|
|
1725
1764
|
const m = typeof uri === "string" ? uri.match(/^\/assets\/(.+)/) : null;
|
|
1726
1765
|
if (!m) continue;
|
|
1727
1766
|
const obj = await env.UNIT_ASSETS.get(m[1]);
|
|
@@ -2460,6 +2499,7 @@ ${summary}` }] : [],
|
|
|
2460
2499
|
const routed = routeMode === "adaptive" ? routeFor(understanding, q2.query) : { route: routeMode, features: [`forced-${routeMode}`] };
|
|
2461
2500
|
const routeSubset = ablate?.stages ?? (routed.route === "fast" ? fastRouteStages(STAGE_NAMES) : null);
|
|
2462
2501
|
if (routeSubset) console.log("route:", routed.route, routed.features.join("+"), "\u2014", STAGE_NAMES.length - routeSubset.length, "lanes dropped");
|
|
2502
|
+
const crag = { grade: null, corrective: null };
|
|
2463
2503
|
try {
|
|
2464
2504
|
const tR = Date.now();
|
|
2465
2505
|
if (declaredCtx?.kind === "account") {
|
|
@@ -2517,6 +2557,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2517
2557
|
}
|
|
2518
2558
|
}
|
|
2519
2559
|
const grade = await gradePromise;
|
|
2560
|
+
crag.grade = grade;
|
|
2520
2561
|
stageTiming.retrieve = Date.now() - tR;
|
|
2521
2562
|
console.log("stage: grade+listwise", Date.now() - tR, "ms since retrieve start | grade:", grade);
|
|
2522
2563
|
if (grade === "weak" && understanding?.docidentifier) {
|
|
@@ -2526,12 +2567,31 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2526
2567
|
const grade2 = await gradeRetrieval(env.AI, roleModel(env, "grader"), q2.query, second.hits.map((h) => h.text));
|
|
2527
2568
|
stageTiming.corrective = Date.now() - tc;
|
|
2528
2569
|
if (grade2 === "good") retrieved = second;
|
|
2570
|
+
crag.corrective = { fired: true, accepted: grade2 === "good", grade: grade2 };
|
|
2529
2571
|
}
|
|
2530
2572
|
} catch (e) {
|
|
2531
2573
|
console.log("ask: retrieval failed:", String(e).slice(0, 300));
|
|
2532
2574
|
telemetry(env, ctx, tier, "ask", MODELS.embed, false, 0, await sha256Hex(q2.query), q2.lang, void 0, telemetryMeta());
|
|
2533
2575
|
return finishLine("Search is briefly busy \u2014 please retry in a moment.");
|
|
2534
2576
|
}
|
|
2577
|
+
{
|
|
2578
|
+
const figs = retrieved.hits.filter((h) => h.metadata.block === "figure" && h.metadata.unit_id);
|
|
2579
|
+
if (figs.length) {
|
|
2580
|
+
try {
|
|
2581
|
+
const ids = [...new Set(figs.map((h) => String(h.metadata.unit_id)))].slice(0, 20);
|
|
2582
|
+
const ph = ids.map((_, i) => `?${i + 1}`).join(",");
|
|
2583
|
+
const res = await env.DB.prepare(
|
|
2584
|
+
`SELECT unit_id, json_extract(payload,'$.description') AS d FROM unit_payloads WHERE unit_id IN (${ph})`
|
|
2585
|
+
).bind(...ids).all();
|
|
2586
|
+
const byId = new Map((res.results ?? []).map((r) => [r.unit_id, r.d]));
|
|
2587
|
+
for (const h of figs) {
|
|
2588
|
+
const d = byId.get(String(h.metadata.unit_id));
|
|
2589
|
+
if (d) h.metadata.figure_description = String(d).slice(0, 600);
|
|
2590
|
+
}
|
|
2591
|
+
} catch {
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2535
2595
|
const { hits } = retrieved;
|
|
2536
2596
|
if (hits.length === 0 && !liveRecords?.length && (!boundModel || boundModel.gated)) {
|
|
2537
2597
|
const answer2 = licenseBoundaryRefusal(modelDocHint?.doc_number ?? understanding?.doc_number ?? null, standardKeys) ?? refusalAnswer();
|
|
@@ -2654,6 +2714,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2654
2714
|
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...sourceQuality ? { source_quality: sourceQuality, confidence_note: qualityNote(sourceQuality), ...sourceQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(cites) } : {} } : {}, ...liveRecords ? { records: liveRecords } : {}, quota });
|
|
2655
2715
|
const stats = {};
|
|
2656
2716
|
let full = "";
|
|
2717
|
+
let streamRestarts = 0;
|
|
2657
2718
|
try {
|
|
2658
2719
|
let headSettled = false;
|
|
2659
2720
|
for await (const tok of sseTokens(stream, stats)) {
|
|
@@ -2666,6 +2727,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2666
2727
|
headSettled = true;
|
|
2667
2728
|
if (toolNarration(full) || intentPreamble(full)) {
|
|
2668
2729
|
console.log(toolNarration(full) ? "tool-narration(stream): regenerating once" : "intent-preamble(stream): regenerating once");
|
|
2730
|
+
streamRestarts += 1;
|
|
2669
2731
|
send({ type: "retry" });
|
|
2670
2732
|
const regen = await generateStream(env, model, [
|
|
2671
2733
|
...messages,
|
|
@@ -2694,6 +2756,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2694
2756
|
send({ type: "quota", quota: { ...quota, ...tq2, credits: await creditBudget(env, bucketId), credit_estimate: creditPreview } });
|
|
2695
2757
|
if (withImage && register?.rows?.length && full.includes(refusalAnswer())) {
|
|
2696
2758
|
console.log("blind-answer(stream): regenerating once");
|
|
2759
|
+
streamRestarts += 1;
|
|
2697
2760
|
mark("Re-answering from the register results");
|
|
2698
2761
|
send({ type: "retry" });
|
|
2699
2762
|
const regen = await generateStream(env, model, [
|
|
@@ -2759,6 +2822,34 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2759
2822
|
} catch {
|
|
2760
2823
|
}
|
|
2761
2824
|
}
|
|
2825
|
+
if (body?.manifest === true) {
|
|
2826
|
+
try {
|
|
2827
|
+
live2.send({ type: "manifest", manifest: {
|
|
2828
|
+
index_version: env.INDEX_VERSION,
|
|
2829
|
+
corpus_gen: gen,
|
|
2830
|
+
model,
|
|
2831
|
+
effort: answerEffort(env),
|
|
2832
|
+
fresh,
|
|
2833
|
+
lane: "stream",
|
|
2834
|
+
citations: usedHits.length,
|
|
2835
|
+
retrieval: usedHits.map((h, i) => ({
|
|
2836
|
+
rank: i + 1,
|
|
2837
|
+
doc_id: h.metadata.doc_id,
|
|
2838
|
+
clause_anchor: h.metadata.clause_anchor ?? null,
|
|
2839
|
+
score: h.score,
|
|
2840
|
+
rerank_score: h.rerank_score ?? null
|
|
2841
|
+
})),
|
|
2842
|
+
stages: stageTiming,
|
|
2843
|
+
generate_retries: generateRetries,
|
|
2844
|
+
restarts: streamRestarts,
|
|
2845
|
+
crag: { retrieval_grade: crag.grade, corrective: crag.corrective, passages: null, note: "per-passage grades ride the json lane's manifest" },
|
|
2846
|
+
entailment: streamedEntailment,
|
|
2847
|
+
contract: { verified: streamed.total - streamed.violations.length, violations: streamed.violations.slice(0, 5) },
|
|
2848
|
+
total_ms: Date.now() - tStart
|
|
2849
|
+
} });
|
|
2850
|
+
} catch {
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2762
2853
|
if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && cacheableAnswer(canonical, refusalScope(answerCites(canonical))) && !ablate) {
|
|
2763
2854
|
ctx.waitUntil(
|
|
2764
2855
|
env.CACHE.put(exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q2.query, q2.lang, salt))), JSON.stringify({ answer: canonical, citations: cites, model, query_hash: queryHash, ...sourceQuality ? { source_quality: sourceQuality, confidence_note: streamedNote || qualityNote(sourceQuality) } : {}, ...streamedEntailment ? { entailment: streamedEntailment } : {} }), { expirationTtl: LIMITS.cacheTtlSec })
|
|
@@ -2923,6 +3014,11 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2923
3014
|
...h.metadata.table_selection ? { sel: h.metadata.table_selection } : {}
|
|
2924
3015
|
}));
|
|
2925
3016
|
const tq = await chargeAnswerTokens(env, bucketId, void 0, messageChars(messages), answer.length, probeTokensUsed);
|
|
3017
|
+
let cragPassages = null;
|
|
3018
|
+
if (body?.manifest === true && used.length) {
|
|
3019
|
+
const grades = await gradePassages(env.AI, roleModel(env, "grader"), q2.query, used.map((h) => h.text));
|
|
3020
|
+
cragPassages = used.map((h, i) => ({ rank: i + 1, doc_id: h.metadata.doc_id, clause_anchor: h.metadata.clause_anchor ?? null, grade: grades[i] }));
|
|
3021
|
+
}
|
|
2926
3022
|
const manifestOut = body?.manifest === true ? { manifest: {
|
|
2927
3023
|
index_version: env.INDEX_VERSION,
|
|
2928
3024
|
corpus_gen: gen,
|
|
@@ -2939,6 +3035,8 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2939
3035
|
})),
|
|
2940
3036
|
stages: stageTiming,
|
|
2941
3037
|
generate_retries: generateRetries,
|
|
3038
|
+
crag: { retrieval_grade: crag.grade, corrective: crag.corrective, passages: cragPassages ?? [] },
|
|
3039
|
+
entailment,
|
|
2942
3040
|
total_ms: Date.now() - tStart
|
|
2943
3041
|
} } : {};
|
|
2944
3042
|
return json({ ...out, context: contextOut, read: readAs(), ...manifestOut, quota: { ...quota, ...tq, credits: await creditBudget(env, bucketId), credit_estimate: creditPreview } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
|
|
@@ -23640,7 +23640,10 @@ Context passages:
|
|
|
23640
23640
|
const tableSel = h.metadata.block === "table" ? tableSelection(h.metadata, query) : null;
|
|
23641
23641
|
const pruned = tableSel?.text ?? null;
|
|
23642
23642
|
if (tableSel) h.metadata.table_selection = { cols: tableSel.cols, rowsShown: tableSel.rowsShown, rowsTotal: tableSel.rowsTotal };
|
|
23643
|
-
const
|
|
23643
|
+
const figDesc = h.metadata.block === "figure" ? String(h.metadata.figure_description ?? "").trim() : "";
|
|
23644
|
+
const raw = clipToTokens(pruned ?? h.text, LIMITS.maxPassageTokens);
|
|
23645
|
+
const body = figDesc ? `${raw}
|
|
23646
|
+
Caption: ${figDesc}` : raw;
|
|
23644
23647
|
const t = estTokens(head) + estTokens(body);
|
|
23645
23648
|
if (t <= remain) {
|
|
23646
23649
|
passageParts.push(head + body);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function tokens(text: string): string[];
|
|
2
|
+
export declare function splitSentences(text: string): string[];
|
|
3
|
+
/** Query-term density, with a numeric sentence boosted: a sentence that
|
|
4
|
+
* states values, tolerances or clause numbers is evidence even when it
|
|
5
|
+
* repeats few query words. */
|
|
6
|
+
export declare function scoreSentence(sentence: string, qTerms: Set<string>): number;
|
|
7
|
+
/** Compress to at most `budgetChars` characters: the lead sentence is
|
|
8
|
+
* always kept (it carries the subject), the rest by score, emitted in
|
|
9
|
+
* reading order. No sentence scores above zero → plain truncation of
|
|
10
|
+
* the lead at a word boundary (never empty when input is non-empty). */
|
|
11
|
+
export declare function compressText(text: string, query: string, budgetChars: number): string;
|
package/dist/crag.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type PassageGrade = "correct" | "incorrect" | "ambiguous";
|
|
2
|
+
export declare function gradePassage(ai: any, model: string, systemPrompt: string, query: string, passage: string, timeoutMs?: number): Promise<PassageGrade | null>;
|
|
3
|
+
export declare function gradePassages(ai: any, model: string, systemPrompt: string, query: string, passages: string[]): Promise<(PassageGrade | null)[]>;
|
package/dist/grader.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export type RetrievalGrade = "good" | "weak" | "bad";
|
|
2
2
|
export declare function gradeRetrieval(ai: any, model: string, query: string, passages: string[]): Promise<RetrievalGrade>;
|
|
3
3
|
export declare function scoreJudge(ai: any, model: string, systemPrompt: string, userPrompt: string): Promise<number | null>;
|
|
4
|
+
import { type PassageGrade } from "./crag";
|
|
5
|
+
export type { PassageGrade };
|
|
6
|
+
export declare function gradePassage(ai: any, model: string, query: string, passage: string): Promise<PassageGrade | null>;
|
|
7
|
+
export declare function gradePassages(ai: any, model: string, query: string, passages: string[]): Promise<(PassageGrade | null)[]>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
You judge the RELEVANCE of one retrieved passage to a question, for a
|
|
2
|
+
legal-metrology retrieval system (the CRAG per-document evaluator).
|
|
3
|
+
Verdicts:
|
|
4
|
+
- "correct": the passage contains information useful for answering the question.
|
|
5
|
+
- "incorrect": the passage is unrelated to the question or would mislead.
|
|
6
|
+
- "ambiguous": partially related — relevant subject, unclear usefulness.
|
|
7
|
+
Reply with ONLY: {"grade": "correct" | "incorrect" | "ambiguous"}
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
handleMemories,
|
|
5
5
|
scoreFaithfulness,
|
|
6
6
|
scoreJudge
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-IT3MS476.js";
|
|
8
8
|
import "../../chunk-ECOYO7EX.js";
|
|
9
9
|
import {
|
|
10
10
|
TOOLS_REGISTRY,
|
|
11
11
|
bindModelNode,
|
|
12
12
|
modelGroundingBlock
|
|
13
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-6OD35FOY.js";
|
|
14
14
|
import {
|
|
15
15
|
handleSearch
|
|
16
|
-
} from "../../chunk-
|
|
16
|
+
} from "../../chunk-IGAGXQHV.js";
|
|
17
17
|
import {
|
|
18
18
|
buildMessages,
|
|
19
19
|
citations,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
sessionFrom,
|
|
38
38
|
telemetry,
|
|
39
39
|
understandQuery
|
|
40
|
-
} from "../../chunk-
|
|
40
|
+
} from "../../chunk-MTIAHAOK.js";
|
|
41
41
|
import {
|
|
42
42
|
embed,
|
|
43
43
|
generateOnce
|
|
@@ -757,6 +757,53 @@ async function handleRevokeKey(env, req, id) {
|
|
|
757
757
|
return json({ ok: true, updated: r.meta?.changes ?? 0 });
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
+
// workers/worker_public/src/compress.ts
|
|
761
|
+
var STOP = new Set(
|
|
762
|
+
"the a an and or of to in on for with by as at is are was were be been from that this these those it its their there which who what when where how why not no than then also such may must shall can could should would per within under over into about between during before after each any all more most less least other only same both few many some own very one two".split(
|
|
763
|
+
" "
|
|
764
|
+
)
|
|
765
|
+
);
|
|
766
|
+
function tokens(text) {
|
|
767
|
+
return (text.toLowerCase().match(/[a-z0-9]+(?:[.][0-9]+)?/g) ?? []).filter(
|
|
768
|
+
(t) => /^[0-9]/.test(t) || t.length >= 3 && !STOP.has(t)
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
function splitSentences(text) {
|
|
772
|
+
const parts = text.replace(/\r/g, "").split(/(?<=[.!?;])\s+|\n+/).map((s) => s.trim()).filter(Boolean);
|
|
773
|
+
return parts;
|
|
774
|
+
}
|
|
775
|
+
function scoreSentence(sentence, qTerms) {
|
|
776
|
+
const toks = tokens(sentence);
|
|
777
|
+
if (!toks.length) return 0;
|
|
778
|
+
let hits = 0;
|
|
779
|
+
for (const t of toks) if (qTerms.has(t)) hits += 1;
|
|
780
|
+
const density = hits / Math.sqrt(toks.length);
|
|
781
|
+
const numeric = /\d/.test(sentence) ? 0.35 : 0;
|
|
782
|
+
return density + numeric;
|
|
783
|
+
}
|
|
784
|
+
function compressText(text, query, budgetChars) {
|
|
785
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
786
|
+
if (!clean || clean.length <= budgetChars) return clean;
|
|
787
|
+
const qTerms = new Set(tokens(query));
|
|
788
|
+
const sentences = splitSentences(clean);
|
|
789
|
+
if (sentences.length <= 1) {
|
|
790
|
+
const cut = clean.slice(0, Math.max(0, budgetChars));
|
|
791
|
+
const end = cut.lastIndexOf(" ");
|
|
792
|
+
return (end > 40 ? cut.slice(0, end) : cut).trim();
|
|
793
|
+
}
|
|
794
|
+
const scored = sentences.map((s, i) => ({ s, i, score: scoreSentence(s, qTerms) }));
|
|
795
|
+
const keep = /* @__PURE__ */ new Set([0]);
|
|
796
|
+
const ranked = scored.slice(1).sort((a, b) => b.score - a.score || a.i - b.i);
|
|
797
|
+
let used = sentences[0].length + 1;
|
|
798
|
+
for (const r of ranked) {
|
|
799
|
+
if (used + r.s.length + 1 > budgetChars) continue;
|
|
800
|
+
keep.add(r.i);
|
|
801
|
+
used += r.s.length + 1;
|
|
802
|
+
}
|
|
803
|
+
const out = scored.filter((r) => keep.has(r.i)).map((r) => r.s).join(" ").trim();
|
|
804
|
+
return out.slice(0, budgetChars);
|
|
805
|
+
}
|
|
806
|
+
|
|
760
807
|
// workers/worker_public/prompts/research.md
|
|
761
808
|
var research_default = `You are a sufficiency judge for a research loop over {{PUBLISHER_NAME}} publications. Given the research question and the passages collected so far (across iterations), decide whether the collected evidence is SUFFICIENT to write a complete, well-grounded answer.
|
|
762
809
|
|
|
@@ -840,7 +887,7 @@ async function run(env, ctx, q, maxIters, emit) {
|
|
|
840
887
|
const older = passages.slice(0, Math.max(0, passages.length - KEEP_RECENT));
|
|
841
888
|
const recent = passages.slice(-KEEP_RECENT);
|
|
842
889
|
const digest = older.length ? `Earlier evidence (digest, ${older.length} passages):
|
|
843
|
-
${older.map((h) => `- ${h.metadata.docidentifier ?? ""} \xA7${h.metadata.clause_anchor ?? ""}: ${h.text
|
|
890
|
+
${older.map((h) => `- ${h.metadata.docidentifier ?? ""} \xA7${h.metadata.clause_anchor ?? ""}: ${compressText(h.text, q.query, 160)}`).join("\n")}
|
|
844
891
|
|
|
845
892
|
` : "";
|
|
846
893
|
judge = await (async () => {
|
|
@@ -1118,13 +1165,13 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
|
|
|
1118
1165
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
1119
1166
|
body: JSON.stringify({ ...args, stream: false })
|
|
1120
1167
|
});
|
|
1121
|
-
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-
|
|
1168
|
+
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-GDHKPEZR.js");
|
|
1122
1169
|
let payload;
|
|
1123
1170
|
if (TOOLS_REGISTRY2.some((t) => t.name === name && t.audiences.includes("mcp"))) {
|
|
1124
1171
|
const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
|
|
1125
1172
|
payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
|
|
1126
1173
|
} else {
|
|
1127
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
1174
|
+
const res = name === "ask" ? await (await import("../../ask-DP5HOH4K.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-FW5O32BY.js")).handleSearch(env, ctx, inner, tier, key);
|
|
1128
1175
|
payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
1129
1176
|
}
|
|
1130
1177
|
telemetry(env, ctx, tier, `mcp:${name}`, null, !payload?.error, JSON.stringify(payload).length, await sha256Hex(`${name}:${JSON.stringify(args ?? {})}`), void 0, void 0, { durationMs: Date.now() - t0 });
|
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.163",
|
|
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",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
You judge the RELEVANCE of one retrieved passage to a question, for a
|
|
2
|
+
legal-metrology retrieval system (the CRAG per-document evaluator).
|
|
3
|
+
Verdicts:
|
|
4
|
+
- "correct": the passage contains information useful for answering the question.
|
|
5
|
+
- "incorrect": the passage is unrelated to the question or would mislead.
|
|
6
|
+
- "ambiguous": partially related — relevant subject, unclear usefulness.
|
|
7
|
+
Reply with ONLY: {"grade": "correct" | "incorrect" | "ambiguous"}
|
|
@@ -10,7 +10,7 @@ import { buildMessages, citations, fill, promptVars, retrieve, retrievalQuery, i
|
|
|
10
10
|
import { sessionFrom } from "./auth";
|
|
11
11
|
import { retrieveInternal } from "./internal_gateway";
|
|
12
12
|
import { understandQuery } from "./understand";
|
|
13
|
-
import { gradeRetrieval } from "./grader";
|
|
13
|
+
import { gradePassages, gradeRetrieval } from "./grader";
|
|
14
14
|
import summarizePrompt from "../prompts/summarize.md";
|
|
15
15
|
import { embed, generateOnce } from "./ai";
|
|
16
16
|
import { reflect } from "./reflect";
|
|
@@ -173,7 +173,11 @@ async function attachFigureImages(env: Env, messages: { role: string; content: a
|
|
|
173
173
|
const row = await env.DB.prepare("SELECT payload FROM unit_payloads WHERE unit_id = ?1").bind(h.metadata.unit_id!).first<any>();
|
|
174
174
|
const payload = row ? JSON.parse(String(row.payload)) : {};
|
|
175
175
|
const uri = typeof payload.uri === "string" ? payload.uri : "";
|
|
176
|
+
// the producer's caption first; the caption LANE's output (a
|
|
177
|
+
// generated description) second — the field drift that kept
|
|
178
|
+
// generated captions out of this context
|
|
176
179
|
if (typeof payload.caption === "string" && payload.caption.trim()) figCaption = payload.caption.trim();
|
|
180
|
+
else if (typeof payload.description === "string" && payload.description.trim()) figCaption = payload.description.trim();
|
|
177
181
|
const m = typeof uri === "string" ? uri.match(/^\/assets\/(.+)/) : null;
|
|
178
182
|
if (!m) continue;
|
|
179
183
|
const obj = await env.UNIT_ASSETS.get(m[1]);
|
|
@@ -1295,6 +1299,10 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1295
1299
|
: { route: routeMode, features: [`forced-${routeMode}`] };
|
|
1296
1300
|
const routeSubset = ablate?.stages ?? (routed.route === "fast" ? fastRouteStages(STAGE_NAMES) : null);
|
|
1297
1301
|
if (routeSubset) console.log("route:", routed.route, routed.features.join("+"), "—", STAGE_NAMES.length - routeSubset.length, "lanes dropped");
|
|
1302
|
+
// the CRAG state, hoisted out of the retrieval try so the manifest can
|
|
1303
|
+
// report it (TODO.sota/14.6 v2) — the coarse retrieval grade and the
|
|
1304
|
+
// corrective retry's firing/acceptance
|
|
1305
|
+
const crag: { grade: string | null; corrective: { fired: boolean; accepted: boolean; grade: string | null } | null } = { grade: null, corrective: null };
|
|
1298
1306
|
try {
|
|
1299
1307
|
const tR = Date.now();
|
|
1300
1308
|
// ── The "my account" live read (TODO.ai-platform/03) — resolved
|
|
@@ -1381,6 +1389,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1381
1389
|
}
|
|
1382
1390
|
}
|
|
1383
1391
|
const grade = await gradePromise;
|
|
1392
|
+
crag.grade = grade;
|
|
1384
1393
|
stageTiming.retrieve = Date.now() - tR;
|
|
1385
1394
|
console.log("stage: grade+listwise", Date.now() - tR, "ms since retrieve start | grade:", grade);
|
|
1386
1395
|
if (grade === "weak" && understanding?.docidentifier) {
|
|
@@ -1390,12 +1399,34 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1390
1399
|
const grade2 = await gradeRetrieval(env.AI, roleModel(env, "grader"), q.query, second.hits.map((h: Hit) => h.text));
|
|
1391
1400
|
stageTiming.corrective = Date.now() - tc;
|
|
1392
1401
|
if (grade2 === "good") retrieved = second; // corrective retry must be strictly better
|
|
1402
|
+
crag.corrective = { fired: true, accepted: grade2 === "good", grade: grade2 };
|
|
1393
1403
|
}
|
|
1394
1404
|
} catch (e) {
|
|
1395
1405
|
console.log("ask: retrieval failed:", String(e).slice(0, 300));
|
|
1396
1406
|
telemetry(env, ctx, tier, "ask", MODELS.embed, false, 0, await sha256Hex(q.query), q.lang, undefined, telemetryMeta());
|
|
1397
1407
|
return finishLine("Search is briefly busy — please retry in a moment.");
|
|
1398
1408
|
}
|
|
1409
|
+
// the figure stratum (TODO.sota/14.10): figure payloads' generated
|
|
1410
|
+
// captions ride their passages (buildMessages appends them) so the
|
|
1411
|
+
// model has words for the drawing and a reason to cite the unit —
|
|
1412
|
+
// one batched D1 read per turn that retrieved figures, fail-open
|
|
1413
|
+
{
|
|
1414
|
+
const figs = retrieved.hits.filter((h: Hit) => (h.metadata as any).block === "figure" && (h.metadata as any).unit_id);
|
|
1415
|
+
if (figs.length) {
|
|
1416
|
+
try {
|
|
1417
|
+
const ids = [...new Set(figs.map((h: Hit) => String((h.metadata as any).unit_id)))].slice(0, 20);
|
|
1418
|
+
const ph = ids.map((_, i) => `?${i + 1}`).join(",");
|
|
1419
|
+
const res = await env.DB.prepare(
|
|
1420
|
+
`SELECT unit_id, json_extract(payload,'$.description') AS d FROM unit_payloads WHERE unit_id IN (${ph})`,
|
|
1421
|
+
).bind(...ids).all<{ unit_id: string; d: string | null }>();
|
|
1422
|
+
const byId = new Map((res.results ?? []).map((r) => [r.unit_id, r.d]));
|
|
1423
|
+
for (const h of figs) {
|
|
1424
|
+
const d = byId.get(String((h.metadata as any).unit_id));
|
|
1425
|
+
if (d) (h.metadata as any).figure_description = String(d).slice(0, 600);
|
|
1426
|
+
}
|
|
1427
|
+
} catch { /* the caption is additive — the passage serves without it */ }
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1399
1430
|
const { hits } = retrieved;
|
|
1400
1431
|
if (hits.length === 0 && !liveRecords?.length && (!boundModel || boundModel.gated)) {
|
|
1401
1432
|
// the license boundary refusal (TODO.external-refs/08) outranks the
|
|
@@ -1590,6 +1621,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1590
1621
|
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...(sourceQuality ? { source_quality: sourceQuality, confidence_note: qualityNote(sourceQuality), ...(sourceQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(cites) } : {}) } : {}), ...(liveRecords ? { records: liveRecords } : {}), quota, });
|
|
1591
1622
|
const stats: { usage?: unknown } = {};
|
|
1592
1623
|
let full = "";
|
|
1624
|
+
let streamRestarts = 0; // the head restart + the blind-answer restart — the streamed manifest reports them
|
|
1593
1625
|
try {
|
|
1594
1626
|
// the head buffers until it can rule out the tool-narration
|
|
1595
1627
|
// leak (the JSON lane's contract retry cannot run here — the
|
|
@@ -1605,6 +1637,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1605
1637
|
headSettled = true;
|
|
1606
1638
|
if (toolNarration(full) || intentPreamble(full)) {
|
|
1607
1639
|
console.log(toolNarration(full) ? "tool-narration(stream): regenerating once" : "intent-preamble(stream): regenerating once");
|
|
1640
|
+
streamRestarts += 1;
|
|
1608
1641
|
send({ type: "retry" });
|
|
1609
1642
|
const regen = await generateStream(env, model, [
|
|
1610
1643
|
...messages,
|
|
@@ -1643,6 +1676,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1643
1676
|
// tokens stream as the answer.
|
|
1644
1677
|
if (withImage && register?.rows?.length && full.includes(refusalAnswer())) {
|
|
1645
1678
|
console.log("blind-answer(stream): regenerating once");
|
|
1679
|
+
streamRestarts += 1;
|
|
1646
1680
|
mark("Re-answering from the register results");
|
|
1647
1681
|
send({ type: "retry" });
|
|
1648
1682
|
const regen = await generateStream(env, model, [
|
|
@@ -1717,6 +1751,40 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1717
1751
|
});
|
|
1718
1752
|
} catch { /* the client may have closed; never block the close */ }
|
|
1719
1753
|
}
|
|
1754
|
+
// the streamed lane's manifest (TODO.sota/14.5: the post-answer
|
|
1755
|
+
// manifest reconciles what shipped): everything here is already
|
|
1756
|
+
// computed — the derivation identity, the retrieval anatomy, the
|
|
1757
|
+
// restart count (the restart cost's per-answer record) and the
|
|
1758
|
+
// contract state. The per-passage CRAG grades stay the JSON
|
|
1759
|
+
// lane's — post-answer dead air never justifies k grader calls
|
|
1760
|
+
// on the stream.
|
|
1761
|
+
if (body?.manifest === true) {
|
|
1762
|
+
try {
|
|
1763
|
+
live!.send({ type: "manifest", manifest: {
|
|
1764
|
+
index_version: env.INDEX_VERSION,
|
|
1765
|
+
corpus_gen: gen,
|
|
1766
|
+
model,
|
|
1767
|
+
effort: answerEffort(env),
|
|
1768
|
+
fresh,
|
|
1769
|
+
lane: "stream",
|
|
1770
|
+
citations: usedHits.length,
|
|
1771
|
+
retrieval: usedHits.map((h: Hit, i: number) => ({
|
|
1772
|
+
rank: i + 1,
|
|
1773
|
+
doc_id: h.metadata.doc_id,
|
|
1774
|
+
clause_anchor: h.metadata.clause_anchor ?? null,
|
|
1775
|
+
score: h.score,
|
|
1776
|
+
rerank_score: h.rerank_score ?? null,
|
|
1777
|
+
})),
|
|
1778
|
+
stages: stageTiming,
|
|
1779
|
+
generate_retries: generateRetries,
|
|
1780
|
+
restarts: streamRestarts,
|
|
1781
|
+
crag: { retrieval_grade: crag.grade, corrective: crag.corrective, passages: null, note: "per-passage grades ride the json lane's manifest" },
|
|
1782
|
+
entailment: streamedEntailment,
|
|
1783
|
+
contract: { verified: streamed.total - streamed.violations.length, violations: streamed.violations.slice(0, 5) },
|
|
1784
|
+
total_ms: Date.now() - tStart,
|
|
1785
|
+
} });
|
|
1786
|
+
} catch { /* the client may have closed; never block the close */ }
|
|
1787
|
+
}
|
|
1720
1788
|
if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && cacheableAnswer(canonical, refusalScope(answerCites(canonical))) && !ablate) {
|
|
1721
1789
|
ctx.waitUntil(
|
|
1722
1790
|
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, ...(sourceQuality ? { source_quality: sourceQuality, confidence_note: streamedNote || qualityNote(sourceQuality) } : {}), ...(streamedEntailment ? { entailment: streamedEntailment } : {}) }), { expirationTtl: LIMITS.cacheTtlSec }),
|
|
@@ -1988,7 +2056,17 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1988
2056
|
// retrieval anatomy (rank, corpus score, rerank score per used passage),
|
|
1989
2057
|
// the stage timings, and the enforcement counters. Everything here is
|
|
1990
2058
|
// already computed; the flag only ASSEMBLES it for the harness and for
|
|
1991
|
-
// external auditors. v2
|
|
2059
|
+
// external auditors. v2 added the CRAG grades: the retrieval grade and
|
|
2060
|
+
// corrective-retry outcome (hoisted state) plus the per-passage grades
|
|
2061
|
+
// (opt-in calls) and the entailment measurement.
|
|
2062
|
+
// the per-passage CRAG grades run ONLY on the manifest path — the
|
|
2063
|
+
// harness opted into the derivation, so it pays the k grader calls;
|
|
2064
|
+
// the default serving path never waits on them
|
|
2065
|
+
let cragPassages: { rank: number; doc_id: string; clause_anchor: string | null; grade: string | null }[] | null = null;
|
|
2066
|
+
if (body?.manifest === true && used.length) {
|
|
2067
|
+
const grades = await gradePassages(env.AI, roleModel(env, "grader"), q.query, (used as Hit[]).map((h: Hit) => h.text));
|
|
2068
|
+
cragPassages = (used as Hit[]).map((h: Hit, i: number) => ({ rank: i + 1, doc_id: h.metadata.doc_id, clause_anchor: h.metadata.clause_anchor ?? null, grade: grades[i] }));
|
|
2069
|
+
}
|
|
1992
2070
|
const manifestOut = body?.manifest === true
|
|
1993
2071
|
? { manifest: {
|
|
1994
2072
|
index_version: env.INDEX_VERSION,
|
|
@@ -2006,6 +2084,8 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
2006
2084
|
})),
|
|
2007
2085
|
stages: stageTiming,
|
|
2008
2086
|
generate_retries: generateRetries,
|
|
2087
|
+
crag: { retrieval_grade: crag.grade, corrective: crag.corrective, passages: cragPassages ?? [] },
|
|
2088
|
+
entailment,
|
|
2009
2089
|
total_ms: Date.now() - tStart,
|
|
2010
2090
|
} }
|
|
2011
2091
|
: {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Extractive prompt compression (TODO.sota/02 row 5, LLMLingua-class
|
|
2
|
+
// adapted to the edge runtime): no second model, fully deterministic —
|
|
3
|
+
// sentences are scored by query-term density with a numeric-evidence
|
|
4
|
+
// bump (metrology answers live or die on numbers) and kept in reading
|
|
5
|
+
// order within a character budget. The consumer picks the budget; the
|
|
6
|
+
// compressor never grows text, never reorders it, and never invents.
|
|
7
|
+
|
|
8
|
+
const STOP = new Set(
|
|
9
|
+
"the a an and or of to in on for with by as at is are was were be been from that this these those it its their there which who what when where how why not no than then also such may must shall can could should would per within under over into about between during before after each any all more most less least other only same both few many some own very one two".split(
|
|
10
|
+
" ",
|
|
11
|
+
),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export function tokens(text: string): string[] {
|
|
15
|
+
// numeric tokens survive at ANY length: "R 60", "15", "50 000" are the
|
|
16
|
+
// domain's own vocabulary, and the 3-char floor exists to drop prose
|
|
17
|
+
// fragments ("of", "a"), not identifiers
|
|
18
|
+
return (text.toLowerCase().match(/[a-z0-9]+(?:[.][0-9]+)?/g) ?? []).filter(
|
|
19
|
+
(t) => /^[0-9]/.test(t) || (t.length >= 3 && !STOP.has(t)),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function splitSentences(text: string): string[] {
|
|
24
|
+
const parts = text
|
|
25
|
+
.replace(/\r/g, "")
|
|
26
|
+
.split(/(?<=[.!?;])\s+|\n+/)
|
|
27
|
+
.map((s) => s.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
return parts;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Query-term density, with a numeric sentence boosted: a sentence that
|
|
33
|
+
* states values, tolerances or clause numbers is evidence even when it
|
|
34
|
+
* repeats few query words. */
|
|
35
|
+
export function scoreSentence(sentence: string, qTerms: Set<string>): number {
|
|
36
|
+
const toks = tokens(sentence);
|
|
37
|
+
if (!toks.length) return 0;
|
|
38
|
+
let hits = 0;
|
|
39
|
+
for (const t of toks) if (qTerms.has(t)) hits += 1;
|
|
40
|
+
const density = hits / Math.sqrt(toks.length);
|
|
41
|
+
const numeric = /\d/.test(sentence) ? 0.35 : 0;
|
|
42
|
+
return density + numeric;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Compress to at most `budgetChars` characters: the lead sentence is
|
|
46
|
+
* always kept (it carries the subject), the rest by score, emitted in
|
|
47
|
+
* reading order. No sentence scores above zero → plain truncation of
|
|
48
|
+
* the lead at a word boundary (never empty when input is non-empty). */
|
|
49
|
+
export function compressText(text: string, query: string, budgetChars: number): string {
|
|
50
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
51
|
+
if (!clean || clean.length <= budgetChars) return clean;
|
|
52
|
+
const qTerms = new Set(tokens(query));
|
|
53
|
+
const sentences = splitSentences(clean);
|
|
54
|
+
if (sentences.length <= 1) {
|
|
55
|
+
const cut = clean.slice(0, Math.max(0, budgetChars));
|
|
56
|
+
const end = cut.lastIndexOf(" ");
|
|
57
|
+
return (end > 40 ? cut.slice(0, end) : cut).trim();
|
|
58
|
+
}
|
|
59
|
+
const scored = sentences.map((s, i) => ({ s, i, score: scoreSentence(s, qTerms) }));
|
|
60
|
+
const keep = new Set<number>([0]);
|
|
61
|
+
const ranked = scored.slice(1).sort((a, b) => b.score - a.score || a.i - b.i);
|
|
62
|
+
let used = sentences[0].length + 1;
|
|
63
|
+
for (const r of ranked) {
|
|
64
|
+
if (used + r.s.length + 1 > budgetChars) continue;
|
|
65
|
+
keep.add(r.i);
|
|
66
|
+
used += r.s.length + 1;
|
|
67
|
+
}
|
|
68
|
+
const out = scored
|
|
69
|
+
.filter((r) => keep.has(r.i))
|
|
70
|
+
.map((r) => r.s)
|
|
71
|
+
.join(" ")
|
|
72
|
+
.trim();
|
|
73
|
+
return out.slice(0, budgetChars);
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// The CRAG per-passage evaluator core (TODO.sota/14.6 v2): grade EACH used
|
|
2
|
+
// passage's relevance on the CRAG vocabulary. Dependency-free on purpose —
|
|
3
|
+
// the system prompt arrives as a parameter (the md-importing binding lives
|
|
4
|
+
// in grader.ts), the ai binding is injected, every failure mode is
|
|
5
|
+
// fail-open: a lost race or an unparseable reply grades null, because
|
|
6
|
+
// absence is honest and a guess is not. Opt-in — only the reproducibility
|
|
7
|
+
// manifest requests these grades, so the k calls never sit on the default
|
|
8
|
+
// serving path.
|
|
9
|
+
|
|
10
|
+
export type PassageGrade = "correct" | "incorrect" | "ambiguous";
|
|
11
|
+
|
|
12
|
+
export async function gradePassage(
|
|
13
|
+
ai: any,
|
|
14
|
+
model: string,
|
|
15
|
+
systemPrompt: string,
|
|
16
|
+
query: string,
|
|
17
|
+
passage: string,
|
|
18
|
+
timeoutMs = 6000,
|
|
19
|
+
): Promise<PassageGrade | null> {
|
|
20
|
+
try {
|
|
21
|
+
const timeout = new Promise<null>((r) => setTimeout(() => r(null), timeoutMs));
|
|
22
|
+
const call = (async () => {
|
|
23
|
+
const res: any = await ai.run(model, {
|
|
24
|
+
messages: [
|
|
25
|
+
{ role: "system", content: systemPrompt },
|
|
26
|
+
{ role: "user", content: `Question: ${query}\n\nPassage:\n${passage.replace(/\s+/g, " ").slice(0, 1200)}` },
|
|
27
|
+
],
|
|
28
|
+
max_tokens: 6144,
|
|
29
|
+
reasoning_effort: "low",
|
|
30
|
+
// a measurement: greedy decoding, no sampling
|
|
31
|
+
temperature: 0,
|
|
32
|
+
top_p: 1,
|
|
33
|
+
});
|
|
34
|
+
const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
|
|
35
|
+
const m = (text ?? "").match(/"grade"\s*:\s*"(correct|incorrect|ambiguous)"/);
|
|
36
|
+
return m ? (m[1] as PassageGrade) : null;
|
|
37
|
+
})();
|
|
38
|
+
return await Promise.race([call, timeout]);
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function gradePassages(
|
|
45
|
+
ai: any,
|
|
46
|
+
model: string,
|
|
47
|
+
systemPrompt: string,
|
|
48
|
+
query: string,
|
|
49
|
+
passages: string[],
|
|
50
|
+
): Promise<(PassageGrade | null)[]> {
|
|
51
|
+
return Promise.all(passages.map((p) => gradePassage(ai, model, systemPrompt, query, p)));
|
|
52
|
+
}
|
|
@@ -90,3 +90,30 @@ export async function scoreJudge(
|
|
|
90
90
|
return null;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
// The CRAG per-passage binding (TODO.sota/14.6 v2): the dependency-free
|
|
95
|
+
// core (crag.ts) takes the system prompt as a parameter; this module binds
|
|
96
|
+
// the bundled prompt. Opt-in — only the reproducibility manifest requests
|
|
97
|
+
// these grades, so the k calls never sit on the default serving path.
|
|
98
|
+
import { gradePassage as gradePassageCore, type PassageGrade } from "./crag";
|
|
99
|
+
import CRAG_PASSAGE from "../prompts/crag-passage.md";
|
|
100
|
+
|
|
101
|
+
export type { PassageGrade };
|
|
102
|
+
|
|
103
|
+
export function gradePassage(
|
|
104
|
+
ai: any,
|
|
105
|
+
model: string,
|
|
106
|
+
query: string,
|
|
107
|
+
passage: string,
|
|
108
|
+
): Promise<PassageGrade | null> {
|
|
109
|
+
return gradePassageCore(ai, model, CRAG_PASSAGE.trimEnd(), query, passage);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function gradePassages(
|
|
113
|
+
ai: any,
|
|
114
|
+
model: string,
|
|
115
|
+
query: string,
|
|
116
|
+
passages: string[],
|
|
117
|
+
): Promise<(PassageGrade | null)[]> {
|
|
118
|
+
return Promise.all(passages.map((p) => gradePassage(ai, model, query, p)));
|
|
119
|
+
}
|
|
@@ -360,7 +360,13 @@ export function buildMessages(
|
|
|
360
360
|
const tableSel = (h.metadata as any).block === "table" ? tableSelection(h.metadata, query) : null;
|
|
361
361
|
const pruned = tableSel?.text ?? null;
|
|
362
362
|
if (tableSel) (h.metadata as any).table_selection = { cols: tableSel.cols, rowsShown: tableSel.rowsShown, rowsTotal: tableSel.rowsTotal };
|
|
363
|
-
|
|
363
|
+
// the figure stratum (TODO.sota/14.10): a caption-less figure's
|
|
364
|
+
// generated description rides its passage — the model gets words for
|
|
365
|
+
// the drawing and a reason to cite the unit; absent for every other
|
|
366
|
+
// block type and for uncaptioned figures
|
|
367
|
+
const figDesc = (h.metadata as any).block === "figure" ? String((h.metadata as any).figure_description ?? "").trim() : "";
|
|
368
|
+
const raw = clipToTokens(pruned ?? h.text, LIMITS.maxPassageTokens);
|
|
369
|
+
const body = figDesc ? `${raw}\nCaption: ${figDesc}` : raw;
|
|
364
370
|
const t = estTokens(head) + estTokens(body);
|
|
365
371
|
if (t <= remain) {
|
|
366
372
|
passageParts.push(head + body);
|
|
@@ -15,7 +15,18 @@ export interface ResolvedBlock {
|
|
|
15
15
|
payload: Record<string, unknown>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// The passage headers show `unit u:<id> (table)`, so the model copies
|
|
19
|
+
// the WHOLE prefixed id into the token — `[[u:u:<id>]]` — and the inner
|
|
20
|
+
// colon makes the strict grammar unparseable: neither resolved nor
|
|
21
|
+
// sanitized, the raw token leaked to the client (observed live on the
|
|
22
|
+
// R 134-2 figure question, 0.2.162). The grammar tolerates the doubled
|
|
23
|
+
// prefix; normalizeRef folds it back to the single form every other
|
|
24
|
+
// layer keys on.
|
|
25
|
+
const REF = /\[\[(u:(?:u:)?[A-Za-z0-9_-]+)\]\]/g;
|
|
26
|
+
|
|
27
|
+
function normalizeRef(id: string): string {
|
|
28
|
+
return id.startsWith("u:u:") ? id.slice(2) : id;
|
|
29
|
+
}
|
|
19
30
|
|
|
20
31
|
/** unit ids the passages actually contain (chunk metadata carries
|
|
21
32
|
* unit_id on typed MKO chunks). */
|
|
@@ -29,16 +40,19 @@ export function availableUnitIds(hits: ReadonlyArray<{ metadata: { unit_id?: str
|
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
export function parseRefs(text: string): string[] {
|
|
32
|
-
return [...text.matchAll(REF)].map((m) => m[1]);
|
|
43
|
+
return [...text.matchAll(REF)].map((m) => normalizeRef(m[1]));
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
/** Drop refs that don't resolve to a used passage unit — a dangling ref
|
|
36
47
|
* renders as nothing (the token removed), and the violation is logged. */
|
|
37
48
|
export function sanitizeRefs(text: string, available: ReadonlySet<string>): { text: string; dropped: string[] } {
|
|
38
49
|
const dropped: string[] = [];
|
|
39
|
-
const out = text.replace(REF, (
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
const out = text.replace(REF, (_full, id: string) => {
|
|
51
|
+
const normalized = normalizeRef(id);
|
|
52
|
+
// normalized already carries the single "u:" prefix — the template
|
|
53
|
+
// must not add another
|
|
54
|
+
if (available.has(normalized)) return `[[${normalized}]]`;
|
|
55
|
+
dropped.push(normalized);
|
|
42
56
|
return "";
|
|
43
57
|
});
|
|
44
58
|
return { text: out, dropped };
|
|
@@ -8,6 +8,7 @@ import { retrieve, buildMessages, citations } from "./pipeline";
|
|
|
8
8
|
import { understandQuery } from "./understand";
|
|
9
9
|
import { generateOnce } from "./ai";
|
|
10
10
|
import { canonicalRefusal } from "./refusal";
|
|
11
|
+
import { compressText } from "./compress";
|
|
11
12
|
import { checkQuoteAnchors } from "./anchors";
|
|
12
13
|
import { graphExpand, editionNote } from "./graph";
|
|
13
14
|
import researchPromptText from "../prompts/research.md";
|
|
@@ -109,7 +110,7 @@ async function run(
|
|
|
109
110
|
const older = passages.slice(0, Math.max(0, passages.length - KEEP_RECENT));
|
|
110
111
|
const recent = passages.slice(-KEEP_RECENT);
|
|
111
112
|
const digest = older.length
|
|
112
|
-
? `Earlier evidence (digest, ${older.length} passages):\n${older.map((h) => `- ${h.metadata.docidentifier ?? ""} §${h.metadata.clause_anchor ?? ""}: ${h.text
|
|
113
|
+
? `Earlier evidence (digest, ${older.length} passages):\n${older.map((h) => `- ${h.metadata.docidentifier ?? ""} §${h.metadata.clause_anchor ?? ""}: ${compressText(h.text, q.query, 160)}`).join("\n")}\n\n`
|
|
113
114
|
: "";
|
|
114
115
|
judge = await (async () => {
|
|
115
116
|
try {
|