mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Build the Coach snapshot once per turn and widen the round budget to three
Each retrieval tool re-scoped the snapshot to a single source, so its cache key never matched the inventory's full-source build — a four-tool turn paid four extra full snapshot builds on top of the inventory's. Thread the inventory's probe scope to every tool so a per-tool read lands the one cache entry the inventory already primed, collapsing the turn to a single build (a window override still gets its own correct build). Raise the loop's round cap from two to three so a sequential cross-metric why-chain isn't starved; the per-round token budget is unchanged and the absolute ceiling tracks one above so the final round still forces prose.
This commit is contained in:
1 parent
d45d60627f
commit
abceff04c7
8 files changed
+334
-71
No files matched your search
@@ -71,9 +71,10 @@ vi.mock("@/lib/ai/coach/types", () => ({
|
||||
// thin stub keeps the import graph satisfied without pulling the real schemas.
|
||||
vi.mock("@/lib/ai/coach/tools", () => ({
|
||||
COACH_TOOL_DEFS: [],
|
||||
MAX_ROUNDS: 2,
|
||||
MAX_ROUNDS: 3,
|
||||
buildCoachDataInventory: vi.fn(),
|
||||
renderDataInventory: vi.fn(),
|
||||
renderFocusHint: vi.fn(() => ""),
|
||||
buildToolModeAddendum: vi.fn(),
|
||||
runCoachToolLoop: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -138,31 +138,36 @@ vi.mock("@/lib/ai/coach/snapshot", () => ({
|
||||
}));
|
||||
|
||||
// Tool-module surface — stub the inventory + loop so we assert routing only.
|
||||
const { buildCoachDataInventory, renderDataInventory, runCoachToolLoop } =
|
||||
vi.hoisted(() => ({
|
||||
buildCoachDataInventory: vi.fn(async () => ({
|
||||
entries: [],
|
||||
restMode: false,
|
||||
cycleEnabled: false,
|
||||
window: "last30days",
|
||||
})),
|
||||
renderDataInventory: vi.fn(
|
||||
() => "DATA INVENTORY\n- blood pressure: present",
|
||||
),
|
||||
runCoachToolLoop: vi.fn(async () => ({
|
||||
result: { content: "Your BP is steady.", tokensUsed: 80, model: "m" },
|
||||
workingProviderType: "anthropic",
|
||||
totalTokens: 80,
|
||||
rounds: 2,
|
||||
toolTrace: [{ name: "get_metric_series", present: true }],
|
||||
toolResults: [],
|
||||
})),
|
||||
}));
|
||||
vi.mock("@/lib/ai/coach/tools", () => ({
|
||||
COACH_TOOL_DEFS: [{ name: "get_metric_series" }],
|
||||
MAX_ROUNDS: 2,
|
||||
const {
|
||||
buildCoachDataInventory,
|
||||
renderDataInventory,
|
||||
renderFocusHint,
|
||||
runCoachToolLoop,
|
||||
} = vi.hoisted(() => ({
|
||||
buildCoachDataInventory: vi.fn(async () => ({
|
||||
entries: [],
|
||||
restMode: false,
|
||||
cycleEnabled: false,
|
||||
window: "last30days",
|
||||
probeScope: { sources: ["bp", "hrv"], window: "last30days" },
|
||||
})),
|
||||
renderDataInventory: vi.fn(() => "DATA INVENTORY\n- blood pressure: present"),
|
||||
renderFocusHint: vi.fn(() => ""),
|
||||
runCoachToolLoop: vi.fn(async () => ({
|
||||
result: { content: "Your BP is steady.", tokensUsed: 80, model: "m" },
|
||||
workingProviderType: "anthropic",
|
||||
totalTokens: 80,
|
||||
rounds: 2,
|
||||
toolTrace: [{ name: "get_metric_series", present: true }],
|
||||
toolResults: [],
|
||||
})),
|
||||
}));
|
||||
vi.mock("@/lib/ai/coach/tools", () => ({
|
||||
COACH_TOOL_DEFS: [{ name: "get_metric_series" }],
|
||||
MAX_ROUNDS: 3,
|
||||
buildCoachDataInventory,
|
||||
renderDataInventory,
|
||||
renderFocusHint,
|
||||
buildToolModeAddendum: vi.fn(() => "TOOL ADDENDUM"),
|
||||
runCoachToolLoop,
|
||||
}));
|
||||
@@ -246,11 +251,11 @@ describe("coach chat — tool-mode routing (F1)", () => {
|
||||
{ providerType: "anthropic", instance: {} },
|
||||
]);
|
||||
await post(chatReq({ message: "How is my BP?" }));
|
||||
// maxTokens (1500) × MAX_ROUNDS (2). The 4th arg is the provider-aware
|
||||
// maxTokens (1500) × MAX_ROUNDS (3). The 4th arg is the provider-aware
|
||||
// daily cap (F1) — mocked to the user-plan ceiling for this BYOK chain.
|
||||
expect(reserveBudget).toHaveBeenCalledWith(
|
||||
"u1",
|
||||
3000,
|
||||
4500,
|
||||
"2026-06-21",
|
||||
2_000_000,
|
||||
);
|
||||
|
||||
@@ -575,6 +575,11 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}. Fetch
|
||||
temperature: AI_BUDGETS.coach.temperature,
|
||||
maxTokens: AI_BUDGETS.coach.maxTokens,
|
||||
fallbackWindow: effectiveScope?.window,
|
||||
// v1.21.0 (D5-1) — share the inventory's full-source snapshot across
|
||||
// every tool so the turn builds ONE snapshot, not one per tool. The
|
||||
// probe scope is the exact scope the inventory was built against, so the
|
||||
// per-tool reads land its 60s LRU entry.
|
||||
sharedScope: inventory.probeScope,
|
||||
// v1.20.1 — thread the abort signal so a mid-generation disconnect tears
|
||||
// down the per-round provider calls instead of paying the full cost.
|
||||
signal: request.signal,
|
||||
|
||||
Reference in new issue
Block a user