From d5c4cf0dc1fdc376febd3d9ba65eea024e7272e2 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/2] :sparkles: add RAG search API
---
bin/V2/SearchRagDocumentsCommand.php | 103 ++++++++++++++++++
bin/cli.php | 5 +-
src/V2/Client.php | 40 +++++--
src/V2/ClientOptions/BaseSearchParameters.php | 37 +++++++
.../ClientOptions/ModelSearchParameters.php | 41 +++++++
.../RagDocumentSearchParameters.php | 50 +++++++++
src/V2/Http/MindeeApiV2.php | 61 ++++++++---
src/V2/Parsing/Search/BaseSearchResponse.php | 43 ++++++++
src/V2/Parsing/Search/ModelSearchResponse.php | 40 +++++++
src/V2/Parsing/Search/RagDocument.php | 75 +++++++++++++
.../Search/RagDocumentSearchResponse.php | 40 +++++++
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 +-
16 files changed, 655 insertions(+), 66 deletions(-)
create mode 100644 bin/V2/SearchRagDocumentsCommand.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/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/V2/Client.php b/src/V2/Client.php
index 9997a827..ae3bf3c6 100644
--- a/src/V2/Client.php
+++ b/src/V2/Client.php
@@ -10,10 +10,13 @@
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\Http\MindeeApiV2;
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;
/**
* Mindee Client V2.
@@ -165,13 +168,36 @@ public function enqueueAndGetResult(
}
/**
- * 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.
+ * Searches for a list of available models matching the given criteria.
+ *
+ * Pass a {@see ModelSearchParameters} object to use the full search API
+ * (including pagination). Passing individual `$modelName`/`$modelType`
+ * strings is deprecated and kept for backward compatibility.
+ *
+ * @param ModelSearchParameters|string|null $modelName Search parameters, or a model name filter (deprecated).
+ * @param string|null $modelType Optional model type to filter by (deprecated string-based path only).
+ * @return ModelSearchResponse The list of models matching the criteria.
+ */
+ public function searchModels(
+ ModelSearchParameters|string|null $modelName = null,
+ ?string $modelType = null
+ ): ModelSearchResponse {
+ if ($modelName instanceof ModelSearchParameters) {
+ return $this->mindeeApi->searchModels($modelName);
+ }
+
+ return $this->mindeeApi->searchModelsObsolete(
+ new ModelSearchParameters($modelName, $modelType)
+ );
+ }
+
+ /**
+ * 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 searchModels(?string $modelName = null, ?string $modelType = null): SearchResponse
+ public function searchRagDocuments(RagDocumentSearchParameters $params): RagDocumentSearchResponse
{
- return $this->mindeeApi->searchModels($modelName, $modelType);
+ 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..91383c6d 100644
--- a/src/V2/Http/MindeeApiV2.php
+++ b/src/V2/Http/MindeeApiV2.php
@@ -18,11 +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\ModelSearchResponse;
+use Mindee\V2\Parsing\Search\RagDocumentSearchResponse;
use Mindee\V2\Parsing\Search\SearchResponse;
use ReflectionClass;
use ReflectionException;
@@ -389,20 +393,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 +418,42 @@ 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.
+ * 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 models matching the given criteria (deprecated response type).
+ * @param ModelSearchParameters $params Search parameters.
* @return SearchResponse The list of models matching the criteria.
+ * @deprecated Use {@see searchModels()} instead.
*/
- public function searchModels(?string $modelName = null, ?string $modelType = null): SearchResponse
+ public function searchModelsObsolete(ModelSearchParameters $params): SearchResponse
{
- return $this->processResponse(SearchResponse::class, $this->reqGetSearchModels($modelName, $modelType));
+ return $this->processResponse(
+ SearchResponse::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/Search/BaseSearchResponse.php b/src/V2/Parsing/Search/BaseSearchResponse.php
new file mode 100644
index 00000000..e1002c65
--- /dev/null
+++ b/src/V2/Parsing/Search/BaseSearchResponse.php
@@ -0,0 +1,43 @@
+> $rawResponse Raw server response array.
+ */
+ public function __construct(array $rawResponse)
+ {
+ parent::__construct($rawResponse);
+ $this->pagination = new PaginationMetadata($rawResponse['pagination']);
+ }
+
+ /**
+ * Renders the shared pagination metadata block.
+ *
+ * @return array Lines composing the pagination section.
+ */
+ protected function paginationLines(): array
+ {
+ return [
+ '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..4f6d0cfa
--- /dev/null
+++ b/src/V2/Parsing/Search/ModelSearchResponse.php
@@ -0,0 +1,40 @@
+> $rawResponse Raw server response array.
+ */
+ public function __construct(array $rawResponse)
+ {
+ parent::__construct($rawResponse);
+ $this->models = new SearchModels($rawResponse['models']);
+ }
+
+ /**
+ * @return string String representation.
+ */
+ public function __toString(): string
+ {
+ return implode("\n", array_merge(
+ [
+ 'Models',
+ '######',
+ (string) $this->models,
+ ],
+ $this->paginationLines()
+ ));
+ }
+}
diff --git a/src/V2/Parsing/Search/RagDocument.php b/src/V2/Parsing/Search/RagDocument.php
new file mode 100644
index 00000000..ddb2668b
--- /dev/null
+++ b/src/V2/Parsing/Search/RagDocument.php
@@ -0,0 +1,75 @@
+> $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 = $this->parseDate($rawResponse['last_match_at'] ?? null);
+ $this->status = $rawResponse['status'];
+ }
+
+ /**
+ * Parse a date string into a DateTimeImmutable object.
+ *
+ * @param string|null $dateString Date string to parse.
+ * @return DateTimeImmutable|null Parsed date or null if empty/invalid.
+ */
+ private function parseDate(?string $dateString): ?DateTimeImmutable
+ {
+ if (empty($dateString)) {
+ return null;
+ }
+ try {
+ return new DateTimeImmutable($dateString);
+ } catch (Exception) {
+ return null;
+ }
+ }
+}
diff --git a/src/V2/Parsing/Search/RagDocumentSearchResponse.php b/src/V2/Parsing/Search/RagDocumentSearchResponse.php
new file mode 100644
index 00000000..01a1a5ae
--- /dev/null
+++ b/src/V2/Parsing/Search/RagDocumentSearchResponse.php
@@ -0,0 +1,40 @@
+> $rawResponse Raw server response array.
+ */
+ public function __construct(array $rawResponse)
+ {
+ parent::__construct($rawResponse);
+ $this->ragDocuments = new RagDocuments($rawResponse['rag_documents']);
+ }
+
+ /**
+ * @return string String representation.
+ */
+ public function __toString(): string
+ {
+ return implode("\n", array_merge(
+ [
+ 'RAG Documents',
+ '############',
+ (string) $this->ragDocuments,
+ ],
+ $this->paginationLines()
+ ));
+ }
+}
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..fa7b7666 160000
--- a/tests/resources
+++ b/tests/resources
@@ -1 +1 @@
-Subproject commit e41ab97c2833f15ea5c4edf221c2d197d212f632
+Subproject commit fa7b76669c0e53fc46ca9f87b0151bac3ed40083
From a54cc75a569e7908cbdec7e406005bab84e814e5 Mon Sep 17 00:00:00 2001
From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com>
Date: Thu, 23 Jul 2026 18:10:56 +0200
Subject: [PATCH 2/2] apply suggested changes
---
bin/V2/SearchModelsCommand.php | 5 +-
src/Parsing/DateHelper.php | 51 +++++++++++++++++++
src/V2/Client.php | 22 ++------
src/V2/Http/MindeeApiV2.php | 15 ------
src/V2/Parsing/Job/Job.php | 24 ++-------
src/V2/Parsing/Job/JobWebhook.php | 22 +-------
src/V2/Parsing/Search/BaseSearchResponse.php | 26 ++++++----
src/V2/Parsing/Search/ModelSearchResponse.php | 17 +++----
src/V2/Parsing/Search/RagDocument.php | 22 +-------
.../Search/RagDocumentSearchResponse.php | 17 +++----
10 files changed, 97 insertions(+), 124 deletions(-)
create mode 100644 src/Parsing/DateHelper.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/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);
- }
-
- return $this->mindeeApi->searchModelsObsolete(
- new ModelSearchParameters($modelName, $modelType)
- );
+ public function searchModels(?ModelSearchParameters $params = null): ModelSearchResponse
+ {
+ return $this->mindeeApi->searchModels($params ?? new ModelSearchParameters());
}
/**
diff --git a/src/V2/Http/MindeeApiV2.php b/src/V2/Http/MindeeApiV2.php
index 91383c6d..5af23128 100644
--- a/src/V2/Http/MindeeApiV2.php
+++ b/src/V2/Http/MindeeApiV2.php
@@ -27,7 +27,6 @@
use Mindee\V2\Parsing\Job\JobResponse;
use Mindee\V2\Parsing\Search\ModelSearchResponse;
use Mindee\V2\Parsing\Search\RagDocumentSearchResponse;
-use Mindee\V2\Parsing\Search\SearchResponse;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;
@@ -430,20 +429,6 @@ public function searchModels(ModelSearchParameters $params): ModelSearchResponse
);
}
- /**
- * Retrieves a list of models matching the given criteria (deprecated response type).
- * @param ModelSearchParameters $params Search parameters.
- * @return SearchResponse The list of models matching the criteria.
- * @deprecated Use {@see searchModels()} instead.
- */
- public function searchModelsObsolete(ModelSearchParameters $params): SearchResponse
- {
- return $this->processResponse(
- SearchResponse::class,
- $this->reqGetSearch("/v2/search/models", $params->getQueryParams())
- );
- }
-
/**
* Retrieves a list of RAG documents matching the given criteria.
* @param RagDocumentSearchParameters $params Search parameters.
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
index e1002c65..143a07b2 100644
--- a/src/V2/Parsing/Search/BaseSearchResponse.php
+++ b/src/V2/Parsing/Search/BaseSearchResponse.php
@@ -27,17 +27,25 @@ public function __construct(array $rawResponse)
}
/**
- * Renders the shared pagination metadata block.
+ * Lines composing the response-specific body (header + items).
*
- * @return array Lines composing the pagination section.
+ * @return array Body lines.
*/
- protected function paginationLines(): array
+ abstract protected function bodyLines(): array;
+
+ /**
+ * @return string String representation.
+ */
+ public function __toString(): string
{
- return [
- 'Pagination Metadata',
- '###################',
- (string) $this->pagination,
- '',
- ];
+ 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
index 4f6d0cfa..4b573f81 100644
--- a/src/V2/Parsing/Search/ModelSearchResponse.php
+++ b/src/V2/Parsing/Search/ModelSearchResponse.php
@@ -24,17 +24,14 @@ public function __construct(array $rawResponse)
}
/**
- * @return string String representation.
+ * @return array Body lines.
*/
- public function __toString(): string
+ protected function bodyLines(): array
{
- return implode("\n", array_merge(
- [
- 'Models',
- '######',
- (string) $this->models,
- ],
- $this->paginationLines()
- ));
+ return [
+ 'Models',
+ '######',
+ (string) $this->models,
+ ];
}
}
diff --git a/src/V2/Parsing/Search/RagDocument.php b/src/V2/Parsing/Search/RagDocument.php
index ddb2668b..cfb26a45 100644
--- a/src/V2/Parsing/Search/RagDocument.php
+++ b/src/V2/Parsing/Search/RagDocument.php
@@ -5,7 +5,7 @@
namespace Mindee\V2\Parsing\Search;
use DateTimeImmutable;
-use Exception;
+use Mindee\Parsing\DateHelper;
/**
* Individual RAG document information.
@@ -51,25 +51,7 @@ public function __construct(array $rawResponse)
$this->filename = $rawResponse['filename'];
$this->createdAt = new DateTimeImmutable($rawResponse['created_at']);
$this->totalMatches = $rawResponse['total_matches'];
- $this->lastMatchAt = $this->parseDate($rawResponse['last_match_at'] ?? null);
+ $this->lastMatchAt = DateHelper::parseDateImmutable($rawResponse['last_match_at'] ?? null);
$this->status = $rawResponse['status'];
}
-
- /**
- * Parse a date string into a DateTimeImmutable object.
- *
- * @param string|null $dateString Date string to parse.
- * @return DateTimeImmutable|null Parsed date or null if empty/invalid.
- */
- private function parseDate(?string $dateString): ?DateTimeImmutable
- {
- if (empty($dateString)) {
- return null;
- }
- try {
- return new DateTimeImmutable($dateString);
- } catch (Exception) {
- return null;
- }
- }
}
diff --git a/src/V2/Parsing/Search/RagDocumentSearchResponse.php b/src/V2/Parsing/Search/RagDocumentSearchResponse.php
index 01a1a5ae..4157fdef 100644
--- a/src/V2/Parsing/Search/RagDocumentSearchResponse.php
+++ b/src/V2/Parsing/Search/RagDocumentSearchResponse.php
@@ -24,17 +24,14 @@ public function __construct(array $rawResponse)
}
/**
- * @return string String representation.
+ * @return array Body lines.
*/
- public function __toString(): string
+ protected function bodyLines(): array
{
- return implode("\n", array_merge(
- [
- 'RAG Documents',
- '############',
- (string) $this->ragDocuments,
- ],
- $this->paginationLines()
- ));
+ return [
+ 'RAG Documents',
+ '############',
+ (string) $this->ragDocuments,
+ ];
}
}