Spécifications des hooks¶
Les 10 hookspecs se trouvent dans backend/app/hookspecs.py.
Chaque hookspec définit le contrat d'appel ; les plugins les
implémentent avec @hookimpl. Trois d'entre eux sont des
variantes d'appel IA (sync / async / stream) publiées
progressivement depuis v1.5.0 et v1.6.0 ; les autres sont
inchangés depuis v0.2.0.
get_assessment_questions¶
@hookspec
def get_assessment_questions(lang: str) -> list[dict] | None:
"""Retourne le pack de questions pour la langue demandée.
Implémenté par : plugin assessment.
Mode : list (pas firstresult). La route utilise actuellement
le résultat du premier plugin ; une future fonctionnalité
« packs de questions multiples » pourrait laisser les
fournisseurs enregistrer des packs nommés.
"""
Forme de retour :
[
{
"id": "q01",
"type": "single" | "multi",
"text": "...",
"answers": [
{"id": "a", "text": "...", "weights": {"deductive": 1.0}},
...
]
},
...
]
calculate_profile¶
@hookspec(firstresult=True)
def calculate_profile(answers: list[dict]) -> dict[str, float]:
"""Agrège les réponses brutes en un profil à 6 méthodes.
Implémenté par : plugin assessment.
firstresult : exactement un plugin calcule le profil.
"""
Forme de answers en entrée :
[
{"question_id": "q01", "answer_ids": ["a", "b"]}, # multi
{"question_id": "q02", "answer_id": "c"}, # single
...
]
Retour : dict[method, float] avec les six clés de méthode.
create_session_prompt¶
@hookspec(firstresult=True)
def create_session_prompt(
project: dict,
profile: dict,
method: str,
step: int,
lang: str,
) -> str:
"""Compose le prompt système pour une cellule (méthode, étape, langue).
Implémenté par : plugin session.
firstresult : exactement un plugin compose le prompt.
"""
Retour : une chaîne prête à être envoyée comme message de rôle
system. L'implémentation du plugin session lit depuis le dict
_PROMPTS à 42 cellules et ajoute un bloc de contexte.
ai_complete¶
@hookspec(firstresult=True)
def ai_complete(
messages: list[dict[str, Any]],
model: str,
api_key: str,
max_tokens: int = 1024,
) -> str | None:
"""Appelle le fournisseur IA, retourne le texte de l'assistant.
Implémenté par : ai-anthropic, ai-openai, ai-gemini.
firstresult : le plugin du fournisseur correspondant retourne
le texte ; les autres retournent None.
"""
Chaque plugin fournisseur vérifie le préfixe du model
(claude-*, gpt-*, gemini-*) et retourne le texte de
l'assistant s'il est propriétaire du modèle. Les plugins non
correspondants retournent None, laissant firstresult passer au
suivant.
Forme de messages (style OpenAI) :
[
{"role": "system", "content": "..."},
{"role": "user", "content": "..."},
{"role": "assistant", "content": "..."},
{"role": "user", "content": "..."},
]
Les plugins fournisseurs normalisent cette forme vers leur propre
API (Anthropic sépare system ; Gemini le plie à l'intérieur).
ai_complete_async (v1.5.0+)¶
@hookspec(firstresult=True)
async def ai_complete_async(
messages: list[dict[str, Any]],
model: str,
api_key: str,
max_tokens: int = 1024,
) -> str:
"""Variante awaitable de ai_complete.
Implémenté par : ai-anthropic, ai-openai, ai-gemini.
Utilisé à la limite de cycle étape 6 → 7 pour que
step-eval + topic-transition se déclenchent en parallèle
via asyncio.gather (économise ~T_2 de latence).
"""
L'utilitaire orchestrateur call_ai_complete_async préfère ce
hook ; replie sur ai_complete encapsulé dans
asyncio.to_thread quand non implémenté.
ai_complete_stream (v1.6.0+)¶
@hookspec(firstresult=True)
def ai_complete_stream(
messages: list[dict[str, Any]],
model: str,
api_key: str,
max_tokens: int = 1024,
) -> AsyncIterator[str]:
"""Retourne un itérateur async de deltas de texte.
Implémenté par : ai-anthropic, ai-openai, ai-gemini,
chacun utilisant le streaming async natif du SDK fournisseur.
Alimente ``POST /api/plugins/session/{id}/message/stream``
(SSE : émet les événements ``start`` / ``chunk`` / ``done``).
"""
recommend_method_switch¶
@hookspec
def recommend_method_switch(
history: list[dict],
profile: dict,
) -> dict | None:
"""Retourne une recommandation de changement, ou None.
Implémenté par : plugin session.
Mode : list. Les plugins retournent des recommandations
individuelles ; un futur arbitre choisira la non-None de
plus haute confiance.
"""
Forme de retour :
{
"recommended": True,
"to_method": "dialogic",
"reason": "Trois sessions de compréhension stagnante.",
"confidence": 0.75, # optionnel
}
Retourner None (ou {"recommended": False}) quand aucun
changement n'est justifié.
on_session_complete¶
@hookspec
def on_session_complete(
session: dict,
rating: dict,
) -> None:
"""Hook d'effet secondaire : déclenché quand une session est terminée.
Implémenté par : plugin tracking (écrit un ProgressCommit).
Mode : list. Chaque abonné s'exécute ; les erreurs sont
capturées et journalisées mais n'annulent pas la fin de session.
"""
Les erreurs dans ce hook NE DOIVENT PAS se propager — le
wrapper _fire_on_session_complete dans
backend/app/main.py les capture et les journalise.
get_progress_summary¶
@hookspec
def get_progress_summary(
project_id: str,
db: Session,
) -> dict | None:
"""Retourne une tranche de namespace du résumé de progression.
Implémenté par : plugin tracking (retourne la tranche
de namespace "tracking").
Mode : list. Les tranches sont fusionnées superficiellement
dans la route.
"""
Chaque plugin retourne un dict clé par un namespace unique.
Le plugin tracking retourne {"tracking": {...}, "step_evaluation": {...}}.
Un futur plugin de détection de stagnation pourrait retourner
{"stagnation": {...}}, etc.
get_tool_recommendations¶
@hookspec
def get_tool_recommendations(
profile: dict,
lang: str,
limit: int = 5,
) -> list[dict] | None:
"""Retourne les recommandations d'outils externes classées.
Implémenté par : plugin tools.
Mode : list. La route utilise actuellement le résultat du
premier plugin ; un futur recommandeur multi-sources pourrait
les fusionner.
"""
Forme de retour :
[
{
"name": "Anki",
"url": "https://apps.ankiweb.net/",
"why": "...",
"weight_keys": ["deductive", "error_based"],
"score": 0.5
},
...
]
firstresult vs mode list¶
| Hookspec | Mode | Pourquoi |
|---|---|---|
| get_assessment_questions | list (firstresult effectif) | Actuellement un seul pack ; futur possible avec packs nommés |
| calculate_profile | firstresult | Un seul algorithme |
| create_session_prompt | firstresult | Un seul compositeur de prompt |
| ai_complete | firstresult | Le fournisseur correspondant possède l'appel |
| recommend_method_switch | list | Plusieurs plugins peuvent suggérer |
| on_session_complete | list | Les effets secondaires se diffusent |
| get_progress_summary | list | Les tranches de namespace fusionnent |
| get_tool_recommendations | list (firstresult effectif) | Actuellement une seule source |
Les modes list qui se comportent actuellement comme firstresult
sont intentionnels : le contrat est ouvert à l'extension
multi-plugin, mais l'implémentation actuelle utilise seulement
le premier.