mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
feat: add strict tool message ordering setting for OpenAI Compatible providers
Adds a new setting `openAiStrictToolMessageOrdering` to OpenAI Compatible providers that enables merging text content after tool results into the last tool message. This fixes "Unexpected role user after role tool" errors with NVIDIA NIM, OpenRouter, and other strict OpenAI-compatible APIs. When enabled, environment_details and other text content that follows tool_result messages are merged into the last tool message instead of being sent as a separate user message. Fixes #10540
This commit is contained in:
parent
e8ed344b0f
commit
db061661cc
4 changed files with 34 additions and 5 deletions
|
|
@ -250,6 +250,7 @@ const openAiSchema = baseProviderSettingsSchema.extend({
|
|||
openAiStreamingEnabled: z.boolean().optional(),
|
||||
openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration.
|
||||
openAiHeaders: z.record(z.string(), z.string()).optional(),
|
||||
openAiStrictToolMessageOrdering: z.boolean().optional(), // Merge text after tool results to avoid "user after tool" errors.
|
||||
})
|
||||
|
||||
const ollamaSchema = baseProviderSettingsSchema.extend({
|
||||
|
|
|
|||
|
|
@ -122,7 +122,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
}
|
||||
}
|
||||
|
||||
convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
|
||||
// When openAiStrictToolMessageOrdering is enabled, merge text content after tool results
|
||||
// into the last tool message. This fixes "user after tool" errors with NVIDIA NIM,
|
||||
// OpenRouter, and other strict OpenAI-compatible APIs.
|
||||
const mergeToolResultText = this.options.openAiStrictToolMessageOrdering ?? false
|
||||
convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages, { mergeToolResultText })]
|
||||
|
||||
if (modelInfo.supportsPromptCache) {
|
||||
// Note: the following logic is copied from openrouter:
|
||||
|
|
@ -224,11 +228,15 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
yield this.processUsageMetrics(lastUsage, modelInfo)
|
||||
}
|
||||
} else {
|
||||
// When openAiStrictToolMessageOrdering is enabled, merge text content after tool results
|
||||
// into the last tool message. This fixes "user after tool" errors with NVIDIA NIM,
|
||||
// OpenRouter, and other strict OpenAI-compatible APIs.
|
||||
const mergeToolResultText = this.options.openAiStrictToolMessageOrdering ?? false
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||
model: modelId,
|
||||
messages: deepseekReasoner
|
||||
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
: [systemMessage, ...convertToOpenAiMessages(messages)],
|
||||
: [systemMessage, ...convertToOpenAiMessages(messages, { mergeToolResultText })],
|
||||
...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }),
|
||||
...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }),
|
||||
...(metadata?.toolProtocol === "native" && {
|
||||
|
|
@ -342,6 +350,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
if (this.options.openAiStreamingEnabled ?? true) {
|
||||
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
|
||||
|
||||
// When openAiStrictToolMessageOrdering is enabled, merge text content after tool results
|
||||
// into the last tool message. This fixes "user after tool" errors with NVIDIA NIM,
|
||||
// OpenRouter, and other strict OpenAI-compatible APIs.
|
||||
const mergeToolResultText = this.options.openAiStrictToolMessageOrdering ?? false
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: modelId,
|
||||
messages: [
|
||||
|
|
@ -349,7 +361,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
role: "developer",
|
||||
content: `Formatting re-enabled\n${systemPrompt}`,
|
||||
},
|
||||
...convertToOpenAiMessages(messages),
|
||||
...convertToOpenAiMessages(messages, { mergeToolResultText }),
|
||||
],
|
||||
stream: true,
|
||||
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
|
||||
|
|
@ -379,6 +391,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
|
||||
yield* this.handleStreamResponse(stream)
|
||||
} else {
|
||||
// When openAiStrictToolMessageOrdering is enabled, merge text content after tool results
|
||||
// into the last tool message. This fixes "user after tool" errors with NVIDIA NIM,
|
||||
// OpenRouter, and other strict OpenAI-compatible APIs.
|
||||
const mergeToolResultText = this.options.openAiStrictToolMessageOrdering ?? false
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||
model: modelId,
|
||||
messages: [
|
||||
|
|
@ -386,7 +402,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
role: "developer",
|
||||
content: `Formatting re-enabled\n${systemPrompt}`,
|
||||
},
|
||||
...convertToOpenAiMessages(messages),
|
||||
...convertToOpenAiMessages(messages, { mergeToolResultText }),
|
||||
],
|
||||
reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
|
||||
temperature: undefined,
|
||||
|
|
|
|||
|
|
@ -158,6 +158,16 @@ export const OpenAICompatible = ({
|
|||
onChange={handleInputChange("openAiStreamingEnabled", noTransform)}>
|
||||
{t("settings:modelInfo.enableStreaming")}
|
||||
</Checkbox>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.openAiStrictToolMessageOrdering ?? false}
|
||||
onChange={handleInputChange("openAiStrictToolMessageOrdering", noTransform)}>
|
||||
{t("settings:providers.openAiStrictToolMessageOrdering")}
|
||||
</Checkbox>
|
||||
<div className="text-sm text-vscode-descriptionForeground ml-6">
|
||||
{t("settings:providers.openAiStrictToolMessageOrderingDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.includeMaxTokens ?? true}
|
||||
|
|
|
|||
|
|
@ -531,7 +531,9 @@
|
|||
"placeholder": "Default: claude",
|
||||
"maxTokensLabel": "Max Output Tokens",
|
||||
"maxTokensDescription": "Maximum number of output tokens for Claude Code responses. Default is 8000."
|
||||
}
|
||||
},
|
||||
"openAiStrictToolMessageOrdering": "Strict tool message ordering",
|
||||
"openAiStrictToolMessageOrderingDescription": "Enable this if your API returns errors like \"Unexpected role 'user' after role 'tool'\". This merges text content into tool messages to fix compatibility with NVIDIA NIM, OpenRouter, and other strict OpenAI-compatible APIs."
|
||||
},
|
||||
"browser": {
|
||||
"enable": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue