Construire un pipeline RAG minimaliste avec LangChain et Ollama

Prérequis

  • Ollama installé et en cours d'exécution localement.
  • Un modèle d'embedding compatible et un modèle de langue disponibles via Ollama.

Dépendances

pypdf
faiss-cpu
langchain
langchain-community
langchain-openai
tiktoken
langchainhub

Chargement et nettoyage du PDF

La première étape consiste à lire le document et à normaliser le texte avant de le découper en fragments.

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

def normaliser_contenu(documents):
    for doc in documents:
        doc.page_content = (
            doc.page_content.replace("\t", " ")
            .replace("\n", " ")
            .replace("  ", " ")
            .strip()
        )
    return documents

def decouper_pdf(chemin, taille_bloc=800, chevauchement=150):
    documents = PyPDFLoader(chemin).load()
    fractionneur = RecursiveCharacterTextSplitter(
        chunk_size=taille_bloc,
        chunk_overlap=chevauchement,
        length_function=len,
    )
    fragments = fractionneur.split_documents(documents)
    return normaliser_contenu(fragments)

Configuration de l'embedder

On choisit ici d'utiliser Ollama comme fournisseur d'embeddings. D'autres fournisseurs peuvent être branchés via une structure de registre.

from langchain_community.embeddings import OllamaEmbeddings

def embedder_ollama(modele="smartwang/bge-large-zh-v1.5-f32.gguf"):
    return OllamaEmbeddings(
        base_url="http://127.0.0.1:11434",
        model=modele,
    )

Indexation dans FAISS

Les fragments nettoyés sont convertis en vecteurs et stockés dans un index FAISS pour une recherche rapide par similarité.

from langchain_community.vectorstores import FAISS

def indexer_documents(chemin_pdf, taille_bloc=800, chevauchement=150):
    fragments = decouper_pdf(chemin_pdf, taille_bloc, chevauchement)
    embeddings = embedder_ollama()
    index = FAISS.from_documents(fragments, embeddings)
    return index

Création du retriever

Le retriever interroge l'index et retourne les documents les plus proches de la question utilisateur.

def creer_retriever(index, k=2):
    return index.as_retriever(search_kwargs={"k": k})

def recuperer_contexte(question, retriever):
    resultats = retriever.invoke(question)
    return [r.page_content for r in resultats]

Configuration du modèle de langue

On connecte ici un modèle Ollama via l'API compatible OpenAI, ce qui permet d'utiliser ChatOpenAI avec l'endpoint local.

from langchain_openai import ChatOpenAI

def creer_llm(modele="qwen2.5:1.5b"):
    return ChatOpenAI(
        api_key="token",
        base_url="http://127.0.0.1:11434/v1",
        model=modele,
        temperature=0,
    )

Génération de réponses structurées

On définit un modèle de sortie structuré et une chaîne LangChain qui injecte le contexte et la question dans un prompt.

from langchain_core.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field

class ReponseContextuelle(BaseModel):
    texte: str = Field(
        description="Réponse concise à la question, fondée uniquement sur le contexte fourni."
    )

def creer_chaine_reponse(llm):
    gabarit = """
    À l'aide du contexte ci-dessous, répondez de manière concise à la question.
    Contexte :
    {context}

    Question : {question}
    """

    prompt = PromptTemplate(
        template=gabarit,
        input_variables=["context", "question"],
    )
    return prompt | llm.with_structured_output(ReponseContextuelle)

def repondre(question, contexte, chaine):
    sortie = chaine.invoke({"question": question, "context": contexte})
    return {
        "question": question,
        "context": contexte,
        "reponse": sortie.texte,
    }

Assemblage du pipelnie

Le script final charge le PDF, indexe les fragments, récupère les passages pertinents et génère une réponse.

chemin_pdf = "data/rag.pdf"

index = indexer_documents(chemin_pdf)
retriever = creer_retriever(index)

question = "Présentez OpenAI."
contexte = recuperer_contexte(question, retriever)

llm = creer_llm()
chaine = creer_chaine_reponse(llm)

print(repondre(question, contexte, chaine))

Étiquettes: langchain rag faiss Ollama Qwen

Publié le 25 juin à 03h53