Flashcard
Question/réponse simple pour la mémorisation
Le service IA est le cœur de Mindlet, responsable de la génération automatique de contenu pédagogique à partir de documents importés par les utilisateurs.
| Composant | Technologie | Rôle |
|---|---|---|
| Orchestration | LangChain | Chaînes de traitement LLM |
| Agents | LangGraph | Graphes d’agents intelligents |
| Embeddings | OpenAI / Ollama | Vectorisation des contenus |
| Base vectorielle | Qdrant | Stockage et recherche sémantique |
| LLM | GPT-4 / Claude / Mistral | Génération de texte |
┌─────────────────────────────────────────────────────────────┐│ SERVICE IA │├─────────────────────────────────────────────────────────────┤│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Document │ │ Content │ │ Question │ ││ │ Ingestion │──│ Analysis │──│ Generation │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ │ │ │ ││ ▼ ▼ ▼ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Embedding │ │ Semantic │ │ Card │ ││ │ Service │ │ Search │ │ Builder │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ │ │ │ ││ └────────────────┼──────────────────┘ ││ ▼ ││ ┌──────────────┐ ││ │ Qdrant │ ││ │ Vector DB │ ││ └──────────────┘ │└─────────────────────────────────────────────────────────────┘Ingestion du document
Analyse du contenu
Vectorisation
Génération des questions
Construction des cartes
from langchain.chains import LLMChainfrom langchain.prompts import PromptTemplate
document_analysis_prompt = PromptTemplate( input_variables=["content"], template=""" Analysez le contenu suivant et extrayez: 1. Les concepts clés (5-10 maximum) 2. Les définitions importantes 3. Les relations entre concepts 4. Les points difficiles à mémoriser
Contenu: {content}
Répondez en JSON structuré. """)
analysis_chain = LLMChain( llm=llm, prompt=document_analysis_prompt, output_parser=JsonOutputParser())question_generation_prompt = PromptTemplate( input_variables=["concept", "context", "difficulty"], template=""" Génère une question pédagogique sur le concept suivant:
Concept: {concept} Contexte: {context} Difficulté: {difficulty}
La question doit: - Être claire et non ambiguë - Tester la compréhension, pas la mémorisation brute - Avoir une réponse vérifiable
Format de sortie: {{ "question": "...", "answer": "...", "explanation": "...", "difficulty": 1-5 }} """)from langgraph.graph import StateGraph, END
class GenerationState(TypedDict): document: str concepts: List[str] questions: List[Dict] cards: List[Card] quality_score: float
def create_generation_graph(): workflow = StateGraph(GenerationState)
# Nœuds du graphe workflow.add_node("extract_concepts", extract_concepts) workflow.add_node("generate_questions", generate_questions) workflow.add_node("validate_quality", validate_quality) workflow.add_node("build_cards", build_cards) workflow.add_node("regenerate", regenerate_low_quality)
# Transitions workflow.add_edge("extract_concepts", "generate_questions") workflow.add_conditional_edges( "generate_questions", check_quality, { "pass": "build_cards", "fail": "regenerate" } ) workflow.add_edge("regenerate", "validate_quality") workflow.add_edge("build_cards", END)
return workflow.compile()def validate_quality(state: GenerationState) -> GenerationState: """Évalue la qualité des questions générées."""
quality_prompt = """ Évaluez la qualité de cette question pédagogique:
Question: {question} Réponse: {answer}
Critères (0-1 chacun): - Clarté - Pertinence pédagogique - Vérifiabilité de la réponse - Niveau de difficulté approprié
Score global (moyenne des critères): """
scores = [] for q in state["questions"]: score = evaluate_question(q, quality_prompt) scores.append(score)
state["quality_score"] = sum(scores) / len(scores) return state
def check_quality(state: GenerationState) -> str: """Décide si la qualité est suffisante.""" if state["quality_score"] >= 0.7: return "pass" return "fail"from qdrant_client import QdrantClientfrom qdrant_client.models import Distance, VectorParams
client = QdrantClient(host="qdrant", port=6333)
# Création de la collectionclient.create_collection( collection_name="mindlet_documents", vectors_config=VectorParams( size=1536, # Dimension OpenAI distance=Distance.COSINE ))class EmbeddingService: def __init__(self, model: str = "text-embedding-3-small"): self.model = model self.client = OpenAI()
async def embed_document(self, document: Document) -> List[float]: """Génère l'embedding d'un document.""" chunks = self.chunk_document(document.content) embeddings = []
for chunk in chunks: response = await self.client.embeddings.create( model=self.model, input=chunk ) embeddings.append({ "vector": response.data[0].embedding, "metadata": { "document_id": document.id, "chunk_index": chunks.index(chunk), "text": chunk } })
return embeddings
async def search_similar( self, query: str, limit: int = 5 ) -> List[Dict]: """Recherche les contenus similaires.""" query_embedding = await self.embed_text(query)
results = self.qdrant.search( collection_name="mindlet_documents", query_vector=query_embedding, limit=limit )
return [ { "text": hit.payload["text"], "score": hit.score, "document_id": hit.payload["document_id"] } for hit in results ]Flashcard
Question/réponse simple pour la mémorisation
QCM
Question à choix multiples avec distracteurs
Vrai/Faux
Affirmation à valider avec explication
Association
Relier des éléments correspondants
Texte à trous
Compléter les mots manquants
Carte mentale
Visualisation des relations entre concepts
def generate_mcq(concept: str, context: str) -> Dict: """Génère un QCM avec distracteurs pertinents."""
prompt = f""" Créez un QCM sur: {concept} Contexte: {context}
Générez: - 1 bonne réponse - 3 distracteurs plausibles mais incorrects
Les distracteurs doivent: - Être vraisemblables - Cibler des erreurs de compréhension courantes - Ne pas être trivialement faux """
response = llm.generate(prompt)
return { "type": "mcq", "question": response.question, "correct_answer": response.correct, "options": shuffle([ response.correct, *response.distractors ]), "explanation": response.explanation }from functools import lru_cacheimport redis
redis_client = redis.Redis(host='redis', port=6379)
def cache_embedding(func): """Decorator pour cacher les embeddings.""" async def wrapper(text: str) -> List[float]: cache_key = f"embedding:{hash(text)}"
cached = redis_client.get(cache_key) if cached: return json.loads(cached)
embedding = await func(text) redis_client.setex(cache_key, 3600, json.dumps(embedding))
return embedding return wrapperasync def process_batch(documents: List[Document]) -> List[Card]: """Traite plusieurs documents en parallèle."""
tasks = [ asyncio.create_task(process_document(doc)) for doc in documents ]
results = await asyncio.gather(*tasks, return_exceptions=True)
cards = [] for result in results: if isinstance(result, Exception): logger.error(f"Error processing document: {result}") else: cards.extend(result)
return cards| Métrique | Description | Cible |
|---|---|---|
| Latence génération | Temps pour générer des cartes | < 10s |
| Qualité questions | Score moyen de validation | > 0.7 |
| Taux de succès | Documents traités sans erreur | > 95% |
| Utilisation cache | Hit rate du cache embeddings | > 60% |
Intelligence artificielle au service de l’apprentissage personnalisé.