Estrategias para el bingo.

  1. Jugar En Casino Con Bitcoin Desde España: En esta página puedes probar la demostración gratuita de Alice Adventure por diversión y aprender sobre todas las características del juego, sin riesgo de perder dinero.
  2. Inkabet Casino Codigo Promocional Y Codigo Bonus 2026 - Los archivos que debe adjuntar dependen de su problema.
  3. Ruleta Europea Bono De Bienvenida: Los jugadores siguen amando las creaciones de Endorphina porque proporcionan una singularidad real que los jugadores de todo el mundo pueden apreciar.

Poker irpf.

Casino Para Jugar Slots Con Criptomonedas
Esto hace que sea fácil averiguar cuáles te gustaría jugar.
Juegos Slot En Madrid
Muchas de las tragamonedas en línea de Belatra Games como esta interactúan de manera similar, así que asegúrese de encontrar una que lo entretenga mejor.
Es posible que haya escuchado que es imposible apostar en línea en muchos países..

Números premiados en la lotería de navidad del 2025.

Ruleta Online Usdt
Con la abundancia de juegos de Oriente en el mercado de nuestra generación, puede pensar que ya conoce la tendencia sobre cómo los desarrolladores retratan las Ciudades antiguas, los templos y más.
Poker Online Con Bono De Bienvenida
Si los Chiefs comienzan a derribarlos, Sanders podría muy bien conseguir su touchdown, pero las 50 yardas terrestres serían una tarea difícil, especialmente cuando los Eagles necesitarán ingresar a este juego con el plan de juego de tener que lanzar para mantenerse al día con el mariscal de campo de los Chiefs, Patrick Mahomes.
Juegos De Maquinas De Bingo Gratis Para Jugar

Agente de Planificación y Ejecución (Plan-and-Execute Agent) especializado en realizar investigación historiográfica y etimológica especializada

El propósito central de este agente de Agente de Planificación y Ejecución (Plan-and-Execute Agent) es realizar investigación historiográfica y etimológica especializada (específicamente en toponimia pirenaica, como sugiere la pregunta de ejemplo), siguiendo un plan estructurado, utilizando múltiples herramientas de consulta de bases de datos y refinando continuamente una hipótesis central hasta alcanzar un alto nivel de certeza.


1. Arquitectura General y Modelos LLM

Este script establece un flujo de trabajo cíclico donde el agente planifica, ejecuta el paso y luego replanifica, todo mientras registra meticulosamente el proceso.

Modelos y Roles

El sistema utiliza dos modelos de lenguaje grandes (LLMs), cada uno con un rol específico para maximizar la eficiencia y precisión:

  1. GPT-4o (para Planificación y Replanificación): Utilizado para las tareas de razonamiento de alto nivel (plan_step y replan_step). Se le pide generar un output estructurado usando Pydantic (Plan o Act), asegurando que la salida sea lógica y predecible.
  2. Deepseek (para Ejecución y Uso de Herramientas): Utilizado para el nodo de ejecución (execute_step). Este modelo actúa como el Agente ReAct (Reasoning and Acting) que decide qué herramientas especializadas usar para cumplir el paso del plan.

Herramientas Especializadas

El agente está armado con una vasta colección de herramientas específicas, lo que lo convierte en un experto en su dominio:

  • Herramientas Externas: wikipedia_tool, ddg_news_tool (para búsquedas generales).
  • Herramienta Humana: request_human_feedback_tool (permite solicitar la intervención o validación de un usuario en un paso crítico).
  • Herramientas RAG Específicas: Una larga lista de agentes de recuperación de información (RAG) especializados en bases de datos etimológicas, históricas y geográficas (ej: georgia_agent, hierro_agent, anatolia_agent, vasciberaqui_agent, etc.).

2. El Corazón del Agente: El Estado y la Hipótesis

La estructura PlanExecute (Estado) es el motor que impulsa el flujo. No solo almacena el input y el plan, sino que incluye la lógica de persistencia de hipótesis, que es la parte más innovadora del código:

CampoPropósitoExplicación
main_hypothesisRastreoLista de las raíces etimológicas o teorías principales identificadas hasta ahora.
hypothesis_certaintyCondiciónMide la confianza del agente en la hipótesis actual («alta», «media», «baja»). Este valor controla la lógica del flujo.
past_stepsMemoriaHistorial de las tareas ejecutadas y sus resultados.
log_filenameAuditoríaNombre del archivo Markdown donde se registra la traza de ejecución.

Lógica de Restricción Crítica (Dynamic Prompt)

La función generate_agent_system_message es crucial. Genera el prompt del sistema de forma dinámica para el Agente y el Planner:

  1. Define el rol experto del agente (Investigador Lingüístico/Historiográfico).
  2. Impone un formato de salida estricto en la respuesta del agente, exigiendo las líneas Grado de Certeza: [alta/media/baja] y Hipótesis Principal: [Lista de elementos].
  3. Aplica una Restricción Lógica: Si el estado indica que la hypothesis_certainty es "alta", el prompt del sistema se actualiza para prohibir que el agente formule nuevas hipótesis. Su única tarea restante es verificar, refutar rivales o concluir, lo que fuerza al agente a cerrar el ciclo de investigación.

3. Nodos del Workflow y Extracción de Datos

El flujo de trabajo es una cadena de nodos que se ejecuta en bucle hasta que se encuentra una respuesta final:

A. execute_step (La Ejecución y Extracción)

Este nodo es el más complejo:

  1. Toma el primer task del plan.
  2. Invoca al Agente ReAct (Deepseek) con las herramientas para ejecutar la tarea.
  3. Aplica una lógica de extracción por Regex a la respuesta del agente. Busca las líneas de Grado de Certeza: y Hipótesis Principal: para actualizar el estado.
  4. Lógica de Sobrescritura: Solo actualiza la hipótesis y la certeza si la nueva certeza detectada es igual o mayor que la certeza actual, asegurando que el agente no retroceda en sus hallazgos.

B. replan_step (Decisión y Replanificación)

Este nodo es la puerta de salida o la continuación del bucle:

  1. Invoca al GPT-4o para que revise el progreso.
  2. Recibe una estructura Act, que puede ser un Response (si el trabajo ha terminado) o un nuevo Plan (si se necesita más investigación).
  3. La lógica aquí también es sensible a la certeza: si la certeza es «alta», el prompt se ajusta para presionar al modelo a generar una Response (respuesta final) y terminar el flujo.

C. log_to_file

Este nodo (ejecutado después de cada paso principal) es un sistema de auditoría y trazabilidad. Escribe la salida de cada nodo en un archivo Markdown, creando una traza completa de la toma de decisiones del agente.

D. should_end (Condición)

Una función simple que determina el siguiente paso:

  • END: Si se ha generado una respuesta final (state.get("response") es verdadero) o si el plan está vacío.
  • «agent»: Si aún hay pasos pendientes en el plan.

4. Ejecución Asíncrona

El bloque final utiliza asyncio.run(run_planner_agent(...)) para iniciar el motor LangGraph. Esto es necesario porque app.astream() es una función asíncrona que genera eventos en tiempo real (streaming). El código itera sobre estos eventos, registrando el progreso y finalmente imprimiendo la final_response capturada.

En resumen, este script no es solo un agente, sino un pipeline de investigación autosuficiente que utiliza la inteligencia de dos modelos LLM para planificar, ejecutar y refinar una investigación compleja y especializada, con un mecanismo incorporado para saber cuándo detenerse (al alcanzar la «certeza alta»).

import asyncio
import logging
import operator
import os
import re
from datetime import datetime
from typing import TypedDict , List , Union , Annotated , Tuple

from langchain.schema import SystemMessage , HumanMessage
from langchain_core.prompts import ChatPromptTemplate , MessagesPlaceholder
from langgraph.constants import END , START
from langgraph.graph import StateGraph
from langgraph.prebuilt import create_react_agent
from pydantic import BaseModel , Field

# --- ASUNCIONES DE IMPORTACIONES REALES ---
try:
    from agent_service_toolkit.retrievers.grafos.draw_graph import draw_graph_with_graphviz
    from agent_service_toolkit.retrievers.vall_aran_hypotesis_flow.vall_global_utils import generar_edge_labels , \
        merge_graph_and_legend
    from agent_service_toolkit.tools.registry.external_rools.external_tool_functions import wikipedia_tool , \
        ddg_news_tool
    from agent_service_toolkit.tools.registry.rag_tools.rag_registry import agent_tools
    from src.agent_service_toolkit.core.load_ret_llm_models import get_model
    from agent_service_toolkit.tools.registry.function_tools.request_human_feedback.request_human_feedback_tool import \
        request_human_feedback_tool
except ImportError as e:
    logging.warning ( f"🚨 Advertencia de Importación: Algunas herramientas o utilidades no están disponibles. {e}" )


    # Definiciones dummy para que el código compile si faltan tus módulos
    def dummy_tool(query: str) -> str:
        return f"Dummy result for {query}"


    wikipedia_tool = dummy_tool
    ddg_news_tool = dummy_tool
    request_human_feedback_tool = dummy_tool
    agent_tools = {}

# --- Tool and Model Setup ---
logging.basicConfig ( level = logging.INFO , format = '%(asctime)s - %(levelname)s - %(message)s' )

# 1. Preparar herramientas
deepseek_model_loaded = get_model ( "Deepseek" )
gpt4o_model_loaded = get_model ( "gpt-4o" )

if deepseek_model_loaded and gpt4o_model_loaded:
    try:
        planner_tools_list = [ wikipedia_tool , ddg_news_tool , request_human_feedback_tool ]
        # Agregamos todas las herramientas RAG disponibles
        rag_keys = [
            "georgia_agent" , "etimology_agent" , "hierro_agent" , "bronce_agent" , "armenia_agent" , "anatolia_agent" ,
            "calcolitic_agent" , "mesolitic_agent" , "neolitic_agent" , "vall_ager_agent" , "vasciberaqui_agent" ,
            "vall_aran_agent" , "iberos_agent" , "celtas_agent" , "indoeuropa_agent" , "Tradition_agent" ]
        for key in rag_keys:
            planner_tools_list.extend ( agent_tools.get ( key , [ ] ) )

        planner_tools = planner_tools_list
        logging.info ( f"[Setup] {len ( planner_tools )} Herramientas cargadas (RAG + Externas)." )
    except Exception as e:
        logging.error ( f"[Setup] Fallo al cargar herramientas RAG/Personalizadas: {e}. Usando solo las básicas." )
        planner_tools = [ wikipedia_tool , ddg_news_tool , request_human_feedback_tool ]
else:
    logging.error ( "⛔ ERROR: Modelos LLM no cargados. El agente fallará en la ejecución." )
    planner_tools = [ ]


# --- Pydantic Data Structures ---
class Plan ( BaseModel ):
    """Plan inicial con pasos a seguir"""
    steps: List[ str ] = Field ( description = "Pasos ordenados para resolver la tarea" )


class Response ( BaseModel ):
    """Respuesta final al usuario"""
    response: str


class Act ( BaseModel ):
    """Acción a realizar: responder o planificar."""
    action: Union[ Response , Plan ]


class PlanExecute ( TypedDict ):
    """
    Estado compartido en el workflow. Incluye persistencia de la hipótesis etimológica.
    """
    input: str
    plan: List[ str ]
    past_steps: Annotated[ List[ Tuple ] , operator.add ]
    response: str
    log_filename: str
    last_node_output: str
    # 🔑 CAMBIO CLAVE: main_hypothesis ahora es una lista para múltiples entradas
    hypothesis_certainty: str  # "alta", "media", "baja"
    main_hypothesis: List[ str ]  # Lista de las hipótesis principales


# --- LLM and Agent Definitions ---
deepseek_model = deepseek_model_loaded
gpt4o_model = gpt4o_model_loaded

if not deepseek_model or not gpt4o_model:
    def dummy_runnable(state):
        return {"messages": [ HumanMessage ( content = "Dummy response" ) ]}


    planner_agent = dummy_runnable
    planner_runnable = dummy_runnable
    replanner_runnable = dummy_runnable
else:
    # --- PROMPT TEMPLATE GENERAL (Para inyección de contexto) ---
    def generate_agent_system_message(state: PlanExecute) -> str:
        """Genera el mensaje del sistema dinámico para los prompts de Agent y Planner."""
        base_message = (
            "Eres un **Investigador Lingüístico e Historiográfico experto en etimología y toponimia pirenaica**. "
            "Tu tarea es ejecutar un paso de un plan mayor relacionado con el estudio diacrónico de nombres de lugar, "
            "raíces lingüísticas y patrones de asentamiento en el ámbito pirenaico. "
            "Utiliza tus herramientas para obtener información detallada. **Enfoca tu respuesta en las evidencias filológicas, "
            "arqueológicas, paleográficas o geográficas** que sustenten o refuten el paso de la tarea, asegurando la **precisión semántica y cronológica**."

            # 📢 INSTRUCCIÓN CRÍTICA DE FORMATO MEJORADA Y DESTACADA
            "\n\n🚨 **INSTRUCCIÓN DE PERSISTENCIA:** Si tu respuesta incluye la identificación de etimologías principales, "
            "DEBES terminar tu respuesta con estas dos líneas de forma obligatoria. Si hay múltiples, USA UN GUION POR CADA UNA (Formato de lista Markdown):"
            "\n**Grado de Certeza:** [alta/media/baja]"
            "\n**Hipótesis Principal:** "
            "\n- [La primera raíz etimológica y su significado. Ej: Prerromana BERG- ('aprisco/corral')]"
            "\n- [La segunda raíz etimológica y su significado. Ej: Preindoeuropea IBER- ('agua/río')]"
        )

        # 🔑 Lógica de Restricción
        if state.get ( "hypothesis_certainty" ) == "alta":
            # Formateamos la lista de hipótesis para la restricción
            hypotheses_str = "\n".join ( f"- {h}" for h in state[ 'main_hypothesis' ] )
            base_message += (
                "\n\n**RESTRICCIÓN CRÍTICA:** Ya se encontró una o varias hipótesis de ALTA CERTEZA: "
                f"\n{hypotheses_str}\nA partir de ahora, NO DEBES FORMULAR NUEVAS HIPÓTESIS. "
                "Tu única función es: 1) Verificar la hipótesis contra fuentes históricas, 2) Refutar hipótesis rivales, o 3) Concluir la tarea. "
            )
        return base_message


    # 1. Tool-Using Agent (for EXECUTION)
    planner_agent = create_react_agent (
        model = deepseek_model ,
        tools = planner_tools ,
        prompt = ChatPromptTemplate.from_messages (
            [
                SystemMessage (
                    content = "Este es un placeholder, el SystemMessage real se inyecta en execute_step." ) ,
                MessagesPlaceholder ( variable_name = "messages" )
                ] ) ,
        name = "planner_agent"
        )


# --- Funciones de los nodos ---

async def plan_step(state: PlanExecute):
    """Genera un plan inicial y lo prepara para el log."""
    logging.info ( "    [Node: planner] Iniciando Planificación inicial..." )

    system_message_content = generate_agent_system_message ( state )

    current_planner_runnable = (
            ChatPromptTemplate.from_messages (
                [
                    SystemMessage (
                        content = system_message_content + "\n\nTu único propósito es generar un plan de investigación estructurado." ) ,
                    MessagesPlaceholder ( variable_name = "messages" )
                    ] )
            | gpt4o_model.with_structured_output ( Plan )
    )

    plan_obj = await current_planner_runnable.ainvoke ( {"messages": [ HumanMessage ( content = state[ "input" ] ) ]} )

    if isinstance ( plan_obj , Plan ):
        plan_steps = plan_obj.steps
        output_str = "## 📝 Plan Inicial Generado\n\n" + "\n".join (
            f"{i + 1}. {step}" for i , step in enumerate ( plan_steps ) )
        return {"plan": plan_steps , "last_node_output": output_str}
    else:
        output_str = f"## ❌ Error de Planificación\n\nNo se pudo generar un Plan estructurado. Tipo recibido: {type ( plan_obj )}"
        return {"plan": [ ] , "last_node_output": output_str}


async def execute_step(state: PlanExecute):
    """Ejecuta el primer paso del plan, extrae la certeza y prepara la salida para el log."""
    logging.info ( "    [Node: agent] Iniciando Ejecución de paso..." )
    plan = state[ "plan" ]
    if not plan: return {"response": "No hay pasos en el plan."}
    task = plan[ 0 ]

    system_message_content = generate_agent_system_message ( state )

    if not planner_agent:
        response_content = "El modelo de ejecución no está cargado. Paso simulado."
        remaining_plan = plan[ 1: ]
    else:
        plan_str = "\n".join ( f"{i + 1}. {s}" for i , s in enumerate ( plan ) )
        task_formatted = f"For the following plan:\n{plan_str}\n\nExecute step 1: {task}"

        messages_to_agent = [
            SystemMessage ( content = system_message_content ) ,
            HumanMessage ( content = task_formatted )
            ]

        agent_response = await planner_agent.ainvoke ( {"messages": messages_to_agent} )
        remaining_plan = plan[ 1: ]
        response_content = agent_response[ "messages" ][ -1 ].content if agent_response[
            "messages" ] else "Execution complete."

    # 🔑 Lógica de EXTRACCIÓN de Hipótesis y Certeza (Adaptada a Listas)
    new_hypothesis = state.get ( "main_hypothesis" , [ "Aún no establecida" ] )  # Ahora una lista
    new_certainty = state.get ( "hypothesis_certainty" , "baja" )

    # 1. Búsqueda de Certeza
    match_certainty = re.search ( r"Grado de Certeza:\s*(alta|media|baja)" , response_content , re.IGNORECASE )

    # 2. Búsqueda de Hipótesis Principal: Buscamos la línea "Hipótesis Principal:"
    match_hypothesis_start = re.search ( r"Hipótesis Principal:\s*\n" , response_content , re.IGNORECASE )

    if match_certainty and match_hypothesis_start:

        potential_certainty = match_certainty.group ( 1 ).lower ( )

        # 2b. Extraer todas las líneas que comienzan con un guion después del inicio de la hipótesis
        start_index = match_hypothesis_start.end ( )
        # Buscamos todas las líneas que inician con '-' desde el punto de inicio
        potential_hypotheses = re.findall ( r"^- (.*)" , response_content[ start_index: ] , re.MULTILINE )

        if potential_hypotheses:

            # Filtramos hipótesis vacías y verificamos la longitud mínima (relevancia)
            valid_hypotheses = [ h.strip ( ) for h in potential_hypotheses if len ( h.strip ( ) ) > 15 ]

            if valid_hypotheses:

                # Mapa para comparar niveles de certeza
                certainty_order = {"baja": 1 , "media": 2 , "alta": 3}
                current_certainty_level = certainty_order.get ( new_certainty , 1 )
                potential_certainty_level = certainty_order.get ( potential_certainty , 1 )

                # Solo actualizamos si la nueva certeza es igual o mayor
                if potential_certainty_level >= current_certainty_level:
                    new_certainty = potential_certainty
                    new_hypothesis = valid_hypotheses  # Guardamos la lista completa de hipótesis válidas
                    logging.info (
                        f"✅ [Extractor] {len ( new_hypothesis )} Hipótesis guardadas con certeza {new_certainty.upper ( )}." )
                else:
                    logging.warning (
                        f"⚠️ [Extractor] Nueva hipótesis de menor certeza ({potential_certainty}) ignorada." )
            else:
                logging.warning ( "⚠️ [Extractor] Hipótesis detectadas pero son demasiado cortas/irrelevantes." )

    # Si la extracción falla o es irrelevante, el estado se mantiene como estaba.

    # Formato de salida para el log (usando guiones ya que new_hypothesis es una lista)
    hypothesis_output = "\n".join ( f"- {h}" for h in new_hypothesis )

    output_str = (
        f"## ➡️ Paso Ejecutado\n"
        f"**Tarea:** `{task}`\n"
        f"**Certeza Guardada:** `{new_certainty.upper ( )}`\n"
        f"**Hipótesis Guardada:**\n{hypothesis_output}\n\n"  # Formato de salida modificado
        f"**Resultado Completo:**\n```markdown\n{response_content}\n```\n"
    )

    return {
        "plan": remaining_plan ,
        "past_steps": [ (task , response_content) ] ,
        "last_node_output": output_str ,
        "main_hypothesis": new_hypothesis ,  # Retorna la lista
        "hypothesis_certainty": new_certainty
        }


async def replan_step(state: PlanExecute):
    """Revisa el plan, decide si continuar/responder y prepara la salida para el log."""
    logging.info ( "    [Node: replan] Iniciando Decisión/Replanificación..." )

    system_message_content = generate_agent_system_message ( state )

    # 🔑 Lógica de RESTRICCIÓN para el replanner
    restriction_message = (
        "Revisa el plan original, la tarea inicial y los pasos ya ejecutados en el contexto de la evolución de nombres de lugar. "
        "- **Si la respuesta final está completa y se basa en una sólida evidencia filológica y cronológica**, devuelve una respuesta final (Response). "
        "- **Si el plan es incompleto o la información obtenida es insuficiente**, genera SOLO los pasos pendientes o un nuevo Plan que aborde la brecha de información. "
        "- Tu respuesta DEBE ser una de las dos estructuras Pydantic (Response o Plan)."
    )
    if state.get ( "hypothesis_certainty" ) == "alta":
        restriction_message = (
                                  f"RESTRICCIÓN CRÍTICA: La hipótesis principal ya tiene CERTEZA ALTA. "
                                  f"Si el plan restante solo verifica hechos menores o la información es suficiente, DEBES devolver la estructura RESPONSE. "
                                  f"NO generes un nuevo Plan si la certeza es alta."
                              ) + restriction_message

    current_replanner_runnable = (
            ChatPromptTemplate.from_messages (
                [
                    SystemMessage ( content = system_message_content ) ,
                    SystemMessage ( content = restriction_message ) ,
                    MessagesPlaceholder ( variable_name = "messages" )
                    ] )
            | gpt4o_model.with_structured_output ( Act )
    )

    # Adaptación para la lista de hipótesis en el contexto
    current_hypotheses = "\n".join ( f"- {h}" for h in state.get ( "main_hypothesis" , [ "N/A" ] ) )

    past_steps_summary = "\n".join ( f" - {step}: {result[ :50 ]}..." for step , result in state[ 'past_steps' ] )
    context_messages = [
        SystemMessage ( content = f"Tarea Inicial: {state[ 'input' ]}" ) ,
        SystemMessage (
            content = f"Hipótesis actual (Certeza {state[ 'hypothesis_certainty' ].upper ( )}):\n{current_hypotheses}" ) ,
        SystemMessage ( content = f"Plan Restante: {state[ 'plan' ]}" ) ,
        SystemMessage ( content = f"Pasos Completados:\n{past_steps_summary}" )
        ]

    output = await current_replanner_runnable.ainvoke ( {"messages": context_messages} )
    output_str = ""

    if isinstance ( output.action , Response ):
        output_str = f"## 🛑 Decisión Final: Responder\n\n**Respuesta Final:**\n{output.action.response}"
        return {"response": output.action.response , "last_node_output": output_str}
    elif isinstance ( output.action , Plan ):
        new_plan_steps = output.action.steps
        output_str = "## 🔄 Decisión: Replanificar\n\n**Nuevo Plan:**\n" + "\n".join (
            f"{i + 1}. {step}" for i , step in enumerate ( new_plan_steps ) )
        return {"plan": new_plan_steps , "last_node_output": output_str}
    else:
        output_str = "## ❌ Error de Replanificación\n\nNo se produjo una estructura Act válida."
        return {"response": "Error: Planificación inválida o nula." , "last_node_output": output_str}


# --- NODO DE LOGGING A ARCHIVO / CONDICIONAL ---
def log_to_file(state: PlanExecute):
    """Escribe la salida del nodo anterior en un archivo Markdown."""
    reports_dir = "informes_trace"
    os.makedirs ( reports_dir , exist_ok = True )
    log_filename = state.get ( "log_filename" )

    if not log_filename:
        timestamp = datetime.now ( ).strftime ( "%Y%m%d_%H%M%S" )
        log_filename = os.path.join ( reports_dir , f"trace_{timestamp}.md" )
        with open ( log_filename , "w" , encoding = "utf-8" ) as f:
            f.write ( f"# 📊 Traza de Ejecución del Workflow\n" )
            f.write ( f"## 📥 Tarea Inicial\n{state[ 'input' ]}\n\n---\n" )
        logging.info ( f"💾 [LogToFile] Archivo de traza creado: {log_filename}" )

    if state.get ( "last_node_output" ):
        output_to_write = state[ "last_node_output" ]
        with open ( log_filename , "a" , encoding = "utf-8" ) as f:
            f.write ( f"\n\n# 🕒 {datetime.now ( ).strftime ( '%H:%M:%S' )} - Salida del Nodo\n\n" )
            f.write ( output_to_write )
            f.write ( "\n\n---\n" )
        logging.info ( f"💾 [LogToFile] Salida del nodo registrada en {log_filename}." )

    return {"log_filename": log_filename , "last_node_output": ""}


def should_end(state: PlanExecute):
    """Condición de finalización, llamada DESPUÉS del log_replan."""
    if state.get ( "response" ):
        logging.info ( "[Condition] Terminando: Respuesta final generada." )
        return END

    if not state.get ( "plan" ):
        logging.info ( "[Condition] Terminando: Plan vacío y sin respuesta final." )
        return END

    logging.info ( "[Condition] Continuando: Se necesita el siguiente paso ('agent')." )
    return "agent"


# --- Construcción del workflow ---
workflow = StateGraph ( PlanExecute )
workflow.add_node ( "planner" , plan_step )
workflow.add_node ( "agent" , execute_step )
workflow.add_node ( "replan" , replan_step )
workflow.add_node ( "log_plan" , log_to_file )
workflow.add_node ( "log_exec" , log_to_file )
workflow.add_node ( "log_replan" , log_to_file )

# 1. Flujo Inicial: START -> planner -> log_plan -> agent
workflow.add_edge ( START , "planner" )
workflow.add_edge ( "planner" , "log_plan" )
workflow.add_edge ( "log_plan" , "agent" )

# 2. Bucle de Ejecución: agent -> log_exec -> replan
workflow.add_edge ( "agent" , "log_exec" )
workflow.add_edge ( "log_exec" , "replan" )

# 3. Decisión y Bucle: replan -> log_replan -> condicional (agent/END)
workflow.add_edge ( "replan" , "log_replan" )
workflow.add_conditional_edges (
    "log_replan" ,
    should_end ,
    {
        "agent": "agent" ,
        END: END
        }
    )

# --- Compilación ---
app = workflow.compile ( )
logging.info ( "[Workflow] Workflow compilado con éxito." )


# --- Ejecución asíncrona ---
async def run_planner_agent(question):
    """Define y ejecuta el flujo asíncrono del LangGraph."""
    inputs = {
        "input": question ,
        "past_steps": [ ] ,
        "log_filename": "" ,
        "last_node_output": "" ,
        # 🔑 Valores iniciales para los nuevos campos
        "hypothesis_certainty": "baja" ,
        "main_hypothesis": [ "Aún no establecida" ]  # Ahora una lista
        }
    config = {"recursion_limit": 50}

    logging.info ( f"[Execution] Iniciando para la tarea: {inputs[ 'input' ]}" )

    logging.info ( "-" * 50 )
    logging.info ( "--- LangGraph Trace ---" )
    final_response = None

    # LÓGICA DE EJECUCIÓN
    async for event in app.astream ( inputs , config = config ):
        for k , v in event.items ( ):
            if k == "__end__":
                logging.info ( "-" * 50 )
                logging.info ( "[Execution] Evento FINALIZADO." )
                if v.get ( 'response' ):
                    final_response = v[ 'response' ]
                break

            if k in [ "planner" , "agent" , "replan" ]:
                logging.info ( f"[Stream] 🔄 {k.upper ( )} - Nodo ejecutado." )
            elif k in [ "log_plan" , "log_exec" , "log_replan" ]:
                logging.info ( f"[Stream] 💾 LOGGING ({k}): Registrando salida en archivo Markdown." )

    logging.info ( "[Execution] Ejecución finalizada." )

    if final_response:
        print ( "\n" + "=" * 80 )
        print ( "      ✨ RESPUESTA FINAL DEL AGENTE (Capturado del estado final) ✨" )
        print ( "=" * 80 )
        print ( final_response )
        print ( "=" * 80 + "\n" )
        return final_response


if __name__ == "__main__":

    # Pregunta de investigación
    RESEARCH_QUESTION = "Analiza la etimología del topónimo 'Ager' desde sus raíces, y cita las fuentes documentales más antiguas. Tiene origen vascoaquitano o indoeuropeo?"

    # Ejecución asíncrona
    asyncio.run ( run_planner_agent ( question = RESEARCH_QUESTION ) )

    # --- Visualization ---
    try:
        edge_labels = generar_edge_labels ( app.get_graph ( ) )
        draw_graph_with_graphviz ( app , "planner_completo" , edge_labels = edge_labels )
        merge_graph_and_legend (
            main_graph_path = "grafos/planner_completo.png" ,
            legend_path = "grafos/planner_completo_legend.png" ,
            output_path = "grafos/planner_pipeline_completo_with_legend.png" ,
            padding = 20 ,
            )
        logging.info ( "\n[Visualization] Visualización del grafo generada en 'grafos/' folder." )
    except Exception as e:
        logging.error (
            f"\n[Visualization] No se pudo generar la visualización del grafo. {e}"
            )

2 thoughts on “Agente de Planificación y Ejecución (Plan-and-Execute Agent) especializado en realizar investigación historiográfica y etimológica especializada

Deja una respuesta

Your email address will not be published. Required fields are marked *.

*
*

Entradas recientes