Aller au contenu

Guide des plugins

Ce guide vous accompagne dans la création d'un plugin Adaptive Learner de bout en bout.


Structure du plugin

plugins/adaptive-learner-plugin-monplugin/
  adaptive_learner_monplugin/
    __init__.py
    plugin.py
    routes.py
    service.py
  tests/
    test_monplugin.py
  pyproject.toml

1. pyproject.toml

[tool.poetry]
name = "adaptive-learner-plugin-monplugin"
version = "1.0.0"
description = "Mon plugin Adaptive Learner"
packages = [{include = "adaptive_learner_monplugin"}]

[tool.poetry.dependencies]
python = "^3.11"
pluginforge = "^0.10.0"
fastapi = "^0.136"

[project.entry-points."adaptive_learner.plugins"]
monplugin = "adaptive_learner_monplugin.plugin:MonpluginPlugin"

2. plugin.py

from pluginforge import BasePlugin, hookimpl
from fastapi import FastAPI

class MonpluginPlugin(BasePlugin):
    name = "monplugin"
    version = "1.0.0"
    target_application = "adaptive_learner"
    license_tier = "core"

    def register_routes(self, app: FastAPI) -> None:
        from .routes import router
        app.include_router(router, prefix="/api/plugins/monplugin", tags=["monplugin"])

    @hookimpl
    def on_session_complete(self, session: dict, db) -> None:
        """Appelé par le plugin session lorsqu'une session se termine."""
        # Votre logique ici
        pass

3. routes.py

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from . import service

router = APIRouter()

@router.get("/items/{user_id}")
def list_items(user_id: str, db: Session = Depends(get_db)):
    return service.get_items(user_id, db)

@router.post("/items")
def create_item(body: service.ItemCreate, db: Session = Depends(get_db)):
    return service.create_item(body, db)

4. service.py

from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.exceptions import NotFoundError

class ItemCreate(BaseModel):
    user_id: str
    content: str

def get_items(user_id: str, db: Session) -> list:
    # Pas d'HTTPException ici — utilisez des sous-classes AdaptiveLearnerError
    return db.query(...).filter_by(user_id=user_id).all()

def create_item(body: ItemCreate, db: Session):
    item = Item(user_id=body.user_id, content=body.content)
    db.add(item)
    db.commit()
    return item

5. Tests

# tests/test_monplugin.py
import pytest
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_list_items_empty(test_user):
    resp = client.get(f"/api/plugins/monplugin/items/{test_user.id}")
    assert resp.status_code == 200
    assert resp.json() == []

def test_create_item(test_user):
    resp = client.post("/api/plugins/monplugin/items", json={
        "user_id": test_user.id,
        "content": "Test"
    })
    assert resp.status_code == 200
    assert resp.json()["content"] == "Test"

6. Installer et activer

# Depuis le répertoire backend
poetry add --editable ../plugins/adaptive-learner-plugin-monplugin

# Activer dans backend/config/app.yaml
# plugins:
#   enabled:
#     - monplugin

Redémarrez le backend — votre plugin sera découvert via les entry points.


Les 10 hookspecs

Hook Signature
get_assessment_questions (lang: str) -> list[dict]
calculate_profile (answers: list[dict]) -> dict
create_session_prompt (profile: dict, step: int, method: str, lang: str) -> str
ai_complete (prompt: str, model: str, messages: list) -> str
ai_complete_async async (prompt, model, messages) -> str
ai_complete_stream async (prompt, model, messages) -> AsyncIterator[str]
recommend_method_switch (session: dict, history: list) -> dict|None
on_session_complete (session: dict, db: Session) -> None
get_progress_summary (project_id: str, db: Session) -> dict
get_tool_recommendations (profile: dict, lang: str) -> list[dict]

Utilisez @hookimpl sur vos implémentations. Un seul plugin peut implémenter ai_complete (firstresult). Tous peuvent implémenter on_session_complete.


Configuration du plugin

Ajoutez un fichier backend/config/plugins/monplugin.yaml :

enabled: true
max_items: 50      # paramètre configurable par l'utilisateur
debug_mode: false  # INTERNE — pas de contrôle UI

Lisez-le depuis votre plugin :

def activate(self) -> None:
    self._settings = self.config.get("settings", {})
    self._max_items = self._settings.get("max_items", 50)

Règle : tout paramètre influençant le comportement visible par l'utilisateur doit soit apparaître dans l'UI des Paramètres, soit être marqué # INTERNE.