Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ const litellmSchema = baseProviderSettingsSchema.extend({
litellmApiKey: z.string().optional(),
litellmModelId: z.string().optional(),
litellmUsePromptCache: z.boolean().optional(),
litellmUseAzureBedrock: z.boolean().optional(),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency: The property is named litellmUseAzureBedrock but it configures AWS Bedrock (Amazon), not Azure (Microsoft). This conflation of cloud providers in the variable name could confuse developers. Consider renaming to litellmUseAwsBedrock or litellmUseBedrock to match the actual functionality.

Fix it with Roo Code or mention @roomote and request a fix.

})

const cerebrasSchema = apiModelIdProviderModelSchema.extend({
Expand Down
301 changes: 301 additions & 0 deletions src/api/providers/__tests__/lite-llm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ vi.mock("../fetchers/modelCache", () => ({
"claude-3-opus": { ...litellmDefaultModelInfo, maxTokens: 8192 },
"llama-3": { ...litellmDefaultModelInfo, maxTokens: 8192 },
"gpt-4-turbo": { ...litellmDefaultModelInfo, maxTokens: 8192 },
"bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0": {
...litellmDefaultModelInfo,
maxTokens: 8192,
supportsNativeTools: true,
},
"anthropic.claude-sonnet-4-20250514-v1:0": {
...litellmDefaultModelInfo,
maxTokens: 8192,
supportsNativeTools: true,
},
"amazon.titan-text-express-v1": { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true },
})
}),
getModelsFromCache: vi.fn().mockReturnValue(undefined),
Expand Down Expand Up @@ -388,4 +399,294 @@ describe("LiteLLMHandler", () => {
expect(createCall.max_completion_tokens).toBeUndefined()
})
})

describe("Bedrock model handling", () => {
it("should exclude parallel_tool_calls when litellmUseAzureBedrock is explicitly true", async () => {
const options: ApiHandlerOptions = {
...mockOptions,
litellmModelId: "gpt-4", // Non-Bedrock model ID
litellmUseAzureBedrock: true, // Explicitly set to Bedrock
}
handler = new LiteLLMHandler(options)

const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test" }]

// Mock the stream response
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Response" } }],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
},
}
},
}

mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})

const metadata = {
taskId: "test-task",
tools: [
{
type: "function" as const,
function: {
name: "test_tool",
description: "A test tool",
parameters: { type: "object", properties: {} },
},
},
],
toolProtocol: "native" as const,
parallelToolCalls: true,
}

const generator = handler.createMessage(systemPrompt, messages, metadata)
for await (const chunk of generator) {
// Consume the generator
}

// Verify that parallel_tool_calls is NOT included when litellmUseAzureBedrock is true
const createCall = mockCreate.mock.calls[0][0]
expect(createCall.parallel_tool_calls).toBeUndefined()
expect(createCall.tools).toBeDefined()
})

it("should include parallel_tool_calls when litellmUseAzureBedrock is explicitly false", async () => {
const options: ApiHandlerOptions = {
...mockOptions,
litellmModelId: "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", // Bedrock model ID
litellmUseAzureBedrock: false, // Explicitly set to NOT Bedrock
}
handler = new LiteLLMHandler(options)

const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test" }]

// Mock the stream response
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Response" } }],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
},
}
},
}

mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})

const metadata = {
taskId: "test-task",
tools: [
{
type: "function" as const,
function: {
name: "test_tool",
description: "A test tool",
parameters: { type: "object", properties: {} },
},
},
],
toolProtocol: "native" as const,
parallelToolCalls: true,
}

const generator = handler.createMessage(systemPrompt, messages, metadata)
for await (const chunk of generator) {
// Consume the generator
}

// Verify that parallel_tool_calls IS included when litellmUseAzureBedrock is false
const createCall = mockCreate.mock.calls[0][0]
expect(createCall.parallel_tool_calls).toBe(true)
expect(createCall.tools).toBeDefined()
})

it("should auto-detect and exclude parallel_tool_calls for Bedrock models when litellmUseAzureBedrock is not set", async () => {
const bedrockModels = ["bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", "amazon.titan-text-express-v1"]

for (const modelId of bedrockModels) {
vi.clearAllMocks()

const options: ApiHandlerOptions = {
...mockOptions,
litellmModelId: modelId,
}
handler = new LiteLLMHandler(options)

const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test" }]

// Mock the stream response
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Response" } }],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
},
}
},
}

mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})

const metadata = {
taskId: "test-task",
tools: [
{
type: "function" as const,
function: {
name: "test_tool",
description: "A test tool",
parameters: { type: "object", properties: {} },
},
},
],
toolProtocol: "native" as const,
parallelToolCalls: true,
}

const generator = handler.createMessage(systemPrompt, messages, metadata)
for await (const chunk of generator) {
// Consume the generator
}

// Verify that parallel_tool_calls is NOT included for Bedrock models
const createCall = mockCreate.mock.calls[0][0]
expect(createCall.parallel_tool_calls).toBeUndefined()
expect(createCall.tools).toBeDefined() // Tools should still be present
}
})

it("should auto-detect and include parallel_tool_calls for non-Bedrock models when litellmUseAzureBedrock is not set", async () => {
const nonBedrockModels = [
"gpt-4",
"claude-3-opus",
"gpt-4-turbo",
"anthropic.claude-sonnet-4-20250514-v1:0",
]

for (const modelId of nonBedrockModels) {
vi.clearAllMocks()

const options: ApiHandlerOptions = {
...mockOptions,
litellmModelId: modelId,
}
handler = new LiteLLMHandler(options)

const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test" }]

// Mock the stream response
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Response" } }],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
},
}
},
}

mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})

const metadata = {
taskId: "test-task",
tools: [
{
type: "function" as const,
function: {
name: "test_tool",
description: "A test tool",
parameters: { type: "object", properties: {} },
},
},
],
toolProtocol: "native" as const,
parallelToolCalls: true,
}

const generator = handler.createMessage(systemPrompt, messages, metadata)
for await (const chunk of generator) {
// Consume the generator
}

// Verify that parallel_tool_calls IS included for non-Bedrock models
const createCall = mockCreate.mock.calls[0][0]
expect(createCall.parallel_tool_calls).toBe(true)
expect(createCall.tools).toBeDefined()
}
})

it("should default parallel_tool_calls to false for non-Bedrock models when not specified", async () => {
const options: ApiHandlerOptions = {
...mockOptions,
litellmModelId: "gpt-4",
}
handler = new LiteLLMHandler(options)

const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test" }]

// Mock the stream response
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Response" } }],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
},
}
},
}

mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})

const metadata = {
taskId: "test-task",
tools: [
{
type: "function" as const,
function: {
name: "test_tool",
description: "A test tool",
parameters: { type: "object", properties: {} },
},
},
],
toolProtocol: "native" as const,
// parallelToolCalls not specified
}

const generator = handler.createMessage(systemPrompt, messages, metadata)
for await (const chunk of generator) {
// Consume the generator
}

// Verify that parallel_tool_calls defaults to false
const createCall = mockCreate.mock.calls[0][0]
expect(createCall.parallel_tool_calls).toBe(false)
})
})
})
28 changes: 27 additions & 1 deletion src/api/providers/lite-llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa
return /\bgpt-?5(?!\d)/i.test(modelId)
}

/**
* Check if the model is routed through AWS Bedrock
* Bedrock doesn't support the parallel_tool_calls parameter
*
* If the user has explicitly set litellmUseAzureBedrock, use that setting.
* Otherwise, fall back to auto-detection based on model ID patterns.
* Note: We exclude 'anthropic.' prefix as it can match direct Anthropic API access through LiteLLM
*/
private isBedrockModel(modelId: string): boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn’t seem right

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roomote address this

@ghost ghost Dec 21, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixaroo Clock   See task on Roo Cloud

This issue was already addressed in commit bbd325f. The anthropic. prefix was removed from Bedrock detection to prevent false positives when users access Anthropic directly through LiteLLM. All tests pass.

View commit | Revert commit

// User-specified option takes precedence
if (this.options.litellmUseAzureBedrock !== undefined) {
return this.options.litellmUseAzureBedrock
}

// Fall back to auto-detection
const lowerModel = modelId.toLowerCase()
return (
lowerModel.includes("bedrock") ||
lowerModel.includes("amazon.") ||
// Match AWS Bedrock model ID patterns (excluding anthropic to avoid false positives)
/^(amazon|ai21|cohere|meta|mistral)\./.test(lowerModel)
)
}

override async *createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
Expand Down Expand Up @@ -133,7 +157,9 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa
},
...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }),
...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }),
...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }),
// Bedrock doesn't support parallel_tool_calls parameter, so exclude it for Bedrock models
...(useNativeTools &&
!this.isBedrockModel(modelId) && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }),
}

// GPT-5 models require max_completion_tokens instead of the deprecated max_tokens parameter
Expand Down
14 changes: 14 additions & 0 deletions webview-ui/src/components/settings/providers/LiteLLM.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ export const LiteLLM = ({
simplifySettings={simplifySettings}
/>

{/* Bedrock backend option */}
<div className="mt-4">
<VSCodeCheckbox
checked={apiConfiguration.litellmUseAzureBedrock || false}
onChange={(e: any) => {
setApiConfigurationField("litellmUseAzureBedrock", e.target.checked)
}}>
<span className="font-medium">{t("settings:providers.litellmUseAzureBedrock")}</span>
</VSCodeCheckbox>
<div className="text-sm text-vscode-descriptionForeground ml-6 mt-1">
{t("settings:providers.litellmUseAzureBedrockDescription")}
</div>
</div>

{/* Show prompt caching option if the selected model supports it */}
{(() => {
const selectedModelId = apiConfiguration.litellmModelId || litellmDefaultModelId
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@
"getXaiApiKey": "Get xAI API Key",
"litellmApiKey": "LiteLLM API Key",
"litellmBaseUrl": "LiteLLM Base URL",
"litellmUseAzureBedrock": "Backend is AWS Bedrock",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typographical note: The key litellmUseAzureBedrock mentions 'Azure' while the value and description refer to 'AWS Bedrock'. Please verify if the key should be renamed (e.g., to litellmUseAWSBedrock) to avoid confusion.

"litellmUseAzureBedrockDescription": "Enable this if your LiteLLM proxy routes to AWS Bedrock models. This ensures Bedrock-incompatible parameters are excluded from requests.",
"awsCredentials": "AWS Credentials",
"awsProfile": "AWS Profile",
"awsApiKey": "Amazon Bedrock API Key",
Expand Down
Loading