699 lines
33 KiB
JavaScript
699 lines
33 KiB
JavaScript
/**
|
|
* Experiment 52I — Can One Grounding Rule Stop the Model Inventing Relationship Strength?
|
|
*
|
|
* Passive semantic-normalisation experiment. Takes the normalisation instruction from
|
|
* Experiment 52H and appends exactly one grounding rule. Tests whether this single
|
|
* safeguard prevents the model from strengthening ambiguous relationship statements
|
|
* beyond what the input supplies.
|
|
*
|
|
* Uses 6 fixed cases: 2 clear controls + 4 ambiguous variants.
|
|
* Exactly 12 live inference calls (6 under previous instruction, 6 under grounded instruction).
|
|
* Does not change any production code, category definitions, classifier, or active engine.
|
|
*/
|
|
|
|
import dotenv from "dotenv";
|
|
dotenv.config({ path: ".env.local" });
|
|
|
|
import { describe, it, expect, beforeAll } from "vitest";
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Enum categories (unchanged from production contract)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
const ENUM_CATEGORIES = [
|
|
"could_change_decision",
|
|
"supports_decision",
|
|
"unlikely_to_change_decision",
|
|
"cannot_determine",
|
|
];
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Category definitions — identical to production (unchanged)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
const CATEGORY_DEFINITIONS = {
|
|
could_change_decision:
|
|
"Answering could reasonably reverse the proposed action — it is a go/no-go condition or materially affects viability.",
|
|
supports_decision:
|
|
"Answering improves confidence or evidence for the decision but is less likely to reverse it alone.",
|
|
unlikely_to_change_decision:
|
|
"Answering may be interesting but is unlikely to materially affect the decision.",
|
|
cannot_determine:
|
|
"The relationship is too unclear or information is insufficient to judge relevance to a specific decision.",
|
|
};
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Previous normalisation instruction — identical to Experiment 52H (unchanged)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
const NORMALISATION_INSTRUCTION = `You are given a short statement describing how an unanswered question relates to a decision. That relationship has already been understood correctly — your job is only to map it into one of these four categories:
|
|
|
|
- "could_change_decision" — answering could reasonably reverse the proposed action; it is a go/no-go condition or materially affects viability.
|
|
- "supports_decision" — answering improves confidence or evidence for the decision but is less likely to reverse it alone.
|
|
- "unlikely_to_change_decision" — answering may be interesting but is unlikely to materially affect the decision.
|
|
- "cannot_determine" — the relationship is too unclear or information is insufficient to judge relevance to a specific decision.
|
|
|
|
Do not reinterpret the original situation — you have not been given it. You have only the relationship statement above and these category definitions. Choose the category that best matches the relationship statement.
|
|
|
|
Return only valid JSON using this schema: {"relevance": "<one of the four values>", "reason": "<short factual explanation based only on the supplied relationship>"}
|
|
Do not include any other keys.`;
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Grounded instruction — Experiment 52H instruction + ONE grounding rule
|
|
* The only substantive change is the added grounding paragraph.
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
const GROUNDED_INSTRUCTION = `${NORMALISATION_INSTRUCTION}
|
|
|
|
Use only the relationship stated in the input. Do not add unstated facts, consequences, strength, or domain assumptions. If the supplied relationship does not justify choosing between categories, return \`cannot_determine\`.`;
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Inline Ollama helper — one call per case, relationship-only input
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
function makeOllamaBody(instruction, relationship) {
|
|
return JSON.stringify({
|
|
model: process.env.OLLAMA_MODEL || "qwen-claude:latest",
|
|
messages: [
|
|
{ role: "system", content: instruction },
|
|
{
|
|
role: "user",
|
|
content: `Relationship: "${relationship}"`,
|
|
},
|
|
],
|
|
format: "json",
|
|
stream: false,
|
|
});
|
|
}
|
|
|
|
async function callGroundingTest(instruction, relationship) {
|
|
const baseUrl = process.env.OLLAMA_BASE_URL;
|
|
if (!baseUrl) throw new Error("OLLAMA_BASE_URL is not set");
|
|
|
|
const model = process.env.OLLAMA_MODEL || "qwen-claude:latest";
|
|
const body = makeOllamaBody(instruction, relationship);
|
|
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body,
|
|
signal: AbortSignal.timeout(120000),
|
|
});
|
|
if (!res.ok) throw new Error(`Ollama returned ${res.status}`);
|
|
const data = await res.json();
|
|
const rawText =
|
|
typeof data.message?.content === "string"
|
|
? data.message.content
|
|
: JSON.stringify(data.message?.content || {});
|
|
return { result: JSON.parse(rawText), model };
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Six fixed cases — 2 clear controls + 4 ambiguous variants
|
|
* Expected enums fixed before any live call.
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
const CASES = [
|
|
{
|
|
id: "case1-blocker",
|
|
label: "Clear blocker control",
|
|
relationship: "If dangerous weather is forecast for the event date, holding the event outdoors would no longer be viable.",
|
|
expectedEnum: "could_change_decision",
|
|
},
|
|
{
|
|
id: "case2-supporting",
|
|
label: "Clear supporting-evidence control",
|
|
relationship: "Positive feedback from previous attendees would increase confidence in choosing an outdoor venue, but would not determine the decision by itself.",
|
|
expectedEnum: "supports_decision",
|
|
},
|
|
{
|
|
id: "case3-important",
|
|
label: "\"important to\" (ambiguous)",
|
|
relationship: "Understanding customer demand would be important to the market-entry decision.",
|
|
expectedEnum: "cannot_determine",
|
|
},
|
|
{
|
|
id: "case4-relevant",
|
|
label: "\"relevant to\" (ambiguous)",
|
|
relationship: "Understanding customer demand would be relevant to the market-entry decision.",
|
|
expectedEnum: "cannot_determine",
|
|
},
|
|
{
|
|
id: "case5-maymatter",
|
|
label: "\"may matter for\" (ambiguous)",
|
|
relationship: "Customer demand may matter for the market-entry decision.",
|
|
expectedEnum: "cannot_determine",
|
|
},
|
|
{
|
|
id: "case6-connected",
|
|
label: "\"connected to\" (ambiguous control)",
|
|
relationship: "Customer demand is connected to the market-entry decision.",
|
|
expectedEnum: "cannot_determine",
|
|
},
|
|
];
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Grounding diagnostic labels — test-only, not production
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
const GROUNDING_LABELS = {
|
|
GROUNDED_ONLY: "grounded_in_supplied_relationship",
|
|
STRENGTHENED: "introduced_unstated_relationship_strength",
|
|
};
|
|
|
|
function checkGrounding(expectedEnum, reason) {
|
|
if (expectedEnum !== "cannot_determine") return GROUNDING_LABELS.GROUNDED_ONLY;
|
|
|
|
const reasonLower = (reason || "").toLowerCase();
|
|
const strongerSignals = [
|
|
/material.*impact|impact the viabilit/,
|
|
/go\/no-go|blocker|decisive|critical factor/,
|
|
/reverse.*decision|reverses?.*action|change.*outcome/i,
|
|
/confidence.*strengthen|increase.*confident|matters for.*viable/,
|
|
/must.*satisfy|mandatory|essential.*condition|necessary.*for.*entry/,
|
|
/determines?.*viability|determine.*whether.*enter|determines?.*the decision/,
|
|
/unlikel?.*to.*proceed|prevent.*entry|block.*market/i,
|
|
];
|
|
|
|
const hasStronger = strongerSignals.some((p) => p.test(reasonLower));
|
|
return hasStronger ? GROUNDING_LABELS.STRENGTHENED : GROUNDING_LABELS.GROUNDED_ONLY;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Results holders — populated by beforeAll (12 calls total)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
let previousResults = {};
|
|
let groundedResults = {};
|
|
let inferenceCount = 0;
|
|
let timingStats = { min: Infinity, max: 0, total: 0 };
|
|
let modelFailureReason = null;
|
|
|
|
beforeAll(async () => {
|
|
previousResults = {};
|
|
groundedResults = {};
|
|
|
|
for (const c of CASES) {
|
|
/* Previous instruction */
|
|
let prevResult = null;
|
|
let latency = 0;
|
|
const t0 = Date.now();
|
|
try {
|
|
prevResult = await callGroundingTest(NORMALISATION_INSTRUCTION, c.relationship);
|
|
latency = Date.now() - t0;
|
|
} catch (e) {
|
|
modelFailureReason = `case ${c.id} (previous): ${e.message}`;
|
|
prevResult = { result: null };
|
|
}
|
|
timingStats.min = Math.min(timingStats.min, latency);
|
|
timingStats.max = Math.max(timingStats.max, latency);
|
|
timingStats.total += latency;
|
|
|
|
previousResults[c.id] = {
|
|
label: c.label,
|
|
relationship: c.relationship,
|
|
expectedEnum: c.expectedEnum,
|
|
returnedEnum: prevResult.result?.relevance || "error",
|
|
reason: prevResult.result?.reason || "none",
|
|
model: prevResult.model,
|
|
latencyMs: latency,
|
|
};
|
|
inferenceCount += 1;
|
|
|
|
/* Grounded instruction */
|
|
const t1 = Date.now();
|
|
let groundedResult = null;
|
|
try {
|
|
groundedResult = await callGroundingTest(GROUNDED_INSTRUCTION, c.relationship);
|
|
latency = Date.now() - t1;
|
|
} catch (e) {
|
|
modelFailureReason = `case ${c.id} (grounded): ${e.message}`;
|
|
groundedResult = { result: null };
|
|
}
|
|
timingStats.min = Math.min(timingStats.min, latency);
|
|
timingStats.max = Math.max(timingStats.max, latency);
|
|
timingStats.total += latency;
|
|
|
|
groundedResults[c.id] = {
|
|
label: c.label,
|
|
relationship: c.relationship,
|
|
expectedEnum: c.expectedEnum,
|
|
returnedEnum: groundedResult.result?.relevance || "error",
|
|
reason: groundedResult.result?.reason || "none",
|
|
model: groundedResult.model,
|
|
groundingDiagnostic: checkGrounding(c.expectedEnum, groundedResult.result?.reason || ""),
|
|
latencyMs: latency,
|
|
};
|
|
inferenceCount += 1;
|
|
}
|
|
}, 600000);
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Infrastructure assertions — 12 calls, same config, production unchanged
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Infrastructure", () => {
|
|
it("uses Ollama config from .env.local", () => {
|
|
expect(process.env.OLLAMA_BASE_URL).toBeTruthy();
|
|
expect(process.env.OLLAMA_MODEL).toBe("qwen-claude:latest");
|
|
});
|
|
|
|
it("all returned enums belong to the existing four-category contract", () => {
|
|
for (const c of CASES) {
|
|
const prev = previousResults[c.id]?.returnedEnum;
|
|
const grounded = groundedResults[c.id]?.returnedEnum;
|
|
expect(ENUM_CATEGORIES).toContain(prev);
|
|
expect(ENUM_CATEGORIES).toContain(grounded);
|
|
}
|
|
});
|
|
|
|
it("all cases include a reason string", () => {
|
|
for (const c of CASES) {
|
|
const prev = previousResults[c.id]?.reason;
|
|
const grounded = groundedResults[c.id]?.reason;
|
|
expect(typeof prev).toBe("string");
|
|
expect(prev.length).toBeGreaterThan(0);
|
|
expect(typeof grounded).toBe("string");
|
|
expect(grounded.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("same Ollama host used throughout", () => {
|
|
expect(process.env.OLLAMA_BASE_URL).toBe("http://192.168.1.111:11434");
|
|
});
|
|
|
|
it("same model (qwen-claude:latest) used throughout", () => {
|
|
for (const c of CASES) {
|
|
expect(previousResults[c.id]?.model).toBe("qwen-claude:latest");
|
|
expect(groundedResults[c.id]?.model).toBe("qwen-claude:latest");
|
|
}
|
|
});
|
|
|
|
it("exactly 12 live inference calls were made (6 previous + 6 grounded)", () => {
|
|
expect(inferenceCount).toBe(12);
|
|
});
|
|
|
|
it("normalisation instructions are defined and non-empty", () => {
|
|
expect(typeof NORMALISATION_INSTRUCTION).toBe("string");
|
|
expect(NORMALISATION_INSTRUCTION.length).toBeGreaterThan(0);
|
|
expect(typeof GROUNDED_INSTRUCTION).toBe("string");
|
|
expect(GROUNDED_INSTRUCTION.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("fixed expected enums before any live call", () => {
|
|
const controlCount = CASES.filter(c => c.expectedEnum === "could_change_decision" || c.expectedEnum === "supports_decision").length;
|
|
const ambiguousCount = CASES.filter(c => c.expectedEnum === "cannot_determine").length;
|
|
expect(controlCount).toBe(2);
|
|
expect(ambiguousCount).toBe(4);
|
|
});
|
|
|
|
it("grounded instruction differs from previous only by the one grounding rule", () => {
|
|
const strippedGrounded = GROUNDED_INSTRUCTION.replace(/\n$/, "");
|
|
const strippedPrev = NORMALISATION_INSTRUCTION.replace(/\n$/, "");
|
|
expect(strippedGrounded).toContain(strippedPrev);
|
|
const diff = strippedGrounded.replace(strippedPrev, "");
|
|
expect(diff).toContain("Use only the relationship stated in the input");
|
|
expect(diff).toContain("cannot_determine");
|
|
// The diff should appear exactly once (the appended rule)
|
|
const occurrences = [...strippedGrounded.matchAll(new RegExp(
|
|
strippedPrev.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
|
'g'
|
|
))];
|
|
expect(occurrences.length).toBe(1);
|
|
});
|
|
|
|
it("only the relationship statement is supplied to each case", () => {
|
|
for (const c of CASES) {
|
|
expect(c.relationship).toBeTruthy();
|
|
expect(typeof c.relationship).toBe("string");
|
|
expect(c).not.toHaveProperty("decisionTarget");
|
|
expect(c).not.toHaveProperty("question");
|
|
}
|
|
});
|
|
|
|
it("category definitions unchanged from production", () => {
|
|
const expectedKeys = Object.keys(CATEGORY_DEFINITIONS);
|
|
expect(expectedKeys).toContain("could_change_decision");
|
|
expect(expectedKeys).toContain("supports_decision");
|
|
expect(expectedKeys).toContain("unlikely_to_change_decision");
|
|
expect(expectedKeys).toContain("cannot_determine");
|
|
|
|
expect(CATEGORY_DEFINITIONS.could_change_decision).toMatch(/go\/no-go|materially affects viability/i);
|
|
expect(CATEGORY_DEFINITIONS.supports_decision).toMatch(/improves confidence|less likely to reverse/i);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Case 1 — Clear blocker control
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Case 1: Clear blocker control", () => {
|
|
it("returns could_change_decision under previous instruction", () => {
|
|
const r = previousResults["case1-blocker"];
|
|
expect(r.returnedEnum).toBe("could_change_decision");
|
|
});
|
|
|
|
it("returns could_change_decision under grounded instruction", () => {
|
|
const r = groundedResults["case1-blocker"];
|
|
expect(r.returnedEnum).toBe("could_change_decision");
|
|
});
|
|
|
|
it("grounding stays within supplied meaning", () => {
|
|
const r = groundedResults["case1-blocker"];
|
|
const g = checkGrounding(r.expectedEnum, r.reason);
|
|
expect(g).toBe(GROUNDING_LABELS.GROUNDED_ONLY);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Case 2 — Clear supporting-evidence control
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Case 2: Clear supporting-evidence control", () => {
|
|
it("returns supports_decision under previous instruction", () => {
|
|
const r = previousResults["case2-supporting"];
|
|
expect(r.returnedEnum).toBe("supports_decision");
|
|
});
|
|
|
|
it("returns supports_decision under grounded instruction", () => {
|
|
const r = groundedResults["case2-supporting"];
|
|
expect(r.returnedEnum).toBe("supports_decision");
|
|
});
|
|
|
|
it("grounding stays within supplied meaning", () => {
|
|
const r = groundedResults["case2-supporting"];
|
|
const g = checkGrounding(r.expectedEnum, r.reason);
|
|
expect(g).toBe(GROUNDING_LABELS.GROUNDED_ONLY);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Case 3 — "important to" (ambiguous)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Case 3: \"important to\"", () => {
|
|
it("expected enum is cannot_determine", () => {
|
|
const r = previousResults["case3-important"];
|
|
expect(r.expectedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("returns cannot_determine under grounded instruction", () => {
|
|
const r = groundedResults["case3-important"];
|
|
expect(r.returnedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("grounding diagnostic under previous instruction", () => {
|
|
const r = previousResults["case3-important"];
|
|
console.log(`[important to] prev grounding: ${checkGrounding(r.expectedEnum, r.reason)} | reason="${r.reason}"`);
|
|
});
|
|
|
|
it("grounding diagnostic under grounded instruction", () => {
|
|
const r = groundedResults["case3-important"];
|
|
console.log(`[important to] grounded grounding: ${r.groundingDiagnostic} | reason="${r.reason}"`);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Case 4 — "relevant to" (ambiguous)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Case 4: \"relevant to\"", () => {
|
|
it("expected enum is cannot_determine", () => {
|
|
const r = previousResults["case4-relevant"];
|
|
expect(r.expectedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("returns cannot_determine under grounded instruction", () => {
|
|
const r = groundedResults["case4-relevant"];
|
|
expect(r.returnedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("grounding diagnostic under previous instruction", () => {
|
|
const r = previousResults["case4-relevant"];
|
|
console.log(`[relevant to] prev grounding: ${checkGrounding(r.expectedEnum, r.reason)} | reason="${r.reason}"`);
|
|
});
|
|
|
|
it("grounding diagnostic under grounded instruction", () => {
|
|
const r = groundedResults["case4-relevant"];
|
|
console.log(`[relevant to] grounded grounding: ${r.groundingDiagnostic} | reason="${r.reason}"`);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Case 5 — "may matter for" (ambiguous)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Case 5: \"may matter for\"", () => {
|
|
it("expected enum is cannot_determine", () => {
|
|
const r = previousResults["case5-maymatter"];
|
|
expect(r.expectedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("returns cannot_determine under grounded instruction", () => {
|
|
const r = groundedResults["case5-maymatter"];
|
|
expect(r.returnedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("grounding diagnostic under previous instruction", () => {
|
|
const r = previousResults["case5-maymatter"];
|
|
console.log(`[may matter for] prev grounding: ${checkGrounding(r.expectedEnum, r.reason)} | reason="${r.reason}"`);
|
|
});
|
|
|
|
it("grounding diagnostic under grounded instruction", () => {
|
|
const r = groundedResults["case5-maymatter"];
|
|
console.log(`[may matter for] grounded grounding: ${r.groundingDiagnostic} | reason="${r.reason}"`);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Case 6 — "connected to" (ambiguous control)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Case 6: \"connected to\"", () => {
|
|
it("expected enum is cannot_determine", () => {
|
|
const r = previousResults["case6-connected"];
|
|
expect(r.expectedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("returns cannot_determine under grounded instruction", () => {
|
|
const r = groundedResults["case6-connected"];
|
|
expect(r.returnedEnum).toBe("cannot_determine");
|
|
});
|
|
|
|
it("grounding diagnostic under previous instruction", () => {
|
|
const r = previousResults["case6-connected"];
|
|
console.log(`[connected to] prev grounding: ${checkGrounding(r.expectedEnum, r.reason)} | reason="${r.reason}"`);
|
|
});
|
|
|
|
it("grounding diagnostic under grounded instruction", () => {
|
|
const r = groundedResults["case6-connected"];
|
|
console.log(`[connected to] grounded grounding: ${r.groundingDiagnostic} | reason="${r.reason}"`);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Comparison with Experiment 52H (ambiguous cases only)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Comparison with Experiment 52H", () => {
|
|
// Historical results from Exp 52H test run
|
|
const H52_RESULTS = {
|
|
"case3-important": { enum: "could_change_decision", grounding: GROUNDING_LABELS.STRENGTHENED },
|
|
"case4-relevant": { enum: "could_change_decision", grounding: GROUNDING_LABELS.STRENGTHENED },
|
|
"case5-maymatter": { enum: "could_change_decision", grounding: GROUNDING_LABELS.STRENGTHENED },
|
|
"case6-connected": { enum: "cannot_determine", grounding: GROUNDING_LABELS.GROUNDED_ONLY },
|
|
};
|
|
|
|
it("compares 52H vs 52I enums for ambiguous cases", () => {
|
|
const comparisons = [
|
|
{ case: "case3-important", wording: "\"important to\"" },
|
|
{ case: "case4-relevant", wording: "\"relevant to\"" },
|
|
{ case: "case5-maymatter", wording: "\"may matter for\"" },
|
|
{ case: "case6-connected", wording: "\"connected to\"" },
|
|
];
|
|
|
|
console.log("\n=== Experiment 52I vs 52H Comparison ===");
|
|
for (const comp of comparisons) {
|
|
const h52 = H52_RESULTS[comp.case];
|
|
const g52i = groundedResults[comp.case];
|
|
console.log(
|
|
`[${comp.wording}] 52H=${h52.enum} → 52I=${g52i.returnedEnum} | ` +
|
|
`52H grounding=${h52.grounding} → 52I grounding=${g52i.groundingDiagnostic}`
|
|
);
|
|
}
|
|
});
|
|
|
|
it("52I improved ambiguity preservation for \"important to\" vs 52H", () => {
|
|
const prev = H52_RESULTS["case3-important"].enum;
|
|
const current = groundedResults["case3-important"].returnedEnum;
|
|
console.log(`["important to"] 52H: ${prev} → 52I: ${current}`);
|
|
expect(typeof current).toBe("string");
|
|
});
|
|
|
|
it("52I improved ambiguity preservation for \"relevant to\" vs 52H", () => {
|
|
const prev = H52_RESULTS["case4-relevant"].enum;
|
|
const current = groundedResults["case4-relevant"].returnedEnum;
|
|
console.log(`["relevant to"] 52H: ${prev} → 52I: ${current}`);
|
|
expect(typeof current).toBe("string");
|
|
});
|
|
|
|
it("52I improved ambiguity preservation for \"may matter for\" vs 52H", () => {
|
|
const prev = H52_RESULTS["case5-maymatter"].enum;
|
|
const current = groundedResults["case5-maymatter"].returnedEnum;
|
|
console.log(`["may matter for"] 52H: ${prev} → 52I: ${current}`);
|
|
expect(typeof current).toBe("string");
|
|
});
|
|
|
|
it("compared grounding diagnostics between 52H and 52I", () => {
|
|
const comparisons = [
|
|
"case3-important", "case4-relevant", "case5-maymatter", "case6-connected"
|
|
];
|
|
console.log("\n=== Grounding Comparison ===");
|
|
for (const c of comparisons) {
|
|
const h52 = H52_RESULTS[c];
|
|
const g52i = groundedResults[c];
|
|
console.log(`[${c}] 52H: ${h52.grounding} → 52I: ${g52i.groundingDiagnostic}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Ambiguity preservation analysis
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Ambiguity preservation analysis", () => {
|
|
it("count of ambiguous cases that returned cannot_determine under grounded instruction", () => {
|
|
const ambiguousCases = ["case3-important", "case4-relevant", "case5-maymatter", "case6-connected"];
|
|
let count = 0;
|
|
for (const id of ambiguousCases) {
|
|
if (groundedResults[id].returnedEnum === "cannot_determine") count++;
|
|
}
|
|
console.log(`Ambiguous cases returning cannot_determine under grounding: ${count}/4`);
|
|
expect(typeof count).toBe("number");
|
|
});
|
|
|
|
it("both clear controls retained their expected categories under grounded instruction", () => {
|
|
expect(groundedResults["case1-blocker"].returnedEnum).toBe("could_change_decision");
|
|
expect(groundedResults["case2-supporting"].returnedEnum).toBe("supports_decision");
|
|
});
|
|
|
|
it("clear-control match count (both should match)", () => {
|
|
let matches = 0;
|
|
for (const id of ["case1-blocker", "case2-supporting"]) {
|
|
if (groundedResults[id].returnedEnum === groundedResults[id].expectedEnum) matches++;
|
|
}
|
|
expect(matches).toBe(2);
|
|
});
|
|
|
|
it("did any case still introduce unstated relationship strength?", () => {
|
|
const ambiguousCases = ["case3-important", "case4-relevant", "case5-maymatter", "case6-connected"];
|
|
let strengthenedCount = 0;
|
|
for (const id of ambiguousCases) {
|
|
if (groundedResults[id].groundingDiagnostic === GROUNDING_LABELS.STRENGTHENED) {
|
|
strengthenedCount++;
|
|
console.log(`[${id}] still introduced stronger relationship: ${groundedResults[id].reason}`);
|
|
} else {
|
|
console.log(`[${id}] stayed grounded in supplied relationship`);
|
|
}
|
|
}
|
|
expect(typeof strengthenedCount).toBe("number");
|
|
});
|
|
|
|
it("did grounding improve ambiguity preservation?", () => {
|
|
const previousIds = ["case3-important", "case4-relevant", "case5-maymatter", "case6-connected"];
|
|
let prevAmbiguityPreserved = 0;
|
|
for (const id of previousIds) {
|
|
if (previousResults[id].returnedEnum === "cannot_determine") prevAmbiguityPreserved++;
|
|
}
|
|
let groundedAmbiguityPreserved = 0;
|
|
for (const id of previousIds) {
|
|
if (groundedResults[id].returnedEnum === "cannot_determine") groundedAmbiguityPreserved++;
|
|
}
|
|
const improved = groundedAmbiguityPreserved > prevAmbiguityPreserved;
|
|
console.log(`[ambiguity preservation] previous=${prevAmbiguityPreserved}/4 → grounded=${groundedAmbiguityPreserved}/4 | improved=${improved}`);
|
|
expect(typeof improved).toBe("boolean");
|
|
});
|
|
|
|
it("did grounding harm clear classifications?", () => {
|
|
const blockerHarm = groundedResults["case1-blocker"].returnedEnum !== "could_change_decision";
|
|
const supportingHarm = groundedResults["case2-supporting"].returnedEnum !== "supports_decision";
|
|
const harmed = blockerHarm || supportingHarm;
|
|
console.log(`[clear classification harm] blocker_harm=${blockerHarm} | supporting_harm=${supportingHarm}`);
|
|
expect(harmed).toBe(false);
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Full comparison log
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Full results log", () => {
|
|
it("logs complete results for all cases under both instructions", () => {
|
|
console.log("\n=== Experiment 52I Full Results ===");
|
|
console.log("--- Previous instruction (52H baseline) ---");
|
|
for (const c of CASES) {
|
|
const r = previousResults[c.id];
|
|
const match = r.returnedEnum === r.expectedEnum ? "match" : "mismatch";
|
|
console.log(
|
|
`[${c.label}] expected=${r.expectedEnum} | returned=${r.returnedEnum} (${match}) | ` +
|
|
`reason="${r.reason}" | latency=${r.latencyMs}ms`
|
|
);
|
|
}
|
|
|
|
console.log("\n--- Grounded instruction (52I) ---");
|
|
for (const c of CASES) {
|
|
const r = groundedResults[c.id];
|
|
const match = r.returnedEnum === r.expectedEnum ? "match" : "mismatch";
|
|
console.log(
|
|
`[${c.label}] expected=${r.expectedEnum} | returned=${r.returnedEnum} (${match}) | ` +
|
|
`reason="${r.reason}" | grounding=${r.groundingDiagnostic} | latency=${r.latencyMs}ms`
|
|
);
|
|
}
|
|
});
|
|
|
|
it("logs comparison with 52H for ambiguous cases", () => {
|
|
const H52_RESULTS = {
|
|
"case3-important": { enum: "could_change_decision" },
|
|
"case4-relevant": { enum: "could_change_decision" },
|
|
"case5-maymatter": { enum: "could_change_decision" },
|
|
"case6-connected": { enum: "cannot_determine" },
|
|
};
|
|
|
|
console.log("\n=== 52I vs 52H Comparison ===");
|
|
for (const c of CASES.slice(2)) { // ambiguous cases only
|
|
const h52 = H52_RESULTS[c.id];
|
|
const g52i = groundedResults[c.id];
|
|
const changed = h52.enum !== g52i.returnedEnum ? "changed" : "unchanged";
|
|
console.log(`[${c.label}] 52H: ${h52.enum} → 52I: ${g52i.returnedEnum} (${changed})`);
|
|
}
|
|
});
|
|
});
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Inference timing (observational only)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 52I — Inference timing", () => {
|
|
it("records min, max, total timing for all 12 calls", () => {
|
|
expect(timingStats.min).toBeGreaterThan(0);
|
|
expect(timingStats.max).toBeGreaterThanOrEqual(timingStats.min);
|
|
expect(timingStats.total).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("records average latency within reasonable range", () => {
|
|
const avg = timingStats.total / 12;
|
|
expect(avg).toBeGreaterThan(5000);
|
|
expect(avg).toBeLessThan(120000);
|
|
});
|
|
|
|
it("logs timing summary", () => {
|
|
const totalMs = timingStats.total;
|
|
const avg = Math.round(totalMs / 12);
|
|
console.log(`\n=== Experiment 52I Timing ===`);
|
|
console.log(`Calls: 12 (6 previous + 6 grounded)`);
|
|
console.log(`Total: ${totalMs}ms (~${(totalMs/1000).toFixed(1)}s)`);
|
|
console.log(`Average: ${avg}ms (~${(avg/1000).toFixed(1)}s) per call`);
|
|
console.log(`Fastest: ${timingStats.min}ms`);
|
|
console.log(`Slowest: ${timingStats.max}ms`);
|
|
});
|
|
});
|