Skip to content
Closed
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
3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class Application extends App implements IBootstrap {
public const DEFAULT_QUOTA_PERIOD = 30;
public const DEFAULT_QUOTA_CONFIG = ['length' => self::DEFAULT_QUOTA_PERIOD, 'unit' => 'day', 'day' => 1];

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_OPENAI_TEXT_GENERATION_TIME = 10; // seconds
public const DEFAULT_LOCALAI_TEXT_GENERATION_TIME = 60; // seconds
public const DEFAULT_OPENAI_IMAGE_GENERATION_TIME = 20; // seconds
Expand Down
14 changes: 14 additions & 0 deletions lib/Service/OpenAiSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,20 @@ public function getQuotaPeriod(): array {
return $value;
}

/**
* @return string
*/
public function getSummarySystemPrompt(): string {
return $this->appConfig->getValueString(Application::APP_ID, 'system_prompt_summary', Application::DEFAULT_SUMMARY_SYSTEM_PROMPT, lazy: true) ?: Application::DEFAULT_SUMMARY_SYSTEM_PROMPT;
}

/**
* @return string
*/
public function getTalkSummarySystemPrompt(): string {
return $this->appConfig->getValueString(Application::APP_ID, 'system_prompt_talk_summary', Application::DEFAULT_SUMMARY_SYSTEM_PROMPT, lazy: true) ?: Application::DEFAULT_SUMMARY_SYSTEM_PROMPT;
}

/**
* @return int[]
*/
Expand Down
14 changes: 12 additions & 2 deletions lib/TaskProcessing/SummaryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public function getOptionalInputShape(): array {
$this->l->t('The model used to generate the completion'),
EShapeType::Enum
),
'talk_meeting' => new ShapeDescriptor(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

talk_meeting suggests sth related to meeting content, it should be is_talk_meeting or is_from_talk_meeting for boolean

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could even make it more general with sth like: 'source': '<enum>'

$this->l->t("Talk meeting flag"),
$this->l->t('Flag that indicates if the summary is generated from a Talk meeting recording.'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flag indicating whether ... sounds more natural and concise

EShapeType::Number
),
];
}

Expand Down Expand Up @@ -106,6 +111,7 @@ public function getOptionalInputShapeDefaults(): array {
'model' => $adminModel,
'format' => 'auto',
'complexity' => 'medium',
'talk_meeting' => 0,
];
}

Expand Down Expand Up @@ -139,6 +145,7 @@ public function process(?string $userId, array $input, callable $reportProgress)
$model = $input['model'];
}

$isTalkMeeting = (bool)($input['talk_meeting'] ?? 0);
$prompts = $this->chunkService->chunkSplitPrompt($prompt);
$newNumChunks = count($prompts);
$progress = 0.0;
Expand All @@ -153,8 +160,11 @@ public function process(?string $userId, array $input, callable $reportProgress)

try {
$completions = [];
$summarySystemPrompt = '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. ';
if ($isTalkMeeting) {
$summarySystemPrompt = $this->openAiSettingsService->getTalkSummarySystemPrompt();
} else {
$summarySystemPrompt = $this->openAiSettingsService->getSummarySystemPrompt();
}
if (isset($input['format'])) {
if ($input['format'] === 'paragraph') {
$summarySystemPrompt .= 'Return the summary as a paragraph. ';
Expand Down
164 changes: 164 additions & 0 deletions tests/unit/Providers/OpenAiProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,170 @@ public function testSummaryProvider(): void {
$this->quotaUsageMapper->deleteUserQuotaUsages(self::TEST_USER1);
}

public function testSummaryProviderConfigurableSystemPrompt(): void {
try {
$configurableSystemPrompt = 'Configurable summary system prompt from app config';
$appConfig = \OCP\Server::get(IAppConfig::class);
$appConfig->setValueString(
Application::APP_ID,
'system_prompt_summary',
$configurableSystemPrompt,
);

$summaryProvider = new SummaryProvider(
$this->openAiApiService,
$this->openAiSettingsService,
$this->createMock(\OCP\IL10N::class),
$this->chunkService,
self::TEST_USER1,
);

$prompt = 'This is a test prompt';
$n = 1;

$response = '{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-3.5-turbo-0613",
"system_fingerprint": "fp_44709d6fcb",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "This is a test response."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"total_tokens": 21
}
}';

$url = self::OPENAI_API_BASE . 'chat/completions';

$options = ['timeout' => Application::OPENAI_DEFAULT_REQUEST_TIMEOUT, 'headers' => ['User-Agent' => Application::USER_AGENT, 'Authorization' => self::AUTHORIZATION_HEADER, 'Content-Type' => 'application/json']];
$options['body'] = json_encode([
'model' => Application::DEFAULT_COMPLETION_MODEL_ID,
'messages' => [
['role' => 'system', 'content' => $configurableSystemPrompt],
['role' => 'user', 'content' => $prompt],
],
'n' => $n,
'stream' => false,
'max_completion_tokens' => Application::DEFAULT_MAX_NUM_OF_TOKENS,
'user' => self::TEST_USER1,
]);

$iResponse = $this->createMock(\OCP\Http\Client\IResponse::class);
$iResponse->method('getBody')->willReturn($response);
$iResponse->method('getStatusCode')->willReturn(200);
$iResponse->method('getHeader')->with('Content-Type')->willReturn('application/json');

$this->iClient->expects($this->once())->method('post')->with($url, $options)->willReturn($iResponse);

$result = $summaryProvider->process(self::TEST_USER1, ['input' => $prompt], fn () => true);
$this->assertEquals('This is a test response.', $result['output']);

// Check that token usage is logged properly
$usage = $this->quotaUsageMapper->getQuotaUnitsOfUser(self::TEST_USER1, Application::QUOTA_TYPE_TEXT);
$this->assertEquals(21, $usage);
// Clear quota usage
$this->quotaUsageMapper->deleteUserQuotaUsages(self::TEST_USER1);
} finally {
$appConfig->deleteKey(
Application::APP_ID,
'system_prompt_summary'
);
}
}

public function testSummaryProviderConfigurableTalkSystemPrompt(): void {
try {
$configurableSystemPrompt = 'Configurable Talk summary system prompt from app config';
$appConfig = \OCP\Server::get(IAppConfig::class);
$appConfig->setValueString(
Application::APP_ID,
'system_prompt_talk_summary',
$configurableSystemPrompt,
);

$summaryProvider = new SummaryProvider(
$this->openAiApiService,
$this->openAiSettingsService,
$this->createMock(\OCP\IL10N::class),
$this->chunkService,
self::TEST_USER1,
);

$prompt = 'This is a test prompt';
$n = 1;

$response = '{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-3.5-turbo-0613",
"system_fingerprint": "fp_44709d6fcb",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "This is a test response."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"total_tokens": 21
}
}';

$url = self::OPENAI_API_BASE . 'chat/completions';

$options = ['timeout' => Application::OPENAI_DEFAULT_REQUEST_TIMEOUT, 'headers' => ['User-Agent' => Application::USER_AGENT, 'Authorization' => self::AUTHORIZATION_HEADER, 'Content-Type' => 'application/json']];
$options['body'] = json_encode([
'model' => Application::DEFAULT_COMPLETION_MODEL_ID,
'messages' => [
['role' => 'system', 'content' => $configurableSystemPrompt],
['role' => 'user', 'content' => $prompt],
],
'n' => $n,
'stream' => false,
'max_completion_tokens' => Application::DEFAULT_MAX_NUM_OF_TOKENS,
'user' => self::TEST_USER1,
]);

$iResponse = $this->createMock(\OCP\Http\Client\IResponse::class);
$iResponse->method('getBody')->willReturn($response);
$iResponse->method('getStatusCode')->willReturn(200);
$iResponse->method('getHeader')->with('Content-Type')->willReturn('application/json');

$this->iClient->expects($this->once())->method('post')->with($url, $options)->willReturn($iResponse);

$result = $summaryProvider->process(self::TEST_USER1, ['input' => $prompt, 'talk_meeting' => 1], fn () => true);
$this->assertEquals('This is a test response.', $result['output']);

// Check that token usage is logged properly
$usage = $this->quotaUsageMapper->getQuotaUnitsOfUser(self::TEST_USER1, Application::QUOTA_TYPE_TEXT);
$this->assertEquals(21, $usage);
// Clear quota usage
$this->quotaUsageMapper->deleteUserQuotaUsages(self::TEST_USER1);
} finally {
$appConfig->deleteKey(
Application::APP_ID,
'system_prompt_talk_summary'
);
}
}

public function testProofreadProvider(): void {
$proofreadProvider = new ProofreadProvider(
$this->openAiApiService,
Expand Down