diff --git a/lib/TaskProcessing/AnalyzeImagesProvider.php b/lib/TaskProcessing/AnalyzeImagesProvider.php index b39878ed..361f5603 100644 --- a/lib/TaskProcessing/AnalyzeImagesProvider.php +++ b/lib/TaskProcessing/AnalyzeImagesProvider.php @@ -92,7 +92,13 @@ public function getOutputShapeEnumValues(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOptionalOutputShapeEnumValues(): array { @@ -198,29 +204,46 @@ public function process( if ($preferStreaming) { $chunks = $this->openAiAPIService->createStreamedChatCompletion($userId, $model, $prompt, $systemPrompt, $history, 1, $maxTokens); $time = microtime(true); - $fullOutput = ''; + $streamedOutput = ''; + $streamedReasoning = ''; foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $fullOutput .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $fullOutput]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($fullOutput !== '') { - $reportOutput(['output' => $fullOutput]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn()['messages']; + $returnValue = $chunks->getReturn(); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } else { - $completion = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, $systemPrompt, $history, 1, $maxTokens); - $completion = $completion['messages']; + $returnValue = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, $systemPrompt, $history, 1, $maxTokens); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } if (count($completion) > 0) { - return ['output' => array_pop($completion)]; + return [ + 'output' => array_pop($completion), + 'reasoning' => count($reasoning) > 0 ? array_pop($reasoning) : '', + ]; } throw new ProcessingException('No result in OpenAI/LocalAI response.'); diff --git a/lib/TaskProcessing/ChangeToneProvider.php b/lib/TaskProcessing/ChangeToneProvider.php index a1f52489..994cc5a3 100644 --- a/lib/TaskProcessing/ChangeToneProvider.php +++ b/lib/TaskProcessing/ChangeToneProvider.php @@ -104,7 +104,13 @@ public function getOutputShapeEnumValues(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOptionalOutputShapeEnumValues(): array { @@ -136,8 +142,10 @@ public function process( } $chunks = $this->chunkService->chunkSplitPrompt($textInput, true, $maxTokens); - $streamedResult = ''; - $result = ''; + $streamedOutput = ''; + $streamedReasoning = ''; + $fullOutput = ''; + $fullReasoning = ''; $increase = 1.0 / (float)count($chunks); $progress = 0.0; foreach ($chunks as $textInput) { @@ -148,26 +156,40 @@ public function process( $chunks = $this->openAiAPIService->createStreamedChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); $time = microtime(true); foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $streamedResult .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $streamedResult]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($streamedResult !== '') { - $reportOutput(['output' => $streamedResult]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn()['messages']; + $returnValue = $chunks->getReturn(); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } else { - $completion = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); - $completion = $completion['messages']; + $returnValue = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } } else { $completion = $this->openAiAPIService->createCompletion($userId, $prompt, 1, $model, $maxTokens); + $reasoning = []; } } catch (UserFacingProcessingException $e) { throw $e; @@ -176,8 +198,11 @@ public function process( } $progress += $increase; $reportProgress($progress); + if (count($reasoning) > 0) { + $fullReasoning .= array_pop($reasoning); + } if (count($completion) > 0) { - $result .= array_pop($completion); + $fullOutput .= array_pop($completion); continue; } @@ -185,6 +210,9 @@ public function process( } $endTime = time(); $this->openAiAPIService->updateExpTextProcessingTime($endTime - $startTime); - return ['output' => $result]; + return [ + 'output' => $fullOutput, + 'reasoning' => $fullReasoning, + ]; } } diff --git a/lib/TaskProcessing/ContextWriteProvider.php b/lib/TaskProcessing/ContextWriteProvider.php index f3ad45ed..f7156456 100644 --- a/lib/TaskProcessing/ContextWriteProvider.php +++ b/lib/TaskProcessing/ContextWriteProvider.php @@ -92,7 +92,13 @@ public function getOutputShapeEnumValues(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOptionalOutputShapeEnumValues(): array { @@ -128,10 +134,13 @@ public function process( } $chunks = $this->chunkService->chunkSplitPrompt($sourceMaterial, true, $maxTokens); - $result = ''; + $fullOutput = ''; + $fullReasoning = ''; + $increase = 1.0 / (float)count($chunks); $progress = 0.0; - $fullOutput = ''; + $streamedOutput = ''; + $streamedReasoning = ''; foreach ($chunks as $sourceMaterial) { $prompt = 'You\'re a professional copywriter tasked with copying an instructed or demonstrated *WRITING STYLE*' @@ -148,34 +157,51 @@ public function process( $chunks = $this->openAiAPIService->createStreamedChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); $time = microtime(true); foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $fullOutput .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $fullOutput]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($fullOutput !== '') { - $reportOutput(['output' => $fullOutput]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn()['messages']; + $returnValue = $chunks->getReturn(); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } else { - $completion = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); - $completion = $completion['messages']; + $returnValue = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } } else { $completion = $this->openAiAPIService->createCompletion($userId, $prompt, 1, $model, $maxTokens); + $reasoning = []; } } catch (UserFacingProcessingException $e) { throw $e; } catch (\Throwable $e) { throw new ProcessingException('OpenAI/LocalAI request failed: ' . $e->getMessage()); } + if (count($reasoning) > 0) { + $fullReasoning .= array_pop($reasoning); + } if (count($completion) > 0) { - $result .= array_pop($completion); + $fullOutput .= array_pop($completion); $progress += $increase; $reportProgress($progress); continue; @@ -185,6 +211,9 @@ public function process( } $endTime = time(); $this->openAiAPIService->updateExpTextProcessingTime($endTime - $startTime); - return ['output' => $result]; + return [ + 'output' => $fullOutput, + 'reasoning' => $fullReasoning, + ]; } } diff --git a/lib/TaskProcessing/ReformulateProvider.php b/lib/TaskProcessing/ReformulateProvider.php index d2250ab0..cce1845f 100644 --- a/lib/TaskProcessing/ReformulateProvider.php +++ b/lib/TaskProcessing/ReformulateProvider.php @@ -92,7 +92,13 @@ public function getOutputShapeEnumValues(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOptionalOutputShapeEnumValues(): array { @@ -122,10 +128,12 @@ public function process( $model = $this->openAiSettingsService->getAdminDefaultCompletionModelId(); } $chunks = $this->chunkService->chunkSplitPrompt($prompt, true, $maxTokens); - $result = ''; + $fullOutput = ''; + $fullReasoning = ''; $increase = 1.0 / (float)count($chunks); $progress = 0.0; - $fullOutput = ''; + $streamedOutput = ''; + $streamedReasoning = ''; foreach ($chunks as $chunk) { $prompt = 'Reformulate the following text. Use the same language as the original text. Output only the reformulation. Here is the text:' . "\n\n" . $chunk . "\n\n" . 'Do not mention the used language in your reformulation. Here is your reformulation in the same language:'; @@ -135,34 +143,51 @@ public function process( $chunks = $this->openAiAPIService->createStreamedChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); $time = microtime(true); foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $fullOutput .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $fullOutput]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($fullOutput !== '') { - $reportOutput(['output' => $fullOutput]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn()['messages']; + $returnValue = $chunks->getReturn(); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } else { - $completion = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); - $completion = $completion['messages']; + $returnValue = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } } else { $completion = $this->openAiAPIService->createCompletion($userId, $prompt, 1, $model, $maxTokens); + $reasoning = []; } } catch (UserFacingProcessingException $e) { throw $e; } catch (\Throwable $e) { throw new ProcessingException('OpenAI/LocalAI request failed: ' . $e->getMessage()); } + if (count($reasoning) > 0) { + $fullReasoning .= array_pop($reasoning); + } if (count($completion) > 0) { - $result .= array_pop($completion); + $fullOutput .= array_pop($completion); $progress += $increase; $reportProgress($progress); continue; @@ -173,6 +198,9 @@ public function process( $endTime = time(); $this->openAiAPIService->updateExpTextProcessingTime($endTime - $startTime); - return ['output' => $result]; + return [ + 'output' => $fullOutput, + 'reasoning' => $fullReasoning, + ]; } } diff --git a/lib/TaskProcessing/TextToTextChatProvider.php b/lib/TaskProcessing/TextToTextChatProvider.php index 85c86e46..14b1d2c3 100644 --- a/lib/TaskProcessing/TextToTextChatProvider.php +++ b/lib/TaskProcessing/TextToTextChatProvider.php @@ -83,7 +83,13 @@ public function getOutputShapeEnumValues(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOptionalOutputShapeEnumValues(): array { @@ -127,25 +133,39 @@ public function process( if ($preferStreaming) { $chunks = $this->openAiAPIService->createStreamedChatCompletion($userId, $adminModel, $userPrompt, $systemPrompt, $history, 1, $maxTokens); $time = microtime(true); - $fullOutput = ''; + $streamedOutput = ''; + $streamedReasoning = ''; foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $fullOutput .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $fullOutput]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($fullOutput !== '') { - $reportOutput(['output' => $fullOutput]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn()['messages']; + $returnValue = $chunks->getReturn(); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } else { - $completion = $this->openAiAPIService->createChatCompletion($userId, $adminModel, $userPrompt, $systemPrompt, $history, 1, $maxTokens); - $completion = $completion['messages']; + $returnValue = $this->openAiAPIService->createChatCompletion($userId, $adminModel, $userPrompt, $systemPrompt, $history, 1, $maxTokens); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } } catch (UserFacingProcessingException $e) { throw $e; @@ -155,7 +175,10 @@ public function process( if (count($completion) > 0) { $endTime = time(); $this->openAiAPIService->updateExpTextProcessingTime($endTime - $startTime); - return ['output' => array_pop($completion)]; + return [ + 'output' => array_pop($completion), + 'reasoning' => count($reasoning) > 0 ? array_pop($reasoning) : '', + ]; } throw new ProcessingException('No result in OpenAI/LocalAI response.'); diff --git a/lib/TaskProcessing/TextToTextChatWithToolsProvider.php b/lib/TaskProcessing/TextToTextChatWithToolsProvider.php index dba51267..7616c896 100644 --- a/lib/TaskProcessing/TextToTextChatWithToolsProvider.php +++ b/lib/TaskProcessing/TextToTextChatWithToolsProvider.php @@ -78,7 +78,13 @@ public function getOutputShapeEnumValues(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOptionalOutputShapeEnumValues(): array { @@ -138,24 +144,35 @@ public function process( $userId, $adminModel, $userPrompt, $systemPrompt, $history, 1, $maxTokens, null, $toolMessage, $tools ); $time = microtime(true); - $fullOutput = ''; + $streamedOutput = ''; + $streamedReasoning = ''; foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $fullOutput .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $fullOutput]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($fullOutput !== '') { - $reportOutput(['output' => $fullOutput]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn(); + $returnValue = $chunks->getReturn(); } else { - $completion = $this->openAiAPIService->createChatCompletion( + $returnValue = $this->openAiAPIService->createChatCompletion( $userId, $adminModel, $userPrompt, $systemPrompt, $history, 1, $maxTokens, null, $toolMessage, $tools ); } @@ -164,12 +181,13 @@ public function process( } catch (\Throwable $e) { throw new ProcessingException('OpenAI/LocalAI request failed: ' . $e->getMessage()); } - if (count($completion['messages']) > 0 || count($completion['tool_calls']) > 0) { + if (count($returnValue['messages']) > 0 || count($returnValue['tool_calls']) > 0) { $endTime = time(); $this->openAiAPIService->updateExpTextProcessingTime($endTime - $startTime); return [ - 'output' => array_pop($completion['messages']) ?? '', - 'tool_calls' => array_pop($completion['tool_calls']) ?? '', + 'output' => array_pop($returnValue['messages']) ?? '', + 'reasoning' => count($returnValue['reasoning_messages']) > 0 ? array_pop($returnValue['reasoning_messages']) : '', + 'tool_calls' => array_pop($returnValue['tool_calls']) ?? '', ]; } diff --git a/lib/TaskProcessing/TextToTextProvider.php b/lib/TaskProcessing/TextToTextProvider.php index 5b825448..5b8c0a3f 100644 --- a/lib/TaskProcessing/TextToTextProvider.php +++ b/lib/TaskProcessing/TextToTextProvider.php @@ -86,7 +86,13 @@ public function getOptionalInputShapeDefaults(): array { } public function getOptionalOutputShape(): array { - return []; + return [ + 'reasoning' => new ShapeDescriptor( + $this->l->t('Reasoning content'), + $this->l->t('The model reasoning behind the output'), + EShapeType::Text, + ), + ]; } public function getOutputShapeEnumValues(): array { @@ -102,13 +108,7 @@ public function process( ): array { $reportOutput = $options->getReportIntermediateOutput(); $preferStreaming = $options->getPreferStreaming(); - /* - foreach (range(1, 20) as $i) { - $reportProgress($i / 100 * 5); - error_log('aa ' . ($i / 100 * 5)); - sleep(1); - } - */ + $startTime = time(); if (!isset($input['input']) || !is_string($input['input'])) { throw new ProcessingException('Invalid prompt'); @@ -131,28 +131,43 @@ public function process( if ($preferStreaming) { $chunks = $this->openAiAPIService->createStreamedChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); $time = microtime(true); - $fullOutput = ''; + $streamedOutput = ''; + $streamedReasoning = ''; foreach ($chunks as $chunk) { - if (($chunk['kind'] ?? null) !== 'content') { + if (!in_array($chunk['kind'] ?? null, ['content', 'reasoning_content'], true)) { continue; } - $fullOutput .= $chunk['text']; + if ($chunk['kind'] === 'reasoning_content') { + $streamedReasoning .= $chunk['text']; + } elseif ($chunk['kind'] === 'content') { + $streamedOutput .= $chunk['text']; + } // we don't report more often than every 250ms if (microtime(true) - $time >= 0.25) { - $reportOutput(['output' => $fullOutput]); + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); $time = microtime(true); } } - if ($fullOutput !== '') { - $reportOutput(['output' => $fullOutput]); + if ($streamedOutput !== '' || $streamedReasoning !== '') { + $reportOutput([ + 'output' => $streamedOutput, + 'reasoning' => $streamedReasoning, + ]); } - $completion = $chunks->getReturn()['messages']; + $returnValue = $chunks->getReturn(); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } else { - $completion = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); - $completion = $completion['messages']; + $returnValue = $this->openAiAPIService->createChatCompletion($userId, $model, $prompt, null, null, 1, $maxTokens); + $completion = $returnValue['messages']; + $reasoning = $returnValue['reasoning_messages']; } } else { $completion = $this->openAiAPIService->createCompletion($userId, $prompt, 1, $model, $maxTokens); + $reasoning = []; } } catch (UserFacingProcessingException $e) { throw $e; @@ -162,7 +177,10 @@ public function process( if (count($completion) > 0) { $endTime = time(); $this->openAiAPIService->updateExpTextProcessingTime($endTime - $startTime); - return ['output' => array_pop($completion)]; + return [ + 'output' => array_pop($completion), + 'reasoning' => count($reasoning) > 0 ? array_pop($reasoning) : '', + ]; } throw new ProcessingException('No result in OpenAI/LocalAI response.');