Support batch embeddings for Gemini provider#915
Support batch embeddings for Gemini provider#915larskoole wants to merge 1 commit intoprism-php:mainfrom
Conversation
The Gemini handler previously threw an exception when more than one input was provided. This adds support for multiple inputs by using the batchEmbedContents endpoint, which returns separate embedding vectors per input. Single-input requests continue to use the embedContent endpoint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds multi-input (batch) embedding support for the Gemini provider by routing requests with multiple inputs to Gemini’s batchEmbedContents endpoint, while keeping single-input behavior on embedContent.
Changes:
- Route Gemini embedding requests to
embedContent(single input) vsbatchEmbedContents(multiple inputs). - Parse
embeddings[].valuesfrom batch responses intoEmbeddingobjects. - Update Gemini embeddings tests and add a batch response fixture.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/Providers/Gemini/Handlers/Embeddings.php | Adds single vs batch routing, batch request builder, and batch response mapping. |
| tests/Providers/Gemini/EmbeddingsTest.php | Replaces the “throws on multiple inputs” test with a batch embeddings test. |
| tests/Fixtures/gemini/embeddings-batch-1.json | Adds a recorded fixture representing a batchEmbedContents response. |
Comments suppressed due to low confidence (1)
src/Providers/Gemini/Handlers/Embeddings.php:40
- Gemini error responses (e.g.
{error: ...}) will currently be reported as a generic “Invalid response format...” because the handler never calls the sharedValidatesResponselogic used by other Gemini handlers. Consider applyingPrism\Prism\Providers\Gemini\Concerns\ValidatesResponseand callingvalidateResponse($response)in both single and batch paths so provider error codes/messages aren’t lost.
$response = $this->sendRequest($request);
$data = $response->json();
if (! isset($data['embedding'])) {
throw PrismException::providerResponseError(
'Gemini Error: Invalid response format or missing embedding data'
);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (! isset($data['embeddings'])) { | ||
| throw PrismException::providerResponseError( | ||
| 'Gemini Error: Invalid batch response format or missing embeddings data' | ||
| ); | ||
| } | ||
|
|
||
| $embeddings = array_map( | ||
| fn (array $embedding): Embedding => Embedding::fromArray($embedding['values'] ?? []), | ||
| $data['embeddings'], | ||
| ); | ||
|
|
There was a problem hiding this comment.
handleBatchRequest() only checks that embeddings exists, but will silently create empty Embedding objects when an item is missing values. This can hide partial/malformed provider responses; consider validating each item has a non-empty values array (and throwing a providerResponseError if not).
| if (! isset($data['embeddings'])) { | |
| throw PrismException::providerResponseError( | |
| 'Gemini Error: Invalid batch response format or missing embeddings data' | |
| ); | |
| } | |
| $embeddings = array_map( | |
| fn (array $embedding): Embedding => Embedding::fromArray($embedding['values'] ?? []), | |
| $data['embeddings'], | |
| ); | |
| if (! isset($data['embeddings']) || ! is_array($data['embeddings'])) { | |
| throw PrismException::providerResponseError( | |
| 'Gemini Error: Invalid batch response format or missing embeddings data' | |
| ); | |
| } | |
| $embeddings = []; | |
| foreach ($data['embeddings'] as $index => $embedding) { | |
| if (! is_array($embedding) | |
| || ! array_key_exists('values', $embedding) | |
| || ! is_array($embedding['values']) | |
| || $embedding['values'] === [] | |
| ) { | |
| throw PrismException::providerResponseError( | |
| sprintf( | |
| 'Gemini Error: Invalid embedding item at index %d: missing or empty values', | |
| $index | |
| ) | |
| ); | |
| } | |
| $embeddings[] = Embedding::fromArray($embedding['values']); | |
| } |
| $requests = array_map( | ||
| fn (string $text): array => Arr::whereNotNull([ | ||
| 'model' => "models/{$model}", | ||
| 'content' => [ | ||
| 'parts' => [ | ||
| ['text' => $text], | ||
| ], | ||
| ], |
There was a problem hiding this comment.
In sendBatchRequest(), each inner request sets 'model' => "models/{$model}" while the single-request path uses $request->model() (no models/ prefix) and the endpoint URL already includes the model. This mismatch can cause the batch call to fail if the API expects the inner request model (when present) to exactly match the URL model; consider omitting the inner model field or using the same format as the single-request path.
| it('returns batch embeddings from multiple inputs', function (): void { | ||
| FixtureResponse::fakeResponseSequence('models/text-embedding-004:batchEmbedContents', 'gemini/embeddings-batch'); | ||
|
|
||
| $response = Prism::embeddings() | ||
| ->using(Provider::Gemini, 'text-embedding-004') | ||
| ->fromInput('1') | ||
| ->fromInput('2') | ||
| ->fromInput('First sentence.') | ||
| ->fromInput('Second sentence.') | ||
| ->asEmbeddings(); |
There was a problem hiding this comment.
The new batch path isn’t covered beyond the happy-path mapping. Consider adding a test that exercises batch embeddings with provider options (e.g. title / taskType / outputDimensionality) to ensure those options are still sent/handled correctly when routing to batchEmbedContents.
Summary
The Gemini embeddings handler currently throws a
PrismExceptionwhen more than one input is provided viafromArray()or multiplefromInput()calls.This adds support for multiple inputs by using the Gemini
batchEmbedContentsendpoint, which returns separate embedding vectors per input.embedContentendpoint (no behavior change)batchEmbedContents, mapping eachembeddings[].valuesto anEmbeddingobjectChanges
src/Providers/Gemini/Handlers/Embeddings.php: Replaced the multi-input exception with routing logic. AddedhandleSingleRequest(),handleBatchRequest(), andsendBatchRequest()methods.tests/Providers/Gemini/EmbeddingsTest.php: Replaced the "throws exception" test with a batch embeddings test.tests/Fixtures/gemini/embeddings-batch-1.json: Fixture for the batch response.Test plan