fix: one-shot and thinking models can conflict
Some checks failed
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Some checks failed
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
This commit is contained in:
@@ -130,7 +130,7 @@ export class ProviderRegistry {
|
|||||||
private genericOpenAIApiKey = '';
|
private genericOpenAIApiKey = '';
|
||||||
private genericOpenAIProvider: ReturnType<typeof createOpenAI> | null = null;
|
private genericOpenAIProvider: ReturnType<typeof createOpenAI> | null = null;
|
||||||
private genericOpenAIModelIds = new Set<string>();
|
private genericOpenAIModelIds = new Set<string>();
|
||||||
private genericOpenAICapabilities = new Map<string, { tools: boolean; vision: boolean }>();
|
private genericOpenAICapabilities = new Map<string, { tools: boolean; vision: boolean; disableThinking: boolean }>();
|
||||||
private modelCatalogEngine = new ModelCatalogEngine();
|
private modelCatalogEngine = new ModelCatalogEngine();
|
||||||
private _offlineMode = false;
|
private _offlineMode = false;
|
||||||
|
|
||||||
@@ -353,19 +353,23 @@ export class ProviderRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get capability overrides for a specific generic OpenAI model. */
|
/** Get capability overrides for a specific generic OpenAI model. */
|
||||||
getGenericOpenAIModelCapabilities(modelId: string): { tools: boolean; vision: boolean } {
|
getGenericOpenAIModelCapabilities(modelId: string): { tools: boolean; vision: boolean; disableThinking: boolean } {
|
||||||
return this.genericOpenAICapabilities.get(modelId) ?? { tools: false, vision: false };
|
return this.genericOpenAICapabilities.get(modelId) ?? { tools: false, vision: false, disableThinking: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set capability overrides for a specific generic OpenAI model. */
|
/** Set capability overrides for a specific generic OpenAI model. */
|
||||||
setGenericOpenAIModelCapabilities(modelId: string, caps: { tools: boolean; vision: boolean }): void {
|
setGenericOpenAIModelCapabilities(modelId: string, caps: { tools: boolean; vision: boolean; disableThinking?: boolean }): void {
|
||||||
this.genericOpenAICapabilities.set(modelId, caps);
|
this.genericOpenAICapabilities.set(modelId, {
|
||||||
|
tools: caps.tools,
|
||||||
|
vision: caps.vision,
|
||||||
|
disableThinking: caps.disableThinking ?? false,
|
||||||
|
});
|
||||||
this.invalidateModelCache();
|
this.invalidateModelCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get all stored generic OpenAI capability overrides. */
|
/** Get all stored generic OpenAI capability overrides. */
|
||||||
getAllGenericOpenAIModelCapabilities(): Record<string, { tools: boolean; vision: boolean }> {
|
getAllGenericOpenAIModelCapabilities(): Record<string, { tools: boolean; vision: boolean; disableThinking: boolean }> {
|
||||||
const result: Record<string, { tools: boolean; vision: boolean }> = {};
|
const result: Record<string, { tools: boolean; vision: boolean; disableThinking: boolean }> = {};
|
||||||
for (const [id, caps] of this.genericOpenAICapabilities) {
|
for (const [id, caps] of this.genericOpenAICapabilities) {
|
||||||
result[id] = caps;
|
result[id] = caps;
|
||||||
}
|
}
|
||||||
@@ -373,10 +377,14 @@ export class ProviderRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Load generic OpenAI capability overrides from serialized object. */
|
/** Load generic OpenAI capability overrides from serialized object. */
|
||||||
loadGenericOpenAIModelCapabilities(data: Record<string, { tools: boolean; vision: boolean }>): void {
|
loadGenericOpenAIModelCapabilities(data: Record<string, { tools: boolean; vision: boolean; disableThinking?: boolean }>): void {
|
||||||
this.genericOpenAICapabilities.clear();
|
this.genericOpenAICapabilities.clear();
|
||||||
for (const [id, caps] of Object.entries(data)) {
|
for (const [id, caps] of Object.entries(data)) {
|
||||||
this.genericOpenAICapabilities.set(id, caps);
|
this.genericOpenAICapabilities.set(id, {
|
||||||
|
tools: caps.tools,
|
||||||
|
vision: caps.vision,
|
||||||
|
disableThinking: caps.disableThinking ?? false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,6 +398,11 @@ export class ProviderRegistry {
|
|||||||
return this.genericOpenAICapabilities.get(modelId)?.vision ?? false;
|
return this.genericOpenAICapabilities.get(modelId)?.vision ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check whether a generic OpenAI model should disable reasoning/thinking output. */
|
||||||
|
genericOpenAIModelDisablesThinking(modelId: string): boolean {
|
||||||
|
return this.genericOpenAICapabilities.get(modelId)?.disableThinking ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect the effective provider for a model ID, checking Ollama, LM Studio,
|
* Detect the effective provider for a model ID, checking Ollama, LM Studio,
|
||||||
* and generic OpenAI registration first, then falling back to prefix-based detection.
|
* and generic OpenAI registration first, then falling back to prefix-based detection.
|
||||||
@@ -491,9 +504,41 @@ export class ProviderRegistry {
|
|||||||
throw new Error(`Generic OpenAI endpoint not configured for model '${modelId}'`);
|
throw new Error(`Generic OpenAI endpoint not configured for model '${modelId}'`);
|
||||||
}
|
}
|
||||||
if (!this.genericOpenAIProvider) {
|
if (!this.genericOpenAIProvider) {
|
||||||
|
const genericFetch: typeof fetch = async (input, init) => {
|
||||||
|
const url = typeof input === 'string'
|
||||||
|
? input
|
||||||
|
: input instanceof URL
|
||||||
|
? input.toString()
|
||||||
|
: input.url;
|
||||||
|
|
||||||
|
if (url.endsWith('/chat/completions') && typeof init?.body === 'string') {
|
||||||
|
try {
|
||||||
|
const body = JSON.parse(init.body) as { model?: string; chat_template_kwargs?: Record<string, unknown> };
|
||||||
|
if (body.model && this.genericOpenAIModelDisablesThinking(body.model)) {
|
||||||
|
const nextInit = {
|
||||||
|
...init,
|
||||||
|
body: JSON.stringify({
|
||||||
|
...body,
|
||||||
|
chat_template_kwargs: {
|
||||||
|
...body.chat_template_kwargs,
|
||||||
|
enable_thinking: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
return fetch(input, nextInit);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Fall back to the original request if the body isn't JSON.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(input, init);
|
||||||
|
};
|
||||||
|
|
||||||
this.genericOpenAIProvider = createOpenAI({
|
this.genericOpenAIProvider = createOpenAI({
|
||||||
baseURL: this.genericOpenAIBaseURL,
|
baseURL: this.genericOpenAIBaseURL,
|
||||||
apiKey: this.genericOpenAIApiKey || 'dummy-key',
|
apiKey: this.genericOpenAIApiKey || 'dummy-key',
|
||||||
|
fetch: genericFetch,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.genericOpenAIProvider.chat(modelId);
|
return this.genericOpenAIProvider.chat(modelId);
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ export function registerChatHandlers(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Set capability override for a single generic OpenAI model
|
// Set capability override for a single generic OpenAI model
|
||||||
ipcMain.handle('chat:setGenericOpenAIModelCapabilities', async (_, modelId: string, caps: { tools: boolean; vision: boolean }) => {
|
ipcMain.handle('chat:setGenericOpenAIModelCapabilities', async (_, modelId: string, caps: { tools: boolean; vision: boolean; disableThinking: boolean }) => {
|
||||||
try {
|
try {
|
||||||
await ensureInitialized();
|
await ensureInitialized();
|
||||||
const reg = getProviders();
|
const reg = getProviders();
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ export const electronAPI: ElectronAPI = {
|
|||||||
validateGenericOpenAIConfig: () => ipcRenderer.invoke('chat:validateGenericOpenAIConfig'),
|
validateGenericOpenAIConfig: () => ipcRenderer.invoke('chat:validateGenericOpenAIConfig'),
|
||||||
getGenericOpenAIModels: () => ipcRenderer.invoke('chat:getGenericOpenAIModels'),
|
getGenericOpenAIModels: () => ipcRenderer.invoke('chat:getGenericOpenAIModels'),
|
||||||
getGenericOpenAIModelCapabilities: () => ipcRenderer.invoke('chat:getGenericOpenAIModelCapabilities'),
|
getGenericOpenAIModelCapabilities: () => ipcRenderer.invoke('chat:getGenericOpenAIModelCapabilities'),
|
||||||
setGenericOpenAIModelCapabilities: (modelId: string, caps: { tools: boolean; vision: boolean }) => ipcRenderer.invoke('chat:setGenericOpenAIModelCapabilities', modelId, caps),
|
setGenericOpenAIModelCapabilities: (modelId: string, caps: { tools: boolean; vision: boolean; disableThinking: boolean }) => ipcRenderer.invoke('chat:setGenericOpenAIModelCapabilities', modelId, caps),
|
||||||
|
|
||||||
// Offline / Airplane Mode
|
// Offline / Airplane Mode
|
||||||
getOfflineMode: () => ipcRenderer.invoke('chat:getOfflineMode'),
|
getOfflineMode: () => ipcRenderer.invoke('chat:getOfflineMode'),
|
||||||
|
|||||||
@@ -1031,8 +1031,8 @@ export interface ElectronAPI {
|
|||||||
setGenericOpenAIApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
setGenericOpenAIApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
validateGenericOpenAIConfig: () => Promise<{ isValid: boolean; models: ChatModel[]; error?: string }>;
|
validateGenericOpenAIConfig: () => Promise<{ isValid: boolean; models: ChatModel[]; error?: string }>;
|
||||||
getGenericOpenAIModels: () => Promise<ChatModel[]>;
|
getGenericOpenAIModels: () => Promise<ChatModel[]>;
|
||||||
getGenericOpenAIModelCapabilities: () => Promise<Record<string, { tools: boolean; vision: boolean }>>;
|
getGenericOpenAIModelCapabilities: () => Promise<Record<string, { tools: boolean; vision: boolean; disableThinking: boolean }>>;
|
||||||
setGenericOpenAIModelCapabilities: (modelId: string, caps: { tools: boolean; vision: boolean }) => Promise<{ success: boolean; error?: string }>;
|
setGenericOpenAIModelCapabilities: (modelId: string, caps: { tools: boolean; vision: boolean; disableThinking: boolean }) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
|
||||||
// Offline / Airplane mode
|
// Offline / Airplane mode
|
||||||
getOfflineMode: () => Promise<boolean>;
|
getOfflineMode: () => Promise<boolean>;
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export const SettingsView: React.FC = () => {
|
|||||||
const [hasGenericOpenAIApiKey, setHasGenericOpenAIApiKey] = useState(false);
|
const [hasGenericOpenAIApiKey, setHasGenericOpenAIApiKey] = useState(false);
|
||||||
const [newGenericOpenAIApiKey, setNewGenericOpenAIApiKey] = useState('');
|
const [newGenericOpenAIApiKey, setNewGenericOpenAIApiKey] = useState('');
|
||||||
const [genericOpenAIModels, setGenericOpenAIModels] = useState<{id: string; name: string}[]>([]);
|
const [genericOpenAIModels, setGenericOpenAIModels] = useState<{id: string; name: string}[]>([]);
|
||||||
const [genericOpenAICapabilities, setGenericOpenAICapabilities] = useState<Record<string, { tools: boolean; vision: boolean }>>({});
|
const [genericOpenAICapabilities, setGenericOpenAICapabilities] = useState<Record<string, { tools: boolean; vision: boolean; disableThinking: boolean }>>({});
|
||||||
const [offlineModeEnabled, setOfflineModeEnabled] = useState(false);
|
const [offlineModeEnabled, setOfflineModeEnabled] = useState(false);
|
||||||
const [offlineChatModel, setOfflineChatModel] = useState('');
|
const [offlineChatModel, setOfflineChatModel] = useState('');
|
||||||
const [offlineTitleModel, setOfflineTitleModel] = useState('');
|
const [offlineTitleModel, setOfflineTitleModel] = useState('');
|
||||||
@@ -1466,8 +1466,8 @@ export const SettingsView: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenericOpenAICapabilityToggle = async (modelId: string, field: 'tools' | 'vision', value: boolean) => {
|
const handleGenericOpenAICapabilityToggle = async (modelId: string, field: 'tools' | 'vision' | 'disableThinking', value: boolean) => {
|
||||||
const current = genericOpenAICapabilities[modelId] ?? { tools: false, vision: false };
|
const current = genericOpenAICapabilities[modelId] ?? { tools: false, vision: false, disableThinking: false };
|
||||||
const updated = { ...current, [field]: value };
|
const updated = { ...current, [field]: value };
|
||||||
try {
|
try {
|
||||||
const result = await window.electronAPI?.chat.setGenericOpenAIModelCapabilities(modelId, updated);
|
const result = await window.electronAPI?.chat.setGenericOpenAIModelCapabilities(modelId, updated);
|
||||||
@@ -1939,11 +1939,12 @@ export const SettingsView: React.FC = () => {
|
|||||||
<th>{t('settings.ai.genericOpenAICapModel')}</th>
|
<th>{t('settings.ai.genericOpenAICapModel')}</th>
|
||||||
<th>{t('settings.ai.genericOpenAICapTools')}</th>
|
<th>{t('settings.ai.genericOpenAICapTools')}</th>
|
||||||
<th>{t('settings.ai.genericOpenAICapVision')}</th>
|
<th>{t('settings.ai.genericOpenAICapVision')}</th>
|
||||||
|
<th>{t('settings.ai.genericOpenAICapDisableThinking')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{genericOpenAIModels.map(m => {
|
{genericOpenAIModels.map(m => {
|
||||||
const caps = genericOpenAICapabilities[m.id] ?? { tools: false, vision: false };
|
const caps = genericOpenAICapabilities[m.id] ?? { tools: false, vision: false, disableThinking: false };
|
||||||
return (
|
return (
|
||||||
<tr key={m.id}>
|
<tr key={m.id}>
|
||||||
<td>{m.name}</td>
|
<td>{m.name}</td>
|
||||||
@@ -1961,6 +1962,13 @@ export const SettingsView: React.FC = () => {
|
|||||||
onChange={(e) => handleGenericOpenAICapabilityToggle(m.id, 'vision', e.target.checked)}
|
onChange={(e) => handleGenericOpenAICapabilityToggle(m.id, 'vision', e.target.checked)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={caps.disableThinking}
|
||||||
|
onChange={(e) => handleGenericOpenAICapabilityToggle(m.id, 'disableThinking', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -884,6 +884,7 @@
|
|||||||
"settings.ai.genericOpenAICapModel": "Modell",
|
"settings.ai.genericOpenAICapModel": "Modell",
|
||||||
"settings.ai.genericOpenAICapTools": "Tools",
|
"settings.ai.genericOpenAICapTools": "Tools",
|
||||||
"settings.ai.genericOpenAICapVision": "Vision",
|
"settings.ai.genericOpenAICapVision": "Vision",
|
||||||
|
"settings.ai.genericOpenAICapDisableThinking": "Denkmodus deaktivieren",
|
||||||
"settings.toast.genericOpenAIEnabled": "Generischer OpenAI-Endpunkt aktiviert",
|
"settings.toast.genericOpenAIEnabled": "Generischer OpenAI-Endpunkt aktiviert",
|
||||||
"settings.toast.genericOpenAIDisabled": "Generischer OpenAI-Endpunkt deaktiviert",
|
"settings.toast.genericOpenAIDisabled": "Generischer OpenAI-Endpunkt deaktiviert",
|
||||||
"settings.toast.genericOpenAISettingsSaved": "Generische OpenAI-Einstellungen gespeichert",
|
"settings.toast.genericOpenAISettingsSaved": "Generische OpenAI-Einstellungen gespeichert",
|
||||||
|
|||||||
@@ -884,6 +884,7 @@
|
|||||||
"settings.ai.genericOpenAICapModel": "Model",
|
"settings.ai.genericOpenAICapModel": "Model",
|
||||||
"settings.ai.genericOpenAICapTools": "Tools",
|
"settings.ai.genericOpenAICapTools": "Tools",
|
||||||
"settings.ai.genericOpenAICapVision": "Vision",
|
"settings.ai.genericOpenAICapVision": "Vision",
|
||||||
|
"settings.ai.genericOpenAICapDisableThinking": "Disable Thinking",
|
||||||
"settings.toast.genericOpenAIEnabled": "Generic OpenAI endpoint enabled",
|
"settings.toast.genericOpenAIEnabled": "Generic OpenAI endpoint enabled",
|
||||||
"settings.toast.genericOpenAIDisabled": "Generic OpenAI endpoint disabled",
|
"settings.toast.genericOpenAIDisabled": "Generic OpenAI endpoint disabled",
|
||||||
"settings.toast.genericOpenAISettingsSaved": "Generic OpenAI settings saved",
|
"settings.toast.genericOpenAISettingsSaved": "Generic OpenAI settings saved",
|
||||||
|
|||||||
@@ -884,6 +884,7 @@
|
|||||||
"settings.ai.genericOpenAICapModel": "Modelo",
|
"settings.ai.genericOpenAICapModel": "Modelo",
|
||||||
"settings.ai.genericOpenAICapTools": "Herramientas",
|
"settings.ai.genericOpenAICapTools": "Herramientas",
|
||||||
"settings.ai.genericOpenAICapVision": "Visión",
|
"settings.ai.genericOpenAICapVision": "Visión",
|
||||||
|
"settings.ai.genericOpenAICapDisableThinking": "Desactivar razonamiento",
|
||||||
"settings.toast.genericOpenAIEnabled": "Endpoint genérico OpenAI habilitado",
|
"settings.toast.genericOpenAIEnabled": "Endpoint genérico OpenAI habilitado",
|
||||||
"settings.toast.genericOpenAIDisabled": "Endpoint genérico OpenAI deshabilitado",
|
"settings.toast.genericOpenAIDisabled": "Endpoint genérico OpenAI deshabilitado",
|
||||||
"settings.toast.genericOpenAISettingsSaved": "Configuración genérica de OpenAI guardada",
|
"settings.toast.genericOpenAISettingsSaved": "Configuración genérica de OpenAI guardada",
|
||||||
|
|||||||
@@ -884,6 +884,7 @@
|
|||||||
"settings.ai.genericOpenAICapModel": "Modèle",
|
"settings.ai.genericOpenAICapModel": "Modèle",
|
||||||
"settings.ai.genericOpenAICapTools": "Outils",
|
"settings.ai.genericOpenAICapTools": "Outils",
|
||||||
"settings.ai.genericOpenAICapVision": "Vision",
|
"settings.ai.genericOpenAICapVision": "Vision",
|
||||||
|
"settings.ai.genericOpenAICapDisableThinking": "Désactiver le raisonnement",
|
||||||
"settings.toast.genericOpenAIEnabled": "Point de terminaison OpenAI générique activé",
|
"settings.toast.genericOpenAIEnabled": "Point de terminaison OpenAI générique activé",
|
||||||
"settings.toast.genericOpenAIDisabled": "Point de terminaison OpenAI générique désactivé",
|
"settings.toast.genericOpenAIDisabled": "Point de terminaison OpenAI générique désactivé",
|
||||||
"settings.toast.genericOpenAISettingsSaved": "Paramètres OpenAI génériques enregistrés",
|
"settings.toast.genericOpenAISettingsSaved": "Paramètres OpenAI génériques enregistrés",
|
||||||
|
|||||||
@@ -884,6 +884,7 @@
|
|||||||
"settings.ai.genericOpenAICapModel": "Modello",
|
"settings.ai.genericOpenAICapModel": "Modello",
|
||||||
"settings.ai.genericOpenAICapTools": "Strumenti",
|
"settings.ai.genericOpenAICapTools": "Strumenti",
|
||||||
"settings.ai.genericOpenAICapVision": "Visione",
|
"settings.ai.genericOpenAICapVision": "Visione",
|
||||||
|
"settings.ai.genericOpenAICapDisableThinking": "Disattiva ragionamento",
|
||||||
"settings.toast.genericOpenAIEnabled": "Endpoint generico OpenAI abilitato",
|
"settings.toast.genericOpenAIEnabled": "Endpoint generico OpenAI abilitato",
|
||||||
"settings.toast.genericOpenAIDisabled": "Endpoint generico OpenAI disabilitato",
|
"settings.toast.genericOpenAIDisabled": "Endpoint generico OpenAI disabilitato",
|
||||||
"settings.toast.genericOpenAISettingsSaved": "Impostazioni generiche OpenAI salvate",
|
"settings.toast.genericOpenAISettingsSaved": "Impostazioni generiche OpenAI salvate",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { generateText } from 'ai';
|
||||||
import { ProviderRegistry } from '../../src/main/engine/ai/providers';
|
import { ProviderRegistry } from '../../src/main/engine/ai/providers';
|
||||||
|
|
||||||
vi.mock('../../src/main/engine/ModelCatalogEngine', () => ({
|
vi.mock('../../src/main/engine/ModelCatalogEngine', () => ({
|
||||||
@@ -55,4 +56,57 @@ describe('generic OpenAI-compatible provider support', () => {
|
|||||||
expect(registry.getKnownLocalModels()).toEqual([]);
|
expect(registry.getKnownLocalModels()).toEqual([]);
|
||||||
expect(() => registry.resolveModel('custom-model')).toThrow(/not available offline/i);
|
expect(() => registry.resolveModel('custom-model')).toThrow(/not available offline/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stores disableThinking for generic endpoint models', () => {
|
||||||
|
registry.setGenericOpenAIModelCapabilities('custom-model', { tools: false, vision: true, disableThinking: true });
|
||||||
|
|
||||||
|
expect(registry.getGenericOpenAIModelCapabilities('custom-model')).toEqual({
|
||||||
|
tools: false,
|
||||||
|
vision: true,
|
||||||
|
disableThinking: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects enable_thinking false only when disableThinking is enabled', async () => {
|
||||||
|
registry.setGenericOpenAIEnabled(true);
|
||||||
|
registry.setGenericOpenAIBaseURL('http://localhost:4000/v1');
|
||||||
|
registry.registerGenericOpenAIModel('custom-model');
|
||||||
|
registry.setGenericOpenAIModelCapabilities('custom-model', { tools: false, vision: false, disableThinking: true });
|
||||||
|
|
||||||
|
const mockFetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
||||||
|
id: 'chatcmpl-test',
|
||||||
|
object: 'chat.completion',
|
||||||
|
created: 1,
|
||||||
|
model: 'custom-model',
|
||||||
|
choices: [{
|
||||||
|
index: 0,
|
||||||
|
message: {
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Short title',
|
||||||
|
},
|
||||||
|
finish_reason: 'stop',
|
||||||
|
}],
|
||||||
|
usage: { prompt_tokens: 10, completion_tokens: 2, total_tokens: 12 },
|
||||||
|
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = mockFetch;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const model = registry.resolveModel('custom-model');
|
||||||
|
const result = await generateText({
|
||||||
|
model,
|
||||||
|
prompt: 'hello',
|
||||||
|
maxOutputTokens: 10,
|
||||||
|
maxRetries: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.text).toBe('Short title');
|
||||||
|
const [, request] = mockFetch.mock.calls[0] as [string, { body: string }];
|
||||||
|
expect(JSON.parse(request.body)).toMatchObject({
|
||||||
|
chat_template_kwargs: { enable_thinking: false },
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user