From ff721f474b4e4ab14e052072c45b9ede0515ce06 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:19:00 +0200 Subject: [PATCH 1/3] :sparkles: add RAG search API --- bin/V2/SearchModelsCommand.php | 5 +- bin/V2/SearchRagDocumentsCommand.php | 103 ++++++++++++++++++ bin/cli.php | 5 +- src/Parsing/DateHelper.php | 51 +++++++++ src/V2/Client.php | 26 +++-- src/V2/ClientOptions/BaseSearchParameters.php | 37 +++++++ .../ClientOptions/ModelSearchParameters.php | 41 +++++++ .../RagDocumentSearchParameters.php | 50 +++++++++ src/V2/Http/MindeeApiV2.php | 50 ++++++--- src/V2/Parsing/Job/Job.php | 24 +--- src/V2/Parsing/Job/JobWebhook.php | 22 +--- src/V2/Parsing/Search/BaseSearchResponse.php | 51 +++++++++ src/V2/Parsing/Search/ModelSearchResponse.php | 37 +++++++ src/V2/Parsing/Search/RagDocument.php | 57 ++++++++++ .../Search/RagDocumentSearchResponse.php | 37 +++++++ src/V2/Parsing/Search/RagDocuments.php | 48 ++++++++ src/V2/Parsing/Search/SearchResponse.php | 44 +------- tests/V2/Cli/MindeeCliCommandV2Test.php | 37 +++++++ .../Parsing/RagDocumentSearchResponseTest.php | 55 ++++++++++ tests/resources | 2 +- 20 files changed, 672 insertions(+), 110 deletions(-) create mode 100644 bin/V2/SearchRagDocumentsCommand.php create mode 100644 src/Parsing/DateHelper.php create mode 100644 src/V2/ClientOptions/BaseSearchParameters.php create mode 100644 src/V2/ClientOptions/ModelSearchParameters.php create mode 100644 src/V2/ClientOptions/RagDocumentSearchParameters.php create mode 100644 src/V2/Parsing/Search/BaseSearchResponse.php create mode 100644 src/V2/Parsing/Search/ModelSearchResponse.php create mode 100644 src/V2/Parsing/Search/RagDocument.php create mode 100644 src/V2/Parsing/Search/RagDocumentSearchResponse.php create mode 100644 src/V2/Parsing/Search/RagDocuments.php create mode 100644 tests/V2/Parsing/RagDocumentSearchResponseTest.php diff --git a/bin/V2/SearchModelsCommand.php b/bin/V2/SearchModelsCommand.php index c4ff273e..3777b5a6 100644 --- a/bin/V2/SearchModelsCommand.php +++ b/bin/V2/SearchModelsCommand.php @@ -6,6 +6,7 @@ use Exception; use Mindee\V2\Client; +use Mindee\V2\ClientOptions\ModelSearchParameters; use Mindee\V2\Error\MindeeV2HttpException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -83,7 +84,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client = new Client($apiKey ?: null); try { - $response = $client->searchModels($name ?: null, $modelType ?: null); + $response = $client->searchModels( + 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..47d22d44 --- /dev/null +++ b/bin/V2/SearchRagDocumentsCommand.php @@ -0,0 +1,103 @@ +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->searchRagDocuments( + 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/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->searchModels($modelName, $modelType); + return $this->mindeeApi->searchModels($params ?? new ModelSearchParameters()); + } + + /** + * Searches for a list of RAG documents matching the given criteria. + * @param RagDocumentSearchParameters $params Search parameters. + * @return RagDocumentSearchResponse The list of RAG documents matching the criteria. + */ + public function searchRagDocuments(RagDocumentSearchParameters $params): RagDocumentSearchResponse + { + return $this->mindeeApi->searchRagDocuments($params); } } diff --git a/src/V2/ClientOptions/BaseSearchParameters.php b/src/V2/ClientOptions/BaseSearchParameters.php new file mode 100644 index 00000000..5d222509 --- /dev/null +++ b/src/V2/ClientOptions/BaseSearchParameters.php @@ -0,0 +1,37 @@ + 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/ClientOptions/ModelSearchParameters.php b/src/V2/ClientOptions/ModelSearchParameters.php new file mode 100644 index 00000000..51452233 --- /dev/null +++ b/src/V2/ClientOptions/ModelSearchParameters.php @@ -0,0 +1,41 @@ + 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/ClientOptions/RagDocumentSearchParameters.php b/src/V2/ClientOptions/RagDocumentSearchParameters.php new file mode 100644 index 00000000..46367fa2 --- /dev/null +++ b/src/V2/ClientOptions/RagDocumentSearchParameters.php @@ -0,0 +1,50 @@ + 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/src/V2/Http/MindeeApiV2.php b/src/V2/Http/MindeeApiV2.php index 6835c1b9..5af23128 100644 --- a/src/V2/Http/MindeeApiV2.php +++ b/src/V2/Http/MindeeApiV2.php @@ -18,12 +18,15 @@ use Mindee\Input\LocalInputSource; use Mindee\Input\UrlInputSource; use Mindee\V2\ClientOptions\BaseParameters; +use Mindee\V2\ClientOptions\ModelSearchParameters; +use Mindee\V2\ClientOptions\RagDocumentSearchParameters; 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\ModelSearchResponse; +use Mindee\V2\Parsing\Search\RagDocumentSearchResponse; use ReflectionClass; use ReflectionException; use ReflectionProperty; @@ -389,20 +392,16 @@ private function checkValidResponse(array $result): void } /** + * Makes a GET call to a search endpoint. + * @param string $path Search endpoint path (e.g. `/v2/search/models`). + * @param array $queryParams Query parameters to append. * @return array> Server response. */ - private function reqGetSearchModels(?string $modelName = null, ?string $modelType = null): array + private function reqGetSearch(string $path, array $queryParams): array { - $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); + $url = $this->baseUrl . $path; + if (!empty($queryParams)) { + $url .= '?' . http_build_query($queryParams); } $ch = $this->initChannel(); @@ -418,13 +417,28 @@ private function reqGetSearchModels(?string $modelName = null, ?string $modelTyp } /** - * 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. + * Retrieves a list of models matching the given criteria. + * @param ModelSearchParameters $params Search parameters. + * @return ModelSearchResponse The list of models matching the criteria. */ - public function searchModels(?string $modelName = null, ?string $modelType = null): SearchResponse + public function searchModels(ModelSearchParameters $params): ModelSearchResponse { - return $this->processResponse(SearchResponse::class, $this->reqGetSearchModels($modelName, $modelType)); + return $this->processResponse( + ModelSearchResponse::class, + $this->reqGetSearch("/v2/search/models", $params->getQueryParams()) + ); + } + + /** + * Retrieves a list of RAG documents matching the given criteria. + * @param RagDocumentSearchParameters $params Search parameters. + * @return RagDocumentSearchResponse The list of RAG documents matching the criteria. + */ + public function searchRagDocuments(RagDocumentSearchParameters $params): RagDocumentSearchResponse + { + return $this->processResponse( + RagDocumentSearchResponse::class, + $this->reqGetSearch("/v2/search/rag-documents", $params->getQueryParams()) + ); } } 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/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/Parsing/RagDocumentSearchResponseTest.php b/tests/V2/Parsing/RagDocumentSearchResponseTest.php new file mode 100644 index 00000000..166915aa --- /dev/null +++ b/tests/V2/Parsing/RagDocumentSearchResponseTest.php @@ -0,0 +1,55 @@ +ragDocuments); + foreach ($response->ragDocuments as $document) { + self::assertInstanceOf(RagDocument::class, $document); + self::assertNotEmpty($document->id); + self::assertNotEmpty($document->modelId); + self::assertNotEmpty($document->filename); + } + + $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(0, $firstItem->totalMatches); + self::assertNull($firstItem->lastMatchAt); + self::assertEquals("Processing", $firstItem->status); + + $thirdItem = $response->ragDocuments[2]; + self::assertEquals("a6bcae7d-0439-476b-8a63-5a39ec05dc21", $thirdItem->id); + self::assertEquals("invoice_03.pdf", $thirdItem->filename); + self::assertEquals(5, $thirdItem->totalMatches); + self::assertNotNull($thirdItem->lastMatchAt); + self::assertEquals("Active", $thirdItem->status); + + self::assertEquals(50, $response->pagination->perPage); + self::assertEquals(1, $response->pagination->page); + self::assertEquals(3, $response->pagination->totalItems); + self::assertEquals(1, $response->pagination->totalPages); + } +} 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 From 0f64651faf7bef98709da511d4bf14249d616a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Fri, 7 Aug 2026 17:56:24 +0200 Subject: [PATCH 2/3] harmonize --- bin/V2/BaseInferenceCommand.php | 6 ++--- bin/V2/ClassificationCommand.php | 4 ++-- bin/V2/CropCommand.php | 4 ++-- bin/V2/ExtractionCommand.php | 4 ++-- bin/V2/OcrCommand.php | 4 ++-- bin/V2/SearchModelsCommand.php | 2 +- bin/V2/SearchRagDocumentsCommand.php | 2 +- bin/V2/SplitCommand.php | 4 ++-- src/V2/Client.php | 22 +++++++++---------- ...rameters.php => BaseProductParameters.php} | 2 +- src/V2/Http/MindeeApiV2.php | 14 ++++++------ .../Params/ClassificationParameters.php | 4 ++-- src/V2/Product/Crop/Params/CropParameters.php | 4 ++-- .../Params/ExtractionParameters.php | 4 ++-- src/V2/Product/Ocr/Params/OcrParameters.php | 4 ++-- .../Product/Split/Params/SplitParameters.php | 4 ++-- .../Models}/ModelSearchParameters.php | 4 +++- .../RagDocumentSearchParameters.php | 3 ++- ...Test.php => BaseProductParametersTest.php} | 6 ++--- 19 files changed, 52 insertions(+), 49 deletions(-) rename src/V2/ClientOptions/{BaseParameters.php => BaseProductParameters.php} (97%) rename src/V2/{ClientOptions => Search/Models}/ModelSearchParameters.php (92%) rename src/V2/{ClientOptions => Search/RagDocuments}/RagDocumentSearchParameters.php (93%) rename tests/V2/ClientOptions/{BaseParametersTest.php => BaseProductParametersTest.php} (80%) 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 3777b5a6..9bb62661 100644 --- a/bin/V2/SearchModelsCommand.php +++ b/bin/V2/SearchModelsCommand.php @@ -6,8 +6,8 @@ use Exception; use Mindee\V2\Client; -use Mindee\V2\ClientOptions\ModelSearchParameters; use Mindee\V2\Error\MindeeV2HttpException; +use Mindee\V2\Search\Models\ModelSearchParameters; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; diff --git a/bin/V2/SearchRagDocumentsCommand.php b/bin/V2/SearchRagDocumentsCommand.php index 47d22d44..a91ffbfd 100644 --- a/bin/V2/SearchRagDocumentsCommand.php +++ b/bin/V2/SearchRagDocumentsCommand.php @@ -6,8 +6,8 @@ use Exception; use Mindee\V2\Client; -use Mindee\V2\ClientOptions\RagDocumentSearchParameters; use Mindee\V2\Error\MindeeV2HttpException; +use Mindee\V2\Search\RagDocuments\RagDocumentSearchParameters; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; 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/src/V2/Client.php b/src/V2/Client.php index 9006825e..48e940bf 100644 --- a/src/V2/Client.php +++ b/src/V2/Client.php @@ -9,14 +9,14 @@ use Mindee\Error\MindeeException; use Mindee\Http\CancellationToken; use Mindee\Input\InputSource; -use Mindee\V2\ClientOptions\BaseParameters; -use Mindee\V2\ClientOptions\ModelSearchParameters; -use Mindee\V2\ClientOptions\RagDocumentSearchParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Http\MindeeApiV2; use Mindee\V2\Parsing\Inference\BaseResponse; use Mindee\V2\Parsing\Job\JobResponse; use Mindee\V2\Parsing\Search\ModelSearchResponse; use Mindee\V2\Parsing\Search\RagDocumentSearchResponse; +use Mindee\V2\Search\Models\ModelSearchParameters; +use Mindee\V2\Search\RagDocuments\RagDocumentSearchParameters; /** * Mindee Client V2. @@ -43,14 +43,14 @@ public function __construct(?string $apiKey = null) /** * Send the document to an asynchronous endpoint and return its ID in the queue. * @param InputSource $inputSource File to parse. - * @param BaseParameters $params Parameters relating to prediction options. + * @param BaseProductParameters $params Parameters relating to prediction options. * @return JobResponse A JobResponse containing the job (queue) corresponding to a document. * @throws MindeeException Throws if the input document is not provided. * @category Asynchronous */ public function enqueue( InputSource $inputSource, - BaseParameters $params + BaseProductParameters $params ): JobResponse { return $this->mindeeApi->reqPostEnqueue($inputSource, $params); } @@ -105,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(); diff --git a/src/V2/ClientOptions/BaseParameters.php b/src/V2/ClientOptions/BaseProductParameters.php similarity index 97% rename from src/V2/ClientOptions/BaseParameters.php rename to src/V2/ClientOptions/BaseProductParameters.php index f146d025..62492eb1 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. diff --git a/src/V2/Http/MindeeApiV2.php b/src/V2/Http/MindeeApiV2.php index 5af23128..a16e5f1d 100644 --- a/src/V2/Http/MindeeApiV2.php +++ b/src/V2/Http/MindeeApiV2.php @@ -17,9 +17,7 @@ use Mindee\Input\InputSource; use Mindee\Input\LocalInputSource; use Mindee\Input\UrlInputSource; -use Mindee\V2\ClientOptions\BaseParameters; -use Mindee\V2\ClientOptions\ModelSearchParameters; -use Mindee\V2\ClientOptions\RagDocumentSearchParameters; +use Mindee\V2\ClientOptions\BaseProductParameters; use Mindee\V2\Error\MindeeV2HttpException; use Mindee\V2\Error\MindeeV2HttpUnknownException; use Mindee\V2\Parsing\Error\ErrorResponse; @@ -27,6 +25,8 @@ use Mindee\V2\Parsing\Job\JobResponse; use Mindee\V2\Parsing\Search\ModelSearchResponse; use Mindee\V2\Parsing\Search\RagDocumentSearchResponse; +use Mindee\V2\Search\Models\ModelSearchParameters; +use Mindee\V2\Search\RagDocuments\RagDocumentSearchParameters; use ReflectionClass; use ReflectionException; use ReflectionProperty; @@ -164,11 +164,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); @@ -339,13 +339,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(); diff --git a/src/V2/Product/Classification/Params/ClassificationParameters.php b/src/V2/Product/Classification/Params/ClassificationParameters.php index 057f9e1c..7a626759 100644 --- a/src/V2/Product/Classification/Params/ClassificationParameters.php +++ b/src/V2/Product/Classification/Params/ClassificationParameters.php @@ -5,12 +5,12 @@ 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. */ -class ClassificationParameters extends BaseParameters +class ClassificationParameters extends BaseProductParameters { /** * @var string Slug of the endpoint. diff --git a/src/V2/Product/Crop/Params/CropParameters.php b/src/V2/Product/Crop/Params/CropParameters.php index e7cdf652..52fcef5d 100644 --- a/src/V2/Product/Crop/Params/CropParameters.php +++ b/src/V2/Product/Crop/Params/CropParameters.php @@ -5,12 +5,12 @@ 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. */ -class CropParameters extends BaseParameters +class CropParameters extends BaseProductParameters { /** * @var string Slug of the endpoint. diff --git a/src/V2/Product/Extraction/Params/ExtractionParameters.php b/src/V2/Product/Extraction/Params/ExtractionParameters.php index 5deaf086..30c8371f 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. */ -class ExtractionParameters extends BaseParameters +class ExtractionParameters extends BaseProductParameters { /** * @var string|null Additional text context used by the model during inference. diff --git a/src/V2/Product/Ocr/Params/OcrParameters.php b/src/V2/Product/Ocr/Params/OcrParameters.php index 51423730..17587a06 100644 --- a/src/V2/Product/Ocr/Params/OcrParameters.php +++ b/src/V2/Product/Ocr/Params/OcrParameters.php @@ -5,12 +5,12 @@ 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. */ -class OcrParameters extends BaseParameters +class OcrParameters extends BaseProductParameters { /** * @var string Slug of the endpoint. 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/ClientOptions/ModelSearchParameters.php b/src/V2/Search/Models/ModelSearchParameters.php similarity index 92% rename from src/V2/ClientOptions/ModelSearchParameters.php rename to src/V2/Search/Models/ModelSearchParameters.php index 51452233..59e231d7 100644 --- a/src/V2/ClientOptions/ModelSearchParameters.php +++ b/src/V2/Search/Models/ModelSearchParameters.php @@ -2,7 +2,9 @@ declare(strict_types=1); -namespace Mindee\V2\ClientOptions; +namespace Mindee\V2\Search\Models; + +use Mindee\V2\ClientOptions\BaseSearchParameters; /** * Search parameters for models. diff --git a/src/V2/ClientOptions/RagDocumentSearchParameters.php b/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php similarity index 93% rename from src/V2/ClientOptions/RagDocumentSearchParameters.php rename to src/V2/Search/RagDocuments/RagDocumentSearchParameters.php index 46367fa2..63a762f2 100644 --- a/src/V2/ClientOptions/RagDocumentSearchParameters.php +++ b/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace Mindee\V2\ClientOptions; +namespace Mindee\V2\Search\RagDocuments; use Mindee\Error\ErrorCode; use Mindee\Error\MindeeException; +use Mindee\V2\ClientOptions\BaseSearchParameters; /** * Search parameters for RAG documents. 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'; }; From e416291740319a65de385563de85558d390e96c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Mon, 10 Aug 2026 13:04:55 +0200 Subject: [PATCH 3/3] use generic search method --- bin/V2/SearchModelsCommand.php | 4 +- bin/V2/SearchRagDocumentsCommand.php | 4 +- src/V2/Client.php | 33 ++++++---- .../ClientOptions/BaseProductParameters.php | 2 +- src/V2/ClientOptions/BaseSearchParameters.php | 5 ++ src/V2/Http/MindeeApiV2.php | 50 ++++------------ .../Params/ClassificationParameters.php | 4 +- src/V2/Product/Crop/Params/CropParameters.php | 4 +- .../Params/ExtractionParameters.php | 4 +- src/V2/Product/Ocr/Params/OcrParameters.php | 4 +- .../Search/Models/ModelSearchParameters.php | 5 ++ .../RagDocumentSearchParameters.php | 5 ++ tests/V2/Parsing/SearchResponseTest.php | 44 -------------- tests/V2/Search/ModelSearchFunctional.php | 60 +++++++++++++++++++ tests/V2/Search/ModelSearchTest.php | 44 ++++++++++++++ .../V2/Search/RagDocumentSearchFunctional.php | 35 +++++++++++ .../RagDocumentSearchTest.php} | 43 ++++++------- 17 files changed, 226 insertions(+), 124 deletions(-) delete mode 100644 tests/V2/Parsing/SearchResponseTest.php create mode 100644 tests/V2/Search/ModelSearchFunctional.php create mode 100644 tests/V2/Search/ModelSearchTest.php create mode 100644 tests/V2/Search/RagDocumentSearchFunctional.php rename tests/V2/{Parsing/RagDocumentSearchResponseTest.php => Search/RagDocumentSearchTest.php} (56%) diff --git a/bin/V2/SearchModelsCommand.php b/bin/V2/SearchModelsCommand.php index 9bb62661..3031b1b4 100644 --- a/bin/V2/SearchModelsCommand.php +++ b/bin/V2/SearchModelsCommand.php @@ -7,6 +7,7 @@ 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; @@ -84,7 +85,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client = new Client($apiKey ?: null); try { - $response = $client->searchModels( + $response = $client->search( + ModelSearchResponse::class, new ModelSearchParameters($name ?: null, $modelType ?: null) ); } catch (MindeeV2HttpException $e) { diff --git a/bin/V2/SearchRagDocumentsCommand.php b/bin/V2/SearchRagDocumentsCommand.php index a91ffbfd..7a9f07af 100644 --- a/bin/V2/SearchRagDocumentsCommand.php +++ b/bin/V2/SearchRagDocumentsCommand.php @@ -7,6 +7,7 @@ use Exception; use Mindee\V2\Client; use Mindee\V2\Error\MindeeV2HttpException; +use Mindee\V2\Parsing\Search\RagDocumentSearchResponse; use Mindee\V2\Search\RagDocuments\RagDocumentSearchParameters; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -82,7 +83,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client = new Client($apiKey ?: null); try { - $response = $client->searchRagDocuments( + $response = $client->search( + RagDocumentSearchResponse::class, new RagDocumentSearchParameters($modelId, $filename ?: null) ); } catch (MindeeV2HttpException $e) { diff --git a/src/V2/Client.php b/src/V2/Client.php index 48e940bf..ac2a8b1d 100644 --- a/src/V2/Client.php +++ b/src/V2/Client.php @@ -13,10 +13,10 @@ use Mindee\V2\Http\MindeeApiV2; use Mindee\V2\Parsing\Inference\BaseResponse; use Mindee\V2\Parsing\Job\JobResponse; +use Mindee\V2\ClientOptions\BaseSearchParameters; +use Mindee\V2\Parsing\Search\BaseSearchResponse; use Mindee\V2\Parsing\Search\ModelSearchResponse; -use Mindee\V2\Parsing\Search\RagDocumentSearchResponse; use Mindee\V2\Search\Models\ModelSearchParameters; -use Mindee\V2\Search\RagDocuments\RagDocumentSearchParameters; /** * Mindee Client V2. @@ -168,22 +168,31 @@ public function enqueueAndGetResult( } /** - * Searches for a list of available models matching the given criteria. - * @param ModelSearchParameters|null $params Search parameters (name, model type, pagination). - * @return ModelSearchResponse The list of models matching the criteria. + * 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 searchModels(?ModelSearchParameters $params = null): ModelSearchResponse + public function search(string $responseClass, BaseSearchParameters $params): BaseSearchResponse { - return $this->mindeeApi->searchModels($params ?? new ModelSearchParameters()); + return $this->mindeeApi->reqGetSearch($responseClass, $params); } /** - * Searches for a list of RAG documents matching the given criteria. - * @param RagDocumentSearchParameters $params Search parameters. - * @return RagDocumentSearchResponse The list of RAG documents matching the criteria. + * 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 ModelSearchResponse The list of models matching the criteria. + * @deprecated Use search(ModelSearchResponse::class, new ModelSearchParameters(...)) instead. */ - public function searchRagDocuments(RagDocumentSearchParameters $params): RagDocumentSearchResponse + public function searchModels(?string $modelName = null, ?string $modelType = null): ModelSearchResponse { - return $this->mindeeApi->searchRagDocuments($params); + return $this->mindeeApi->reqGetSearch( + ModelSearchResponse::class, + new ModelSearchParameters($modelName, $modelType) + ); } } diff --git a/src/V2/ClientOptions/BaseProductParameters.php b/src/V2/ClientOptions/BaseProductParameters.php index 62492eb1..91ad08fd 100644 --- a/src/V2/ClientOptions/BaseProductParameters.php +++ b/src/V2/ClientOptions/BaseProductParameters.php @@ -20,7 +20,7 @@ abstract class BaseProductParameters 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 index 5d222509..9d1da486 100644 --- a/src/V2/ClientOptions/BaseSearchParameters.php +++ b/src/V2/ClientOptions/BaseSearchParameters.php @@ -9,6 +9,11 @@ */ abstract class BaseSearchParameters { + /** + * @var string Slug of the resource. + */ + public static string $slug; + /** * @param integer|null $page 1-based page index. * @param integer|null $perPage Number of items per page. diff --git a/src/V2/Http/MindeeApiV2.php b/src/V2/Http/MindeeApiV2.php index a16e5f1d..162edd42 100644 --- a/src/V2/Http/MindeeApiV2.php +++ b/src/V2/Http/MindeeApiV2.php @@ -18,15 +18,13 @@ use Mindee\Input\LocalInputSource; use Mindee\Input\UrlInputSource; 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\ModelSearchResponse; -use Mindee\V2\Parsing\Search\RagDocumentSearchResponse; -use Mindee\V2\Search\Models\ModelSearchParameters; -use Mindee\V2\Search\RagDocuments\RagDocumentSearchParameters; +use Mindee\V2\Parsing\Search\BaseSearchResponse; use ReflectionClass; use ReflectionException; use ReflectionProperty; @@ -392,14 +390,18 @@ private function checkValidResponse(array $result): void } /** - * Makes a GET call to a search endpoint. - * @param string $path Search endpoint path (e.g. `/v2/search/models`). - * @param array $queryParams Query parameters to append. - * @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 reqGetSearch(string $path, array $queryParams): array + public function reqGetSearch(string $responseClass, BaseSearchParameters $params): BaseResponse { - $url = $this->baseUrl . $path; + $queryParams = $params->getQueryParams(); + $url = $this->baseUrl . "/v2/search/" . $params::$slug; if (!empty($queryParams)) { $url .= '?' . http_build_query($queryParams); } @@ -413,32 +415,6 @@ private function reqGetSearch(string $path, array $queryParams): array 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), ]; curl_close($ch); - return $resp; - } - - /** - * Retrieves a list of models matching the given criteria. - * @param ModelSearchParameters $params Search parameters. - * @return ModelSearchResponse The list of models matching the criteria. - */ - public function searchModels(ModelSearchParameters $params): ModelSearchResponse - { - return $this->processResponse( - ModelSearchResponse::class, - $this->reqGetSearch("/v2/search/models", $params->getQueryParams()) - ); - } - - /** - * Retrieves a list of RAG documents matching the given criteria. - * @param RagDocumentSearchParameters $params Search parameters. - * @return RagDocumentSearchResponse The list of RAG documents matching the criteria. - */ - public function searchRagDocuments(RagDocumentSearchParameters $params): RagDocumentSearchResponse - { - return $this->processResponse( - RagDocumentSearchResponse::class, - $this->reqGetSearch("/v2/search/rag-documents", $params->getQueryParams()) - ); + return $this->processResponse($responseClass, $resp); } } diff --git a/src/V2/Product/Classification/Params/ClassificationParameters.php b/src/V2/Product/Classification/Params/ClassificationParameters.php index 7a626759..537bf5ca 100644 --- a/src/V2/Product/Classification/Params/ClassificationParameters.php +++ b/src/V2/Product/Classification/Params/ClassificationParameters.php @@ -8,12 +8,12 @@ use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters for a classification utility inference. + * Parameters accepted by the asynchronous Classification product endpoint. */ 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 52fcef5d..d81fd378 100644 --- a/src/V2/Product/Crop/Params/CropParameters.php +++ b/src/V2/Product/Crop/Params/CropParameters.php @@ -8,12 +8,12 @@ use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters for a crop utility inference. + * Parameters accepted by the asynchronous Crop product endpoint. */ 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 30c8371f..e2ca64af 100644 --- a/src/V2/Product/Extraction/Params/ExtractionParameters.php +++ b/src/V2/Product/Extraction/Params/ExtractionParameters.php @@ -7,7 +7,7 @@ use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters accepted by the asynchronous **inference** v2 endpoint. + * Parameters accepted by the asynchronous Extraction product endpoint. */ class ExtractionParameters extends BaseProductParameters { @@ -23,7 +23,7 @@ class ExtractionParameters extends BaseProductParameters 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 17587a06..7882ded1 100644 --- a/src/V2/Product/Ocr/Params/OcrParameters.php +++ b/src/V2/Product/Ocr/Params/OcrParameters.php @@ -8,12 +8,12 @@ use Mindee\V2\ClientOptions\BaseProductParameters; /** - * Parameters for an ocr utility inference. + * Parameters accepted by the asynchronous OCR product endpoint. */ 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/Search/Models/ModelSearchParameters.php b/src/V2/Search/Models/ModelSearchParameters.php index 59e231d7..4756f1f1 100644 --- a/src/V2/Search/Models/ModelSearchParameters.php +++ b/src/V2/Search/Models/ModelSearchParameters.php @@ -11,6 +11,11 @@ */ class ModelSearchParameters extends BaseSearchParameters { + /** + * @var string Slug of the resource. + */ + public static string $slug = "models"; + /** * @param string|null $name Case-insensitive search term for the model name. * @param string|null $modelType Case-insensitive search term for the model type. diff --git a/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php b/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php index 63a762f2..d25a9cd7 100644 --- a/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php +++ b/src/V2/Search/RagDocuments/RagDocumentSearchParameters.php @@ -13,6 +13,11 @@ */ class RagDocumentSearchParameters extends BaseSearchParameters { + /** + * @var string Slug of the resource. + */ + public static string $slug = "rag-documents"; + /** * @param string|null $modelId Model identifier to search in (required). * @param string|null $filename Case-insensitive substring search on filename. 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/Parsing/RagDocumentSearchResponseTest.php b/tests/V2/Search/RagDocumentSearchTest.php similarity index 56% rename from tests/V2/Parsing/RagDocumentSearchResponseTest.php rename to tests/V2/Search/RagDocumentSearchTest.php index 166915aa..89a10496 100644 --- a/tests/V2/Parsing/RagDocumentSearchResponseTest.php +++ b/tests/V2/Search/RagDocumentSearchTest.php @@ -1,55 +1,58 @@ ragDocuments); - foreach ($response->ragDocuments as $document) { - self::assertInstanceOf(RagDocument::class, $document); - self::assertNotEmpty($document->id); - self::assertNotEmpty($document->modelId); - self::assertNotEmpty($document->filename); - } + 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::assertNotNull($thirdItem->lastMatchAt); + self::assertEquals(new DateTimeImmutable("2026-06-18T14:35:46.248006Z"), $thirdItem->lastMatchAt); self::assertEquals("Active", $thirdItem->status); - - self::assertEquals(50, $response->pagination->perPage); - self::assertEquals(1, $response->pagination->page); - self::assertEquals(3, $response->pagination->totalItems); - self::assertEquals(1, $response->pagination->totalPages); } }