mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix(openai): remove convertToSimpleMessages to fix tool calling for OpenAI-compatible providers (#10575)
This commit is contained in:
parent
b7bd859473
commit
907b94bc40
23 changed files with 1 additions and 236 deletions
|
|
@ -242,7 +242,6 @@ const vertexSchema = apiModelIdProviderModelSchema.extend({
|
|||
const openAiSchema = baseProviderSettingsSchema.extend({
|
||||
openAiBaseUrl: z.string().optional(),
|
||||
openAiApiKey: z.string().optional(),
|
||||
openAiLegacyFormat: z.boolean().optional(),
|
||||
openAiR1FormatEnabled: z.boolean().optional(),
|
||||
openAiModelId: z.string().optional(),
|
||||
openAiCustomModelInfo: modelInfoSchema.nullish(),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import { XmlMatcher } from "../../utils/xml-matcher"
|
|||
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { convertToSimpleMessages } from "../transform/simple-format"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
|
||||
|
|
@ -90,10 +89,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
const modelUrl = this.options.openAiBaseUrl ?? ""
|
||||
const modelId = this.options.openAiModelId ?? ""
|
||||
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
|
||||
const enabledLegacyFormat = this.options.openAiLegacyFormat ?? false
|
||||
const isAzureAiInference = this._isAzureAiInference(modelUrl)
|
||||
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
|
||||
const ark = modelUrl.includes(".volces.com")
|
||||
|
||||
if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) {
|
||||
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata)
|
||||
|
|
@ -110,8 +107,6 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
|
||||
if (deepseekReasoner) {
|
||||
convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
} else if (ark || enabledLegacyFormat) {
|
||||
convertedMessages = [systemMessage, ...convertToSimpleMessages(messages)]
|
||||
} else {
|
||||
if (modelInfo.supportsPromptCache) {
|
||||
systemMessage = {
|
||||
|
|
@ -233,9 +228,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
model: modelId,
|
||||
messages: deepseekReasoner
|
||||
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
: enabledLegacyFormat
|
||||
? [systemMessage, ...convertToSimpleMessages(messages)]
|
||||
: [systemMessage, ...convertToOpenAiMessages(messages)],
|
||||
: [systemMessage, ...convertToOpenAiMessages(messages)],
|
||||
...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }),
|
||||
...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }),
|
||||
...(metadata?.toolProtocol === "native" && {
|
||||
|
|
|
|||
|
|
@ -1,140 +0,0 @@
|
|||
// npx vitest run src/api/transform/__tests__/simple-format.spec.ts
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { convertToSimpleContent, convertToSimpleMessages } from "../simple-format"
|
||||
|
||||
describe("simple-format", () => {
|
||||
describe("convertToSimpleContent", () => {
|
||||
it("returns string content as-is", () => {
|
||||
const content = "Hello world"
|
||||
expect(convertToSimpleContent(content)).toBe("Hello world")
|
||||
})
|
||||
|
||||
it("extracts text from text blocks", () => {
|
||||
const content = [
|
||||
{ type: "text", text: "Hello" },
|
||||
{ type: "text", text: "world" },
|
||||
] as Anthropic.Messages.TextBlockParam[]
|
||||
expect(convertToSimpleContent(content)).toBe("Hello\nworld")
|
||||
})
|
||||
|
||||
it("converts image blocks to descriptive text", () => {
|
||||
const content = [
|
||||
{ type: "text", text: "Here's an image:" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ImageBlockParam>
|
||||
expect(convertToSimpleContent(content)).toBe("Here's an image:\n[Image: image/png]")
|
||||
})
|
||||
|
||||
it("converts tool use blocks to descriptive text", () => {
|
||||
const content = [
|
||||
{ type: "text", text: "Using a tool:" },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "tool-1",
|
||||
name: "read_file",
|
||||
input: { path: "test.txt" },
|
||||
},
|
||||
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ToolUseBlockParam>
|
||||
expect(convertToSimpleContent(content)).toBe("Using a tool:\n[Tool Use: read_file]")
|
||||
})
|
||||
|
||||
it("handles string tool result content", () => {
|
||||
const content = [
|
||||
{ type: "text", text: "Tool result:" },
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-1",
|
||||
content: "Result text",
|
||||
},
|
||||
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ToolResultBlockParam>
|
||||
expect(convertToSimpleContent(content)).toBe("Tool result:\nResult text")
|
||||
})
|
||||
|
||||
it("handles array tool result content with text and images", () => {
|
||||
const content = [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-1",
|
||||
content: [
|
||||
{ type: "text", text: "Result 1" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/jpeg",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
{ type: "text", text: "Result 2" },
|
||||
],
|
||||
},
|
||||
] as Anthropic.Messages.ToolResultBlockParam[]
|
||||
expect(convertToSimpleContent(content)).toBe("Result 1\n[Image: image/jpeg]\nResult 2")
|
||||
})
|
||||
|
||||
it("filters out empty strings", () => {
|
||||
const content = [
|
||||
{ type: "text", text: "Hello" },
|
||||
{ type: "text", text: "" },
|
||||
{ type: "text", text: "world" },
|
||||
] as Anthropic.Messages.TextBlockParam[]
|
||||
expect(convertToSimpleContent(content)).toBe("Hello\nworld")
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertToSimpleMessages", () => {
|
||||
it("converts messages with string content", () => {
|
||||
const messages = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
] as Anthropic.Messages.MessageParam[]
|
||||
expect(convertToSimpleMessages(messages)).toEqual([
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
])
|
||||
})
|
||||
|
||||
it("converts messages with complex content", () => {
|
||||
const messages = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Look at this:" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "I see the image" },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "tool-1",
|
||||
name: "analyze_image",
|
||||
input: { data: "base64data" },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as Anthropic.Messages.MessageParam[]
|
||||
expect(convertToSimpleMessages(messages)).toEqual([
|
||||
{ role: "user", content: "Look at this:\n[Image: image/png]" },
|
||||
{ role: "assistant", content: "I see the image\n[Tool Use: analyze_image]" },
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
/**
|
||||
* Convert complex content blocks to simple string content
|
||||
*/
|
||||
export function convertToSimpleContent(content: Anthropic.Messages.MessageParam["content"]): string {
|
||||
if (typeof content === "string") {
|
||||
return content
|
||||
}
|
||||
|
||||
// Extract text from content blocks
|
||||
return content
|
||||
.map((block) => {
|
||||
if (block.type === "text") {
|
||||
return block.text
|
||||
}
|
||||
if (block.type === "image") {
|
||||
return `[Image: ${block.source.media_type}]`
|
||||
}
|
||||
if (block.type === "tool_use") {
|
||||
return `[Tool Use: ${block.name}]`
|
||||
}
|
||||
if (block.type === "tool_result") {
|
||||
if (typeof block.content === "string") {
|
||||
return block.content
|
||||
}
|
||||
if (Array.isArray(block.content)) {
|
||||
return block.content
|
||||
.map((part) => {
|
||||
if (part.type === "text") {
|
||||
return part.text
|
||||
}
|
||||
if (part.type === "image") {
|
||||
return `[Image: ${part.source.media_type}]`
|
||||
}
|
||||
return ""
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Anthropic messages to simple format with string content
|
||||
*/
|
||||
export function convertToSimpleMessages(
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
): Array<{ role: "user" | "assistant"; content: string }> {
|
||||
return messages.map((message) => ({
|
||||
role: message.role,
|
||||
content: convertToSimpleContent(message.content),
|
||||
}))
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@ export const OpenAICompatible = ({
|
|||
const { t } = useAppTranslation()
|
||||
|
||||
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
|
||||
const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat)
|
||||
|
||||
const [openAiModels, setOpenAiModels] = useState<Record<string, ModelInfo> | null>(null)
|
||||
|
||||
|
|
@ -155,16 +154,6 @@ export const OpenAICompatible = ({
|
|||
onChange={handleInputChange("openAiR1FormatEnabled", noTransform)}
|
||||
openAiR1FormatEnabled={apiConfiguration?.openAiR1FormatEnabled ?? false}
|
||||
/>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={openAiLegacyFormatSelected}
|
||||
onChange={(checked: boolean) => {
|
||||
setOpenAiLegacyFormatSelected(checked)
|
||||
setApiConfigurationField("openAiLegacyFormat", checked)
|
||||
}}>
|
||||
{t("settings:providers.useLegacyFormat")}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.openAiStreamingEnabled ?? true}
|
||||
onChange={handleInputChange("openAiStreamingEnabled", noTransform)}>
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ca/settings.json
generated
1
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Utilitzar URL base personalitzada",
|
||||
"useReasoning": "Activar raonament",
|
||||
"useHostHeader": "Utilitzar capçalera Host personalitzada",
|
||||
"useLegacyFormat": "Utilitzar el format d'API OpenAI antic",
|
||||
"customHeaders": "Capçaleres personalitzades",
|
||||
"headerName": "Nom de la capçalera",
|
||||
"headerValue": "Valor de la capçalera",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/de/settings.json
generated
1
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -280,7 +280,6 @@
|
|||
"useCustomBaseUrl": "Benutzerdefinierte Basis-URL verwenden",
|
||||
"useReasoning": "Reasoning aktivieren",
|
||||
"useHostHeader": "Benutzerdefinierten Host-Header verwenden",
|
||||
"useLegacyFormat": "Altes OpenAI API-Format verwenden",
|
||||
"customHeaders": "Benutzerdefinierte Headers",
|
||||
"headerName": "Header-Name",
|
||||
"headerValue": "Header-Wert",
|
||||
|
|
|
|||
|
|
@ -287,7 +287,6 @@
|
|||
"useCustomBaseUrl": "Use custom base URL",
|
||||
"useReasoning": "Enable reasoning",
|
||||
"useHostHeader": "Use custom Host header",
|
||||
"useLegacyFormat": "Use legacy OpenAI API format",
|
||||
"customHeaders": "Custom Headers",
|
||||
"headerName": "Header name",
|
||||
"headerValue": "Header value",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/es/settings.json
generated
1
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Usar URL base personalizada",
|
||||
"useReasoning": "Habilitar razonamiento",
|
||||
"useHostHeader": "Usar encabezado Host personalizado",
|
||||
"useLegacyFormat": "Usar formato API de OpenAI heredado",
|
||||
"customHeaders": "Encabezados personalizados",
|
||||
"headerName": "Nombre del encabezado",
|
||||
"headerValue": "Valor del encabezado",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/fr/settings.json
generated
1
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Utiliser une URL de base personnalisée",
|
||||
"useReasoning": "Activer le raisonnement",
|
||||
"useHostHeader": "Utiliser un en-tête Host personnalisé",
|
||||
"useLegacyFormat": "Utiliser le format API OpenAI hérité",
|
||||
"customHeaders": "En-têtes personnalisés",
|
||||
"headerName": "Nom de l'en-tête",
|
||||
"headerValue": "Valeur de l'en-tête",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/hi/settings.json
generated
1
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "कस्टम बेस URL का उपयोग करें",
|
||||
"useReasoning": "तर्क सक्षम करें",
|
||||
"useHostHeader": "कस्टम होस्ट हेडर का उपयोग करें",
|
||||
"useLegacyFormat": "पुराने OpenAI API प्रारूप का उपयोग करें",
|
||||
"customHeaders": "कस्टम हेडर्स",
|
||||
"headerName": "हेडर नाम",
|
||||
"headerValue": "हेडर मूल्य",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/id/settings.json
generated
1
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -282,7 +282,6 @@
|
|||
"useCustomBaseUrl": "Gunakan base URL kustom",
|
||||
"useReasoning": "Aktifkan reasoning",
|
||||
"useHostHeader": "Gunakan Host header kustom",
|
||||
"useLegacyFormat": "Gunakan format API OpenAI legacy",
|
||||
"customHeaders": "Header Kustom",
|
||||
"headerName": "Nama header",
|
||||
"headerValue": "Nilai header",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/it/settings.json
generated
1
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Usa URL base personalizzato",
|
||||
"useReasoning": "Abilita ragionamento",
|
||||
"useHostHeader": "Usa intestazione Host personalizzata",
|
||||
"useLegacyFormat": "Usa formato API OpenAI legacy",
|
||||
"customHeaders": "Intestazioni personalizzate",
|
||||
"headerName": "Nome intestazione",
|
||||
"headerValue": "Valore intestazione",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ja/settings.json
generated
1
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "カスタムベースURLを使用",
|
||||
"useReasoning": "推論を有効化",
|
||||
"useHostHeader": "カスタムHostヘッダーを使用",
|
||||
"useLegacyFormat": "レガシーOpenAI API形式を使用",
|
||||
"customHeaders": "カスタムヘッダー",
|
||||
"headerName": "ヘッダー名",
|
||||
"headerValue": "ヘッダー値",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ko/settings.json
generated
1
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "사용자 정의 기본 URL 사용",
|
||||
"useReasoning": "추론 활성화",
|
||||
"useHostHeader": "사용자 정의 Host 헤더 사용",
|
||||
"useLegacyFormat": "레거시 OpenAI API 형식 사용",
|
||||
"customHeaders": "사용자 정의 헤더",
|
||||
"headerName": "헤더 이름",
|
||||
"headerValue": "헤더 값",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/nl/settings.json
generated
1
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Aangepaste basis-URL gebruiken",
|
||||
"useReasoning": "Redenering inschakelen",
|
||||
"useHostHeader": "Aangepaste Host-header gebruiken",
|
||||
"useLegacyFormat": "Verouderd OpenAI API-formaat gebruiken",
|
||||
"customHeaders": "Aangepaste headers",
|
||||
"headerName": "Headernaam",
|
||||
"headerValue": "Headerwaarde",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pl/settings.json
generated
1
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Użyj niestandardowego URL bazowego",
|
||||
"useReasoning": "Włącz rozumowanie",
|
||||
"useHostHeader": "Użyj niestandardowego nagłówka Host",
|
||||
"useLegacyFormat": "Użyj starszego formatu API OpenAI",
|
||||
"customHeaders": "Niestandardowe nagłówki",
|
||||
"headerName": "Nazwa nagłówka",
|
||||
"headerValue": "Wartość nagłówka",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
1
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Usar URL base personalizado",
|
||||
"useReasoning": "Habilitar raciocínio",
|
||||
"useHostHeader": "Usar cabeçalho Host personalizado",
|
||||
"useLegacyFormat": "Usar formato de API OpenAI legado",
|
||||
"customHeaders": "Cabeçalhos personalizados",
|
||||
"headerName": "Nome do cabeçalho",
|
||||
"headerValue": "Valor do cabeçalho",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ru/settings.json
generated
1
webview-ui/src/i18n/locales/ru/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Использовать пользовательский базовый URL",
|
||||
"useReasoning": "Включить рассуждения",
|
||||
"useHostHeader": "Использовать пользовательский Host-заголовок",
|
||||
"useLegacyFormat": "Использовать устаревший формат OpenAI API",
|
||||
"customHeaders": "Пользовательские заголовки",
|
||||
"headerName": "Имя заголовка",
|
||||
"headerValue": "Значение заголовка",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/tr/settings.json
generated
1
webview-ui/src/i18n/locales/tr/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Özel temel URL kullan",
|
||||
"useReasoning": "Akıl yürütmeyi etkinleştir",
|
||||
"useHostHeader": "Özel Host başlığı kullan",
|
||||
"useLegacyFormat": "Eski OpenAI API formatını kullan",
|
||||
"customHeaders": "Özel Başlıklar",
|
||||
"headerName": "Başlık adı",
|
||||
"headerValue": "Başlık değeri",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/vi/settings.json
generated
1
webview-ui/src/i18n/locales/vi/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "Sử dụng URL cơ sở tùy chỉnh",
|
||||
"useReasoning": "Bật lý luận",
|
||||
"useHostHeader": "Sử dụng tiêu đề Host tùy chỉnh",
|
||||
"useLegacyFormat": "Sử dụng định dạng API OpenAI cũ",
|
||||
"customHeaders": "Tiêu đề tùy chỉnh",
|
||||
"headerName": "Tên tiêu đề",
|
||||
"headerValue": "Giá trị tiêu đề",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
1
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "使用自定义基础 URL",
|
||||
"useReasoning": "启用推理",
|
||||
"useHostHeader": "使用自定义 Host 标头",
|
||||
"useLegacyFormat": "使用传统 OpenAI API 格式",
|
||||
"customHeaders": "自定义标头",
|
||||
"headerName": "标头名称",
|
||||
"headerValue": "标头值",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
1
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
|
|
@ -278,7 +278,6 @@
|
|||
"useCustomBaseUrl": "使用自訂基礎 URL",
|
||||
"useReasoning": "啟用推理",
|
||||
"useHostHeader": "使用自訂 Host 標頭",
|
||||
"useLegacyFormat": "使用舊版 OpenAI API 格式",
|
||||
"customHeaders": "自訂標頭",
|
||||
"headerName": "標頭名稱",
|
||||
"headerValue": "標頭值",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue