Skip to content
Open
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ They might work when connecting to other services, without any guarantee.

Instead of connecting to the OpenAI API for these, you can also connect to a self-hosted [LocalAI](https://localai.io) instance or [Ollama](https://ollama.com/) instance
or to any service that implements an API similar to the OpenAI one, for example:
[IONOS AI Model Hub](https://docs.ionos.com/cloud/ai/ai-model-hub), [Plusserver](https://www.plusserver.com/en/ai-platform/) or [MistralAI](https://mistral.ai).
[IONOS AI Model Hub](https://docs.ionos.com/cloud/ai/ai-model-hub), [Plusserver](https://www.plusserver.com/en/ai-platform/), [MistralAI](https://mistral.ai) or [Eden AI](https://www.edenai.co).
Make sure to use the OpenAI-compatible endpoint instead of the custom ones they provide.

Eden AI additionally offers a European endpoint, `https://api.eu.edenai.run/v3`, which only exposes
models cleared for processing in the EU and refuses anything served elsewhere. Use it as the service
URL when your instance needs the data to stay in Europe.

:warning: This app is mainly tested with OpenAI. We do not guarantee it works perfectly
with other services that implement OpenAI-compatible APIs with slight differences.

Expand Down
43 changes: 43 additions & 0 deletions lib/Service/OpenAiAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,52 @@ public function getModels(?string $userId, ServiceConfig $service): array {
throw new Exception($this->l10n->t('Invalid models response received'), Http::STATUS_INTERNAL_SERVER_ERROR);
}

if ($service->isUsingEdenAi()) {
$modelsResponse['data'] = array_merge(
$modelsResponse['data'],
$this->getEdenAiAudioModels($userId, $service),
);
}

return $modelsResponse;
}

/**
* Eden AI keeps one catalogue per modality, so its main models endpoint
* returns the text models only. Speech to text and text to speech have
* their own endpoints, and without them the transcription and speech model
* selectors in the admin settings have nothing to offer.
*
* A failure here is not fatal. The text models have already been retrieved
* and are usable on their own, so we log and return what we have.
*
* @param ?string $userId
* @param ServiceConfig $service
* @return list<array> the audio models, or an empty list
*/
private function getEdenAiAudioModels(?string $userId, ServiceConfig $service): array {
$models = [];
foreach (['audio/transcriptions/models', 'audio/speech/models'] as $endPoint) {
try {
$response = $this->request($userId, $service, $endPoint);
} catch (Exception $e) {
$this->logger->warning('Error retrieving Eden AI models from ' . $endPoint . ' (exc): ' . $e->getMessage());
continue;
}
if (isset($response['error']) || !isset($response['data']) || !is_array($response['data'])) {
$this->logger->warning('Error retrieving Eden AI models from ' . $endPoint . ': ' . json_encode($response));
continue;
}
foreach ($response['data'] as $model) {
if (is_array($model) && isset($model['id'])) {
$models[] = $model;
}
}
}

return $models;
}

/**
* Check whether quota is exceeded for a user
*
Expand Down
10 changes: 10 additions & 0 deletions lib/Service/ServiceConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ public function isUsingIonos(): bool {
return str_starts_with(strtolower($this->url), 'https://openai.inference.de-txl.ionos.com');
}

/**
* Eden AI serves the global endpoint and an EU one that only exposes
* models cleared for European processing. Both behave the same way here.
*/
public function isUsingEdenAi(): bool {
$url = strtolower($this->url);
return str_starts_with($url, 'https://api.edenai.run')
|| str_starts_with($url, 'https://api.eu.edenai.run');
}

public function getApiKey(): string {
return $this->apiKey;
}
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/Service/MultiServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -509,4 +509,20 @@ public function testProviderIdsEndInTheirTaskTypeId(): void {
}
$this->assertGreaterThan(15, $checked, 'the factory should have built a provider per task type');
}

public function testEdenAiServiceIsRecognisedOnBothEndpoints(): void {
$global = $this->addService(['url' => 'https://api.edenai.run/v3']);
$this->assertTrue($global->isUsingEdenAi());
$this->assertFalse($global->isUsingOpenAi());

// the European endpoint only exposes models cleared for EU processing
$european = $this->addService(['url' => 'https://api.eu.edenai.run/v3']);
$this->assertTrue($european->isUsingEdenAi());

$mixedCase = $this->addService(['url' => 'HTTPS://API.EDENAI.RUN/v3']);
$this->assertTrue($mixedCase->isUsingEdenAi());

$other = $this->addService(['url' => self::IMAGE_BASE]);
$this->assertFalse($other->isUsingEdenAi());
}
}