From 189e5d5a2002e6ec5ee2aa1f606f315537601a3d Mon Sep 17 00:00:00 2001 From: YuTengjing Date: Tue, 17 Mar 2026 22:27:59 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(model-runtime):=20prune=20un?= =?UTF-8?q?supported=20xAI=20reasoning=20penalties=20(#13066)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/providers/xai/index.test.ts | 52 +++++++++++++++++++ .../model-runtime/src/providers/xai/index.ts | 34 +++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/packages/model-runtime/src/providers/xai/index.test.ts b/packages/model-runtime/src/providers/xai/index.test.ts index 4b3fc6b513..57b4cd82e1 100644 --- a/packages/model-runtime/src/providers/xai/index.test.ts +++ b/packages/model-runtime/src/providers/xai/index.test.ts @@ -22,9 +22,61 @@ describe('LobeXAI - custom features', () => { beforeEach(() => { instance = new LobeXAI({ apiKey: 'test_api_key' }); + vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue( + new ReadableStream() as any, + ); vi.spyOn(instance['client'].responses, 'create').mockResolvedValue(new ReadableStream() as any); }); + describe('chatCompletion.handlePayload', () => { + it('should remove unsupported penalty parameters for reasoning models', async () => { + await instance.chat({ + apiMode: 'chatCompletion', + frequency_penalty: 0.4, + messages: [{ content: 'Hello', role: 'user' }], + model: 'grok-4', + presence_penalty: 0.6, + } as any); + + const createCall = (instance['client'].chat.completions.create as Mock).mock.calls[0][0]; + + expect(createCall.frequency_penalty).toBeUndefined(); + expect(createCall.presence_penalty).toBeUndefined(); + expect(createCall.stream).toBe(true); + }); + + it('should remove unsupported penalty parameters for grok-4.1 reasoning variants', async () => { + await instance.chat({ + apiMode: 'chatCompletion', + frequency_penalty: 0.4, + messages: [{ content: 'Hello', role: 'user' }], + model: 'grok-4-1-fast-reasoning', + presence_penalty: 0.6, + } as any); + + const createCall = (instance['client'].chat.completions.create as Mock).mock.calls[0][0]; + + expect(createCall.frequency_penalty).toBeUndefined(); + expect(createCall.presence_penalty).toBeUndefined(); + expect(createCall.stream).toBe(true); + }); + + it('should preserve penalty parameters for non-reasoning models', async () => { + await instance.chat({ + apiMode: 'chatCompletion', + frequency_penalty: 0.4, + messages: [{ content: 'Hello', role: 'user' }], + model: 'grok-4-fast-non-reasoning', + presence_penalty: 0.6, + } as any); + + const createCall = (instance['client'].chat.completions.create as Mock).mock.calls[0][0]; + + expect(createCall.frequency_penalty).toBe(0.4); + expect(createCall.presence_penalty).toBe(0.6); + }); + }); + describe('responses.handlePayload', () => { it('should add web_search and x_search tools when enabledSearch is true', async () => { await instance.chat({ diff --git a/packages/model-runtime/src/providers/xai/index.ts b/packages/model-runtime/src/providers/xai/index.ts index 8364967549..a536368474 100644 --- a/packages/model-runtime/src/providers/xai/index.ts +++ b/packages/model-runtime/src/providers/xai/index.ts @@ -1,6 +1,7 @@ -import { ModelProvider } from 'model-bank'; +import { LOBE_DEFAULT_MODEL_LIST, ModelProvider } from 'model-bank'; import { createOpenAICompatibleRuntime } from '../../core/openaiCompatibleFactory'; +import type { ChatStreamPayload } from '../../types'; import { MODEL_LIST_CONFIGS, processModelList } from '../../utils/modelParse'; import { createXAIImage } from './createImage'; @@ -8,9 +9,38 @@ export interface XAIModelCard { id: string; } +const xaiReasoningModels = new Set( + LOBE_DEFAULT_MODEL_LIST.filter( + (model) => + model.providerId === ModelProvider.XAI && + model.type === 'chat' && + !!model.abilities?.reasoning, + ).map((model) => model.id), +); + +const isXAIReasoningModel = (model: string) => xaiReasoningModels.has(model); + +const pruneUnsupportedReasoningParameters = (payload: ChatStreamPayload) => { + if (!isXAIReasoningModel(payload.model)) return payload; + + return { + ...payload, + // xAI reasoning models reject these parameters: + // https://docs.x.ai/developers/model-capabilities/text/reasoning + frequency_penalty: undefined, + presence_penalty: undefined, + stop: undefined, + } as ChatStreamPayload; +}; + export const LobeXAI = createOpenAICompatibleRuntime({ baseURL: 'https://api.x.ai/v1', chatCompletion: { + handlePayload: (payload) => + ({ + ...pruneUnsupportedReasoningParameters(payload), + stream: payload.stream ?? true, + }) as any, useResponse: true, }, createImage: createXAIImage, @@ -27,7 +57,7 @@ export const LobeXAI = createOpenAICompatibleRuntime({ provider: ModelProvider.XAI, responses: { handlePayload: (payload) => { - const { enabledSearch, tools, ...rest } = payload; + const { enabledSearch, tools, ...rest } = pruneUnsupportedReasoningParameters(payload); const xaiTools = enabledSearch ? [...(tools || []), { type: 'web_search' }, { type: 'x_search' }]