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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
This commit is contained in:
Claude 2026-10-02 23:57:36 +00:00
parent 04b7513e57
commit 5fa5779b6b
No known key found for this signature in database
4 changed files with 62 additions and 3 deletions

View File

@ -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(

View File

@ -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<AgentRunRequest["model"]> {
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" } }),
]);

View File

@ -336,6 +336,13 @@ describe("consolidateProfile", () => {
expect(request.prompt).not.toContain("asthma");
expect(request.prompt.match(/<conversation>/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({

View File

@ -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<AgentRunRequest["model"]>;
}
@ -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),