diff --git a/appinfo/info.xml b/appinfo/info.xml
index 75449e62..6f388944 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -101,7 +101,7 @@ Negative:
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]>
- 3.8.0
+ 3.9.0
agpl
Julien Veyssier
OpenAi
@@ -121,6 +121,7 @@ Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud
OCA\OpenAi\Cron\CleanupQuotaDb
+ OCA\OpenAi\Cron\RefreshModels
OCA\OpenAi\Settings\Admin
diff --git a/lib/Controller/OpenAiAPIController.php b/lib/Controller/OpenAiAPIController.php
index 3860942e..df7e9114 100644
--- a/lib/Controller/OpenAiAPIController.php
+++ b/lib/Controller/OpenAiAPIController.php
@@ -31,7 +31,7 @@ public function __construct(
#[NoAdminRequired]
public function getModels(): DataResponse {
try {
- $response = $this->openAiAPIService->getModels($this->userId);
+ $response = $this->openAiAPIService->getModels($this->userId, true);
return new DataResponse($response);
} catch (Exception $e) {
$code = $e->getCode() === 0 ? Http::STATUS_BAD_REQUEST : intval($e->getCode());
diff --git a/lib/Cron/RefreshModels.php b/lib/Cron/RefreshModels.php
new file mode 100644
index 00000000..0de60679
--- /dev/null
+++ b/lib/Cron/RefreshModels.php
@@ -0,0 +1,31 @@
+setInterval(60 * 60 * 24); // Daily
+ }
+
+ protected function run($argument) {
+ $this->logger->debug('Run daily model refresh job');
+ $this->openAIAPIService->getModels(null, true);
+ }
+}
diff --git a/lib/Migration/Version030900Date20251006152735.php b/lib/Migration/Version030900Date20251006152735.php
new file mode 100644
index 00000000..a721fc98
--- /dev/null
+++ b/lib/Migration/Version030900Date20251006152735.php
@@ -0,0 +1,34 @@
+openAIAPIService->getModels(null, true);
+ }
+}
diff --git a/lib/Service/OpenAiAPIService.php b/lib/Service/OpenAiAPIService.php
index c08cf038..09b658df 100644
--- a/lib/Service/OpenAiAPIService.php
+++ b/lib/Service/OpenAiAPIService.php
@@ -39,7 +39,6 @@
class OpenAiAPIService {
private IClient $client;
private ?array $modelsMemoryCache = null;
- private ?bool $areCredsValid = null;
public function __construct(
private LoggerInterface $logger,
@@ -111,68 +110,80 @@ private function isModelListValid($models): bool {
/**
* @param ?string $userId
+ * @param bool $refresh
* @return array|string[]
* @throws Exception
*/
- public function getModels(?string $userId): array {
- // caching against 'getModelEnumValues' calls from all the providers
- if ($this->areCredsValid === false) {
- $this->logger->info('Cannot get OpenAI models without an API key');
- return [];
- } elseif ($this->areCredsValid === null) {
- if ($this->isUsingOpenAi() && $this->openAiSettingsService->getUserApiKey($userId, true) === '') {
- $this->areCredsValid = false;
- $this->logger->info('Cannot get OpenAI models without an API key');
- return [];
- }
- $this->areCredsValid = true;
- }
-
- if ($this->modelsMemoryCache !== null) {
- $this->logger->debug('Getting OpenAI models from the memory cache');
- return $this->modelsMemoryCache;
- }
-
+ public function getModels(?string $userId, bool $refresh = false): array {
+ $cache = $this->cacheFactory->createDistributed(Application::APP_ID);
$userCacheKey = Application::MODELS_CACHE_KEY . '_' . ($userId ?? '');
$adminCacheKey = Application::MODELS_CACHE_KEY . '-main';
- $cache = $this->cacheFactory->createDistributed(Application::APP_ID);
- // try to get models from the user cache first
- if ($userId !== null) {
- $userCachedModels = $cache->get($userCacheKey);
- if ($userCachedModels) {
- $this->logger->debug('Getting OpenAI models from user cache for user ' . $userId);
- return $userCachedModels;
+ if (!$refresh) {
+ if ($this->modelsMemoryCache !== null) {
+ $this->logger->debug('Getting OpenAI models from the memory cache');
+ return $this->modelsMemoryCache;
}
- }
- // if the user has an API key or uses basic auth, skip the admin cache
- if (!(
- $this->openAiSettingsService->getUserApiKey($userId, false) !== ''
- || (
- $this->openAiSettingsService->getUseBasicAuth()
- && $this->openAiSettingsService->getUserBasicUser($userId) !== ''
- && $this->openAiSettingsService->getUserBasicPassword($userId) !== ''
- )
- )) {
- // if no user cache or userId is null, try to get from the admin cache
- if ($adminCachedModels = $cache->get($adminCacheKey)) {
- $this->logger->debug('Getting OpenAI models from the main distributed cache');
- return $adminCachedModels;
+ // try to get models from the user cache first
+ if ($userId !== null) {
+ $userCachedModels = $cache->get($userCacheKey);
+ if ($userCachedModels) {
+ $this->logger->debug('Getting OpenAI models from user cache for user ' . $userId);
+ $this->modelsMemoryCache = $userCachedModels;
+ return $userCachedModels;
+ }
}
+
+ // if the user has an API key or uses basic auth, skip the admin cache
+ if ($userId === null || (
+ $this->openAiSettingsService->getUserApiKey($userId, false) === ''
+ && (
+ !$this->openAiSettingsService->getUseBasicAuth()
+ || $this->openAiSettingsService->getUserBasicUser($userId) === ''
+ || $this->openAiSettingsService->getUserBasicPassword($userId) === ''
+ )
+ )) {
+ // here we know there is either no user cache or userId is null
+ // so if there is no user-defined service credentials
+ // we try to get the models from the admin cache
+ if ($adminCachedModels = $cache->get($adminCacheKey)) {
+ $this->logger->debug('Getting OpenAI models from the main distributed cache');
+ $this->modelsMemoryCache = $adminCachedModels;
+ return $adminCachedModels;
+ }
+ }
+
+ // if we don't need to refresh to model list and it's not been found in the cache, it is obtained from the DB
+ $modelsObjectString = $this->appConfig->getValueString(Application::APP_ID, 'models', '{"data":[],"object":"list"}');
+ $fallbackModels = [
+ 'data' => [],
+ 'object' => 'list',
+ ];
+ try {
+ $newCache = json_decode($modelsObjectString, true) ?? $fallbackModels;
+ } catch (Throwable $e) {
+ $this->logger->warning('Could not decode the model JSON string', ['model_string', $modelsObjectString, 'exception' => $e]);
+ $newCache = $fallbackModels;
+ }
+ $cache->set($userId !== null ? $userCacheKey : $adminCacheKey, $newCache, Application::MODELS_CACHE_TTL);
+ $this->modelsMemoryCache = $newCache;
+ return $newCache;
}
+ // we know we are refreshing so we clear the caches and make the network request
+ $cache->remove($adminCacheKey);
+ $cache->remove($userCacheKey);
+
try {
$this->logger->debug('Actually getting OpenAI models with a network request');
$modelsResponse = $this->request($userId, 'models');
} catch (Exception $e) {
$this->logger->warning('Error retrieving models (exc): ' . $e->getMessage());
- $this->areCredsValid = false;
throw $e;
}
if (isset($modelsResponse['error'])) {
$this->logger->warning('Error retrieving models: ' . json_encode($modelsResponse));
- $this->areCredsValid = false;
throw new Exception($modelsResponse['error'], Http::STATUS_INTERNAL_SERVER_ERROR);
}
if (!isset($modelsResponse['data'])) {
@@ -182,13 +193,14 @@ public function getModels(?string $userId): array {
if (!$this->isModelListValid($modelsResponse['data'])) {
$this->logger->warning('Invalid models response: ' . json_encode($modelsResponse));
- $this->areCredsValid = false;
throw new Exception($this->l10n->t('Invalid models response received'), Http::STATUS_INTERNAL_SERVER_ERROR);
}
$cache->set($userId !== null ? $userCacheKey : $adminCacheKey, $modelsResponse, Application::MODELS_CACHE_TTL);
$this->modelsMemoryCache = $modelsResponse;
- $this->areCredsValid = true;
+ // we always store the model list after getting it
+ $modelsObjectString = json_encode($modelsResponse);
+ $this->appConfig->setValueString(Application::APP_ID, 'models', $modelsObjectString);
return $modelsResponse;
}