Skip to content

Commit 2d4f190

Browse files
committed
store the model list in the DB, only refresh it when the settings page is accessed and on upgrade to 3.9.0 and once a day in a bg job
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
1 parent 08ceead commit 2d4f190

File tree

5 files changed

+121
-46
lines changed

5 files changed

+121
-46
lines changed

appinfo/info.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ Negative:
101101
102102
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
103103
]]> </description>
104-
<version>3.8.0</version>
104+
<version>3.9.0</version>
105105
<licence>agpl</licence>
106106
<author>Julien Veyssier</author>
107107
<namespace>OpenAi</namespace>
@@ -121,6 +121,7 @@ Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud
121121
</dependencies>
122122
<background-jobs>
123123
<job>OCA\OpenAi\Cron\CleanupQuotaDb</job>
124+
<job>OCA\OpenAi\Cron\RefreshModels</job>
124125
</background-jobs>
125126
<settings>
126127
<admin>OCA\OpenAi\Settings\Admin</admin>

lib/Controller/OpenAiAPIController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(
3131
#[NoAdminRequired]
3232
public function getModels(): DataResponse {
3333
try {
34-
$response = $this->openAiAPIService->getModels($this->userId);
34+
$response = $this->openAiAPIService->getModels($this->userId, true);
3535
return new DataResponse($response);
3636
} catch (Exception $e) {
3737
$code = $e->getCode() === 0 ? Http::STATUS_BAD_REQUEST : intval($e->getCode());

lib/Cron/RefreshModels.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\OpenAi\Cron;
11+
12+
use OCA\OpenAi\Service\OpenAiAPIService;
13+
use OCP\AppFramework\Utility\ITimeFactory;
14+
use OCP\BackgroundJob\TimedJob;
15+
use Psr\Log\LoggerInterface;
16+
17+
class RefreshModels extends TimedJob {
18+
public function __construct(
19+
ITimeFactory $time,
20+
private OpenAiAPIService $openAIAPIService,
21+
private LoggerInterface $logger,
22+
) {
23+
parent::__construct($time);
24+
$this->setInterval(60 * 60 * 24); // Daily
25+
}
26+
27+
protected function run($argument) {
28+
$this->logger->debug('Run daily model refresh job');
29+
$this->openAIAPIService->getModels(null, true);
30+
}
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\OpenAi\Migration;
11+
12+
use Closure;
13+
use OCA\OpenAi\Service\OpenAiAPIService;
14+
use OCP\Migration\IOutput;
15+
use OCP\Migration\SimpleMigrationStep;
16+
17+
class Version030900Date20251006152735 extends SimpleMigrationStep {
18+
19+
public function __construct(
20+
private OpenAIAPIService $openAIAPIService,
21+
) {
22+
}
23+
24+
/**
25+
* @param IOutput $output
26+
* @param Closure $schemaClosure
27+
* @param array $options
28+
*/
29+
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
30+
// we refresh the model list to make sure they are stored in oc_appconfig
31+
// so they are available immediately after the app upgrade to populate the task types enum values
32+
$this->openAIAPIService->getModels(null, true);
33+
}
34+
}

lib/Service/OpenAiAPIService.php

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
class OpenAiAPIService {
4040
private IClient $client;
4141
private ?array $modelsMemoryCache = null;
42-
private ?bool $areCredsValid = null;
4342

4443
public function __construct(
4544
private LoggerInterface $logger,
@@ -111,68 +110,77 @@ private function isModelListValid($models): bool {
111110

112111
/**
113112
* @param ?string $userId
113+
* @param bool $refresh
114114
* @return array|string[]
115115
* @throws Exception
116116
*/
117-
public function getModels(?string $userId): array {
118-
// caching against 'getModelEnumValues' calls from all the providers
119-
if ($this->areCredsValid === false) {
120-
$this->logger->info('Cannot get OpenAI models without an API key');
121-
return [];
122-
} elseif ($this->areCredsValid === null) {
123-
if ($this->isUsingOpenAi() && $this->openAiSettingsService->getUserApiKey($userId, true) === '') {
124-
$this->areCredsValid = false;
125-
$this->logger->info('Cannot get OpenAI models without an API key');
126-
return [];
117+
public function getModels(?string $userId, bool $refresh = false): array {
118+
$cache = $this->cacheFactory->createDistributed(Application::APP_ID);
119+
if (!$refresh) {
120+
if ($this->modelsMemoryCache !== null) {
121+
$this->logger->debug('Getting OpenAI models from the memory cache');
122+
return $this->modelsMemoryCache;
127123
}
128-
$this->areCredsValid = true;
129-
}
130124

131-
if ($this->modelsMemoryCache !== null) {
132-
$this->logger->debug('Getting OpenAI models from the memory cache');
133-
return $this->modelsMemoryCache;
134-
}
125+
$userCacheKey = Application::MODELS_CACHE_KEY . '_' . ($userId ?? '');
126+
$adminCacheKey = Application::MODELS_CACHE_KEY . '-main';
135127

136-
$userCacheKey = Application::MODELS_CACHE_KEY . '_' . ($userId ?? '');
137-
$adminCacheKey = Application::MODELS_CACHE_KEY . '-main';
138-
$cache = $this->cacheFactory->createDistributed(Application::APP_ID);
128+
// try to get models from the user cache first
129+
if ($userId !== null) {
130+
$userCachedModels = $cache->get($userCacheKey);
131+
if ($userCachedModels) {
132+
$this->logger->debug('Getting OpenAI models from user cache for user ' . $userId);
133+
return $userCachedModels;
134+
}
135+
}
139136

140-
// try to get models from the user cache first
141-
if ($userId !== null) {
142-
$userCachedModels = $cache->get($userCacheKey);
143-
if ($userCachedModels) {
144-
$this->logger->debug('Getting OpenAI models from user cache for user ' . $userId);
145-
return $userCachedModels;
137+
// if the user has an API key or uses basic auth, skip the admin cache
138+
if (!(
139+
$this->openAiSettingsService->getUserApiKey($userId, false) !== ''
140+
|| (
141+
$this->openAiSettingsService->getUseBasicAuth()
142+
&& $this->openAiSettingsService->getUserBasicUser($userId) !== ''
143+
&& $this->openAiSettingsService->getUserBasicPassword($userId) !== ''
144+
)
145+
)) {
146+
// if no user cache or userId is null, try to get from the admin cache
147+
if ($adminCachedModels = $cache->get($adminCacheKey)) {
148+
$this->logger->debug('Getting OpenAI models from the main distributed cache');
149+
return $adminCachedModels;
150+
}
146151
}
147-
}
148152

149-
// if the user has an API key or uses basic auth, skip the admin cache
150-
if (!(
151-
$this->openAiSettingsService->getUserApiKey($userId, false) !== ''
152-
|| (
153-
$this->openAiSettingsService->getUseBasicAuth()
154-
&& $this->openAiSettingsService->getUserBasicUser($userId) !== ''
155-
&& $this->openAiSettingsService->getUserBasicPassword($userId) !== ''
156-
)
157-
)) {
158-
// if no user cache or userId is null, try to get from the admin cache
159-
if ($adminCachedModels = $cache->get($adminCacheKey)) {
160-
$this->logger->debug('Getting OpenAI models from the main distributed cache');
161-
return $adminCachedModels;
153+
// 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
154+
$modelsObjectString = $this->appConfig->getValueString(Application::APP_ID, 'models', '{"data":[],"object":"list"}');
155+
$fallbackModels = [
156+
'data' => [],
157+
'object' => 'list',
158+
];
159+
try {
160+
$newCache = json_decode($modelsObjectString, true) ?? $fallbackModels;
161+
} catch (Throwable $e) {
162+
$newCache = $fallbackModels;
162163
}
164+
$cache->set($userId !== null ? $userCacheKey : $adminCacheKey, $newCache, Application::MODELS_CACHE_TTL);
165+
$this->modelsMemoryCache = $newCache;
166+
return $newCache;
163167
}
164168

169+
// we know we are refreshing so we clear the caches and make the network request
170+
$adminCacheKey = Application::MODELS_CACHE_KEY . '-main';
171+
$cache->remove($adminCacheKey);
172+
$userCacheKey = Application::MODELS_CACHE_KEY . '_' . ($userId ?? '');
173+
$cache->remove($userCacheKey);
174+
165175
try {
166176
$this->logger->debug('Actually getting OpenAI models with a network request');
167177
$modelsResponse = $this->request($userId, 'models');
168178
} catch (Exception $e) {
169179
$this->logger->warning('Error retrieving models (exc): ' . $e->getMessage());
170-
$this->areCredsValid = false;
171180
throw $e;
172181
}
173182
if (isset($modelsResponse['error'])) {
174183
$this->logger->warning('Error retrieving models: ' . json_encode($modelsResponse));
175-
$this->areCredsValid = false;
176184
throw new Exception($modelsResponse['error'], Http::STATUS_INTERNAL_SERVER_ERROR);
177185
}
178186
if (!isset($modelsResponse['data'])) {
@@ -182,13 +190,14 @@ public function getModels(?string $userId): array {
182190

183191
if (!$this->isModelListValid($modelsResponse['data'])) {
184192
$this->logger->warning('Invalid models response: ' . json_encode($modelsResponse));
185-
$this->areCredsValid = false;
186193
throw new Exception($this->l10n->t('Invalid models response received'), Http::STATUS_INTERNAL_SERVER_ERROR);
187194
}
188195

189196
$cache->set($userId !== null ? $userCacheKey : $adminCacheKey, $modelsResponse, Application::MODELS_CACHE_TTL);
190197
$this->modelsMemoryCache = $modelsResponse;
191-
$this->areCredsValid = true;
198+
// we always store the model list after getting it
199+
$modelsObjectString = json_encode($modelsResponse);
200+
$this->appConfig->setValueString(Application::APP_ID, 'models', $modelsObjectString);
192201
return $modelsResponse;
193202
}
194203

0 commit comments

Comments
 (0)