Skip to content

Comments

Support batch embeddings for Gemini provider#915

Open
larskoole wants to merge 1 commit intoprism-php:mainfrom
larskoole:feat/gemini-batch-embeddings
Open

Support batch embeddings for Gemini provider#915
larskoole wants to merge 1 commit intoprism-php:mainfrom
larskoole:feat/gemini-batch-embeddings

Conversation

@larskoole
Copy link
Contributor

Summary

The Gemini embeddings handler currently throws a PrismException when more than one input is provided via fromArray() or multiple fromInput() calls.

This adds support for multiple inputs by using the Gemini batchEmbedContents endpoint, which returns separate embedding vectors per input.

  • Single input: continues using the existing embedContent endpoint (no behavior change)
  • Multiple inputs: uses batchEmbedContents, mapping each embeddings[].values to an Embedding object

Changes

  • src/Providers/Gemini/Handlers/Embeddings.php: Replaced the multi-input exception with routing logic. Added handleSingleRequest(), handleBatchRequest(), and sendBatchRequest() 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

  • Existing single-input embedding tests still pass
  • New batch embeddings test verifies multiple inputs return separate embeddings
  • All 7 Gemini embedding tests pass

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>
Copilot AI review requested due to automatic review settings February 20, 2026 12:37
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) vs batchEmbedContents (multiple inputs).
  • Parse embeddings[].values from batch responses into Embedding objects.
  • 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 shared ValidatesResponse logic used by other Gemini handlers. Consider applying Prism\Prism\Providers\Gemini\Concerns\ValidatesResponse and calling validateResponse($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.

Comment on lines +59 to +69
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'],
);

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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']);
}

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +116
$requests = array_map(
fn (string $text): array => Arr::whereNotNull([
'model' => "models/{$model}",
'content' => [
'parts' => [
['text' => $text],
],
],
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to 55
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();
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant