diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 708d1b35..c34ddfe6 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -46,8 +46,6 @@ class Application extends App implements IBootstrap { public const DEFAULT_QUOTA_PERIOD = 30; public const MAX_QUOTA_PERIOD_DAYS = 3650; public const MAX_QUOTA_PERIOD_MONTHS = 120; - public const DEFAULT_SUMMARY_SYSTEM_PROMPT = 'You are a helpful assistant that summarizes text in the same language as the text. ' - . 'You should only return the summary without any additional information.'; public const DEFAULT_QUOTA_CONFIG = ['length' => self::DEFAULT_QUOTA_PERIOD, 'unit' => 'day', 'day' => 1]; public const DEFAULT_OPENAI_TEXT_GENERATION_TIME = 10; // seconds diff --git a/lib/Service/OpenAiSettingsService.php b/lib/Service/OpenAiSettingsService.php index 33e17570..8e48a555 100644 --- a/lib/Service/OpenAiSettingsService.php +++ b/lib/Service/OpenAiSettingsService.php @@ -27,7 +27,6 @@ class OpenAiSettingsService { private const ADMIN_CONFIG_TYPES = [ 'quota_period' => 'array', 'usage_storage_time' => 'integer', - 'summary_system_prompt' => 'string', ]; private const USER_CONFIG_TYPES = [ @@ -136,19 +135,6 @@ public function getUsageStorageTime(): int { return $this->appConfig->getValueInt(Application::APP_ID, 'usage_storage_time', Application::DEFAULT_QUOTA_PERIOD, lazy: true); } - /** - * System prompt for the summary provider. Empty falls back to the default unless $raw is true. - */ - public function getSummarySystemPrompt(bool $raw = false): string { - $default = $raw ? '' : Application::DEFAULT_SUMMARY_SYSTEM_PROMPT; - return $this->appConfig->getValueString( - Application::APP_ID, - 'summary_system_prompt', - $default, - lazy: true - ) ?: $default; - } - /** * @param string|null $userId * @return string @@ -160,13 +146,12 @@ public function getUserSTTLanguage(?string $userId): string { /** * Get the instance-wide admin config for the settings page * - * @return array{quota_period: array, usage_storage_time: int, summary_system_prompt: string} + * @return array{quota_period: array, usage_storage_time: int} */ public function getAdminConfig(): array { return [ 'quota_period' => $this->getQuotaPeriod(), 'usage_storage_time' => $this->getUsageStorageTime(), - 'summary_system_prompt' => $this->getSummarySystemPrompt(true), ]; } @@ -233,15 +218,6 @@ public function setUsageStorageTime(int $usageStorageTime): void { $this->appConfig->setValueInt(Application::APP_ID, 'usage_storage_time', $usageStorageTime, lazy: true); } - public function setSummarySystemPrompt(string $summarySystemPrompt): void { - $this->appConfig->setValueString( - Application::APP_ID, - 'summary_system_prompt', - $summarySystemPrompt, - lazy: true - ); - } - /** * @param string $userId * @param string $language @@ -274,9 +250,6 @@ public function setAdminConfig(array $adminConfig): void { if (isset($adminConfig['usage_storage_time'])) { $this->setUsageStorageTime($adminConfig['usage_storage_time']); } - if (isset($adminConfig['summary_system_prompt'])) { - $this->setSummarySystemPrompt($adminConfig['summary_system_prompt']); - } } /** diff --git a/lib/Service/ServiceConfig.php b/lib/Service/ServiceConfig.php index 0bd8b7bf..583be81b 100644 --- a/lib/Service/ServiceConfig.php +++ b/lib/Service/ServiceConfig.php @@ -38,6 +38,8 @@ class ServiceConfig implements JsonSerializable { 'llm_extra_params' => 'string', 'max_tokens' => 'integer', 'chunk_size' => 'integer', + 'system_prompt_summary' => 'string', + 'system_prompt_translate' => 'string', 'multimodal_image_enabled' => 'boolean', 'multimodal_audio_enabled' => 'boolean', 'multimodal_video_enabled' => 'boolean', @@ -83,6 +85,8 @@ public function __construct( private string $llmExtraParams = '', private int $maxTokens = Application::DEFAULT_MAX_NUM_OF_TOKENS, private int $chunkSize = Application::DEFAULT_CHUNK_SIZE, + private string $systemPromptSummary = '', + private string $systemPromptTranslate = '', // these mirror the defaults of the single-service configuration, so // that a migrated service and a newly connected one accept the same // kinds of attachment @@ -150,6 +154,10 @@ public function with(array $values): self { break; case 'chunk_size': $new->chunkSize = (int)$value === 0 ? 0 : max(Application::MIN_CHUNK_SIZE, (int)$value); break; + case 'system_prompt_summary': $new->systemPromptSummary = (string)$value; + break; + case 'system_prompt_translate': $new->systemPromptTranslate = (string)$value; + break; case 'multimodal_image_enabled': $new->multimodalImageEnabled = (bool)$value; break; case 'multimodal_audio_enabled': $new->multimodalAudioEnabled = (bool)$value; @@ -349,6 +357,24 @@ public function getChunkSize(): int { return $this->chunkSize; } + /** + * The admin-configured fallback system prompt for the summary task type. + * An empty string means the built-in default prompt is used. + */ + public function getSystemPromptSummary(): string { + return $this->systemPromptSummary; + } + + /** + * The admin-configured system prompt appended to the built-in translation + * prompt for the translation task type. The built-in prompt is always kept, + * as it enforces the expected JSON response format. + * An empty string means only the built-in prompt is used. + */ + public function getSystemPromptTranslate(): string { + return $this->systemPromptTranslate; + } + public function getMultimodalImageEnabled(): bool { return $this->multimodalImageEnabled; } @@ -466,6 +492,8 @@ public function jsonSerialize(): array { 'llm_extra_params' => $this->llmExtraParams, 'max_tokens' => $this->maxTokens, 'chunk_size' => $this->chunkSize, + 'system_prompt_summary' => $this->systemPromptSummary, + 'system_prompt_translate' => $this->systemPromptTranslate, 'multimodal_image_enabled' => $this->multimodalImageEnabled, 'multimodal_audio_enabled' => $this->multimodalAudioEnabled, 'multimodal_video_enabled' => $this->multimodalVideoEnabled, diff --git a/lib/Service/TranslateService.php b/lib/Service/TranslateService.php index c3190688..fff9dc9e 100644 --- a/lib/Service/TranslateService.php +++ b/lib/Service/TranslateService.php @@ -85,6 +85,12 @@ public function translate( $promptStart = 'Translate the following text to ' . $toLanguage . ': '; } + $systemPrompt = self::SYSTEM_PROMPT; + $adminSystemPrompt = $service->getSystemPromptTranslate(); + if ($adminSystemPrompt !== '') { + $systemPrompt .= PHP_EOL . $adminSystemPrompt; + } + $cache = $this->cacheFactory->createDistributed('integration_openai'); foreach ($chunks as $chunk) { $progress += $increase; @@ -105,12 +111,12 @@ public function translate( if ($service->isUsingOpenAi() || $service->getChatEndpointEnabled()) { $completionsObj = $this->openAiAPIService->createChatCompletion( - $userId, $service, $model, $prompt, TranslateService::SYSTEM_PROMPT, null, 1, $maxTokens, TranslateService::JSON_RESPONSE_FORMAT + $userId, $service, $model, $prompt, $systemPrompt, null, 1, $maxTokens, TranslateService::JSON_RESPONSE_FORMAT ); $completions = $completionsObj['messages']; } else { $completions = $this->openAiAPIService->createCompletion( - $userId, $service, $prompt . PHP_EOL . TranslateService::SYSTEM_PROMPT . PHP_EOL . PHP_EOL, 1, $model, $maxTokens + $userId, $service, $prompt . PHP_EOL . $systemPrompt . PHP_EOL . PHP_EOL, 1, $model, $maxTokens ); } diff --git a/lib/TaskProcessing/ProviderFactory.php b/lib/TaskProcessing/ProviderFactory.php index 2f2949e7..43a2792e 100644 --- a/lib/TaskProcessing/ProviderFactory.php +++ b/lib/TaskProcessing/ProviderFactory.php @@ -12,7 +12,6 @@ use OCA\OpenAi\AppInfo\Application; use OCA\OpenAi\Service\ChunkService; use OCA\OpenAi\Service\OpenAiAPIService; -use OCA\OpenAi\Service\OpenAiSettingsService; use OCA\OpenAi\Service\ServiceConfig; use OCA\OpenAi\Service\ServicesService; use OCA\OpenAi\Service\TranslateService; @@ -36,7 +35,6 @@ class ProviderFactory { public function __construct( private ServicesService $servicesService, private OpenAiAPIService $openAiAPIService, - private OpenAiSettingsService $openAiSettingsService, private ChunkService $chunkService, private TranslateService $translateService, private WatermarkingService $watermarkingService, @@ -82,7 +80,7 @@ private function getTextProviders(ServiceConfig $service, string $model): array new TextToTextProvider($this->openAiAPIService, $this->l, $service, $model), new TextToTextChatProvider($this->openAiAPIService, $this->l, $service, $model), new TextToTextChatWithToolsProvider($this->openAiAPIService, $this->l, $service, $model), - new SummaryProvider($this->openAiAPIService, $this->l, $this->chunkService, $this->openAiSettingsService, $service, $model), + new SummaryProvider($this->openAiAPIService, $this->l, $this->chunkService, $service, $model), new HeadlineProvider($this->openAiAPIService, $this->l, $service, $model), new TopicsProvider($this->openAiAPIService, $this->l, $this->chunkService, $this->logger, $service, $model), new ContextWriteProvider($this->openAiAPIService, $this->chunkService, $this->l, $service, $model), diff --git a/lib/TaskProcessing/SummaryProvider.php b/lib/TaskProcessing/SummaryProvider.php index 63c1e140..e5524c48 100644 --- a/lib/TaskProcessing/SummaryProvider.php +++ b/lib/TaskProcessing/SummaryProvider.php @@ -11,7 +11,6 @@ use OCA\OpenAi\Service\ChunkService; use OCA\OpenAi\Service\OpenAiAPIService; -use OCA\OpenAi\Service\OpenAiSettingsService; use OCA\OpenAi\Service\ServiceConfig; use OCP\IL10N; use OCP\TaskProcessing\EShapeType; @@ -25,11 +24,13 @@ class SummaryProvider implements ISynchronousProvider { use ProviderIdentity; + public const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant that summarizes text in the same language as the text. ' + . 'You should only return the summary without any additional information. '; + public function __construct( private OpenAiAPIService $openAiAPIService, private IL10N $l, private ChunkService $chunkService, - private OpenAiSettingsService $openAiSettingsService, private ServiceConfig $service, private string $model, ) { @@ -76,6 +77,11 @@ public function getOptionalInputShape(): array { $this->l->t('The maximum number of words/tokens that can be generated in the completion.'), EShapeType::Number ), + 'system_prompt_summary' => new ShapeDescriptor( + $this->l->t('Summary system prompt'), + $this->l->t('Summary system prompt. When set, it overrides the default prompt and the Format and Complexity options are ignored.'), + EShapeType::Text + ), ]; } @@ -100,6 +106,7 @@ public function getOptionalInputShapeDefaults(): array { 'max_tokens' => $this->service->getMaxTokens(), 'format' => 'auto', 'complexity' => 'medium', + 'system_prompt_summary' => '', ]; } @@ -144,21 +151,36 @@ public function process(?string $userId, array $input, callable $reportProgress) try { $completions = []; - $summarySystemPrompt = $this->openAiSettingsService->getSummarySystemPrompt() . ' '; - if (isset($input['format'])) { - if ($input['format'] === 'paragraph') { - $summarySystemPrompt .= 'Return the summary as a paragraph. '; - } elseif ($input['format'] === 'bullet_points') { - $summarySystemPrompt .= 'Return the summary as a list of bullet points. '; - } elseif ($input['format'] === 'sentence') { - $summarySystemPrompt .= 'Return the summary as a single sentence. Do not include more than one sentence. '; + $customSystemPrompt = isset($input['system_prompt_summary']) && is_string($input['system_prompt_summary']) + ? trim($input['system_prompt_summary']) + : ''; + if ($customSystemPrompt !== '') { + // A user provided prompt overrides the others; format and complexity are ignored + $summarySystemPrompt = $customSystemPrompt . ' '; + } else { + // Fallback to the admin-configured prompt, then to default. Format and complexity appended + $summarySystemPrompt = $this->service->getSystemPromptSummary(); + if ($summarySystemPrompt === '') { + $summarySystemPrompt = self::DEFAULT_SYSTEM_PROMPT; + } else { + $summarySystemPrompt .= ' '; } - } - if (isset($input['complexity'])) { - if ($input['complexity'] === 'complex') { - $summarySystemPrompt .= 'Use complex language and vocabulary appropriate for an expert in the subject. '; - } elseif ($input['complexity'] === 'simple') { - $summarySystemPrompt .= 'Use simple language and vocabulary appropriate for a 5 year old. '; + + if (isset($input['format'])) { + if ($input['format'] === 'paragraph') { + $summarySystemPrompt .= 'Return the summary as a paragraph. '; + } elseif ($input['format'] === 'bullet_points') { + $summarySystemPrompt .= 'Return the summary as a list of bullet points. '; + } elseif ($input['format'] === 'sentence') { + $summarySystemPrompt .= 'Return the summary as a single sentence. Do not include more than one sentence. '; + } + } + if (isset($input['complexity'])) { + if ($input['complexity'] === 'complex') { + $summarySystemPrompt .= 'Use complex language and vocabulary appropriate for an expert in the subject. '; + } elseif ($input['complexity'] === 'simple') { + $summarySystemPrompt .= 'Use simple language and vocabulary appropriate for a 5 year old. '; + } } } if ($this->service->isUsingOpenAi() || $this->service->getChatEndpointEnabled()) { diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue index 91f9c0b6..78803c7e 100644 --- a/src/components/AdminSettings.vue +++ b/src/components/AdminSettings.vue @@ -58,19 +58,6 @@ -