Integración de IA¶
Adaptive Learner ejecuta cada conversación de aprendizaje a
través de hasta tres llamadas a la IA por viaje de ida y
vuelta — la respuesta en streaming, el evaluador de pasos y (en
el paso 7) el evaluador de transición de tema. Tres proveedores
se incluyen por defecto; los nuevos proveedores se conectan
mediante la familia de hooks ai_complete*.
El hook ai_complete¶
# backend/app/hookspecs.py
@hookspec(firstresult=True)
def ai_complete(
messages: list[dict[str, Any]],
model: str,
api_key: str,
max_tokens: int = 1024,
) -> str | None:
"""Devuelve el texto del asistente, o None si este plugin no maneja ``model``."""
firstresult=True significa que pluggy se detiene en el primer
retorno distinto de None. Cada plugin de proveedor comprueba el
prefijo del model y devuelve el texto del asistente si es
propietario del modelo:
@hookimpl
def ai_complete(
self, messages, model, api_key, max_tokens
) -> str | None:
if not model.startswith("claude-"):
return None
# ... llamar a la API de Anthropic, devolver el texto ...
Se incluyen tres plugins: ai-anthropic (claude-), ai-openai
(gpt-), ai-gemini (gemini-*).
Variantes asíncrona y en streaming¶
@hookspec(firstresult=True)
async def ai_complete_async(messages, model, api_key, max_tokens) -> str:
"""Awaitable; misma forma que ai_complete. v1.5.0+."""
@hookspec(firstresult=True)
def ai_complete_stream(messages, model, api_key, max_tokens):
"""Devuelve un iterador asíncrono de deltas de texto. v1.6.0+."""
ai_complete_async lo usa la ruta de sesión en el límite de
ciclo paso 6→7 para que la evaluación de pasos y la transición
de tema se disparen de forma concurrente mediante asyncio.gather
(async_evaluation: true en app.yaml).
ai_complete_stream alimenta el endpoint SSE en streaming
POST /api/plugins/session/{id}/message/stream que emite eventos
start / chunk / done.
Lógica de selección de proveedor (v1.20.0)¶
_resolve_active_key() de la ruta de sesión llama a
services/settings.resolve_api_key(db, user_id, provider)
que recorre la cadena de tres capas:
- Variable de entorno
ADAPTIVE_LEARNER_<PROVEEDOR>_API_KEY. ai.<proveedor>.api_keyen~/.config/adaptive_learner/secrets.yaml.UserSettings.api_key_<proveedor>descifrado con Fernet.None— la llamada muestraai_erroren la interfaz.
resolve_default_model(db, user_id, provider) recorre la misma
cadena para el sobreescritura del modelo (env > yaml > sobreescritura
de interfaz > DEFAULT_MODELS[provider]).
Luego se dispara ai_complete* con los valores resueltos. El
plugin del proveedor correspondiente devuelve el texto; los demás
devuelven None (firstresult se detiene en el primer resultado).
Arquitectura de doble prompt (v0.5.0) + auto-bucle (v1.4.0)¶
Cada POST /api/plugins/session/{id}/message para un rol user
realiza hasta tres llamadas a la IA:
- Respuesta de aprendizaje — en streaming mediante
ai_complete_stream. Prompt del sistema compuesto porbuild_prompt(project, profile, method, cycle_step, lang)desde la matriz de 42 celdas.max_tokens=1024. SSE emite eventosstart/chunk/done. - Evaluador de pasos — prompt del sistema separado
(
EVALUATION_SYSTEM_PROMPT) que pide a la IA leer el intercambio y emitir un veredicto JSON (advance,confidence,reason,suggested_step).max_tokens=256. El veredicto del evaluador dirige el avance decycle_step(condicionado aconfidence ≥ 0.6). - Transición de tema — solo en el paso 7. Una tercera
llamada a la IA juzga si el tema fue integrado y si se debe
iniciar un nuevo ciclo en un nuevo subtema. Límite de
max_cycles=5por sesión.
Si el evaluador devuelve JSON no analizable, se activa el
fallback determinista de +1 (limitado a 7) y se registra
fallback_used=True.
El límite de ciclo (paso 6 → 7) dispara la evaluación de pasos
y la transición de tema de forma concurrente mediante
asyncio.gather (ahorra ~T₂ de latencia). Devuelto en el bloque
timings de la respuesta del mensaje (learning_ms,
evaluation_ms, topic_transition_ms, total_ms,
parallel_saved_ms).
La matriz de prompts de 42 celdas¶
plugins/adaptive-learner-plugin-session/adaptive_learner_session/prompts.py
contiene un dict[method, dict[step, dict[lang, str]]] — seis
métodos, siete pasos, dos idiomas, 84 celdas. Cada celda es 1-2
oraciones que establecen el rol de la IA + la tarea del paso. Un
bloque de contexto («Proyecto de aprendizaje: 'X' | Objetivo: 'Y'.
Pista del perfil: …») se adjunta en el momento de la composición.
Para el modo Dexie, los prompts se exportan literalmente a
frontend/src/data/session-prompts.json y los carga
frontend/src/storage/prompts.ts. El mismo texto, el mismo
formato de bloque de contexto — sin deriva posible.
Añadir un nuevo proveedor¶
- Crea
plugins/adaptive-learner-plugin-ai-nuevoproveedor/. - Implementa el hookimpl
ai_complete: comprueba el prefijo del modelo, llama a la API HTTP del proveedor, devuelve el texto. - Añade el prefijo del proveedor a
DEFAULT_MODELSenai_orchestration.pycon un modelo por defecto económico. - Añade el nombre del proveedor al enum
AIProviderenapp/schemas/__init__.py. - Añádelo a
AI_PROVIDERSenfrontend/src/lib/constants.ts. - Para paridad en modo Dexie: añade un cliente a
frontend/src/storage/ai-providers.tsy enrútalo desdeaiComplete().
Cada plugin de proveedor prueba su hookimpl + la llamada al
proveedor de forma aislada — consulta
plugins/adaptive-learner-plugin-ai-anthropic/tests/ como
plantilla (la llamada HTTP al proveedor está simulada).
Llamadas directas desde el navegador (modo Dexie)¶
En el modo Dexie la llamada a la IA no pasa por el sistema de
plugins. storage/ai-providers.ts realiza la solicitud HTTP
directamente. Anthropic requiere el encabezado
anthropic-dangerous-direct-browser-access: true para superar
la verificación previa CORS; OpenAI y Gemini aceptan llamadas
directas desde el navegador por defecto.
La lógica de doble prompt es la misma en ambos modos —
storage/session-flow.ts llama a aiComplete() dos veces y
analiza el JSON del evaluador de la misma manera que lo hace el
backend.
Umbral de confianza¶
session.step_evaluation.confidence_threshold en
backend/config/app.yaml (por defecto 0.6) determina si un
veredicto real del evaluador (sin fallback) realmente mueve el
paso del ciclo. Ponlo más alto para ser más conservador, más bajo
para ser más agresivo. Los veredictos de fallback (fallos de
análisis) siempre aplican el avance de +1 independientemente.
El puerto Dexie refleja esto con un 0.6 fijo en
storage/session-flow.ts. Una fase futura lo expondrá en la
interfaz de Ajustes.
Otras superficies de IA (resumen de solo lectura)¶
Varias funciones fuera de sesión usan los mismos plugins de
proveedor de IA mediante ai_complete*:
- Analizador de conversaciones (Fase 12 / v0.9.0+) —
frontend/src/chat_import/analysis.tsdivide los transcripts importados en fragmentos de 16K caracteres con superposición de 2 mensajes, disparaai_completepor fragmento y fusiona los resultados. Extrae tema / puntos débiles / patrones de errores / método recomendado / vocabulario (desde v1.20.0). El analizador JSON tolerante maneja el comportamiento defectuoso de modelos de clase Haiku (salida entre vallas de código, prosa de preámbulo). - Extracción Anki (Fase 30 / v1.17.0) —
.../anki/card_extraction.pyextrae candidatos a tarjetas de memoria de una sesión o conversación; la ruta de vocabulario se ejecuta del lado del cliente sin IA cuandoanalysis_result.vocabularyestá relleno. - Preguntas de estudio NotebookLM + guía (Fase 32 / v1.19.0)
—
.../notebooklm/question_generator.py+study_guide.py; analizador JSON tolerante; las preguntas editadas por el usuario omiten la regeneración. - Juez de pronunciación (Fase 31 / v1.18.0) —
.../pronunciation.pygenera frases objetivo y juzga la similitud del audio del aprendiz (habilitación condicionada a la taxonomía de asignatura de Idiomas).