# LeoCRM Plugin Development Guide > **Version:** 1.0 > **Datum:** 2026-07-23 > **Gültig für:** Alle Plugin-Entwickler --- ## 1. Plugin-Struktur Jedes Plugin liegt unter `app/plugins/builtins//`: ``` app/plugins/builtins/my_plugin/ ├── __init__.py ├── plugin.py # Plugin-Klasse mit Manifest ├── routes.py # API-Routes ├── models.py # SQLAlchemy-Modelle (optional) ├── schemas.py # Pydantic-Schemas (optional) ├── services.py # Business-Logik (optional) ├── migrations/ # SQL-Migrationen │ └── 0001_initial.sql └── tests/ # Plugin-Tests (optional) ``` ## 2. Plugin-Manifest Das Manifest definiert Metadaten, Abhängigkeiten, Routes, Events und Permissions: ```python from app.plugins.base import BasePlugin from app.plugins.manifest import PluginManifest, PluginRouteDef class MyPlugin(BasePlugin): manifest = PluginManifest( name="my_plugin", version="1.0.0", display_name="My Plugin", description="Description of what the plugin does.", dependencies=["permissions"], # Other plugins this depends on routes=[ PluginRouteDef( path="/api/v1/my-plugin", module="app.plugins.builtins.my_plugin.routes", router_attr="router", ), ], events=["contact.created", "contact.updated"], migrations=["0001_initial.sql"], permissions=[ "my_plugin:read", "my_plugin:write", "my_plugin:delete", ], agent_capabilities=[ "my_plugin:search", "my_plugin:analyze", ], ) ``` ### Manifest-Felder | Feld | Typ | Pflicht | Beschreibung | |---|---|---|---| | `name` | `str` | Ja | Eindeutiger Plugin-Name (snake_case) | | `version` | `str` | Ja | Semantic Version | | `display_name` | `str` | Ja | Anzeigename | | `description` | `str` | Nein | Kurzbeschreibung | | `dependencies` | `list[str]` | Nein | Andere Plugins, die geladen sein müssen | | `routes` | `list[PluginRouteDef]` | Nein | API-Routen-Definitionen | | `events` | `list[str]` | Nein | Events, die das Plugin abonniert | | `migrations` | `list[str]` | Nein | SQL-Migrationsdateien | | `permissions` | `list[str]` | Nein | RBAC-Permissions, die das Plugin definiert | | `field_definitions` | `list[FieldDefinition]` | Nein | Feld-Level-Permissions | | `agent_capabilities` | `list[str]` | Nein | KI-Agent-Fähigkeiten, die das Plugin bietet | | `is_core` | `bool` | Nein | Core-Plugin (kann nicht deaktiviert werden) | ## 3. RBAC-Permissions ### Permissions definieren Im Manifest werden alle Permissions des Plugins aufgelistet: ```python permissions=[ "my_plugin:read", "my_plugin:write", "my_plugin:delete", "my_plugin:admin", ], ``` ### Routes absichern Jede Route muss mit `require_permission` abgesichert werden: ```python from app.deps import get_current_user, require_permission from fastapi import Depends @router.get("", dependencies=[Depends(require_permission("my_plugin:read"))]) async def list_items(current_user: dict = Depends(get_current_user)): ... @router.post("", status_code=201, dependencies=[Depends(require_permission("my_plugin:write"))]) async def create_item(data: ItemCreate, current_user: dict = Depends(get_current_user)): ... @router.delete("/{item_id}", dependencies=[Depends(require_permission("my_plugin:delete"))]) async def delete_item(item_id: str, current_user: dict = Depends(get_current_user)): ... ``` ### Permission-Namenskonvention - Format: `:` - Standard-Actions: `read`, `write`, `delete`, `share`, `admin` - Beispiele: `calendar:read`, `dms:write`, `tags:delete` ## 4. KI-Agent-Framework LeoCRM bietet ein integriertes KI-Agent-Framework basierend auf **LiteLLM** und **PydanticAI**. ### Architektur ``` Plugin (ai_assistant, ai_proactive, zukünftige) ↓ LiteLLM (unified LLM interface — 100+ Provider) ↓ Provider (OpenAI, Anthropic, Google, Ollama, ...) ↑ Tool Registry (Plugin-Tools für KI-Agenten) ``` ### LiteLLM — Unified LLM Interface LiteLLM bietet eine einheitliche API für über 100 LLM-Provider. Alle KI-Funktionen in LeoCRM nutzen `litellm.acompletion()`: ```python import litellm response = await litellm.acompletion( model="openai/gpt-4o", # oder anthropic/claude-3-sonnet, ollama/llama3 messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_query}, ], temperature=0.3, max_tokens=1000, api_key=os.environ.get("AI_API_KEY"), api_base=os.environ.get("AI_API_BASE"), # optional für self-hosted ) content = response.choices[0].message.content ``` ### Konfiguration | Env-Var | Beschreibung | Standard | |---|---|---| | `AI_MODEL` | Modell-Name (z.B. `gpt-4o`, `claude-3-sonnet`, `llama3`) | — | | `AI_API_KEY` | API-Key für den Provider | — | | `AI_API_BASE` | Custom API-Base-URL (optional) | Provider-Standard | | `AI_PROVIDER` | Provider-Präfix (`openai`, `anthropic`, `google`, `ollama`) | `openai` | Wenn `AI_MODEL` und `AI_API_KEY` nicht gesetzt sind, läuft der LLM-Client im Mock-Modus (keyword-basierte Action-Mapping für Tests). ### Tool Registry — KI-Tools registrieren Plugins können Tools registrieren, die KI-Agenten während Chat-Sessions aufrufen können. Jedes Tool deklariert Name, Beschreibung, JSON-Schema für Parameter und einen async Handler. ```python from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry registry = get_tool_registry() registry.register( name="search_contacts", description="Search contacts by name, email, or phone number", parameters={ "type": "object", "properties": { "query": {"type": "string", "description": "Search query"}, "limit": {"type": "integer", "description": "Max results", "default": 10}, }, "required": ["query"], }, handler=my_search_handler, plugin_name="my_plugin", required_permission="contacts:read", category="search", ) ``` ### Tool Handler Der Handler ist eine async Funktion, die Argumente und Kontext empfängt: ```python async def my_search_handler(arguments: dict, context: dict) -> str: query = arguments.get("query", "") limit = arguments.get("limit", 10) # ... perform search ... return json.dumps({"results": results}) ``` ### Tools bei Plugin-Deaktivierung abmelden ```python def on_deactivate(self): registry = get_tool_registry() registry.unregister_plugin("my_plugin") ``` ### Agent Capabilities im Manifest Das `agent_capabilities` Feld im Manifest deklariert, welche KI-Fähigkeiten ein Plugin bietet: ```python agent_capabilities=[ "contact_search", # Kontakt-Suche "email_draft", # E-Mail-Entwürfe generieren "calendar_scheduling", # Terminvorschläge ], ``` Diese Informationen werden vom AI Assistant verwendet, um Nutzern zu zeigen, welche KI-Funktionen verfügbar sind. ## 5. Events Plugins können Events abonnieren und auslösen: ```python # Im Manifest: events=["contact.created", "contact.updated", "contact.deleted"] # Event-Handler im Plugin: async def on_contact_created(self, event_data: dict): # Reagiere auf neues Kontakt-Event pass ``` Events werden vom Event-Publisher im Contact-Service ausgelöst: ```python from app.core.events import publish_event await publish_event(db, "contact.created", {"contact_id": str(contact.id)}) ``` ## 6. Datenbank-Migrationen SQL-Migrationen liegen unter `migrations/` im Plugin-Verzeichnis: ```sql -- migrations/0001_initial.sql CREATE TABLE my_plugin_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id), name VARCHAR(200) NOT NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); ``` Im Manifest referenzieren: ```python migrations=["0001_initial.sql"] ``` ## 7. UI-Integration Siehe `docs/ui-design-guidelines.md` für Frontend-Konventionen. - Plugin-Seiten verwenden das 3-Spalten-Explorer-Layout - PluginToolbar für Aktionen - Plugin-Settings als eigene Settings-Sub-Seite - i18n-Keys mit Plugin-Präfix ## 8. Testing Tests liegen unter `tests/` im Plugin-Verzeichnis oder im zentralen `tests/` Ordner: ```python # tests/test_my_plugin.py import pytest from httpx import AsyncClient @pytest.mark.asyncio async def test_list_items_requires_permission(client: AsyncClient, auth_headers): response = await client.get("/api/v1/my-plugin", headers=auth_headers) assert response.status_code == 200 @pytest.mark.asyncio async def test_list_items_without_permission_returns_403(client: AsyncClient, no_perm_headers): response = await client.get("/api/v1/my-plugin", headers=no_perm_headers) assert response.status_code == 403 ``` ## 9. Plugin-Beispiel Minimal-Beispiel für ein neues Plugin: ```python # app/plugins/builtins/my_plugin/plugin.py from app.plugins.base import BasePlugin from app.plugins.manifest import PluginManifest, PluginRouteDef class MyPlugin(BasePlugin): manifest = PluginManifest( name="my_plugin", version="1.0.0", display_name="My Plugin", description="A minimal example plugin.", dependencies=[], routes=[ PluginRouteDef( path="/api/v1/my-plugin", module="app.plugins.builtins.my_plugin.routes", router_attr="router", ), ], events=[], migrations=[], permissions=["my_plugin:read", "my_plugin:write"], agent_capabilities=[], ) ``` ```python # app/plugins/builtins/my_plugin/routes.py from fastapi import APIRouter, Depends from app.deps import get_current_user, require_permission router = APIRouter() @router.get("", dependencies=[Depends(require_permission("my_plugin:read"))]) async def list_items(current_user: dict = Depends(get_current_user)): return {"items": []} ``` --- *Dieses Dokument ist verbindlich für alle Plugin-Entwicklung an LeoCRM.*