Publicado el 5 min de lectura

Comprueba las dependencias de tareas de IA antes de programarlas con Python

En esta página

Un asistente de IA convierte tus notas de una reunión en un plan de publicación de cinco tareas. Los pasos parecen razonables, pero «review» espera a «publish», mientras «publish» espera a «review». Ninguna de esas dos tareas puede empezar. Otra respuesta menciona un requisito previo que no existe.

Comprueba la estructura de dependencias antes de conectar el plan a un programador de tareas. Este tutorial crea una vista previa de solo lectura con Python: rechaza ambos errores y devuelve un orden completo para un plan válido. Complementa los conceptos de planificación de los patrones de diseño de IA agéntica; no requiere llamadas a modelos, cuentas ni paquetes externos.

Cinco tarjetas de tareas unidas por flechas verdes, con una lupa que destaca un ciclo de dependencias de color ámbar.

1. Define qué significa el plan

Usa un diccionario que asocie cada identificador de tarea con sus requisitos previos. Por ejemplo, {"review": ["draft"]} significa que la redacción debe terminar antes de la revisión, no al revés. Una lista vacía indica que la tarea no tiene requisitos previos.

Establece un contrato pequeño y explícito:

  • Acepta entre 1 y 100 tareas, cada una con un identificador ASCII en minúsculas de hasta 40 caracteres, que empiece con una letra; después puede contener dígitos y guiones bajos.
  • Exige una lista de identificadores de requisitos previos sin repeticiones para cada tarea.
  • Rechaza requisitos no declarados y dependencias circulares, incluida una tarea que dependa de sí misma.

La función siguiente acepta un diccionario ya decodificado, no texto del modelo sin procesar. Si recibes JSON, limita el tamaño de entrada antes de decodificarlo y rechaza por separado las claves de objeto duplicadas. La documentación de JSON de Python advierte del consumo excesivo de recursos con entradas no confiables. El límite de 100 tareas es una política del tutorial, no una afirmación sobre la capacidad del programador.

2. Crea un validador que solo genere una vista previa

La documentación de graphlib de Python explica los diccionarios de requisitos previos y la detección de ciclos. También señala que los requisitos no declarados se añaden automáticamente como nodos. Los rechazamos expresamente para que una tarea inventada no entre en el plan sin avisar.

Convierte el resultado de static_order() en una tupla antes de devolver nada. Nunca ejecutes tareas mientras consumes ese iterador: la validación debe terminar primero. Guarda este código como check_plan.py y usa Python 3.11 o posterior:

import re
from graphlib import CycleError, TopologicalSorter

TASK_ID = re.compile(r"[a-z][a-z0-9_]{0,39}")


def checked_order(plan):
    if not isinstance(plan, dict) or not 1 <= len(plan) <= 100:
        raise ValueError("expected 1 to 100 tasks")
    if any(not isinstance(t, str) or not TASK_ID.fullmatch(t) for t in plan):
        raise ValueError("invalid task ID")
    for dependencies in plan.values():
        if not isinstance(dependencies, list) or any(
            not isinstance(d, str) for d in dependencies
        ):
            raise ValueError("dependencies must be lists of task IDs")
        if len(dependencies) != len(set(dependencies)):
            raise ValueError("repeated dependency")
    unknown = sorted({d for deps in plan.values() for d in deps} - set(plan))
    if unknown:
        raise ValueError("unknown dependencies: " + ", ".join(unknown))
    try:
        return tuple(TopologicalSorter(plan).static_order())
    except CycleError as error:
        raise ValueError("cycle detected") from error


def main():
    plan = {
        "collect": [],
        "draft": ["collect"],
        "artwork": ["collect"],
        "review": ["draft", "artwork"],
        "publish": ["review"],
    }
    order = checked_order(plan)
    positions = {task: i for i, task in enumerate(order)}
    assert set(order) == set(plan) and len(order) == len(plan)
    assert all(positions[d] < positions[t] for t in plan for d in plan[t])
    print("valid: 5 tasks, all prerequisites precede dependents")
    cases = [
        ("unknown", {"draft": ["missing"]}, "unknown dependencies: missing"),
        ("cycle", {"draft": ["review"], "review": ["draft"]}, "cycle detected"),
        ("self_cycle", {"review": ["review"]}, "cycle detected"),
        ("bad_shape", {"draft": "collect"}, "dependencies must be lists of task IDs"),
        ("repeated", {"a": [], "b": ["a", "a"]}, "repeated dependency"),
        ("empty", {}, "expected 1 to 100 tasks"),
    ]
    for label, candidate, expected in cases:
        try:
            checked_order(candidate)
        except ValueError as error:
            assert str(error) == expected
            print(f"{label}: rejected ({error})")
        else:
            raise AssertionError(f"{label} was accepted")


if __name__ == "__main__":
    main()

La función solo devuelve identificadores. Nombres como publish son etiquetas inertes del caso de prueba, no comandos, aprobaciones ni permisos para publicar. Mantén los manejadores ejecutables en código controlado por la aplicación, fuera de la respuesta del modelo.

3. Valida los casos positivos y negativos

Ejecuta sin la opción -O de Python para mantener activas las aserciones del caso de prueba:

python3 check_plan.py

Salida estándar esperada:

valid: 5 tasks, all prerequisites precede dependents
unknown: rejected (unknown dependencies: missing)
cycle: rejected (cycle detected)
self_cycle: rejected (cycle detected)
bad_shape: rejected (dependencies must be lists of task IDs)
repeated: rejected (repeated dependency)
empty: rejected (expected 1 to 100 tasks)

El comando se probó localmente con Python 3.11.4: código de salida 0 y salida de error vacía. La prueba acepta un plan de cinco tareas y rechaza seis planes inválidos. Los rechazos son resultados esperados, por lo que el comando completo termina correctamente; un caso aceptado por error provoca una aserción.

El caso positivo comprueba que cada tarea aparezca una sola vez y que cada requisito previo preceda a la tarea que depende de él. No exige un único orden exacto entre tareas independientes. Conserva estos casos al cambiar los prompts, siguiendo el enfoque de pruebas fijas de la comparación de regresiones entre prompts de IA.

4. Resuelve problemas antes de añadir ejecución

  • Dependencia desconocida: busca un error de escritura o una tarea omitida. Solicita una propuesta corregida; no crees silenciosamente una tarea vacía para completar la referencia.
  • Ciclo detectado: revisa las listas de requisitos previos con la persona responsable. Elimina una relación solo si cambia el requisito real, no simplemente para superar la validación.
  • Formato incorrecto o dependencia repetida: vuelve a pedir el mismo contrato de diccionario. No adivines si una cadena representa un identificador, varios o una explicación.
  • Grafo válido, flujo incorrecto: las comprobaciones estructurales no detectan una aprobación ausente ni una tarea inventada pero declarada. Contrasta los identificadores y los requisitos obligatorios con un registro y una política controlados por la aplicación. El tutorial de campos JSON permitidos para cambios de IA muestra el principio relacionado de restringir propuestas antes de escribir.
  • Fallos durante la ejecución: un orden no es un registro de tareas completadas. Un ejecutor real debe esperar a que los requisitos previos terminen correctamente, gestionar fallos y reintentos y comprobar permisos antes de causar efectos. Esta vista previa no implementa ninguno de esos comportamientos.

Conclusión práctica

Trata el plan generado por IA como una propuesta. Exige referencias conocidas, rechaza ciclos y valida el grafo completo antes de presentar un orden para su aprobación. Un plan que se puede ordenar está listo para revisar el flujo de trabajo, no para ejecutarse automáticamente.

Artículos relacionados

Últimos artículos