Writing a ChatProcessor Plugin
The AI module provides a plugin system for processing chat input and generating responses through ChatProcessor plugins. This allows developers to create custom chatbot behaviors, implement features like RAG (Retrieval-Augmented Generation), integrate custom AI services, or create specialized processing workflows.
Overview
ChatProcessor plugins decouple any chatbot UI from any processing logic. The AI module provides the plugin infrastructure in the core module, while the AI Chatbot submodule provides a reference implementation for using these plugins in a chatbot interface.
The plugin system is built on Drupal's plugin system and uses PHP 8 attributes for plugin discovery.
Creating a ChatProcessor Plugin
1. Create the Plugin Class
Create a new PHP class in src/Plugin/ChatProcessor/ that extends ChatProcessorBase:
<?php
namespace Drupal\your_module\Plugin\ChatProcessor;
use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\ai\OperationType\Chat\ChatOutput;
use Drupal\ai\Attribute\ChatProcessor;
use Drupal\ai\Base\ChatProcessorBase;
use Drupal\ai\Plugin\ChatProcessor\ChatProcessorInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
#[ChatProcessor(
id: 'your_plugin_id',
label: new TranslatableMarkup('Your Plugin Label'),
description: new TranslatableMarkup('Description of what your plugin does.')
)]
class YourPlugin extends ChatProcessorBase implements ChatProcessorInterface {
/**
* {@inheritdoc}
*/
public function doExecute(): ChatOutput {
$input = $this->getInput();
// Your processing logic here
$responseMessage = new ChatMessage('assistant', 'Your response');
return new ChatOutput($responseMessage, [], [], NULL);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'your_setting' => 'default_value',
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['your_setting'] = [
'#type' => 'textfield',
'#title' => $this->t('Your Setting'),
'#default_value' => $this->configuration['your_setting'],
];
return $form;
}
}
2. Required Methods
All ChatProcessor plugins must implement:
doExecute(): Main processing logic that returns aChatOutputdefaultConfiguration(): Default configuration values (fromConfigurableInterface)buildConfigurationForm(): Configuration form (fromPluginFormInterface)
3. Available Methods
The ChatProcessorBase class provides these methods:
Input/Output Management
setInput(ChatInput $input): Set the chat inputgetInput(): ?ChatInput: Get the chat inputsetOutput(ChatOutput $output): Set the outputgetOutput(): ?ChatOutput: Get the output
Thread Management
setThreadId(string $threadId): Set thread ID for conversation continuitygetThreadId(): ?string: Get thread ID
Execution State
setFinished(bool $finished): Set execution finished stategetFinished(): bool: Get execution finished state
File Handling
setInputFiles(array $files): Set non-image files for processinggetInputFiles(): array: Get non-image filesallowedFileExtensions(): array: Define allowed file extensions (override to customize)allowsImages(): bool: Define if images are allowed (override to customize)
4. File Handling
You can control what file types your plugin accepts:
/**
* {@inheritdoc}
*/
public function allowedFileExtensions(): array {
return ['pdf', 'doc', 'docx', 'txt'];
}
/**
* {@inheritdoc}
*/
public function allowsImages(): bool {
return TRUE; // or FALSE to disable image uploads
}
Built-in Plugins
There are two example implementations in ai_test module.
Using ChatProcessor Plugins
Programmatic Usage
// Get the plugin manager
$manager = \Drupal::service('plugin.manager.ai.chat_processor');
// Get available plugins
$plugins = $manager->getDefinitions();
// Create a plugin instance
$plugin = $manager->createInstance('your_plugin_id', [
'your_setting' => 'custom_value',
]);
// Set input
$messages = [
new ChatMessage('user', 'Hello, how can you help?'),
];
$input = new ChatInput($messages);
$plugin->setInput($input);
// Execute
$output = $plugin->execute();
// Get response
$response = $output->getNormalized();
echo $response->getText();
Examples
Simple Echo Plugin
public function doExecute(): ChatOutput {
$input = $this->getInput();
$messages = $input->getMessages();
$userMessage = '';
foreach ($messages as $message) {
if ($message instanceof ChatMessage && $message->getRole() === 'user') {
$userMessage = $message->getText();
break;
}
}
$responseMessage = new ChatMessage('assistant', "You said: " . $userMessage);
return new ChatOutput($responseMessage, [], [], NULL);
}
RAG Plugin with Custom Search
public function doExecute(): ChatOutput {
$input = $this->getInput();
$userMessage = $this->extractUserMessage($input);
// Perform custom search
$relevantContent = $this->searchContent($userMessage);
// Create enhanced input with context
$enhancedInput = $this->addContextToInput($input, $relevantContent);
// Process with AI provider
$aiProvider = \Drupal::service('ai.provider');
$response = $aiProvider->chat($enhancedInput, 'openai', 'gpt-4')->getNormalized();
return new ChatOutput($response, [], [], NULL);
}
Architecture
Class Hierarchy
ChatProcessorInterface (interface)
├── Extends: PluginFormInterface
├── Extends: ConfigurableInterface
└── ChatProcessorBase (abstract base class)
├── Extends: PluginBase
└── Your Plugin Implementation
Plugin Discovery
The ChatProcessorPluginManager uses PHP 8 attribute-based discovery to find plugins across all modules:
- Namespace:
Plugin/ChatProcessor - Attribute:
Drupal\ai\Attribute\ChatProcessor - Interface:
Drupal\ai\Plugin\ChatProcessor\ChatProcessorInterface
Troubleshooting
Plugin Not Found
- Ensure your plugin class is in the correct namespace:
Drupal\your_module\Plugin\ChatProcessor - Check that the
#[ChatProcessor]attribute is properly defined - Clear the plugin cache:
drush cache:rebuild
Configuration Issues
- Verify your configuration form is properly implemented
- Check the configuration schema matches your form structure
- Ensure
defaultConfiguration()returns an array with all expected keys
Streaming Issues
- Verify your plugin returns a
ChatOutputobject - Check that the input has streaming enabled:
$input->setStreamedOutput(TRUE) - Ensure your AI provider supports streaming
- Return a
StreamedChatMessageIteratorfor streaming responses
Type Errors
- Ensure you're using
ChatInput,ChatOutput, andChatMessagefromDrupal\ai\OperationType\Chat - Check that your
doExecute()method returns aChatOutputinstance - Verify dependency injection is correctly configured in
create()and__construct()
Best Practices
- Use Dependency Injection: Inject services through the constructor for better testability
- Handle Errors Gracefully: Catch exceptions and return meaningful error messages
- Implement Configuration: Provide sensible defaults and clear form labels
- Document Your Plugin: Add docblocks explaining what your plugin does
- Test Thoroughly: Test with various input types, including edge cases
- Support Streaming: Consider implementing streaming for better UX with long responses
- Validate Input: Check that required data is present before processing
See Also
- Base Calls - Using AI operations
- Chat Operations - Working with chat operations
- Streaming Chat - Implementing streaming
- AI Chatbot Module - Reference implementation
This document was generated with AI assistance.