Skip to main content

drupal ai agents plugin guide

drupal/ai_agents Plugin Development Guide

Overview

The drupal/ai_agents module is a comprehensive framework for creating intelligent agents within Drupal that can perform text-to-action operations. It enables developers to build "agents of all sorts, including text-to-action agents that can manipulate your actual Drupal configurations or content based on textual or multimodal instructions."

The module leverages Drupal's plugin system and integrates with the drupal/ai module's provider abstraction layer, allowing agents to communicate with 48+ AI platforms including Anthropic, OpenAI, Google Gemini, AWS Bedrock, and more.

Architecture

High-Level Architecture

The drupal/ai_agents module follows a layered architecture:


‚   User Interface Layer                          ‚
‚   (Chatbot, Admin UI, Forms)                   ‚
˜
               ‚
¼
‚   Agent Framework Layer                         ‚
‚   - Agent Plugins                               ‚
‚   - Tool Management                             ‚
‚   - Agent Lifecycle                             ‚
˜
               ‚
¼
‚   Tool Execution Layer                          ‚
‚   - Tool Plugins                                ‚
‚   - Natural Language Processing                 ‚
‚   - Action Execution                            ‚
˜
               ‚
¼
‚   AI Provider Integration Layer                 ‚
‚   (drupal/ai module)                            ‚
‚   - Provider Abstraction                        ‚
‚   - Model Management                            ‚
‚   - Token Handling                              ‚
˜
               ‚
¼
‚   External AI Services                          ‚
‚   (OpenAI, Anthropic, Google, AWS, etc.)       ‚
˜

Plugin System

The drupal/ai_agents module uses Drupal's plugin system with two primary plugin types:

  1. Agent Plugins: Represent AI agents that can perform specific tasks
  2. Tool Plugins: Represent specific actions/tools that agents can execute

Plugin Discovery

Plugins are discovered using:

  • Attributes (modern approach, Drupal 10.2+): PHP attributes that register plugins
  • Annotations (legacy approach, pre-Drupal 10.2): Annotation classes for plugin registration

Plugin Manager

The plugin manager orchestrates plugin discovery and instantiation:

/** * Service container provides the plugin manager */ \Drupal::service('plugin.manager.ai_agents.agent') \Drupal::service('plugin.manager.ai_agents.tool')

Configuration System

The module uses Drupal's Configuration API with YAML schema definitions for:

  • Agent definitions
  • Tool configurations
  • Provider settings
  • Context and constraints

Built-in Agents

The drupal/ai_agents module comes with three pre-installed agents:

1. Field Type Agent

Purpose: Creates, edits, and answers questions about fields on fieldable entities

Capabilities:

  • Discover existing fields on entities
  • Create new fields based on natural language descriptions
  • Modify field properties and settings
  • Answer questions about field configurations

Configuration:

  • Entity type selection
  • Bundle (content type, taxonomy vocabulary) selection
  • Field operation scope

Example Usage:

"Create a text field called 'Product Description' on Article content type"
"What fields does the Product node type have?"
"Add a price field to the Product content type"

2. Content Type Agent

Purpose: Manages node type (content type) creation, editing, and inquiries

Capabilities:

  • Create new content types with specified properties
  • Edit existing content type settings
  • Answer questions about content type structure
  • Manage content type permissions

Configuration:

  • Content type naming conventions
  • Field association policies
  • Permission templates

Example Usage:

"Create a new content type called Blog Post"
"What content types exist on this site?"
"Add workflow states to the Article content type"

3. Taxonomy Agent

Purpose: Handles vocabulary and taxonomy term operations

Capabilities:

  • Create and manage taxonomies (vocabularies)
  • Add taxonomy terms
  • Organize taxonomy hierarchies
  • Query taxonomy information

Configuration:

  • Vocabulary namespacing
  • Term weight management
  • Hierarchy depth constraints

Example Usage:

"Create a Tags vocabulary"
"Add a 'Featured' term to the Categories taxonomy"
"What are all the tags on this site?"

Creating Custom Agents

Step 1: Create a New Module

First, create a custom module to house your agent. Follow standard Drupal module structure:

my_custom_agent/
 src/
‚    Plugin/
‚   ‚    AiAgent/
‚   ‚        MyCustomAgent.php
‚    Service/
‚        MyCustomAgentService.php
 config/
‚    install/
‚        my_custom_agent.settings.yml
 my_custom_agent.info.yml
 my_custom_agent.module
 my_custom_agent.services.yml

Step 2: Create Module Info File

Create my_custom_agent.info.yml:

name: 'My Custom Agent' description: 'Custom AI agent for specific domain tasks' type: module core_version_requirement: '>=10.0' dependencies: - drupal:core - ai_agents:ai_agents - ai:ai version: 1.0.0

Step 3: Define Services

Create my_custom_agent.services.yml:

services: my_custom_agent.agent_service: class: Drupal\my_custom_agent\Service\MyCustomAgentService arguments: - '@plugin.manager.ai_agents.agent' - '@plugin.manager.ai_agents.tool' - '@config.factory' - '@logger.channel.my_custom_agent'

Step 4: Create the Agent Plugin

Create src/Plugin/AiAgent/MyCustomAgent.php:

<?php namespace Drupal\my_custom_agent\Plugin\AiAgent; use Drupal\ai_agents\Attribute\AiAgent; use Drupal\ai_agents\Plugin\AiAgentBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Custom AI Agent for domain-specific tasks. */ #[AiAgent( id: 'my_custom_agent', label: 'My Custom Agent', description: 'An AI agent that performs custom tasks', provider: 'anthropic', // Default provider )] class MyCustomAgent extends AiAgentBase { use StringTranslationTrait; /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = parent::buildConfigurationForm($form, $form_state); // Add custom configuration options $form['custom_setting'] = [ '#type' => 'textfield', '#title' => $this->t('Custom Setting'), '#default_value' => $this->configuration['custom_setting'] ?? '', '#required' => TRUE, ]; return $form; } /** * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); $this->configuration['custom_setting'] = $form_state->getValue('custom_setting'); } /** * {@inheritdoc} */ public function getAvailableTools() { $tools = []; // Define which tools this agent can use $tools[] = 'my_custom_agent_tool_1'; $tools[] = 'my_custom_agent_tool_2'; return $tools; } /** * {@inheritdoc} */ public function getAgentPrompt() { return $this->t("You are a helpful AI assistant specializing in custom domain tasks. You can help users by executing available tools to accomplish their goals. Always provide clear explanations of what you're doing and confirm actions with users."); } /** * {@inheritdoc} */ public function validateConfiguration() { $errors = []; // Add custom validation logic if (empty($this->configuration['custom_setting'])) { $errors[] = $this->t('Custom setting is required'); } return $errors; } }

Step 5: Create Tool Plugins

Tools are the actual capabilities that agents can execute. Create src/Plugin/AiAgentTool/MyCustomTool.php:

<?php namespace Drupal\my_custom_agent\Plugin\AiAgentTool; use Drupal\ai_agents\Attribute\AiAgentTool; use Drupal\ai_agents\Plugin\AiAgentToolBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Custom tool for AI agents. */ #[AiAgentTool( id: 'my_custom_agent_tool_1', label: 'Custom Tool', description: 'Performs a specific custom action', agent_types: ['my_custom_agent'], )] class MyCustomTool extends AiAgentToolBase { use StringTranslationTrait; /** * {@inheritdoc} */ public function getToolDefinition() { return [ 'name' => 'custom_tool', 'description' => 'Performs a custom action based on natural language input', 'parameters' => [ 'type' => 'object', 'properties' => [ 'action' => [ 'type' => 'string', 'description' => 'The specific action to perform', ], 'context' => [ 'type' => 'string', 'description' => 'Additional context for the action', ], ], 'required' => ['action'], ], ]; } /** * {@inheritdoc} */ public function execute(array $parameters = []) { $action = $parameters['action'] ?? ''; $context = $parameters['context'] ?? ''; // Implement the actual tool logic try { $result = $this->performCustomAction($action, $context); return [ 'success' => TRUE, 'data' => $result, 'message' => $this->t('Action completed successfully'), ]; } catch (\Exception $e) { return [ 'success' => FALSE, 'message' => $this->t('Error executing tool: @error', ['@error' => $e->getMessage()]), ]; } } /** * {@inheritdoc} */ public function validateParameters(array $parameters) { $errors = []; if (empty($parameters['action'])) { $errors[] = $this->t('Action parameter is required'); } return $errors; } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = parent::buildConfigurationForm($form, $form_state); $form['tool_option'] = [ '#type' => 'select', '#title' => $this->t('Tool Option'), '#options' => [ 'option1' => $this->t('Option 1'), 'option2' => $this->t('Option 2'), ], '#default_value' => $this->configuration['tool_option'] ?? 'option1', ]; return $form; } /** * Performs the custom action. * * @param string $action * The action to perform. * @param string $context * Additional context. * * @return array * The action result. */ private function performCustomAction($action, $context) { // Implementation details return [ 'action_performed' => $action, 'result' => 'Success', ]; } }

Tool-Calling Patterns

Understanding Tool Calls

Tool calling is the mechanism by which agents execute specific actions. The flow works as follows:

1. User Input (Natural Language)
        †
2. Agent receives input with available tools
        †
3. AI Provider processes request and suggests tool call
        †
4. Tool Execution Layer parses tool call
        †
5. Tool Plugin executes the action
        †
6. Result returned to Agent
        †
7. Agent incorporates result into response
        †
8. Response returned to User

Tool Definition Schema

Tools must define their interface for the AI provider:

public function getToolDefinition() { return [ 'name' => 'tool_name', 'description' => 'What this tool does', 'parameters' => [ 'type' => 'object', 'properties' => [ 'param1' => [ 'type' => 'string', 'description' => 'Description of param1', ], 'param2' => [ 'type' => 'number', 'description' => 'Description of param2', ], ], 'required' => ['param1'], // At least one parameter typically required ], ]; }

Parameter Types

Common parameter types for tools:

TypeDescriptionExample
stringText parameterEntity name, query
numberNumeric parameterCount, ID, weight
booleanTrue/false optionEnable/disable flag
arrayList of valuesTags, IDs, options
objectComplex nested dataConfiguration object

Tool Execution Best Practices

  1. Always Validate Input:

    public function execute(array $parameters = []) { $errors = $this->validateParameters($parameters); if (!empty($errors)) { return ['success' => FALSE, 'errors' => $errors]; } // Execute... }
  2. Handle Errors Gracefully:

    try { // Perform action } catch (\Exception $e) { return [ 'success' => FALSE, 'message' => 'Human-readable error message', 'error_code' => get_class($e), ]; }
  3. Return Structured Results:

    return [ 'success' => TRUE|FALSE, 'data' => [...], // The actual result 'message' => 'User-facing message', 'metadata' => [...], // Additional context ];
  4. Respect Permissions:

    if (!$user->hasPermission('required_permission')) { return [ 'success' => FALSE, 'message' => 'You do not have permission to perform this action', ]; }

Integration with drupal/ai

AI Provider Integration

The drupal/ai_agents module integrates with drupal/ai's provider abstraction layer:

// Get AI provider service $ai_provider = \Drupal::service('ai.provider'); // Get available providers $providers = $ai_provider->getProviders(); // Use a specific provider $response = $ai_provider->sendMessage( $prompt, $provider_id, // e.g., 'anthropic', 'openai' $options // Configuration options );

Supported Providers

The drupal/ai module supports 48+ AI platforms:

  • Large Language Models: Anthropic Claude, OpenAI GPT, Google Gemini, AWS Bedrock, Azure OpenAI, Hugging Face, llama2
  • Vector Databases: Milvus, Pinecone, Postgres pgvector, Azure AI Search, SQLite
  • Emerging Platforms: Amazee.io custom models

Provider Configuration

Configure providers in Drupal settings:

// In settings.php or through UI $config['ai.settings']['default_provider'] = 'anthropic'; $config['ai.provider.anthropic'] = [ 'api_key' => 'your-api-key', 'model' => 'claude-3-sonnet', ];

Using Providers in Agents

class MyAgent extends AiAgentBase { protected function callAiProvider($prompt, array $tools = []) { $ai_provider = \Drupal::service('ai.provider'); $response = $ai_provider->sendMessage( $prompt, $this->configuration['provider'], [ 'tools' => $tools, 'temperature' => 0.7, 'max_tokens' => 2000, ] ); return $response; } }

Agent Lifecycle Management

Agent State Management

Agents maintain state during conversations:

/** * Get chat history for the agent. */ public function getChatHistory() { return \Drupal::service('ai_agents.chat_history') ->getHistory($this->getPluginId()); } /** * Set chat history for context preservation. */ public function setChatHistory(array $history) { \Drupal::service('ai_agents.chat_history') ->setHistory($this->getPluginId(), $history); }

Agent Context

Define context that the agent should consider:

#[AiAgent( id: 'my_agent', label: 'My Agent', description: 'Custom agent', context_definitions: [ 'user' => 'current_user', 'language' => 'current_language', 'site' => 'site_configuration', ], )] class MyAgent extends AiAgentBase { public function getContextualInformation() { $context = []; // Include current user info $context['user'] = \Drupal::currentUser()->getEmail(); // Include language $context['language'] = \Drupal::languageManager() ->getCurrentLanguage() ->getId(); // Include site name $context['site_name'] = \Drupal::config('system.site')->get('name'); return $context; } }

Tool Availability Management

Dynamically control which tools are available:

public function getAvailableTools() { $user = \Drupal::currentUser(); $tools = []; // Base tools available to everyone $tools[] = 'search_tool'; // Permission-based tool availability if ($user->hasPermission('create content')) { $tools[] = 'content_creation_tool'; } if ($user->hasPermission('manage users')) { $tools[] = 'user_management_tool'; } return $tools; } public function getToolRestrictions() { return [ 'content_creation_tool' => [ 'require_permission' => 'create content', 'max_daily_uses' => 100, ], 'user_management_tool' => [ 'require_permission' => 'administer users', 'require_confirmation' => TRUE, ], ]; }

Natural Language Processing & Action Execution

Text-to-Action Flow

The drupal/ai_agents module converts natural language to actions:

User: "Create a new Article with the title 'Hello World'"
         †
     [NLP Parser]
         †
 Identified Actions:
  - Entity: node
  - Bundle: article
  - Operation: create
  - Fields: {title: "Hello World"}
         †
  [Permission Check]
         †
  [Action Execution]
         †
 Result: "Article created with ID 123"

Command Parsing

Tools help the AI understand what actions are available:

public function getToolDefinition() { return [ 'name' => 'create_node', 'description' => 'Create a new node (content)', 'parameters' => [ 'type' => 'object', 'properties' => [ 'type' => [ 'type' => 'string', 'description' => 'The node type (e.g., article, page)', ], 'title' => [ 'type' => 'string', 'description' => 'The node title', ], 'body' => [ 'type' => 'string', 'description' => 'The node body content', ], 'status' => [ 'type' => 'boolean', 'description' => 'Whether to publish the node immediately', ], ], 'required' => ['type', 'title'], ], ]; }

Action Execution

Execute the parsed action:

public function execute(array $parameters = []) { // Create the node $node = \Drupal::entityTypeManager() ->getStorage('node') ->create([ 'type' => $parameters['type'], 'title' => $parameters['title'], 'body' => $parameters['body'] ?? '', 'status' => $parameters['status'] ?? TRUE, ]); try { $node->save(); return [ 'success' => TRUE, 'data' => [ 'node_id' => $node->id(), 'url' => $node->toUrl()->toString(), ], 'message' => "Node created successfully (ID: {$node->id()})", ]; } catch (\Exception $e) { return [ 'success' => FALSE, 'message' => "Failed to create node: {$e->getMessage()}", ]; } }

Configuration Schema

Config File Structure

Define configuration schema in config/schema/my_custom_agent.schema.yml:

my_custom_agent.settings: type: config_object label: 'My Custom Agent settings' mapping: agent_config: type: mapping label: 'Agent configuration' mapping: default_provider: type: string label: 'Default AI provider' model: type: string label: 'AI model to use' temperature: type: float label: 'Temperature setting' max_tokens: type: integer label: 'Maximum tokens' tool_config: type: sequence label: 'Tool configurations' sequence: - type: mapping mapping: tool_id: type: string label: 'Tool ID' enabled: type: boolean label: 'Tool enabled' max_uses_per_day: type: integer label: 'Maximum uses per day'

Accessing Configuration

// Get configuration $config = \Drupal::config('my_custom_agent.settings'); $provider = $config->get('agent_config.default_provider'); // Set configuration $config->set('agent_config.default_provider', 'openai') ->save(); // Use in agent class MyAgent extends AiAgentBase { public function getAgentProvider() { return \Drupal::config('my_custom_agent.settings') ->get('agent_config.default_provider'); } }

Best Practices

1. Security and Permissions

Always check permissions before executing tools:

public function execute(array $parameters = []) { if (!$this->currentUser->hasPermission('required_permission')) { return [ 'success' => FALSE, 'message' => 'You do not have permission for this action', ]; } // Execute action }

2. Error Handling and Logging

Implement robust error handling:

try { // Execute action } catch (\Drupal\Core\Entity\EntityStorageException $e) { $this->logger->error('Entity storage error: @message', [ '@message' => $e->getMessage(), ]); return ['success' => FALSE]; } catch (\Exception $e) { $this->logger->error('Unexpected error: @message', [ '@message' => $e->getMessage(), ]); }

3. Rate Limiting and Throttling

Prevent abuse by implementing rate limits:

private function checkRateLimit($tool_id) { $key = "ai_agents:$tool_id:" . \Drupal::currentUser()->id(); $uses = \Drupal::service('tempstore.private') ->get('ai_agents') ->get($key); if ($uses >= $max_daily_uses) { return FALSE; } return TRUE; }

4. Validation and Sanitization

Always validate and sanitize input:

public function validateParameters(array $parameters) { $errors = []; if (empty($parameters['title'])) { $errors[] = 'Title is required'; } if (strlen($parameters['title']) > 255) { $errors[] = 'Title must be less than 255 characters'; } // Sanitize input $parameters['title'] = \Drupal\Component\Utility\Html::escape( $parameters['title'] ); return $errors; }

5. Testing Agents and Tools

Create tests for custom agents:

<?php namespace Drupal\Tests\my_custom_agent\Unit\Plugin\AiAgent; use Drupal\Tests\UnitTestCase; use Drupal\my_custom_agent\Plugin\AiAgent\MyCustomAgent; class MyCustomAgentTest extends UnitTestCase { public function testAgentInstantiation() { $agent = new MyCustomAgent([], 'my_agent', []); $this->assertInstanceOf(MyCustomAgent::class, $agent); } public function testToolDefinition() { // Test tool definition schema } public function testToolExecution() { // Test tool execution with various inputs } }

6. Documentation

Document your agent and tools:

/** * Custom agent for managing blog posts. * * This agent can: * - Create new blog posts * - Edit existing posts * - Publish/unpublish posts * - Query posts by various criteria * * @usage * "Create a new blog post titled 'My Blog Post'" * "Edit the blog post with ID 123" * "List all published blog posts" */ #[AiAgent( id: 'blog_post_agent', label: 'Blog Post Agent', description: 'Manages blog posts through natural language commands', )] class BlogPostAgent extends AiAgentBase { // ... }

7. Performance Optimization

Optimize for performance:

// Cache tool definitions $cache_key = 'ai_agents:tools:' . $this->getPluginId(); $cached = \Drupal::cache() ->get($cache_key); if ($cached) { return $cached->data; } $tools = $this->getAvailableTools(); \Drupal::cache() ->set($cache_key, $tools, time() + 3600);

8. AI Agents & Evaluations Recipe

The module can be distributed through recipes combined with the AI Agents & Evaluations Recipe for structured evaluation:

# recipe.yml name: 'My Custom Agent Recipe' description: 'Complete setup for custom agent with evaluation' modules: - ai_agents - my_custom_agent config: - ai_agents_recipes:agent_setup

Common Patterns from Issue Queue

Pattern 1: Dynamic Tool Overriding

Version 1.1.0+ allows overriding available tools:

public function overrideTools(array $tools) { // Filter or modify tools based on context return array_filter($tools, function($tool) { return $this->isToolAllowed($tool); }); }

Pattern 2: Tool Usage Restrictions

Implement restrictions on tool usage:

public function getToolRestrictions() { return [ 'dangerous_tool' => [ 'require_confirmation' => TRUE, 'require_admin_approval' => TRUE, 'log_usage' => TRUE, ], ]; }

Pattern 3: Context-Aware Execution

Use context to modify execution:

public function execute(array $parameters = []) { $context = $this->getContextualInformation(); if ($context['language'] === 'es') { $parameters['language'] = 'es'; } // Execute with context }

Pattern 4: Multi-Step Tool Chains

Allow agents to chain multiple tool calls:

public function executeChain(array $tool_calls = []) { $results = []; foreach ($tool_calls as $call) { $result = $this->executeTool( $call['tool_id'], $call['parameters'] ); $results[] = $result; // Stop if a step fails if (!$result['success']) { break; } } return $results; }

Example: Complete Blog Post Agent

Full Working Example

<?php namespace Drupal\blog_post_agent\Plugin\AiAgent; use Drupal\ai_agents\Attribute\AiAgent; use Drupal\ai_agents\Plugin\AiAgentBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\node\Entity\Node; /** * AI Agent for managing blog posts. * * This agent enables natural language management of blog posts including: * - Creating new articles * - Editing existing articles * - Publishing/unpublishing content * - Querying articles with filters */ #[AiAgent( id: 'blog_post_agent', label: 'Blog Post Manager', description: 'Create, edit, and manage blog posts through natural language', provider: 'anthropic', )] class BlogPostAgent extends AiAgentBase { use StringTranslationTrait; /** * {@inheritdoc} */ public function getAvailableTools() { return [ 'blog_create_post', 'blog_edit_post', 'blog_publish_post', 'blog_unpublish_post', 'blog_list_posts', 'blog_delete_post', ]; } /** * {@inheritdoc} */ public function getAgentPrompt() { return $this->t("You are a helpful blog post management assistant. You can help users create, edit, publish, and manage blog posts. Always confirm significant actions with users before executing them. Be clear about what you're doing and provide useful feedback."); } /** * {@inheritdoc} */ public function getToolRestrictions() { return [ 'blog_delete_post' => [ 'require_confirmation' => TRUE, 'require_permission' => 'delete any article content', ], 'blog_publish_post' => [ 'require_permission' => 'access content overview', ], ]; } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = parent::buildConfigurationForm($form, $form_state); $form['blog_category'] = [ '#type' => 'textfield', '#title' => $this->t('Default Blog Category'), '#description' => $this->t('Default taxonomy term for new blog posts'), '#default_value' => $this->configuration['blog_category'] ?? '', ]; $form['default_author'] = [ '#type' => 'checkbox', '#title' => $this->t('Assign to Current User'), '#description' => $this->t('Automatically assign created posts to current user'), '#default_value' => $this->configuration['default_author'] ?? TRUE, ]; return $form; } /** * {@inheritdoc} */ public function validateConfiguration() { $errors = []; // Add validation as needed return $errors; } }

Tool implementation:

<?php namespace Drupal\blog_post_agent\Plugin\AiAgentTool; use Drupal\ai_agents\Attribute\AiAgentTool; use Drupal\ai_agents\Plugin\AiAgentToolBase; use Drupal\node\Entity\Node; /** * Tool for creating blog posts. */ #[AiAgentTool( id: 'blog_create_post', label: 'Create Blog Post', description: 'Create a new blog post', agent_types: ['blog_post_agent'], )] class CreateBlogPostTool extends AiAgentToolBase { /** * {@inheritdoc} */ public function getToolDefinition() { return [ 'name' => 'create_blog_post', 'description' => 'Create a new blog post article', 'parameters' => [ 'type' => 'object', 'properties' => [ 'title' => [ 'type' => 'string', 'description' => 'The blog post title (required)', ], 'body' => [ 'type' => 'string', 'description' => 'The blog post content', ], 'tags' => [ 'type' => 'array', 'description' => 'Tags for the post', 'items' => ['type' => 'string'], ], 'published' => [ 'type' => 'boolean', 'description' => 'Whether to publish immediately', ], ], 'required' => ['title', 'body'], ], ]; } /** * {@inheritdoc} */ public function execute(array $parameters = []) { $errors = $this->validateParameters($parameters); if (!empty($errors)) { return ['success' => FALSE, 'message' => implode(', ', $errors)]; } try { $node = Node::create([ 'type' => 'article', 'title' => $parameters['title'], 'body' => [ 'value' => $parameters['body'], 'format' => 'full_html', ], 'status' => $parameters['published'] ?? TRUE, 'uid' => \Drupal::currentUser()->id(), ]); $node->save(); return [ 'success' => TRUE, 'data' => [ 'node_id' => $node->id(), 'title' => $node->getTitle(), 'url' => $node->toUrl()->toString(), ], 'message' => "Blog post created successfully: {$node->getTitle()}", ]; } catch (\Exception $e) { \Drupal::logger('blog_post_agent') ->error('Failed to create blog post: @error', [ '@error' => $e->getMessage(), ]); return [ 'success' => FALSE, 'message' => "Failed to create blog post: {$e->getMessage()}", ]; } } /** * {@inheritdoc} */ public function validateParameters(array $parameters) { $errors = []; if (empty($parameters['title'])) { $errors[] = 'Title is required'; } if (strlen($parameters['title'] ?? '') > 255) { $errors[] = 'Title must be less than 255 characters'; } if (empty($parameters['body'])) { $errors[] = 'Body content is required'; } if (!$this->currentUser->hasPermission('create article content')) { $errors[] = 'You do not have permission to create blog posts'; } return $errors; } }

References and Resources

Version Notes

This guide covers drupal/ai_agents versions 1.0.0 and later, with specific features noted for version 1.1.0+ improvements:

  • v1.0.0: Initial stable release
  • v1.1.0: Added getChatHistory/setChatHistory, tool overriding, and configurable contexts
  • v1.2.0: Bug fixes and stability improvements
  • 1.3.x-dev: Current development branch with new features

Troubleshooting

Plugin Not Discovered

If your agent plugin isn't discovered:

  1. Clear Drupal caches: drush cache:rebuild
  2. Verify module is enabled: drush en my_custom_agent
  3. Check annotation syntax
  4. Verify plugin ID is unique

Tool Execution Failures

Check:

  1. Required permissions are granted
  2. Input parameters match schema
  3. Error logs: /admin/reports/dblog
  4. Drupal logs: logs/drupal.log

Configuration Not Loading

Ensure:

  1. Schema file is in config/schema/
  2. Config file naming matches pattern
  3. Run drush cache:rebuild

Document Version: 1.0 Last Updated: January 8, 2026 Maintainer: Agent 7 - drupal/ai_agents Module Deep Researcher