Accueil / Articles

Agent de recherche LangGraph avec récupération via LlamaIndex et codage HITL

Le découpage en fonction du format, l’acheminement structuré des intentions, les récupérateurs spécialisés en parallèle et la programmation d’interface ligne de commande contrôlée par des interruptions permettent de transformer les documents en expériences exécutables de manière sécurisée.

Éditeur : REACTAPP.TOP4 min de lecture839 mots

Sources: Building a Multi-Agent Research and Coding Assistant with LangGraph and LlamaIndex- Week3 of Learning AI engineering

Transformer des articles académiques en expériences fonctionnelles consiste généralement en un processus lent comprenant la lecture, la synthèse, le codage et la débogage. Un système multi-agents à état peut assumer une plus grande partie de cette charge : LangGraph pour le routage, LlamaIndex pour la recherche spécialisée, ainsi qu’un outil de codage en ligne de commande soumis à l’approbation humaine.

1. Ingestion intelligente des données : tous les fragments ne sont pas égaux

Découper aveuglément des PDF, du Python et des CSV selon une seule stratégie nuit à la qualité de la recherche. Avant d’indexer dans ChromaDB, procédez à un découpage adapté au format : segments narratifs pour le texte prosaïque, blocs logiques pour le code, et tronçons tenant compte des lignes pour les tableaux :

# 5. Setup Chunkers
pdf_chunker = SentenceSplitter(chunk_size=512, chunk_overlap=50)
python_chunker = CodeSplitter(language='python')
csv_reader = CSVReader(concat_rows=False)

# 6. Route and Chunk Data
for doc in documents:
    file_name = doc.metadata.get("file_name", "")
    if file_name.endswith(".pdf"):
        nodes = pdf_chunker.get_nodes_from_documents([doc])
    elif file_name.endswith(".py"):
        nodes = python_chunker.get_nodes_from_documents([doc])
    elif file_name.endswith(".csv"):
        # CSVs are chunked row-by-row by our custom reader
        all_nodes.append(doc)

Les embeddings (par exemple all-minilm d’Ollama) reflètent alors la structure plutôt que des fenêtres de caractères arbitraires.

2. Le cerveau LangGraph : classification des intentions

LangChain fournit des modèles, des prompts et des outils ; LangGraph gère le flux de contrôle. Un dictionnaire State partagé permet de parcourir chaque nœud. Le routage par mots-clés en texte libre est fragile, c’est pourquoi des sorties structurées via Pydantic imposent une intention Literal provenant d’un modèle local tel que llama3.2:3b:

class IntentClassifier(BaseModel):
    message_intent: Literal['chat', 'code', 'knowledge'] = Field(
        ...,
        description="Classify the user intent. 'code' for implementation, 'knowledge' for research details, 'chat' for general tasks."
    )

Les arêtes conditionnelles permettent ensuite d’envoyer les données de manière fiable, sans avoir à parser du charabia.

3. Le chemin de la connaissance : récupération parallèle

Les questions de recherche complexes ne correspondent rarement à une seule recherche par similarité. On peut étendre le chemin de la connaissance à quatre agents LlamaIndex en même temps — abstract_agent, methodology_agent, limitations_agent et conclusion_agent — chacun avec une requête ciblée. Les résultats partiels sont fusionnés à l’aide du réducteur operator.add de LangGraph afin que les auteurs ne se contredisent pas :

class State(TypedDict):
    messages: Annotated[list, add_messages]
    message_intent: str | None
    next_node: str | None
    context: Annotated[list[str], operator.add] # Safely appends parallel findings

analyst_agent lit le contexte combiné et synthétise les avantages, inconvénients et lacunes.

4. Relier la recherche à l’action : codage avec intervention humaine

interrupt() pour demander une approbation :

def accept_coding(state: State):
    user_prompt = state['messages'][-1].content
    decision = interrupt(f'About to run Claude Code with request:\n\n{user_prompt}\n\nApprove? (yes/no, or type a revised request)')

    text = str(decision).strip().lower()

    if text in ['y', 'yes', 'approve', 'ok']:
        return {'next_node': 'coding_agent'}
    if text in ['n', 'no', 'deny', 'cancel']:
        return {'messages': [{'role': 'assistant', 'content': 'Coding request denied.'}], 'next_node': 'denied'}

    # Allows the user to dynamically edit the prompt mid-flight
    return {'messages': [{'role': 'user', 'content': text}], 'next_node': 'accept_coding'}

Conclusion

Leçons opérationnelles

  • Un découpage adapté au format se révèle utile dès que la récupération de code cesse de renvoyer des fonctions incomplètes.
  • Les littéraux d’intention structurés surpassent les routeurs basés sur des expressions régulières lorsque les utilisateurs reformulent le même objectif de cinq manières différentes.
  • Les récupérateurs spécialisés en parallèle nécessitent des réducteurs ; le principe du dernier auteur qui modifie gagne constitue une faille silencieuse entraînant une perte de données.
  • L’HITL sur les commandes shell est indispensable en dehors d’un environnement sandbox temporaire.
  • Garantissez que les index de LlamaIndex soient versionnés avec l’hash du corpus de documents afin que la méthodologie et les limites restent cohérentes après réingestion.
  • Le graphe représente le produit final : les nœuds sont remplaçables, mais c’est le contrat de l’état ainsi que les mécanismes d’approbation qui rendent le processus de transformation de la recherche en code suffisamment sûr pour être exécuté quotidiennement.

    Démarche de bout en bout

    1. Ingérez les documents et les notebooks à l’aide du séparateur adapté au format ; reconstruisez la collection Chroma lorsque les sources changent.
    2. Classifiez l’énoncé de l’utilisateur en catégorie Connaissances ou Programmation (ou selon des catégories plus fines à mesure que le produit évolue).
  • Concernant les connaissances, on les distribue aux quatre récupérateurs spécialisés, on les réduit à l’aide de operator.add, puis on les synthétise via l’analyste.
  • Pour le codage, on rédige une commande CLI, on demande une approbation avant d’exécuter, on ne l’exécute que dans le sandbox, et on renvoie stdout/stderr à l’état du chat.
  • On conserve les points de contrôle du graphe au cas où des sessions de recherche longues devraient reprendre après le sommeil du ordinateur portable.
  • Ce cycle suffit pour remplacer une pile de cahiers de notes ad hoc par une interface d’agent fiable et durable.