This commit is contained in:
Deleted user 2026-05-27 10:38:15 +08:00 committed by GitHub
commit 53db2939bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 299 additions and 1 deletions

View file

@ -349,6 +349,7 @@ const litellmSchema = baseProviderSettingsSchema.extend({
litellmApiKey: z.string().optional(),
litellmModelId: z.string().optional(),
litellmUsePromptCache: z.boolean().optional(),
litellmFlattenContent: z.boolean().optional(),
})
const sambaNovaSchema = apiModelIdProviderModelSchema.extend({

View file

@ -920,4 +920,206 @@ describe("LiteLLMHandler", () => {
expect(id1).not.toBe(id2)
})
})
describe("content flattening for auto_router compatibility", () => {
it("should flatten array content to string by default (when litellmFlattenContent is undefined)", async () => {
const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{ type: "text", text: "Hello" },
{ type: "text", text: "World" },
],
},
]
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Hi!" } }],
usage: { prompt_tokens: 10, completion_tokens: 5 },
}
},
}
mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})
const generator = handler.createMessage(systemPrompt, messages)
for await (const _chunk of generator) {
// Consume
}
const createCall = mockCreate.mock.calls[0][0]
const userMessage = createCall.messages.find((msg: any) => msg.role === "user")
// Content should be flattened to a string
expect(typeof userMessage.content).toBe("string")
expect(userMessage.content).toBe("Hello\n\nWorld")
})
it("should flatten array content to string when litellmFlattenContent is true", async () => {
const optionsWithFlatten: ApiHandlerOptions = {
...mockOptions,
litellmFlattenContent: true,
}
handler = new LiteLLMHandler(optionsWithFlatten)
const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [{ type: "text", text: "Single block" }],
},
]
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 generator = handler.createMessage(systemPrompt, messages)
for await (const _chunk of generator) {
// Consume
}
const createCall = mockCreate.mock.calls[0][0]
const userMessage = createCall.messages.find((msg: any) => msg.role === "user")
expect(typeof userMessage.content).toBe("string")
expect(userMessage.content).toBe("Single block")
})
it("should NOT flatten array content when litellmFlattenContent is false", async () => {
const optionsWithoutFlatten: ApiHandlerOptions = {
...mockOptions,
litellmFlattenContent: false,
}
handler = new LiteLLMHandler(optionsWithoutFlatten)
const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{ type: "text", text: "Hello" },
{ type: "text", text: "World" },
],
},
]
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "Hi!" } }],
usage: { prompt_tokens: 10, completion_tokens: 5 },
}
},
}
mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})
const generator = handler.createMessage(systemPrompt, messages)
for await (const _chunk of generator) {
// Consume
}
const createCall = mockCreate.mock.calls[0][0]
const userMessage = createCall.messages.find((msg: any) => msg.role === "user")
// Content should remain as array
expect(Array.isArray(userMessage.content)).toBe(true)
expect(userMessage.content).toHaveLength(2)
})
it("should preserve string content unchanged regardless of flatten setting", async () => {
const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: "Simple string message",
},
]
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 generator = handler.createMessage(systemPrompt, messages)
for await (const _chunk of generator) {
// Consume
}
const createCall = mockCreate.mock.calls[0][0]
const userMessage = createCall.messages.find((msg: any) => msg.role === "user")
expect(typeof userMessage.content).toBe("string")
expect(userMessage.content).toBe("Simple string message")
})
it("should NOT flatten array content if it contains non-text blocks (images)", async () => {
const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{ type: "text", text: "Look at this image:" },
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: "iVBORw0KGgo=",
},
},
],
},
]
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: "I see..." } }],
usage: { prompt_tokens: 10, completion_tokens: 5 },
}
},
}
mockCreate.mockReturnValue({
withResponse: vi.fn().mockResolvedValue({ data: mockStream }),
})
const generator = handler.createMessage(systemPrompt, messages)
for await (const _chunk of generator) {
// Consume
}
const createCall = mockCreate.mock.calls[0][0]
const userMessage = createCall.messages.find((msg: any) => msg.role === "user")
// Content should remain as array since it contains image
expect(Array.isArray(userMessage.content)).toBe(true)
})
})
})

View file

@ -109,6 +109,45 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa
})
}
/**
* Flatten array content in messages to simple string format.
*
* LiteLLM's auto_router feature passes user content to embedding models for semantic routing.
* However, embedding models expect plain strings, not arrays of content blocks.
* When Roo Code sends messages with array content like:
* {"role": "user", "content": [{"type": "text", "text": "..."}]}
*
* The auto_router fails because it passes this array directly to embeddings.
* This method flattens such content to simple strings:
* {"role": "user", "content": "..."}
*
* This is enabled by default via litellmFlattenContent option.
* Users who need multimodal content (images) can disable this.
*/
private flattenMessageContent(
messages: OpenAI.Chat.ChatCompletionMessageParam[],
): OpenAI.Chat.ChatCompletionMessageParam[] {
return messages.map((msg) => {
// Only flatten user and system messages with array content
if ((msg.role === "user" || msg.role === "system") && Array.isArray(msg.content)) {
// Check if all content blocks are text type
const allText = msg.content.every(
(part) => typeof part === "object" && "type" in part && part.type === "text",
)
// Only flatten if all content is text (no images)
if (allText) {
const textParts = msg.content.map((part) => (part as { type: "text"; text: string }).text)
return {
...msg,
content: textParts.join("\n\n"),
}
}
}
return msg
})
}
override async *createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
@ -116,10 +155,16 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa
): ApiStream {
const { id: modelId, info } = await this.fetchModel()
const openAiMessages = convertToOpenAiMessages(messages, {
let openAiMessages = convertToOpenAiMessages(messages, {
normalizeToolCallId: sanitizeOpenAiCallId,
})
// Flatten array content to string for compatibility with LiteLLM's auto_router
// This is enabled by default (when litellmFlattenContent is undefined or true)
if (this.options.litellmFlattenContent !== false) {
openAiMessages = this.flattenMessageContent(openAiMessages)
}
// Prepare messages with cache control if enabled and supported
let systemMessage: OpenAI.Chat.ChatCompletionMessageParam
let enhancedMessages: OpenAI.Chat.ChatCompletionMessageParam[]

View file

@ -182,6 +182,20 @@ export const LiteLLM = ({
}
return null
})()}
{/* Flatten content option for auto_router compatibility */}
<div className="mt-4">
<VSCodeCheckbox
checked={apiConfiguration.litellmFlattenContent !== false}
onChange={(e: any) => {
setApiConfigurationField("litellmFlattenContent", e.target.checked)
}}>
<span className="font-medium">{t("settings:providers.litellmFlattenContent")}</span>
</VSCodeCheckbox>
<div className="text-sm text-vscode-descriptionForeground ml-6 mt-1">
{t("settings:providers.litellmFlattenContentDescription")}
</div>
</div>
</>
)
}

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Obtenir clau API de xAI",
"litellmApiKey": "Clau API de LiteLLM",
"litellmBaseUrl": "URL base de LiteLLM",
"litellmFlattenContent": "Aplanar contingut dels missatges",
"litellmFlattenContentDescription": "Converteix el contingut dels missatges en format matriu a cadenes de text simples. Activeu aquesta opció per a la compatibilitat amb auto_router de LiteLLM i les funcions d'encaminament basades en incrustacions. Desactiveu només si necessiteu contingut multimodal (imatges).",
"awsCredentials": "Credencials d'AWS",
"awsProfile": "Perfil d'AWS",
"awsApiKey": "Clau d'API d'Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "xAI API-Schlüssel erhalten",
"litellmApiKey": "LiteLLM API-Schlüssel",
"litellmBaseUrl": "LiteLLM Basis-URL",
"litellmFlattenContent": "Nachrichteninhalt abflachen",
"litellmFlattenContentDescription": "Konvertiert Array-formatierten Nachrichteninhalt in einfache Zeichenketten. Aktivieren Sie dies für die Kompatibilität mit LiteLLMs auto_router und Embedding-basierten Routing-Funktionen. Deaktivieren Sie es nur, wenn Sie multimodale Inhalte (Bilder) benötigen.",
"awsCredentials": "AWS Anmeldedaten",
"awsProfile": "AWS Profil",
"awsApiKey": "Amazon Bedrock API-Schlüssel",

View file

@ -467,6 +467,8 @@
"getXaiApiKey": "Get xAI API Key",
"litellmApiKey": "LiteLLM API Key",
"litellmBaseUrl": "LiteLLM Base URL",
"litellmFlattenContent": "Flatten message content",
"litellmFlattenContentDescription": "Convert array-formatted message content to plain strings. Enable this for compatibility with LiteLLM's auto_router and embedding-based routing features. Disable only if you need multimodal content (images).",
"awsCredentials": "AWS Credentials",
"awsProfile": "AWS Profile",
"awsApiKey": "Amazon Bedrock API Key",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Obtener clave API de xAI",
"litellmApiKey": "Clave API de LiteLLM",
"litellmBaseUrl": "URL base de LiteLLM",
"litellmFlattenContent": "Aplanar contenido del mensaje",
"litellmFlattenContentDescription": "Convierte el contenido del mensaje en formato de matriz a cadenas de texto simples. Habilite esto para compatibilidad con auto_router de LiteLLM y funciones de enrutamiento basadas en embeddings. Deshabilite solo si necesita contenido multimodal (imágenes).",
"awsCredentials": "Credenciales de AWS",
"awsProfile": "Perfil de AWS",
"awsApiKey": "Clave de API de Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Obtenir la clé API xAI",
"litellmApiKey": "Clé API LiteLLM",
"litellmBaseUrl": "URL de base LiteLLM",
"litellmFlattenContent": "Aplatir le contenu des messages",
"litellmFlattenContentDescription": "Convertit le contenu des messages au format tableau en chaînes de caractères simples. Activez cette option pour la compatibilité avec auto_router de LiteLLM et les fonctionnalités de routage basées sur les embeddings. Désactivez uniquement si vous avez besoin de contenu multimodal (images).",
"awsCredentials": "Identifiants AWS",
"awsProfile": "Profil AWS",
"awsApiKey": "Clé API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "xAI API कुंजी प्राप्त करें",
"litellmApiKey": "LiteLLM API कुंजी",
"litellmBaseUrl": "LiteLLM आधार URL",
"litellmFlattenContent": "संदेश सामग्री को सपाट करें",
"litellmFlattenContentDescription": "ऐरे प्रारूप में संदेश सामग्री को सादा स्ट्रिंग में बदलें। LiteLLM के auto_router और एम्बेडिंग-आधारित रूटिंग सुविधाओं के साथ संगतता के लिए इसे सक्षम करें। केवल तभी अक्षम करें जब आपको मल्टीमॉडल सामग्री (छवियां) की आवश्यकता हो।",
"awsCredentials": "AWS क्रेडेंशियल्स",
"awsProfile": "AWS प्रोफाइल",
"awsApiKey": "Amazon बेडरॉक API कुंजी",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Dapatkan xAI API Key",
"litellmApiKey": "LiteLLM API Key",
"litellmBaseUrl": "LiteLLM Base URL",
"litellmFlattenContent": "Ratakan konten pesan",
"litellmFlattenContentDescription": "Mengubah konten pesan berformat array menjadi string teks biasa. Aktifkan untuk kompatibilitas dengan auto_router LiteLLM dan fitur routing berbasis embedding. Nonaktifkan hanya jika Anda memerlukan konten multimodal (gambar).",
"awsCredentials": "AWS Credentials",
"awsProfile": "AWS Profile",
"awsApiKey": "Kunci API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Ottieni chiave API xAI",
"litellmApiKey": "Chiave API LiteLLM",
"litellmBaseUrl": "URL base LiteLLM",
"litellmFlattenContent": "Appiattisci contenuto messaggi",
"litellmFlattenContentDescription": "Converte il contenuto dei messaggi in formato array in stringhe semplici. Abilita questa opzione per la compatibilità con auto_router di LiteLLM e le funzionalità di routing basate su embedding. Disabilita solo se hai bisogno di contenuti multimodali (immagini).",
"awsCredentials": "Credenziali AWS",
"awsProfile": "Profilo AWS",
"awsApiKey": "Chiave API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "xAI APIキーを取得",
"litellmApiKey": "LiteLLM APIキー",
"litellmBaseUrl": "LiteLLM ベースURL",
"litellmFlattenContent": "メッセージコンテンツをフラット化",
"litellmFlattenContentDescription": "配列形式のメッセージコンテンツをプレーンな文字列に変換します。LiteLLMのauto_routerおよび埋め込みベースのルーティング機能との互換性のために有効にしてください。マルチモーダルコンテンツ画像が必要な場合のみ無効にしてください。",
"awsCredentials": "AWS認証情報",
"awsProfile": "AWSプロファイル",
"awsApiKey": "Amazon Bedrock APIキー",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "xAI API 키 받기",
"litellmApiKey": "LiteLLM API 키",
"litellmBaseUrl": "LiteLLM 기본 URL",
"litellmFlattenContent": "메시지 콘텐츠 평탄화",
"litellmFlattenContentDescription": "배열 형식의 메시지 콘텐츠를 일반 문자열로 변환합니다. LiteLLM의 auto_router 및 임베딩 기반 라우팅 기능과의 호환성을 위해 활성화하세요. 멀티모달 콘텐츠(이미지)가 필요한 경우에만 비활성화하세요.",
"awsCredentials": "AWS 자격 증명",
"awsProfile": "AWS 프로필",
"awsApiKey": "Amazon Bedrock API 키",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "xAI API-sleutel ophalen",
"litellmApiKey": "LiteLLM API-sleutel",
"litellmBaseUrl": "LiteLLM basis-URL",
"litellmFlattenContent": "Berichtinhoud afvlakken",
"litellmFlattenContentDescription": "Converteert berichtinhoud in array-formaat naar platte tekst. Schakel dit in voor compatibiliteit met LiteLLM's auto_router en op embedding gebaseerde routeringsfuncties. Schakel alleen uit als u multimodale inhoud (afbeeldingen) nodig heeft.",
"awsCredentials": "AWS-inloggegevens",
"awsProfile": "AWS-profiel",
"awsApiKey": "Amazon Bedrock API-sleutel",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Uzyskaj klucz API xAI",
"litellmApiKey": "Klucz API LiteLLM",
"litellmBaseUrl": "URL bazowy LiteLLM",
"litellmFlattenContent": "Spłaszcz zawartość wiadomości",
"litellmFlattenContentDescription": "Konwertuje zawartość wiadomości w formacie tablicy na zwykłe ciągi znaków. Włącz dla kompatybilności z auto_router LiteLLM i funkcjami routingu opartymi na osadzeniach. Wyłącz tylko jeśli potrzebujesz treści multimodalnych (obrazów).",
"awsCredentials": "Poświadczenia AWS",
"awsProfile": "Profil AWS",
"awsApiKey": "Klucz API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Obter chave de API xAI",
"litellmApiKey": "Chave API LiteLLM",
"litellmBaseUrl": "URL base LiteLLM",
"litellmFlattenContent": "Achatar conteúdo das mensagens",
"litellmFlattenContentDescription": "Converte o conteúdo das mensagens em formato de array para strings simples. Habilite para compatibilidade com o auto_router do LiteLLM e recursos de roteamento baseados em embeddings. Desabilite apenas se precisar de conteúdo multimodal (imagens).",
"awsCredentials": "Credenciais AWS",
"awsProfile": "Perfil AWS",
"awsApiKey": "Chave de API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Получить xAI API-ключ",
"litellmApiKey": "API-ключ LiteLLM",
"litellmBaseUrl": "Базовый URL LiteLLM",
"litellmFlattenContent": "Выровнять содержимое сообщений",
"litellmFlattenContentDescription": "Преобразует содержимое сообщений в формате массива в простые строки. Включите для совместимости с auto_router LiteLLM и функциями маршрутизации на основе эмбеддингов. Отключайте только при необходимости мультимодального контента (изображений).",
"awsCredentials": "AWS-учётные данные",
"awsProfile": "Профиль AWS",
"awsApiKey": "Ключ API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "xAI API Anahtarı Al",
"litellmApiKey": "LiteLLM API Anahtarı",
"litellmBaseUrl": "LiteLLM Temel URL",
"litellmFlattenContent": "Mesaj içeriğini düzleştir",
"litellmFlattenContentDescription": "Dizi formatındaki mesaj içeriğini düz metin dizelerine dönüştürür. LiteLLM'in auto_router ve gömme tabanlı yönlendirme özellikleriyle uyumluluk için etkinleştirin. Yalnızca çok modlu içeriğe (görüntüler) ihtiyacınız varsa devre dışı bırakın.",
"awsCredentials": "AWS Kimlik Bilgileri",
"awsProfile": "AWS Profili",
"awsApiKey": "Amazon Bedrock API Anahtarı",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "Lấy khóa API xAI",
"litellmApiKey": "Khóa API LiteLLM",
"litellmBaseUrl": "URL cơ sở LiteLLM",
"litellmFlattenContent": "Làm phẳng nội dung tin nhắn",
"litellmFlattenContentDescription": "Chuyển đổi nội dung tin nhắn định dạng mảng thành chuỗi văn bản thuần. Bật tùy chọn này để tương thích với auto_router của LiteLLM và các tính năng định tuyến dựa trên embedding. Chỉ tắt nếu bạn cần nội dung đa phương thức (hình ảnh).",
"awsCredentials": "Thông tin xác thực AWS",
"awsProfile": "Hồ sơ AWS",
"awsApiKey": "Khóa API Amazon Bedrock",

View file

@ -405,6 +405,8 @@
"getXaiApiKey": "获取 xAI API 密钥",
"litellmApiKey": "LiteLLM API 密钥",
"litellmBaseUrl": "LiteLLM 基础 URL",
"litellmFlattenContent": "展平消息内容",
"litellmFlattenContentDescription": "将数组格式的消息内容转换为纯字符串。启用此选项以兼容 LiteLLM 的 auto_router 和基于嵌入的路由功能。仅在需要多模态内容(图像)时禁用。",
"awsCredentials": "AWS 凭证",
"awsProfile": "AWS 配置文件",
"awsApiKey": "Amazon Bedrock API 密钥",

View file

@ -415,6 +415,8 @@
"getXaiApiKey": "取得 xAI API 金鑰",
"litellmApiKey": "LiteLLM API 金鑰",
"litellmBaseUrl": "LiteLLM 基礎 URL",
"litellmFlattenContent": "展平訊息內容",
"litellmFlattenContentDescription": "將陣列格式的訊息內容轉換為純字串。啟用此選項以相容 LiteLLM 的 auto_router 和基於嵌入的路由功能。僅在需要多模態內容(圖像)時停用。",
"awsCredentials": "AWS 認證",
"awsProfile": "AWS Profile",
"awsApiKey": "Amazon Bedrock API 金鑰",