@lobehub/lobehub 2.0.0-next.82 → 2.0.0-next.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/changelog/v1.json +21 -0
- package/docs/usage/providers/comfyui.mdx +1 -1
- package/docs/usage/providers/comfyui.zh-CN.mdx +1 -1
- package/locales/ar/error.json +2 -2
- package/locales/ar/modelProvider.json +1 -1
- package/locales/ar/models.json +7 -1
- package/locales/bg-BG/error.json +2 -2
- package/locales/bg-BG/modelProvider.json +1 -1
- package/locales/bg-BG/models.json +7 -1
- package/locales/de-DE/error.json +2 -2
- package/locales/de-DE/modelProvider.json +1 -1
- package/locales/de-DE/models.json +7 -1
- package/locales/en-US/error.json +2 -2
- package/locales/en-US/modelProvider.json +1 -1
- package/locales/en-US/models.json +7 -1
- package/locales/es-ES/error.json +2 -2
- package/locales/es-ES/modelProvider.json +1 -1
- package/locales/es-ES/models.json +7 -1
- package/locales/fa-IR/error.json +2 -2
- package/locales/fa-IR/modelProvider.json +1 -1
- package/locales/fa-IR/models.json +7 -1
- package/locales/fr-FR/error.json +2 -2
- package/locales/fr-FR/modelProvider.json +1 -1
- package/locales/fr-FR/models.json +7 -1
- package/locales/it-IT/error.json +2 -2
- package/locales/it-IT/modelProvider.json +1 -1
- package/locales/it-IT/models.json +7 -1
- package/locales/ja-JP/error.json +2 -2
- package/locales/ja-JP/modelProvider.json +1 -1
- package/locales/ja-JP/models.json +7 -1
- package/locales/ko-KR/error.json +2 -2
- package/locales/ko-KR/modelProvider.json +1 -1
- package/locales/ko-KR/models.json +7 -1
- package/locales/nl-NL/error.json +2 -2
- package/locales/nl-NL/modelProvider.json +1 -1
- package/locales/nl-NL/models.json +7 -1
- package/locales/pl-PL/error.json +2 -2
- package/locales/pl-PL/modelProvider.json +1 -1
- package/locales/pl-PL/models.json +7 -1
- package/locales/pt-BR/error.json +2 -2
- package/locales/pt-BR/modelProvider.json +1 -1
- package/locales/pt-BR/models.json +7 -1
- package/locales/ru-RU/error.json +2 -2
- package/locales/ru-RU/modelProvider.json +1 -1
- package/locales/ru-RU/models.json +7 -1
- package/locales/tr-TR/error.json +2 -2
- package/locales/tr-TR/modelProvider.json +1 -1
- package/locales/tr-TR/models.json +7 -1
- package/locales/vi-VN/error.json +2 -2
- package/locales/vi-VN/modelProvider.json +1 -1
- package/locales/vi-VN/models.json +7 -1
- package/locales/zh-CN/error.json +2 -2
- package/locales/zh-CN/modelProvider.json +1 -1
- package/locales/zh-CN/models.json +7 -1
- package/locales/zh-TW/error.json +2 -2
- package/locales/zh-TW/modelProvider.json +1 -1
- package/locales/zh-TW/models.json +7 -1
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/google.ts +54 -0
- package/packages/model-bank/src/aiModels/novita.ts +3 -2
- package/packages/model-bank/src/aiModels/nvidia.ts +14 -0
- package/packages/model-bank/src/aiModels/ollamacloud.ts +23 -2
- package/packages/model-bank/src/aiModels/qwen.ts +88 -0
- package/packages/model-bank/src/aiModels/siliconcloud.ts +20 -0
- package/packages/model-bank/src/aiModels/vercelaigateway.ts +0 -17
- package/packages/model-bank/src/aiModels/volcengine.ts +1 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +108 -64
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +150 -125
- package/packages/model-runtime/src/providers/newapi/index.test.ts +3 -75
- package/packages/model-runtime/src/providers/newapi/index.ts +1 -14
- package/packages/model-runtime/src/providers/openrouter/index.test.ts +3 -2
- package/packages/model-runtime/src/providers/openrouter/index.ts +1 -1
- package/src/app/[variants]/(main)/settings/provider/features/CreateNewProvider/index.tsx +19 -6
- package/src/app/[variants]/(main)/settings/provider/features/customProviderSdkOptions.ts +1 -0
- package/src/config/modelProviders/aihubmix.ts +1 -0
- package/src/config/modelProviders/newapi.ts +1 -0
- package/src/locales/default/modelProvider.ts +1 -1
|
@@ -1007,78 +1007,122 @@ describe('LobeOpenAICompatibleFactory', () => {
|
|
|
1007
1007
|
});
|
|
1008
1008
|
|
|
1009
1009
|
describe('responses routing', () => {
|
|
1010
|
-
it(
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1010
|
+
it(
|
|
1011
|
+
'should route to Responses API when chatCompletion.useResponse is true',
|
|
1012
|
+
async () => {
|
|
1013
|
+
const LobeMockProviderUseResponses = createOpenAICompatibleRuntime({
|
|
1014
|
+
baseURL: 'https://api.test.com/v1',
|
|
1015
|
+
chatCompletion: {
|
|
1016
|
+
useResponse: true,
|
|
1017
|
+
},
|
|
1018
|
+
provider: ModelProvider.OpenAI,
|
|
1019
|
+
});
|
|
1018
1020
|
|
|
1019
|
-
|
|
1021
|
+
const inst = new LobeMockProviderUseResponses({ apiKey: 'test' });
|
|
1020
1022
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1023
|
+
// Mock responses.create to return a proper stream-like object
|
|
1024
|
+
const mockResponsesCreate = vi
|
|
1025
|
+
.spyOn(inst['client'].responses, 'create')
|
|
1026
|
+
.mockResolvedValue({
|
|
1027
|
+
toReadableStream: () =>
|
|
1028
|
+
new ReadableStream({
|
|
1029
|
+
start(controller) {
|
|
1030
|
+
controller.close();
|
|
1031
|
+
},
|
|
1032
|
+
}),
|
|
1033
|
+
} as any);
|
|
1027
1034
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
});
|
|
1035
|
+
// Mock getModelPricing to prevent async issues
|
|
1036
|
+
vi.mock('../../utils/model', () => ({
|
|
1037
|
+
getModelPricing: vi.fn().mockResolvedValue({}),
|
|
1038
|
+
}));
|
|
1033
1039
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1040
|
+
try {
|
|
1041
|
+
await inst.chat({
|
|
1042
|
+
messages: [{ content: 'hi', role: 'user' }],
|
|
1043
|
+
model: 'any-model',
|
|
1044
|
+
temperature: 0,
|
|
1045
|
+
});
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
// Catch errors from incomplete mocking, we only care that responses.create was called
|
|
1048
|
+
}
|
|
1036
1049
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
useResponseModels: ['special-model', /special-\w+/],
|
|
1042
|
-
},
|
|
1043
|
-
provider: ModelProvider.OpenAI,
|
|
1044
|
-
});
|
|
1045
|
-
const inst = new LobeMockProviderUseResponseModels({ apiKey: 'test' });
|
|
1046
|
-
const spy = vi.spyOn(inst['client'].responses, 'create');
|
|
1047
|
-
// Prevent hanging by mocking normal chat completion stream
|
|
1048
|
-
vi.spyOn(inst['client'].chat.completions, 'create').mockResolvedValue(
|
|
1049
|
-
new ReadableStream() as any,
|
|
1050
|
-
);
|
|
1050
|
+
expect(mockResponsesCreate).toHaveBeenCalled();
|
|
1051
|
+
},
|
|
1052
|
+
{ timeout: 10000 },
|
|
1053
|
+
);
|
|
1051
1054
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1055
|
+
it(
|
|
1056
|
+
'should route to Responses API when model matches useResponseModels',
|
|
1057
|
+
async () => {
|
|
1058
|
+
const LobeMockProviderUseResponseModels = createOpenAICompatibleRuntime({
|
|
1059
|
+
baseURL: 'https://api.test.com/v1',
|
|
1060
|
+
chatCompletion: {
|
|
1061
|
+
useResponseModels: ['special-model', /special-\w+/],
|
|
1062
|
+
},
|
|
1063
|
+
provider: ModelProvider.OpenAI,
|
|
1064
|
+
});
|
|
1065
|
+
const inst = new LobeMockProviderUseResponseModels({ apiKey: 'test' });
|
|
1066
|
+
const spy = vi.spyOn(inst['client'].responses, 'create');
|
|
1067
|
+
// Prevent hanging by mocking normal chat completion stream
|
|
1068
|
+
vi.spyOn(inst['client'].chat.completions, 'create').mockResolvedValue(
|
|
1069
|
+
new ReadableStream() as any,
|
|
1070
|
+
);
|
|
1062
1071
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1072
|
+
// First invocation: model contains the string
|
|
1073
|
+
spy.mockResolvedValueOnce({
|
|
1074
|
+
toReadableStream: () =>
|
|
1075
|
+
new ReadableStream({
|
|
1076
|
+
start(controller) {
|
|
1077
|
+
controller.close();
|
|
1078
|
+
},
|
|
1079
|
+
}),
|
|
1080
|
+
} as any);
|
|
1081
|
+
try {
|
|
1082
|
+
await inst.chat({
|
|
1083
|
+
messages: [{ content: 'hi', role: 'user' }],
|
|
1084
|
+
model: 'prefix-special-model-suffix',
|
|
1085
|
+
temperature: 0,
|
|
1086
|
+
});
|
|
1087
|
+
} catch (e) {
|
|
1088
|
+
// Catch errors from incomplete mocking
|
|
1089
|
+
}
|
|
1090
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
1073
1091
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1092
|
+
// Second invocation: model matches the RegExp
|
|
1093
|
+
spy.mockResolvedValueOnce({
|
|
1094
|
+
toReadableStream: () =>
|
|
1095
|
+
new ReadableStream({
|
|
1096
|
+
start(controller) {
|
|
1097
|
+
controller.close();
|
|
1098
|
+
},
|
|
1099
|
+
}),
|
|
1100
|
+
} as any);
|
|
1101
|
+
try {
|
|
1102
|
+
await inst.chat({
|
|
1103
|
+
messages: [{ content: 'hi', role: 'user' }],
|
|
1104
|
+
model: 'special-xyz',
|
|
1105
|
+
temperature: 0,
|
|
1106
|
+
});
|
|
1107
|
+
} catch (e) {
|
|
1108
|
+
// Catch errors from incomplete mocking
|
|
1109
|
+
}
|
|
1110
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
1111
|
+
|
|
1112
|
+
// Third invocation: model does not match any useResponseModels patterns
|
|
1113
|
+
try {
|
|
1114
|
+
await inst.chat({
|
|
1115
|
+
messages: [{ content: 'hi', role: 'user' }],
|
|
1116
|
+
model: 'unrelated-model',
|
|
1117
|
+
temperature: 0,
|
|
1118
|
+
});
|
|
1119
|
+
} catch (e) {
|
|
1120
|
+
// Catch errors
|
|
1121
|
+
}
|
|
1122
|
+
expect(spy).toHaveBeenCalledTimes(2); // Ensure no additional calls were made
|
|
1123
|
+
},
|
|
1124
|
+
{ timeout: 10000 },
|
|
1125
|
+
);
|
|
1082
1126
|
});
|
|
1083
1127
|
|
|
1084
1128
|
describe('DEBUG', () => {
|
|
@@ -138,10 +138,10 @@ export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> =
|
|
|
138
138
|
useToolsCalling?: boolean;
|
|
139
139
|
};
|
|
140
140
|
models?:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
| ((params: { client: OpenAI }) => Promise<ChatModelCard[]>)
|
|
142
|
+
| {
|
|
143
|
+
transformModel?: (model: OpenAI.Model) => ChatModelCard;
|
|
144
|
+
};
|
|
145
145
|
provider: string;
|
|
146
146
|
responses?: {
|
|
147
147
|
handlePayload?: (
|
|
@@ -205,6 +205,81 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
205
205
|
this.logPrefix = `lobe-model-runtime:${this.id}`;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Determine if should use Responses API based on various configuration options
|
|
210
|
+
* @param params - Configuration parameters
|
|
211
|
+
* @returns true if should use Responses API, false otherwise
|
|
212
|
+
*/
|
|
213
|
+
private shouldUseResponsesAPI(params: {
|
|
214
|
+
/** Context for logging (e.g., 'chat', 'generateObject', 'tool calling') */
|
|
215
|
+
context?: string;
|
|
216
|
+
/** Factory/instance level useResponse flag */
|
|
217
|
+
flagUseResponse?: boolean;
|
|
218
|
+
/** Factory/instance level model patterns for Responses API */
|
|
219
|
+
flagUseResponseModels?: Array<string | RegExp>;
|
|
220
|
+
/** The model ID to check */
|
|
221
|
+
model?: string;
|
|
222
|
+
/** Explicit responseApi flag */
|
|
223
|
+
responseApi?: boolean;
|
|
224
|
+
/** User-specified API mode (highest priority) */
|
|
225
|
+
userApiMode?: string;
|
|
226
|
+
}): boolean {
|
|
227
|
+
const {
|
|
228
|
+
model,
|
|
229
|
+
userApiMode,
|
|
230
|
+
responseApi,
|
|
231
|
+
flagUseResponse,
|
|
232
|
+
flagUseResponseModels,
|
|
233
|
+
context = 'operation',
|
|
234
|
+
} = params;
|
|
235
|
+
|
|
236
|
+
const log = debug(`${this.logPrefix}:shouldUseResponsesAPI`);
|
|
237
|
+
|
|
238
|
+
// Priority 1: User explicitly set apiMode via switch
|
|
239
|
+
if (userApiMode === 'responses') {
|
|
240
|
+
log('using Responses API: explicit userApiMode=%s', userApiMode);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Priority 2: userApiMode is explicitly set to something else
|
|
245
|
+
if (userApiMode !== undefined) {
|
|
246
|
+
log('using Chat Completions API: userApiMode=%s', userApiMode);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Priority 3: Explicit responseApi flag
|
|
251
|
+
if (responseApi) {
|
|
252
|
+
log('using Responses API: explicit responseApi flag for %s', context);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Priority 4: Factory/instance level useResponse flag
|
|
257
|
+
if (flagUseResponse) {
|
|
258
|
+
log('using Responses API: flagUseResponse=true for %s', context);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Priority 5: Check if model matches useResponseModels patterns
|
|
263
|
+
if (model && flagUseResponseModels?.length) {
|
|
264
|
+
const matches = flagUseResponseModels.some((m: string | RegExp) =>
|
|
265
|
+
typeof m === 'string' ? model.includes(m) : (m as RegExp).test(model),
|
|
266
|
+
);
|
|
267
|
+
if (matches) {
|
|
268
|
+
log('using Responses API: model %s matches useResponseModels config', model);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Priority 6: Check built-in responsesAPIModels
|
|
274
|
+
if (model && responsesAPIModels.has(model)) {
|
|
275
|
+
log('using Responses API: model %s in built-in responsesAPIModels', model);
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
log('using Chat Completions API for %s', context);
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
208
283
|
async chat({ responseMode, ...payload }: ChatStreamPayload, options?: ChatMethodOptions) {
|
|
209
284
|
try {
|
|
210
285
|
const log = debug(`${this.logPrefix}:chat`);
|
|
@@ -212,41 +287,39 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
212
287
|
|
|
213
288
|
log('chat called with model: %s, stream: %s', payload.model, payload.stream ?? true);
|
|
214
289
|
|
|
215
|
-
|
|
290
|
+
let processedPayload: any = payload;
|
|
291
|
+
const userApiMode = (payload as any).apiMode as string | undefined;
|
|
216
292
|
const modelId = (payload as any).model as string | undefined;
|
|
217
|
-
const shouldUseResponses = (() => {
|
|
218
|
-
const instanceChat = ((this._options as any).chatCompletion || {}) as {
|
|
219
|
-
useResponse?: boolean;
|
|
220
|
-
useResponseModels?: Array<string | RegExp>;
|
|
221
|
-
};
|
|
222
|
-
const flagUseResponse =
|
|
223
|
-
instanceChat.useResponse ?? (chatCompletion ? chatCompletion.useResponse : undefined);
|
|
224
|
-
const flagUseResponseModels =
|
|
225
|
-
instanceChat.useResponseModels ?? chatCompletion?.useResponseModels;
|
|
226
|
-
|
|
227
|
-
if (!chatCompletion && !instanceChat) return false;
|
|
228
|
-
if (flagUseResponse) return true;
|
|
229
|
-
if (!modelId || !flagUseResponseModels?.length) return false;
|
|
230
|
-
return flagUseResponseModels.some((m: string | RegExp) =>
|
|
231
|
-
typeof m === 'string' ? modelId.includes(m) : (m as RegExp).test(modelId),
|
|
232
|
-
);
|
|
233
|
-
})();
|
|
234
293
|
|
|
235
|
-
|
|
294
|
+
const instanceChat = ((this._options as any).chatCompletion || {}) as {
|
|
295
|
+
useResponse?: boolean;
|
|
296
|
+
useResponseModels?: Array<string | RegExp>;
|
|
297
|
+
};
|
|
298
|
+
const flagUseResponse =
|
|
299
|
+
instanceChat.useResponse ?? (chatCompletion ? chatCompletion.useResponse : undefined);
|
|
300
|
+
const flagUseResponseModels =
|
|
301
|
+
instanceChat.useResponseModels ?? chatCompletion?.useResponseModels;
|
|
302
|
+
|
|
303
|
+
// Determine if should use Responses API
|
|
304
|
+
const shouldUseResponses = this.shouldUseResponsesAPI({
|
|
305
|
+
context: 'chat',
|
|
306
|
+
flagUseResponse,
|
|
307
|
+
flagUseResponseModels,
|
|
308
|
+
model: modelId,
|
|
309
|
+
userApiMode,
|
|
310
|
+
});
|
|
311
|
+
|
|
236
312
|
if (shouldUseResponses) {
|
|
237
|
-
log('using Responses API mode');
|
|
238
313
|
processedPayload = { ...payload, apiMode: 'responses' } as any;
|
|
239
|
-
} else {
|
|
240
|
-
log('using Chat Completions API mode');
|
|
241
314
|
}
|
|
242
315
|
|
|
243
316
|
// 再进行工厂级处理
|
|
244
317
|
const postPayload = chatCompletion?.handlePayload
|
|
245
318
|
? chatCompletion.handlePayload(processedPayload, this._options)
|
|
246
319
|
: ({
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
320
|
+
...processedPayload,
|
|
321
|
+
stream: processedPayload.stream ?? true,
|
|
322
|
+
} as OpenAI.ChatCompletionCreateParamsStreaming);
|
|
250
323
|
|
|
251
324
|
if ((postPayload as any).apiMode === 'responses') {
|
|
252
325
|
return this.handleResponseAPIMode(processedPayload, options);
|
|
@@ -312,13 +385,13 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
312
385
|
return StreamingResponse(
|
|
313
386
|
chatCompletion?.handleStream
|
|
314
387
|
? chatCompletion.handleStream(prod, {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
388
|
+
callbacks: streamOptions.callbacks,
|
|
389
|
+
inputStartAt,
|
|
390
|
+
})
|
|
318
391
|
: OpenAIStream(prod, {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
392
|
+
...streamOptions,
|
|
393
|
+
inputStartAt,
|
|
394
|
+
}),
|
|
322
395
|
{
|
|
323
396
|
headers: options?.headers,
|
|
324
397
|
},
|
|
@@ -342,9 +415,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
342
415
|
return StreamingResponse(
|
|
343
416
|
chatCompletion?.handleStream
|
|
344
417
|
? chatCompletion.handleStream(stream, {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
418
|
+
callbacks: streamOptions.callbacks,
|
|
419
|
+
inputStartAt,
|
|
420
|
+
})
|
|
348
421
|
: OpenAIStream(stream, { ...streamOptions, enableStreaming: false, inputStartAt }),
|
|
349
422
|
{
|
|
350
423
|
headers: options?.headers,
|
|
@@ -500,47 +573,23 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
500
573
|
}
|
|
501
574
|
|
|
502
575
|
// Factory-level Responses API routing control (supports instance override)
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
log('using Responses API due to useResponse flag');
|
|
521
|
-
return true;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Use factory-configured model list if provided
|
|
525
|
-
if (model && flagUseResponseModels?.length) {
|
|
526
|
-
const matches = flagUseResponseModels.some((m: string | RegExp) =>
|
|
527
|
-
typeof m === 'string' ? model.includes(m) : (m as RegExp).test(model),
|
|
528
|
-
);
|
|
529
|
-
if (matches) {
|
|
530
|
-
log('using Responses API: model %s matches useResponseModels config', model);
|
|
531
|
-
return true;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Default: use built-in responsesAPIModels
|
|
536
|
-
if (model && responsesAPIModels.has(model)) {
|
|
537
|
-
log('using Responses API: model %s in built-in responsesAPIModels', model);
|
|
538
|
-
return true;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
log('using Chat Completions API for generateObject');
|
|
542
|
-
return false;
|
|
543
|
-
})();
|
|
576
|
+
const instanceGenerateObject = ((this._options as any).generateObject || {}) as {
|
|
577
|
+
useResponse?: boolean;
|
|
578
|
+
useResponseModels?: Array<string | RegExp>;
|
|
579
|
+
};
|
|
580
|
+
const flagUseResponse =
|
|
581
|
+
instanceGenerateObject.useResponse ??
|
|
582
|
+
(generateObjectConfig ? generateObjectConfig.useResponse : undefined);
|
|
583
|
+
const flagUseResponseModels =
|
|
584
|
+
instanceGenerateObject.useResponseModels ?? generateObjectConfig?.useResponseModels;
|
|
585
|
+
|
|
586
|
+
const shouldUseResponses = this.shouldUseResponsesAPI({
|
|
587
|
+
context: 'generateObject',
|
|
588
|
+
flagUseResponse,
|
|
589
|
+
flagUseResponseModels,
|
|
590
|
+
model,
|
|
591
|
+
responseApi,
|
|
592
|
+
});
|
|
544
593
|
|
|
545
594
|
// Apply schema transformation if configured
|
|
546
595
|
const processedSchema = generateObjectConfig?.handleSchema
|
|
@@ -790,11 +839,11 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
790
839
|
...res,
|
|
791
840
|
...(reasoning || reasoning_effort
|
|
792
841
|
? {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
842
|
+
reasoning: {
|
|
843
|
+
...reasoning,
|
|
844
|
+
...(reasoning_effort && { effort: reasoning_effort }),
|
|
845
|
+
},
|
|
846
|
+
}
|
|
798
847
|
: {}),
|
|
799
848
|
input,
|
|
800
849
|
...(max_tokens && { max_output_tokens: max_tokens }),
|
|
@@ -885,47 +934,23 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
885
934
|
);
|
|
886
935
|
|
|
887
936
|
// Factory-level Responses API routing control (supports instance override)
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
log('using Responses API due to useResponse flag');
|
|
906
|
-
return true;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// Use factory-configured model list if provided
|
|
910
|
-
if (model && flagUseResponseModels?.length) {
|
|
911
|
-
const matches = flagUseResponseModels.some((m: string | RegExp) =>
|
|
912
|
-
typeof m === 'string' ? model.includes(m) : (m as RegExp).test(model),
|
|
913
|
-
);
|
|
914
|
-
if (matches) {
|
|
915
|
-
log('using Responses API: model %s matches useResponseModels config', model);
|
|
916
|
-
return true;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// Default: use built-in responsesAPIModels
|
|
921
|
-
if (model && responsesAPIModels.has(model)) {
|
|
922
|
-
log('using Responses API: model %s in built-in responsesAPIModels', model);
|
|
923
|
-
return true;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
log('using Chat Completions API for tool calling');
|
|
927
|
-
return false;
|
|
928
|
-
})();
|
|
937
|
+
const instanceGenerateObject = ((this._options as any).generateObject || {}) as {
|
|
938
|
+
useResponse?: boolean;
|
|
939
|
+
useResponseModels?: Array<string | RegExp>;
|
|
940
|
+
};
|
|
941
|
+
const flagUseResponse =
|
|
942
|
+
instanceGenerateObject.useResponse ??
|
|
943
|
+
(generateObjectConfig ? generateObjectConfig.useResponse : undefined);
|
|
944
|
+
const flagUseResponseModels =
|
|
945
|
+
instanceGenerateObject.useResponseModels ?? generateObjectConfig?.useResponseModels;
|
|
946
|
+
|
|
947
|
+
const shouldUseResponses = this.shouldUseResponsesAPI({
|
|
948
|
+
context: 'tool calling',
|
|
949
|
+
flagUseResponse,
|
|
950
|
+
flagUseResponseModels,
|
|
951
|
+
model,
|
|
952
|
+
responseApi,
|
|
953
|
+
});
|
|
929
954
|
|
|
930
955
|
if (shouldUseResponses) {
|
|
931
956
|
log('calling responses.create for tool calling');
|
|
@@ -5,7 +5,7 @@ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
5
5
|
import { responsesAPIModels } from '../../const/models';
|
|
6
6
|
import { ChatStreamPayload } from '../../types/chat';
|
|
7
7
|
import * as modelParseModule from '../../utils/modelParse';
|
|
8
|
-
import { LobeNewAPIAI, NewAPIModelCard, NewAPIPricing,
|
|
8
|
+
import { LobeNewAPIAI, NewAPIModelCard, NewAPIPricing, params } from './index';
|
|
9
9
|
|
|
10
10
|
// Mock external dependencies
|
|
11
11
|
vi.mock('../../utils/modelParse');
|
|
@@ -701,78 +701,6 @@ describe('NewAPI Runtime - 100% Branch Coverage', () => {
|
|
|
701
701
|
});
|
|
702
702
|
});
|
|
703
703
|
|
|
704
|
-
describe('HandlePayload Function - Direct Testing', () => {
|
|
705
|
-
beforeEach(() => {
|
|
706
|
-
// Mock responsesAPIModels as a Set for testing
|
|
707
|
-
(responsesAPIModels as any).has = vi.fn((model: string) => model === 'o1-pro');
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
it('should add apiMode for models in responsesAPIModels set', () => {
|
|
711
|
-
(responsesAPIModels as any).has = vi.fn((model: string) => model === 'o1-pro');
|
|
712
|
-
|
|
713
|
-
const payload: ChatStreamPayload = {
|
|
714
|
-
model: 'o1-pro',
|
|
715
|
-
messages: [{ role: 'user', content: 'test' }],
|
|
716
|
-
temperature: 0.5,
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
const result = handlePayload(payload);
|
|
720
|
-
expect(result).toEqual({ ...payload, apiMode: 'responses' });
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
it('should add apiMode for gpt- models', () => {
|
|
724
|
-
(responsesAPIModels as any).has = vi.fn(() => false);
|
|
725
|
-
|
|
726
|
-
const payload: ChatStreamPayload = {
|
|
727
|
-
model: 'gpt-4o',
|
|
728
|
-
messages: [{ role: 'user', content: 'test' }],
|
|
729
|
-
temperature: 0.5,
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
const result = handlePayload(payload);
|
|
733
|
-
expect(result).toEqual({ ...payload, apiMode: 'responses' });
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
it('should add apiMode for o1 models', () => {
|
|
737
|
-
(responsesAPIModels as any).has = vi.fn(() => false);
|
|
738
|
-
|
|
739
|
-
const payload: ChatStreamPayload = {
|
|
740
|
-
model: 'o1-mini',
|
|
741
|
-
messages: [{ role: 'user', content: 'test' }],
|
|
742
|
-
temperature: 0.5,
|
|
743
|
-
};
|
|
744
|
-
|
|
745
|
-
const result = handlePayload(payload);
|
|
746
|
-
expect(result).toEqual({ ...payload, apiMode: 'responses' });
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
it('should add apiMode for o3 models', () => {
|
|
750
|
-
(responsesAPIModels as any).has = vi.fn(() => false);
|
|
751
|
-
|
|
752
|
-
const payload: ChatStreamPayload = {
|
|
753
|
-
model: 'o3-turbo',
|
|
754
|
-
messages: [{ role: 'user', content: 'test' }],
|
|
755
|
-
temperature: 0.5,
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
const result = handlePayload(payload);
|
|
759
|
-
expect(result).toEqual({ ...payload, apiMode: 'responses' });
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
it('should not modify payload for regular models', () => {
|
|
763
|
-
(responsesAPIModels as any).has = vi.fn(() => false);
|
|
764
|
-
|
|
765
|
-
const payload: ChatStreamPayload = {
|
|
766
|
-
model: 'claude-3-sonnet',
|
|
767
|
-
messages: [{ role: 'user', content: 'test' }],
|
|
768
|
-
temperature: 0.5,
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
const result = handlePayload(payload);
|
|
772
|
-
expect(result).toEqual(payload);
|
|
773
|
-
});
|
|
774
|
-
});
|
|
775
|
-
|
|
776
704
|
describe('Routers Function - Direct Testing', () => {
|
|
777
705
|
it('should generate routers with correct apiTypes', () => {
|
|
778
706
|
const options = { apiKey: 'test', baseURL: 'https://api.newapi.com/v1' };
|
|
@@ -823,11 +751,11 @@ describe('NewAPI Runtime - 100% Branch Coverage', () => {
|
|
|
823
751
|
expect(routers[3].options.baseURL).toBe('https://custom.com/v1');
|
|
824
752
|
});
|
|
825
753
|
|
|
826
|
-
it('should configure openai router with
|
|
754
|
+
it('should configure openai router with useResponseModels', () => {
|
|
827
755
|
const options = { apiKey: 'test', baseURL: 'https://custom.com/v1' };
|
|
828
756
|
const routers = params.routers(options);
|
|
829
757
|
|
|
830
|
-
expect((routers[3].options as any).chatCompletion?.
|
|
758
|
+
expect((routers[3].options as any).chatCompletion?.useResponseModels).toBeDefined();
|
|
831
759
|
});
|
|
832
760
|
|
|
833
761
|
it('should filter anthropic models for anthropic router', () => {
|
|
@@ -4,7 +4,6 @@ import urlJoin from 'url-join';
|
|
|
4
4
|
import { responsesAPIModels } from '../../const/models';
|
|
5
5
|
import { createRouterRuntime } from '../../core/RouterRuntime';
|
|
6
6
|
import { CreateRouterRuntimeOptions } from '../../core/RouterRuntime/createRuntime';
|
|
7
|
-
import { ChatStreamPayload } from '../../types/chat';
|
|
8
7
|
import { detectModelProvider, processMultiProviderModelList } from '../../utils/modelParse';
|
|
9
8
|
|
|
10
9
|
export interface NewAPIModelCard {
|
|
@@ -26,18 +25,6 @@ export interface NewAPIPricing {
|
|
|
26
25
|
supported_endpoint_types?: string[];
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
export const handlePayload = (payload: ChatStreamPayload) => {
|
|
30
|
-
// Handle OpenAI responses API mode
|
|
31
|
-
if (
|
|
32
|
-
responsesAPIModels.has(payload.model) ||
|
|
33
|
-
payload.model.includes('gpt-') ||
|
|
34
|
-
/^o\d/.test(payload.model)
|
|
35
|
-
) {
|
|
36
|
-
return { ...payload, apiMode: 'responses' };
|
|
37
|
-
}
|
|
38
|
-
return payload;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
28
|
export const params = {
|
|
42
29
|
debug: {
|
|
43
30
|
chatCompletion: () => process.env.DEBUG_NEWAPI_CHAT_COMPLETION === '1',
|
|
@@ -178,7 +165,7 @@ export const params = {
|
|
|
178
165
|
...options,
|
|
179
166
|
baseURL: urlJoin(userBaseURL, '/v1'),
|
|
180
167
|
chatCompletion: {
|
|
181
|
-
|
|
168
|
+
useResponseModels: [...Array.from(responsesAPIModels), /gpt-\d(?!\d)/, /^o\d/],
|
|
182
169
|
},
|
|
183
170
|
},
|
|
184
171
|
},
|