Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ai Vertex Ai Platform Laravel Package

symfony/ai-vertex-ai-platform

Google Vertex AI platform bridge for Symfony AI, enabling Gemini inference, text embeddings, and ADC authentication for Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Package

    composer require symfony/ai-vertex-ai-platform:^0.15
    

    The bridge now requires symfony/ai v0.15+. Release 0.15.0 bumps every constraint in the monorepo to 0.15, so pin the bridge and the core package to the same minor — a mixed lock file (ai:0.14 + ai-vertex-ai-platform:0.15) will resolve but the wire-field mapping described below will not match the core's option normalizer.

  2. Configure Authentication Add Vertex AI credentials to config/packages/ai.yaml:

    ai:
        providers:
            vertex_ai:
                client: 'vertex_ai.client'
                project_id: '%env(VERTEX_AI_PROJECT_ID)%'
                location: 'us-central1'
                auth:
                    # ADC (recommended for GCP environments)
                    adc: true
                    # OR API key
                    # api_key: '%env(VERTEX_AI_API_KEY)%'
    
  3. First Use Case: Text Generation Inject the GeminiClient and call a model. Note the generationConfig block — as of 0.15 the bridge expects its native wire field name (maxOutputTokens), not the canonical max_output_tokens:

    use Symfony\AI\Gemini\GeminiClient;
    use Symfony\AI\Gemini\Model\ChatCompletion;
    
    class MyService {
        public function __construct(private GeminiClient $gemini) {}
    
        public function generateText(): string {
            $response = $this->gemini->chatCompletion(
                new ChatCompletion('gemini-1.5-flash-latest'),
                [
                    'messages' => [['role' => 'user', 'content' => 'Hello!']],
                    'generationConfig' => ['maxOutputTokens' => 512],
                ]
            );
            return $response->getChoices()[0]->getMessage()->getContent();
        }
    }
    
  4. Verify with a Test

    use Symfony\AI\Test\MockGeminiClient;
    
    $mockClient = new MockGeminiClient();
    $mockClient->expects('chatCompletion')
        ->andReturn(new ChatCompletionResponse([new ChatChoice([
            new ChatMessage('assistant', 'Hi there!')
        ])]));
    

Laravel note: the bridge is a Symfony bundle, so there is no service provider auto-discovery. Register the client yourself in AppServiceProvider (or a dedicated AiServiceProvider) and pull credentials from config/services.php; everything below works unchanged once the GeminiClient is in Laravel's container.


Implementation Patterns

Core Workflows

1. Model Routing (Provider Abstraction)

use Symfony\AI\Provider\ProviderInterface;

class HybridAIService {
    public function __construct(
        private ProviderInterface $vertexProvider,
        private ProviderInterface $openAIProvider
    ) {}

    public function generate(string $prompt, bool $useFlashModel = false): string {
        $provider = $useFlashModel ? $this->vertexProvider : $this->openAIProvider;
        $response = $provider->chatCompletion(
            new ChatCompletion($useFlashModel ? 'gemini-1.5-flash-latest' : 'gpt-4'),
            [
                'messages' => [['role' => 'user', 'content' => $prompt]],
                'generationConfig' => ['maxOutputTokens' => 1024],
            ]
        );
        return $response->getChoices()[0]->getMessage()->getContent();
    }
}

Because each bridge now translates its own wire field, keep option arrays bridge-specific. A shared "generic options" array passed to both providers is the fastest way to hit the rename gotcha below — build the option array after the provider is chosen.

2. Streaming Responses

use Symfony\AI\Gemini\Model\ChatCompletion;
use Symfony\AI\Streaming\StreamingResponse;

public function streamChatResponse(string $prompt): StreamingResponse {
    $response = $this->gemini->chatCompletion(
        new ChatCompletion('gemini-1.5-flash-latest'),
        [
            'messages' => [['role' => 'user', 'content' => $prompt]],
            'generationConfig' => ['maxOutputTokens' => 2048],
        ],
        ['stream' => true]
    );

    return new StreamingResponse(function () use ($response) {
        foreach ($response->getChoices()[0]->getMessage()->getContentDeltas() as $delta) {
            yield $delta->getContent();
        }
    });
}

3. Embeddings for Semantic Search

use Symfony\AI\Gemini\Model\Embedding;

public function generateEmbedding(string $text): array {
    $response = $this->gemini->embedding(
        new Embedding('text-embedding-004'),
        ['input' => [$text]]
    );
    return $response->getEmbeddings()[0]->getValues();
}

4. Multimodal Processing

use Symfony\AI\Gemini\Model\MultipartChatCompletion;

public function analyzeDocument(string $filePath, string $prompt): string {
    $response = $this->gemini->multipartChatCompletion(
        new MultipartChatCompletion('gemini-1.5-flash-latest'),
        [
            'contents' => [
                ['mime_type' => 'application/pdf', 'data' => file_get_contents($filePath)],
                ['role' => 'user', 'parts' => [['text' => $prompt]]],
            ],
            'generationConfig' => ['maxOutputTokens' => 4096],
        ]
    );
    return $response->getChoices()[0]->getMessage()->getContent();
}

5. Centralizing Generation Options

With per-bridge wire fields, a single factory that returns the correct option shape is worth the 20 lines:

final class VertexGenerationOptions
{
    public static function make(int $maxOutputTokens, float $temperature = 0.7): array
    {
        return [
            'generationConfig' => [
                'maxOutputTokens' => $maxOutputTokens,
                'temperature' => $temperature,
            ],
        ];
    }
}

Swap the factory (not every call site) when a future release renames a field again.


Integration Tips

Authentication

  • ADC: preferred for GCP. Ensure the runtime service account has the cloud-platform scope.
  • API Keys: for non-GCP environments or CI:
    ai:
        providers:
            vertex_ai:
                auth:
                    api_key: '%env(VERTEX_AI_API_KEY)%'
    

Error Handling

try {
    $response = $this->gemini->chatCompletion($model, $options);
} catch (\Symfony\AI\Exception\VertexAIException $e) {
    $this->logger->error('Vertex AI error: ' . $e->getMessage(), ['model' => $model->getModelName()]);
    throw new \RuntimeException('AI service unavailable', 0, $e);
}

Configuration

# .env
VERTEX_AI_PROJECT_ID=your-project-id
VERTEX_AI_LOCATION=us-central1
VERTEX_AI_API_KEY=${VERTEX_AI_API_KEY:-}

Dependency Injection

# config/services.yaml
services:
    Symfony\AI\Gemini\GeminiClient:
        arguments:
            $model: 'gemini-1.5-flash-latest'
            $provider: '@ai.provider.vertex_ai'

Testing

$mockClient = new MockGeminiClient();
$mockClient->expects('chatCompletion')
    ->withArgs(function ($model, $args) {
        return $model->getModelName() === 'gemini-1.5-flash-latest'
            && ($args['generationConfig']['maxOutputTokens'] ?? null) === 512;
    })
    ->andReturn(new ChatCompletionResponse([new ChatChoice([
        new ChatMessage('assistant', 'Mocked response!')
    ])]));

Asserting on the wire field is now the cheapest regression test you can add for the 0.15 rename.


Gotchas and Tips

Pitfalls

1. max_output_tokens was renamed in 0.15 (breaking)

Feature #2622 removes the canonical max_output_tokens option and makes each bridge emit its own wire field. For the Vertex/Gemini bridge that means maxOutputTokens inside generationConfig.

Before (≤ 0.14):

['max_output_tokens' => 1024]

After (0.15+):

['generationConfig' => ['maxOutputTokens' => 1024]]

The failure mode is quiet: an unrecognized key is generally not rejected by the Vertex API, so output just runs to the model default. Fix: grep your codebase for max_output_tokens, update every call site, and add an assertion on the returned usage/token count (or a mock withArgs check) so a silent fallback can't slip back in.

2. Mixed 0.14/0.15 installs

The constraint bump means the bridge and symfony/ai core must move together (composer update "symfony/ai*" -W). A partial update leaves the option normalizer and the wire-field mapper out of sync — the symptom is the same silent-default behaviour as above plus confusing Undefined array key notices.

3. Authentication Issues

  • ADC not working: check GOOGLE_APPLICATION_CREDENTIALS or the Compute Engine metadata server.
  • API key permissions: the principal needs Vertex AI User (or Vertex AI Service Agent). Regenerate in GCP IAM.

4. Model Availability

Vertex AI deprecates models (e.g. gemini-1.0). Use the catalog instead of hardcoding:

$models = $this->gemini->getModelCatalog()->getModels();

5. Binary Data Handling

Multipart payloads cap out around 10MB. Compress or chunk:

$chunks = array_chunk(file_get_contents($filePath), 5_000_000);

6. Token Limits

text-embedding-004 accepts ~32K input tokens; Gemini output caps differ per model and are now expressed via maxOutputTokens. Truncate defensively before sending:

$text = mb_substr($longText, 0, 8000);

7. Streaming Quirks

Deltas can end abruptly on connection drops — wrap with a timeout and retry:

$response = $this->gemini->chatCompletion(
    new ChatCompletion('gemini-1.5-flash-latest'),
    ['messages' => [['role' => 'user', 'content' => $prompt]]],
    ['stream' => true, 'timeout' => 30]
);

Don't persist partial output until the stream closes cleanly.

8. Extension Points

  • Decorate ProviderInterface rather than subclassing GeminiClient — decorators survive bridge version bumps better.
  • Wrap the client in a small VertexGateway class so the option shape (generationConfig, wire field names) lives in one place.
  • Log the outgoing option array at debug level when tuning token limits; it's the only reliable way to confirm the bridge actually emitted maxOutputTokens.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums