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:
- Centralized Token Management - One place to manage all AI provider tokens
- Cost Tracking - Track token usage and costs per project/team
- Rate Limiting - Prevent runaway API costs
- Model Abstraction - Switch models without changing Drupal code
- Security - Tokens never exposed to Drupal, OIDC authentication
- 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();
2. OIDC Authentication (Recommended)
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
- Implement OSSA Bridge: See
integrations/ossa-bridge-drupal.md - Add ECA AI Actions: See
integrations/eca-ai-actions.md - Setup Vector Search: See
integrations/vector-search-drupal.md - Configure Monitoring: See GitLab Observability docs
References
- GitLab AI Gateway Docs: https://docs.gitlab.com/ee/development/ai_gateway/
- drupal/ai Module: https://www.drupal.org/project/ai
- OSSA Spec: https://gitlab.com/blueflyio/platform-agents/ossa
- Phase 1 Research:
modules/drupal-ai-integration-guide.md