Skip to content

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 a ChatOutput
  • defaultConfiguration(): Default configuration values (from ConfigurableInterface)
  • buildConfigurationForm(): Configuration form (from PluginFormInterface)

3. Available Methods

The ChatProcessorBase class provides these methods:

Input/Output Management

  • setInput(ChatInput $input): Set the chat input
  • getInput(): ?ChatInput: Get the chat input
  • setOutput(ChatOutput $output): Set the output
  • getOutput(): ?ChatOutput: Get the output

Thread Management

  • setThreadId(string $threadId): Set thread ID for conversation continuity
  • getThreadId(): ?string: Get thread ID

Execution State

  • setFinished(bool $finished): Set execution finished state
  • getFinished(): bool: Get execution finished state

File Handling

  • setInputFiles(array $files): Set non-image files for processing
  • getInputFiles(): array: Get non-image files
  • allowedFileExtensions(): 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);
}
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 ChatOutput object
  • Check that the input has streaming enabled: $input->setStreamedOutput(TRUE)
  • Ensure your AI provider supports streaming
  • Return a StreamedChatMessageIterator for streaming responses

Type Errors

  • Ensure you're using ChatInput, ChatOutput, and ChatMessage from Drupal\ai\OperationType\Chat
  • Check that your doExecute() method returns a ChatOutput instance
  • Verify dependency injection is correctly configured in create() and __construct()

Best Practices

  1. Use Dependency Injection: Inject services through the constructor for better testability
  2. Handle Errors Gracefully: Catch exceptions and return meaningful error messages
  3. Implement Configuration: Provide sensible defaults and clear form labels
  4. Document Your Plugin: Add docblocks explaining what your plugin does
  5. Test Thoroughly: Test with various input types, including edge cases
  6. Support Streaming: Consider implementing streaming for better UX with long responses
  7. Validate Input: Check that required data is present before processing

See Also

This document was generated with AI assistance.