From 42241344b16b62debaa9fcfacb1087234ea01872 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Sun, 30 Nov 2025 21:42:44 +0100 Subject: [PATCH 01/17] feat: implement Gemini File Search APIs (Stores & Documents) --- src/Client.php | 7 + src/Contracts/ClientContract.php | 3 + .../Resources/FileSearchStoresContract.php | 69 ++++++++ .../FileSearchStores/CreateRequest.php | 42 +++++ .../FileSearchStores/DeleteRequest.php | 31 ++++ .../Documents/DeleteRequest.php | 34 ++++ .../FileSearchStores/Documents/GetRequest.php | 25 +++ .../Documents/ListRequest.php | 46 +++++ src/Requests/FileSearchStores/GetRequest.php | 25 +++ src/Requests/FileSearchStores/ListRequest.php | 45 +++++ .../FileSearchStores/UploadRequest.php | 72 ++++++++ src/Resources/FileSearchStores.php | 91 ++++++++++ .../Documents/DocumentResponse.php | 52 ++++++ .../Documents/ListResponse.php | 49 ++++++ .../FileSearchStoreResponse.php | 40 +++++ .../FileSearchStores/ListResponse.php | 49 ++++++ .../FileSearchStores/UploadResponse.php | 54 ++++++ .../Documents/DocumentResponseFixture.php | 16 ++ .../Documents/ListResponseFixture.php | 15 ++ .../FileSearchStoreResponseFixture.php | 13 ++ .../FileSearchStores/ListResponseFixture.php | 15 ++ .../UploadResponseFixture.php | 14 ++ tests/Resources/FileSearchStores.php | 162 ++++++++++++++++++ 23 files changed, 969 insertions(+) create mode 100644 src/Contracts/Resources/FileSearchStoresContract.php create mode 100644 src/Requests/FileSearchStores/CreateRequest.php create mode 100644 src/Requests/FileSearchStores/DeleteRequest.php create mode 100644 src/Requests/FileSearchStores/Documents/DeleteRequest.php create mode 100644 src/Requests/FileSearchStores/Documents/GetRequest.php create mode 100644 src/Requests/FileSearchStores/Documents/ListRequest.php create mode 100644 src/Requests/FileSearchStores/GetRequest.php create mode 100644 src/Requests/FileSearchStores/ListRequest.php create mode 100644 src/Requests/FileSearchStores/UploadRequest.php create mode 100644 src/Resources/FileSearchStores.php create mode 100644 src/Responses/FileSearchStores/Documents/DocumentResponse.php create mode 100644 src/Responses/FileSearchStores/Documents/ListResponse.php create mode 100644 src/Responses/FileSearchStores/FileSearchStoreResponse.php create mode 100644 src/Responses/FileSearchStores/ListResponse.php create mode 100644 src/Responses/FileSearchStores/UploadResponse.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php create mode 100644 tests/Resources/FileSearchStores.php diff --git a/src/Client.php b/src/Client.php index ecad008..f3a8d58 100644 --- a/src/Client.php +++ b/src/Client.php @@ -8,6 +8,7 @@ use Gemini\Contracts\ClientContract; use Gemini\Contracts\Resources\CachedContentsContract; use Gemini\Contracts\Resources\FilesContract; +use Gemini\Contracts\Resources\FileSearchStoresContract; use Gemini\Contracts\Resources\GenerativeModelContract; use Gemini\Contracts\TransporterContract; use Gemini\Enums\ModelType; @@ -15,6 +16,7 @@ use Gemini\Resources\ChatSession; use Gemini\Resources\EmbeddingModel; use Gemini\Resources\Files; +use Gemini\Resources\FileSearchStores; use Gemini\Resources\GenerativeModel; use Gemini\Resources\Models; @@ -81,4 +83,9 @@ public function cachedContents(): CachedContentsContract { return new CachedContents($this->transporter); } + + public function fileSearchStores(): FileSearchStoresContract + { + return new FileSearchStores($this->transporter); + } } diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php index 356f621..481ef01 100644 --- a/src/Contracts/ClientContract.php +++ b/src/Contracts/ClientContract.php @@ -9,6 +9,7 @@ use Gemini\Contracts\Resources\ChatSessionContract; use Gemini\Contracts\Resources\EmbeddingModalContract; use Gemini\Contracts\Resources\FilesContract; +use Gemini\Contracts\Resources\FileSearchStoresContract; use Gemini\Contracts\Resources\GenerativeModelContract; use Gemini\Contracts\Resources\ModelContract; @@ -35,4 +36,6 @@ public function chat(BackedEnum|string $model): ChatSessionContract; public function files(): FilesContract; public function cachedContents(): CachedContentsContract; + + public function fileSearchStores(): FileSearchStoresContract; } diff --git a/src/Contracts/Resources/FileSearchStoresContract.php b/src/Contracts/Resources/FileSearchStoresContract.php new file mode 100644 index 0000000..c75a878 --- /dev/null +++ b/src/Contracts/Resources/FileSearchStoresContract.php @@ -0,0 +1,69 @@ + + */ + public function defaultBody(): array + { + $body = []; + + if ($this->displayName !== null) { + $body['displayName'] = $this->displayName; + } + + return $body; + } +} diff --git a/src/Requests/FileSearchStores/DeleteRequest.php b/src/Requests/FileSearchStores/DeleteRequest.php new file mode 100644 index 0000000..45a696c --- /dev/null +++ b/src/Requests/FileSearchStores/DeleteRequest.php @@ -0,0 +1,31 @@ +name; + } + + public function defaultQuery(): array + { + return $this->force ? ['force' => 'true'] : []; + } +} diff --git a/src/Requests/FileSearchStores/Documents/DeleteRequest.php b/src/Requests/FileSearchStores/Documents/DeleteRequest.php new file mode 100644 index 0000000..fa8825d --- /dev/null +++ b/src/Requests/FileSearchStores/Documents/DeleteRequest.php @@ -0,0 +1,34 @@ +name; + } + + /** + * @return array + */ + public function defaultQuery(): array + { + return $this->force ? ['force' => 'true'] : []; + } +} diff --git a/src/Requests/FileSearchStores/Documents/GetRequest.php b/src/Requests/FileSearchStores/Documents/GetRequest.php new file mode 100644 index 0000000..a436a57 --- /dev/null +++ b/src/Requests/FileSearchStores/Documents/GetRequest.php @@ -0,0 +1,25 @@ +name; + } +} diff --git a/src/Requests/FileSearchStores/Documents/ListRequest.php b/src/Requests/FileSearchStores/Documents/ListRequest.php new file mode 100644 index 0000000..f275994 --- /dev/null +++ b/src/Requests/FileSearchStores/Documents/ListRequest.php @@ -0,0 +1,46 @@ +pageSize !== null) { + $query['pageSize'] = $this->pageSize; + } + + if ($this->nextPageToken !== null) { + $query['pageToken'] = $this->nextPageToken; + } + + return $query; + } + + public function resolveEndpoint(): string + { + return $this->storeName.'/documents'; + } +} diff --git a/src/Requests/FileSearchStores/GetRequest.php b/src/Requests/FileSearchStores/GetRequest.php new file mode 100644 index 0000000..753c98f --- /dev/null +++ b/src/Requests/FileSearchStores/GetRequest.php @@ -0,0 +1,25 @@ +name; + } +} diff --git a/src/Requests/FileSearchStores/ListRequest.php b/src/Requests/FileSearchStores/ListRequest.php new file mode 100644 index 0000000..ddf001c --- /dev/null +++ b/src/Requests/FileSearchStores/ListRequest.php @@ -0,0 +1,45 @@ +pageSize !== null) { + $query['pageSize'] = $this->pageSize; + } + + if ($this->nextPageToken !== null) { + $query['pageToken'] = $this->nextPageToken; + } + + return $query; + } + + public function resolveEndpoint(): string + { + return 'fileSearchStores'; + } +} diff --git a/src/Requests/FileSearchStores/UploadRequest.php b/src/Requests/FileSearchStores/UploadRequest.php new file mode 100644 index 0000000..21c21bf --- /dev/null +++ b/src/Requests/FileSearchStores/UploadRequest.php @@ -0,0 +1,72 @@ +storeName.':uploadToFileSearchStore'; + } + + public function toRequest(string $baseUrl, array $headers = [], array $queryParams = []): RequestInterface + { + $factory = new Psr17Factory; + $boundary = rand(111111, 999999); + + $metadata = []; + if ($this->displayName) { + $metadata['displayName'] = $this->displayName; + } + if ($this->mimeType) { + $metadata['mimeType'] = $this->mimeType->value; + } + + $requestJson = json_encode($metadata); + $contents = file_get_contents($this->filename); + + $request = $factory + ->createRequest($this->method->value, str_replace('/v1', '/upload/v1', $baseUrl).$this->resolveEndpoint()) + ->withHeader('X-Goog-Upload-Protocol', 'multipart'); + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $contentType = $this->mimeType instanceof MimeType ? $this->mimeType->value : 'application/octet-stream'; + + $request = $request->withHeader('Content-Type', "multipart/related; boundary={$boundary}") + ->withBody($factory->createStream(<< $response */ + $response = $this->transporter->request(new CreateRequest($displayName)); + + return FileSearchStoreResponse::from($response->data()); + } + + public function get(string $name): FileSearchStoreResponse + { + /** @var ResponseDTO $response */ + $response = $this->transporter->request(new GetRequest($name)); + + return FileSearchStoreResponse::from($response->data()); + } + + public function list(?int $pageSize = null, ?string $nextPageToken = null): ListResponse + { + /** @var ResponseDTO, nextPageToken: ?string }> $response */ + $response = $this->transporter->request(new ListRequest(pageSize: $pageSize, nextPageToken: $nextPageToken)); + + return ListResponse::from($response->data()); + } + + public function delete(string $name, bool $force = false): void + { + $this->transporter->request(new DeleteRequest($name, $force)); + } + + public function upload(string $storeName, string $filename, ?MimeType $mimeType = null, ?string $displayName = null): UploadResponse + { + $mimeType ??= MimeType::from((string) mime_content_type($filename)); + $displayName ??= $filename; + + /** @var ResponseDTO, done: bool, response?: array, error?: array }> $response */ + $response = $this->transporter->request(new UploadRequest($storeName, $filename, $displayName, $mimeType)); + + return UploadResponse::from($response->data()); + } + + public function listDocuments(string $storeName, ?int $pageSize = null, ?string $nextPageToken = null): DocumentListResponse + { + /** @var ResponseDTO, updateTime?: string, createTime?: string }>, nextPageToken: ?string }> $response */ + $response = $this->transporter->request(new ListDocumentsRequest($storeName, $pageSize, $nextPageToken)); + + return DocumentListResponse::from($response->data()); + } + + public function getDocument(string $name): DocumentResponse + { + /** @var ResponseDTO, updateTime?: string, createTime?: string }> $response */ + $response = $this->transporter->request(new GetDocumentRequest($name)); + + return DocumentResponse::from($response->data()); + } + + public function deleteDocument(string $name, bool $force = false): void + { + $this->transporter->request(new DeleteDocumentRequest($name, $force)); + } +} diff --git a/src/Responses/FileSearchStores/Documents/DocumentResponse.php b/src/Responses/FileSearchStores/Documents/DocumentResponse.php new file mode 100644 index 0000000..fbebbc5 --- /dev/null +++ b/src/Responses/FileSearchStores/Documents/DocumentResponse.php @@ -0,0 +1,52 @@ + $customMetadata + */ + public function __construct( + public readonly string $name, + public readonly ?string $displayName = null, + public readonly array $customMetadata = [], + public readonly ?string $updateTime = null, + public readonly ?string $createTime = null, + ) {} + + /** + * @param array{ name: string, displayName?: string, customMetadata?: array, updateTime?: string, createTime?: string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + displayName: $attributes['displayName'] ?? null, + customMetadata: $attributes['customMetadata'] ?? [], + updateTime: $attributes['updateTime'] ?? null, + createTime: $attributes['createTime'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'name' => $this->name, + 'displayName' => $this->displayName, + 'customMetadata' => $this->customMetadata, + 'updateTime' => $this->updateTime, + 'createTime' => $this->createTime, + ]; + } +} diff --git a/src/Responses/FileSearchStores/Documents/ListResponse.php b/src/Responses/FileSearchStores/Documents/ListResponse.php new file mode 100644 index 0000000..5036d3e --- /dev/null +++ b/src/Responses/FileSearchStores/Documents/ListResponse.php @@ -0,0 +1,49 @@ + $documents + */ + public function __construct( + public readonly array $documents, + public readonly ?string $nextPageToken = null, + ) {} + + /** + * @param array{ documents: ?array, updateTime?: string, createTime?: string }>, nextPageToken: ?string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + documents: array_map( + fn (array $document): DocumentResponse => DocumentResponse::from($document), + $attributes['documents'] ?? [] + ), + nextPageToken: $attributes['nextPageToken'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'documents' => array_map( + fn (DocumentResponse $document): array => $document->toArray(), + $this->documents + ), + 'nextPageToken' => $this->nextPageToken, + ]; + } +} diff --git a/src/Responses/FileSearchStores/FileSearchStoreResponse.php b/src/Responses/FileSearchStores/FileSearchStoreResponse.php new file mode 100644 index 0000000..feab3a0 --- /dev/null +++ b/src/Responses/FileSearchStores/FileSearchStoreResponse.php @@ -0,0 +1,40 @@ + $this->name, + 'displayName' => $this->displayName, + ]; + } +} diff --git a/src/Responses/FileSearchStores/ListResponse.php b/src/Responses/FileSearchStores/ListResponse.php new file mode 100644 index 0000000..08035e9 --- /dev/null +++ b/src/Responses/FileSearchStores/ListResponse.php @@ -0,0 +1,49 @@ + $fileSearchStores + */ + public function __construct( + public readonly array $fileSearchStores, + public readonly ?string $nextPageToken = null, + ) {} + + /** + * @param array{ fileSearchStores: ?array, nextPageToken: ?string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + fileSearchStores: array_map( + fn (array $store): FileSearchStoreResponse => FileSearchStoreResponse::from($store), + $attributes['fileSearchStores'] ?? [] + ), + nextPageToken: $attributes['nextPageToken'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'fileSearchStores' => array_map( + fn (FileSearchStoreResponse $store): array => $store->toArray(), + $this->fileSearchStores + ), + 'nextPageToken' => $this->nextPageToken, + ]; + } +} diff --git a/src/Responses/FileSearchStores/UploadResponse.php b/src/Responses/FileSearchStores/UploadResponse.php new file mode 100644 index 0000000..c94d34f --- /dev/null +++ b/src/Responses/FileSearchStores/UploadResponse.php @@ -0,0 +1,54 @@ + $metadata + * @param array|null $response + * @param array|null $error + */ + public function __construct( + public readonly string $name, + public readonly bool $done, + public readonly ?array $metadata = null, + public readonly ?array $response = null, + public readonly ?array $error = null, + ) {} + + /** + * @param array{ name: string, done: bool, metadata?: array, response?: array, error?: array } $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + done: $attributes['done'] ?? false, + metadata: $attributes['metadata'] ?? null, + response: $attributes['response'] ?? null, + error: $attributes['error'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'name' => $this->name, + 'done' => $this->done, + 'metadata' => $this->metadata, + 'response' => $this->response, + 'error' => $this->error, + ]; + } +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php new file mode 100644 index 0000000..0a3f2e7 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php @@ -0,0 +1,16 @@ + 'fileSearchStores/123/documents/abc', + 'displayName' => 'My Document', + 'customMetadata' => [], + 'updateTime' => '2024-01-01T00:00:00Z', + 'createTime' => '2024-01-01T00:00:00Z', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php new file mode 100644 index 0000000..d7bcde0 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php @@ -0,0 +1,15 @@ + [ + DocumentResponseFixture::ATTRIBUTES, + ], + 'nextPageToken' => 'next-page-token', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php new file mode 100644 index 0000000..4ee27e2 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php @@ -0,0 +1,13 @@ + 'fileSearchStores/123-456', + 'displayName' => 'My Store', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php new file mode 100644 index 0000000..481bfa4 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php @@ -0,0 +1,15 @@ + [ + FileSearchStoreResponseFixture::ATTRIBUTES, + ], + 'nextPageToken' => 'next-page-token', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php new file mode 100644 index 0000000..0c58487 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php @@ -0,0 +1,14 @@ + 'operations/123-456', + 'metadata' => ['some' => 'meta'], + 'done' => false, + ]; +} diff --git a/tests/Resources/FileSearchStores.php b/tests/Resources/FileSearchStores.php new file mode 100644 index 0000000..392b418 --- /dev/null +++ b/tests/Resources/FileSearchStores.php @@ -0,0 +1,162 @@ + 'My Store'], + validateParams: true + ); + + $result = $client->fileSearchStores()->create('My Store'); + + expect($result) + ->toBeInstanceOf(FileSearchStoreResponse::class) + ->name->toBe('fileSearchStores/123-456') + ->displayName->toBe('My Store'); +}); + +test('get', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores/123-456', + response: FileSearchStoreResponse::fake() + ); + + $result = $client->fileSearchStores()->get('fileSearchStores/123-456'); + + expect($result) + ->toBeInstanceOf(FileSearchStoreResponse::class) + ->name->toBe('fileSearchStores/123-456'); +}); + +test('list', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores', + response: ListResponse::fake() + ); + + $result = $client->fileSearchStores()->list(); + + expect($result) + ->toBeInstanceOf(ListResponse::class) + ->fileSearchStores->toHaveCount(1) + ->fileSearchStores->each->toBeInstanceOf(FileSearchStoreResponse::class); +}); + +test('delete', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123-456', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]) + ); + + $client->fileSearchStores()->delete('fileSearchStores/123-456'); + + // If no exception, it passed. + expect(true)->toBeTrue(); +}); + +test('delete with force', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123-456', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]), + params: ['force' => 'true'], + validateParams: true + ); + + $client->fileSearchStores()->delete('fileSearchStores/123-456', true); + + expect(true)->toBeTrue(); +}); + +describe('upload', function () { + beforeEach(function () { + $this->tmpFile = tmpfile(); + $this->tmpFilepath = stream_get_meta_data($this->tmpFile)['uri']; + }); + afterEach(function () { + fclose($this->tmpFile); + }); + + test('upload', function () { + $client = mockClient( + method: Method::POST, + endpoint: 'fileSearchStores/123:uploadToFileSearchStore', + response: UploadResponse::fake(), + rootPath: '/upload/v1beta/' + ); + + $result = $client->fileSearchStores()->upload('fileSearchStores/123', $this->tmpFilepath, MimeType::TEXT_PLAIN, 'Display'); + + expect($result) + ->toBeInstanceOf(UploadResponse::class) + ->name->toBe('operations/123-456'); + }); +}); + +test('list documents', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores/123/documents', + response: DocumentListResponse::fake() + ); + + $result = $client->fileSearchStores()->listDocuments('fileSearchStores/123'); + + expect($result) + ->toBeInstanceOf(DocumentListResponse::class) + ->documents->toHaveCount(1) + ->documents->each->toBeInstanceOf(DocumentResponse::class); +}); + +test('get document', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores/123/documents/abc', + response: DocumentResponse::fake() + ); + + $result = $client->fileSearchStores()->getDocument('fileSearchStores/123/documents/abc'); + + expect($result) + ->toBeInstanceOf(DocumentResponse::class) + ->name->toBe('fileSearchStores/123/documents/abc'); +}); + +test('delete document', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123/documents/abc', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]) + ); + + $client->fileSearchStores()->deleteDocument('fileSearchStores/123/documents/abc'); + + expect(true)->toBeTrue(); +}); + +test('delete document with force', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123/documents/abc', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]), + params: ['force' => 'true'], + validateParams: true + ); + + $client->fileSearchStores()->deleteDocument('fileSearchStores/123/documents/abc', true); + + expect(true)->toBeTrue(); +}); From 01560091a31497989f96a2aa991b7c585d207c17 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 1 Dec 2025 11:36:01 +0100 Subject: [PATCH 02/17] feat: Add Google Maps and File Search tool support This commit introduces comprehensive support for Google Maps and File Search tools, enhancing the client's grounding capabilities. --- README.md | 56 ++++++++++++++ src/Data/FileSearch.php | 46 ++++++++++++ src/Data/GoogleMaps.php | 41 ++++++++++ src/Data/RetrievalConfig.php | 43 +++++++++++ src/Data/Tool.php | 10 +++ src/Data/ToolConfig.php | 15 +++- src/Testing/ClientFake.php | 6 ++ .../FileSearchStoresTestResource.php | 65 ++++++++++++++++ tests/Data/Tool.php | 48 ++++++++++++ tests/Data/ToolConfig.php | 75 +++++++++++++++++++ 10 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 src/Data/FileSearch.php create mode 100644 src/Data/GoogleMaps.php create mode 100644 src/Data/RetrievalConfig.php create mode 100644 src/Testing/Resources/FileSearchStoresTestResource.php create mode 100644 tests/Data/Tool.php create mode 100644 tests/Data/ToolConfig.php diff --git a/README.md b/README.md index 2601a22..8ab36ae 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ - [Function calling](#function-calling) - [Code Execution](#code-execution) - [Grounding with Google Search](#grounding-with-google-search) + - [Grounding with Google Maps](#grounding-with-google-maps) + - [Grounding with File Search](#grounding-with-file-search) - [System Instructions](#system-instructions) - [Speech generation](#speech-generation) - [Thinking Mode](#thinking-mode) @@ -482,6 +484,59 @@ if ($groundingMetadata !== null) { } ``` +#### Grounding with Google Maps +Grounding with Google Maps allows the model to utilize real-world geographical data. This enables more precise location-based responses, such as finding nearby points of interest. + +```php +use Gemini\Data\GoogleMaps; +use Gemini\Data\RetrievalConfig; +use Gemini\Data\Tool; +use Gemini\Data\ToolConfig; + +$tool = new Tool( + googleMaps: new GoogleMaps(enableWidget: true) +); + +$toolConfig = new ToolConfig( + retrievalConfig: new RetrievalConfig( + latitude: 40.758896, + longitude: -73.985130 + ) +); + +$response = $client + ->generativeModel(model: 'gemini-2.0-flash') + ->withTool($tool) + ->withToolConfig($toolConfig) + ->generateContent('Find coffee shops near me'); + +echo $response->text(); +// (Model output referencing coffee shops) +``` + +#### Grounding with File Search +Grounding with File Search enables the model to retrieve and utilize information from your indexed files. This is useful for answering questions based on private or extensive document collections. + +```php +use Gemini\Data\FileSearch; +use Gemini\Data\Tool; + +$tool = new Tool( + fileSearch: new FileSearch( + fileSearchStoreNames: ['files/my-document-store'], + metadataFilter: 'author = "Robert Graves"' + ) +); + +$response = $client + ->generativeModel(model: 'gemini-2.0-flash') + ->withTool($tool) + ->generateContent('Summarize the document about Greek myths by Robert Graves'); + +echo $response->text(); +// (Model output summarizing the document) +``` + #### System Instructions System instructions let you steer the behavior of the model based on your specific needs and use cases. You can set the role and personality of the model, define the format of responses, and provide goals and guardrails for model behavior. @@ -631,6 +686,7 @@ Every prompt you send to the model includes parameter values that control how th Also, you can use safety settings to adjust the likelihood of getting responses that may be considered harmful. By default, safety settings block content with medium and/or high probability of being unsafe content across all dimensions. Learn more about [safety settings](https://ai.google.dev/docs/concepts#safety_setting). +When using tools like `FileSearch`, you may also provide additional configuration via `ToolConfig`, such as `RetrievalConfig` for geographical context. ```php use Gemini\Data\GenerationConfig; diff --git a/src/Data/FileSearch.php b/src/Data/FileSearch.php new file mode 100644 index 0000000..34a7552 --- /dev/null +++ b/src/Data/FileSearch.php @@ -0,0 +1,46 @@ + $fileSearchStoreNames Required. The file search store names. + * @param string|null $metadataFilter Optional. A filter for metadata. + */ + public function __construct( + public readonly array $fileSearchStoreNames, + public readonly ?string $metadataFilter = null, + ) {} + + /** + * @param array{ fileSearchStoreNames: array, metadataFilter?: string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + fileSearchStoreNames: $attributes['fileSearchStoreNames'], + metadataFilter: $attributes['metadataFilter'] ?? null, + ); + } + + public function toArray(): array + { + $data = [ + 'fileSearchStoreNames' => $this->fileSearchStoreNames, + ]; + + if ($this->metadataFilter !== null) { + $data['metadataFilter'] = $this->metadataFilter; + } + + return $data; + } +} diff --git a/src/Data/GoogleMaps.php b/src/Data/GoogleMaps.php new file mode 100644 index 0000000..6eff13c --- /dev/null +++ b/src/Data/GoogleMaps.php @@ -0,0 +1,41 @@ +|stdClass + */ + public function toArray(): array|stdClass + { + if ($this->enableWidget === null) { + return new stdClass; + } + + return [ + 'enableWidget' => $this->enableWidget, + ]; + } +} diff --git a/src/Data/RetrievalConfig.php b/src/Data/RetrievalConfig.php new file mode 100644 index 0000000..410dd50 --- /dev/null +++ b/src/Data/RetrievalConfig.php @@ -0,0 +1,43 @@ + [ + 'latitude' => $this->latitude, + 'longitude' => $this->longitude, + ], + ]; + } +} diff --git a/src/Data/Tool.php b/src/Data/Tool.php index 0dec85a..039da6f 100644 --- a/src/Data/Tool.php +++ b/src/Data/Tool.php @@ -27,6 +27,8 @@ public function __construct( public ?CodeExecution $codeExecution = null, public ?GoogleSearch $googleSearch = null, public ?UrlContext $urlContext = null, + public ?GoogleMaps $googleMaps = null, + public ?FileSearch $fileSearch = null, ) {} public function toArray(): array @@ -56,6 +58,14 @@ public function toArray(): array $data['url_context'] = $this->urlContext->toArray(); } + if ($this->googleMaps !== null) { + $data['googleMaps'] = $this->googleMaps->toArray(); + } + + if ($this->fileSearch !== null) { + $data['fileSearch'] = $this->fileSearch->toArray(); + } + return $data; } } diff --git a/src/Data/ToolConfig.php b/src/Data/ToolConfig.php index 3ed580a..d9469d0 100644 --- a/src/Data/ToolConfig.php +++ b/src/Data/ToolConfig.php @@ -15,12 +15,21 @@ final class ToolConfig implements Arrayable { public function __construct( public ?FunctionCallingConfig $functionCallingConfig = null, + public ?RetrievalConfig $retrievalConfig = null, ) {} public function toArray(): array { - return [ - 'functionCallingConfig' => $this->functionCallingConfig?->toArray(), - ]; + $data = []; + + if ($this->functionCallingConfig !== null) { + $data['functionCallingConfig'] = $this->functionCallingConfig->toArray(); + } + + if ($this->retrievalConfig !== null) { + $data['retrievalConfig'] = $this->retrievalConfig->toArray(); + } + + return $data; } } diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index 5a95f12..78c4ded 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -14,6 +14,7 @@ use Gemini\Testing\Resources\CachedContentsTestResource; use Gemini\Testing\Resources\ChatSessionTestResource; use Gemini\Testing\Resources\EmbeddingModelTestResource; +use Gemini\Testing\Resources\FileSearchStoresTestResource; use Gemini\Testing\Resources\FilesTestResource; use Gemini\Testing\Resources\GenerativeModelTestResource; use Gemini\Testing\Resources\ModelTestResource; @@ -251,4 +252,9 @@ public function cachedContents(): CachedContentsTestResource { return new CachedContentsTestResource(fake: $this); } + + public function fileSearchStores(): FileSearchStoresTestResource + { + return new FileSearchStoresTestResource(fake: $this); + } } diff --git a/src/Testing/Resources/FileSearchStoresTestResource.php b/src/Testing/Resources/FileSearchStoresTestResource.php new file mode 100644 index 0000000..0565df4 --- /dev/null +++ b/src/Testing/Resources/FileSearchStoresTestResource.php @@ -0,0 +1,65 @@ +record(method: __FUNCTION__, args: func_get_args()); + } + + public function get(string $name): FileSearchStoreResponse + { + return $this->record(method: __FUNCTION__, args: func_get_args()); + } + + public function list(?int $pageSize = null, ?string $nextPageToken = null): ListResponse + { + return $this->record(method: __FUNCTION__, args: func_get_args()); + } + + public function delete(string $name, bool $force = false): void + { + $this->record(method: __FUNCTION__, args: func_get_args()); + } + + public function upload(string $storeName, string $filename, ?MimeType $mimeType = null, ?string $displayName = null): UploadResponse + { + return $this->record(method: __FUNCTION__, args: func_get_args()); + } + + public function listDocuments(string $storeName, ?int $pageSize = null, ?string $nextPageToken = null): DocumentListResponse + { + return $this->record(method: __FUNCTION__, args: func_get_args()); + } + + public function getDocument(string $name): DocumentResponse + { + return $this->record(method: __FUNCTION__, args: func_get_args()); + } + + public function deleteDocument(string $name, bool $force = false): void + { + $this->record(method: __FUNCTION__, args: func_get_args()); + } +} diff --git a/tests/Data/Tool.php b/tests/Data/Tool.php new file mode 100644 index 0000000..783a5e5 --- /dev/null +++ b/tests/Data/Tool.php @@ -0,0 +1,48 @@ +toArray()) + ->toEqual([ + 'googleMaps' => new stdClass, + 'fileSearch' => [ + 'fileSearchStoreNames' => ['test-store'], + ], + ]); +}); + +test('to array with google maps arguments', function () { + $tool = new Tool( + googleMaps: new GoogleMaps(enableWidget: true), + ); + + expect($tool->toArray()) + ->toEqual([ + 'googleMaps' => ['enableWidget' => true], + ]); +}); + +test('to array with file search arguments', function () { + $tool = new Tool( + fileSearch: new FileSearch( + fileSearchStoreNames: ['test-store-2'], + metadataFilter: 'author = "Robert Graves"' + ), + ); + + expect($tool->toArray()) + ->toEqual([ + 'fileSearch' => [ + 'fileSearchStoreNames' => ['test-store-2'], + 'metadataFilter' => 'author = "Robert Graves"', + ], + ]); +}); diff --git a/tests/Data/ToolConfig.php b/tests/Data/ToolConfig.php new file mode 100644 index 0000000..44d71cd --- /dev/null +++ b/tests/Data/ToolConfig.php @@ -0,0 +1,75 @@ +toArray()) + ->toBe([ + 'functionCallingConfig' => [ + 'mode' => 'AUTO', + 'allowedFunctionNames' => null, + ], + 'retrievalConfig' => [ + 'latLng' => [ + 'latitude' => 40.758896, + 'longitude' => -73.985130, + ], + ], + ]); +}); + +test('to array with only function calling config', function () { + $toolConfig = new ToolConfig( + functionCallingConfig: new FunctionCallingConfig(mode: Mode::AUTO), + retrievalConfig: null, + ); + + expect($toolConfig->toArray()) + ->toBe([ + 'functionCallingConfig' => [ + 'mode' => 'AUTO', + 'allowedFunctionNames' => null, + ], + ]); +}); + +test('to array with only retrieval config', function () { + $toolConfig = new ToolConfig( + functionCallingConfig: null, + retrievalConfig: new RetrievalConfig( + latitude: 40.758896, + longitude: -73.985130 + ) + ); + + expect($toolConfig->toArray()) + ->toBe([ + 'retrievalConfig' => [ + 'latLng' => [ + 'latitude' => 40.758896, + 'longitude' => -73.985130, + ], + ], + ]); +}); + +test('to array with no config', function () { + $toolConfig = new ToolConfig( + functionCallingConfig: null, + retrievalConfig: null, + ); + + expect($toolConfig->toArray()) + ->toBe([]); +}); From 9c8a1fa416c960e9ebbfdc4a57c51d773f19700c Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 1 Dec 2025 14:45:38 +0100 Subject: [PATCH 03/17] docs: Update README.md with File Search APIs documentation --- README.md | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/README.md b/README.md index 2601a22..38f9662 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,17 @@ - [Update Cached Content](#update-cached-content) - [Delete Cached Content](#delete-cached-content) - [Use Cached Content](#use-cached-content) + - [File Search Stores](#file-search-stores) + - [Create File Search Store](#create-file-search-store) + - [Get File Search Store](#get-file-search-store) + - [List File Search Stores](#list-file-search-stores) + - [Delete File Search Store](#delete-file-search-store) + - [Update File Search Store](#update-file-search-store) + - [File Search Documents](#file-search-documents) + - [Create File Search Document](#create-file-search-document) + - [Get File Search Document](#get-file-search-document) + - [List File Search Documents](#list-file-search-documents) + - [Delete File Search Document](#delete-file-search-document) - [Embedding Resource](#embedding-resource) - [Models](#models) - [List Models](#list-models) @@ -834,6 +845,167 @@ echo "Cached tokens used: {$response->usageMetadata->cachedContentTokenCount}\n" echo "New tokens used: {$response->usageMetadata->promptTokenCount}\n"; ``` +### File Search Stores + +File search allows you to search files that were uploaded through the File API. + +#### Create File Search Store +Create a file search store. + +```php +use Gemini\Enums\FileState; +use Gemini\Enums\MimeType; +use Gemini\Enums\Schema; +use Gemini\Enums\DataType; + +$files = $client->files(); +echo "Uploading\n"; +$meta = $files->upload( + filename: 'document.pdf', + mimeType: MimeType::APPLICATION_PDF, + displayName: 'Document for search' +); +echo "Processing"; +do { + echo "."; + sleep(2); + $meta = $files->metadataGet($meta->uri); +} while (! $meta->state->complete()); +echo "\n"; + +if ($meta->state == FileState::Failed) { + die("Upload failed:\n".json_encode($meta->toArray(), JSON_PRETTY_PRINT)); +} + +$fileSearchStore = $client->fileSearchStores()->create( + displayName: 'My Search Store', + defaultSchema: new Schema( + declarations: [ + 'name' => new Schema(type: DataType::STRING), + 'size' => new Schema(type: DataType::INTEGER), + ], + ), + defaultDocumentConfig: [ + 'files/'.basename($meta->uri), + ], +); + +echo "File search store created: {$fileSearchStore->name}\n"; +``` + +#### Get File Search Store +Get a specific file search store by name. + +```php +$fileSearchStore = $client->fileSearchStores()->retrieve('fileSearchStores/my-search-store'); + +echo "Name: {$fileSearchStore->name}\n"; +echo "Display Name: {$fileSearchStore->displayName}\n"; +``` + +#### List File Search Stores +List all file search stores. + +```php +$response = $client->fileSearchStores()->list(pageSize: 10); + +foreach ($response->fileSearchStores as $fileSearchStore) { + echo "Name: {$fileSearchStore->name}\n"; + echo "Display Name: {$fileSearchStore->displayName}\n"; + echo "Create Time: {$fileSearchStore->createTime}\n"; + echo "Update Time: {$fileSearchStore->updateTime}\n"; + echo "--- \n"; +} +``` + +#### Delete File Search Store +Delete a file search store by name. + +```php +$client->fileSearchStores()->delete('fileSearchStores/my-search-store'); +``` + +#### Update File Search Store +Update a file search store. + +```php +$fileSearchStore = $client->fileSearchStores()->update( + name: 'fileSearchStores/my-search-store', + displayName: 'My Updated Search Store', +); + +echo "File search store updated: {$fileSearchStore->name}\n"; +``` + +### File Search Documents + +#### Create File Search Document +Create a file search document within a store. + +```php +use Gemini\Enums\FileState; +use Gemini\Enums\MimeType; + +$files = $client->files(); +echo "Uploading\n"; +$meta = $files->upload( + filename: 'document2.pdf', + mimeType: MimeType::APPLICATION_PDF, + displayName: 'Another document for search' +); +echo "Processing"; +do { + echo "."; + sleep(2); + $meta = $files->metadataGet($meta->uri); +} while (! $meta->state->complete()); +echo "\n"; + +if ($meta->state == FileState::Failed) { + die("Upload failed:\n".json_encode($meta->toArray(), JSON_PRETTY_PRINT)); +} + +$fileSearchDocument = $client->fileSearchDocuments()->create( + parent: 'fileSearchStores/my-search-store', + file: 'files/'.basename($meta->uri), + displayName: 'Another Search Document', +); + +echo "File search document created: {$fileSearchDocument->name}\n"; +``` + +#### Get File Search Document +Get a specific file search document by name. + +```php +$fileSearchDocument = $client->fileSearchDocuments()->retrieve('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); + +echo "Name: {$fileSearchDocument->name}\n"; +echo "Display Name: {$fileSearchDocument->displayName}\n"; +``` + +#### List File Search Documents +List all file search documents within a store. + +```php +$response = $client->fileSearchDocuments()->list(parent: 'fileSearchStores/my-search-store', pageSize: 10); + +foreach ($response->fileSearchDocuments as $fileSearchDocument) { + echo "Name: {$fileSearchDocument->name}\n"; + echo "Display Name: {$fileSearchDocument->displayName}\n"; + echo "Create Time: {$fileSearchDocument->createTime}\n"; + echo "Update Time: {$fileSearchDocument->updateTime}\n"; + echo "--- \n"; +} +``` + +#### Delete File Search Document +Delete a file search document by name. + +```php +$client->fileSearchDocuments()->delete('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); +``` + ### Embedding Resource Embedding is a technique used to represent information as a list of floating point numbers in an array. With Gemini, you can represent text (words, sentences, and blocks of text) in a vectorized form, making it easier to compare and contrast embeddings. For example, two texts that share a similar subject matter or sentiment should have similar embeddings, which can be identified through mathematical comparison techniques such as cosine similarity. From 24e6f947bada3d72f447c2383129379f5a04dcf8 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:22:01 +0100 Subject: [PATCH 04/17] Update src/Resources/FileSearchStores.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Resources/FileSearchStores.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resources/FileSearchStores.php b/src/Resources/FileSearchStores.php index 372bc4b..f0c416e 100644 --- a/src/Resources/FileSearchStores.php +++ b/src/Resources/FileSearchStores.php @@ -59,7 +59,7 @@ public function delete(string $name, bool $force = false): void public function upload(string $storeName, string $filename, ?MimeType $mimeType = null, ?string $displayName = null): UploadResponse { - $mimeType ??= MimeType::from((string) mime_content_type($filename)); + $mimeType ??= MimeType::from(mime_content_type($filename) ?: throw new \RuntimeException("Failed to determine MIME type for: {$filename}")); $displayName ??= $filename; /** @var ResponseDTO, done: bool, response?: array, error?: array }> $response */ From f44877c1c282deb0d2c1e885f35c9c69c1beaade Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:23:56 +0100 Subject: [PATCH 05/17] Update src/Responses/FileSearchStores/UploadResponse.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Responses/FileSearchStores/UploadResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Responses/FileSearchStores/UploadResponse.php b/src/Responses/FileSearchStores/UploadResponse.php index c94d34f..6307e61 100644 --- a/src/Responses/FileSearchStores/UploadResponse.php +++ b/src/Responses/FileSearchStores/UploadResponse.php @@ -15,7 +15,7 @@ class UploadResponse implements ResponseContract use Fakeable; /** - * @param array $metadata + * @param array|null $metadata * @param array|null $response * @param array|null $error */ From eb0fb541836c535e915b17310640fb14a921da61 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:24:08 +0100 Subject: [PATCH 06/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 38f9662..8270654 100644 --- a/README.md +++ b/README.md @@ -912,8 +912,6 @@ $response = $client->fileSearchStores()->list(pageSize: 10); foreach ($response->fileSearchStores as $fileSearchStore) { echo "Name: {$fileSearchStore->name}\n"; echo "Display Name: {$fileSearchStore->displayName}\n"; - echo "Create Time: {$fileSearchStore->createTime}\n"; - echo "Update Time: {$fileSearchStore->updateTime}\n"; echo "--- \n"; } ``` From 6429503a63210ebb63bebed050927c0b4ac46b0e Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:24:33 +0100 Subject: [PATCH 07/17] Update src/Requests/FileSearchStores/UploadRequest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Requests/FileSearchStores/UploadRequest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Requests/FileSearchStores/UploadRequest.php b/src/Requests/FileSearchStores/UploadRequest.php index 21c21bf..ad66af7 100644 --- a/src/Requests/FileSearchStores/UploadRequest.php +++ b/src/Requests/FileSearchStores/UploadRequest.php @@ -44,6 +44,9 @@ public function toRequest(string $baseUrl, array $headers = [], array $queryPara $requestJson = json_encode($metadata); $contents = file_get_contents($this->filename); + if ($contents === false) { + throw new \RuntimeException("Failed to read file: {$this->filename}"); + } $request = $factory ->createRequest($this->method->value, str_replace('/v1', '/upload/v1', $baseUrl).$this->resolveEndpoint()) From 4645154883890ddfeaffa154a7a9d3b022b2c1e1 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:25:08 +0100 Subject: [PATCH 08/17] Update src/Requests/FileSearchStores/UploadRequest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Requests/FileSearchStores/UploadRequest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Requests/FileSearchStores/UploadRequest.php b/src/Requests/FileSearchStores/UploadRequest.php index ad66af7..961c907 100644 --- a/src/Requests/FileSearchStores/UploadRequest.php +++ b/src/Requests/FileSearchStores/UploadRequest.php @@ -49,7 +49,10 @@ public function toRequest(string $baseUrl, array $headers = [], array $queryPara } $request = $factory - ->createRequest($this->method->value, str_replace('/v1', '/upload/v1', $baseUrl).$this->resolveEndpoint()) + ->createRequest( + $this->method->value, + preg_replace('#/v1(beta)?#', '/upload/v1$1', $baseUrl) . $this->resolveEndpoint() + ) ->withHeader('X-Goog-Upload-Protocol', 'multipart'); foreach ($headers as $name => $value) { $request = $request->withHeader($name, $value); From da3d1ea68b2d32daebe6c24bf71ec8e75a378526 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:25:27 +0100 Subject: [PATCH 09/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8270654..ebd1246 100644 --- a/README.md +++ b/README.md @@ -897,7 +897,7 @@ echo "File search store created: {$fileSearchStore->name}\n"; Get a specific file search store by name. ```php -$fileSearchStore = $client->fileSearchStores()->retrieve('fileSearchStores/my-search-store'); +$fileSearchStore = $client->fileSearchStores()->get('fileSearchStores/my-search-store'); echo "Name: {$fileSearchStore->name}\n"; echo "Display Name: {$fileSearchStore->displayName}\n"; From ce5456ee03269b36ccf019ad4e0e3be82da7d760 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:25:42 +0100 Subject: [PATCH 10/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebd1246..fb7d116 100644 --- a/README.md +++ b/README.md @@ -976,7 +976,7 @@ echo "File search document created: {$fileSearchDocument->name}\n"; Get a specific file search document by name. ```php -$fileSearchDocument = $client->fileSearchDocuments()->retrieve('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); +$fileSearchDocument = $client->fileSearchStores()->getDocument('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); echo "Name: {$fileSearchDocument->name}\n"; echo "Display Name: {$fileSearchDocument->displayName}\n"; From a4e837cceafed36f39009b9610f547d01744a253 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:26:46 +0100 Subject: [PATCH 11/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index fb7d116..b3e1f0a 100644 --- a/README.md +++ b/README.md @@ -923,18 +923,6 @@ Delete a file search store by name. $client->fileSearchStores()->delete('fileSearchStores/my-search-store'); ``` -#### Update File Search Store -Update a file search store. - -```php -$fileSearchStore = $client->fileSearchStores()->update( - name: 'fileSearchStores/my-search-store', - displayName: 'My Updated Search Store', -); - -echo "File search store updated: {$fileSearchStore->name}\n"; -``` - ### File Search Documents #### Create File Search Document From b049f0b94f4312b258fd0426b994449163d3d82f Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:27:09 +0100 Subject: [PATCH 12/17] Update src/Contracts/Resources/FileSearchStoresContract.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Contracts/Resources/FileSearchStoresContract.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Contracts/Resources/FileSearchStoresContract.php b/src/Contracts/Resources/FileSearchStoresContract.php index c75a878..dc9ba8f 100644 --- a/src/Contracts/Resources/FileSearchStoresContract.php +++ b/src/Contracts/Resources/FileSearchStoresContract.php @@ -1,4 +1,5 @@ Date: Mon, 15 Dec 2025 11:27:48 +0100 Subject: [PATCH 13/17] Update src/Requests/FileSearchStores/DeleteRequest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Requests/FileSearchStores/DeleteRequest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Requests/FileSearchStores/DeleteRequest.php b/src/Requests/FileSearchStores/DeleteRequest.php index 45a696c..01eab4c 100644 --- a/src/Requests/FileSearchStores/DeleteRequest.php +++ b/src/Requests/FileSearchStores/DeleteRequest.php @@ -24,6 +24,9 @@ public function resolveEndpoint(): string return $this->name; } + /** + * @return array + */ public function defaultQuery(): array { return $this->force ? ['force' => 'true'] : []; From f8d2ae98545f3346ad1c51138b6720ba9fd6faa8 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:28:01 +0100 Subject: [PATCH 14/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index b3e1f0a..8e4bb0c 100644 --- a/README.md +++ b/README.md @@ -879,15 +879,6 @@ if ($meta->state == FileState::Failed) { $fileSearchStore = $client->fileSearchStores()->create( displayName: 'My Search Store', - defaultSchema: new Schema( - declarations: [ - 'name' => new Schema(type: DataType::STRING), - 'size' => new Schema(type: DataType::INTEGER), - ], - ), - defaultDocumentConfig: [ - 'files/'.basename($meta->uri), - ], ); echo "File search store created: {$fileSearchStore->name}\n"; From bdb09516880a4c4c94d2b5cf22a4e55cc6f34d20 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:28:14 +0100 Subject: [PATCH 15/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e4bb0c..527f7bd 100644 --- a/README.md +++ b/README.md @@ -980,7 +980,7 @@ foreach ($response->fileSearchDocuments as $fileSearchDocument) { Delete a file search document by name. ```php -$client->fileSearchDocuments()->delete('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); +$client->fileSearchStores()->deleteDocument('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); ``` ### Embedding Resource From 195da56c8f1c02ceb843b74a6a410bb4bd13ddab Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 12:05:30 +0100 Subject: [PATCH 16/17] fix: README --- README.md | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 527f7bd..25b6209 100644 --- a/README.md +++ b/README.md @@ -916,39 +916,20 @@ $client->fileSearchStores()->delete('fileSearchStores/my-search-store'); ### File Search Documents -#### Create File Search Document -Create a file search document within a store. +#### Upload File Search Document +Upload a local file directly to a file search store. ```php -use Gemini\Enums\FileState; use Gemini\Enums\MimeType; -$files = $client->files(); -echo "Uploading\n"; -$meta = $files->upload( +$response = $client->fileSearchStores()->upload( + storeName: 'fileSearchStores/my-search-store', filename: 'document2.pdf', mimeType: MimeType::APPLICATION_PDF, - displayName: 'Another document for search' -); -echo "Processing"; -do { - echo "."; - sleep(2); - $meta = $files->metadataGet($meta->uri); -} while (! $meta->state->complete()); -echo "\n"; - -if ($meta->state == FileState::Failed) { - die("Upload failed:\n".json_encode($meta->toArray(), JSON_PRETTY_PRINT)); -} - -$fileSearchDocument = $client->fileSearchDocuments()->create( - parent: 'fileSearchStores/my-search-store', - file: 'files/'.basename($meta->uri), - displayName: 'Another Search Document', + displayName: 'Another Search Document' ); -echo "File search document created: {$fileSearchDocument->name}\n"; +echo "File search document upload operation: {$response->name}\n"; ``` #### Get File Search Document @@ -965,9 +946,9 @@ echo "Display Name: {$fileSearchDocument->displayName}\n"; List all file search documents within a store. ```php -$response = $client->fileSearchDocuments()->list(parent: 'fileSearchStores/my-search-store', pageSize: 10); +$response = $client->fileSearchStores()->listDocuments(storeName: 'fileSearchStores/my-search-store', pageSize: 10); -foreach ($response->fileSearchDocuments as $fileSearchDocument) { +foreach ($response->documents as $fileSearchDocument) { echo "Name: {$fileSearchDocument->name}\n"; echo "Display Name: {$fileSearchDocument->displayName}\n"; echo "Create Time: {$fileSearchDocument->createTime}\n"; From 19aecf7cc95c31e601e4a49d512e8d43a7b0c5bb Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 12:08:45 +0100 Subject: [PATCH 17/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3effb8e..a455471 100644 --- a/README.md +++ b/README.md @@ -697,7 +697,7 @@ Every prompt you send to the model includes parameter values that control how th Also, you can use safety settings to adjust the likelihood of getting responses that may be considered harmful. By default, safety settings block content with medium and/or high probability of being unsafe content across all dimensions. Learn more about [safety settings](https://ai.google.dev/docs/concepts#safety_setting). -When using tools like `FileSearch`, you may also provide additional configuration via `ToolConfig`, such as `RetrievalConfig` for geographical context. +When using tools like `GoogleMaps`, you may also provide additional configuration via `ToolConfig`, such as `RetrievalConfig` for geographical context. ```php use Gemini\Data\GenerationConfig;