@konneal/engine 0.2.55 → 0.2.56
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-PJBGJIXF.js → ask-5XMD22SJ.js} +1 -1
- package/dist/{chunk-DKQXJN6G.js → chunk-ZVMQHCNL.js} +59 -2
- package/dist/nameplate-parse.d.ts +8 -0
- package/dist/nameplate.d.ts +5 -0
- package/dist/prompts/nameplate.md +8 -0
- package/dist/worker_public/src/index.js +2 -2
- package/package.json +1 -1
- package/workers/worker_public/prompts/nameplate.md +8 -0
- package/workers/worker_public/src/ask.ts +16 -1
- package/workers/worker_public/src/certificates.ts +1 -1
- package/workers/worker_public/src/nameplate-parse.ts +29 -0
- package/workers/worker_public/src/nameplate.ts +33 -0
|
@@ -1866,7 +1866,7 @@ function exactCacheKey(indexVersion, gen, ns, queryHash) {
|
|
|
1866
1866
|
|
|
1867
1867
|
// workers/worker_public/src/certificates.ts
|
|
1868
1868
|
function isRegisterShaped(query) {
|
|
1869
|
-
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);
|
|
1869
|
+
return /\bcertif(ied|icates?|ication)s?\b/i.test(query) && /\b(is|are|still|currently|valid|status|suspended|revoked|was|were|have|has|hold|holds|possess|carry|got)\b/i.test(query);
|
|
1870
1870
|
}
|
|
1871
1871
|
function queryFamily(query) {
|
|
1872
1872
|
const m = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(query);
|
|
@@ -1979,6 +1979,53 @@ function registerNote(rows) {
|
|
|
1979
1979
|
].join("\n");
|
|
1980
1980
|
}
|
|
1981
1981
|
|
|
1982
|
+
// workers/worker_public/prompts/nameplate.md
|
|
1983
|
+
var nameplate_default = `You read instrument nameplates. From the photograph, extract the
|
|
1984
|
+
manufacturer's name exactly as printed and the model designation exactly
|
|
1985
|
+
as printed. Answer with ONLY a JSON object on one line:
|
|
1986
|
+
|
|
1987
|
+
{"manufacturer": "\u2026", "model": "\u2026"}
|
|
1988
|
+
|
|
1989
|
+
If the photograph shows no manufacturer or no model, use null for the
|
|
1990
|
+
missing value. Never guess beyond what is printed.
|
|
1991
|
+
`;
|
|
1992
|
+
|
|
1993
|
+
// workers/worker_public/src/nameplate-parse.ts
|
|
1994
|
+
function parseNameplate(text) {
|
|
1995
|
+
const m = /\{[^{}]*\}/.exec(String(text ?? ""));
|
|
1996
|
+
if (!m) return null;
|
|
1997
|
+
try {
|
|
1998
|
+
const j = JSON.parse(m[0]);
|
|
1999
|
+
const clean = (v) => typeof v === "string" && v.trim() ? v.trim().slice(0, 80) : null;
|
|
2000
|
+
const out = { manufacturer: clean(j.manufacturer), model: clean(j.model) };
|
|
2001
|
+
return out.manufacturer || out.model ? out : null;
|
|
2002
|
+
} catch {
|
|
2003
|
+
return null;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
function nameplateRegisterQuery(np, question) {
|
|
2007
|
+
const fam = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(question);
|
|
2008
|
+
return [np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// workers/worker_public/src/nameplate.ts
|
|
2012
|
+
async function extractNameplate(ai, model, image) {
|
|
2013
|
+
try {
|
|
2014
|
+
const res = await ai.run(model, {
|
|
2015
|
+
messages: [
|
|
2016
|
+
{ role: "system", content: nameplate_default.trimEnd() },
|
|
2017
|
+
{ role: "user", content: [{ type: "image_url", image_url: { url: image } }] }
|
|
2018
|
+
],
|
|
2019
|
+
max_tokens: 512,
|
|
2020
|
+
reasoning_effort: "low"
|
|
2021
|
+
});
|
|
2022
|
+
const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
|
|
2023
|
+
return parseNameplate(text ?? "");
|
|
2024
|
+
} catch {
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1982
2029
|
// workers/worker_public/src/ask.ts
|
|
1983
2030
|
function userImageDataUrl(body) {
|
|
1984
2031
|
const img = body?.image;
|
|
@@ -2332,7 +2379,17 @@ async function handleAsk(env, ctx, req, tier, key) {
|
|
|
2332
2379
|
console.log("understand:", understanding?.intent ?? "null", understanding?.doc_number ? `doc#${understanding.doc_number}${understanding.edition ? "@" + understanding.edition : ""}` : "nodoc", "|", q.query.slice(0, 50));
|
|
2333
2380
|
const graphDocNumbers = await graphExpand(env, understanding);
|
|
2334
2381
|
const eNote = await editionNote(env, understanding);
|
|
2335
|
-
|
|
2382
|
+
let register = await searchRegister(env.DB, q.query);
|
|
2383
|
+
if (withImage && !register?.rows?.length && /certificate|certified|certification/i.test(q.query)) {
|
|
2384
|
+
const np = await extractNameplate(env.AI, MODELS.member, withImage);
|
|
2385
|
+
if (np?.manufacturer) {
|
|
2386
|
+
const bridged = await searchRegister(env.DB, nameplateRegisterQuery(np, q.query));
|
|
2387
|
+
if (bridged?.rows?.length) {
|
|
2388
|
+
register = bridged;
|
|
2389
|
+
console.log("nameplate-bridge:", np.manufacturer, np.model ?? "", "\u2192", bridged.rows.length, "register rows");
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2336
2393
|
console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
|
|
2337
2394
|
const regNote = register ? registerNote(register.rows) : void 0;
|
|
2338
2395
|
if (understanding?.intent === "conversational") {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface Nameplate {
|
|
2
|
+
manufacturer: string | null;
|
|
3
|
+
model: string | null;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseNameplate(text: string): Nameplate | null;
|
|
6
|
+
/** The register query the extraction feeds: the nameplate's tokens lead,
|
|
7
|
+
* the question's family (if named) scopes. */
|
|
8
|
+
export declare function nameplateRegisterQuery(np: Nameplate, question: string): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { nameplateRegisterQuery, type Nameplate } from "./nameplate-parse.ts";
|
|
2
|
+
export type { Nameplate };
|
|
3
|
+
export { nameplateRegisterQuery };
|
|
4
|
+
export declare function extractNameplate(ai: any, model: string, image: string): Promise<Nameplate | null>;
|
|
5
|
+
export declare const nameplateModel: () => "@cf/zai-org/glm-5.3-flash";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
You read instrument nameplates. From the photograph, extract the
|
|
2
|
+
manufacturer's name exactly as printed and the model designation exactly
|
|
3
|
+
as printed. Answer with ONLY a JSON object on one line:
|
|
4
|
+
|
|
5
|
+
{"manufacturer": "…", "model": "…"}
|
|
6
|
+
|
|
7
|
+
If the photograph shows no manufacturer or no model, use null for the
|
|
8
|
+
missing value. Never guess beyond what is printed.
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
modelGroundingBlock,
|
|
10
10
|
scoreFaithfulness,
|
|
11
11
|
scoreJudge
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-ZVMQHCNL.js";
|
|
13
13
|
import {
|
|
14
14
|
buildMessages,
|
|
15
15
|
cfBlobs,
|
|
@@ -958,7 +958,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
958
958
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
959
959
|
body: JSON.stringify({ ...args, stream: false })
|
|
960
960
|
});
|
|
961
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
961
|
+
const res = name === "ask" ? await (await import("../../ask-5XMD22SJ.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-PBOODCMQ.js")).handleSearch(env, ctx, inner, tier, key);
|
|
962
962
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
963
963
|
});
|
|
964
964
|
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.56",
|
|
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,8 @@
|
|
|
1
|
+
You read instrument nameplates. From the photograph, extract the
|
|
2
|
+
manufacturer's name exactly as printed and the model designation exactly
|
|
3
|
+
as printed. Answer with ONLY a JSON object on one line:
|
|
4
|
+
|
|
5
|
+
{"manufacturer": "…", "model": "…"}
|
|
6
|
+
|
|
7
|
+
If the photograph shows no manufacturer or no model, use null for the
|
|
8
|
+
missing value. Never guess beyond what is printed.
|
|
@@ -40,6 +40,7 @@ import { json, err, corsHeaders, readJson, validateQuery, type ApiKey } from "./
|
|
|
40
40
|
import { clientIp, checkQuota, telemetry } from "./quota";
|
|
41
41
|
import { graphExpand, editionNote } from "./graph";
|
|
42
42
|
import { registerNote, searchRegister } from "./certificates";
|
|
43
|
+
import { extractNameplate, nameplateRegisterQuery } from "./nameplate.ts";
|
|
43
44
|
import { P } from "./profile.ts";
|
|
44
45
|
|
|
45
46
|
/** User-uploaded image for multimodal questions: a data URL
|
|
@@ -619,7 +620,21 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
619
620
|
// register-shaped question queries the D1 table directly and the rows
|
|
620
621
|
// join the prompt as an authoritative note — exact matches, never a
|
|
621
622
|
// similarity guess about certification standing
|
|
622
|
-
|
|
623
|
+
let register = await searchRegister(env.DB, q.query);
|
|
624
|
+
// the nameplate bridge (TODO.new-era/6's image lane): a certificate
|
|
625
|
+
// question WITH a photograph carries its manufacturer in the pixels —
|
|
626
|
+
// one vision call extracts it, and those tokens re-query the register
|
|
627
|
+
// so the answer cites the actual certificate rows
|
|
628
|
+
if (withImage && !register?.rows?.length && /certificate|certified|certification/i.test(q.query)) {
|
|
629
|
+
const np = await extractNameplate((env as any).AI, MODELS.member, withImage);
|
|
630
|
+
if (np?.manufacturer) {
|
|
631
|
+
const bridged = await searchRegister(env.DB, nameplateRegisterQuery(np, q.query));
|
|
632
|
+
if (bridged?.rows?.length) {
|
|
633
|
+
register = bridged;
|
|
634
|
+
console.log("nameplate-bridge:", np.manufacturer, np.model ?? "", "→", bridged.rows.length, "register rows");
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
623
638
|
console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
|
|
624
639
|
const regNote = register ? registerNote(register.rows) : undefined;
|
|
625
640
|
|
|
@@ -22,7 +22,7 @@ export interface RegisterRow {
|
|
|
22
22
|
* trigger the citation-graph notes already use (query-shaped notes,
|
|
23
23
|
* never a router). */
|
|
24
24
|
export function isRegisterShaped(query: string): boolean {
|
|
25
|
-
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);
|
|
25
|
+
return /\bcertif(ied|icates?|ication)s?\b/i.test(query) && /\b(is|are|still|currently|valid|status|suspended|revoked|was|were|have|has|hold|holds|possess|carry|got)\b/i.test(query);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Search tokens: words that can identify a holder or a model — words of
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The nameplate bridge's pure half (TODO.new-era/6's image lane): parse
|
|
2
|
+
// the vision call's answer into {manufacturer, model}. Lives apart from
|
|
3
|
+
// the AI-call module so the plain-node suites can exercise it (the
|
|
4
|
+
// prompt is a bundled .md, which only esbuild can load).
|
|
5
|
+
|
|
6
|
+
export interface Nameplate {
|
|
7
|
+
manufacturer: string | null;
|
|
8
|
+
model: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseNameplate(text: string): Nameplate | null {
|
|
12
|
+
const m = /\{[^{}]*\}/.exec(String(text ?? ""));
|
|
13
|
+
if (!m) return null;
|
|
14
|
+
try {
|
|
15
|
+
const j = JSON.parse(m[0]) as { manufacturer?: unknown; model?: unknown };
|
|
16
|
+
const clean = (v: unknown) => (typeof v === "string" && v.trim() ? v.trim().slice(0, 80) : null);
|
|
17
|
+
const out = { manufacturer: clean(j.manufacturer), model: clean(j.model) };
|
|
18
|
+
return out.manufacturer || out.model ? out : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The register query the extraction feeds: the nameplate's tokens lead,
|
|
25
|
+
* the question's family (if named) scopes. */
|
|
26
|
+
export function nameplateRegisterQuery(np: Nameplate, question: string): string {
|
|
27
|
+
const fam = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(question);
|
|
28
|
+
return [np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The nameplate bridge (TODO.new-era/6, the image lane): a certificate
|
|
2
|
+
// question that arrives WITH a photograph carries its manufacturer only
|
|
3
|
+
// in the pixels. A single low-effort vision call extracts the printed
|
|
4
|
+
// manufacturer and model, and those tokens feed the certificate
|
|
5
|
+
// register's D1 lane — so "does this load cell's manufacturer hold the
|
|
6
|
+
// certificate" answers with the actual register rows instead of advice.
|
|
7
|
+
// The shape rules are the 2026-09-09 multimodal findings: the image
|
|
8
|
+
// rides its OWN short user message, never a long passage.
|
|
9
|
+
import nameplatePrompt from "../prompts/nameplate.md";
|
|
10
|
+
import { parseNameplate, nameplateRegisterQuery, type Nameplate } from "./nameplate-parse.ts";
|
|
11
|
+
import { MODELS } from "./config.ts";
|
|
12
|
+
|
|
13
|
+
export type { Nameplate };
|
|
14
|
+
export { nameplateRegisterQuery };
|
|
15
|
+
|
|
16
|
+
export async function extractNameplate(ai: any, model: string, image: string): Promise<Nameplate | null> {
|
|
17
|
+
try {
|
|
18
|
+
const res: any = await ai.run(model, {
|
|
19
|
+
messages: [
|
|
20
|
+
{ role: "system", content: nameplatePrompt.trimEnd() },
|
|
21
|
+
{ role: "user", content: [{ type: "image_url", image_url: { url: image } }] },
|
|
22
|
+
],
|
|
23
|
+
max_tokens: 512,
|
|
24
|
+
reasoning_effort: "low",
|
|
25
|
+
});
|
|
26
|
+
const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
|
|
27
|
+
return parseNameplate(text ?? "");
|
|
28
|
+
} catch {
|
|
29
|
+
return null; // the bridge is optional — the ask never depends on it
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const nameplateModel = () => MODELS.member;
|