Skip to content

Service Intelligence Artificielle

LangChain LangGraph Qdrant

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.

ComposantTechnologieRôle
OrchestrationLangChainChaînes de traitement LLM
AgentsLangGraphGraphes d’agents intelligents
EmbeddingsOpenAI / OllamaVectorisation des contenus
Base vectorielleQdrantStockage et recherche sémantique
LLMGPT-4 / Claude / MistralGénération de texte
┌─────────────────────────────────────────────────────────────┐
│ SERVICE IA │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Document │ │ Content │ │ Question │ │
│ │ Ingestion │──│ Analysis │──│ Generation │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Embedding │ │ Semantic │ │ Card │ │
│ │ Service │ │ Search │ │ Builder │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └────────────────┼──────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Qdrant │ │
│ │ Vector DB │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
  1. Ingestion du document

    • Extraction du texte (PDF, DOCX, PPTX, audio, vidéo)
    • Nettoyage et normalisation
    • Découpage en chunks
  2. Analyse du contenu

    • Identification des concepts clés
    • Extraction des définitions
    • Détection de la structure
  3. Vectorisation

    • Génération des embeddings
    • Stockage dans Qdrant
    • Indexation pour la recherche
  4. Génération des questions

    • Création de questions variées
    • Validation de la pertinence
    • Formatage selon le type de carte
  5. Construction des cartes

    • Assemblage question/réponse
    • Ajout des métadonnées
    • Classification par difficulté
from langchain.chains import LLMChain
from 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()
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
client = QdrantClient(host="qdrant", port=6333)
# Création de la collection
client.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_cache
import 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 wrapper
async 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étriqueDescriptionCible
Latence générationTemps pour générer des cartes< 10s
Qualité questionsScore moyen de validation> 0.7
Taux de succèsDocuments traités sans erreur> 95%
Utilisation cacheHit rate du cache embeddings> 60%

Intelligence artificielle au service de l’apprentissage personnalisé.