Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 49 additions & 18 deletions api/doc/settings/put-req/.type/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export declare function assertValid(data: any, options?: import('@data-fair/lib-
export declare function returnValid(data: any, options?: import('@data-fair/lib-validation').AssertValidOptions): SettingsPut

// see https://github.com/bcherny/json-schema-to-typescript/issues/439 if some types are not exported
export type Provider = OpenAI | Anthropic | Google | Mistral | OpenRouter | Ollama | Mock;
export type Provider = OpenAI | Anthropic | Google | Mistral | OpenRouter | Ollama | Scaleway | OpenAICompatible | Mock;
export type ProviderType = "openai";
export type ProviderID = string;
export type DisplayName = string;
Expand Down Expand Up @@ -38,16 +38,27 @@ export type DisplayName5 = string;
export type Enabled5 = boolean;
export type APIKey5 = string;
export type BaseURL = string;
export type ProviderType6 = "mock";
export type ProviderType6 = "scaleway";
export type ProviderID6 = string;
export type DisplayName6 = string;
export type Enabled6 = boolean;
export type APIKey6 = string;
export type ProviderType7 = "openai-compatible";
export type ProviderID7 = string;
export type DisplayName7 = string;
export type Enabled7 = boolean;
export type BaseURL1 = string;
export type APIKey7 = string;
export type ProviderType8 = "mock";
export type ProviderID8 = string;
export type DisplayName8 = string;
export type Enabled8 = boolean;
export type AIProviders = Provider[];
export type ModelID = string;
export type Name = string;
export type ProviderType7 = string;
export type ProviderType9 = string;
export type ProviderName = string;
export type ProviderID7 = string;
export type ProviderID9 = string;
export type InputPricePer1MTokens = number;
export type OutputPricePer1MTokens = number;
export type InputPricePer1MTokens1 = number;
Expand Down Expand Up @@ -124,14 +135,34 @@ export type Ollama = {
baseURL: BaseURL;
[k: string]: unknown;
}
/**
* To a message "hello" respond "world", to a message "call tool ARG1 ARG2" respond with a tool call, to anything else respond "what do you mean ?"
*/
export type Mock = {
export type Scaleway = {
type: ProviderType6;
id: ProviderID6;
name: DisplayName6;
enabled: Enabled6;
apiKey: APIKey6;
[k: string]: unknown;
}
/**
* Generic provider for any OpenAI-compatible endpoint (Together, Fireworks, Groq, DeepInfra, vLLM, LM Studio, etc.). API Key is optional for unauthenticated local servers.
*/
export type OpenAICompatible = {
type: ProviderType7;
id: ProviderID7;
name: DisplayName7;
enabled: Enabled7;
baseURL: BaseURL1;
apiKey?: APIKey7;
[k: string]: unknown;
}
/**
* To a message "hello" respond "world", to a message "call tool ARG1 ARG2" respond with a tool call, to anything else respond "what do you mean ?"
*/
export type Mock = {
type: ProviderType8;
id: ProviderID8;
name: DisplayName8;
enabled: Enabled8;
[k: string]: unknown;
}
export type Models = {
Expand All @@ -157,9 +188,9 @@ export type Model = {
id: ModelID;
name: Name;
provider: {
type: ProviderType7;
type: ProviderType9;
name: ProviderName;
id: ProviderID7;
id: ProviderID9;
[k: string]: unknown;
};
[k: string]: unknown;
Expand All @@ -180,9 +211,9 @@ export type Model1 = {
id: ModelID;
name: Name;
provider: {
type: ProviderType7;
type: ProviderType9;
name: ProviderName;
id: ProviderID7;
id: ProviderID9;
[k: string]: unknown;
};
[k: string]: unknown;
Expand All @@ -203,9 +234,9 @@ export type Model2 = {
id: ModelID;
name: Name;
provider: {
type: ProviderType7;
type: ProviderType9;
name: ProviderName;
id: ProviderID7;
id: ProviderID9;
[k: string]: unknown;
};
[k: string]: unknown;
Expand All @@ -226,9 +257,9 @@ export type Model3 = {
id: ModelID;
name: Name;
provider: {
type: ProviderType7;
type: ProviderType9;
name: ProviderName;
id: ProviderID7;
id: ProviderID9;
[k: string]: unknown;
};
[k: string]: unknown;
Expand Down Expand Up @@ -289,9 +320,9 @@ export type Model4 = {
id: ModelID;
name: Name;
provider: {
type: ProviderType7;
type: ProviderType9;
name: ProviderName;
id: ProviderID7;
id: ProviderID9;
[k: string]: unknown;
};
[k: string]: unknown;
Expand Down
1,168 changes: 725 additions & 443 deletions api/doc/settings/put-req/.type/validate.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions api/src/models/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export function createModel (provider: Provider, modelId: string): LanguageModel
return createOpenRouter({ apiKey: provider.apiKey })(modelId) as unknown as LanguageModel
case 'ollama':
return createOllama({ baseURL: provider.baseURL })(modelId)
case 'scaleway':
return createOpenAI({ apiKey: provider.apiKey, baseURL: 'https://api.scaleway.ai/v1' })(modelId)
case 'openai-compatible':
return createOpenAI({ apiKey: provider.apiKey, baseURL: provider.baseURL })(modelId)
case 'mock':
if (modelId === 'evaluator-mock-model') return createEvaluatorMockLanguageModel()
return createMockLanguageModel(modelId)
Expand Down
21 changes: 21 additions & 0 deletions api/src/models/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ async function fetchOpenAIModels (apiKey: string): Promise<CoreModelInfo[]> {
}
}

async function fetchOpenAICompatibleModels (baseURL: string, apiKey?: string): Promise<CoreModelInfo[]> {
try {
const headers: Record<string, string> = {}
if (apiKey) headers.Authorization = `Bearer ${apiKey}`
const response = await axios.get(`${baseURL.replace(/\/$/, '')}/models`, { headers })
return response.data.data.map((model: any) => ({
id: model.id,
name: model.name || model.id
}))
} catch (err) {
console.error(`Failed to fetch OpenAI-compatible models from ${baseURL}:`, err)
return []
}
}

async function fetchAnthropicModels (apiKey: string): Promise<CoreModelInfo[]> {
try {
const response = await axios.get('https://api.anthropic.com/v1/models', {
Expand Down Expand Up @@ -122,6 +137,10 @@ async function fetchModelsForProvider (
]
}

if (provider.type === 'openai-compatible') {
return fetchOpenAICompatibleModels(provider.baseURL, provider.apiKey)
}

if (!provider.apiKey) {
return []
}
Expand All @@ -137,6 +156,8 @@ async function fetchModelsForProvider (
return fetchMistralModels(provider.apiKey)
case 'openrouter':
return fetchOpenRouterModels(provider.apiKey)
case 'scaleway':
return fetchOpenAICompatibleModels('https://api.scaleway.ai/v1', provider.apiKey)
default:
return []
}
Expand Down
111 changes: 111 additions & 0 deletions api/types/settings/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,117 @@ export default {
default: 'http://localhost:11434'
}
}
}, {
required: ['type', 'name', 'id', 'enabled', 'apiKey'],
title: 'Scaleway',
properties: {
type: {
type: 'string',
title: 'Provider Type',
const: 'scaleway'
},
id: {
type: 'string',
title: 'Provider ID',
'x-i18n-title': {
en: 'Provider ID',
fr: 'ID du fournisseur'
},
readOnly: true
},
name: {
type: 'string',
title: 'Display Name',
'x-i18n-title': {
en: 'Display Name',
fr: "Nom d'affichage"
},
layout: {
getDefaultData: '"Scaleway"',
},
},
enabled: {
type: 'boolean',
title: 'Enabled',
'x-i18n-title': {
en: 'Enabled',
fr: 'Activé'
},
default: true
},
apiKey: {
type: 'string',
title: 'API Key',
'x-i18n-title': {
en: 'API Key',
fr: 'Clé API'
}
}
}
}, {
required: ['type', 'name', 'id', 'enabled', 'baseURL'],
title: 'OpenAI Compatible',
'x-i18n-title': {
en: 'OpenAI Compatible',
fr: 'Compatible OpenAI'
},
description: 'Generic provider for any OpenAI-compatible endpoint (Together, Fireworks, Groq, DeepInfra, vLLM, LM Studio, etc.). API Key is optional for unauthenticated local servers.',
'x-i18n-description': {
en: 'Generic provider for any OpenAI-compatible endpoint (Together, Fireworks, Groq, DeepInfra, vLLM, LM Studio, etc.). API Key is optional for unauthenticated local servers.',
fr: 'Fournisseur générique pour tout endpoint compatible OpenAI (Together, Fireworks, Groq, DeepInfra, vLLM, LM Studio, etc.). La clé API est optionnelle pour les serveurs locaux sans authentification.'
},
properties: {
type: {
type: 'string',
title: 'Provider Type',
const: 'openai-compatible'
},
id: {
type: 'string',
title: 'Provider ID',
'x-i18n-title': {
en: 'Provider ID',
fr: 'ID du fournisseur'
},
readOnly: true
},
name: {
type: 'string',
title: 'Display Name',
'x-i18n-title': {
en: 'Display Name',
fr: "Nom d'affichage"
},
layout: {
getDefaultData: '"OpenAI Compatible"',
},
},
enabled: {
type: 'boolean',
title: 'Enabled',
'x-i18n-title': {
en: 'Enabled',
fr: 'Activé'
},
default: true
},
baseURL: {
type: 'string',
title: 'Base URL',
'x-i18n-title': {
en: 'Base URL',
fr: 'URL de base'
}
},
apiKey: {
type: 'string',
title: 'API Key',
'x-i18n-title': {
en: 'API Key',
fr: 'Clé API'
}
}
}
}, {
required: ['type', 'name', 'id', 'enabled'],
title: 'Mock',
Expand Down
26 changes: 25 additions & 1 deletion tests/features/settings/settings.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ test.describe('Settings API', () => {
{ type: 'google', name: 'Google', apiKey: 'sk-google-test-123' },
{ type: 'mistral', name: 'Mistral', apiKey: 'sk-mistral-test-123' },
{ type: 'openrouter', name: 'OpenRouter', apiKey: 'sk-or-test-123' },
{ type: 'ollama', name: 'Ollama', apiKey: 'sk-ollama-test-123', baseURL: 'http://localhost:11434' }
{ type: 'ollama', name: 'Ollama', apiKey: 'sk-ollama-test-123', baseURL: 'http://localhost:11434' },
{ type: 'scaleway', name: 'Scaleway', apiKey: 'scw-test-123' },
{ type: 'openai-compatible', name: 'My Endpoint', apiKey: 'sk-compat-test-123', baseURL: 'http://localhost:8080/v1' }
]

for (const p of providerTypes) {
Expand Down Expand Up @@ -162,6 +164,28 @@ test.describe('Settings API', () => {
assert.equal(getRes.data.providers[0].apiKey, '********')
})

test('should handle openai-compatible provider without apiKey', async () => {
const settingsData = {
providers: [
{
id: 'provider-compat',
type: 'openai-compatible',
name: 'Local LM Studio',
enabled: true,
baseURL: 'http://localhost:1234/v1'
}
],
models: { assistant: { model: mockModel } },
quotas: defaultQuotas
}

const res = await admin.put('/api/settings/user/test-standalone1', settingsData)
assert.equal(res.status, 200)
assert.equal(res.data.providers[0].type, 'openai-compatible')
assert.equal(res.data.providers[0].baseURL, 'http://localhost:1234/v1')
assert.equal(res.data.providers[0].apiKey, undefined)
})

test('should handle ollama provider with baseURL', async () => {
const settingsData = {
providers: [
Expand Down
Loading
Loading