Notes pratiques : Créer un agent IA auto-corrigé avec Self-RAG et LangGraph
Guide pas à pas des notes pratiques : Créer un agent IA auto-corrigé avec Self-RAG et LangGraph : contrats, vérifications et emplacements de code prêts à l’emploi pour les équipes qui utilisent ce modèle.
Sources: Build a Self-Correcting AI Agent with Self-RAG & LangGraph
Ce guide reconstitue le parcours allant des matières premières à un système fonctionnel pour : Créer un agent IA auto-corrigé avec Self-RAG et LangGraph. L’accent est mis sur des étapes opérationnelles, des vérifications explicites, ainsi que du code que vous pouvez intégrer directement dans un dépôt sans devoir deviner l’intention.
Pourquoi les pipelines RAG standards sont fragiles
Afin de comprendre pourquoi les pipelines RAG standards sont fragiles, il faut définir les entrées, le responsable de chaque étape et les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans avoir à deviner l’état caché. La configuration doit être conservée en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Il est nécessaire de citer les passages qui ont réellement servi de base à la réponse ; sans citations, il est impossible pour les opérateurs de distinguer une hallucination d’un manque d’indexation.
Le pipeline linéaire : Récupérer et supposer
Pour le pipeline linéaire : avant de modifier du code, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure. Citez les passages qui justifient réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
[User Query] ──> [Vector Database] ──> [Top-K Chunks] ──> [LLM Prompt] ──> [Final Answer]
Anatomie d’une défaillance silencieuse
Pour une analyse d’une défaillance silencieuse, il convient de définir les entrées, le responsable de l’étape et les critères de fin avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui fondent réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
La cause racine : la confiance aveugle
Pour en identifier la cause racine : la confiance aveugle, il convient de définir les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque dans l’indexation.
La solution : l’introduction de boucles auto-réfléchissantes
Pour la solution : en introduisant des boucles auto-réfléchissantes, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le parcours passe d’un environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pour la solution : en introduisant des boucles auto-réfléchissantes, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours optimal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie de l’environnement de production.
CT, pas de correction ultérieure en polonais.De la récupération aveugle à l’autoréflexion : le modèle Self-RAG
Lorsque vous travaillez sur « De la récupération aveugle à l’autoréflexion : le modèle Self-RAG », notez d’abord les exigences : entrées requises, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un problème de récupération insuffisante.
Les trois piliers de l’auto-correction
Lorsque vous travaillez sur « Les Trois Piliers de l’Auto-correction », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminaisons partielles silencieuses. Évaluez la capacité de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Le changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.
[ Query ]
│
▼
1. Is Retrieval Needed? ──(No)──► Generate directly from parametric memory
│
(Yes)
▼
[ Retrieve ]
│
▼
2. Are Docs Relevant? ──(No)──► Reformulate search query & retry
│
(Yes)
▼
[ Generate ]
│
▼
3. Is Answer Faithful? ──(No)──► Regenerate or extract better context
│
(Yes)
▼
[ Final Output ]
Les tokens de réflexion en tant que signaux de contrôle
Lorsque vous utilisez les tokens de réflexion comme signaux de contrôle, écrivez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Notez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Mémorisez les instructions système stables et les schémas des outils. Envoyer à nouveau un préambule identique est une cause fréquente de gaspillage. Lorsque vous utilisez les tokens de réflexion comme signaux de contrôle, écrivez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement.
L’architecture de l’autoréflexion
L’architecture de l’autoréflexion fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple réussi, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.
graph TD
Start[User Query] --> DecideNeed{Is Retrieval Needed?}
DecideNeed -- No --> GenerateDirect[Generate Output]
DecideNeed -- Yes --> Retrieve[Retrieve Documents]
Retrieve --> GradeDocs[Grade Document Relevance]
GradeDocs -- Irrelevant --> Fallback[Rewrite Query / Fallback]
GradeDocs -- Relevant --> GenerateRef[Generate Response with Context]
GenerateRef --> GradeFaith[Grade Faithfulness & Hallucinations]
GradeFaith -- Hallucinated --> GenerateRef
GradeFaith -- Faithful --> GradeUtility[Grade Utility]
GradeUtility -- Low Utility --> Rewrite[Refine Response]
GradeUtility -- High Utility --> End[Final Answer]
Comparaison des pipelines et des workflows
Comparer les pipelines et les workflows fonctionne le mieux lorsqu’ils sont considérés comme une surface mesurable. Recueillez un exemple exemplaire, un cas d’échec ainsi que la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des critères de succès et refusez les terminaisons partielles silencieuses. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.
Concevoir avec LangGraph : des chaînes linéaires aux machines d’état
Architecting with LangGraph: From Linear Chains to State Machines fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemple idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration aux environnements partagés. Séparez la politique de segmentation des données de la politique de récupération ; modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. Architecting with LangGraph: From Linear Chains to State Machines fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemple idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.
Linear RAG: [Query] ──> [Retrieve] ──> [Generate] ──> [Response] (No correction)
Self-RAG: [Query] ──> [Retrieve] ──> [Evaluate] ──┬─ (Valid) ──> [Response]
▲ │
└─────── (Invalid) ──────┘ (Loop & Refine)
Les limites des chaînes linéaires
Pour les « Les limites des chaînes linéaires », il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui fondent réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
Démystifier la machine à états de l’IA
Pour démystifier la machine à états de l’IA, il convient de définir les entrées, le responsable de chaque étape ainsi que les critères de sortie avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
┌──────────────────────┐
│ Start │
└──────────┬───────────┘
▼
┌──────────────┐ ┌──────────────────────┐
│ Formulate │◄─┤ Retrieve Docs ├─┐
│ New Query │ └──────────────────────┘ │
└──────▲───────┘ │
│ ▼
┌──────┴───────┐ ┌──────────────────────┐ │
│ Re-Retrieve │ │ Grade Relevance │─┘
└──────────────┘ └──────────┬───────────┘
▼
┌──────────────────────┐
│ Generate Answer │
└──────────┬───────────┘
▼
┌──────────────────────┐
│ Grade Hallucination │
└──────────────────────┘
Dans les composants principaux de LangGraph
Dans le guide « À l’intérieur des composants principaux de LangGraph », il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Dans le guide « À l’intérieur des composants principaux de LangGraph », il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours optimal et les procédures de récupération. Les tentatives de réexécution, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations apportées ultérieurement.
Lorsque vous travaillez sur la mise en œuvre de l’état central du graphe, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts volumineux. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus complexe. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts ; le simple changement de prompts ne résout que rarement un système de récupération insuffisant.
from typing import List, TypedDict
class GraphState(TypedDict):
"""
Represents the central state of our Self-RAG agent.
This TypedDict serves as the single source of truth passed between
each node in the LangGraph execution flow.
"""
question: str # The original user query
generation: str # The most recent answer generated by the LLM
documents: List[str] # Relevant text passages retrieved from our database
steps: List[str] # An audit trail tracking the sequence of execution steps
Construction du graphe, partie 1 : Définition des nœuds d’état et d’action
Lorsque vous travaillez sur « Construire le graphe, partie 1 : Définir les nœuds d’état et d’action », notez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès, et refusez les terminations partielles silencieuses. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Le changement fréquent des prompts résout rarement un système de récupération insuffisant.
Comprendre l’orchestration basée sur l’état
Lorsque vous travaillez sur Understanding State-Based Orchestration, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent de prompts ne résout que rarement un système de récupération insuffisant. Lorsque vous travaillez sur Understanding State-Based Orchestration, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure.
+-------------------------------------------------------------+
| GRAPH STATE |
| - question: str |
| - documents: List[Document] |
| - generation: str |
| - relevance_grade: str |
+-------------------------------------------------------------+
|
+----------------------+----------------------+
| | |
v v v
+--------------+ +--------------+ +--------------+
| Retrieve | | Grade Docs | | Generate |
| Node | | Node | | Node |
+--------------+ +--------------+ +--------------+
Conception du registre d’état partagé
La conception du registre d’état partagé se révèle la plus efficace lorsqu’elle est considérée comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et des notes de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.
from typing import List, Dict, Any
from typing_extensions import TypedDict
from langchain_core.documents import Document
class GraphState(TypedDict):
"""
Represents the state of our Self-RAG graph.
Attributes:
question: The user query string.
documents: A list of retrieved document chunks.
generation: The generated answer from the LLM.
relevance_grade: The evaluation score of our documents ('yes' or 'no').
"""
question: str
documents: List[Document]
generation: str
relevance_grade: str
Le nœud de récupération : interaction avec les bases de connaissances
Le Nœud de récupération : l’interaction avec les bases de connaissances fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définez des critères de succès et refusez toute complétion partielle silencieuse. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.embeddings import FakeEmbeddings
# Initialize a mock vector store to simulate a production knowledge base
embeddings = FakeEmbeddings(size=1536)
vectorstore = InMemoryVectorStore(embeddings)
# Seed our vector store with sample documentation chunks
vectorstore.add_documents([
Document(page_content="LangGraph is a library for building stateful, multi-actor applications with LLMs.", metadata={"source": "docs"}),
Document(page_content="Self-RAG is an architecture that introduces self-reflection and grading into retrieval chains.", metadata={"source": "paper"}),
Document(page_content="Vector databases index text embeddings to enable fast semantic similarity search.", metadata={"source": "tech-blog"})
])
# Create a retriever interface
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
def retrieve(state: GraphState) -> Dict[str, Any]:
"""
Retrieve documents from the vector database based on the current question.
Args:
state (GraphState): The current state of the graph.
Returns:
dict: An update to the state containing the retrieved documents.
"""
print("--- NODE: RETRIEVING DOCUMENTS ---")
question = state["question"]
# Perform the semantic vector search
retrieved_docs = retriever.invoke(question)
# Return the updated document list to the graph state
return {"documents": retrieved_docs}
Le Nœud de génération : synthèse consciente du contexte
Le nœud Génération : Synthèse consciente du contexte fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût en tokens ou requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. Le nœud Génération : Synthèse consciente du contexte fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours réussi et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.language_models.fake import FakeListLLM
# Set up a mock LLM that simulates generating structured responses
# In production, replace this with: from langchain_openai import ChatOpenAI
llm = FakeListLLM(responses=[
"Self-RAG is an advanced architecture that uses self-reflection and document grading to improve retrieval accuracy."
])
def generate(state: GraphState) -> Dict[str, Any]:
"""
Generate an answer using the retrieved documents as grounding context.
Args:
state (GraphState): The current state of the graph.
Returns:
dict: An update to the state containing the generated answer.
"""
print("--- NODE: GENERATING ANSWER ---")
question = state["question"]
documents = state["documents"]
# Format the retrieved documents into a single context block
context = "\n\n".join([doc.page_content for doc in documents])
# Construct the instruction prompt
prompt = ChatPromptTemplate.from_template(
"You are an assistant for question-answering tasks.\n"
"Use the following pieces of retrieved context to answer the question.\n"
"If you do not know the answer, say that you do not know.\n"
"Question: {question}\n"
"Context: {context}\n"
"Answer:"
)
# Format the prompt with our state variables and run the model
chain = prompt | llm
generation = chain.invoke({"question": question, "context": context})
# Save the generated response to the state
return {"generation": generation}
Le nœud Documents d’évaluation : L’inspecteur de qualité
Pour le nœud des documents de grade : l’inspecteur qualité doit définir les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Préférer des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citer les passages qui fondent réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
+-------------------+
| Retrieved Docs |
+-------------------+
|
v
+-----------------------+
| Is Doc Relevant? |
+-----------------------+
/ \
Yes / \ No
/ \
v v
+-------------------+ +------------------+
| Keep in State | | Filter / Discard |
| (Pass to Gen) | | (Trigger Loop) |
+-------------------+ +------------------+
from pydantic import BaseModel, Field
from langchain_core.output_parsers import JsonOutputParser
class DocumentGrade(BaseModel):
"""Binary score for document relevance assessment."""
binary_score: str = Field(
description="Relevance score for the document. Must be 'yes' or 'no'."
)
def grade_documents(state: GraphState) -> Dict[str, Any]:
"""
Assess whether the retrieved documents are relevant to the user question.
Filters out any irrelevant documents from the state.
Args:
state (GraphState): The current state of the graph.
Returns:
dict: Updated documents list and a global relevance grade flag.
"""
print("--- NODE: GRADING RETRIEVED DOCUMENTS ---")
question = state["question"]
documents = state["documents"]
# Use a mock parser to simulate LLM grading
# In production, use: llm.with_structured_output(DocumentGrade)
parser = JsonOutputParser(pydantic_object=DocumentGrade)
filtered_docs = []
has_relevant_doc = "no"
# Evaluate each document individually
for doc in documents:
# Construct a strict grading prompt
grade_prompt = (
f"Question: {question}\n"
f"Document: {doc.page_content}\n"
"Assess if this document is relevant to the question. Output JSON with key 'binary_score' as 'yes' or 'no'."
)
# Simulating LLM evaluation logic (in production, run the LLM chain here)
# For our mock run, if the query terms overlap with the document, mark 'yes'
is_relevant = any(term.lower() in doc.page_content.lower() for term in question.split())
score = "yes" if is_relevant else "no"
if score == "yes":
print(f" -> DOCUMENT GRADING: [RELEVANT] - Source: {doc.metadata.get('source')}")
filtered_docs.append(doc)
has_relevant_doc = "yes"
else:
print(f" -> DOCUMENT GRADING: [NOT RELEVANT] - Source: {doc.metadata.get('source')}")
# Update state with filtered documents and set the control flag
return {
"documents": filtered_docs,
"relevance_grade": has_relevant_doc
}
Construction du graphe, partie 2 : Connexion des décisions avec des arêtes conditionnelles
Pour la construction du graphe, partie 2 : Associer les décisions aux arêtes conditionnelles, il convient de définir les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
+------------------+
| route_question |
+--------+---------+
|
+--------------+--------------+
| |
[web_search] [retrieve]
| |
+--------------> +------------+-------------+
| grade_documents |
+------------+--------------+
|
+-----+-----+
| Decision |
+-----+-----+
|
+-----------------+-----------------+
| |
(transform_query) (generate)
^ |
| +------+------+
| | Decision |
| +------+------+
| |
+--- [hallucination / unsat] -------+------> [__end__]
Le routage du point d’entrée : examiner la requête en premier
Pour le routeur de point d’entrée : en inspectant d’abord la requête, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût évite les factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pour le routeur de point d’entrée : en inspectant d’abord la requête, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie de
le produit, et non une mise en forme ultérieure.# router.py
from typing import Literal
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from state import AgentState
# Define the structured output schema for our router
class RouteQuery(BaseModel):
"""Route a user query to the most authoritative datasource."""
datasource: Literal["vectorstore", "web_search"] = Field(
...,
description="Given a user question choose to route it to web search or a vectorstore.",
)
def route_question(state: AgentState) -> str:
"""
Routes the initial question to either the vectorstore retrieval node or web search.
Args:
state (AgentState): The current graph state containing the user query.
Returns:
str: The next node to execute ("vectorstore" or "web_search").
"""
print("--- ROUTING QUESTION ---")
question = state["question"]
# Initialize our LLM with structured output mapping to our Pydantic schema
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_router = llm.with_structured_output(RouteQuery)
# Create the routing system prompt
system_prompt = """You are an expert router directing user questions to a vectorstore or web search.
The vectorstore contains highly technical documentation regarding LangGraph, LangChain, and state machines.
Use the vectorstore for these specialized topics. Otherwise, use web_search for general knowledge or current events."""
route_prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{question}"),
])
# Run the router chain
router_chain = route_prompt | structured_router
decision = router_chain.invoke({"question": question})
if decision.datasource == "web_search":
print("--- ROUTING TO WEB SEARCH ---")
return "web_search"
print("--- ROUTING TO VECTORSTORE ---")
return "retrieve"
The Context Gate : Évaluer la pertinence des documents
Lorsque vous travaillez sur The Context Gate : Évaluer la pertinence des documents, notez d’abord les éléments requis : les entrées nécessaires, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération insuffisant.
# context_gate.py
from state import AgentState
def decide_to_generate(state: AgentState) -> str:
"""
Evaluates the document grading state to choose the next step.
If any documents were graded as relevant, we proceed to answer generation.
If all documents were deemed irrelevant, we rewrite the query to try again.
Args:
state (AgentState): The current state of the agent.
Returns:
str: Next node to execute ("generate" or "transform_query").
"""
print("--- ASSESSING DOCUMENT RELEVANCE ---")
# Retrieve the decision variable set by the grading node
run_web_search = state.get("run_web_search", False)
if run_web_search:
# No documents were relevant, we must rewrite and search the web
print("--- DECISION: ALL DOCUMENTS IRRELEVANT, ROUTING TO QUERY TRANSFORMATION ---")
return "transform_query"
# We found relevant documents, we can proceed safely to generation
print("--- DECISION: RELEVANT DOCUMENTS FOUND, ROUTING TO GENERATE ---")
return "generate"
The Self-Correction Loop : Vérifier la fidélité et l’utilité
Lorsque vous travaillez sur « The Self-Correction Loop: Verifying Faithfulness and Utility », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Le simple changement de prompts ne résout que rarement un système de récupération insuffisant.
+-------------------------+
| Answer Generated |
+------------+------------+
|
v
+--------------------------------+
| Is the answer grounded in |
| retrieved context? |
+----------------+---------------+
|
+---------------+---------------+
| |
[No] [Yes]
| |
v v
+-------------------+ +---------------------+
| Re-route to: | | Is the answer |
| "generate" | | actually useful? |
+-------------------+ +----------+----------+
|
+--------------+--------------+
| |
[No] [Yes]
| |
v v
+-------------------+ +------------------+
| Re-route to: | | Re-route to: |
| "transform_query" | | "__end__" |
+-------------------+ +------------------+
# self_correction.py
from typing import Literal
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from state import AgentState
class GradeHallucinations(BaseModel):
"""Binary score for hallucination present in generation."""
binary_score: Literal["yes", "no"] = Field(
...,
description="Answer is grounded in the facts, 'yes' or 'no'",
)
class GradeAnswer(BaseModel):
"""Binary score to assess if answer addresses question."""
binary_score: Literal["yes", "no"] = Field(
...,
description="Answer addresses the question, 'yes' or 'no'",
)
def grade_generation_v_documents_and_question(state: AgentState) -> str:
"""
Grades the generation for hallucinations and utility.
Args:
state (AgentState): The current graph state.
Returns:
str: Next step path ("supported" -> check utility, "not supported" -> regenerate, "not useful" -> rewrite query)
"""
print("--- CHECKING FOR HALLUCINATIONS ---")
question = state["question"]
documents = state["documents"]
generation = state["generation"]
# Initialize evaluator LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# ----------------------------------------
# STEP 1: Groundedness (Hallucination) Check
# ----------------------------------------
hallucination_grader = llm.with_structured_output(GradeHallucinations)
hallucination_system_prompt = """You are an objective grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts.
Give a binary score of 'yes' if the generation is strictly grounded in and supported by the provided facts. Otherwise, give 'no'."""
hallucination_prompt = ChatPromptTemplate.from_messages([
("system", hallucination_system_prompt),
("human", "Set of facts: \n\n {documents} \n\n LLM generation: {generation}"),
])
# Format document contents into a single string context
doc_context = "\n\n".join([doc.page_content for doc in documents])
hallucination_chain = hallucination_prompt | hallucination_grader
res = hallucination_chain.invoke({"documents": doc_context, "generation": generation})
if res.binary_score == "no":
print("--- DECISION: HALLUCINATION DETECTED! RE-TRYING GENERATION ---")
return "generate"
print("--- DECISION: GENERATION IS GROUNDED IN DOCUMENTS ---")
# ----------------------------------------
# STEP 2: Utility (Answer Relevance) Check
# ----------------------------------------
answer_grader = llm.with_structured_output(GradeAnswer)
answer_system_prompt = """You are an objective grader assessing whether an LLM generation addresses and resolves the user's question.
Give a binary score of 'yes' if the answer directly addresses and completely answers the user's question. Otherwise, give 'no'."""
answer_prompt = ChatPromptTemplate.from_messages([
("system", answer_system_prompt),
("human", "User question: \n\n {question} \n\n LLM generation: {generation}"),
])
answer_chain = answer_prompt | answer_grader
res_answer = answer_chain.invoke({"question": question, "generation": generation})
if res_answer.binary_score == "yes":
print("--- DECISION: ANSWER IS USEFUL AND ACCURATE. ENDING GRAPH ---")
return "useful"
print("--- DECISION: ANSWER DOES NOT FULLY ADDRESS QUESTION. REWRITING QUERY ---")
return "not useful"
Assemblage et compilation de StateGraph
Lorsque vous travaillez sur l’assemblage et la compilation du StateGraph, notez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération insuffisant. Lorsque vous travaillez sur l’assemblage et la compilation du StateGraph, notez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours optimal et les procédures de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure.
# graph_builder.py
from langgraph.graph import END, StateGraph
from state import AgentState
# Import node functions from Part 1
from nodes import retrieve, generate, transform_query, web_search, grade_documents
# Import conditional routing edges
from router import route_question
from context_gate import decide_to_generate
from self_correction import grade_generation_v_documents_and_question
# Initialize our state-based graph
workflow = StateGraph(AgentState)
# ----------------------------------------
# 1. Register All Computational Nodes
# ----------------------------------------
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("generate", generate)
workflow.add_node("transform_query", transform_query)
workflow.add_node("web_search", web_search)
# ----------------------------------------
# 2. Configure Node Routing & Transitions
# ----------------------------------------
# The entry point of our execution is dynamic, decided by the routing edge
workflow.set_conditional_entry_point(
route_question,
{
"web_search": "web_search",
"retrieve": "retrieve",
},
)
# After retrieval, we must immediately grade our retrieved documents
workflow.add_edge("retrieve", "grade_documents")
# Once documents are graded, the Context Gate decides where to go
workflow.add_conditional_edges(
"grade_documents",
decide_to_generate,
{
"transform_query": "transform_query",
"generate": "generate",
},
)
# If query transformation occurs, we execute a web search to gather new data
workflow.add_edge("transform_query", "web_search")
# After web searching, we feed those newly acquired documents back to the grade step
workflow.add_edge("web_search", "grade_documents")
# Once generation occurs, our Self-Correction edge decides the final quality check
workflow.add_conditional_edges(
"generate",
grade_generation_v_documents_and_question,
{
"generate": "generate", # Loop back to fix hallucination
"not useful": "transform_query", # Loop back to rewrite query and search
"useful": END, # Exit cleanly
},
)
# ----------------------------------------
# 3. Compile and Run the Graph
# ----------------------------------------
app = workflow.compile()
# visualize.py
from graph_builder import app
try:
# Print the plain text visualization of the state machine architecture
app.get_graph().print_ascii()
except Exception as e:
print(f"ASCII generation failed: {e}")
+----------------+
| __start__ |
+-------+--------+
|
+---------+---------+
v v
+------------+ +------------+
| web_search | | retrieve |
+-----+------+ +-----+------+
| |
| v
| +-----+------+
| | grade_docs |
| +-----+------+
| |
+---------+---------+
|
v
+---------+---------+
| decide_to_gen |
+----+---------+----+
| |
+----------+ +----------+
v v
+-----+------+ +-----+------+
| transform_q| | generate |
+-----+------+ +-----+------+
| |
| v
| +---------+---------+
| | self_correct |
| +----+----+----+----+
| | | |
+--------------------------+ | +---> [__end__]
v
(regenerate)
Synthèse d’ingénierie
La synthèse d’ingénierie fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.
Applications dans le monde réel
Real-World Applications fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemple exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des critères de succès et refusez toute mise en œuvre partielle silencieuse. Séparez la politique de segmentation des données de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.
Bots avancés de support client
Les bots avancés de support client fonctionnent le mieux lorsqu’ils sont considérés comme un domaine mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le champ d’application. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le système passe de la démonstration aux environnements partagés. Séparez la politique de segmentation des données de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. Les bots avancés de support client fonctionnent le mieux lorsqu’ils sont considérés comme un domaine mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le champ d’application. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures.
Systèmes automatisés de vérification des faits
Pour les systèmes automatisés de vérification des faits, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
Moteurs de recommandations dynamiques
Pour les moteurs de recommandation dynamiques, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute complétion partielle silencieuse. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
Aides à la recherche scientifique
Pour les assistants de recherche scientifique, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pour les assistants de recherche scientifique, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours optimal et les procédures de récupération. Les tentatives de réexécution, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations apportées ultérieurement.
Quand utiliser quelle approche ?
Lorsque vous travaillez sur la question « Quand utiliser quelle approche ? », notez d’abord les éléments requis : les entrées nécessaires, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.
Règles de sécurité en production : coûts, latence et observabilité
Lorsque vous travaillez sur Production Guardrails: Costs, Latency, and Observability, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Le changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.
Équilibrer coût et latence grâce à un routage asymétrique
Lorsque vous travaillez sur l’équilibrage des coûts et de la latence avec un routage asymétrique, notez d’abord les spécifications : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le chemin passe de l’environnement de démonstration à des environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent de prompts ne résout que rarement un système de récupération insuffisant. Lorsque vous travaillez sur l’équilibrage des coûts et de la latence avec un routage asymétrique, notez d’abord les spécifications : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement.
Disjoncteurs de freinage d’urgence dans l’état LangGraph
Les disjoncteurs de freinage d’urgence dans l’état LangGraph fonctionnent le mieux lorsqu’ils sont considérés comme une surface mesurable. Capturez un transcript idéal, un cas de défaillance et la note de réversion avant d’élargir le champ d’application. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, la défaillance doit pointer vers une seule responsabilité plutôt qu’un processus embrouillé. Séparez la politique de segmentation des données de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.
from typing import Dict, TypedDict, Literal
# Define graph state with a loop counter to act as a circuit breaker
class GraphState(TypedDict):
question: str
generation: str
documents: list[str]
loop_step: int # Tracks iterations to prevent runaway execution costs
MAX_RETRIES = 3
def grade_generation_route(state: GraphState) -> Literal["web_search", "finalize", "max_retries_exceeded"]:
"""
Evaluates the quality of generation and routes the agent accordingly.
Why: Acts as a deterministic circuit breaker when LLMs get stuck in correction loops.
"""
current_step = state.get("loop_step", 0)
# 1. Evaluate circuit breaker before making any expensive LLM calls
if current_step >= MAX_RETRIES:
return "max_retries_exceeded"
# 2. Simulated grader check (In production, this queries your grading model)
is_accurate = False # Assume we failed the grade in this step
if is_accurate:
return "finalize"
else:
# Route back for retrieval/generation rewrite
return "web_search"
Prompts structurés pour une évaluation déterministe
Les prompts structurés pour une évaluation déterministe fonctionnent le mieux lorsqu’ils sont considérés comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définites des critères de succès et refusez toute complétion partielle silencieuse. Fixez un budget de tokens par tour et par session. Les outils agents élargissent lourdement le contexte ; des plafonds stricts empêchent que les démonstrations ne se transforment en factures inattendues.
from langchain_core.pydantic_v1 import BaseModel, Field
class GradeDocument(BaseModel):
"""Binary score for document relevance assessment."""
binary_score: str = Field(
description="Relevance score for the document. Must be 'yes' or 'no'."
)
explanation: str = Field(
description="A brief, single-sentence justification for the given score."
)
Suivi de l’exécution du graphe avec des moteurs d’observabilité
Le suivi de l’exécution du graphe avec des moteurs d’observabilité fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de rollback avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. Le suivi de l’exécution du graphe avec des moteurs d’observabilité fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de rollback avant d’élargir le périmètre. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.
[User Query] ──> [retrieve] ──> [grade_documents] (Score: No)
│
▼
[finalize] <── [generate] <── [web_search] (Updated State: Loop Count = 1)
Ce qu’un flux de travail piloté par l’état révèle
Pour ce qu’un flux de travail piloté par l’état révèle, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
Liste de contrôle opérationnelle
La liste de contrôle opérationnelle fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre.
Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.
Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.
Imposez une approbation humaine pour les opérations qui entraînent des dépenses ou modifient des données de production. Une connexion en temps de compilation ne garantit pas une couverture complète des besoins métier.
Rédigez un guide de procédures succinct : comment rotationner les clés, comment vider la file d’attente, comment annuler la dernière ingestion.
Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définez des vérifications de succès, et refusez toute mise à jour partielle silencieuse.
Au préalable de promouvoir l’ensemble du système, figez les versions, conservez une transcription exemplaire pour le parcours critique, et confirmez les étapes de réversion. Les environnements partagés nécessitent des limites de fréquence, des vérifications d’affectation, ainsi qu’un responsable clair pour la rotation des secrets. Préférez une fiabilité solide à des démonstrations brillantes mais ponctuelles.
Note de lot pour eeb69aedbcbc : ne pas inclure les clés du fournisseur dans le répertoire, fixer une limite pour les tokens par session, et stocker les transcriptions à côté des fichiers d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.