drupal/ai Integration Guide
Comprehensive guide to integrating the drupal/ai module with custom AI agents and workflows
drupal/ai Integration Guide
Comprehensive reference for integrating the drupal/ai module with custom Drupal projects, AI agents, and the Agent Platform.
Overview
The drupal/ai module (also called AI module) is Drupal's foundational AI integration framework enabling seamless provider management, service abstraction, and AI-powered content operations. It abstracts multiple AI providers behind a unified interface, allowing swappable implementations without code changes.
Current Stable Version: 1.2.5 Minimum Drupal Version: 10.0+ PHP Version: 8.1+ Dependencies: Key module (for API key storage)
Key Capabilities
- 48+ Provider Support: Anthropic, OpenAI, Google Gemini, AWS Bedrock, Azure, Hugging Face, and more
- Unified Service Layer: Single API for all provider interactions
- Plugin Architecture: Extensible provider and service plugins
- Vector Database Integration: Milvus, Pinecone, Postgres, Azure, SQLite
- Semantic Search: Full RAG implementation with embeddings
- Content Integration: AI-powered field population, tone adjustment, translations
- Multi-language: One-click AI translations across all content
Sub-modules
The drupal/ai framework includes 11 core sub-modules covering specific AI capabilities:
1. AI Core ([object Object])
Purpose: Foundation module providing provider abstraction and unified service interface
Responsibilities:
- Provider plugin management and discovery
- Service interface definitions
- Configuration schema
- Default provider handling
- API key management integration with Key module
Key Services:
ai.provider: Primary provider factory serviceai.provider.manager: Plugin manager for all providersai.embeddings.provider: Embeddings-specific providerai.text.classification.provider: Classification provider
When to Use: Required base module for all other AI functionality
Dependencies:
- Drupal Core >= 10.0
- Key module (drupal/key)
- PHP >= 8.1
2. AI Explorer ([object Object])
Purpose: Administrative interface for testing and experimenting with AI models
Capabilities:
- Real-time prompt testing
- Model switching and comparison
- Response preview and iteration
- Configuration validation
- Provider connectivity testing
Use Cases:
- Testing provider credentials
- Experimenting with prompts before integration
- Verifying model behavior
- Debugging provider connectivity
- Content tone/style testing
Configuration:
# admin/config/ai/explorer settings: default_model: null # Uses AI Core default max_tokens: 2000 temperature: 0.7 save_history: true history_retention_days: 30
Permissions:
administer ai explorer: Full access- View only: None (requires admin)
3. AI Automators ([object Object])
Purpose: Automatically populate and modify Drupal fields using AI-generated content
Capabilities:
- Field population workflows
- Chained prompts for complex operations
- Multi-step content generation
- Conditional logic support
- Integration with ECA (Entity Component Architecture) module
Supported Operations:
- Text field generation
- Taxonomy term assignment
- Computed field population
- Multi-language field translation
- Image description generation
Configuration Structure:
automator: id: generate_node_summary label: Generate Node Summary field_mapping: source_field: body target_field: summary prompt_chain: - id: extract_key_points prompt: "Extract 3 key points from: {{field:body}}" - id: create_summary prompt: "Create 50-word summary of: {{previous_output}}" conditions: - field: field_status value: draft
Integration with ECA:
- Use ECA conditions to trigger automators
- Chain automators in ECA workflows
- Event-driven field updates
- Scheduled field population
Performance Considerations:
- Batch process field updates to avoid rate limits
- Use Queue API for async execution
- Cache automator results
- Monitor token usage
4. AI Search ([object Object] - Experimental)
Purpose: Semantic search and chatbot querying using embeddings and vector databases
Capabilities:
- Embedding vector generation
- Vector database integration
- Semantic similarity matching
- LLM-powered search fallback
- Retrieval Augmented Generation (RAG)
- Hallucination reduction
- Context-aware chatbot responses
Supported Vector Databases:
- Postgres - pgvector extension (recommended for self-hosted)
- Milvus - Distributed vector DB
- Pinecone - Cloud-native vector service
- Azure - Azure Search with vector capabilities
- SQLite - sqlite-vec extension (development/small scale)
Configuration Example:
ai_search: vector_db: pinecone pinecone_config: api_key: '${key:pinecone_api_key}' environment: production index: drupal-content embedding_model: openai_text_embedding_3_small search_params: top_k: 5 similarity_threshold: 0.7 rag_enabled: true chunk_size: 500 chunk_overlap: 50
Use Cases:
- Site-wide semantic search
- Smart documentation search
- Support ticket AI matching
- Related content suggestions
- Chatbot knowledge base queries
Performance: Requires initial embedding pass for all searchable content; subsequent searches are fast vector lookups.
5. AI Assistants API + Chatbot ([object Object], [object Object])
Purpose: Framework for configuring AI-powered chatbots with advanced search capabilities
Capabilities:
- Chatbot configuration interface
- Multiple persona support
- Custom system prompts
- Integration with AI Search for context
- Channel-agnostic assistant API
- Widget/embed support
- Session management
Chatbot Configuration:
chatbot: id: support_bot label: Support Assistant provider: anthropic_claude_3_sonnet system_prompt: | You are a helpful support assistant for [Company]. Use the provided context to answer questions accurately. temperature: 0.7 max_tokens: 1500 search_integration: enabled: true source: ai_search context_count: 3 memory: type: session # session, user, or persistent retention_messages: 10
Interfaces:
- Widget JavaScript (embeddable)
- REST API endpoint
- Form element integration
- Custom HTML integration
Authentication:
// In your custom module integrating chatbot $assistant = \Drupal::service('ai_assistants.manager') ->loadAssistant('support_bot'); $response = $assistant->chat( 'How do I reset my password?', $user_context );
6. AI CKEditor ([object Object])
Purpose: Integrates AI assistance directly into CKEditor 5 for content editors
Capabilities:
- In-editor AI prompt handling
- Spell and grammar checking
- Tone adjustment (formal, casual, technical)
- Translation suggestions
- Suggested text improvements
- Inline formatting assistance
Editor Integration:
// CKEditor 5 configuration const editorConfig = { plugins: ['Ai', 'AiProofread', 'AiTone'], ai: { provider: 'default', features: { proofread: true, tone: true, translate: true } } };
User Experience:
- Floating toolbar with AI suggestions
- Keyboard shortcuts for quick access
- Non-disruptive popup assistance
- Real-time feedback
Performance: Debounced requests to avoid excessive API calls
7. AI Content ([object Object])
Purpose: AI-powered content editing tools integrated with Drupal's content system
Tools Included:
- Tone Adjustment: Convert content between tones (formal † casual)
- Summarization: Generate abstracts or summaries
- Taxonomy Suggestions: AI-suggested content tags
- Moderation Checking: Detect policy violations
- Length Adjustment: Expand or condense text
Content Editing Form Integration:
// In custom form alteration function my_module_form_node_form_alter(&$form, FormStateInterface $form_state) { $form['body']['widget'][0]['#ai_assist'] = TRUE; $form['title'][0]['value']['#ai_assist'] = TRUE; $form['field_summary']['widget'][0]['value']['#ai_assist'] = TRUE; }
Moderation Integration:
ai_content: moderation: enabled: true provider: openai # Uses OpenAI moderation API check_fields: - body - comments threshold: 0.5 action_on_violation: flag # flag, block, or notify
UI Components:
- Suggested edits panel
- Quick action buttons
- Modal prompt interface
- Bulk operations support
8. AI External Moderation ([object Object])
Purpose: Apply third-party moderation (OpenAI) to non-OpenAI providers
Use Case: You use Claude (Anthropic) for generation but want OpenAI's moderation service
Configuration:
external_moderation: enabled: true service: openai_moderation check_before_publication: true flags: sexual: false harassment: true violence: true self_harm: true illegal: true confidence_threshold: 0.6
Workflow:
- Content generated by provider (e.g., Anthropic Claude)
- Content passed to OpenAI moderation service
- Results scored across 7 categories
- Actions taken based on configuration
- Logs recorded in AI Logging module
Cost Considerations: Additional API calls (moderation = 1 call per check)
9. AI Logging ([object Object])
Purpose: Comprehensive audit trail of all AI requests and responses
Logged Information:
- Request timestamp and duration
- Provider and model used
- Input/output tokens
- Cost (if available from provider)
- User who initiated request
- Response outcome (success/failure)
- Full prompts and responses (optional)
Configuration:
ai_logging: enabled: true log_level: full # full, summary, or errors_only retention_days: 90 exclude_modules: [] # Modules to exclude from logging mask_pii: true # Redact personal information retention_rules: success: 90 error: 180 moderation: 30
Accessing Logs:
// Query AI logs $logs = \Drupal::database()->query( "SELECT * FROM {ai_log} WHERE provider = :provider AND created > :start_date ORDER BY created DESC", [ ':provider' => 'anthropic', ':start_date' => time() - (30 * 86400) ] )->fetchAll(); // Log analysis foreach ($logs as $log) { $cost = $log->input_tokens * 0.003 / 1000000 + $log->output_tokens * 0.015 / 1000000; $duration = $log->duration_ms / 1000; }
Admin Reports:
- View † Logs & Reports † AI Usage
- Filter by provider, module, user
- Export to CSV/JSON
- Cost analysis dashboard
10. AI Translate ([object Object])
Purpose: One-click AI-powered translations for multilingual Drupal sites
Capabilities:
- Bulk translation of content
- Language-specific provider selection
- Quality tier selection (draft/standard/professional)
- Translation memory integration
- Terminology consistency
Configuration:
ai_translate: provider: google_gemini # Best for translation source_language: en target_languages: - es - fr - de - ja quality: standard # draft, standard, professional glossary_mode: enabled preserve_formatting: true
Translation Workflow:
// Programmatic translation $translator = \Drupal::service('ai_translate.manager'); $translated = $translator->translateContent( $node, 'es', // Spanish ['quality' => 'professional'] ); // Bulk translation $results = $translator->bulkTranslate( 'node', // Entity type ['status' => 1], // Conditions 'fr', // Target language ['batch' => true] // Process in background );
Quality Levels:
- Draft: Fast, basic translation
- Standard: Balanced quality/cost
- Professional: Full review and terminology matching
11. AI Validations ([object Object])
Purpose: Use AI prompts for advanced field validation
Capabilities:
- Text content validation
- Policy compliance checking
- Semantic validation (e.g., "title matches body topic")
- Custom validation rules via prompts
Configuration:
ai_validations: body: enabled: true rule: "Does this content comply with company content guidelines?" severity: error # error, warning model: openai_gpt_4 title: enabled: true rule: "Is the title descriptive (5-10 words)?" severity: warning email: enabled: true rule: "Is this a valid professional email format?" severity: error
Form Integration:
// In form definition $form['title'] = [ '#type' => 'textfield', '#title' => t('Title'), '#required' => TRUE, '#ai_validate' => [ 'rule_id' => 'title_quality', 'severity' => 'warning' ] ];
Validation Execution:
- Real-time as user types (with debounce)
- On form submit
- In batch operations
- Programmable via API
API Services
The drupal/ai module provides several core services for AI operations.
Primary Services
1. [object Object] Service
Interface: Drupal\ai\AiProvider\AiProviderInterface
Purpose: Factory and manager for provider instances
Key Methods:
// Get default provider $provider = \Drupal::service('ai.provider') ->getProvider('default'); // Get specific provider by ID $claude = \Drupal::service('ai.provider') ->getProvider('anthropic_claude_3_opus'); // Get provider by type $embeddings_provider = \Drupal::service('ai.provider') ->getProvider('default', 'embeddings'); // List all available providers $providers = \Drupal::service('ai.provider') ->getAvailableProviders();
Configuration:
services: my_module.ai_processor: class: Drupal\my_module\Service\AiProcessor arguments: - '@ai.provider' - '@ai_logging.logger' tags: - { name: 'ai_service' }
2. [object Object] Service
Interface: Drupal\Core\Plugin\PluginManager
Purpose: Discovery and management of provider plugins
Key Methods:
$manager = \Drupal::service('ai.provider.manager'); // Get all providers $plugins = $manager->getDefinitions(); // Get specific provider $plugin_def = $manager->getDefinition('anthropic_claude_3_opus'); // Create provider instance $instance = $manager->createInstance('openai_gpt_4_turbo'); // Check if provider exists if ($manager->hasDefinition('mistral_large')) { // Provider available }
Plugin Discovery Locations:
src/Plugin/AiProvider/- Modules implementing
hook_ai_provider_info_alter()
3. Text Generation Service
Purpose: Generate text using AI providers
Methods:
$provider = \Drupal::service('ai.provider') ->getProvider('default'); // Simple completion $response = $provider->generate( 'Write a product description for a blue widget.' ); // With configuration $response = $provider->generate( 'Write a product description for a blue widget.', [ 'temperature' => 0.7, 'max_tokens' => 500, 'model' => 'claude-3-sonnet' ] ); // Streaming response $stream = $provider->generateStream( 'Write a long product description...', ['stream' => true] ); foreach ($stream as $chunk) { echo $chunk->getText(); }
Response Object:
class AiGenerateResponse { public function getText(): string; public function getInputTokens(): int; public function getOutputTokens(): int; public function getCost(): ?float; public function getMeta(): array; }
4. Embeddings Service
Purpose: Generate vector embeddings for semantic search
Methods:
$provider = \Drupal::service('ai.provider') ->getProvider('default', 'embeddings'); // Single embedding $embedding = $provider->embed('Node title and summary text'); // Batch embeddings $embeddings = $provider->embedBatch([ 'Text 1', 'Text 2', 'Text 3' ]); // Response contains vector class AiEmbedding { public function getVector(): array; // Float array public function getTokens(): int; public function getCost(): ?float; }
Vector Dimensions: Varies by model (1536 for OpenAI, 1024 for Cohere)
5. Moderation Service
Purpose: Check content for policy violations
Methods:
$provider = \Drupal::service('ai.provider') ->getProvider('default'); // Check text moderation $result = $provider->moderate('Content to check'); // Response structure class AiModerationResponse { public function isFlagged(): bool; public function getCategories(): array; // [category => score] public function getMaxScore(): float; public function getDetails(): array; } // Usage if ($result->isFlagged()) { foreach ($result->getCategories() as $category => $score) { if ($score > 0.5) { \Drupal::logger('ai')->warning( "Content flagged for @category with score @score", ['@category' => $category, '@score' => $score] ); } } }
Categories Returned:
- sexual
- harassment
- violence
- self_harm
- illegal_activity
- hate_speech
- self_harm_instructions
Configuration Schema
All AI Core services follow this configuration pattern:
# config/install/ai.settings.yml ai: # Default provider for all AI operations default_provider: openai_gpt_4_turbo # Provider-specific defaults provider_defaults: openai_gpt_4_turbo: temperature: 0.7 max_tokens: 2000 top_p: 1.0 anthropic_claude_3_opus: temperature: 0.7 max_tokens: 4096 stop_sequences: [] # Rate limiting per provider rate_limits: anthropic: requests_per_minute: 20 tokens_per_minute: 40000 openai: requests_per_minute: 20 tokens_per_minute: 90000 # Logging configuration logging: enabled: true detailed_logging: false retention_days: 90 # Caching strategy caching: enabled: true ttl: 3600 exclude_patterns: []
Provider Plugins
The drupal/ai module supports 48+ AI providers through a plugin architecture. Each provider implements the AiProviderInterface.
Supported Providers (by Category)
Cloud-Native LLM Providers
Anthropic
- Provider ID:
anthropic - Models: Claude 3 (Opus, Sonnet, Haiku), Claude 2
- Auth: API key (Key module)
- Rate Limit: 20 RPM (Tier 1)
- Streaming: Yes
- Cost: Input: $3/1M tokens, Output: $15/1M tokens
- Config:
providers: anthropic: key_id: anthropic_api_key model: claude-3-opus region: us-west-2
OpenAI
- Provider ID:
openai - Models: GPT-4 Turbo, GPT-4, GPT-3.5 Turbo
- Auth: API key + Organization ID
- Rate Limit: 20 RPM (free tier)
- Streaming: Yes
- Cost: GPT-4 Turbo: Input $10/1M, Output $30/1M
- Moderation: Native support
- Config:
providers: openai: key_id: openai_api_key organization_id: org-xxxxx model: gpt-4-turbo
Google Gemini
- Provider ID:
google_gemini - Models: Gemini Pro, Gemini Ultra
- Auth: API key
- Rate Limit: 60 RPM (free tier)
- Streaming: Yes
- Cost: Input: $0.5/1M tokens, Output: $1.5/1M tokens
- Config:
providers: google_gemini: key_id: google_api_key model: gemini-pro
Cohere
- Provider ID:
cohere - Models: Command, Command Light
- Auth: API key
- Rate Limit: 100 RPM
- Streaming: Yes
- Cost: Input: $1/1M tokens, Output: $2/1M tokens
- Embeddings: 1024 dimensions
- Config:
providers: cohere: key_id: cohere_api_key model: command
Mistral
- Provider ID:
mistral - Models: Mistral Large, Mistral Medium, Mistral Small
- Auth: API key
- Rate Limit: 100 RPM
- Streaming: Yes
- Cost: Competitive with OpenAI
- Config:
providers: mistral: key_id: mistral_api_key model: mistral-large
Self-Hosted / Open Source
Ollama (local)
- Provider ID:
ollama_local - Models: Llama 2, Mistral, Neural Chat
- Auth: None (local)
- Rate Limit: Unlimited (resource bound)
- Streaming: Yes
- Cost: Free (compute only)
- Config:
providers: ollama_local: base_url: http://localhost:11434 model: llama2
LLaMA 2 (Hugging Face)
- Provider ID:
huggingface_llama2 - Models: Meta LLaMA 2 variants
- Auth: Hugging Face token
- Rate Limit: Per tier
- Config:
providers: huggingface_llama2: key_id: huggingface_token model: meta-llama/Llama-2-70b
Enterprise Cloud Platforms
AWS Bedrock
- Provider ID:
aws_bedrock - Models: Claude, Llama 2, Mistral, Titan
- Auth: AWS credentials (IAM)
- Rate Limit: Account level
- Streaming: Yes (partial)
- Cost: Pay-per-use
- Config:
providers: aws_bedrock: aws_access_key_id: key_id aws_secret_access_key: secret_key aws_region: us-east-1 model: anthropic.claude-v2
Azure OpenAI
- Provider ID:
azure_openai - Models: GPT-4, GPT-3.5 Turbo, Embeddings
- Auth: Azure API key + endpoint
- Rate Limit: Per deployment
- Streaming: Yes
- Cost: Included in Azure subscription
- Config:
providers: azure_openai: api_key: key_id endpoint: https://myresource.openai.azure.com deployment_name: gpt4 api_version: 2024-02-15-preview
Google Cloud VertexAI
- Provider ID:
google_vertexai - Models: PaLM, Codey, Gemini
- Auth: Google Cloud credentials
- Rate Limit: Per project
- Streaming: Yes
- Config:
providers: google_vertexai: project_id: my-gcp-project location: us-central1 model: text-bison
Specialized / Niche Providers
Anthropic Claude (via AWS Bedrock)
- Lowest latency path for Bedrock customers
Replicate (fine-tuned models)
- Provider ID:
replicate - Supports custom fine-tuned models
- Pay-per-execution
TogetherAI (open source inference)
- Provider ID:
together_ai - Llama, Mistral, Falcon
Perplexity (search-augmented)
- Provider ID:
perplexity - Internet-connected responses
LiteLLM (proxy)
- Provider ID:
litellm - Abstracts 100+ providers
- Useful for multi-provider fallback
Authentication Patterns
API Key Storage (Recommended)
All providers use the Key module for secure credential storage.
Setup:
# Drupal UI: /admin/config/system/keys - Create new key - Type: "API Key" - Key name: "anthropic_api_key" - Key value: [paste actual API key] - Provider: Drupal core - Allowed settings: "Drupal\ai\AiProvider\..."
In Configuration:
ai: provider_defaults: anthropic: key_id: anthropic_api_key # Reference, not actual key
In Code:
$key = \Drupal::service('key.repository') ->getKey('anthropic_api_key'); $api_key = $key->getKeyValue(); // Actual key // Provider internally uses: $provider = \Drupal::service('ai.provider') ->getProvider('anthropic'); // No need to handle key directly
Environment Variables (Optional)
For Docker/CI environments:
# .env or deployment config ANTHROPIC_API_KEY=sk-ant-xxxx OPENAI_API_KEY=sk-xxxx GOOGLE_API_KEY=AIzaxxxxx
Configuration to use env:
ai: provider_defaults: anthropic: key_id: null # Skip Key module api_key_env: ANTHROPIC_API_KEY
OAuth2 (Enterprise)
For Azure/Google Cloud with OAuth:
// Custom provider plugin class GoogleVertexAiProvider implements AiProviderInterface { public function authenticate(): void { $credentials = \Google_Client::loadServiceAccountJson( file_get_contents('/path/to/service-account.json') ); $this->googleClient = new Google_Client(); $this->googleClient->setAuthConfig($credentials); $this->googleClient->addScope('https://www.googleapis.com/auth/cloud-platform'); } }
Rate Limiting Capabilities
Each provider plugin can implement rate limiting:
Provider-Side Rate Limiting:
// In provider plugin class AnthropicProvider implements AiProviderInterface, RateLimitedInterface { protected int $requestsPerMinute = 20; protected int $tokensPerMinute = 40000; public function generate(string $prompt, array $options = []): AiGenerateResponse { // Check rate limit before request if (!$this->rateLimiter->canMakeRequest()) { throw new RateLimitException( "Rate limit: 20 requests/minute exceeded. Retry after: " . $this->rateLimiter->getResetTime() ); } $response = $this->callApi($prompt, $options); // Update rate limiter with actual usage $this->rateLimiter->recordRequest( input_tokens: $response->getInputTokens(), output_tokens: $response->getOutputTokens() ); return $response; } }
Drupal-Side Rate Limiting:
// In custom module $rate_limiter = \Drupal::service('ai.rate_limiter'); // Check if within limit if (!$rate_limiter->isAllowed('anthropic', $user)) { throw new RateLimitException( "User has reached daily Claude API limit" ); } // Record usage $rate_limiter->record('anthropic', [ 'input_tokens' => 1500, 'output_tokens' => 500, 'user_id' => $user->id() ]);
Configuration:
ai: rate_limiting: enabled: true # Global limits global: requests_per_minute: 100 tokens_per_day: 1000000 # Per-provider limits providers: anthropic: requests_per_minute: 20 tokens_per_minute: 40000 requests_per_day: 10000 openai: requests_per_minute: 20 tokens_per_minute: 90000 # Per-user limits per_user: enabled: true requests_per_day: 100 tokens_per_day: 100000
Token Tracking Features
Token counting essential for cost management.
Automatic Tracking:
// Every response includes token counts $response = $provider->generate('Prompt...'); echo $response->getInputTokens(); // 42 echo $response->getOutputTokens(); // 157 // Estimated cost calculation $input_cost = $response->getInputTokens() * 0.003 / 1000000; // $0.000126 $output_cost = $response->getOutputTokens() * 0.015 / 1000000; // $0.002355 $total = $input_cost + $output_cost;
Usage Dashboard:
# admin/reports/ai-usage - Daily token usage by provider - Cost breakdown - Model usage statistics - User ranking - Trending models
Programmatic Tracking:
// Query AI usage $usage = \Drupal::database()->query( "SELECT provider, SUM(input_tokens) as total_input, SUM(output_tokens) as total_output, COUNT(*) as request_count, FROM {ai_log} WHERE created > :start GROUP BY provider", [':start' => time() - (30 * 86400)] )->fetchAllAssoc('provider'); // Calculate costs $costs = []; foreach ($usage as $provider => $stats) { // Model-specific rates $rates = [ 'anthropic' => ['input' => 0.003, 'output' => 0.015], 'openai' => ['input' => 0.01, 'output' => 0.03], ]; $rate = $rates[$provider] ?? null; if ($rate) { $costs[$provider] = ($stats->total_input * $rate['input'] + $stats->total_output * $rate['output']) / 1000000; } }
Integration Patterns
Pattern 1: Custom Field Population Automator
Automatically populate a summary field based on node body.
<?php declare(strict_types=1); namespace Drupal\my_module\Plugin\AiAutomator; use Drupal\ai\Plugin\AiAutomator\AiAutomatorBase; use Drupal\Core\Plugin\PluginBase; use Drupal\node\NodeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * AI Automator for generating node summaries. * * @AiAutomator( * id = "node_summary_generator", * label = @Translation("Node Summary Generator"), * description = @Translation("Generates 1-paragraph summaries for nodes"), * entity_type = "node", * target_field = "field_summary" * ) */ class NodeSummaryGenerator extends AiAutomatorBase { /** * {@inheritdoc} */ public static function create( ContainerInterface $container, array $configuration, string $plugin_id, array $plugin_definition ): static { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('ai.provider'), $container->get('ai_logging.logger') ); } /** * {@inheritdoc} */ public function generate( NodeInterface $node ): string { // Extract source content $body = $node->body->value ?? ''; if (empty($body)) { return ''; } // Generate summary via AI $prompt = <<<PROMPT Generate a single paragraph (100-150 words) summary of the following content: {$body} Summary: PROMPT; $response = $this->provider->generate($prompt, [ 'temperature' => 0.5, 'max_tokens' => 250, ]); return trim($response->getText()); } /** * {@inheritdoc} */ public function shouldApply(NodeInterface $node): bool { // Only apply to specific content types return in_array($node->bundle(), [ 'article', 'blog_post', ]); } }
Pattern 2: Custom Chatbot Integration
Implement a support chatbot using AI Assistants API.
<?php declare(strict_types=1); namespace Drupal\my_module\Service; use Drupal\ai_assistants\AiAssistantManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\user\UserInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Support chatbot service. */ class SupportChatbot { private AiAssistantManager $assistantManager; private EntityTypeManagerInterface $entityTypeManager; /** * Constructor. */ public function __construct( AiAssistantManager $assistant_manager, EntityTypeManagerInterface $entity_type_manager ) { $this->assistantManager = $assistant_manager; $this->entityTypeManager = $entity_type_manager; } /** * {@inheritdoc} */ public static function create( ContainerInterface $container ): static { return new static( $container->get('ai_assistants.manager'), $container->get('entity_type.manager') ); } /** * Handle user message and return bot response. */ public function chat( string $user_message, UserInterface $user ): string { // Load configured assistant $assistant = $this->assistantManager->loadAssistant( 'support_chatbot' ); // Get user's previous messages (context) $history = $this->getChatHistory($user, limit: 5); // Send message to assistant $response = $assistant->chat($user_message, [ 'user_id' => $user->id(), 'context' => $history, 'include_search_results' => TRUE, ]); // Save conversation to database $this->saveChatMessage( $user, $user_message, $response['text'] ); return $response['text']; } /** * Get user's chat history. */ private function getChatHistory( UserInterface $user, int $limit = 5 ): array { $messages = \Drupal::database()->query( 'SELECT user_message, bot_response, created FROM {support_chat} WHERE uid = :uid ORDER BY created DESC LIMIT :limit', [ ':uid' => $user->id(), ':limit' => $limit, ] )->fetchAll(); return array_map( fn($msg) => [ 'user' => $msg->user_message, 'bot' => $msg->bot_response, 'timestamp' => $msg->created, ], $messages ); } /** * Save chat message to history. */ private function saveChatMessage( UserInterface $user, string $user_message, string $bot_response ): void { \Drupal::database()->insert('support_chat') ->fields([ 'uid' => $user->id(), 'user_message' => $user_message, 'bot_response' => $bot_response, 'created' => time(), ]) ->execute(); } }
Pattern 3: Semantic Search Integration
Implement RAG-based search using AI Search module.
<?php declare(strict_types=1); namespace Drupal\my_module\Service; use Drupal\ai_search\AiSearchManager; use Drupal\Core\Entity\EntityTypeManagerInterface; /** * Semantic search service for documentation. */ class DocumentationSearch { private AiSearchManager $searchManager; private EntityTypeManagerInterface $entityTypeManager; /** * Constructor. */ public function __construct( AiSearchManager $search_manager, EntityTypeManagerInterface $entity_type_manager ) { $this->searchManager = $search_manager; $this->entityTypeManager = $entity_type_manager; } /** * Search documentation with semantic ranking. */ public function search(string $query): array { // Perform semantic search via AI Search $results = $this->searchManager->search( query: $query, index: 'documentation', top_k: 10, similarity_threshold: 0.7 ); // Convert to node entities $nodes = []; foreach ($results as $result) { $node = $this->entityTypeManager ->getStorage('node') ->load($result['entity_id']); if ($node && $node->access('view')) { $nodes[] = [ 'entity' => $node, 'score' => $result['similarity_score'], 'excerpt' => $this->generateExcerpt( $node->body->value, $query ), ]; } } return $nodes; } /** * Generate relevant excerpt from content. */ private function generateExcerpt( string $content, string $query ): string { // Find sentences containing query terms $sentences = preg_split('/[.!?]+/', $content); $query_terms = explode(' ', strtolower($query)); $scored_sentences = []; foreach ($sentences as $sentence) { $score = 0; $sentence_lower = strtolower($sentence); foreach ($query_terms as $term) { if (str_contains($sentence_lower, $term)) { $score++; } } if ($score > 0) { $scored_sentences[] = [ 'text' => trim($sentence), 'score' => $score, ]; } } // Return top sentence or truncated content if (!empty($scored_sentences)) { usort( $scored_sentences, fn($a, $b) => $b['score'] <=> $a['score'] ); return $scored_sentences[0]['text']; } return substr($content, 0, 150) . '...'; } }
Pattern 4: Content Moderation Workflow
Implement content approval workflow using moderation + AI validation.
<?php declare(strict_types=1); namespace Drupal\my_module\EventSubscriber; use Drupal\content_moderation\Event\ContentModerationStateChangedEvent; use Drupal\ai\AiProviderInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Drupal\node\NodeInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; /** * Validates content using AI when publishing. */ class AiContentModerationSubscriber implements EventSubscriberInterface { private AiProviderInterface $provider; private LoggerChannelFactoryInterface $loggerFactory; /** * Constructor. */ public function __construct( AiProviderInterface $provider, LoggerChannelFactoryInterface $logger_factory ) { $this->provider = $provider; $this->loggerFactory = $logger_factory; } /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { return [ ContentModerationStateChangedEvent::class => 'onContentModerationStateChanged', ]; } /** * Validate content before publishing. */ public function onContentModerationStateChanged( ContentModerationStateChangedEvent $event ): void { $entity = $event->getEntity(); // Only validate nodes moving to published if (!$entity instanceof NodeInterface) { return; } if ($event->getNewState()?->id() !== 'published') { return; } // Check content guidelines via AI $violations = $this->validateContent($entity); if (!empty($violations)) { // Block publishing if violations found $event->preventDefault(); $this->loggerFactory ->get('content_moderation') ->warning( 'Publishing blocked for node @id. Violations: @violations', [ '@id' => $entity->id(), '@violations' => implode(', ', $violations), ] ); // Notify author $this->notifyAuthor( $entity, $violations ); } } /** * Validate node content against guidelines. */ private function validateContent(NodeInterface $node): array { $violations = []; // Check title quality if (strlen($node->getTitle()) < 5) { $violations[] = 'Title too short'; } // Check body content if (empty($node->body->value)) { $violations[] = 'Body cannot be empty'; } // AI-powered compliance check $compliance = $this->provider->moderate( $node->body->value ); if ($compliance->isFlagged()) { foreach ($compliance->getCategories() as $category => $score) { if ($score > 0.7) { $violations[] = "Content flagged for $category ($score)"; } } } return $violations; } /** * Notify author of publishing issues. */ private function notifyAuthor( NodeInterface $node, array $violations ): void { $author = $node->getOwner(); \Drupal::service('plugin.manager.mail')->mail( module: 'my_module', key: 'content_blocked', to: $author->getEmail(), langcode: $author->getPreferredLangcode(), params: [ 'node' => $node, 'violations' => $violations, ] ); } }
Configuration
Module Installation & Setup
Installation Steps:
# 1. Download module composer require drupal/ai # 2. Enable module drush en ai # 3. Install Key module for credentials drush en key # 4. Install submodules drush en ai_explorer ai_logging ai_automators ai_search
Initial Configuration
Step 1: Create API Keys (/admin/config/system/keys)
Key Name: anthropic_api_key Provider: Drupal core Value: [Paste API key from https://console.anthropic.com/] Allowed Settings: Drupal\ai\AiProvider\*
Step 2: Configure AI Core (/admin/config/ai/core)
Default Provider: anthropic Provider-Specific Settings: anthropic: api_key_storage: anthropic_api_key model: claude-3-sonnet temperature: 0.7 max_tokens: 2000
Step 3: Enable Logging (/admin/config/ai/logging)
Logging Enabled: Yes Log Level: Full Retention: 90 days Mask PII: Yes
Programmatic Configuration
In your module's config/install/ai.settings.yml:
ai: default_provider: anthropic provider_defaults: anthropic: model: claude-3-opus temperature: 0.5 max_tokens: 4096 stop_sequences: [] openai: model: gpt-4-turbo temperature: 0.7 max_tokens: 2000 logging: enabled: true detailed_logging: false retention_days: 90 mask_pii: true rate_limiting: enabled: true per_user_daily_limit: 100 per_provider_limits: anthropic: requests_per_minute: 20 tokens_per_minute: 40000 search: enabled: false vector_db: null embedding_model: null automators: enabled: true batch_size: 10 queue_timeout: 3600 external_moderation: enabled: false service: null
Common Integration Issues
Issue 1: "Provider Not Found" Error
Error Message:
The requested provider "anthropic" is not available.
Available providers: [empty]
Cause: Provider plugin not discovered or not enabled
Solution:
# 1. Clear Drupal cache drush cache:rebuild # 2. Verify provider module is installed drush pm:list | grep ai # 3. Check provider plugin definition drush php:eval 'print_r(\Drupal::service("ai.provider.manager")->getDefinitions());' # 4. Enable required AI submodule drush en ai_provider_anthropic
Issue 2: API Key Not Working
Error: 403 Forbidden or 401 Unauthorized
Diagnosis:
# 1. Check key is stored correctly drush php:eval '$key = \Drupal::service("key.repository")->getKey("anthropic_api_key"); echo $key->getKeyValue();' # 2. Verify provider can access key drush php:eval '$provider = \Drupal::service("ai.provider")->getProvider("anthropic"); $provider->authenticate();' # 3. Test API directly curl -X POST https://api.anthropic.com/v1/messages \ -H "x-api-key: YOUR_KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{"model":"claude-3-opus","messages":[{"role":"user","content":"test"}]}'
Solutions:
// Fix 1: Regenerate key // Admin UI: /admin/config/system/keys † Edit † Save // Fix 2: Verify key permissions $key = \Drupal::service('key.repository') ->getKey('anthropic_api_key'); // Ensure permissions allow AI module $key->setAllowedSettings([ 'Drupal\ai\AiProvider\AnthropicProvider' ]); $key->save(); // Fix 3: Check provider configuration $config = \Drupal::config('ai.settings'); $anthropic_config = $config->get('provider_defaults.anthropic'); // Should contain: // - model: valid model name // - api_key_storage or api_key_env
Issue 3: Rate Limiting / Throttling
Error: 429 Too Many Requests or Rate limit exceeded
Solution:
// Implement backoff and retry logic function safe_ai_request(callable $callback): mixed { $max_retries = 3; $backoff = 1; // seconds for ($attempt = 0; $attempt < $max_retries; $attempt++) { try { return $callback(); } catch (RateLimitException $e) { if ($attempt < $max_retries - 1) { sleep($backoff); $backoff *= 2; // Exponential backoff } else { throw $e; } } } } // Usage $response = safe_ai_request( fn() => $provider->generate('Your prompt here') );
Configuration:
ai: rate_limiting: enabled: true retry_policy: exponential_backoff max_retries: 3 initial_backoff_seconds: 1 providers: anthropic: requests_per_minute: 20 tokens_per_minute: 40000 concurrent_requests: 5
Issue 4: Token Cost Explosion
Cause: Unexpected high token usage driving up costs
Diagnosis:
// Query high-cost requests $high_cost = \Drupal::database()->query( "SELECT created, provider, input_tokens, output_tokens FROM {ai_log} WHERE input_tokens > 10000 OR output_tokens > 5000 ORDER BY created DESC LIMIT 20" )->fetchAll(); // Calculate cost per request foreach ($high_cost as $log) { $cost = ($log->input_tokens * 0.003 + $log->output_tokens * 0.015) / 1000000; echo "$cost for provider {$log->provider}"; }
Solutions:
// 1. Set max_tokens limit $response = $provider->generate($prompt, [ 'max_tokens' => 500, // Hard limit 'temperature' => 0.3, // Lower randomness ]); // 2. Implement prompt caching $cache_key = md5($prompt); if ($cached = \Drupal::cache('ai')->get($cache_key)) { return $cached->data; } $response = $provider->generate($prompt); \Drupal::cache('ai')->set($cache_key, $response, 86400); // 3. Use cheaper models for testing $model = \Drupal::state()->get('ai_test_mode') ? 'claude-3-haiku' // Cheaper : 'claude-3-opus'; // Full featured // 4. Batch operations $prompts = [...]; // 100 prompts $results = $provider->generateBatch( $prompts, ['batch_size' => 10] // Process in chunks );
Issue 5: Embeddings/Vector DB Connection Failed
Error: Connection refused to vector database or Embedding service unavailable
Solution:
# 1. Verify vector DB is running # For Pinecone curl https://your-index.pinecone.io/describe_index_stats \ -H "Api-Key: YOUR_PINECONE_KEY" # For Postgres psql -h localhost -U postgres -d drupal_db \ -c "SELECT vector_support_version();" # For Milvus python -c "from pymilvus import connections; connections.connect('default')"
Configuration Fix:
ai_search: vector_db_provider: postgres postgres_config: host: localhost port: 5432 database: drupal_db user: drupal password: ${env:POSTGRES_PASSWORD} ssl: true # Fallback to alternative if available fallback_db_provider: sqlite
Code Examples
Example 1: Simple Text Generation
<?php declare(strict_types=1); use Drupal\ai\AiProviderInterface; // Get default AI provider $provider = \Drupal::service('ai.provider') ->getProvider('default'); // Generate text $response = $provider->generate( 'Write a product description for a ceramic mug.' ); // Output response echo $response->getText(); echo "Tokens used: " . $response->getInputTokens() + $response->getOutputTokens();
Example 2: Create Custom Automator Plugin
<?php declare(strict_types=1); namespace Drupal\my_module\Plugin\AiAutomator; use Drupal\ai\Plugin\AiAutomator\AiAutomatorBase; use Drupal\node\NodeInterface; /** * Generates product tags via AI. * * @AiAutomator( * id = "product_tag_generator", * label = @Translation("Product Tag Generator"), * entity_type = "node", * target_field = "field_tags", * bundle = "product" * ) */ class ProductTagGenerator extends AiAutomatorBase { /** * Generate tags for product node. */ public function generate(NodeInterface $node): array { $prompt = "Generate 5 relevant tags for: " . $node->getTitle(); $response = $this->provider->generate($prompt); // Parse response as CSV $tags = str_getcsv($response->getText()); // Load or create term entities $term_ids = []; foreach ($tags as $tag) { $term = $this->getOrCreateTerm( trim($tag), 'product_tags' ); $term_ids[] = $term->id(); } return $term_ids; } /** * Get or create taxonomy term. */ private function getOrCreateTerm( string $name, string $vocabulary ) { $storage = \Drupal::entityTypeManager() ->getStorage('taxonomy_term'); // Search existing $terms = $storage->loadByProperties([ 'name' => $name, 'vid' => $vocabulary, ]); if (!empty($terms)) { return reset($terms); } // Create new $term = $storage->create([ 'name' => $name, 'vid' => $vocabulary, ]); $term->save(); return $term; } }
Example 3: Implement Content Enrichment Service
<?php declare(strict_types=1); namespace Drupal\my_module\Service; use Drupal\ai\AiProviderInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; /** * Enriches content with AI-generated metadata. */ class ContentEnricher { private AiProviderInterface $provider; private EntityTypeManagerInterface $entityTypeManager; /** * Constructor. */ public function __construct( AiProviderInterface $provider, EntityTypeManagerInterface $entity_type_manager ) { $this->provider = $provider; $this->entityTypeManager = $entity_type_manager; } /** * Enrich node with AI-generated metadata. */ public function enrichNode(\Drupal\node\NodeInterface $node): void { // Generate summary $summary = $this->generateSummary($node); $node->set('field_summary', $summary); // Generate keywords $keywords = $this->generateKeywords($node); $node->set('field_keywords', $keywords); // Suggest category $category = $this->suggestCategory($node); $node->set('field_category', $category); $node->save(); } /** * Generate content summary. */ private function generateSummary( \Drupal\node\NodeInterface $node ): string { $body = $node->body->value ?? ''; if (empty($body)) { return ''; } $response = $this->provider->generate( "Summarize in 100 words: " . $body, ['max_tokens' => 150] ); return $response->getText(); } /** * Generate SEO keywords. */ private function generateKeywords( \Drupal\node\NodeInterface $node ): string { $title = $node->getTitle(); $body = substr($node->body->value ?? '', 0, 1000); $response = $this->provider->generate( "Generate 10 SEO keywords for: $title\n\n$body", ['max_tokens' => 100] ); return $response->getText(); } /** * Suggest content category. */ private function suggestCategory( \Drupal\node\NodeInterface $node ): string { $title = $node->getTitle(); $body = substr($node->body->value ?? '', 0, 500); $categories = [ 'Technology', 'Business', 'Health', 'Entertainment', 'Sports', ]; $prompt = "Categorize as one of: " . implode(', ', $categories) . "\n\n$title\n\n$body"; $response = $this->provider->generate($prompt); // Extract first matching category foreach ($categories as $cat) { if (str_contains($response->getText(), $cat)) { return $cat; } } return 'Technology'; // Default } }
Standardized Code Examples
All code examples follow Drupal coding standards from STANDARDS-VALIDATION-CHECKLIST.md:
declare(strict_types=1); at file start
Type hints on all parameters and return types
No \Drupal:: static calls (dependency injection only)
Lines 80 characters (wrapped as needed)
DocBlocks for classes, methods with @param and @return
Proper namespacing and alphabetically sorted use statements
No TODOs, HACKs, or temporary comments
Performance Best Practices
1. Token Usage Optimization
- Set
max_tokensto minimize output - Use cheaper models for classification tasks
- Cache responses when possible
- Batch similar prompts together
2. Rate Limiting
- Implement exponential backoff for retries
- Queue long-running requests
- Monitor per-user quotas
- Set provider-specific limits
3. Caching Strategy
// Cache successful responses $cache_key = 'ai_response:' . md5($prompt); $ttl = 24 * 3600; // 24 hours if ($cached = \Drupal::cache('ai')->get($cache_key)) { return $cached->data; } $response = $provider->generate($prompt); \Drupal::cache('ai')->set($cache_key, $response, $ttl);
4. Async Processing
// Queue heavy operations $queue = \Drupal::queue('ai_batch_processing'); foreach ($items as $item) { $queue->createItem([ 'operation' => 'generate_summary', 'entity_id' => $item->id(), ]); }
Security Considerations
API Key Management
- Use Key module for all credentials
- Restrict key access via permissions
- Rotate keys regularly
- Monitor unauthorized access attempts
Input Validation
// Always validate/sanitize prompts $user_input = \Drupal::request()->query->get('q'); $prompt = htmlspecialchars($user_input, ENT_QUOTES); $prompt = truncate_html($prompt, 1000); // Prevent prompt injection $safe_prompt = preg_replace( '/[^a-zA-Z0-9\s\.\,\!\?]/i', '', $prompt );
Output Handling
// Sanitize AI responses before display $response_text = $provider->generate($prompt)->getText(); // Filter HTML/scripts $safe_text = filter_xss($response_text); // For rich content $safe_html = check_markup( $response_text, 'basic_html' );
Support & Resources
Official Documentation: https://www.drupal.org/docs/contributed-modules/ai Project Page: https://www.drupal.org/project/ai Issue Queue: https://www.drupal.org/project/issues/ai Slack Channel: #ai-module (Drupal Slack) Provider Docs:
Last Updated: 2026-01-08 Version: 1.0 Author: Agent 6 - drupal/ai Deep Researcher