Plugin-Entwicklungsleitfaden
Komplette Anleitung zur Entwicklung von benutzerdefinierten Plugins für Open Ticket AI, einschließlich Projektstruktur, Entry Points und Best Practices.
Plugin-Entwicklungsleitfaden
Erfahren Sie, wie Sie benutzerdefinierte Plugins erstellen, um die Funktionalität von Open Ticket AI zu erweitern.
Packaging & Namensregeln
Open Ticket AI findet Plugins durch ihren Python-Paketnamen und Projekt-Metadaten.
- Distribution/Projektname: muss mit
otai-beginnen. Dies entspricht demAppConfig.PLUGIN_NAME_PREFIX(otai-), den die Runtime verwendet, wenn Registry Keys berechnet werden. - Python-Paketname: verwenden Sie die gleichen Wörter wie den Distribution-Name, aber mit Unterstrichen (
otai_my_plugin). Der Loader konvertiert den Top-Level-Modulnamen automatisch zu kebab-case, sodassotai_my_pluginintern zuotai-my-pluginwird. - Registry Prefix: wenn ein Plugin Injectables registriert, entfernt es den globalen Prefix (
otai-) und behält den Rest (z.B.otai-my-plugin→my-plugin). Dieser Teil wird der Registry Key Prefix, wenn er mit dem Injectable-Namen kombiniert wird, wiemy-plugin:MyPipe.
Empfohlene Projektstruktur
otai-my-plugin/
├── pyproject.toml
├── src/
│ └── otai_my_plugin/
│ ├── __init__.py
│ ├── pipes/
│ │ └── my_pipe.py
│ ├── services/
│ │ └── my_service.py
│ └── plugin_factory.py
└── tests/
└── unit/
└── test_my_plugin.py
Entry Point Contract
Open Ticket AI lädt Plugins über die Entry-Point-Gruppe open_ticket_ai.plugins. Der Entry Point
muss auf eine Callable resolven, die eine AppConfig Instanz akzeptiert und ein Plugin zurückgibt. Subclassing von
Plugin erfüllt diesen Contract bereits, daher können Sie Ihre Subklasse direkt exponieren.
[project.entry-points."open_ticket_ai.plugins"]
my_plugin = "otai_my_plugin.plugin_factory:PluginFactory"
Warum die Klasse referenzieren?
- Der Loader ruft das Ziel wie eine Factory auf. Referenzieren der Subklasse macht die Wiring deklarativ – keine Wrapper-Funktion erforderlich.
- Der Klassenname verdeutlicht, dass die Instantiierung des Plugins Dependency Wiring involvieren kann (zum Beispiel,
Constructor-Parameter neben
AppConfigkönnen vom IoC Container injiziert werden).
Implementierung der PluginFactory Klasse
Alle Plugins inheriten von open_ticket_ai.core.plugins.plugin.Plugin. Die Basisklasse:
- Akzeptiert
AppConfig(injiziert vom Loader) in ihrem Constructor. - Nutzt den Top-Level-Modulnamen, um den Plugin-Namen und Registry Prefix zu inferieren.
- Ruft
_get_all_injectables()währendon_loadauf und registriert jedesInjectableautomatisch mit demComponentRegistryunter dem Pattern<plugin-prefix>:<injectable-registry-name>.
Der Separator zwischen Prefix und Injectable-Namen ist
AppConfig.REGISTRY_IDENTIFIER_SEPERATOR, der standardmäßig : ist.
Override _get_all_injectables() um jedes Injectable zurückzugeben, das Sie exponieren möchten. Sie sollten nicht
registry.register(...) selbst innerhalb on_load aufrufen; die Basisklasse tut dies für Sie.
# src/otai_my_plugin/plugin_factory.py
from open_ticket_ai.core.injectables.injectable import Injectable
from open_ticket_ai.core.plugins.plugin import Plugin
from otai_my_plugin.pipes.my_pipe import MyPipe
from otai_my_plugin.services.my_service import MyService
class PluginFactory(Plugin):
"""Erstellt die Plugin-Instanz und deklariert exportierte Injectables."""
def _get_all_injectables(self) -> list[tone[Injectable]]:
return [
MyPipe,
MyService,
]
Wenn der Loader PluginFactory instantiert, wird die Basisimplementierung:
- Den Registry Prefix berechnen (
my-pluginin diesem Beispiel). MyPipe.get_registry_name()undMyService.get_registry_name()aufrufen.- Jedes Injectable als
my-plugin:<registry-name>mit dem sharedComponentRegistryregistrieren.
Injectables zurückgeben vs. manuelle Registrierung
Vorherige Implementierungen benötigten einen setup(registry) Helper, der manuelle Registrierung durchführte. Mit
dem Factory Pattern oben geben Sie einfach die Liste der Injectables zurück und lassen die Basisklasse den Rest
handeln. Dies macht die Registrierung konsistent und stellt sicher, dass Registry Namen automatisch dem
prefix:Injectable Convention folgen.
Wenn Sie sich vom automatischen Behavior opt-out müssen – zum Beispiel, um zusätzliche Aliases zu registrieren – können Sie
noch innerhalb _get_all_injectables() auf die Registry zugreifen, indem Sie on_load override. Für typische
Use Cases ist die Rückgabe der Liste sufficient und preferred.
pyproject.toml Essentials
Stellen Sie sicher, dass die Metadaten mit den Namensregeln und dem Entry-Point Contract align:
[project]
name = "otai-my-plugin"
version = "0.1.0"
description = "Benutzerdefiniertes Plugin für Open Ticket AI"
requires-python = ">=3.13"
dependencies = [
"isOpen-ticket-ai>=1.0.0,<2.0.0",
]
[project.entry-points."open_ticket_ai.plugins"]
my_plugin = "otai_my_plugin.plugin_factory:PluginFactory"
Nutzen Sie den Distribution-Name (otai-my-plugin) um sowohl den Modul Prefix (otai_my_plugin) als auch
Registry Prefix (my-plugin) zu derivieren. Diese konsistent zu halten stellt sicher, dass der Loader Entry Points
korrekt resolves und predictable Registry Keys produziert.
Packaging, Distribution & Testing
uv build
uv publish
Installieren Sie Ihr Plugin in eine Open Ticket AI Umgebung mit:
uv pip install otai-my-plugin
Schreiben Sie Unit Tests neben Ihrem Plugin Code unter tests/unit/ und führen Sie sie mit
uv run -m pytest aus.
Commercial Plugins & Monetization
Open Ticket AI unterstützt Commercial Plugin Development vollständig mit kompletter Licensing Freedom. Sie können Plugins erstellen und verkaufen ohne Restriktionen vom Core Project.
Developer Freedom
- Choose your license: MIT, GPL, proprietary – Ihre Wahl
- Set your pricing: Free, paid, subscription, one-time – Sie entscheiden
- Support model: Community Support, Commercial Support, oder beide
- No revenue sharing: Keep 100% of your plugin sales
- No marketplace fees: Currently no official marketplace (coming soon)
Current Status & Future Plans
- Plugin Listings: Verfügbar auf der Documentation Site
- No Marketplace Yet: Es gibt aktuell keinen official Plugin Marketplace oder Store
- Future Plans: Eine dedicated Plugins Showcase Page ist geplant mit Search, Categories und Community Ratings
Monetization Strategies
Strategy 1: Private PyPI + License Keys
Hosten Sie Ihr Commercial Plugin auf einem Private PyPI Server (DevPI, Gemfury, AWS CodeArtifact), den Customers mit Authentication Tokens zugreifen. Implementieren Sie License Validation in Ihrem Plugin’s Initialization:
import os
from open_ticket_ai.core.plugins.plugin import Plugin
from open_ticket_ai.core.injectables.injectable import Injectable
class LicenseError(Exception):
"""Raised when license validation fails."""
pass
class MyCommercialPlugin(Plugin):
"""Commercial plugin with license validation."""
def __init__(self, config):
# Validate license before plugin initialization
license_key = os.getenv('MY_PLUGIN_LICENSE_KEY')
if not license_key or not self._validate_license(license_key):
raise LicenseError(
"Valid license key required. "
"Visit https://myplugin.com for licensing."
)
super().__init__(config)
def _validate_license(self, key: str) -> bool:
"""
Implement your license validation logic.
Examples:
- API call to license server
- Signature verification
- Offline validation with cryptographic signatures
"""
# Your validation logic here
return self._verify_with_license_server(key)
def _verify_with_license_server(self, key: str) -> bool:
"""Validate license key against remote server."""
# Example implementation
import requests
try:
response = requests.post(
'https://api.myplugin.com/validate',
json={'license_key': key},
timeout=5
)
return response.status_code == 200 and response.json().get('valid')
except Exception:
# Consider grace period for network failures
return False
def _get_all_injectables(self) -> list[tone[Injectable]]:
return [MyService, MyPipe]
Benefits:
- Full control over distribution
- Secure plugin delivery
- Customer-specific authentication
- Private code repository
Drawbacks:
- Requires maintaining private PyPI infrastructure
- More complex installation for customers
- Less discoverable
Strategy 2: Public PyPI + Runtime License Enforcement
Publizieren Sie Ihr Plugin auf Public PyPI für einfache Discovery, aber implementieren Sie Runtime License Validation. Installation ist free, aber Usage benötigt eine valid License:
import os
from open_ticket_ai.core.plugins.plugin import Plugin
from open_ticket_ai.core.injectables.injectable import Injectable
class LicenseError(Exception):
"""Raised when license validation fails."""
pass
class PublicCommercialPlugin(Plugin):
"""Publicly available plugin with runtime license enforcement."""
def _get_all_injectables(self) -> list[tone[Injectable]]:
"""Validate license before exposing injectables."""
license_key = os.getenv('MY_PLUGIN_LICENSE_KEY')
if not license_key:
raise LicenseError(
"License key required to use this plugin.\n"
"Set the MY_PLUGIN_LICENSE_KEY environment variable.\n"
"Purchase a license at https://myplugin.com"
)
if not self._verify_license(license_key):
raise LicenseError(
"Invalid or expired license key.\n"
"Visit https://myplugin.com to renew or purchase a license."
)
return [MyService, MyPipe]
def _verify_license(self, key: str) -> bool:
"""Your license verification logic."""
# Example: Check license format, expiration, signature
return self._check_license_signature(key)
def _check_license_signature(self, key: str) -> bool:
"""Verify license key signature (example)."""
# Implement cryptographic signature verification
# This allows offline validation
return True # Replace with actual verification
Benefits:
- Easy installation via
uv add otai-my-plugin - Public discovery on PyPI
- Simple customer onboarding
- Trial versions possible (e.g., time-limited grace period)
Drawbacks:
- Plugin code is publicly visible
- Potential for license bypass attempts
- Requires robust license validation
Strategy 3: Freemium Model
Offer a free tier with basic features and charge for premium functionality:
import os
from open_ticket_ai.core.plugins.plugin import Plugin
from open_ticket_ai.core.injectables.injectable import Injectable
class FreemiumPlugin(Plugin):
"""Plugin with free and premium features."""
def _get_all_injectables(self) -> list[tone[Injectable]]:
"""Return free injectables always, premium only with license."""
injectables = [
# Always available
BasicPipe,
BasicService,
]
# Add premium features if licensed
license_key = os.getenv('MY_PLUGIN_LICENSE_KEY')
if license_key and self._verify_license(license_key):
injectables.extend([
PremiumPipe,
AdvancedService,
])
return injectables
def _verify_license(self, key: str) -> bool:
"""Verify premium license."""
return True # Your validation logic
License Validation Best Practices
1. Environment Variables
Use environment variables for license keys to keep them out of configuration files:
license_key = os.getenv('MY_PLUGIN_LICENSE_KEY')
Users set it in their environment:
export MY_PLUGIN_LICENSE_KEY="your-license-key-here"
2. Clear Error Messages
Provide actionable error messages that guide users to purchase licenses:
if not license_key:
raise LicenseError(
"Missing license key for my-plugin.\n"
"\n"
"To use this commercial plugin:\n"
"1. Purchase a license at https://myplugin.com\n"
"2. Set the environment variable: MY_PLUGIN_LICENSE_KEY=<your-key>\n"
"3. Restart Open Ticket AI\n"
"\n"
"For questions, contact support@myplugin.com"
)
3. Fail Fast
Validate licenses during plugin initialization, not at pipe execution time:
# Good: Fails immediately on startup
class MyPlugin(Plugin):
def __init__(self, config):
self._validate_license()
super().__init__(config)
# Bad: Fails when pipe is executed
class BadPipe(Pipe):
def run(self, context):
self._validate_license() # Too late!
4. Offline Support
Consider cached validation for intermittent connectivity:
import time
import json
from pathlib import Path
def _verify_license(self, key: str) -> bool:
"""Verify license with grace period for offline scenarios."""
cache_file = Path.home() / '.my_plugin_license_cache'
# Try online validation first
if self._online_validation(key):
# Cache successful validation
cache_file.write_text(json.dumps({
'validated_at': time.time(),
'key_hash': hashlib.sha256(key.encode()).hexdigest()
}))
return True
# Fall back to cached validation (24-hour grace period)
if cache_file.exists():
cache = json.loads(cache_file.read_text())
age = time.time() - cache['validated_at']
if age < 86400: # 24 hours
return True
return False
5. Grace Periods & Trials
Allow reasonable trial periods or grace windows:
def _verify_license(self, key: str) -> bool:
"""Allow 14-day trial period."""
if key.startswith('TRIAL-'):
# Extract trial start date from key
trial_start = self._parse_trial_date(key)
days_elapsed = (datetime.now() - trial_start).days
if days_elapsed <= 14:
print(f"Trial mode: {14 - days_elapsed} days remaining")
return True
else:
raise LicenseError(
"Trial period expired. Purchase a license at https://myplugin.com"
)
return self._verify_paid_license(key)
Getting Your Plugin Listed
To have your plugin listed on the official plugins page:
- Publish to PyPI with the
otai-prefix (e.g.,otai-my-plugin) - Create documentation describing features, installation, and configuration
- Submit a PR to the Open Ticket AI documentation repository with:
- Plugin name and description
- Installation instructions
- Pricing model (free, commercial, freemium)
- Link to documentation
- Support contact information
- Specify licensing clearly in your plugin’s README and PyPI metadata
Example: Complete Commercial Plugin Structure
otai-my-commercial-plugin/
├── pyproject.toml # Project metadata & dependencies
├── LICENSE # Your chosen license (proprietary, etc.)
├── README.md # Installation & purchase info
├── src/
│ └── otai_my_commercial_plugin/
│ ├── __init__.py
│ ├── plugin_factory.py # Plugin with license validation
│ ├── licensing.py # License validation logic
│ ├── pipes/
│ │ └── my_pipe.py
│ └── services/
│ └── my_service.py
├── tests/
│ └── unit/
│ ├── test_licensing.py # Test license validation
│ └── test_plugin.py
└── docs/
├── installation.md # How to install & license
├── configuration.md # How to configure
└── pricing.md # Pricing & licensing options
Support & Maintenance
As a commercial plugin developer, consider:
- Documentation: Comprehensive guides for installation, configuration, troubleshooting
- Support Channels: Email, issue tracker, Discord/Slack community
- Updates: Regular releases for bug fixes and new features
- Compatibility: Test against new Open Ticket AI versions
- Migration Guides: Help users upgrade between versions
Legal Considerations
- License Agreement: Provide clear terms of use
- Privacy Policy: If collecting any data (license validation calls, analytics)
- Compliance: Ensure your plugin complies with relevant regulations (GDPR, etc.)
- Intellectual Property: Protect your code and trademarks appropriately
