Repository navigation
fix(ai): request readable reasoning summaries for capture #858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { getWorkspaceAiGatewayRoutingOptions } from "#/features/workspaces/ai/ai-gateway-routing"; | ||
| import { | ||
| getAIThreadTitleGatewayProviderOptions, | ||
| getWorkspaceAiGatewayProviderOptions, | ||
| } from "#/features/workspaces/ai/gateway"; | ||
| import { WORKSPACE_AI_CHAT_MODELS } from "#/features/workspaces/ai/models"; | ||
|
|
||
| describe("chat reasoning capture", () => { | ||
| it.each(WORKSPACE_AI_CHAT_MODELS)("keeps $id fallback thinking compatible", (model) => { | ||
| const options = getWorkspaceAiGatewayProviderOptions({ modelId: model.id }); | ||
| const routeModels = [ | ||
| model.gatewayModel, | ||
| ...getWorkspaceAiGatewayRoutingOptions(model.id).models, | ||
| ]; | ||
|
|
||
| expect(options.openai).toMatchObject({ reasoningSummary: "detailed" }); | ||
| for (const provider of ["google", "vertex"]) { | ||
| expect(options[provider]).toMatchObject({ thinkingConfig: { includeThoughts: true } }); | ||
| } | ||
|
|
||
| for (const slug of routeModels) { | ||
| if (slug === "anthropic/claude-haiku-4.5") { | ||
| expect(options.anthropic).toBeUndefined(); | ||
| } | ||
| if (slug === "anthropic/claude-sonnet-5.5") { | ||
| expect(options.anthropic).toEqual({ | ||
| thinking: { type: "adaptive", display: "summarized" }, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| expect(options.openai?.reasoningEffort).toBe(model.id === "gpt-sol" ? "low" : undefined); | ||
| expect(options.google).toEqual({ | ||
| thinkingConfig: { | ||
| includeThoughts: true, | ||
| ...(model.id === "gemini" ? { thinkingLevel: "low" } : {}), | ||
| }, | ||
| }); | ||
| expect(options.vertex).toEqual(options.google); | ||
| }); | ||
|
|
||
| it("leaves title generation at minimal effort without requesting summaries", () => { | ||
| const { gateway: _gateway, ...providers } = getAIThreadTitleGatewayProviderOptions(); | ||
| expect(providers).toEqual({ | ||
| google: { thinkingConfig: { thinkingLevel: "minimal" } }, | ||
| vertex: { thinkingConfig: { thinkingLevel: "minimal" } }, | ||
| openai: { reasoningEffort: "none" }, | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -130,26 +130,34 @@ export function getAIThreadTitleGatewayProviderOptions(): WorkspaceAiProviderOpt | |
| function getWorkspaceAiReasoningOptions( | ||
| modelId: ReturnType<typeof resolveWorkspaceAiChatModelId>, | ||
| ): WorkspaceAiProviderOptions { | ||
| switch (modelId) { | ||
| case "gemini": | ||
| return { | ||
| google: { | ||
| thinkingConfig: { thinkingLevel: "low" }, | ||
| }, | ||
| vertex: { | ||
| thinkingConfig: { thinkingLevel: "low" }, | ||
| }, | ||
| }; | ||
| case "gpt-sol": | ||
| return { | ||
| openai: { | ||
| // Sol 6.1 requires reasoning; low is its minimum supported effort. | ||
| reasoningEffort: "low", | ||
| }, | ||
| }; | ||
| default: | ||
| return {}; | ||
| } | ||
| const routeModels = [ | ||
| getWorkspaceAiChatModel(modelId), | ||
| ...getWorkspaceAiGatewayRoutingOptions(modelId).models, | ||
| ]; | ||
|
|
||
| return { | ||
| openai: { | ||
| reasoningSummary: "detailed", | ||
| // Sol 6.1 requires reasoning; preserve its existing minimum effort. | ||
| ...(modelId === "gpt-sol" ? { reasoningEffort: "low" } : {}), | ||
| }, | ||
| google: { | ||
| thinkingConfig: { | ||
| includeThoughts: true, | ||
| ...(modelId === "gemini" ? { thinkingLevel: "low" } : {}), | ||
| }, | ||
| }, | ||
| vertex: { | ||
| thinkingConfig: { | ||
| includeThoughts: true, | ||
| ...(modelId === "gemini" ? { thinkingLevel: "low" } : {}), | ||
| }, | ||
| }, | ||
| // Include Sonnet fallbacks, but never send adaptive thinking to Haiku. | ||
| ...(routeModels.includes("anthropic/claude-sonnet-5.5") | ||
| ? { anthropic: { thinking: { type: "adaptive", display: "summarized" } } } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Adaptive thinking on Sonnet 5.5 adds reasoning time before the first token, but the 20s Anthropic TTFT budget in getWorkspaceAiGatewayTransportOptions was sized without it. For the claude-sonnet route, a slow first token would abandon the Anthropic leg and fall back to GPT-6.1 Sol or Gemini Pro. Re-measure the Anthropic TTFT with thinking enabled before shipping, or raise the budget. Prompt for AI agents |
||
| : {}), | ||
| }; | ||
| } | ||
|
|
||
| // Rudimint forwards each call to Vercel AI Gateway unchanged and keeps a copy for | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fix the vacuous fallback assertions.
The loop asserts
options.anthropicis undefined only when the route contains the Haiku slug. It asserts the Sonnet value only when the route contains the Sonnet slug. A route can contain both slugs. This happens if a model's primary or fallback list contains Haiku and Sonnet together. The two assertions would then contradict each other. A route with neither slug is not asserted at all.Compute the expected value from
routeModelsinstead. Use one assertion outside the loop.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents