Blog • Artigo
    Agentic AIAWSProduct Engineering

    How to Build a Self-Correcting Multi-Agent AI Product with LangGraph and AWS

    An architecture based on specialized agents that automatically generate, evaluate, and improve responses, with persistence, security, observability, and execution in production on AWS.

    Alexsander
    AlexsanderEngenheiro de Software
    Aug 27, 2026
    9 min de leitura
    How to Build a Self-Correcting Multi-Agent AI Product with LangGraph and AWS

    Most generative Artificial Intelligence applications start simply: the user submits a request, a language model processes the prompt, and a response is returned.

    User → Application → LLM → Response

    This model works well for experimentation, simple assistants, and some internal use cases.

    The problem arises when we want to turn this idea into a product.

    A product needs to do more than generate responses. It needs to control quality, maintain history, handle failures, protect data, control costs, monitor executions, authenticate users, and log what happened during processing.

    It is in this context that the architecture of this article comes into play.

    The proposal is to build a Self-Correcting Multi-Agent AI Product, in which different agents take on specific responsibilities within a workflow coordinated by LangGraph.

    The main flow will be:

    Generate → Evaluate → Correct → Evaluate Again → Approve

    Instead of relying entirely on the first response produced by an LLM, we introduce a quality process within the application itself.


    The product we are building

    Our product allows a user to submit a topic, a request, or content and receive as a result a response that has gone through an automatic process of generation, evaluation, and correction.

    The first version will have three main agents:

    Writer Agent → Reviewer Agent → Correction Agent

    Each agent has a specific responsibility.

    The Writer Agent produces the first version of the content.

    The Reviewer Agent evaluates the result using quality criteria.

    The Correction Agent uses the feedback received to improve the response.

    After correction, the content returns to the Reviewer for a new evaluation.

    This process continues until the response is approved or the maximum number of revisions is reached.

    The product can also present to the user:

    • final response;
    • quality score;
    • evaluated criteria;
    • number of revisions;
    • feedback from agents;
    • execution time;
    • model used;
    • token consumption;
    • estimated cost;
    • execution history;
    • approval status.

    The experience shifts from simply being:

    Prompt → Response

    to being:

    Request → Generation → Evaluation → Correction → Validation → Result

    The user no longer interacts only with a model and begins to use a quality-controlled AI system.


    High-level architecture

    The proposed architecture for the product uses the following components:

    LayerTechnology
    FrontendReact / Next.js
    BackendFastAPI
    OrchestrationLangGraph
    InferenceAmazon Bedrock
    AuthenticationAmazon Cognito
    PersistencePostgreSQL / Amazon RDS
    Workflow StateLangGraph Checkpointer
    SecurityIAM, Secrets Manager, and Bedrock Guardrails
    ObservabilityAmazon CloudWatch
    ContainerizationDocker
    RegistryAmazon ECR
    DeployAWS App Runner
    InfrastructureTerraform
    CI/CDGitHub Actions

    The main flow of the application will be:

    User → React / Next.js → Amazon Cognito → FastAPI → LangGraph → Agents → LLM Provider → Amazon Bedrock

    Surrounding this flow, we will have persistence, security, governance, observability, and continuous delivery mechanisms.


    Why use multiple agents?

    A traditional application based on LLM typically concentrates several responsibilities within the same prompt.

    We can ask the model to:

    • write content;
    • be technical;
    • be clear;
    • validate its own response;
    • check for examples;
    • correct issues;
    • maintain a certain length;
    • confirm that all requirements have been met.

    It is possible to work this way.

    The problem is that we are concentrating generation, evaluation, and correction within the same step.

    From an engineering perspective, we can separate these responsibilities.

    Produce → Evaluate → Correct

    This is the same principle of separation of responsibilities used in traditional software.

    Each agent now has a specific and predictable function.


    Writer Agent

    The first agent is responsible for the initial generation of content.

    It receives:

    Topic + Context + Requirements → Initial Draft

    For example:

    "

    Explain hexagonal architecture to an intermediate developer and provide a practical example.

    We can define some responsibilities for this agent:

    • produce technical content;
    • explain objectively;
    • use examples;
    • stay focused on the subject;
    • respect size limits;
    • follow the requested format;
    • avoid unnecessary information.

    The result produced by the agent is stored in the workflow state.

    Código
    class WorkflowState(TypedDict):
        topic: str
        draft: str
        feedback: str
        decision: str
        score: float
        revision_count: int
    

    The Writer Agent does not need to decide if its own response is good.

    That responsibility belongs to the next agent.


    Reviewer Agent

    The Reviewer Agent acts as a Quality Gate.

    Its goal is to evaluate the content produced by the Writer Agent.

    A simple implementation could return:

    Código
    {
      "decision": "PASS"
    }
    

    or:

    Código
    {
      "decision": "REVISE"
    }
    

    For a professional product, we can go further.

    The Reviewer can produce a structured evaluation:

    Código
    {
      "decision": "REVISE",
      "score": 0.74,
      "criteria": {
        "clarity": 0.91,
        "relevance": 0.89,
        "completeness": 0.68,
        "technical_quality": 0.72
      },
      "issues": [
        "The example is not detailed enough.",
        "The explanation of dependency inversion is incomplete."
      ],
      "suggestions": [
        "Add an example with Ports and Adapters.",
        "Better explain the direction of dependencies."
      ]
    }
    

    Now the product has something much more interesting than a simple opinion from the model.

    We have a structured evaluation, which can be stored, analyzed, and used by the workflow itself.


    The Reviewer as a quality mechanism

    We can establish objective rules for approval.

    For example:

    Overall Score ≥ 0.85

    We can also create additional criteria:

    • clarity ≥ 0.80;
    • relevance ≥ 0.90;
    • technical quality ≥ 0.80;
    • completeness ≥ 0.80.

    The application now combines two mechanisms:

    LLM Evaluation + Deterministic Rules

    Example:

    Código
    approved = (
        score >= 0.85
        and clarity >= 0.80
        and relevance >= 0.90
        and technical_quality >= 0.80
    )
    

    This combination is important.

    We are not simply asking the model if it considers the response good.

    We are using Artificial Intelligence within a mechanism controlled by the application.


    Correction Agent

    When the Reviewer Agent identifies problems, we enter the correction process.

    The Correction Agent receives:

    Original Topic + Current Response + Identified Problems + Suggestions from the Reviewer

    For example:

    "

    Identified Problem: the explanation about ports and adapters was not clear enough.

    "

    Suggestion: add an example showing a repository interface and a PostgreSQL implementation.

    The agent uses this information to produce a new version.

    Draft v1 → Reviewer → Feedback → Reviser → Draft v2

    After that, the new version returns to the Reviewer Agent.


    The self-correction loop

    This is the central part of the architecture.

    The workflow has a cycle.

    Writer → Reviewer → Reviser → Reviewer

    If the content is approved:

    Writer → Reviewer → PASS → Final Result

    If the content needs improvements:

    Writer → Reviewer → REVISE → Reviser → Reviewer

    If there are still problems:

    Reviewer → Reviser → Reviewer → Reviser → Reviewer

    This pattern can be summarized as:

    Generate → Evaluate → Improve → Evaluate

    Or, conceptually:

    Actor → Critic → Refiner

    This cycle transforms simple generation into a controlled improvement process.


    Why LangGraph?

    Traditional pipelines tend to be linear.

    A → B → C → D

    Our workflow is not linear.

    The Reviewer Agent needs to dynamically decide what the next step will be.

    When the response is approved:

    Reviewer → PASS → Final Result

    When there is a need for correction:

    Reviewer → REVISE → Reviser → Reviewer

    We have:

    • shared state;
    • conditional decisions;
    • cycles;
    • checkpoints;
    • multiple agents;
    • termination conditions;
    • possibility of resumption.

    LangGraph takes on the role of workflow orchestrator.

    It is not responsible for generating content.

    Its responsibility is to control the execution of agents and determine how the state evolves during the process.


    Shared state among agents

    All agents work on a shared state.

    We can represent this state as follows:

    Código
    class WorkflowState(TypedDict):
        topic: str
        draft: str
        feedback: str
        decision: str
        score: float
        revision_count: int
    

    During an execution, we might have:

    Código
    {
      "topic": "Hexagonal Architecture",
      "draft": "Hexagonal architecture is...",
      "feedback": "Detail Ports and Adapters.",
      "decision": "REVISE",
      "score": 0.76,
      "revision_count": 1
    }
    

    Each agent modifies only what belongs to its responsibility.

    The flow now works on an explicit representation of the execution state.


    Controlling the number of revisions

    A self-correcting product needs to have limits.

    Otherwise, we could generate an indefinite cycle:

    Reviewer → Reviser → Reviewer → Reviser → Reviewer → ...

    This represents:

    More calls to the LLM → More tokens → More cost → Higher latency

    Therefore, we can establish:

    Código
    MAX_REVISIONS = 3
    

    The logic can follow this rule:

    Reviewer → PASS → Final Result

    or:

    Reviewer → REVISE → Check limit → Reviser

    If the limit has been reached:

    Reviewer → Maximum limit reached → End execution

    There is an important difference.

    Reaching the maximum limit of revisions does not mean that the response has been approved.

    The API should make this explicit.

    Código
    {
      "status": "MAX_REVISIONS_REACHED",
      "approved": false,
      "score": 0.81,
      "revision_count": 3
    }
    

    This transparency is important for a professional product.


    Workflow persistence

    During a proof of concept, we can store the state only in memory.

    In production, this is not sufficient.

    Imagine an ongoing execution:

    Writer → Reviewer → Reviser

    At this moment, the container is restarted.

    Without persistence, the execution is lost.

    We need to be able to recover the workflow.


    LangGraph Checkpointer

    The solution involves using checkpoints.

    LangGraph → Checkpointer → PostgreSQL

    In AWS, we can use:

    LangGraph → Checkpointer → Amazon RDS PostgreSQL

    After each step, the state can be persisted.

    Writer → Checkpoint → Reviewer → Checkpoint → Reviser → Checkpoint

    Each execution receives an identifier.

    Código
    thread_id
    

    Example:

    Código
    a32887cb-36a1-4272
    

    This allows us to reconstruct the state of an execution and continue processing after failures.

    It also opens up possibilities for functionalities such as:

    • pause and resume;
    • human-in-the-loop;
    • recovery after failures;
    • replay;
    • auditing;
    • debugging;
    • execution history.

    Checkpoint is not a business database

    There is an important separation.

    The Checkpointer should store the operational state of the workflow.

    The application database should store the product data.

    These are different responsibilities.

    We can create tables such as:

    Código
    users
    projects
    executions
    execution_steps
    evaluations
    model_usage
    prompt_versions
    

    An executions table could have:

    Código
    executions
    
    id
    user_id
    thread_id
    topic
    status
    approved
    final_answer
    final
    
    Este conteúdo foi útil?
    Compartilhar artigo

    Quer aplicar isso no seu contexto?

    Vamos conversar sobre seus desafios e encontrar o melhor caminho para sua operação.

    Agendar conversa