Inicio / Artículos

Notas prácticas: Crea un agente de IA autocorregible con Self-RAG y LangGraph

Guía paso a paso para utilizar las notas prácticas: Crea un agente de IA autocorregible con Self-RAG y LangGraph: contratos, verificaciones y espacios para código listos para usar por parte de los equipos que implementan este patrón.

Editor: REACTAPP.TOP30 min de lectura6935 palabras

Fuentes: Build a Self-Correcting AI Agent with Self-RAG & LangGraph

Esta guía reconstruye el proceso desde las materias primas hasta un sistema funcional para: Construir un agente de IA autocorregible con Self-RAG y LangGraph. El enfoque está en pasos operativos claros, verificaciones explícitas y código que se puede incorporar directamente a un repositorio sin necesidad de adivinar la intención.

Por qué los pipelines RAG estándar son frágiles

Para entender por qué los pipelines RAG estándar son frágiles, se deben definir las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. La configuración debe mantenerse separada del código de la aplicación; los archivos de entorno, almacenes de secretos y flags de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. También es necesario citar los fragmentos que realmente sustentan la respuesta, ya que sin ellas los operadores no pueden distinguir entre alucinaciones y fallos en el indexado.

El pipeline lineal: recuperar e asumir

Para la pipeline lineal: “Obtener e asumir” define las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.

[User Query] ──> [Vector Database] ──> [Top-K Chunks] ──> [LLM Prompt] ──> [Final Answer]

Anatomía de un fallo silencioso

En “Anatomía de un fallo silencioso”, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea desde un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando una tarea falla, el fallo debe indicar una única responsabilidad y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre una alucinación y una laguna en el indexado.

La causa raíz: confianza ciega

Para abordar la causa raíz: Confianza ciega, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.

La solución: Introducción de bucles autorreflexivos

Para la solución: al introducir bucles autorreflexivos, se deben definir las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea desde un punto de control conocido sin tener que adivinar el estado oculto. Se deben registrar los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Se deben citar los pasajes que realmente sustentan la respuesta; sin ellas, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado. Para la solución: al introducir bucles autorreflexivos, se deben definir las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea desde un punto de control conocido sin tener que adivinar el estado oculto. Se deben documentar tanto el camino óptimo como los procedimientos de recuperación. Las reintentos, las verificaciones humanas y el manejo de mensajes no entregados forman parte del proceso en producción.

CT, no más tarde en polaco.

De la recuperación ciega a la autorreflexión: el patrón Self-RAG

Al trabajar en “De la recuperación ciega a la autorreflexión: el patrón Self-RAG”, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

Los tres pilares de la autocorrección

Al trabajar en “Los tres pilares de la autorecorrección”, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Considere esta etapa como un contrato entre los datos de entrada y las salidas validadas. Asigne nombres a los artefactos, defina las verificaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

       [ 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 ]

Tokens de reflexión como señales de control

Al trabajar con tokens de reflexión como señales de control, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registra los tiempos y el costo de los tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Almacena en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de desperdicio. Al trabajar con tokens de reflexión como señales de control, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documenta tanto el camino óptimo como el de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

La arquitectura de la autorreflexión

La Arquitectura de la Auto Reflexión funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

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]

Comparación de pipelines y flujos de trabajo

Comparar pipelines y flujos de trabajo funciona mejor cuando se consideran como una superficie medible. Capture un registro ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

Arquitectura con LangGraph: De cadenas lineales a máquinas de estado

Architecting with LangGraph: From Linear Chains to State Machines funciona mejor cuando se trata como una superficie medible. Capture un transcripto ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Separe la política de particionamiento del contenido de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. Architecting with LangGraph: From Linear Chains to State Machines funciona mejor cuando se trata como una superficie medible. Capture un transcripto ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras realizadas posteriormente.

Linear RAG:    [Query] ──> [Retrieve] ──> [Generate] ──> [Response] (No correction)
Self-RAG:      [Query] ──> [Retrieve] ──> [Evaluate] ──┬─ (Valid) ──> [Response]
                              ▲                        │
                              └─────── (Invalid) ──────┘ (Loop & Refine)

Los límites de las cadenas lineales

Para trabajar con los límites de las cadenas lineales, se deben definir las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Es preferible utilizar unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Se deben citar los pasajes que realmente sustentan la respuesta; sin ellas, los operadores no pueden distinguir entre alucinaciones y fallos en el indexado.

Desmitificando la máquina de estados de la IA

Para desmitificar la máquina de estados de IA, defina las entradas, el responsable de cada paso y los criterios de salida antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.

                  ┌──────────────────────┐
                  │        Start         │
                  └──────────┬───────────┘
                             ▼
┌──────────────┐  ┌──────────────────────┐
│  Formulate   │◄─┤    Retrieve Docs     ├─┐
│  New Query   │  └──────────────────────┘ │
└──────▲───────┘                           │
       │                                   ▼
┌──────┴───────┐  ┌──────────────────────┐ │
│  Re-Retrieve │  │   Grade Relevance    │─┘
└──────────────┘  └──────────┬───────────┘
                             ▼
                  ┌──────────────────────┐
                  │    Generate Answer   │
                  └──────────┬───────────┘
                             ▼
                  ┌──────────────────────┐
                  │  Grade Hallucination │
                  └──────────────────────┘

Dentro de los componentes principales de LangGraph

En “Dentro de los componentes principales de LangGraph”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el flujo pasa de entornos de demostración a entornos compartidos. Cite los pasajes que realmente sirvieron de base para la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado. En “Dentro de los componentes principales de LangGraph”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras realizadas posteriormente.

Al trabajar en la implementación del estado gráfico central, primero anote el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mida el rendimiento de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

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

Construyendo el grafo, parte 1: Definir nodos de estado y acción

Al trabajar en “Construyendo el grafo, Parte 1: Definición de nodos de estado y acción”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

Comprensión de la orquestación basada en estado

Al trabajar en “Understanding State-Based Orchestration”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Mida la precisión en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en “Understanding State-Based Orchestration”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras adicionales realizadas posteriormente.

+-------------------------------------------------------------+
|                         GRAPH STATE                         |
|  - question: str                                            |
|  - documents: List[Document]                                |
|  - generation: str                                          |
|  - relevance_grade: str                                     |
+-------------------------------------------------------------+
                              |
       +----------------------+----------------------+
       |                      |                      |
       v                      v                      v
+--------------+       +--------------+       +--------------+
| Retrieve     |       | Grade Docs   |       | Generate     |
| Node         |       | Node         |       | Node         |
+--------------+       +--------------+       +--------------+

Diseño del registro de estado compartido

El diseño del registro de estado compartido funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

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

El nodo de recuperación: interacción con bases de conocimiento

El nodo de recuperación: La interacción con las bases de conocimiento funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

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}

El nodo de generación: Síntesis consciente del contexto

El nodo Generar: Síntesis consciente del contexto funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. El nodo Generar: Síntesis consciente del contexto funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son ajustes realizados posteriormente.

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}

El nodo Documentar calificaciones: El inspector de calidad

Para el nodo de documentos de calificación: El inspector de calidad debe definir las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Es preferible utilizar unidades pequeñas y verificables en lugar de scripts extensos. Cuando una etapa falla, el error debe indicar una única responsabilidad y no un proceso complicado. Se deben citar los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre una alucinación y una laguna en el indexado.

                               +-------------------+
                               |  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
    }

Construyendo el grafo, parte 2: Conectar decisiones con aristas condicionales

Para construir el grafo, parte 2: Conexión de decisiones con aristas condicionales, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.

                  +------------------+
                  |  route_question  |
                  +--------+---------+
                           |
            +--------------+--------------+
            |                             |
     [web_search]                    [retrieve]
            |                             |
            +--------------> +------------+-------------+
                             |     grade_documents       |
                             +------------+--------------+
                                          |
                                    +-----+-----+
                                    |  Decision |
                                    +-----+-----+
                                          |
                        +-----------------+-----------------+
                        |                                   |
                  (transform_query)                     (generate)
                        ^                                   |
                        |                            +------+------+
                        |                            |  Decision   |
                        |                            +------+------+
                        |                                   |
                        +--- [hallucination / unsat] -------+------> [__end__]

El enrutador de punto de entrada: Inspeccionar primero la consulta

Para el Enrutador de Punto de Entrada: Inspeccionar primero la consulta. Defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado. Para el Enrutador de Punto de Entrada: Inspeccionar primero la consulta. Defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte de ello.

producto, no la pulida posterior.

# 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: Evaluando la relevancia del documento

Al trabajar en The Context Gate: Evaluando la relevancia del documento, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado. Mida la recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

# 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: Verificando la fidelidad y la utilidad

Al trabajar en “The Self-Correction Loop: Verifying Faithfulness and Utility”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina las verificaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

                          +-------------------------+
                          |   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"

Ensamblando y compilando el StateGraph

Al trabajar en el ensamblaje y compilación de StateGraph, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en el ensamblaje y compilación de StateGraph, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

# 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)

Síntesis de ingeniería

La síntesis de ingeniería funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

Aplicaciones en el mundo real

“Aplicaciones en el mundo real” funciona mejor cuando se trata como una superficie medible. Capture un registro ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

Robots avanzados de soporte al cliente

Los bots avanzados de soporte al cliente funcionan mejor cuando se consideran como un área medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. Los bots avanzados de soporte al cliente funcionan mejor cuando se consideran como un área medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de correos no entregados forman parte del producto, no son mejoras posteriores.

Sistemas automatizados de verificación de hechos

Para los sistemas automatizados de verificación de hechos, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.

Motores de recomendación dinámicos

Para los motores de recomendación dinámicos, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sirvieron de base para la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.

Asistentes de Investigación Científica

Para los asistentes de investigación científica, definan las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registren los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Citen los pasajes que realmente sirvieron de base para la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado. Para los asistentes de investigación científica, definan las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documenten tanto la ruta óptima como la ruta de recuperación. Los intentos repetidos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras realizadas posteriormente.

¿Cuándo debería usar cada enfoque?

Al abordar la pregunta de ¿Cuándo debería usar cada enfoque?, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación ayuda a mantener honestos los cambios posteriores en el código. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

Límites en producción: costos, latencia y observabilidad

Al trabajar en “Production Guardrails: Costs, Latency, and Observability”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina comprobaciones de éxito y evite completaciones parciales silenciosas. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

Equilibrar costo y latencia mediante enrutamiento asimétrico

Al trabajar en el equilibrio entre costo y latencia con enrutamiento asimétrico, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando la ruta pasa de un entorno de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en el equilibrio entre costo y latencia con enrutamiento asimétrico, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

Disyuntores de parada forzosa en el estado de LangGraph

Los disyuntores de parada forzosa en el estado de LangGraph funcionan mejor cuando se tratan como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando falla un paso, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

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 estructurados para una calificación determinista

Los prompts estructurados para una calificación determinista funcionan mejor cuando se consideran como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de manera excesiva; los límites máximos evitan que las demostraciones se conviertan en facturas inesperadas.

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."
    )

Rastreo de la ejecución del grafo con motores de observabilidad

El seguimiento de la ejecución del grafo con motores de observabilidad funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el flujo pasa de entornos de demostración a entornos compartidos. Separe la política de particionamiento de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. El seguimiento de la ejecución del grafo con motores de observabilidad funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente junto con los resultados positivos también los caminos de recuperación. Las reintentos, las verificaciones humanas y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

[User Query] ──> [retrieve] ──> [grade_documents] (Score: No)
                                      │
                                      ▼
[finalize] <── [generate] <── [web_search] (Updated State: Loop Count = 1)

Lo que revela un flujo de trabajo basado en estado

Para lo que revela un flujo de trabajo impulsado por el estado, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa desde un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando una etapa falla, el error debe indicar una única responsabilidad y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.

Lista de verificación operativa

La lista de verificación operativa funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance.

Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar que los operadores puedan auditar sin tener que leer todo el sistema.

Separe la política de particionamiento de la política de recuperación. Cambiar una no debería obligar a reescribir la otra cuando cambian las métricas de calidad.

Coloque la aprobación humana en aquellos procesos que generan gastos o modifican datos de producción. La configuración en tiempo de compilación no equivale a una solución completa para el negocio.

Escriba un manual breve: cómo rotar claves, cómo vaciar la cola y cómo revertir la última operación de ingestión.

Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas.

Antes de promocionar la solución, congele las versiones, capture una transcripción de referencia para el camino crítico y confirme los pasos de reversión. Los entornos compartidos necesitan límites de velocidad, verificaciones de tenencia y un responsable claro para la rotación de secretos. Prefiera una fiabilidad sencilla a demostraciones ingeniosas pero puntuales.

Nota por lotes para eeb69aedbcbc: mantenga las claves del proveedor fuera del repositorio, establezca un límite para los tokens por sesión y almacene las transcripciones junto a los archivos de evaluación para que los cambios posteriores en el modelo sigan siendo comparables.