🐛 fix(model-runtime): prune unsupported xAI reasoning penalties (#13066)

This commit is contained in:
YuTengjing
2026-03-17 22:27:59 +08:00
committed by GitHub
parent b2122a5224
commit 189e5d5a20
2 changed files with 84 additions and 2 deletions

View File

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

View File

@@ -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' }]