mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
merge: wave/v1220-grounding into release/v1.21.2
This commit is contained in:
commit
03ef9a0b6f
2 files changed
+107
-10
No files matched your search
@@ -535,6 +535,15 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}.`;
|
||||
// v1.21.0 (P6) — the present tool-result payloads this turn, for the post-hoc
|
||||
// prose number-verifier. Empty on the no-tools path.
|
||||
let toolResultPayloads: unknown[] = [];
|
||||
// v1.21.2 (A8) — no-tools/local-provider parity for the prose number-verifier.
|
||||
// The tool path grades prose against the figures the tools returned; the
|
||||
// no-tools path has no tools, so the authoritative set is the SNAPSHOT the
|
||||
// model was actually shown this turn — `snapshot.sections`, the structured
|
||||
// record `snapshotJson` is serialised from, which already carries the
|
||||
// correlations-snapshot block. Populated only when the full figures were
|
||||
// delivered this turn (`includeFullSnapshot`); on a cheap follow-up the block
|
||||
// was not re-sent, so there is no fresh authoritative set to grade against.
|
||||
let noToolsSnapshotPayloads: unknown[] = [];
|
||||
let totalTokensSpent: number;
|
||||
// v1.21.0 (F3) — cached-input tokens to subtract at reconcile (prompt-cached
|
||||
// input the user did not re-pay for must not be billed to the daily meter).
|
||||
@@ -614,6 +623,14 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}. Fetch
|
||||
workingProviderType = fallback.workingProvider.providerType;
|
||||
totalTokensSpent = result.tokensUsed ?? 0;
|
||||
cachedTokensSpent = result.cachedInputTokens ?? 0;
|
||||
// v1.21.2 (A8) — the no-tools path showed the model the full SNAPSHOT only
|
||||
// when `includeFullSnapshot` was set; otherwise it shipped the
|
||||
// grounded-elsewhere pointer with no fresh figures, so there is nothing to
|
||||
// grade. When figures WERE delivered, the authoritative set is the
|
||||
// structured snapshot record (incl. the correlations block).
|
||||
if (includeFullSnapshot) {
|
||||
noToolsSnapshotPayloads = [snapshot.sections];
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// The provider chain failed outright — no tokens were billed, so refund
|
||||
@@ -737,18 +754,27 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}. Fetch
|
||||
});
|
||||
}
|
||||
|
||||
// v1.21.0 (P6 / C2-5) — post-hoc numeric verifier on the Coach prose. On the
|
||||
// tool path, cross-check every number the model cited against the figures the
|
||||
// tools actually returned this turn; an unmatched number (transcription /
|
||||
// paraphrase drift) is soft-stripped to "[unverified]" and annotated. Cheap,
|
||||
// non-blocking, and a no-op when no tool returned figures (a qualitative turn
|
||||
// or the no-tools path) — the prompt-level grounding rule remains the
|
||||
// backstop there, exactly like the briefing's "no signals → skip". A blocked
|
||||
// turn already carries canned fallback prose, so skip it.
|
||||
if (!outbound.block && toolResultPayloads.length > 0) {
|
||||
// v1.21.0 (P6 / C2-5) — post-hoc numeric verifier on the Coach prose. Cross-
|
||||
// check every number the model cited against this turn's authoritative figure
|
||||
// set; an unmatched number (transcription / paraphrase drift) is soft-stripped
|
||||
// to "[unverified]" and annotated. Cheap, non-blocking, and a no-op when there
|
||||
// is no authoritative set — the prompt-level grounding rule remains the
|
||||
// backstop, exactly like the briefing's "no signals → skip". A blocked turn
|
||||
// already carries canned fallback prose, so skip it.
|
||||
//
|
||||
// v1.21.2 (A8) — the authoritative set is the figures the tools returned on the
|
||||
// tool path, and the SNAPSHOT the model was shown on the no-tools/local path
|
||||
// (`snapshot.sections`, which already carries the correlations-snapshot block).
|
||||
// Exactly one is populated per turn; the grading, tolerance, and exemptions are
|
||||
// identical, so a number the model invents is flagged the same way on both.
|
||||
const authoritativePayloads =
|
||||
toolResultPayloads.length > 0
|
||||
? toolResultPayloads
|
||||
: noToolsSnapshotPayloads;
|
||||
if (!outbound.block && authoritativePayloads.length > 0) {
|
||||
const unverified = findUnverifiedCoachNumbers(
|
||||
replyText,
|
||||
toolResultPayloads,
|
||||
authoritativePayloads,
|
||||
);
|
||||
if (unverified.length > 0) {
|
||||
const { prose: corrected, stripped } = stripUnverifiedNumbers(
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* v1.21.2 (A8) — no-tools/local-provider parity for the prose number-verifier.
|
||||
*
|
||||
* On the no-tools path the Coach has no tools, so the authoritative figure set
|
||||
* is the SNAPSHOT the model was shown — the structured `snapshot.sections`
|
||||
* record `snapshotJson` is serialised from, which already carries the
|
||||
* correlations-snapshot block. This proves the SAME verifier grades prose
|
||||
* against that record: a number present in the snapshot (including a
|
||||
* correlations-block leaf) is not flagged; a number absent from it is flagged
|
||||
* exactly as on the tool path.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { findUnverifiedCoachNumbers } from "@/lib/ai/coach/coach-prose-grounding";
|
||||
|
||||
/**
|
||||
* A representative `snapshot.sections` record: a derived block plus the
|
||||
* correlations-snapshot block (a `CorrelationsSnapshotBlock`) the no-tools path
|
||||
* folds in. The driver's numeric leaves (`r`, `n`, `pairsTested`, `windowDays`)
|
||||
* are authoritative figures the model may legitimately cite.
|
||||
*/
|
||||
const snapshotSections: Record<string, unknown> = {
|
||||
bloodPressure: {
|
||||
aggregate: { avgSys30: 128, avgDia30: 82 },
|
||||
},
|
||||
correlations: {
|
||||
drivers: [
|
||||
{
|
||||
behaviour: "sleep duration",
|
||||
outcome: "resting heart rate",
|
||||
direction: "lower",
|
||||
lagDays: 1,
|
||||
n: 24,
|
||||
r: 0.62,
|
||||
note: "More sleep tracked with a lower next-day resting heart rate.",
|
||||
},
|
||||
],
|
||||
pairsTested: 18,
|
||||
windowDays: 90,
|
||||
},
|
||||
};
|
||||
|
||||
// The route passes the structured record as a single authoritative payload.
|
||||
const noToolsPayloads: unknown[] = [snapshotSections];
|
||||
|
||||
describe("no-tools verifier parity", () => {
|
||||
it("does not flag a number that traces to a snapshot leaf", () => {
|
||||
const prose = "Your systolic averaged 128 and diastolic 82 this month.";
|
||||
expect(findUnverifiedCoachNumbers(prose, noToolsPayloads)).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not flag a number that traces to the correlations-snapshot block", () => {
|
||||
// r = 0.62 is a leaf of the correlations block folded into the snapshot.
|
||||
const prose = "Sleep and resting heart rate moved together (r = 0.62).";
|
||||
expect(findUnverifiedCoachNumbers(prose, noToolsPayloads)).toEqual([]);
|
||||
});
|
||||
|
||||
it("flags a number the snapshot never carried", () => {
|
||||
const prose = "Your systolic averaged about 138 recently.";
|
||||
const findings = findUnverifiedCoachNumbers(prose, noToolsPayloads);
|
||||
expect(findings).toHaveLength(1);
|
||||
expect(findings[0].value).toBe(138);
|
||||
});
|
||||
|
||||
it("no-ops when the snapshot was not delivered this turn", () => {
|
||||
// Cheap follow-up: the route leaves the no-tools payload set empty, so even
|
||||
// an invented figure is not graded — the prompt-level rule is the backstop.
|
||||
const prose = "Your systolic averaged 138 recently.";
|
||||
expect(findUnverifiedCoachNumbers(prose, [])).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user