This comprehensive reference guide explains how to create integration plugins for the ELITEA platform using the Pylon framework. This template provides examples for integrating Python libraries, Node.js modules, CLI tools, and other external systems.
💡 Quick Start: New to plugin development? Start with the Step-by-Step Guide or Quick Start Template.
🔍 Looking for patterns? Check Integration Patterns for specific tool types.
- Architecture Overview
- Project Structure
- Core Components
- Implementation Steps
- Python Plugin Example
- Node.js Plugin Example
- API Specification
- Best Practices
The ELITEA/Pylon integration follows a service provider pattern where:
- Plugin Registration: The plugin exposes a descriptor endpoint that registers toolkits and their functions with the platform
- Tool Invocation: The platform calls the plugin through standardized HTTP endpoints
- Internal Setup: The plugin manages its own dependencies and runtime environment
- Result Handling: The plugin returns results in a standardized format
graph TD
A[ELITEA Platform] -->|1. Fetch Descriptor| B[Plugin /descriptor]
B -->|2. Register Toolkits| A
A -->|3. Invoke Tool| C[Plugin /tools/.../invoke]
C -->|4. Return Result| A
A -->|5. Check Status| D[Plugin /tools/.../invocations/...]
plugin_name/
├── __init__.py # Module initialization
├── module.py # Main Pylon module
├── metadata.json # Plugin metadata
├── config.yml # Configuration defaults
├── requirements.txt # Python dependencies
├── methods/ # Internal plugin methods
│ ├── __init__.py
│ ├── init.py # Initialization logic
│ ├── config.py # Configuration management
│ └── binaries.py # External tool setup
└── routes/ # HTTP endpoints
├── __init__.py
├── descriptor.py # Toolkit registration
├── invoke.py # Tool invocation
├── invocations.py # Status checking
└── health.py # Health check
The main Pylon module that initializes the plugin:
from pylon.core.tools import module
class Module(module.ModuleModel):
def init(self):
self.descriptor.init_all(
url_prefix="/",
static_url_prefix="/",
)Plugin metadata for the Pylon framework:
{
"name": "Host for tools: your_tool",
"version": "0.1",
"depends_on": [],
"init_after": []
}Default configuration values:
base_path: /data/your_tool
tool_version: v1.0.0
service_location_url: http://127.0.0.1:8080- Create the directory structure as shown above
- Set up
__init__.pyfiles in each package - Define your plugin metadata in
metadata.json
This is the most critical component - it registers your toolkit with the platform:
from pylon.core.tools import web
class Route:
@web.route("/descriptor")
def descriptor_route(self):
service_location_url = self.descriptor.config.get(
"service_location_url", "http://127.0.0.1:8080"
)
return {
"name": "YourServiceProvider",
"service_location_url": service_location_url,
"configuration": {},
"provided_toolkits": [
{
"name": "YourToolkit",
"description": "Description of your toolkit",
"toolkit_config": {
"type": "Your Tool Configuration",
"description": "Configuration for your tool",
"parameters": {},
},
"provided_tools": [
{
"name": "your_function",
"args_schema": {
"input_param": {
"type": "String",
"required": True,
"description": "Description of input parameter"
}
},
"description": "Description of what this tool does",
"tool_metadata": {
"result_target": "artifact", # or "inline"
"result_extension": "pdf", # if applicable
"result_encoding": "base64", # if applicable
},
"tool_result_type": "String",
"sync_invocation_supported": True,
"async_invocation_supported": False,
},
],
"toolkit_metadata": {},
},
]
}Handles tool execution requests:
import flask
from pylon.core.tools import web, log
class Route:
@web.route("/tools/<toolkit_name>/<tool_name>/invoke", methods=["POST"])
def invoke_route(self, toolkit_name, tool_name):
# Validate toolkit and tool names
if toolkit_name != "YourToolkit" or tool_name != "your_function":
return {
"errorCode": "404",
"message": "Resource Not Found",
"details": [],
}, 404
try:
request_data = flask.request.json
# Validate required parameters
if "parameters" not in request_data:
return {
"errorCode": "400",
"message": "Bad Request",
"details": ["Missing parameters"],
}, 400
# Extract parameters
params = request_data["parameters"]
# Execute your tool logic here
result = self.execute_tool(params)
# Return success response
return {
"invocation_id": str(uuid.uuid4()),
"status": "Completed",
"result": result,
"result_type": "String",
}
except Exception as e:
log.exception("Failed to invoke %s:%s", toolkit_name, tool_name)
return {
"errorCode": "500",
"message": "Internal Server Error",
"details": [str(e)],
}, 500Provides health check endpoint:
import time
import datetime
from pylon.core.tools import web
class Route:
@web.route("/health")
def health_route(self):
return {
"status": "UP",
"providerVersion": "latest",
"uptime": int(time.time() - self.start_ts),
"timestamp": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
"extra_info": {},
}import os
import pathlib
from pylon.core.tools import web
class Method:
@web.method()
def runtime_config(self):
result = {}
# Base configuration with defaults
config_map = {
"base_path": str(pathlib.Path(__file__).parent.parent.joinpath("data", "your_tool")),
"tool_version": "v1.0.0",
"url_verify": True,
}
# Override with user configuration
for key, default in config_map.items():
result[key] = self.descriptor.config.get(key, default)
return resultimport time
from pylon.core.tools import web
class Method:
@web.init()
def init(self):
config = self.runtime_config()
# Setup your tool environment
self.setup_environment(config)
# Track startup time for health checks
self.start_ts = time.time()
def setup_environment(self, config):
# Implement your environment setup logic
passHere's a complete example for a Python-based tool integration:
# routes/descriptor.py
class Route:
@web.route("/descriptor")
def descriptor_route(self):
return {
"name": "ImageProcessingProvider",
"service_location_url": self.descriptor.config.get("service_location_url", "http://127.0.0.1:8080"),
"configuration": {},
"provided_toolkits": [{
"name": "ImageToolkit",
"description": "Image processing toolkit using PIL",
"provided_tools": [{
"name": "resize_image",
"args_schema": {
"image_data": {"type": "String", "required": True, "description": "Base64 encoded image"},
"width": {"type": "Integer", "required": True, "description": "Target width"},
"height": {"type": "Integer", "required": True, "description": "Target height"}
},
"description": "Resize an image to specified dimensions",
"tool_result_type": "String",
"sync_invocation_supported": True,
}],
}]
}
# routes/invoke.py
import base64
from PIL import Image
import io
class Route:
@web.route("/tools/<toolkit_name>/<tool_name>/invoke", methods=["POST"])
def invoke_route(self, toolkit_name, tool_name):
if toolkit_name != "ImageToolkit" or tool_name != "resize_image":
return {"errorCode": "404", "message": "Not Found"}, 404
try:
data = flask.request.json["parameters"]
# Decode image
image_bytes = base64.b64decode(data["image_data"])
image = Image.open(io.BytesIO(image_bytes))
# Resize
resized = image.resize((data["width"], data["height"]))
# Encode result
output = io.BytesIO()
resized.save(output, format='PNG')
result_data = base64.b64encode(output.getvalue()).decode()
return {
"invocation_id": str(uuid.uuid4()),
"status": "Completed",
"result": result_data,
"result_type": "String",
}
except Exception as e:
return {"errorCode": "500", "message": str(e)}, 500
# requirements.txt
Pillow>=9.0.0
flask>=2.0.0For Node.js-based tools, you'll need to manage Node.js installation and packages:
# methods/binaries.py - Node.js setup
import subprocess
import os
from pylon.core.tools import web, process
class Method:
@web.method()
def setup_nodejs(self, config):
node_path = config["node_path"]
# Install required packages
packages = ["express", "your-npm-package"]
for package in packages:
process.run_command([
os.path.join(node_path, "npm"), "install", "-g", package
])
# routes/invoke.py - Execute Node.js tool
class Route:
@web.route("/tools/<toolkit_name>/<tool_name>/invoke", methods=["POST"])
def invoke_route(self, toolkit_name, tool_name):
try:
config = self.runtime_config()
node_bin = config["node_bin"]
# Prepare environment
env = os.environ.copy()
env["PATH"] = os.pathsep.join([env["PATH"], node_bin])
# Execute Node.js script
result = process.run_command([
"node", "your-script.js"
], env=env, capture_output=True)
return {
"invocation_id": str(uuid.uuid4()),
"status": "Completed",
"result": result.stdout,
"result_type": "String",
}
except Exception as e:
return {"errorCode": "500", "message": str(e)}, 500{
"name": "ServiceProviderName",
"service_location_url": "http://host:port",
"configuration": {},
"provided_toolkits": [
{
"name": "ToolkitName",
"description": "Toolkit description",
"toolkit_config": {
"type": "Configuration Type",
"description": "Configuration description",
"parameters": {}
},
"provided_tools": [
{
"name": "tool_name",
"args_schema": {
"param_name": {
"type": "String|Integer|Boolean|Object",
"required": true|false,
"description": "Parameter description"
}
},
"description": "Tool description",
"tool_metadata": {
"result_target": "artifact|inline",
"result_extension": "file_extension",
"result_encoding": "base64|utf8"
},
"tool_result_type": "String|Integer|Boolean|Object",
"sync_invocation_supported": true|false,
"async_invocation_supported": true|false
}
],
"toolkit_metadata": {}
}
]
}Request:
{
"parameters": {
"param1": "value1",
"param2": "value2"
}
}Success Response:
{
"invocation_id": "uuid",
"status": "Completed|Running|Failed",
"result": "result_data",
"result_type": "String|Integer|Boolean|Object"
}Error Response:
{
"errorCode": "400|404|500",
"message": "Error message",
"details": ["detail1", "detail2"]
}- Always wrap tool execution in try-catch blocks
- Return meaningful error messages and codes
- Log errors for debugging
- Use configuration files for defaults
- Allow runtime configuration override
- Validate configuration parameters
- Clean up temporary files and resources
- Use context managers for file operations
- Implement proper error recovery
- Validate all input parameters
- Sanitize file paths and commands
- Use secure defaults for network operations
- Cache expensive setup operations
- Use streaming for large data transfers
- Implement proper timeout handling
- Test descriptor endpoint registration
- Test tool invocation with various inputs
- Test error scenarios and edge cases
- Document all parameters and return values
- Provide clear tool descriptions
- Include usage examples
- Dependencies: Ensure all required dependencies are listed in
requirements.txt - Permissions: Set appropriate file permissions for data directories
- Environment: Configure environment variables for production
- Monitoring: Implement logging and health checks
- Scaling: Consider stateless design for horizontal scaling
This guide provides a comprehensive foundation for creating ELITEA/Pylon integration plugins. Use this template implementation as a reference and adapt the patterns to your specific tool requirements.