From 5fa5779b6b60b68a32c1b13b7a7a1e7906e39c46 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 23:57:36 +0000 Subject: [PATCH] feat(adapters): run profile consolidation on the fast tier when one is mapped Fact extraction is short structured work, so consolidation asks the model resolver for the fast tier. The resolver takes a per-call tier and, when the space has not mapped it, keeps the bot's own tier or model as before. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u --- packages/adapters/src/executor.test.ts | 34 +++++++++++++++++++ packages/adapters/src/executor.ts | 13 ++++++- .../src/profile-consolidation.test.ts | 7 ++++ .../adapters/src/profile-consolidation.ts | 11 ++++-- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/packages/adapters/src/executor.test.ts b/packages/adapters/src/executor.test.ts index d295450..c4a885e 100644 --- a/packages/adapters/src/executor.test.ts +++ b/packages/adapters/src/executor.test.ts @@ -1798,6 +1798,40 @@ description: Prepare standup notes ).resolves.toMatchObject({ provider: "xai", id: "grok-4.6" }); }); + it("prefers a tier the call asks for, and keeps the bot's choice when it is unmapped", async () => { + const fastTier = { ...routerTier, tier: "fast", modelId: "router-fast" }; + const mapped = tierPrisma( + { modelProvider: "xai", modelId: "grok-4.6", thinkingLevel: null, modelTier: null }, + fastTier, + ); + await expect( + mapped.executor.resolveModel({ + userId: "user-1", + spaceId: "ws-1", + botId: "bot-1", + tier: "fast", + }), + ).resolves.toMatchObject({ provider: "openai-compatible", id: "router-fast" }); + expect(mapped.findUnique).toHaveBeenCalledWith( + expect.objectContaining({ + where: { spaceId_userId_tier: { spaceId: "ws-1", userId: "user-1", tier: "fast" } }, + }), + ); + + const unmapped = tierPrisma( + { modelProvider: "xai", modelId: "grok-4.6", thinkingLevel: null, modelTier: null }, + null, + ); + await expect( + unmapped.executor.resolveModel({ + userId: "user-1", + spaceId: "ws-1", + botId: "bot-1", + tier: "fast", + }), + ).resolves.toMatchObject({ provider: "xai", id: "grok-4.6" }); + }); + it("resolves a helper tier and reports an unmapped one as null", async () => { const mapped = tierPrisma(null, routerTier); await expect( diff --git a/packages/adapters/src/executor.ts b/packages/adapters/src/executor.ts index e6dfd24..5cb9181 100644 --- a/packages/adapters/src/executor.ts +++ b/packages/adapters/src/executor.ts @@ -728,6 +728,8 @@ export function createRunExecutor(deps: ExecutorDeps) { userId: string; spaceId: string; botId?: string; + /** Preferred tier for this call; when the space has not mapped it, the bot's choice holds. */ + tier?: ModelTier; }): Promise { const override = scope.botId ? await deps.prisma.bot.findFirst({ @@ -744,7 +746,16 @@ export function createRunExecutor(deps: ExecutorDeps) { hasOverride ? findModelCredential(deps.prisma, scope, override!.modelProvider!, override!.modelId) : Promise.resolve(null), - override?.modelTier ? findSpaceModelTier(deps.prisma, scope, override.modelTier) : null, + (scope.tier + ? findSpaceModelTier(deps.prisma, scope, scope.tier) + : Promise.resolve(null) + ).then( + (preferred) => + preferred ?? + (override?.modelTier + ? findSpaceModelTier(deps.prisma, scope, override.modelTier) + : null), + ), findDefaultModelCredential(deps.prisma, scope), deps.prisma.deploymentSettings.findUnique({ where: { id: "default" } }), ]); diff --git a/packages/adapters/src/profile-consolidation.test.ts b/packages/adapters/src/profile-consolidation.test.ts index 01aa3b2..c5a6925 100644 --- a/packages/adapters/src/profile-consolidation.test.ts +++ b/packages/adapters/src/profile-consolidation.test.ts @@ -336,6 +336,13 @@ describe("consolidateProfile", () => { expect(request.prompt).not.toContain("asthma"); expect(request.prompt.match(//g)).toHaveLength(2); + // Extraction asks for the fast tier; an unmapped tier keeps the main bot's model. + expect(consolidateDeps.resolveModel).toHaveBeenCalledWith({ + userId: "user-1", + spaceId: "space-1", + botId: "main-bot", + tier: "fast", + }); // Facts are written under the same per-user lock as every other profile writer. expect(prisma.$queryRaw).toHaveBeenCalledTimes(1); expect(prisma.userProfile.updateMany).toHaveBeenCalledWith({ diff --git a/packages/adapters/src/profile-consolidation.ts b/packages/adapters/src/profile-consolidation.ts index 1a11f38..c505c20 100644 --- a/packages/adapters/src/profile-consolidation.ts +++ b/packages/adapters/src/profile-consolidation.ts @@ -5,7 +5,7 @@ import type { JobPublisher, } from "@rakazo/adapter-kit"; import { profileConsolidateJob } from "@rakazo/adapter-kit"; -import type { MessageBlock, UserFactCategory } from "@rakazo/contracts"; +import type { MessageBlock, ModelTier, UserFactCategory } from "@rakazo/contracts"; import { SENSITIVE_USER_FACT_CATEGORIES, USER_FACT_CATEGORIES, @@ -295,6 +295,7 @@ export interface ConsolidateProfileDeps { userId: string; spaceId: string; botId?: string; + tier?: ModelTier; }) => Promise; } @@ -319,7 +320,13 @@ export async function consolidateProfile( if (!main?.thread) return; let model: AgentRunRequest["model"]; try { - model = await deps.resolveModel({ userId, spaceId: main.spaceId, botId: main.id }); + // Extraction is short structured work: the fast tier when the space maps one. + model = await deps.resolveModel({ + userId, + spaceId: main.spaceId, + botId: main.id, + tier: "fast", + }); } catch (error) { getLogger().info(`profile.consolidate skipped for user ${userId}: no usable model`, { reason: error instanceof Error ? error.message : String(error),