Skip to content

Commit

Permalink
add support for scoped context in query
Browse files Browse the repository at this point in the history
Signed-off-by: Anupam Kumar <[email protected]>
  • Loading branch information
kyteinsky committed Mar 1, 2024
1 parent 6c5cd67 commit ca7c6c0
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 3 deletions.
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OCA\ContextChat\Listener\FileListener;
use OCA\ContextChat\TextProcessing\ContextChatProvider;
use OCA\ContextChat\TextProcessing\FreePromptProvider;
use OCA\ContextChat\TextProcessing\ScopedContextChatProvider;
use OCP\App\Events\AppDisableEvent;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
Expand Down Expand Up @@ -72,6 +73,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(AppDisableEvent::class, AppDisableListener::class);
$context->registerTextProcessingProvider(ContextChatProvider::class);
$context->registerTextProcessingProvider(FreePromptProvider::class);
$context->registerTextProcessingProvider(ScopedContextChatProvider::class);
}

public function boot(IBootContext $context): void {
Expand Down
43 changes: 42 additions & 1 deletion lib/Command/Prompt.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

namespace OCA\ContextChat\Command;

use OCA\ContextChat\Service\ScopeType;
use OCA\ContextChat\TextProcessing\ContextChatTaskType;
use OCA\ContextChat\TextProcessing\ScopedContextChatTaskType;
use OCP\TextProcessing\FreePromptTaskType;
use OCP\TextProcessing\IManager;
use OCP\TextProcessing\Task;
Expand Down Expand Up @@ -43,16 +45,55 @@ protected function configure() {
InputArgument::REQUIRED,
'The prompt'
)
->addOption('no-context', null, InputOption::VALUE_NONE, 'Do not use context');
->addOption(
'no-context',
null,
InputOption::VALUE_NONE,
'Do not use context'
)
->addOption(
'context-sources',
null,
InputOption::VALUE_REQUIRED,
'Context sources to use',
)
->addOption(
'context-providers',
null,
InputOption::VALUE_REQUIRED,
'Context provider to use',
);
}

protected function execute(InputInterface $input, OutputInterface $output) {
$userId = $input->getArgument('uid');
$prompt = $input->getArgument('prompt');
$noContext = $input->getOption('no-context');
$contextSources = $input->getOption('context-sources');
$contextProviders = $input->getOption('context-providers');

if ($noContext && (!empty($contextSources) || !empty($contextProviders))) {
throw new \InvalidArgumentException('Cannot use --no-context with --context-sources or --context-provider');
}

if (!empty($contextSources) && !empty($contextProviders)) {
throw new \InvalidArgumentException('Cannot use --context-sources with --context-provider');
}

if ($noContext) {
$task = new Task(FreePromptTaskType::class, $prompt, 'context_chat', $userId);
} elseif (!empty($contextSources)) {
$task = new Task(ScopedContextChatTaskType::class, json_encode([
'scopeType' => ScopeType::SOURCE,
'scopeList' => explode(',', $contextSources),
'prompt' => $prompt,
]), 'context_chat', $userId);
} elseif (!empty($contextProviders)) {
$task = new Task(ScopedContextChatTaskType::class, json_encode([
'scopeType' => ScopeType::PROVIDER,
'scopeList' => explode(',', $contextProviders),
'prompt' => $prompt,
]), 'context_chat', $userId);
} else {
$task = new Task(ContextChatTaskType::class, $prompt, 'context_chat', $userId);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Command/ScanFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected function configure() {
InputArgument::REQUIRED,
'The user ID to scan the storage of'
)
->addOption('mimetype', 'm', InputOption::VALUE_OPTIONAL, 'The mime type filter');
->addOption('mimetype', 'm', InputOption::VALUE_REQUIRED, 'The mime type filter');
}

protected function execute(InputInterface $input, OutputInterface $output) {
Expand Down
24 changes: 24 additions & 0 deletions lib/Service/LangRopeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
use Psr\Log\LoggerInterface;
use RuntimeException;

enum ScopeType: string {
case PROVIDER = 'provider';
case SOURCE = 'source';
}

class LangRopeService {
public function __construct(
private LoggerInterface $logger,
Expand Down Expand Up @@ -187,6 +192,25 @@ public function query(string $userId, string $prompt, bool $useContext = true):
return ['message' => $this->getWithPresentableSources($response['output'] ?? '', ...($response['sources'] ?? []))];
}

/**
* @param string $userId
* @param string $prompt
* @param ScopeType $scopeType
* @param array<string> $scopeList
* @return array
*/
public function scopedQuery(string $userId, string $prompt, ScopeType $scopeType, array $scopeList): array {
$params = [
'query' => $prompt,
'userId' => $userId,
'scopeType' => $scopeType,
'scopeList' => $scopeList,
];

$response = $this->requestToExApp('/scopedQuery', 'POST', $params);
return ['message' => $this->getWithPresentableSources($response['output'] ?? '', ...($response['sources'] ?? []))];
}

public function getWithPresentableSources(string $llmResponse, string ...$sourceRefs): string {
if (count($sourceRefs) === 0) {
return $llmResponse;
Expand Down
6 changes: 5 additions & 1 deletion lib/TextProcessing/ContextChatProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ public function __construct(
}

public function getName(): string {
return $this->l10n->t('Nextcloud Assistant Context Chat provider');
return $this->l10n->t('Nextcloud Assistant Context Chat Provider');
}

public function process(string $prompt): string {
if ($this->userId === null) {
throw new \RuntimeException('User ID is required to process the prompt.');
}

$response = $this->langRopeService->query($this->userId, $prompt);
if (isset($response['error'])) {
throw new \RuntimeException('No result in ContextChat response. ' . $response['error']);
Expand Down
4 changes: 4 additions & 0 deletions lib/TextProcessing/FreePromptProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public function getName(): string {
}

public function process(string $prompt): string {
if ($this->userId === null) {
throw new \RuntimeException('User ID is required to process the prompt.');
}

$response = $this->langRopeService->query($this->userId, $prompt, false);
if (isset($response['error'])) {
throw new \RuntimeException('No result in ContextChat response. ' . $response['error']);
Expand Down
92 changes: 92 additions & 0 deletions lib/TextProcessing/ScopedContextChatProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);
namespace OCA\ContextChat\TextProcessing;

use OCA\ContextChat\Service\LangRopeService;
use OCA\ContextChat\Service\ScopeType;
use OCP\IL10N;
use OCP\TextProcessing\IProvider;
use OCP\TextProcessing\IProviderWithUserId;

/**
* @template-implements IProviderWithUserId<ScopedContextChatTaskType>
* @template-implements IProvider<ScopedContextChatTaskType>
*/
class ScopedContextChatProvider implements IProvider, IProviderWithUserId {

private ?string $userId = null;

public function __construct(
private LangRopeService $langRopeService,
private IL10N $l10n,
) {
}

public function getName(): string {
return $this->l10n->t('Nextcloud Assistant Scoped Context Chat Provider');
}

/**
* @param string $prompt JSON string with the following structure:
* {
* "scopeType": string,
* "scopeList": list[string],
* "prompt": string,
* }
*
* @return string
*/
public function process(string $prompt): string {
if ($this->userId === null) {
throw new \RuntimeException('User ID is required to process the prompt.');
}

try {
$parsedData = json_decode($prompt, true, flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE);
} catch (\JsonException $e) {
throw new \RuntimeException(
'Invalid JSON string, expected { "scopeType": string, "scopeList": list[string], "prompt": string }',
intval($e->getCode()), $e,
);
}

if (
!is_array($parsedData)
|| !isset($parsedData['scopeType'])
|| !is_string($parsedData['scopeType'])
|| !isset($parsedData['scopeList'])
|| !is_array($parsedData['scopeList'])
|| !isset($parsedData['prompt'])
|| !is_string($parsedData['prompt'])
) {
throw new \RuntimeException('Invalid JSON string, expected { "scopeType": string, "scopeList": list[string], "prompt": string }');
}

$scopeTypeEnum = ScopeType::tryFrom($parsedData['scopeType']);
if ($scopeTypeEnum === null) {
throw new \RuntimeException('Invalid scope type: ' . $parsedData['scopeType']);
}

$response = $this->langRopeService->scopedQuery(
$this->userId,
$parsedData['prompt'],
$scopeTypeEnum,
$parsedData['scopeList'],
);

if (isset($response['error'])) {
throw new \RuntimeException('No result in ContextChat response. ' . $response['error']);
}

return $response['message'] ?? '';
}

public function getTaskType(): string {
return ScopedContextChatTaskType::class;
}

public function setUserId(?string $userId): void {
$this->userId = $userId;
}
}
52 changes: 52 additions & 0 deletions lib/TextProcessing/ScopedContextChatTaskType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Julien Veyssier <[email protected]>
*
* @author Julien Veyssier <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace OCA\ContextChat\TextProcessing;

use OCP\IL10N;
use OCP\TextProcessing\ITaskType;

class ScopedContextChatTaskType implements ITaskType {
public function __construct(
private IL10N $l,
) {
}

/**
* @inheritDoc
* @since 27.1.0
*/
public function getName(): string {
return $this->l->t('Scoped Context Chat');
}

/**
* @inheritDoc
* @since 27.1.0
*/
public function getDescription(): string {
return $this->l->t('Ask a question about the data selected by you.');
}
}

0 comments on commit ca7c6c0

Please sign in to comment.