@konneal/engine 0.1.0 → 0.1.2
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 +106 -5
- package/dist/admin.d.ts +3 -0
- package/dist/{chunk-MB74PTRM.js → chunk-35ODH64W.js} +23 -4
- package/dist/chunk-CAEHIVG5.js +54 -0
- package/dist/{chunk-WWNCWKKC.js → chunk-EHJEELVB.js} +1 -1
- package/dist/{chunk-WOGQM7DJ.js → chunk-OCNLV7Q7.js} +2 -2
- package/dist/chunk-ROF3Q7UC.js +156 -0
- package/dist/codecs.d.ts +22 -0
- package/dist/context.d.ts +2 -9
- package/dist/modelplane.d.ts +1 -1
- package/dist/pipeline.d.ts +8 -1
- package/dist/profile.gen.d.ts +19 -0
- package/dist/profile2.gen.d.ts +89 -0
- package/dist/prompts/conversational.md +1 -1
- package/dist/prompts/enrichment.md +2 -2
- package/dist/prompts/precision.md +1 -1
- package/dist/prompts/research.md +1 -1
- package/dist/prompts/system.md +3 -3
- package/dist/prompts/understanding.md +4 -4
- package/dist/worker_mcp/src/index.d.ts +10 -0
- package/dist/worker_mcp/src/index.js +153 -0
- package/dist/{config.js → worker_public/src/config.js} +2 -2
- package/dist/{index.js → worker_public/src/index.js} +131 -207
- package/dist/{profile.js → worker_public/src/profile.js} +1 -1
- package/dist/{refusal.js → worker_public/src/refusal.js} +2 -2
- package/dist/worker_public/src/requestScope.js +10 -0
- package/package.json +16 -9
- package/profile/prompts.yaml +11 -0
- package/profile/publisher.yaml +9 -0
- package/profile/retrieval.yaml +4 -0
- package/scripts/gen_profile.mjs +6 -5
- package/workers/worker_internal/src/index.ts +1 -1
- package/workers/worker_mcp/src/index.ts +42 -29
- package/workers/worker_mcp/tsconfig.json +11 -4
- package/workers/worker_public/prompts/conversational.md +1 -1
- package/workers/worker_public/prompts/enrichment.md +2 -2
- package/workers/worker_public/prompts/precision.md +1 -1
- package/workers/worker_public/prompts/research.md +1 -1
- package/workers/worker_public/prompts/system.md +3 -3
- package/workers/worker_public/prompts/understanding.md +4 -4
- package/workers/worker_public/src/admin.ts +15 -3
- package/workers/worker_public/src/ask.ts +13 -10
- package/workers/worker_public/src/bubble.ts +13 -6
- package/workers/worker_public/src/codecs.ts +77 -0
- package/workers/worker_public/src/config.ts +1 -1
- package/workers/worker_public/src/context.ts +6 -26
- package/workers/worker_public/src/graph.ts +2 -3
- package/workers/worker_public/src/index.ts +5 -3
- package/workers/worker_public/src/lib/http.ts +5 -9
- package/workers/worker_public/src/livedata.ts +3 -2
- package/workers/worker_public/src/modelplane.ts +13 -5
- package/workers/worker_public/src/pipeline.ts +28 -13
- package/workers/worker_public/src/profile.gen.ts +23 -4
- package/workers/worker_public/src/profile2.gen.ts +122 -0
- package/workers/worker_public/src/refusal.ts +19 -21
- package/workers/worker_public/src/research.ts +4 -2
- package/workers/worker_public/src/stages/conceptGraph.ts +3 -2
- package/workers/worker_public/src/stages/corpusScope.ts +8 -2
- package/workers/worker_public/src/stages/editionCover.ts +2 -4
- package/workers/worker_public/src/understand.ts +2 -1
- package/dist/chunk-LLWPT2XV.js +0 -49
- package/dist/requestScope.js +0 -10
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The serving-side identifier codec registry — the TS mirror of
|
|
2
|
+
// ingest/codecs.py. Each publisher has an identifier grammar; the
|
|
3
|
+
// profile declares the codec id and every publisher-specific parse
|
|
4
|
+
// (how a question names a document, how a label reads, how graph node
|
|
5
|
+
// ids are shaped) lives in the codec implementation, never in domain
|
|
6
|
+
// code. The plain-slug codec is the generic floor: no grammar, no
|
|
7
|
+
// doc-number steering — labels are the identifier as given.
|
|
8
|
+
import { P } from "./profile.ts";
|
|
9
|
+
|
|
10
|
+
export interface DocScope {
|
|
11
|
+
doc_number: string;
|
|
12
|
+
edition?: string;
|
|
13
|
+
label: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RefCodec {
|
|
17
|
+
/** The explicit form: "R 60-1:2021", "urn:…" — null when unparseable. */
|
|
18
|
+
parse(doc: string, edition?: string): DocScope | null;
|
|
19
|
+
/** The gap-tolerant scan over question text — null when nothing names a document. */
|
|
20
|
+
scanQuestion(query: string): DocScope | null;
|
|
21
|
+
/** The graph's node id → document number — null for other shapes. */
|
|
22
|
+
graphDocNumber(nodeId: string): string | null;
|
|
23
|
+
/** A docidentifier's family key ("R-60") for edition steering — null when not of the grammar. */
|
|
24
|
+
familyOf(docidentifier: string): string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** OIML's grammar: type letter (R/D/B/G/E) + 1–3 digits, optional part,
|
|
28
|
+
* optional edition year; the URN provenance form; part numbers are
|
|
29
|
+
* significant (R 60-1), the edition is never part of the number. */
|
|
30
|
+
export const oimlPubid: RefCodec = {
|
|
31
|
+
parse(doc, edition) {
|
|
32
|
+
const m =
|
|
33
|
+
doc.match(/^urn:oiml:pub:([rdbge]):(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i) ??
|
|
34
|
+
doc.match(/^(?:OIML\s+)?([RDBGE])\s*(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i);
|
|
35
|
+
if (!m) return null;
|
|
36
|
+
const type = m[1].toUpperCase();
|
|
37
|
+
const ed = edition ?? m[3] ?? undefined;
|
|
38
|
+
return { doc_number: m[2], ...(ed ? { edition: ed } : {}), label: `OIML ${type} ${m[2]}${ed ? `:${ed}` : ""}` };
|
|
39
|
+
},
|
|
40
|
+
scanQuestion(query) {
|
|
41
|
+
const re = /\b(OIML\s+)?([RDBGE])(\s*)0*(\d{1,3})(?:\s*[-–]\s*\d+)?(?:\s*:\s*(\d{4}))?/gi;
|
|
42
|
+
for (const m of query.matchAll(re)) {
|
|
43
|
+
const [, oimlPrefix, letter, gap, digits, edition] = m;
|
|
44
|
+
// a glued single digit is a class/designation ("E2 weights"), never a naming
|
|
45
|
+
if (digits!.length === 1 && !oimlPrefix && !gap) continue;
|
|
46
|
+
const num = String(Number(digits));
|
|
47
|
+
const type = letter!.toUpperCase();
|
|
48
|
+
return { doc_number: num, ...(edition ? { edition } : {}), label: `OIML ${type} ${num}${edition ? `:${edition}` : ""}` };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
},
|
|
52
|
+
graphDocNumber(nodeId) {
|
|
53
|
+
const m = nodeId.match(/^doc:OIML-[A-Z]-(\d+)-/);
|
|
54
|
+
return m ? m[1] : null;
|
|
55
|
+
},
|
|
56
|
+
familyOf(di) {
|
|
57
|
+
const m = /^(?:OIML\s+)?([A-Z])\s?(\d{1,3})(?:[-–]([0-9A-Za-z]+))?/.exec(di);
|
|
58
|
+
return m ? `${m[1]}-${m[2]}` : null;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** The generic floor: the identifier is whatever string it is. */
|
|
63
|
+
export const plainSlug: RefCodec = {
|
|
64
|
+
parse: () => null,
|
|
65
|
+
scanQuestion: () => null,
|
|
66
|
+
graphDocNumber: () => null,
|
|
67
|
+
familyOf: () => null,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const REGISTRY: Record<string, RefCodec> = {
|
|
71
|
+
"oiml-pubid": oimlPubid,
|
|
72
|
+
"plain-slug": plainSlug,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function refCodec(): RefCodec {
|
|
76
|
+
return REGISTRY[P().publisher.codec] ?? plainSlug;
|
|
77
|
+
}
|
|
@@ -212,7 +212,7 @@ export function datasetsFor(session: unknown): unknown[] {
|
|
|
212
212
|
description: d.description,
|
|
213
213
|
enabled: datasetAllowed(d, session),
|
|
214
214
|
...(d.session
|
|
215
|
-
? { requires: `the ${d.permission ?? "ai-preview"} permission (
|
|
215
|
+
? { requires: `the ${d.permission ?? "ai-preview"} permission (${P().publisher.identity.issuer.replace(/^https?:\/\//, "")})`, authenticated: !!session }
|
|
216
216
|
: {}),
|
|
217
217
|
}));
|
|
218
218
|
}
|
|
@@ -73,28 +73,16 @@ export function parseContext(body: any): DeclaredContext | null {
|
|
|
73
73
|
return { kind: c.kind, label, ...(route ? { route } : {}), ...(doc ? { doc } : {}), ...(edition ? { edition } : {}) };
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* R 60-2 tests) */
|
|
80
|
-
doc_number: string;
|
|
81
|
-
edition?: string;
|
|
82
|
-
/** the canonical label form for the echo + the prompt note */
|
|
83
|
-
label: string;
|
|
84
|
-
}
|
|
76
|
+
import { refCodec, type DocScope } from "./codecs.ts";
|
|
77
|
+
import { P } from "./profile.ts";
|
|
78
|
+
export type { DocScope };
|
|
85
79
|
|
|
86
80
|
/** Parse the two reference forms the estate speaks: the URN the SMART
|
|
87
81
|
* models carry as clause provenance (urn:oiml:pub:r:60-1:2021) and the
|
|
88
82
|
* plain docidentifier (OIML R 60-1:2021 / R 60). Part designations
|
|
89
83
|
* parse but do not narrow the scope (the family IS the scope). */
|
|
90
84
|
export function parseDocRef(doc: string, edition?: string): DocScope | null {
|
|
91
|
-
|
|
92
|
-
doc.match(/^urn:oiml:pub:([rdbge]):(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i) ??
|
|
93
|
-
doc.match(/^(?:OIML\s+)?([RDBGE])\s*(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i);
|
|
94
|
-
if (!m) return null;
|
|
95
|
-
const type = m[1].toUpperCase();
|
|
96
|
-
const ed = edition ?? m[3] ?? undefined;
|
|
97
|
-
return { doc_number: m[2], ...(ed ? { edition: ed } : {}), label: `OIML ${type} ${m[2]}${ed ? `:${ed}` : ""}` };
|
|
85
|
+
return refCodec().parse(doc, edition);
|
|
98
86
|
}
|
|
99
87
|
|
|
100
88
|
/** Read the FIRST publication the question's own text names, in the
|
|
@@ -108,15 +96,7 @@ export function parseDocRef(doc: string, edition?: string): DocScope | null {
|
|
|
108
96
|
* A glued single digit is a class/designation ("E2 weights"), never a
|
|
109
97
|
* naming; part designations parse but do not narrow the family. */
|
|
110
98
|
export function namedDocumentIn(query: string): DocScope | null {
|
|
111
|
-
|
|
112
|
-
for (const m of query.matchAll(re)) {
|
|
113
|
-
const [, oimlPrefix, letter, gap, digits, edition] = m;
|
|
114
|
-
if (digits!.length === 1 && !oimlPrefix && !gap) continue;
|
|
115
|
-
const num = String(Number(digits));
|
|
116
|
-
const type = letter!.toUpperCase();
|
|
117
|
-
return { doc_number: num, ...(edition ? { edition } : {}), label: `OIML ${type} ${num}${edition ? `:${edition}` : ""}` };
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
99
|
+
return refCodec().scanQuestion(query);
|
|
120
100
|
}
|
|
121
101
|
|
|
122
102
|
/** Resolve the declared document against the publications registry: the
|
|
@@ -202,7 +182,7 @@ export function contextNote(declared: DeclaredContext | null, scope: DocScope |
|
|
|
202
182
|
return undefined;
|
|
203
183
|
}
|
|
204
184
|
if (declared.kind === "page") {
|
|
205
|
-
return `Context note: the user is viewing ${declared.label || "a page"}${declared.route ? ` (${declared.route})` : ""} in the
|
|
185
|
+
return `Context note: the user is viewing ${declared.label || "a page"}${declared.route ? ` (${declared.route})` : ""} in the ${P().publisher.product_name} platform. The passages come from the general corpus; frame procedural guidance for that page when relevant.`;
|
|
206
186
|
}
|
|
207
187
|
if (declared.kind === "entity") {
|
|
208
188
|
return scope
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
// the graph says are relevant. Mirrors graph.py's node-id format
|
|
4
4
|
// (doc:OIML-R-60-1-2017, concept:<id>).
|
|
5
5
|
import type { Env } from "./env";
|
|
6
|
+
import { refCodec } from "./codecs.ts";
|
|
6
7
|
|
|
7
8
|
/** Graph expansion (G8 query lane): map understanding's term / named
|
|
8
9
|
* document onto the D1 projection (graph_nodes / graph_edges) and return
|
|
9
10
|
* the doc_numbers the graph says are relevant. Mirrors graph.py's node-id
|
|
10
11
|
* format (doc:OIML-R-60-1-2017, concept:<id>). */
|
|
11
12
|
function docNumberOf(nodeId: string): string | null {
|
|
12
|
-
|
|
13
|
-
const m = nodeId.match(/^doc:OIML-[A-Z]-(\d+)-/);
|
|
14
|
-
return m ? m[1] : null;
|
|
13
|
+
return refCodec().graphDocNumber(nodeId);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export async function graphExpand(env: Env, u: { term?: string | null; defined_terms?: string[]; docidentifier?: string | null } | null): Promise<string[] | undefined> {
|
|
@@ -19,7 +19,7 @@ export type { Env };
|
|
|
19
19
|
import { json, err, corsHeaders, withCors, readJson, authenticate, type ApiKey } from "./lib/http";
|
|
20
20
|
|
|
21
21
|
import { handleSearch } from "./search";
|
|
22
|
-
import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys } from "./admin";
|
|
22
|
+
import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys, handleRevokeKey } from "./admin";
|
|
23
23
|
import { handleResearch } from "./research";
|
|
24
24
|
import { handleAsk } from "./ask";
|
|
25
25
|
|
|
@@ -31,6 +31,7 @@ import { handleAsk } from "./ask";
|
|
|
31
31
|
// handler's, derived from the path. Mirrored in docs/spec-api.md.
|
|
32
32
|
|
|
33
33
|
import { matchRoute, type RouteContext, type Route } from "./lib/router";
|
|
34
|
+
import { P } from "./profile.ts";
|
|
34
35
|
|
|
35
36
|
async function serveIndexPage(c: RouteContext): Promise<Response> {
|
|
36
37
|
// HTML pages are served through the worker with must-revalidate so a
|
|
@@ -112,7 +113,7 @@ async function tierFor(c: RouteContext): Promise<{ tier: "anon" | "key" | "membe
|
|
|
112
113
|
let key: ApiKey | null = null;
|
|
113
114
|
if (isApi) {
|
|
114
115
|
key = await authenticate(c.env, c.req);
|
|
115
|
-
if (!key) return err(401, "unauthorized",
|
|
116
|
+
if (!key) return err(401, "unauthorized", `Provide a valid API key: Authorization: Bearer ${P().publisher.id}_...`);
|
|
116
117
|
}
|
|
117
118
|
let tier: "anon" | "key" | "member" = isApi ? "key" : "anon";
|
|
118
119
|
if (!isApi && c.env.SESSION_SECRET && (await sessionFrom(c.req, c.env as any))) tier = "member";
|
|
@@ -215,7 +216,7 @@ async function verifyRoute(c: RouteContext): Promise<Response> {
|
|
|
215
216
|
const checks = [
|
|
216
217
|
{ name: "quote_anchors", deterministic: true, pass: anchors.violations.length === 0, detail: `${anchors.violations.length} of ${anchors.total} quoted spans absent from the retrieved passages` },
|
|
217
218
|
{ name: "unit_references", deterministic: true, pass: refs.length === validRefs.length, detail: refs.length ? `${validRefs.length}/${refs.length} unit references resolve to served units` : "no unit references" },
|
|
218
|
-
{ name: "citations_present", deterministic: true, pass:
|
|
219
|
+
{ name: "citations_present", deterministic: true, pass: new RegExp(`\\[[^\\]]*(${P().publisher.name})[^\\]]*\\]`).test(answer), detail: "normative claims should carry a passage citation" },
|
|
219
220
|
];
|
|
220
221
|
const faith = await scoreFaithfulness(env.AI, roleModel(env, "grader"), answer, retrieved.hits.map((h: Hit) => h.text));
|
|
221
222
|
return json({
|
|
@@ -421,6 +422,7 @@ export const ROUTES: Route[] = [
|
|
|
421
422
|
{ method: "POST", pattern: "/v1/admin/judge", handler: (c) => handleJudge(c.env, c.req) },
|
|
422
423
|
{ method: "POST", pattern: "/v1/admin/keys", handler: (c) => handleCreateKey(c.env, c.req) },
|
|
423
424
|
{ method: "GET", pattern: "/v1/admin/keys", handler: (c) => handleListKeys(c.env, c.req) },
|
|
425
|
+
{ method: "DELETE", pattern: "/v1/admin/keys/:id", handler: (c) => handleRevokeKey(c.env, c.req, c.params.id) },
|
|
424
426
|
];
|
|
425
427
|
|
|
426
428
|
export default {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// the vocabulary every route handler speaks (TODO.impl/23).
|
|
3
3
|
import { LIMITS, sha256Hex } from "../config";
|
|
4
4
|
import type { Env } from "../env";
|
|
5
|
+
import { isAllowedBubbleOrigin } from "../bubble.ts";
|
|
5
6
|
|
|
6
7
|
export const json = (body: unknown, status = 200, extra: Record<string, string> = {}) =>
|
|
7
8
|
new Response(JSON.stringify(body), {
|
|
@@ -14,14 +15,9 @@ export const err = (status: number, code: string, message: string) =>
|
|
|
14
15
|
|
|
15
16
|
export function corsHeaders(req: Request): Record<string, string> {
|
|
16
17
|
const origin = req.headers.get("origin") ?? "";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// the local dev posture: the platform and the minisites develop on
|
|
21
|
-
// localhost ports against the live service (the bubble bridge admits
|
|
22
|
-
// the same class; anon quota is per-IP, member auth needs the token)
|
|
23
|
-
/^http:\/\/localhost(:\d{1,5})?$/.test(origin) ||
|
|
24
|
-
/^http:\/\/127\.0\.0\.1(:\d{1,5})?$/.test(origin);
|
|
18
|
+
// the publisher origin family (profile-declared) + the local dev
|
|
19
|
+
// posture — the same rule the bubble bridge applies, from one place
|
|
20
|
+
const allowed = isAllowedBubbleOrigin(origin);
|
|
25
21
|
return allowed
|
|
26
22
|
? {
|
|
27
23
|
"access-control-allow-origin": origin,
|
|
@@ -52,7 +48,7 @@ export interface ApiKey {
|
|
|
52
48
|
day_limit: number;
|
|
53
49
|
}
|
|
54
50
|
|
|
55
|
-
export async function authenticate(env: Env, req: Request): Promise<ApiKey | null> {
|
|
51
|
+
export async function authenticate(env: Pick<Env, "DB">, req: Request): Promise<ApiKey | null> {
|
|
56
52
|
const auth = req.headers.get("authorization") ?? "";
|
|
57
53
|
const m = auth.match(/^Bearer\s+(.+)$/i);
|
|
58
54
|
if (!m) return null;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* dependency-free (context.ts's pattern: the unit tests load it on
|
|
26
26
|
* plain node's type stripping, which never resolves extensionless
|
|
27
27
|
* relative imports). */
|
|
28
|
+
import { P } from "./profile.ts";
|
|
28
29
|
async function sha256Hex(s: string): Promise<string> {
|
|
29
30
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s));
|
|
30
31
|
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
@@ -174,7 +175,7 @@ export type LiveRead =
|
|
|
174
175
|
/** The platform page for a record — the role family decides the console
|
|
175
176
|
* (the same page the user's own browser would open). */
|
|
176
177
|
function recordUrl(cfg: LiveDataConfig, roleFamily: string, store: string, row: any): string {
|
|
177
|
-
const std = typeof row.standard_id === "string" ? row.standard_id.replace(
|
|
178
|
+
const std = typeof row.standard_id === "string" ? row.standard_id.replace(new RegExp(`^${P().publisher.id}-`, "i"), "") : null;
|
|
178
179
|
if (store === "certificates") {
|
|
179
180
|
if (roleFamily === "applicant") return `${cfg.platformApi}/app/portal/certificates/${row.id}`;
|
|
180
181
|
if (std) return `${cfg.platformApi}/app/standards/${std}/certificates/${row.id}`;
|
|
@@ -245,7 +246,7 @@ export async function readMyAccount(_env: any, cfg: LiveDataConfig, token: strin
|
|
|
245
246
|
records.push({
|
|
246
247
|
store: "applications",
|
|
247
248
|
id: String(row.id),
|
|
248
|
-
label: `Application ${row.application_number ?? row.id}${row.standard_id ? ` — ${String(row.standard_id).replace(
|
|
249
|
+
label: `Application ${row.application_number ?? row.id}${row.standard_id ? ` — ${String(row.standard_id).replace(new RegExp(`^${P().publisher.id}-`, "i"), "").toUpperCase().replace(/^R(\d)/, "R $1")}` : ""}`,
|
|
249
250
|
url: recordUrl(cfg, family, "applications", row),
|
|
250
251
|
status: row.status,
|
|
251
252
|
date: row.submitted_date ?? row.date_of_application,
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* slug segments (the packages' identifier shapes — /req/<class>/<id>,
|
|
30
30
|
* /conf/<class>/<id>, /term/<id>, /constraint/<id>, /characteristic/<id>,
|
|
31
31
|
* /state-machine/<id>, /dimension/<id>). */
|
|
32
|
+
import { P } from "./profile.ts";
|
|
32
33
|
const NODE_RE = /(?:^|[\s("'`])\/(req|conf|term|constraint|characteristic|state-machine|dimension)\/([a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)?)(?=[\s)"'`,;:.]|$)/i;
|
|
33
34
|
|
|
34
35
|
/** The first model-node id a text names (the declared chip label first,
|
|
@@ -46,7 +47,9 @@ export function modelNodeRefIn(text: string | undefined | null): string | null {
|
|
|
46
47
|
* carry a plane; anything else resolves null (honest: no model to bind). */
|
|
47
48
|
export function standardForDocNumber(docNumber: string | undefined): string | null {
|
|
48
49
|
if (!docNumber) return null;
|
|
49
|
-
|
|
50
|
+
const models = P().sources?.models;
|
|
51
|
+
if (!models?.standards?.length || !models?.standard_prefix) return null;
|
|
52
|
+
return (models.standards as string[]).includes(docNumber) ? `${models.standard_prefix}${docNumber}` : null;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
export interface BoundModelNode {
|
|
@@ -139,7 +142,7 @@ export function modelGroundingBlock(node: BoundModelNode): string {
|
|
|
139
142
|
const c = node.content ?? {};
|
|
140
143
|
const lines: string[] = [];
|
|
141
144
|
lines.push(
|
|
142
|
-
|
|
145
|
+
P().prompts.vars.model_grounding_intro ?? "Model grounding — the model plane's own statement:",
|
|
143
146
|
);
|
|
144
147
|
lines.push(`Node: ${node.node_id} (${node.kind.replace(/_/g, " ")}) — ${node.name} [${node.standard}]`);
|
|
145
148
|
if (node.clause) lines.push(`Provenance: ${node.clause.urn}`);
|
|
@@ -183,7 +186,11 @@ export function modelGroundingBlock(node: BoundModelNode): string {
|
|
|
183
186
|
export function modelCitation(node: BoundModelNode) {
|
|
184
187
|
return {
|
|
185
188
|
doc_id: `model:${node.standard}`,
|
|
186
|
-
docidentifier:
|
|
189
|
+
docidentifier: `${P().publisher.name} SMART model (${(() => {
|
|
190
|
+
const prefix = P().sources?.models?.standard_prefix ?? "";
|
|
191
|
+
const letter = prefix.replace(/^.*-/, "").toUpperCase();
|
|
192
|
+
return String(node.standard).replace(new RegExp(`^${prefix}`, "i"), `${letter} `);
|
|
193
|
+
})()})`,
|
|
187
194
|
edition: "",
|
|
188
195
|
language: "en",
|
|
189
196
|
clause_anchor: node.clause?.ref || "model",
|
|
@@ -209,5 +216,6 @@ export function modelEcho(node: BoundModelNode) {
|
|
|
209
216
|
|
|
210
217
|
/** The per-corpus guidance note (config.ts's DATASETS pattern — every
|
|
211
218
|
* retrieved model-plane chunk carries it, chip or no chip). */
|
|
212
|
-
export
|
|
213
|
-
|
|
219
|
+
export function modelCorpusNote(): string {
|
|
220
|
+
return P().prompts.vars.model_passage_note ?? "";
|
|
221
|
+
}
|
|
@@ -12,9 +12,24 @@ import { tableContext } from "./tablecontext";
|
|
|
12
12
|
// here so the existing import surface keeps working
|
|
13
13
|
export { refusalAnswer } from "./refusal";
|
|
14
14
|
|
|
15
|
+
/** The interpolation source for every prompt: the profile's declared
|
|
16
|
+
* vars plus the derived publisher tokens. Call sites never build
|
|
17
|
+
* their own var map. */
|
|
18
|
+
export function promptVars(extra: Record<string, string> = {}): Record<string, string> {
|
|
19
|
+
// profile vars are snake_case keys; template tokens are UPPER_SNAKE —
|
|
20
|
+
// the map is built here, once, so no call site can spread the raw
|
|
21
|
+
// keys again (the pre-varianlization bug: the identity token sat
|
|
22
|
+
// unmatched and rendered empty in the system prompt)
|
|
23
|
+
const out: Record<string, string> = { PUBLISHER_NAME: P().publisher.name };
|
|
24
|
+
for (const [k, v] of Object.entries(P().prompts?.vars ?? {})) {
|
|
25
|
+
if (typeof v === "string") out[k.toUpperCase()] = v;
|
|
26
|
+
}
|
|
27
|
+
return { ...out, ...extra };
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
/** Fill {{TOKEN}} placeholders in a prompt data file. Unknown/empty tokens
|
|
16
31
|
* resolve to "" so optional lines vanish cleanly. */
|
|
17
|
-
function fill(template: string, vars: Record<string, string>): string {
|
|
32
|
+
export function fill(template: string, vars: Record<string, string>): string {
|
|
18
33
|
return template.replace(/\{\{(\w+)\}\}/g, (_m, k: string) => (k in vars ? vars[k] : ""));
|
|
19
34
|
}
|
|
20
35
|
import { QueryFilters, toVectorizeFilter } from "./selfquery";
|
|
@@ -168,7 +183,7 @@ export function identityNote(member: boolean): string {
|
|
|
168
183
|
const upsell = locked.length
|
|
169
184
|
? `Signed-in members additionally search: ${locked.map((d) => `${d.label} (${d.description})`).join("; ")}.`
|
|
170
185
|
: "";
|
|
171
|
-
return fill(conversationalPromptText, { CORPORA: corpora, UPSELL: upsell })
|
|
186
|
+
return fill(conversationalPromptText, promptVars({ CORPORA: corpora, UPSELL: upsell }))
|
|
172
187
|
.split("\n")
|
|
173
188
|
.filter((l) => l.trim())
|
|
174
189
|
.join("\n");
|
|
@@ -260,14 +275,13 @@ export function buildMessages(
|
|
|
260
275
|
|
|
261
276
|
// the prompt itself is data (prompts/system.md); one rule per line,
|
|
262
277
|
// joined with spaces exactly as the original array form
|
|
263
|
-
const system = fill(systemPromptText, {
|
|
264
|
-
...P().prompts.vars,
|
|
278
|
+
const system = fill(systemPromptText, promptVars({
|
|
265
279
|
HISTORY_CONTEXT: history.length
|
|
266
280
|
? " Earlier turns of this conversation are provided for context — answer the LATEST question, treating the passages below as the source of truth for facts and citations."
|
|
267
281
|
: "",
|
|
268
282
|
CORPUS_NOTES: corpusNotes,
|
|
269
283
|
LANG_CLAUSE: lang ? ` (explicitly requested: ${lang})` : "",
|
|
270
|
-
})
|
|
284
|
+
}))
|
|
271
285
|
.split("\n")
|
|
272
286
|
.map((l) => l.trim())
|
|
273
287
|
.filter(Boolean)
|
|
@@ -348,12 +362,13 @@ export function buildMessages(
|
|
|
348
362
|
};
|
|
349
363
|
}
|
|
350
364
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
365
|
+
/** The publisher's catalog page for a publication, from the profile's
|
|
366
|
+
* URL template ({type} = lowercase doctype, then the number); no
|
|
367
|
+
* template, no catalog link. */
|
|
368
|
+
function publicationUrl(meta: ChunkMeta): string | undefined {
|
|
369
|
+
const tpl = P().publisher.catalog_url_template;
|
|
370
|
+
if (!tpl || !meta.doctype || !meta.doc_number) return undefined;
|
|
371
|
+
return tpl.replace("{type}", meta.doctype.toLowerCase()) + meta.doc_number;
|
|
357
372
|
}
|
|
358
373
|
|
|
359
374
|
export function citations(hits: Hit[]) {
|
|
@@ -368,8 +383,8 @@ export function citations(hits: Hit[]) {
|
|
|
368
383
|
clause_title: h.metadata.clause_title,
|
|
369
384
|
status: h.metadata.status ?? "unknown",
|
|
370
385
|
superseded_by: h.metadata.superseded_by || undefined,
|
|
371
|
-
corpus: h.metadata.corpus ||
|
|
372
|
-
url:
|
|
386
|
+
corpus: h.metadata.corpus || P().publisher.id,
|
|
387
|
+
url: publicationUrl(h.metadata),
|
|
373
388
|
snippet: h.text.slice(0, 400),
|
|
374
389
|
score: h.rerank_score ?? h.score,
|
|
375
390
|
}))
|
|
@@ -8,12 +8,21 @@ export const PROFILE = {
|
|
|
8
8
|
"product_name": "Fixture Answers",
|
|
9
9
|
"description": "A minimal publisher profile exercising every declared surface: an open dataset, a permission-gated dataset, production and lane corpora, prompt vars and retrieval vocabulary.",
|
|
10
10
|
"domains": {
|
|
11
|
-
"public": "fixture.example.org"
|
|
11
|
+
"public": "fixture.example.org",
|
|
12
|
+
"origin_suffix": "fixture.example.org"
|
|
12
13
|
},
|
|
13
14
|
"identity": {
|
|
14
15
|
"issuer": "https://id.fixture.example.org"
|
|
15
16
|
},
|
|
16
|
-
"codec": "plain-slug"
|
|
17
|
+
"codec": "plain-slug",
|
|
18
|
+
"session_cookie": "fixture-session",
|
|
19
|
+
"references": {
|
|
20
|
+
"label_prefix": ""
|
|
21
|
+
},
|
|
22
|
+
"features": {
|
|
23
|
+
"drafts": false,
|
|
24
|
+
"model_plane": false
|
|
25
|
+
}
|
|
17
26
|
},
|
|
18
27
|
"datasets": [
|
|
19
28
|
{
|
|
@@ -90,12 +99,22 @@ export const PROFILE = {
|
|
|
90
99
|
]
|
|
91
100
|
},
|
|
92
101
|
"retrieval": {
|
|
93
|
-
"process_expansion": " fixture certification system framework application evaluation"
|
|
102
|
+
"process_expansion": " fixture certification system framework application evaluation",
|
|
103
|
+
"process_note": "Retrieval note: these passages come from the fixture certification system documents because they govern application procedures for fixture publications."
|
|
94
104
|
},
|
|
95
105
|
"prompts": {
|
|
96
106
|
"vars": {
|
|
97
107
|
"assistant_identity": "the fixture assistant — a public service answering questions about the fixture publisher's documents",
|
|
98
|
-
"refusal_sentence": "I don't have information on this in the indexed fixture documents."
|
|
108
|
+
"refusal_sentence": "I don't have information on this in the indexed fixture documents.",
|
|
109
|
+
"account_note_source": "the user's own fixture account",
|
|
110
|
+
"corpus_kind": "a fixture corpus publication",
|
|
111
|
+
"corpus_kind_plural": "fixture corpus publications",
|
|
112
|
+
"cite_example": "FIXTURE 1:2024 §2.1",
|
|
113
|
+
"cite_quote_example": "FIXTURE 1:2024 §2.1: \"the limit shall not exceed one interval\"",
|
|
114
|
+
"parts_example": "FIXTURE 1-1, FIXTURE 1-A",
|
|
115
|
+
"docid_example": "FIXTURE 1-2",
|
|
116
|
+
"spelling_examples": "\"f1\", \"FIXTURE 1\"",
|
|
117
|
+
"process_vocab": "the fixture certification system framework"
|
|
99
118
|
}
|
|
100
119
|
}
|
|
101
120
|
} as const;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// GENERATED from profile2/*.yaml — regenerate: node scripts/gen_profile.mjs
|
|
2
|
+
// (never edit; the drift test compares this file to the sources)
|
|
3
|
+
export const PROFILE = {
|
|
4
|
+
"publisher": {
|
|
5
|
+
"id": "atlas",
|
|
6
|
+
"name": "Atlas",
|
|
7
|
+
"full_name": "The Atlas Standards Institute",
|
|
8
|
+
"product_name": "Atlas Answers",
|
|
9
|
+
"description": "The second fixture publisher of the reference matrix: a technical-standards institute issuing specifications and errata, with a working-group corpus behind a session permission.",
|
|
10
|
+
"domains": {
|
|
11
|
+
"public": "answers.atlas.example",
|
|
12
|
+
"origin_suffix": "atlas.example"
|
|
13
|
+
},
|
|
14
|
+
"identity": {
|
|
15
|
+
"issuer": "https://identity.atlas.example"
|
|
16
|
+
},
|
|
17
|
+
"codec": "plain-slug",
|
|
18
|
+
"session_cookie": "atlas-session",
|
|
19
|
+
"references": {
|
|
20
|
+
"label_prefix": "ATLAS"
|
|
21
|
+
},
|
|
22
|
+
"production": [
|
|
23
|
+
"spec",
|
|
24
|
+
"errata",
|
|
25
|
+
"model"
|
|
26
|
+
],
|
|
27
|
+
"lanes": {
|
|
28
|
+
"review": [
|
|
29
|
+
"review"
|
|
30
|
+
],
|
|
31
|
+
"glossary": [
|
|
32
|
+
"glossary"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"features": {
|
|
36
|
+
"drafts": false,
|
|
37
|
+
"model_plane": false
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"datasets": [
|
|
41
|
+
{
|
|
42
|
+
"id": "spec",
|
|
43
|
+
"label": "Atlas Specifications",
|
|
44
|
+
"description": "The institute's published specifications and errata",
|
|
45
|
+
"corpora": [
|
|
46
|
+
"spec",
|
|
47
|
+
"errata"
|
|
48
|
+
],
|
|
49
|
+
"note": "Some passages come from the Atlas errata corpus — cite them the same way as every other passage."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "wg",
|
|
53
|
+
"label": "Working-group corpus",
|
|
54
|
+
"description": "An access-restricted corpus proving the permission gate",
|
|
55
|
+
"session": true,
|
|
56
|
+
"permission": "committee",
|
|
57
|
+
"corpora": [
|
|
58
|
+
"wg"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"corpora": {
|
|
63
|
+
"production": [
|
|
64
|
+
"spec",
|
|
65
|
+
"errata",
|
|
66
|
+
"model"
|
|
67
|
+
],
|
|
68
|
+
"lanes": {
|
|
69
|
+
"review": [
|
|
70
|
+
"review"
|
|
71
|
+
],
|
|
72
|
+
"glossary": [
|
|
73
|
+
"glossary"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"corpora": {
|
|
77
|
+
"spec": {
|
|
78
|
+
"repo": "fixtures/matrix2",
|
|
79
|
+
"note": "the second fixture corpus"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"bibliography": {},
|
|
83
|
+
"terminology": {},
|
|
84
|
+
"models": {}
|
|
85
|
+
},
|
|
86
|
+
"sources": {
|
|
87
|
+
"corpora": {},
|
|
88
|
+
"bibliography": {},
|
|
89
|
+
"terminology": {},
|
|
90
|
+
"models": {}
|
|
91
|
+
},
|
|
92
|
+
"ui": {
|
|
93
|
+
"suggestions": [
|
|
94
|
+
"What does ATLAS 12 specify?",
|
|
95
|
+
"Which errata are open?"
|
|
96
|
+
],
|
|
97
|
+
"models_disclosure": [
|
|
98
|
+
{
|
|
99
|
+
"role": "Answers",
|
|
100
|
+
"model": "atlas-answer-model"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"smoke": [
|
|
104
|
+
{
|
|
105
|
+
"label": "sanity",
|
|
106
|
+
"query": "What does ATLAS 12 specify?",
|
|
107
|
+
"expect": "ATLAS"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"retrieval": {
|
|
112
|
+
"process_expansion": " atlas review procedure errata committee specification",
|
|
113
|
+
"process_note": "Retrieval note: these passages come from the Atlas review-procedure documents because they govern the institute's publication process."
|
|
114
|
+
},
|
|
115
|
+
"prompts": {
|
|
116
|
+
"vars": {
|
|
117
|
+
"assistant_identity": "the Atlas Answers assistant — a public service answering questions about the Atlas Standards Institute's specifications",
|
|
118
|
+
"refusal_sentence": "I don't have information on this in the indexed Atlas specifications.",
|
|
119
|
+
"account_note_source": "the user's own Atlas Answers account"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} as const;
|
|
@@ -8,27 +8,24 @@ export function refusalAnswer(): string {
|
|
|
8
8
|
|
|
9
9
|
// the model occasionally paraphrases the refusal sentence ("...information
|
|
10
10
|
// on how to make lasagna in the indexed..."); the API contract is the
|
|
11
|
-
// exact canonical sentence — normalize variants, keep the redirect tail
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
|
|
30
|
-
/^\s*I don[’']?t have any indexed OIML \w+(?:s)? (?:covering|about|on)\b/im,
|
|
31
|
-
];
|
|
11
|
+
// exact canonical sentence — normalize variants, keep the redirect tail.
|
|
12
|
+
// The patterns are built per call: the publisher token is profile data.
|
|
13
|
+
function refusalPatterns(publisher: string): { variant: RegExp; drift: RegExp[] } {
|
|
14
|
+
return {
|
|
15
|
+
variant: new RegExp(`^\\s*I don[’']?t have information on .{1,120}? in the indexed ${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\.?`, "i"),
|
|
16
|
+
drift: [
|
|
17
|
+
new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
|
|
18
|
+
new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
|
|
19
|
+
/\b(?:falls|well) outside\b[^.]{0,120}?\b(?:what I can answer|my scope|the scope of)\b/i,
|
|
20
|
+
/\boutside (?:of )?what (?:I|this service) can answer\b/i,
|
|
21
|
+
// "I can't answer that — weather forecasting is outside my scope":
|
|
22
|
+
// requires the refusal verb, so a scope DISCUSSION inside a real answer
|
|
23
|
+
// ("this exemption is outside the scope of R 60") never matches
|
|
24
|
+
/\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
|
|
25
|
+
new RegExp(`^\\s*I don[’']?t have any indexed ${publisher} \\w+(?:s)? (?:covering|about|on)\b`, "im"),
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
32
29
|
|
|
33
30
|
/** Start of the sentence containing offset `i` (after the nearest ". ",
|
|
34
31
|
* "! ", "? ", or newline before it, else the string start). */
|
|
@@ -54,6 +51,7 @@ function sentenceEnd(answer: string, i: number): number {
|
|
|
54
51
|
export function canonicalRefusal(answer: string): string {
|
|
55
52
|
const CANON = refusalAnswer();
|
|
56
53
|
if (answer.includes(CANON)) return answer;
|
|
54
|
+
const { variant: REFUSAL_VARIANT, drift: REFUSAL_DRIFT } = refusalPatterns(P().publisher.name);
|
|
57
55
|
const variant = answer.match(REFUSAL_VARIANT);
|
|
58
56
|
if (variant) return answer.replace(variant[0], CANON);
|
|
59
57
|
for (const drift of REFUSAL_DRIFT) {
|