浏览代码

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

Hannes Rudolph 6 天之前
父节点
当前提交
ded6486a46

+ 7 - 1
src/api/providers/base-openai-compatible-provider.ts

@@ -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) }),

+ 1 - 1
src/api/providers/cerebras.ts

@@ -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> = {

+ 4 - 1
src/api/providers/chutes.ts

@@ -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 }),

+ 4 - 1
src/api/providers/deepinfra.ts

@@ -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,

+ 4 - 1
src/api/providers/featherless.ts

@@ -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 },
 		}

+ 4 - 1
src/api/providers/huggingface.ts

@@ -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 },
 		}

+ 1 - 1
src/api/providers/lite-llm.ts

@@ -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

+ 1 - 1
src/api/providers/lm-studio.ts

@@ -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)

+ 4 - 4
src/api/providers/openai.ts

@@ -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,

+ 6 - 4
src/api/providers/openrouter.ts

@@ -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.

+ 1 - 1
src/api/providers/qwen-code.ts

@@ -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,

+ 1 - 1
src/api/providers/requesty.ts

@@ -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)

+ 1 - 1
src/api/providers/unbound.ts

@@ -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) {

+ 1 - 1
src/api/providers/vercel-ai-gateway.ts

@@ -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) {

+ 1 - 1
src/api/providers/xai.ts

@@ -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 },