Skip to main content

gitlab ai gateway drupal

GitLab AI Gateway Integration with Drupal

Status: Production Ready Drupal Version: 11.x+ Required Modules: drupal/ai (1.2.5+) GitLab Version: 18.0+

Overview

This guide integrates GitLab AI Gateway with Drupal's AI module framework, enabling Drupal to use GitLab's centralized AI infrastructure with 48+ AI providers (Claude, OpenAI, Anthropic, Azure OpenAI, AWS Bedrock, etc.) through a unified API.

Architecture


 Drupal 11 (blueflyio/llm-platform)                         
                                                             
    
   drupal/ai Module (Provider Plugin System)              
                                                           
     OpenAI Provider (built-in)                         
     Anthropic Provider (built-in)                      
     Azure OpenAI Provider (built-in)                   
     GitLab AI Gateway Provider (custom)  THIS       
    
                                                            

                            
                             HTTPS + OIDC Token
                            

 GitLab AI Gateway (gitlab.com/ai_gateway)                   
                                                             
    
   AI Model Router                                         
     Token Usage Tracking                                 
     Cost Attribution                                     
     Rate Limiting                                        
     Model Selection                                      
    
                                                            

                            
        
                                              
                                              
    
 Anthropic       OpenAI          Azure OpenAI 
 Claude API      GPT API         API          
    

Why Use GitLab AI Gateway?

Benefits:

  1. Centralized Token Management - One place to manage all AI provider tokens
  2. Cost Tracking - Track token usage and costs per project/team
  3. Rate Limiting - Prevent runaway API costs
  4. Model Abstraction - Switch models without changing Drupal code
  5. Security - Tokens never exposed to Drupal, OIDC authentication
  6. Observability - GitLab's built-in monitoring and tracing

Prerequisites

1. GitLab AI Gateway Setup

Option A: GitLab.com Hosted (Recommended)

# Already available at https://gitlab.com/api/v4/ai_gateway # No setup required for GitLab.com SaaS

Option B: Self-Hosted GitLab

# Enable AI Gateway in gitlab.rb ai_gateway['enable'] = true ai_gateway['env'] = { 'AI_GATEWAY_ANTHROPIC_API_KEY' => 'YOUR_ANTHROPIC_KEY', 'AI_GATEWAY_OPENAI_API_KEY' => 'YOUR_OPENAI_KEY' } gitlab-ctl reconfigure gitlab-ctl restart

2. GitLab Project Setup

# Create GitLab project for Drupal AI integration glab project create blueflyio/drupal-ai-gateway \ --description "Drupal + GitLab AI Gateway Integration" # Create Personal Access Token with ai_gateway scope # Settings > Access Tokens > Add new token # Scopes: api, ai_gateway # Save token securely

3. Drupal Requirements

# Install drupal/ai module composer require drupal/ai:^1.2.5 # Enable module drush en ai -y # Verify installation drush pm:list --filter=ai

Implementation

Step 1: Create GitLab AI Gateway Provider Plugin

File: modules/custom/ai_provider_gitlab/src/Plugin/AiProvider/GitLabAiGateway.php

<?php declare(strict_types=1); namespace Drupal\ai_provider_gitlab\Plugin\AiProvider; use Drupal\ai\Attribute\AiProvider; use Drupal\ai\Base\AiProviderClientBase; use Drupal\ai\OperationType\Chat\ChatInput; use Drupal\ai\OperationType\Chat\ChatMessage; use Drupal\ai\OperationType\Chat\ChatOutput; use Drupal\Core\StringTranslation\TranslatableMarkup; use GuzzleHttp\ClientInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * GitLab AI Gateway provider plugin. */ #[AiProvider( id: 'gitlab_ai_gateway', label: new TranslatableMarkup('GitLab AI Gateway'), )] final class GitLabAiGateway extends AiProviderClientBase { /** * GitLab AI Gateway endpoint. */ private const GATEWAY_ENDPOINT = 'https://gitlab.com/api/v4/ai_gateway'; /** * HTTP client. */ private ClientInterface $httpClient; /** * {@inheritdoc} */ public static function create( ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ): static { $instance = parent::create( $container, $configuration, $plugin_id, $plugin_definition ); $instance->httpClient = $container->get('http_client'); return $instance; } /** * {@inheritdoc} */ public function getConfiguredModels(string $operation_type = NULL): array { return [ 'claude-sonnet-4' => 'Claude Sonnet 4 (via GitLab)', 'gpt-4o' => 'GPT-4o (via GitLab)', 'gpt-4-turbo' => 'GPT-4 Turbo (via GitLab)', 'claude-opus-4' => 'Claude Opus 4 (via GitLab)', ]; } /** * {@inheritdoc} */ public function getSupportedOperationTypes(): array { return [ 'chat', 'text_to_text', 'embeddings', ]; } /** * {@inheritdoc} */ public function chat( array|string|ChatInput $input, string $model_id, array $tags = [], ): ChatOutput { // Prepare messages. if (is_string($input)) { $messages = [['role' => 'user', 'content' => $input]]; } elseif (is_array($input)) { $messages = $input; } elseif ($input instanceof ChatInput) { $messages = array_map( fn(ChatMessage $msg) => [ 'role' => $msg->getRole(), 'content' => $msg->getText(), ], $input->getMessages() ); } // Get GitLab token from configuration. $token = $this->configuration['api_key'] ?? ''; if (empty($token)) { throw new \RuntimeException( 'GitLab AI Gateway token not configured' ); } // Call GitLab AI Gateway. $response = $this->httpClient->request('POST', self::GATEWAY_ENDPOINT . '/chat', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', ], 'json' => [ 'model' => $model_id, 'messages' => $messages, ], ]); $data = json_decode($response->getBody()->getContents(), TRUE); // Extract response and usage. $response_text = $data['choices'][0]['message']['content'] ?? ''; $usage = [ 'prompt_tokens' => $data['usage']['prompt_tokens'] ?? 0, 'completion_tokens' => $data['usage']['completion_tokens'] ?? 0, 'total_tokens' => $data['usage']['total_tokens'] ?? 0, ]; // Return ChatOutput. return new ChatOutput( new ChatMessage('assistant', $response_text), $response_text, [], $usage, ); } }

Step 2: Create Module Info File

File: modules/custom/ai_provider_gitlab/ai_provider_gitlab.info.yml

name: 'GitLab AI Gateway Provider' type: module description: 'AI provider plugin for GitLab AI Gateway integration' package: AI core_version_requirement: ^11 dependencies: - ai:ai

Step 3: Configuration Schema

File: modules/custom/ai_provider_gitlab/config/schema/ai_provider_gitlab.schema.yml

ai.provider.gitlab_ai_gateway: type: mapping label: 'GitLab AI Gateway Configuration' mapping: api_key: type: string label: 'GitLab Personal Access Token' endpoint: type: string label: 'AI Gateway Endpoint' project_id: type: integer label: 'GitLab Project ID' cost_tracking: type: boolean label: 'Enable Cost Tracking'

Step 4: Configuration Form

File: modules/custom/ai_provider_gitlab/src/Form/GitLabAiGatewayConfigForm.php

<?php declare(strict_types=1); namespace Drupal\ai_provider_gitlab\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; /** * Configuration form for GitLab AI Gateway provider. */ final class GitLabAiGatewayConfigForm extends ConfigFormBase { /** * {@inheritdoc} */ protected function getEditableConfigNames(): array { return ['ai_provider_gitlab.settings']; } /** * {@inheritdoc} */ public function getFormId(): string { return 'ai_provider_gitlab_config_form'; } /** * {@inheritdoc} */ public function buildForm( array $form, FormStateInterface $form_state ): array { $config = $this->config('ai_provider_gitlab.settings'); $form['api_key'] = [ '#type' => 'textfield', '#title' => $this->t('GitLab Personal Access Token'), '#description' => $this->t( 'Create at: Settings > Access Tokens with ai_gateway scope' ), '#default_value' => $config->get('api_key'), '#required' => TRUE, ]; $form['endpoint'] = [ '#type' => 'textfield', '#title' => $this->t('AI Gateway Endpoint'), '#default_value' => $config->get('endpoint') ?? 'https://gitlab.com/api/v4/ai_gateway', '#required' => TRUE, ]; $form['project_id'] = [ '#type' => 'number', '#title' => $this->t('GitLab Project ID'), '#description' => $this->t( 'Project ID for cost attribution' ), '#default_value' => $config->get('project_id'), ]; $form['cost_tracking'] = [ '#type' => 'checkbox', '#title' => $this->t('Enable Cost Tracking'), '#description' => $this->t( 'Track token usage and costs in GitLab' ), '#default_value' => $config->get('cost_tracking') ?? TRUE, ]; return parent::buildForm($form, $form_state); } /** * {@inheritdoc} */ public function submitForm( array &$form, FormStateInterface $form_state ): void { $this->config('ai_provider_gitlab.settings') ->set('api_key', $form_state->getValue('api_key')) ->set('endpoint', $form_state->getValue('endpoint')) ->set('project_id', $form_state->getValue('project_id')) ->set('cost_tracking', $form_state->getValue('cost_tracking')) ->save(); parent::submitForm($form, $form_state); } }

Usage Examples

Example 1: Basic Chat

<?php use Drupal\ai\OperationType\Chat\ChatInput; use Drupal\ai\OperationType\Chat\ChatMessage; /** @var \Drupal\ai\AiProviderPluginManager $ai_provider_manager */ $ai_provider_manager = \Drupal::service('ai.provider'); // Get GitLab AI Gateway provider. $provider = $ai_provider_manager->createInstance('gitlab_ai_gateway'); // Create chat input. $input = new ChatInput([ new ChatMessage('user', 'Explain Drupal hooks in 2 sentences.'), ]); // Send to Claude via GitLab. $output = $provider->chat($input, 'claude-sonnet-4'); // Get response. $response = $output->getNormalized(); echo $response; // Check token usage. $usage = $output->getUsage(); echo "Tokens used: {$usage['total_tokens']}";

Example 2: Content Generation with ECA

# ECA Model: Generate meta descriptions uuid: generate-meta-description label: 'Generate Meta Description on Node Save' version: '1.0' events: node_presave: plugin: 'node:presave' configuration: bundle: article conditions: meta_description_empty: plugin: 'field:empty' configuration: field_name: field_meta_description actions: generate_description: plugin: 'ai:generate_text' configuration: provider: gitlab_ai_gateway model: claude-sonnet-4 prompt: | Write a 150-character meta description for this article: Title: [node:title] Summary: [node:body:summary] target_field: field_meta_description

Example 3: Semantic Search with Vector Embeddings

<?php use Drupal\ai\OperationType\Embeddings\EmbeddingsInput; // Generate embeddings via GitLab AI Gateway. $provider = \Drupal::service('ai.provider') ->createInstance('gitlab_ai_gateway'); $input = new EmbeddingsInput('Drupal AI integration patterns'); $output = $provider->embeddings($input, 'text-embedding-ada-002'); $embedding = $output->getEmbedding(); // Store in vector database. $vector_storage = \Drupal::service('ai.vector_db'); $vector_storage->upsert([ 'id' => 'doc-123', 'embedding' => $embedding, 'metadata' => ['title' => 'AI Integration Patterns'], ]); // Search by similarity. $query_embedding = $provider->embeddings( new EmbeddingsInput('How to integrate AI in Drupal'), 'text-embedding-ada-002' )->getEmbedding(); $results = $vector_storage->search($query_embedding, 5);

Testing

Unit Tests

File: modules/custom/ai_provider_gitlab/tests/src/Unit/GitLabAiGatewayTest.php

<?php declare(strict_types=1); namespace Drupal\Tests\ai_provider_gitlab\Unit; use Drupal\ai_provider_gitlab\Plugin\AiProvider\GitLabAiGateway; use Drupal\Tests\UnitTestCase; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Response; /** * Tests for GitLab AI Gateway provider. * * @group ai_provider_gitlab */ final class GitLabAiGatewayTest extends UnitTestCase { /** * Tests chat operation. */ public function testChatOperation(): void { $http_client = $this->createMock(Client::class); $http_client ->method('request') ->willReturn(new Response(200, [], json_encode([ 'choices' => [ ['message' => ['content' => 'Test response']], ], 'usage' => [ 'prompt_tokens' => 10, 'completion_tokens' => 20, 'total_tokens' => 30, ], ]))); // Test implementation... } }

Cost Tracking

GitLab AI Gateway automatically tracks:

  • Token Usage: Prompt tokens, completion tokens, total tokens
  • Cost Attribution: Per project, per user, per model
  • Rate Limits: Configurable limits per project
  • Historical Data: 90-day retention

View in GitLab:

Project > Settings > AI Gateway > Usage Analytics

Security Considerations

1. Token Storage

NEVER commit tokens to git:

// WRONG $config['api_key'] = 'glpat-xyz123'; // CORRECT - Use Drupal Key module $key_repository = \Drupal::service('key.repository'); $token = $key_repository->getKey('gitlab_ai_gateway')->getKeyValue();

Instead of Personal Access Tokens, use OIDC:

// Use GitLab CI/CD OIDC token $oidc_token = getenv('CI_JOB_JWT'); $provider = $ai_provider_manager->createInstance('gitlab_ai_gateway', [ 'auth_type' => 'oidc', 'oidc_token' => $oidc_token, ]);

3. Rate Limiting

// Configure rate limits in gitlab-ci.yml ai_gateway_limits: requests_per_minute: 60 tokens_per_day: 1000000 cost_limit_usd: 100.00

Troubleshooting

Issue: "Unauthorized" Error

Cause: Invalid or expired GitLab token

Fix:

# Verify token has ai_gateway scope glab auth status # Create new token if needed # Settings > Access Tokens > Add new token # Scopes: api, ai_gateway

Issue: "Model not available"

Cause: Model not enabled in GitLab AI Gateway

Fix:

# In gitlab.rb ai_gateway['enabled_models'] = [ 'claude-sonnet-4', 'gpt-4o', 'claude-opus-4' ] gitlab-ctl reconfigure

Issue: High Token Usage

Cause: No caching, excessive prompts

Fix:

// Enable response caching $provider = $ai_provider_manager->createInstance('gitlab_ai_gateway', [ 'cache_ttl' => 3600, // 1 hour 'cache_backend' => 'redis', ]);

Performance Optimization

1. Response Caching

use Drupal\Core\Cache\CacheBackendInterface; $cache_key = 'ai_response:' . md5($prompt); $cached = \Drupal::cache()->get($cache_key); if ($cached) { return $cached->data; } $output = $provider->chat($input, 'claude-sonnet-4'); \Drupal::cache()->set($cache_key, $output, CacheBackendInterface::CACHE_PERMANENT);

2. Batch Processing

// Process multiple prompts in batch $batch = [ 'title' => t('Generating AI content...'), 'operations' => [], ]; foreach ($nodes as $node) { $batch['operations'][] = [ 'ai_provider_gitlab_batch_generate', [$node->id(), $provider_id, $model_id], ]; } batch_set($batch);

3. Async Processing with Queue

// Queue AI generation tasks $queue = \Drupal::queue('ai_generation_queue'); $queue->createItem([ 'node_id' => $node->id(), 'provider' => 'gitlab_ai_gateway', 'model' => 'claude-sonnet-4', 'prompt' => $prompt, ]); // Process via cron drush cron

Integration with Other Modules

With drupal/ai_agents

// Use GitLab AI Gateway as agent backend $agent = \Drupal::service('ai.agent_manager') ->createAgent([ 'id' => 'content_moderator', 'provider' => 'gitlab_ai_gateway', 'model' => 'claude-sonnet-4', 'tools' => ['node_moderation', 'taxonomy_tagging'], ]); $result = $agent->execute('Moderate this article: ' . $node->getTitle());

With drupal/eca

# ECA workflow using GitLab AI Gateway events: comment_insert: plugin: 'comment:insert' actions: analyze_sentiment: plugin: 'ai:analyze_sentiment' configuration: provider: gitlab_ai_gateway model: claude-sonnet-4 text: '[comment:body:value]' block_if_negative: plugin: 'comment:unpublish' condition: '[sentiment:score] < 0.3'

Production Checklist

  • GitLab AI Gateway configured and accessible
  • Personal Access Token created with ai_gateway scope
  • Token stored securely using drupal/key module
  • Module enabled: drush en ai_provider_gitlab -y
  • Configuration saved: Admin > Config > AI > Providers
  • Test chat request successful
  • Cost tracking enabled in GitLab
  • Rate limits configured
  • Monitoring alerts set up
  • Documentation updated in wiki

Next Steps

  1. Implement OSSA Bridge: See integrations/ossa-bridge-drupal.md
  2. Add ECA AI Actions: See integrations/eca-ai-actions.md
  3. Setup Vector Search: See integrations/vector-search-drupal.md
  4. Configure Monitoring: See GitLab Observability docs

References