diff --git a/bin/V2/BaseInferenceCommand.php b/bin/V2/BaseInferenceCommand.php index bb6b4cee..27d5549d 100644 --- a/bin/V2/BaseInferenceCommand.php +++ b/bin/V2/BaseInferenceCommand.php @@ -8,7 +8,7 @@ use Mindee\Input\PathInput; use Mindee\Input\UrlInputSource; use Mindee\V2\Client; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Error\MindeeV2HttpException; use Mindee\V2\Parsing\Inference\BaseResponse; use Symfony\Component\Console\Command\Command; @@ -177,13 +177,13 @@ private function resolveInputSource(string $path): PathInput|UrlInputSource|null * @param InputInterface $input CLI input, used to read product-specific options. * @param string $modelId Model identifier. * @param string|null $alias Optional alias. - * @return BaseParameters Parameters object for the V2 client. + * @return BaseProductParameters Parameters object for the V2 client. */ abstract protected function buildParameters( InputInterface $input, string $modelId, ?string $alias - ): BaseParameters; + ): BaseProductParameters; /** * @return class-string Fully-qualified product response class. diff --git a/bin/V2/ClassificationCommand.php b/bin/V2/ClassificationCommand.php index 1f90016d..c272bfe3 100644 --- a/bin/V2/ClassificationCommand.php +++ b/bin/V2/ClassificationCommand.php @@ -4,7 +4,7 @@ namespace Mindee\Cli\V2; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Product\Classification\ClassificationResponse; use Mindee\V2\Product\Classification\Params\ClassificationParameters; use Symfony\Component\Console\Input\InputInterface; @@ -37,7 +37,7 @@ protected function buildParameters( InputInterface $input, string $modelId, ?string $alias - ): BaseParameters { + ): BaseProductParameters { return new ClassificationParameters($modelId, $alias); } } diff --git a/bin/V2/CropCommand.php b/bin/V2/CropCommand.php index 3ff5578b..446649c5 100644 --- a/bin/V2/CropCommand.php +++ b/bin/V2/CropCommand.php @@ -4,7 +4,7 @@ namespace Mindee\Cli\V2; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Product\Crop\CropResponse; use Mindee\V2\Product\Crop\Params\CropParameters; use Symfony\Component\Console\Input\InputInterface; @@ -37,7 +37,7 @@ protected function buildParameters( InputInterface $input, string $modelId, ?string $alias - ): BaseParameters { + ): BaseProductParameters { return new CropParameters($modelId, $alias); } } diff --git a/bin/V2/ExtractionCommand.php b/bin/V2/ExtractionCommand.php index 07f8bf27..8ab0e9a6 100644 --- a/bin/V2/ExtractionCommand.php +++ b/bin/V2/ExtractionCommand.php @@ -4,7 +4,7 @@ namespace Mindee\Cli\V2; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Parsing\Inference\BaseResponse; use Mindee\V2\Product\Extraction\ExtractionResponse; use Mindee\V2\Product\Extraction\Params\ExtractionParameters; @@ -76,7 +76,7 @@ protected function buildParameters( InputInterface $input, string $modelId, ?string $alias - ): BaseParameters { + ): BaseProductParameters { $rag = (bool) $input->getOption('rag'); $rawText = (bool) $input->getOption('raw-text'); $confidence = (bool) $input->getOption('confidence'); diff --git a/bin/V2/OcrCommand.php b/bin/V2/OcrCommand.php index 31079382..e4243687 100644 --- a/bin/V2/OcrCommand.php +++ b/bin/V2/OcrCommand.php @@ -4,7 +4,7 @@ namespace Mindee\Cli\V2; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Product\Ocr\OcrResponse; use Mindee\V2\Product\Ocr\Params\OcrParameters; use Symfony\Component\Console\Input\InputInterface; @@ -37,7 +37,7 @@ protected function buildParameters( InputInterface $input, string $modelId, ?string $alias - ): BaseParameters { + ): BaseProductParameters { return new OcrParameters($modelId, $alias); } } diff --git a/bin/V2/SearchModelsCommand.php b/bin/V2/SearchModelsCommand.php index c4ff273e..3031b1b4 100644 --- a/bin/V2/SearchModelsCommand.php +++ b/bin/V2/SearchModelsCommand.php @@ -7,6 +7,8 @@ use Exception; use Mindee\V2\Client; use Mindee\V2\Error\MindeeV2HttpException; +use Mindee\V2\Parsing\Search\ModelSearchResponse; +use Mindee\V2\Search\Models\ModelSearchParameters; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -83,7 +85,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client = new Client($apiKey ?: null); try { - $response = $client->searchModels($name ?: null, $modelType ?: null); + $response = $client->search( + ModelSearchResponse::class, + new ModelSearchParameters($name ?: null, $modelType ?: null) + ); } catch (MindeeV2HttpException $e) { $output->writeln('' . $e->getMessage() . ''); return Command::FAILURE; diff --git a/bin/V2/SearchRagDocumentsCommand.php b/bin/V2/SearchRagDocumentsCommand.php new file mode 100644 index 00000000..7a9f07af --- /dev/null +++ b/bin/V2/SearchRagDocumentsCommand.php @@ -0,0 +1,105 @@ +setName('search-rag-docs') + ->setDescription('Search available RAG documents for a given model.') + ->addOption( + 'api-key', + 'k', + InputOption::VALUE_REQUIRED, + 'Mindee V2 API key. Falls back to the MINDEE_V2_API_KEY environment variable.' + ) + ->addOption( + 'model-id', + 'm', + InputOption::VALUE_REQUIRED, + 'Filter by model ID.' + ) + ->addOption( + 'filename', + 'f', + InputOption::VALUE_REQUIRED, + 'Filter by file name partial match (case insensitive).' + ) + ->addOption( + 'raw-json', + 'r', + InputOption::VALUE_NONE, + 'Whether to output the raw JSON response.' + ); + } + + /** + * @param InputInterface $input CLI input. + * @param OutputInterface $output CLI output. + * @return integer Exit code. + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $apiKey = $input->getOption('api-key'); + if (!$apiKey && !getenv('MINDEE_V2_API_KEY')) { + $output->writeln( + 'The Mindee V2 API key is missing. ' + . "Please provide it via the '--api-key' option or the MINDEE_V2_API_KEY environment variable." + ); + return Command::FAILURE; + } + + $modelId = $input->getOption('model-id'); + if (!$modelId) { + $output->writeln('The --model-id option is required.'); + return Command::FAILURE; + } + $filename = $input->getOption('filename'); + $raw = (bool) $input->getOption('raw-json'); + + $client = new Client($apiKey ?: null); + + try { + $response = $client->search( + RagDocumentSearchResponse::class, + new RagDocumentSearchParameters($modelId, $filename ?: null) + ); + } catch (MindeeV2HttpException $e) { + $output->writeln('' . $e->getMessage() . ''); + return Command::FAILURE; + } catch (Exception $e) { + $output->writeln("Something went wrong, '" . $e->getMessage() . "' was raised."); + return Command::FAILURE; + } + + if ($raw) { + $output->writeln($response->getRawHttp()); + } else { + $output->write((string) $response); + } + return Command::SUCCESS; + } +} diff --git a/bin/V2/SplitCommand.php b/bin/V2/SplitCommand.php index c7bde490..9badd983 100644 --- a/bin/V2/SplitCommand.php +++ b/bin/V2/SplitCommand.php @@ -4,7 +4,7 @@ namespace Mindee\Cli\V2; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Product\Split\Params\SplitParameters; use Mindee\V2\Product\Split\SplitResponse; use Symfony\Component\Console\Input\InputInterface; @@ -37,7 +37,7 @@ protected function buildParameters( InputInterface $input, string $modelId, ?string $alias - ): BaseParameters { + ): BaseProductParameters { return new SplitParameters($modelId, $alias); } } diff --git a/bin/cli.php b/bin/cli.php index 7ab44b10..fa47ed6f 100755 --- a/bin/cli.php +++ b/bin/cli.php @@ -15,6 +15,7 @@ require __DIR__ . '/V2/OcrCommand.php'; require __DIR__ . '/V2/SplitCommand.php'; require __DIR__ . '/V2/SearchModelsCommand.php'; +require __DIR__ . '/V2/SearchRagDocumentsCommand.php'; use Exception; use Mindee\Cli\V2\ClassificationCommand; @@ -22,6 +23,7 @@ use Mindee\Cli\V2\ExtractionCommand; use Mindee\Cli\V2\OcrCommand; use Mindee\Cli\V2\SearchModelsCommand; +use Mindee\Cli\V2\SearchRagDocumentsCommand; use Mindee\Cli\V2\SplitCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\ArgvInput; @@ -139,8 +141,9 @@ function mindeeRewriteArgvForV1Compat(array $argv, array $knownTopLevelCommands) $cli->add($command); } $cli->add(new SearchModelsCommand()); +$cli->add(new SearchRagDocumentsCommand()); -$knownTopLevelCommands = ['v1', 'search-models', 'list', 'help', 'completion']; +$knownTopLevelCommands = ['v1', 'search-models', 'search-rag-docs', 'list', 'help', 'completion']; foreach ($v2InferenceCommands as $command) { $knownTopLevelCommands[] = $command->getName(); } diff --git a/src/Parsing/DateHelper.php b/src/Parsing/DateHelper.php new file mode 100644 index 00000000..c6f6c448 --- /dev/null +++ b/src/Parsing/DateHelper.php @@ -0,0 +1,51 @@ +mindeeApi->reqPostEnqueue($inputSource, $params); } @@ -102,18 +105,18 @@ public function getJob(string $jobId): JobResponse * @param string $responseClass The response class to construct. * @phpstan-param class-string $responseClass * @param InputSource $inputDoc Input document to parse. - * @param BaseParameters $params Parameters relating to prediction options. + * @param BaseProductParameters $params Parameters relating to prediction options. * @param PollingOptions|null $pollingOptions Options to apply to the polling. * @param CancellationToken|null $cancellationToken CancellationToken to check for cancellation. * @return BaseResponse A response containing parsing results. * @throws MindeeException Throws if enqueueing fails, job fails, or times out. */ public function enqueueAndGetResult( - string $responseClass, - InputSource $inputDoc, - BaseParameters $params, - ?PollingOptions $pollingOptions = null, - ?CancellationToken $cancellationToken = null + string $responseClass, + InputSource $inputDoc, + BaseProductParameters $params, + ?PollingOptions $pollingOptions = null, + ?CancellationToken $cancellationToken = null ): BaseResponse { if (!$pollingOptions) { $pollingOptions = new PollingOptions(); @@ -164,14 +167,32 @@ public function enqueueAndGetResult( ); } + /** + * Searches for resources matching the given criteria. + * + * @template T of BaseSearchResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param BaseSearchParameters $params Search parameters. + * @return T + */ + public function search(string $responseClass, BaseSearchParameters $params): BaseSearchResponse + { + return $this->mindeeApi->reqGetSearch($responseClass, $params); + } + /** * Searches for a list of available models for the given API key. * @param string|null $modelName Optional model name to filter by. * @param string|null $modelType Optional model type to filter by. - * @return SearchResponse The list of models matching the criteria. + * @return ModelSearchResponse The list of models matching the criteria. + * @deprecated Use search(ModelSearchResponse::class, new ModelSearchParameters(...)) instead. */ - public function searchModels(?string $modelName = null, ?string $modelType = null): SearchResponse + public function searchModels(?string $modelName = null, ?string $modelType = null): ModelSearchResponse { - return $this->mindeeApi->searchModels($modelName, $modelType); + return $this->mindeeApi->reqGetSearch( + ModelSearchResponse::class, + new ModelSearchParameters($modelName, $modelType) + ); } } diff --git a/src/V2/ClientOptions/BaseParameters.php b/src/V2/ClientOptions/BaseProductParameters.php similarity index 94% rename from src/V2/ClientOptions/BaseParameters.php rename to src/V2/ClientOptions/BaseProductParameters.php index f146d025..91ad08fd 100644 --- a/src/V2/ClientOptions/BaseParameters.php +++ b/src/V2/ClientOptions/BaseProductParameters.php @@ -7,7 +7,7 @@ /** * Base parameters for running an inference. */ -abstract class BaseParameters +abstract class BaseProductParameters { /** * @var string|null Optional file alias. @@ -20,7 +20,7 @@ abstract class BaseParameters public array $webhookIds; /** - * @var string Slug of the endpoint. + * @var string Slug of the product. */ public static string $slug; diff --git a/src/V2/ClientOptions/BaseSearchParameters.php b/src/V2/ClientOptions/BaseSearchParameters.php new file mode 100644 index 00000000..9d1da486 --- /dev/null +++ b/src/V2/ClientOptions/BaseSearchParameters.php @@ -0,0 +1,42 @@ + Query parameters. + */ + public function getQueryParams(): array + { + $params = []; + if ($this->page !== null && $this->page > 0) { + $params['page'] = (string) $this->page; + } + if ($this->perPage !== null && $this->perPage > 0) { + $params['per_page'] = (string) $this->perPage; + } + return $params; + } +} diff --git a/src/V2/Http/MindeeApiV2.php b/src/V2/Http/MindeeApiV2.php index 6835c1b9..162edd42 100644 --- a/src/V2/Http/MindeeApiV2.php +++ b/src/V2/Http/MindeeApiV2.php @@ -17,13 +17,14 @@ use Mindee\Input\InputSource; use Mindee\Input\LocalInputSource; use Mindee\Input\UrlInputSource; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; +use Mindee\V2\ClientOptions\BaseSearchParameters; use Mindee\V2\Error\MindeeV2HttpException; use Mindee\V2\Error\MindeeV2HttpUnknownException; use Mindee\V2\Parsing\Error\ErrorResponse; use Mindee\V2\Parsing\Inference\BaseResponse; use Mindee\V2\Parsing\Job\JobResponse; -use Mindee\V2\Parsing\Search\SearchResponse; +use Mindee\V2\Parsing\Search\BaseSearchResponse; use ReflectionClass; use ReflectionException; use ReflectionProperty; @@ -161,11 +162,11 @@ protected function setAPIKey(?string $apiKey = null): void /** * @param InputSource $inputDoc Input document. - * @param BaseParameters $params Parameters for the inference. + * @param BaseProductParameters $params Parameters for the inference. * @return JobResponse Server response wrapped in a JobResponse object. * @throws MindeeException Throws if the model ID is not provided. */ - public function reqPostEnqueue(InputSource $inputDoc, BaseParameters $params): JobResponse + public function reqPostEnqueue(InputSource $inputDoc, BaseProductParameters $params): JobResponse { if (!isset($params->modelId)) { throw new MindeeException("Model ID must be provided.", ErrorCode::USER_INPUT_ERROR); @@ -336,13 +337,13 @@ private function sendGetRequest(string $url): array * Starts a CURL session using POST. * * @param InputSource $inputSource File to upload. - * @param BaseParameters $params Parameters. + * @param BaseProductParameters $params Parameters. * @return array> Server response. * @throws MindeeException Throws if the cURL operation doesn't go succeed. */ private function documentEnqueuePost( InputSource $inputSource, - BaseParameters $params + BaseProductParameters $params ): array { $ch = $this->initChannel(); $postFields = $params->asHash(); @@ -389,20 +390,20 @@ private function checkValidResponse(array $result): void } /** - * @return array> Server response. + * Makes a GET call to a search endpoint and returns the deserialized response. + * + * @template T of BaseSearchResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param BaseSearchParameters $params Search parameters (slug and query params derived from this). + * @return T */ - private function reqGetSearchModels(?string $modelName = null, ?string $modelType = null): array + public function reqGetSearch(string $responseClass, BaseSearchParameters $params): BaseResponse { - $url = $this->baseUrl . "/v2/search/models"; - $params = []; - if ($modelName) { - $params['name'] = $modelName; - } - if ($modelType) { - $params['model_type'] = $modelType; - } - if (!empty($params)) { - $url .= '?' . http_build_query($params); + $queryParams = $params->getQueryParams(); + $url = $this->baseUrl . "/v2/search/" . $params::$slug; + if (!empty($queryParams)) { + $url .= '?' . http_build_query($queryParams); } $ch = $this->initChannel(); @@ -414,17 +415,6 @@ private function reqGetSearchModels(?string $modelName = null, ?string $modelTyp 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), ]; curl_close($ch); - return $resp; - } - - /** - * Retrieves a list of models based on criteria. - * @param string|null $modelName Optional model name to filter by. - * @param string|null $modelType Optional model type to filter by. - * @return SearchResponse The list of models matching the criteria. - */ - public function searchModels(?string $modelName = null, ?string $modelType = null): SearchResponse - { - return $this->processResponse(SearchResponse::class, $this->reqGetSearchModels($modelName, $modelType)); + return $this->processResponse($responseClass, $resp); } } diff --git a/src/V2/Parsing/Job/Job.php b/src/V2/Parsing/Job/Job.php index 982853c4..3e2d923d 100644 --- a/src/V2/Parsing/Job/Job.php +++ b/src/V2/Parsing/Job/Job.php @@ -5,7 +5,7 @@ namespace Mindee\V2\Parsing\Job; use DateTime; -use Exception; +use Mindee\Parsing\DateHelper; use Mindee\V2\Parsing\Error\ErrorResponse; use function array_key_exists; @@ -86,9 +86,9 @@ public function __construct(array $rawResponse) $this->error = new ErrorResponse($rawResponse['error']); } - $this->createdAt = $this->parseDate($rawResponse['created_at']); + $this->createdAt = DateHelper::parseDate($rawResponse['created_at']); $this->completedAt = isset($rawResponse['completed_at']) - ? $this->parseDate($rawResponse['completed_at']) + ? DateHelper::parseDate($rawResponse['completed_at']) : null; $this->modelId = $rawResponse['model_id']; @@ -103,22 +103,4 @@ public function __construct(array $rawResponse) } } } - - /** - * Parse a date string into a DateTime object. - * - * @param string|null $dateString Date string to parse. - */ - private function parseDate(?string $dateString): ?DateTime - { - if (empty($dateString)) { - return null; - } - - try { - return new DateTime($dateString); - } catch (Exception) { - return null; - } - } } diff --git a/src/V2/Parsing/Job/JobWebhook.php b/src/V2/Parsing/Job/JobWebhook.php index e12d5ccf..8c9f2f94 100644 --- a/src/V2/Parsing/Job/JobWebhook.php +++ b/src/V2/Parsing/Job/JobWebhook.php @@ -5,7 +5,7 @@ namespace Mindee\V2\Parsing\Job; use DateTime; -use Exception; +use Mindee\Parsing\DateHelper; use Mindee\V2\Parsing\Error\ErrorResponse; /** @@ -40,29 +40,11 @@ public function __construct(array $rawResponse) { $this->id = $rawResponse['id']; $this->createdAt = isset($rawResponse['created_at']) - ? $this->parseDate($rawResponse['created_at']) + ? DateHelper::parseDate($rawResponse['created_at']) : null; $this->status = $rawResponse['status']; $this->error = isset($rawResponse['error']) ? new ErrorResponse($rawResponse['error']) : null; } - - /** - * Parse a date string into a DateTime object. - * - * @param string|null $dateString Date string to parse. - */ - private function parseDate(?string $dateString): ?DateTime - { - if (empty($dateString)) { - return null; - } - - try { - return new DateTime($dateString); - } catch (Exception) { - return null; - } - } } diff --git a/src/V2/Parsing/Search/BaseSearchResponse.php b/src/V2/Parsing/Search/BaseSearchResponse.php new file mode 100644 index 00000000..143a07b2 --- /dev/null +++ b/src/V2/Parsing/Search/BaseSearchResponse.php @@ -0,0 +1,51 @@ +> $rawResponse Raw server response array. + */ + public function __construct(array $rawResponse) + { + parent::__construct($rawResponse); + $this->pagination = new PaginationMetadata($rawResponse['pagination']); + } + + /** + * Lines composing the response-specific body (header + items). + * + * @return array Body lines. + */ + abstract protected function bodyLines(): array; + + /** + * @return string String representation. + */ + public function __toString(): string + { + return implode("\n", array_merge( + $this->bodyLines(), + [ + 'Pagination Metadata', + '###################', + (string) $this->pagination, + '', + ] + )); + } +} diff --git a/src/V2/Parsing/Search/ModelSearchResponse.php b/src/V2/Parsing/Search/ModelSearchResponse.php new file mode 100644 index 00000000..4b573f81 --- /dev/null +++ b/src/V2/Parsing/Search/ModelSearchResponse.php @@ -0,0 +1,37 @@ +> $rawResponse Raw server response array. + */ + public function __construct(array $rawResponse) + { + parent::__construct($rawResponse); + $this->models = new SearchModels($rawResponse['models']); + } + + /** + * @return array Body lines. + */ + protected function bodyLines(): array + { + return [ + 'Models', + '######', + (string) $this->models, + ]; + } +} diff --git a/src/V2/Parsing/Search/RagDocument.php b/src/V2/Parsing/Search/RagDocument.php new file mode 100644 index 00000000..cfb26a45 --- /dev/null +++ b/src/V2/Parsing/Search/RagDocument.php @@ -0,0 +1,57 @@ +> $rawResponse Raw server response array. + */ + public function __construct(array $rawResponse) + { + $this->id = $rawResponse['id']; + $this->modelId = $rawResponse['model_id']; + $this->filename = $rawResponse['filename']; + $this->createdAt = new DateTimeImmutable($rawResponse['created_at']); + $this->totalMatches = $rawResponse['total_matches']; + $this->lastMatchAt = DateHelper::parseDateImmutable($rawResponse['last_match_at'] ?? null); + $this->status = $rawResponse['status']; + } +} diff --git a/src/V2/Parsing/Search/RagDocumentSearchResponse.php b/src/V2/Parsing/Search/RagDocumentSearchResponse.php new file mode 100644 index 00000000..4157fdef --- /dev/null +++ b/src/V2/Parsing/Search/RagDocumentSearchResponse.php @@ -0,0 +1,37 @@ +> $rawResponse Raw server response array. + */ + public function __construct(array $rawResponse) + { + parent::__construct($rawResponse); + $this->ragDocuments = new RagDocuments($rawResponse['rag_documents']); + } + + /** + * @return array Body lines. + */ + protected function bodyLines(): array + { + return [ + 'RAG Documents', + '############', + (string) $this->ragDocuments, + ]; + } +} diff --git a/src/V2/Parsing/Search/RagDocuments.php b/src/V2/Parsing/Search/RagDocuments.php new file mode 100644 index 00000000..0231dc53 --- /dev/null +++ b/src/V2/Parsing/Search/RagDocuments.php @@ -0,0 +1,48 @@ + + */ +class RagDocuments extends ArrayObject implements Stringable +{ + /** + * @param array>> $prediction Raw prediction. + */ + public function __construct(array $prediction) + { + $documents = array_map(static fn($entry) => new RagDocument($entry), $prediction); + + parent::__construct($documents); + } + + /** + * Default string representation. + */ + public function __toString(): string + { + if ($this->count() === 0) { + return "\n"; + } + + $lines = []; + foreach ($this as $document) { + $lines[] = "* :ID: " . $document->id; + $lines[] = " :Model ID: " . $document->modelId; + $lines[] = " :Filename: " . $document->filename; + $lines[] = " :Created At: " . $document->createdAt->format(DATE_ATOM); + $lines[] = " :Total Matches: " . $document->totalMatches; + $lines[] = " :Last Match At: " . ($document->lastMatchAt?->format(DATE_ATOM) ?? ''); + $lines[] = " :Status: " . $document->status; + } + + return implode("\n", $lines) . "\n"; + } +} diff --git a/src/V2/Parsing/Search/SearchResponse.php b/src/V2/Parsing/Search/SearchResponse.php index c58d6d32..64d261b3 100644 --- a/src/V2/Parsing/Search/SearchResponse.php +++ b/src/V2/Parsing/Search/SearchResponse.php @@ -4,47 +4,9 @@ namespace Mindee\V2\Parsing\Search; -use Mindee\V2\Parsing\Inference\BaseResponse; -use Stringable; - /** * Models search response. + * + * @deprecated Use {@see ModelSearchResponse} instead. */ -class SearchResponse extends BaseResponse implements Stringable -{ - /** - * @var SearchModels Parsed search payload. - */ - public SearchModels $models; - - /** - * @var PaginationMetadata Pagination metadata for the search results. - */ - public PaginationMetadata $pagination; - - /** - * @param array> $rawResponse Raw server response array. - */ - public function __construct(array $rawResponse) - { - parent::__construct($rawResponse); - $this->models = new SearchModels($rawResponse['models']); - $this->pagination = new PaginationMetadata($rawResponse['pagination']); - } - - /** - * @return string String representation. - */ - public function __toString(): string - { - return implode("\n", [ - 'Models', - '######', - (string) $this->models, - 'Pagination Metadata', - '###################', - (string) $this->pagination, - '', - ]); - } -} +class SearchResponse extends ModelSearchResponse {} diff --git a/src/V2/Product/Classification/Params/ClassificationParameters.php b/src/V2/Product/Classification/Params/ClassificationParameters.php index 057f9e1c..537bf5ca 100644 --- a/src/V2/Product/Classification/Params/ClassificationParameters.php +++ b/src/V2/Product/Classification/Params/ClassificationParameters.php @@ -5,15 +5,15 @@ namespace Mindee\V2\Product\Classification\Params; use Mindee\ClientOptions\PollingOptions; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters for a classification utility inference. + * Parameters accepted by the asynchronous Classification product endpoint. */ -class ClassificationParameters extends BaseParameters +class ClassificationParameters extends BaseProductParameters { /** - * @var string Slug of the endpoint. + * @var string Slug of the prodcut. */ public static string $slug = "classification"; diff --git a/src/V2/Product/Crop/Params/CropParameters.php b/src/V2/Product/Crop/Params/CropParameters.php index e7cdf652..d81fd378 100644 --- a/src/V2/Product/Crop/Params/CropParameters.php +++ b/src/V2/Product/Crop/Params/CropParameters.php @@ -5,15 +5,15 @@ namespace Mindee\V2\Product\Crop\Params; use Mindee\ClientOptions\PollingOptions; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters for a crop utility inference. + * Parameters accepted by the asynchronous Crop product endpoint. */ -class CropParameters extends BaseParameters +class CropParameters extends BaseProductParameters { /** - * @var string Slug of the endpoint. + * @var string Slug of the product. */ public static string $slug = "crop"; diff --git a/src/V2/Product/Extraction/Params/ExtractionParameters.php b/src/V2/Product/Extraction/Params/ExtractionParameters.php index 5deaf086..e2ca64af 100644 --- a/src/V2/Product/Extraction/Params/ExtractionParameters.php +++ b/src/V2/Product/Extraction/Params/ExtractionParameters.php @@ -4,12 +4,12 @@ namespace Mindee\V2\Product\Extraction\Params; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters accepted by the asynchronous **inference** v2 endpoint. + * Parameters accepted by the asynchronous Extraction product endpoint. */ -class ExtractionParameters extends BaseParameters +class ExtractionParameters extends BaseProductParameters { /** * @var string|null Additional text context used by the model during inference. @@ -23,7 +23,7 @@ class ExtractionParameters extends BaseParameters public ?DataSchema $dataSchema; /** - * @var string Slug of the endpoint. + * @var string Slug of the product. */ public static string $slug = "extraction"; diff --git a/src/V2/Product/Ocr/Params/OcrParameters.php b/src/V2/Product/Ocr/Params/OcrParameters.php index 51423730..7882ded1 100644 --- a/src/V2/Product/Ocr/Params/OcrParameters.php +++ b/src/V2/Product/Ocr/Params/OcrParameters.php @@ -5,15 +5,15 @@ namespace Mindee\V2\Product\Ocr\Params; use Mindee\ClientOptions\PollingOptions; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters for an ocr utility inference. + * Parameters accepted by the asynchronous OCR product endpoint. */ -class OcrParameters extends BaseParameters +class OcrParameters extends BaseProductParameters { /** - * @var string Slug of the endpoint. + * @var string Slug of the product. */ public static string $slug = "ocr"; diff --git a/src/V2/Product/Split/Params/SplitParameters.php b/src/V2/Product/Split/Params/SplitParameters.php index 8de1262e..4b13806d 100644 --- a/src/V2/Product/Split/Params/SplitParameters.php +++ b/src/V2/Product/Split/Params/SplitParameters.php @@ -5,12 +5,12 @@ namespace Mindee\V2\Product\Split\Params; use Mindee\ClientOptions\PollingOptions; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; /** * Parameters for a split utility inference. */ -class SplitParameters extends BaseParameters +class SplitParameters extends BaseProductParameters { /** * @var string Slug of the endpoint. diff --git a/src/V2/Search/Models/ModelSearchParameters.php b/src/V2/Search/Models/ModelSearchParameters.php new file mode 100644 index 00000000..4756f1f1 --- /dev/null +++ b/src/V2/Search/Models/ModelSearchParameters.php @@ -0,0 +1,48 @@ + Query parameters. + */ + public function getQueryParams(): array + { + $params = parent::getQueryParams(); + if (!empty($this->name)) { + $params['name'] = $this->name; + } + if (!empty($this->modelType)) { + $params['model_type'] = $this->modelType; + } + return $params; + } +} diff --git a/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php b/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php new file mode 100644 index 00000000..d25a9cd7 --- /dev/null +++ b/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php @@ -0,0 +1,56 @@ + Query parameters. + * @throws MindeeException Throws if the model ID is not provided. + */ + public function getQueryParams(): array + { + $params = parent::getQueryParams(); + if (!empty($this->modelId)) { + $params['model_id'] = $this->modelId; + } else { + throw new MindeeException( + "ModelId is required in RagDocumentSearchParameters.", + ErrorCode::USER_INPUT_ERROR + ); + } + if (!empty($this->filename)) { + $params['filename'] = $this->filename; + } + return $params; + } +} diff --git a/tests/V2/Cli/MindeeCliCommandV2Test.php b/tests/V2/Cli/MindeeCliCommandV2Test.php index 422b04b1..200a30da 100644 --- a/tests/V2/Cli/MindeeCliCommandV2Test.php +++ b/tests/V2/Cli/MindeeCliCommandV2Test.php @@ -49,6 +49,7 @@ public function testListShouldShowAllV2Commands(): void self::assertStringContainsString('ocr', $stdout); self::assertStringContainsString('split', $stdout); self::assertStringContainsString('search-models', $stdout); + self::assertStringContainsString('search-rag-docs', $stdout); self::assertStringContainsString('v1', $stdout); } @@ -195,6 +196,42 @@ public function testSearchModelsMissingApiKeyMustFail(): void ); } + public function testSearchRagDocsHelpExposesExpectedOptions(): void + { + $cmdOutput = MindeeCliV2TestingUtilities::executeTest(['search-rag-docs', '--help']); + self::assertSame(0, $cmdOutput['code']); + $stdout = implode("\n", $cmdOutput['output']); + self::assertStringContainsString('--api-key', $stdout); + self::assertStringContainsString('--model-id', $stdout); + self::assertStringContainsString('--filename', $stdout); + self::assertStringContainsString('--raw-json', $stdout); + } + + public function testSearchRagDocsMissingApiKeyMustFail(): void + { + $cmdOutput = MindeeCliV2TestingUtilities::executeTest( + ['search-rag-docs', '--model-id', 'some-model-id'], + ['MINDEE_V2_API_KEY' => false] + ); + self::assertSame(1, $cmdOutput['code']); + self::assertStringContainsString( + 'API key is missing', + implode("\n", $cmdOutput['output']) + ); + } + + public function testSearchRagDocsMissingModelIdMustFail(): void + { + $cmdOutput = MindeeCliV2TestingUtilities::executeTest( + ['search-rag-docs', '-k', 'dummy-key'] + ); + self::assertSame(1, $cmdOutput['code']); + self::assertStringContainsString( + '--model-id', + implode("\n", $cmdOutput['output']) + ); + } + public function testV1BackwardCompatibilityDispatch(): void { $cmdOutput = MindeeCliV2TestingUtilities::executeTest( diff --git a/tests/V2/ClientOptions/BaseParametersTest.php b/tests/V2/ClientOptions/BaseProductParametersTest.php similarity index 80% rename from tests/V2/ClientOptions/BaseParametersTest.php rename to tests/V2/ClientOptions/BaseProductParametersTest.php index 6603b97b..aa3cd11f 100644 --- a/tests/V2/ClientOptions/BaseParametersTest.php +++ b/tests/V2/ClientOptions/BaseProductParametersTest.php @@ -4,14 +4,14 @@ namespace V2\ClientOptions; -use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use PHPUnit\Framework\TestCase; -class BaseParametersTest extends TestCase +class BaseProductParametersTest extends TestCase { public function testAsHashShouldSerializeMultipleWebhookIdsAsIndexedFields(): void { - $params = new class ('model-id', null, ['first-id', 'second-id']) extends BaseParameters { + $params = new class ('model-id', null, ['first-id', 'second-id']) extends BaseProductParameters { public static string $slug = 'test'; }; diff --git a/tests/V2/Parsing/SearchResponseTest.php b/tests/V2/Parsing/SearchResponseTest.php deleted file mode 100644 index bc3f6fce..00000000 --- a/tests/V2/Parsing/SearchResponseTest.php +++ /dev/null @@ -1,44 +0,0 @@ -models); - foreach ($response->models as $model) { - self::assertInstanceOf(SearchModel::class, $model); - self::assertNotEmpty($model->id); - self::assertNotEmpty($model->name); - } - self::assertCount(2, $response->models[0]->webhooks); - self::assertEquals("https://failure.mindee.com", $response->models[0]->webhooks[0]->url); - - self::assertEquals(50, $response->pagination->perPage); - self::assertEquals(1, $response->pagination->page); - self::assertGreaterThanOrEqual(5, $response->pagination->totalItems); - self::assertEquals(1, $response->pagination->totalPages); - } -} diff --git a/tests/V2/Search/ModelSearchFunctional.php b/tests/V2/Search/ModelSearchFunctional.php new file mode 100644 index 00000000..ad8f9515 --- /dev/null +++ b/tests/V2/Search/ModelSearchFunctional.php @@ -0,0 +1,60 @@ +client = new Client(getenv('MINDEE_V2_API_KEY') ?: null); + } + + public function testModelSearch_mustHaveResults(): void + { + $response = $this->client->search(ModelSearchResponse::class, new ModelSearchParameters()); + + self::assertNotNull($response); + self::assertNotNull($response->models); + self::assertNotEmpty($response->models); + self::assertNotNull($response->pagination); + self::assertGreaterThan(1, $response->pagination->totalItems); + self::assertEquals(1, $response->pagination->page); + } + + public function testModelSearch_mustReturnEmpty(): void + { + $response = $this->client->search( + ModelSearchResponse::class, + new ModelSearchParameters(name: "je n'existe pas tralala") + ); + + self::assertNotNull($response); + self::assertNotNull($response->models); + self::assertEmpty($response->models); + self::assertNotNull($response->pagination); + self::assertEquals(0, $response->pagination->totalItems); + self::assertEquals(1, $response->pagination->page); + } + + public function testModelSearch_mustReturnEmptyObsolete(): void + { + /** @phpstan-ignore method.deprecated */ + $response = $this->client->searchModels("je n'existe pas tralala"); + + self::assertNotNull($response); + self::assertNotNull($response->models); + self::assertEmpty($response->models); + self::assertNotNull($response->pagination); + self::assertEquals(0, $response->pagination->totalItems); + self::assertEquals(1, $response->pagination->page); + } +} diff --git a/tests/V2/Search/ModelSearchTest.php b/tests/V2/Search/ModelSearchTest.php new file mode 100644 index 00000000..c87d0388 --- /dev/null +++ b/tests/V2/Search/ModelSearchTest.php @@ -0,0 +1,44 @@ +models); + self::assertEquals(5, $response->pagination->totalItems); + self::assertEquals(1, $response->pagination->page); + self::assertEquals(50, $response->pagination->perPage); + self::assertEquals(1, $response->pagination->totalPages); + + $firstItem = $response->models[0]; + self::assertEquals("Extraction With Webhooks", $firstItem->name); + self::assertEquals("afde5151-aa11-aa11-9289-fa04e50ca3b9", $firstItem->id); + self::assertEquals("extraction", $firstItem->modelType); + + self::assertCount(2, $firstItem->webhooks); + self::assertEquals("a2286ed9-aa11-aa11-bdc5-2f8496c5641a", $firstItem->webhooks[0]->id); + self::assertEquals("FAILURE", $firstItem->webhooks[0]->name); + self::assertEquals("https://failure.mindee.com", $firstItem->webhooks[0]->url); + + $lastItem = $response->models[4]; + self::assertEquals("Extraction Without Webhooks Key", $lastItem->name); + self::assertEquals("e14e0923-ee55-ee55-a335-8d2110917d7b", $lastItem->id); + } +} diff --git a/tests/V2/Search/RagDocumentSearchFunctional.php b/tests/V2/Search/RagDocumentSearchFunctional.php new file mode 100644 index 00000000..f31d0c15 --- /dev/null +++ b/tests/V2/Search/RagDocumentSearchFunctional.php @@ -0,0 +1,35 @@ +client = new Client(getenv('MINDEE_V2_API_KEY') ?: null); + $this->findocModelId = getenv('MINDEE_V2_FINDOC_MODEL_ID') ?: ''; + } + + public function testRagDocumentSearch_mustHaveResults(): void + { + $response = $this->client->search( + RagDocumentSearchResponse::class, + new RagDocumentSearchParameters(modelId: $this->findocModelId) + ); + + self::assertNotNull($response); + self::assertNotNull($response->ragDocuments); + self::assertNotNull($response->pagination); + self::assertEquals(1, $response->pagination->page); + } +} diff --git a/tests/V2/Search/RagDocumentSearchTest.php b/tests/V2/Search/RagDocumentSearchTest.php new file mode 100644 index 00000000..89a10496 --- /dev/null +++ b/tests/V2/Search/RagDocumentSearchTest.php @@ -0,0 +1,58 @@ +ragDocuments); + self::assertEquals(3, $response->pagination->totalItems); + self::assertEquals(1, $response->pagination->page); + self::assertEquals(50, $response->pagination->perPage); + self::assertEquals(1, $response->pagination->totalPages); + + $firstItem = $response->ragDocuments[0]; + self::assertEquals("cc831599-c545-48b7-aa27-6d7ccd5b8d32", $firstItem->id); + self::assertEquals("12345678-1234-1234-1234-123456789abc", $firstItem->modelId); + self::assertEquals("invoice_01.pdf", $firstItem->filename); + self::assertEquals(new DateTimeImmutable("2026-06-30T13:13:46.168586Z"), $firstItem->createdAt); + self::assertEquals(0, $firstItem->totalMatches); + self::assertNull($firstItem->lastMatchAt); + self::assertEquals("Processing", $firstItem->status); + + $secondItem = $response->ragDocuments[1]; + self::assertEquals("27467e4c-5602-4315-90d9-3d2da69b05ab", $secondItem->id); + self::assertEquals("12345678-1234-1234-1234-123456789abc", $secondItem->modelId); + self::assertEquals("invoice_02.pdf", $secondItem->filename); + self::assertEquals(new DateTimeImmutable("2026-06-30T13:13:46.168586Z"), $secondItem->createdAt); + self::assertEquals(0, $secondItem->totalMatches); + self::assertNull($secondItem->lastMatchAt); + self::assertEquals("Draft", $secondItem->status); + + $thirdItem = $response->ragDocuments[2]; + self::assertEquals("a6bcae7d-0439-476b-8a63-5a39ec05dc21", $thirdItem->id); + self::assertEquals("12345678-1234-1234-1234-jobid1234567", $thirdItem->modelId); + self::assertEquals("invoice_03.pdf", $thirdItem->filename); + self::assertEquals(new DateTimeImmutable("2026-06-17T14:35:46.228006Z"), $thirdItem->createdAt); + self::assertEquals(5, $thirdItem->totalMatches); + self::assertEquals(new DateTimeImmutable("2026-06-18T14:35:46.248006Z"), $thirdItem->lastMatchAt); + self::assertEquals("Active", $thirdItem->status); + } +} diff --git a/tests/resources b/tests/resources index e41ab97c..4b7f3376 160000 --- a/tests/resources +++ b/tests/resources @@ -1 +1 @@ -Subproject commit e41ab97c2833f15ea5c4edf221c2d197d212f632 +Subproject commit 4b7f33766fab0e67804b84447b73c80a902d886a