-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: wire Vercel AI Gateway reasoning via AI SDK v6 native path #11449
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ import { | |
| } from "../transform/ai-sdk" | ||
| import { applyToolCacheOptions } from "../transform/cache-breakpoints" | ||
| import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" | ||
| import { getModelParams } from "../transform/model-params" | ||
| import type { OpenAiReasoningParams } from "../transform/reasoning" | ||
|
|
||
| import { DEFAULT_HEADERS } from "./constants" | ||
| import { BaseProvider } from "./base-provider" | ||
|
|
@@ -29,6 +31,15 @@ import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from ". | |
| import type { RooMessage } from "../../core/task-persistence/rooMessage" | ||
| import { sanitizeMessagesForProvider } from "../transform/sanitize-messages" | ||
|
|
||
| type ModelSelection = { | ||
| id: string | ||
| info: ModelInfo | ||
| maxTokens?: number | ||
| temperature?: number | ||
| reasoning?: OpenAiReasoningParams | ||
| reasoningBudget?: number | ||
| } | ||
|
|
||
| /** | ||
| * Vercel AI Gateway provider using the built-in AI SDK gateway support. | ||
| * Uses `createGateway` from the `ai` package to communicate with the | ||
|
|
@@ -50,20 +61,41 @@ export class VercelAiGatewayHandler extends BaseProvider implements SingleComple | |
| }) | ||
| } | ||
|
|
||
| override getModel(): { id: string; info: ModelInfo } { | ||
| override getModel(): ModelSelection { | ||
| const id = this.options.vercelAiGatewayModelId ?? vercelAiGatewayDefaultModelId | ||
| const resolveModel = (modelInfo: ModelInfo) => ({ | ||
| id, | ||
| info: modelInfo, | ||
| ...getModelParams({ | ||
| format: "openai", | ||
| modelId: id, | ||
| model: modelInfo, | ||
| settings: this.options, | ||
| defaultTemperature: VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE, | ||
| }), | ||
| }) | ||
|
|
||
| if (this.models[id]) { | ||
| return { id, info: this.models[id] } | ||
| return resolveModel(this.models[id]) | ||
| } | ||
|
|
||
| const cachedModels = getModelsFromCache(this.name) | ||
| if (cachedModels?.[id]) { | ||
| this.models = cachedModels | ||
| return { id, info: cachedModels[id] } | ||
| return resolveModel(cachedModels[id]) | ||
| } | ||
|
|
||
| return { id: vercelAiGatewayDefaultModelId, info: vercelAiGatewayDefaultModelInfo } | ||
| return { | ||
| id: vercelAiGatewayDefaultModelId, | ||
| info: vercelAiGatewayDefaultModelInfo, | ||
| ...getModelParams({ | ||
| format: "openai", | ||
| modelId: vercelAiGatewayDefaultModelId, | ||
| model: vercelAiGatewayDefaultModelInfo, | ||
| settings: this.options, | ||
| defaultTemperature: VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE, | ||
| }), | ||
| } | ||
| } | ||
|
|
||
| public async fetchModel() { | ||
|
|
@@ -115,7 +147,7 @@ export class VercelAiGatewayHandler extends BaseProvider implements SingleComple | |
| messages: RooMessage[], | ||
| metadata?: ApiHandlerCreateMessageMetadata, | ||
| ): ApiStream { | ||
| const { id: modelId, info } = await this.fetchModel() | ||
| const { id: modelId, info, temperature, reasoning, reasoningBudget } = await this.fetchModel() | ||
| const languageModel = this.getLanguageModel(modelId) | ||
|
|
||
| const aiSdkMessages = sanitizeMessagesForProvider(messages) | ||
|
|
@@ -124,18 +156,33 @@ export class VercelAiGatewayHandler extends BaseProvider implements SingleComple | |
| const aiSdkTools = convertToolsForAiSdk(openAiTools) as ToolSet | undefined | ||
| applyToolCacheOptions(aiSdkTools as Parameters<typeof applyToolCacheOptions>[0], metadata?.toolProviderOptions) | ||
|
|
||
| const temperature = this.supportsTemperature(modelId) | ||
| ? (this.options.modelTemperature ?? VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE) | ||
| const resolvedTemperature = this.supportsTemperature(modelId) | ||
| ? (this.options.modelTemperature ?? temperature ?? VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE) | ||
| : undefined | ||
|
|
||
| const reasoningConfig = reasoning ? { enabled: true, effort: reasoning.reasoning_effort } : undefined | ||
| const anthropicProviderOptions = | ||
| modelId.startsWith("anthropic/") && reasoningConfig | ||
| ? { | ||
| anthropic: { | ||
| thinking: { | ||
| type: "enabled" as const, | ||
| budgetTokens: reasoningBudget ?? Math.floor((info.maxTokens ?? 0) * 0.8), | ||
| }, | ||
| }, | ||
| } | ||
| : undefined | ||
|
|
||
| const result = streamText({ | ||
| model: languageModel, | ||
| system: systemPrompt || undefined, | ||
| messages: aiSdkMessages, | ||
| temperature, | ||
| temperature: resolvedTemperature, | ||
| maxOutputTokens: info.maxTokens ?? undefined, | ||
| tools: aiSdkTools, | ||
| toolChoice: mapToolChoice(metadata?.tool_choice), | ||
| ...(reasoningConfig ? { reasoning: reasoningConfig } : {}), | ||
| ...(anthropicProviderOptions ? { providerOptions: anthropicProviderOptions } : {}), | ||
| }) | ||
|
|
||
| try { | ||
|
|
@@ -170,19 +217,34 @@ export class VercelAiGatewayHandler extends BaseProvider implements SingleComple | |
| } | ||
|
|
||
| async completePrompt(prompt: string): Promise<string> { | ||
| const { id: modelId, info } = await this.fetchModel() | ||
| const { id: modelId, info, temperature, reasoning, reasoningBudget } = await this.fetchModel() | ||
| const languageModel = this.getLanguageModel(modelId) | ||
|
|
||
| const temperature = this.supportsTemperature(modelId) | ||
| ? (this.options.modelTemperature ?? VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE) | ||
| const resolvedTemperature = this.supportsTemperature(modelId) | ||
| ? (this.options.modelTemperature ?? temperature ?? VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE) | ||
| : undefined | ||
|
|
||
| const reasoningConfig = reasoning ? { enabled: true, effort: reasoning.reasoning_effort } : undefined | ||
| const anthropicProviderOptions = | ||
| modelId.startsWith("anthropic/") && reasoningConfig | ||
| ? { | ||
| anthropic: { | ||
| thinking: { | ||
| type: "enabled" as const, | ||
| budgetTokens: reasoningBudget ?? Math.floor((info.maxTokens ?? 0) * 0.8), | ||
| }, | ||
| }, | ||
| } | ||
| : undefined | ||
|
Comment on lines
+227
to
+238
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. This Fix it with Roo Code or mention @roomote and request a fix. |
||
|
|
||
| try { | ||
| const { text } = await generateText({ | ||
| model: languageModel, | ||
| prompt, | ||
| maxOutputTokens: info.maxTokens ?? undefined, | ||
| temperature, | ||
| temperature: resolvedTemperature, | ||
| ...(reasoningConfig ? { reasoning: reasoningConfig } : {}), | ||
| ...(anthropicProviderOptions ? { providerOptions: anthropicProviderOptions } : {}), | ||
| }) | ||
|
|
||
| return text | ||
|
|
||
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.
Anthropic requires
temperature = 1.0when extended thinking is enabled. The existinggetModelParamsenforces this for budget-based models (viashouldUseReasoningBudget), but Gateway Anthropic models only havesupportsReasoningEffort: true-- they lacksupportsReasoningBudget-- so the budget path is never entered and the temperature stays at 0.7 (VERCEL_AI_GATEWAY_DEFAULT_TEMPERATURE). If the Vercel AI Gateway does not transparently override the temperature for Anthropic thinking requests, this will cause the API to reject the call. The same applies to the identical block incompletePrompt. Consider forcingresolvedTemperature = 1.0whenanthropicProviderOptionsis defined (and no explicit user temperature is set).Fix it with Roo Code or mention @roomote and request a fix.