feat: enable mergeToolResultText for all OpenAI-compatible providers (#10299)

This commit is contained in:
Hannes Rudolph 2025-12-23 14:19:33 -07:00 committed by GitHub
parent 9b99890c35
commit ded6486a46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 41 additions and 21 deletions

View file

@ -90,7 +90,13 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
model,
max_tokens,
temperature,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
// Enable mergeToolResultText to merge environment_details and other text content
// after tool_results into the last tool message. This prevents reasoning/thinking
// models from dropping reasoning_content when they see a user message after tool results.
messages: [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
stream: true,
stream_options: { include_usage: true },
...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }),

View file

@ -106,7 +106,7 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan
supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml"
// Convert Anthropic messages to OpenAI format (Cerebras is OpenAI-compatible)
const openaiMessages = convertToOpenAiMessages(messages)
const openaiMessages = convertToOpenAiMessages(messages, { mergeToolResultText: true })
// Prepare request body following Cerebras API specification exactly
const requestBody: Record<string, any> = {

View file

@ -44,7 +44,10 @@ export class ChutesHandler extends RouterProvider implements SingleCompletionHan
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model,
max_tokens,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
messages: [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
stream: true,
stream_options: { include_usage: true },
...(metadata?.tools && { tools: metadata.tools }),

View file

@ -72,7 +72,10 @@ export class DeepInfraHandler extends RouterProvider implements SingleCompletion
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelId,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
messages: [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
stream: true,
stream_options: { include_usage: true },
reasoning_effort,

View file

@ -44,7 +44,10 @@ export class FeatherlessHandler extends BaseOpenAiCompatibleProvider<Featherless
model,
max_tokens,
temperature,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
messages: [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
stream: true,
stream_options: { include_usage: true },
}

View file

@ -56,7 +56,10 @@ export class HuggingFaceHandler extends BaseProvider implements SingleCompletion
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelId,
temperature,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
messages: [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
stream: true,
stream_options: { include_usage: true },
}

View file

@ -45,7 +45,7 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa
): ApiStream {
const { id: modelId, info } = await this.fetchModel()
const openAiMessages = convertToOpenAiMessages(messages)
const openAiMessages = convertToOpenAiMessages(messages, { mergeToolResultText: true })
// Prepare messages with cache control if enabled and supported
let systemMessage: OpenAI.Chat.ChatCompletionMessageParam

View file

@ -44,7 +44,7 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
): ApiStream {
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
]
// LM Studio always supports native tools (https://lmstudio.ai/docs/developer/core/tools)

View file

@ -126,7 +126,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
}
}
convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages, { mergeToolResultText: true })]
if (modelInfo.supportsPromptCache) {
// Note: the following logic is copied from openrouter:
@ -234,7 +234,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: enabledLegacyFormat
? [systemMessage, ...convertToSimpleMessages(messages)]
: [systemMessage, ...convertToOpenAiMessages(messages)],
: [systemMessage, ...convertToOpenAiMessages(messages, { mergeToolResultText: true })],
...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }),
...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }),
...(metadata?.toolProtocol === "native" && {
@ -349,7 +349,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
role: "developer",
content: `Formatting re-enabled\n${systemPrompt}`,
},
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
stream: true,
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
@ -386,7 +386,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
role: "developer",
content: `Formatting re-enabled\n${systemPrompt}`,
},
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
],
reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
temperature: undefined,

View file

@ -229,13 +229,15 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
// Convert Anthropic messages to OpenAI format.
// Pass normalization function for Mistral compatibility (requires 9-char alphanumeric IDs)
// Enable mergeToolResultText to merge environment_details after tool_results into the last
// tool message, preventing reasoning/thinking models from dropping reasoning_content.
const isMistral = modelId.toLowerCase().includes("mistral")
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(
messages,
isMistral ? { normalizeToolCallId: normalizeMistralToolCallId } : undefined,
),
...convertToOpenAiMessages(messages, {
mergeToolResultText: true,
...(isMistral && { normalizeToolCallId: normalizeMistralToolCallId }),
}),
]
// DeepSeek highly recommends using user instead of system role.

View file

@ -222,7 +222,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
content: systemPrompt,
}
const convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
const convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages, { mergeToolResultText: true })]
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: model.id,

View file

@ -140,7 +140,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
]
// Map extended efforts to OpenAI Chat Completions-accepted values (omit unsupported)

View file

@ -86,7 +86,7 @@ export class UnboundHandler extends RouterProvider implements SingleCompletionHa
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
]
if (info.supportsPromptCache) {

View file

@ -45,7 +45,7 @@ export class VercelAiGatewayHandler extends RouterProvider implements SingleComp
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
]
if (VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS.has(modelId) && info.supportsPromptCache) {

View file

@ -66,7 +66,7 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
temperature: this.options.modelTemperature ?? XAI_DEFAULT_TEMPERATURE,
messages: [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
...convertToOpenAiMessages(messages, { mergeToolResultText: true }),
] as OpenAI.Chat.ChatCompletionMessageParam[],
stream: true as const,
stream_options: { include_usage: true },