Skip to main content

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 service
  • ai.provider.manager: Plugin manager for all providers
  • ai.embeddings.provider: Embeddings-specific provider
  • ai.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:

  1. Postgres - pgvector extension (recommended for self-hosted)
  2. Milvus - Distributed vector DB
  3. Pinecone - Cloud-native vector service
  4. Azure - Azure Search with vector capabilities
  5. 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:

  1. Content generated by provider (e.g., Anthropic Claude)
  2. Content passed to OpenAI moderation service
  3. Results scored across 7 categories
  4. Actions taken based on configuration
  5. 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

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_tokens to 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