À quoi ça sert

Un RAG (Retrieval-Augmented Generation) résout un problème simple : un LLM ne connaît pas tes documents (PDFs internes, base de connaissance, code source…). Plutôt que de fine-tuner un modèle, on va chercher les passages pertinents avant de poser la question, et on les colle dans le prompt comme contexte.

LlamaIndex automatise cette chaîne :

  • Ingestion — charger des documents depuis 200+ sources (fichiers locaux, Notion, Slack, Confluence, base SQL, APIs…).
  • Indexation — découper en chunks (« Nodes »), calculer les embeddings, stocker dans un vector store.
  • Retrieval — pour une question, retrouver les N chunks les plus pertinents.
  • Réponse — passer la question + le contexte au LLM via un query engine, récupérer la réponse avec ses sources.
  • Chat — version multi-turn qui garde la mémoire de la conversation.
  • Évaluation — outils pour mesurer la qualité d'un RAG (faithfulness, relevance) sans le faire à l'œil.
LlamaIndex vs LangChain ?

LlamaIndex est spécialisé RAG : son API est optimisée pour ingester → indexer → requêter des documents. Plus rapide à mettre en place sur ce cas précis. LangChain est un framework généraliste : RAG, agents, chains, outils — plus large, plus flexible, plus complexe. Règle pratique : si tu fais un chatbot sur tes documents, prends LlamaIndex. Si tu as besoin d'agents qui appellent des outils ou de pipelines multi-LLM, prends LangChain. Les deux peuvent cohabiter — LlamaIndex s'utilise même en interne dans certaines chaînes LangChain.

Un exemple d'usage

RAG complet en 5 lignes utiles, sur un dossier de fichiers texte, avec Ollama en local — pas de clé API à gérer :

python
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding

# Config globale : quel LLM, quels embeddings on utilise partout
Settings.llm = Ollama(model="llama3.2")
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")

# Charger + indexer tout un dossier en 2 lignes
docs = SimpleDirectoryReader("./notes").load_data()
index = VectorStoreIndex.from_documents(docs)

# Poser une question
qe = index.as_query_engine()
print(qe.query("Quelle est la procédure pour déployer en staging ?"))

SimpleDirectoryReader détecte automatiquement le format (PDF, .txt, .md, .docx…) et appelle le bon loader. VectorStoreIndex.from_documents découpe, embed et indexe en une opération. as_query_engine() renvoie un objet callable qui fait le retrieve + le RAG. C'est cette compacité qui fait la force de LlamaIndex pour le pur cas RAG.

How-to : installer et utiliser LlamaIndex

  1. Installer les paquets

    Comme LangChain, LlamaIndex est modulaire : un core + une lib par intégration. Avec UV :

    bash
    uv add llama-index
    
    # Au moins un fournisseur de LLM :
    uv add llama-index-llms-ollama
    uv add llama-index-embeddings-ollama
    
    # (ou OpenAI / Anthropic / HuggingFace selon)
    uv add llama-index-llms-openai
    uv add llama-index-embeddings-openai

    llama-index tout court inclut le core + OpenAI par défaut (héritage historique). Si tu n'utilises pas OpenAI, tu peux installer llama-index-core tout seul.

  2. Configurer le LLM par défaut

    Settings est l'objet global qui dit à LlamaIndex quels LLM et embeddings utiliser. On le configure une fois en début de programme :

    python
    from llama_index.core import Settings
    from llama_index.llms.ollama import Ollama
    from llama_index.embeddings.ollama import OllamaEmbedding
    
    Settings.llm = Ollama(model="llama3.2", request_timeout=120)
    Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")
    Settings.chunk_size = 512
    Settings.chunk_overlap = 50
    Settings vs paramètre explicite

    On peut aussi passer llm= et embed_model= à chaque objet — utile pour tester deux LLMs en parallèle. Mais pour 99% des cas, Settings en début de fichier est plus propre.

  3. Charger des documents

    SimpleDirectoryReader couvre les besoins courants. Pour des sources plus spécifiques, LlamaIndex propose une centaine de readers via le « Llama Hub ».

    python
    from llama_index.core import SimpleDirectoryReader
    
    # Tout un dossier (auto-détection des formats)
    docs = SimpleDirectoryReader("./data").load_data()
    
    # Filtrer par extension
    docs = SimpleDirectoryReader(
        "./data",
        required_exts=[".pdf", ".md"],
        recursive=True,
    ).load_data()
    
    # Fichiers précis
    docs = SimpleDirectoryReader(input_files=["manuel.pdf"]).load_data()
  4. Construire un index

    VectorStoreIndex est l'index par défaut (vectoriel, recherche par similarité cosine). Il existe d'autres index (SummaryIndex, KeywordTableIndex, KnowledgeGraphIndex) pour des stratégies différentes.

    python
    from llama_index.core import VectorStoreIndex
    
    index = VectorStoreIndex.from_documents(docs)
    
    # Persister sur disque pour ne pas tout réindexer à chaque run
    index.storage_context.persist(persist_dir="./index_store")
    
    # Recharger plus tard
    from llama_index.core import StorageContext, load_index_from_storage
    storage = StorageContext.from_defaults(persist_dir="./index_store")
    index = load_index_from_storage(storage)
  5. Query engine (one-shot)

    python
    qe = index.as_query_engine(similarity_top_k=4)
    response = qe.query("Quelle est la garantie produit ?")
    
    print(response)
    print(response.source_nodes)        # les chunks utilisés
    print(response.source_nodes[0].metadata)   # source de chacun

    similarity_top_k contrôle combien de chunks on envoie au LLM. Trop peu = info manquante. Trop = on dépasse la fenêtre. 3-5 est un bon défaut.

  6. Chat engine (multi-turn)

    Le query engine oublie tout entre deux questions. Le chat engine garde l'historique pour les conversations :

    python
    chat = index.as_chat_engine(chat_mode="context")
    print(chat.chat("Quels sont les délais de garantie ?"))
    print(chat.chat("Et pour les pièces détachées ?"))   # se souvient du contexte
    chat.reset()                                   # vide l'historique
  7. Customiser le retrieval

    Pour aller au-delà du retrieval naïf : reranking, filtres sur les métadonnées, recherche hybride (vectoriel + mots-clés).

    python
    from llama_index.core.vector_stores import MetadataFilters, ExactMatchFilter
    
    # Filtrer sur une métadonnée (ex: ne chercher que dans la doc v2)
    filters = MetadataFilters(filters=[
        ExactMatchFilter(key="version", value="v2"),
    ])
    qe = index.as_query_engine(filters=filters, similarity_top_k=5)
  8. Vector store externe (production)

    Par défaut LlamaIndex stocke tout en mémoire. Pour la production, on branche un vrai vector store (PostgreSQL + pgvector, Qdrant, Chroma…) :

    python
    from llama_index.vector_stores.postgres import PGVectorStore
    from llama_index.core import StorageContext, VectorStoreIndex
    
    vs = PGVectorStore.from_params(
        database="rag", host="localhost", password="…",
        port=5432, user="app", table_name="chunks",
        embed_dim=768,
    )
    storage = StorageContext.from_defaults(vector_store=vs)
    index = VectorStoreIndex.from_documents(docs, storage_context=storage)

Aide-mémoire

python (config globale)
from llama_index.core import Settings
Settings.llm = Ollama(model="llama3.2")
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")
python (RAG minimal)
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
docs = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(docs)
print(index.as_query_engine().query("…"))
python (persistance)
index.storage_context.persist("./store")
storage = StorageContext.from_defaults(persist_dir="./store")
index = load_index_from_storage(storage)
python (chat multi-turn)
chat = index.as_chat_engine(chat_mode="context")
chat.chat("…"); chat.chat("…"); chat.reset()

LlamaIndex et le reste de l'écosystème

  • LangChain — l'alternative généraliste. LlamaIndex est plus simple pour le RAG pur ; LangChain s'impose dès qu'on a besoin d'agents, de tools ou de chaînes multi-étapes complexes. Voir l'encadré comparatif plus haut.
  • Ollama / Hugging Face — fournisseurs de LLMs et d'embeddings locaux. Packages dédiés llama-index-llms-ollama et llama-index-embeddings-huggingface.
  • PostgreSQL — avec l'extension pgvector, devient un vector store production prêt pour LlamaIndex via llama-index-vector-stores-postgres.
  • FastAPI — exposer un endpoint /ask qui appelle le query engine. Charger l'index au démarrage via le lifespan, le requêter à chaque requête HTTP.
  • Streamlit — démos RAG en quelques dizaines de lignes : un input texte, un appel query_engine.query(), l'affichage de la réponse et des sources.
  • Pydantic — pour forcer une structure de sortie au LLM (StructuredLLM, sortie typée). Indispensable dès qu'on veut consommer la réponse par programme plutôt que l'afficher brut.
  • MLflow — tracker des expériences RAG (variations de chunk_size, embed_model, top_k) et comparer les métriques d'évaluation produites par les evaluators LlamaIndex.
Le piège : embeddings et chunks figés

Si tu changes de modèle d'embeddings ou de chunk_size après avoir indexé, il faut tout réindexer. Les vecteurs existants ne sont plus dans le même espace. À garder à l'esprit en prod : versionne le couple (embed_model, chunk_size) avec l'index lui-même, par exemple via DVC.

Pour aller plus loin