En este tutorial, creamos un agente de planificación consciente de los costos que equilibra deliberadamente la calidad de la salida con las restricciones del mundo real, como el uso de tokens, la latencia y los presupuestos de llamadas de herramientas. Diseñamos el agente para generar múltiples acciones candidatas, estimamos sus costos y beneficios esperados y luego seleccionamos un plan de ejecución que maximiza el valor sin salirse de presupuestos estrictos. Con esto, demostramos cómo los sistemas agentes pueden ir más allá del comportamiento de “usar siempre el LLM” y, en cambio, razonar explícitamente sobre las compensaciones, la eficiencia y el conocimiento de los recursos, lo cual es fundamental para implementar agentes de manera confiable en entornos restringidos. Consulta los CÓDIGOS COMPLETOS aquí.
importar os, tiempo, matemáticas, json, aleatorio desde clases de datos importar clase de datos, campo desde escribir importar Lista, Dict, Opcional, Tupla, Cualquiera desde getpass importar getpass USE_OPENAI = Verdadero si USE_OPENAI: si no os.getenv(“OPENAI_API_KEY”): os.environ[“OPENAI_API_KEY”] = getpass(“Ingrese OPENAI_API_KEY (oculto): “).strip() intente: desde openai import OpenAI client = OpenAI() excepto Excepción como e: print(“Error al importar el SDK de OpenAI. Volviendo al modo fuera de línea.\nError:”, e) USE_OPENAI = False
Configuramos el entorno de ejecución y cargamos de forma segura la clave API de OpenAI en tiempo de ejecución sin codificarla. También inicializamos el cliente para que el agente vuelva sin problemas al modo fuera de línea si la API no está disponible. Consulta los CÓDIGOS COMPLETOS aquí.
def approx_tokens(texto: str) -> int: return max(1, math.ceil(len(texto) / 4)) @dataclass class Presupuesto: max_tokens: int max_latency_ms: int max_tool_calls: int @dataclass class Gasto: tokens: int = 0 latency_ms: int = 0 tool_calls: int = 0 def inside(self, b: Budget) -> bool: return (self.tokens <= b.max_tokens y self.latency_ms <= b.max_latency_ms y self.tool_calls <= b.max_tool_calls) def add(self, other: "Gastar") -> “Gastar”: return Gastar( tokens=self.tokens + other.tokens, latency_ms=self.latency_ms + other.latency_ms, tool_calls=self.tool_calls + other.tool_calls)
Definimos las abstracciones presupuestarias centrales que permiten al agente razonar explícitamente sobre los costos. Modelamos el uso de tokens, la latencia y las llamadas de herramientas como cantidades de primera clase y proporcionamos métodos de utilidad para acumular y validar el gasto. Nos brinda una base limpia para hacer cumplir las restricciones durante la planificación y ejecución. Consulta los CÓDIGOS COMPLETOS aquí.
@dataclass clase StepOption: nombre: str descripción: str est_spend: Gastar valor_est: float ejecutor: str carga útil: Dict[str, Any] = campo(default_factory=dict) @dataclass clase PlanCandidate: pasos: Lista[StepOption]
gastar: Valor del gasto: float justificación: str = “” def llm_text(prompt: str, *, model: str = “gpt-5”, esfuerzo: str = “low”) -> str: si no USE_OPENAI: return “” t0 = time.time() resp = client.responses.create( model=model, razonamiento={“effort”: esfuerzo}, input=prompt, ) _ = (time.time() – t0) return resp.output_text o “”
Presentamos las estructuras de datos que representan opciones de acción individuales y candidatos a planes completos. También definimos un contenedor LLM liviano que estandariza cómo se genera y mide el texto. Esta separación permite al planificador razonar sobre las acciones de manera abstracta sin estar estrechamente vinculado a los detalles de ejecución. Consulta los CÓDIGOS COMPLETOS aquí.
def generate_step_options(tarea: str) -> Lista[StepOption]: base = [
StepOption(
name=”Clarify deliverables (local)”,
description=”Extract deliverable checklist + acceptance criteria from the task.”,
est_spend=Spend(tokens=60, latency_ms=20, tool_calls=0),
est_value=6.0,
executor=”local”,
),
StepOption(
name=”Outline plan (LLM)”,
description=”Create a structured outline with sections, constraints, and assumptions.”,
est_spend=Spend(tokens=600, latency_ms=1200, tool_calls=1),
est_value=10.0,
executor=”llm”,
payload={“prompt_kind”:”outline”}
),
StepOption(
name=”Outline plan (local)”,
description=”Create a rough outline using templates (no LLM).”,
est_spend=Spend(tokens=120, latency_ms=40, tool_calls=0),
est_value=5.5,
executor=”local”,
),
StepOption(
name=”Risk register (LLM)”,
description=”Generate risks, mitigations, owners, and severity.”,
est_spend=Spend(tokens=700, latency_ms=1400, tool_calls=1),
est_value=9.0,
executor=”llm”,
payload={“prompt_kind”:”risks”}
),
StepOption(
name=”Risk register (local)”,
description=”Generate a standard risk register from a reusable template.”,
est_spend=Spend(tokens=160, latency_ms=60, tool_calls=0),
est_value=5.0,
executor=”local”,
),
StepOption(
name=”Timeline (LLM)”,
description=”Draft a realistic milestone timeline with dependencies.”,
est_spend=Spend(tokens=650, latency_ms=1300, tool_calls=1),
est_value=8.5,
executor=”llm”,
payload={“prompt_kind”:”timeline”}
),
StepOption(
name=”Timeline (local)”,
description=”Draft a simple timeline from a generic milestone template.”,
est_spend=Spend(tokens=150, latency_ms=60, tool_calls=0),
est_value=4.8,
executor=”local”,
),
StepOption(
name=”Quality pass (LLM)”,
description=”Rewrite for clarity, consistency, and formatting.”,
est_spend=Spend(tokens=900, latency_ms=1600, tool_calls=1),
est_value=8.0,
executor=”llm”,
payload={“prompt_kind”:”polish”}
),
StepOption(
name=”Quality pass (local)”,
description=”Light formatting + consistency checks without LLM.”,
est_spend=Spend(tokens=120, latency_ms=50, tool_calls=0),
est_value=3.5,
executor=”local”,
),
]
if USE_OPENAI: meta_prompt = f””” Eres un asistente de planificación. Para la siguiente tarea, propone de 3 a 5 pasos adicionales OPCIONALES que mejoren la calidad, como comprobaciones, validaciones o personalización de las partes interesadas. Haz que cada paso sea breve. TAREA: {task} Devuelve una lista JSON con campos: nombre, descripción, valor_est(1-10). “”” txt = llm_text(meta_prompt, model=”gpt-5″, esfuerzo=”low”) Pruebe: items = json.loads(txt.strip()) para ello en items[:5]: base.append( StepOption( nombre=str(it.get(“nombre”,”Paso adicional (local)”))[:60]descripción=str(it.get(“descripción”,””))[:200]est_spend=Spend(tokens=120, latency_ms=60, tool_calls=0), est_value=float(it.get(“est_value”, 5.0)), executor=”local”, ) ) excepto Excepción: pasar la base de retorno
Nos enfocamos en generar un conjunto diverso de pasos candidatos, que incluyen alternativas locales y basadas en LLM con diferentes compensaciones entre costo y calidad. Opcionalmente, utilizamos el modelo en sí para sugerir mejoras adicionales de bajo costo y al mismo tiempo controlar su impacto en el presupuesto. Al hacerlo, enriquecemos el espacio de acción sin perder eficiencia. Consulta los CÓDIGOS COMPLETOS aquí.
def plan_under_budget (opciones: Lista[StepOption]presupuesto: Presupuesto, *, max_steps: int = 6, beam_width: int = 12, diversidad_penalidad: float = 0.2) -> PlanCandidate: def redundancy_cost(elegido: Lista[StepOption]nuevo: StepOption) -> float: key_new = new.name.split(“(“)[0].strip().lower() superposición = 0 para s elegido: key_s = s.name.split(“(“)[0].strip().lower() if key_s == key_new: superposición += 1 retorno superposición * vigas de penalización de diversidad: Lista[PlanCandidate] = [PlanCandidate(steps=[]gasto=Gasto(), valor=0.0, fundamento=””)]para _ en el rango(max_steps): expandido: Lista[PlanCandidate] = []
para cand en vigas: para optar por opciones: si opta por cand.steps: continuar new_spend = cand.spend.add(opt.est_spend) si no new_spend.within(presupuesto): continuar new_value = cand.value + opt.est_value – redundancy_cost(cand.steps, opt) expandido.append( PlanCandidate( pasos=cand.steps + [opt]gastar=nuevo_gasto, valor=nuevo_valor, fundamento=cand.rationale ) ) si no se expande: romper expandido.sort(clave=lambda c: c.valor, reverso=True) vigas = expandido[:beam_width]
mejor = max(vigas, clave=lambda c: c.value) devuelve mejor
Implementamos la lógica de planificación con presupuesto limitado que busca la combinación de pasos de mayor valor bajo límites estrictos. Aplicamos una búsqueda de estilo haz con penalizaciones por redundancia para evitar la superposición de acciones inútiles. Aquí es donde el agente realmente toma conciencia de los costos al optimizar el valor sujeto a restricciones. Consulta los CÓDIGOS COMPLETOS aquí.
def run_local_step(tarea: str, paso: StepOption, trabajando: Dict[str, Any]) -> str: nombre = paso.nombre.lower() si “aclarar los entregables” en el nombre: devolver ( “Lista de verificación de entregables:\n” “- Resumen ejecutivo\n- Alcance y supuestos\n- Plan de trabajo + hitos\n” “- Registro de riesgos (riesgo, impacto, probabilidad, mitigación, propietario)\n” “- Próximos pasos + datos necesarios\n” ) si “plan de esquema” en el nombre: devolver ( “Esquema:\n1) Contexto y objetivo\n2) Alcance\n3) Enfoque\n4) Cronograma\n5) Riesgos\n6) Próximos pasos\n” ) si “registro de riesgos” en el nombre: return ( “Registro de riesgos (plantilla):\n” “1) Retrasos en el acceso a los datos | Alta | Mitigación: implementación por fases\n” ) si “línea de tiempo” en el nombre: return ( “Línea de tiempo (plantilla):\n” “Semana 1: descubrimiento + requisitos\nSemana 2: prototipo + comentarios\n” “Semana 3: piloto + métricas\nSemana 4: implementación + entrega\n” ) si “pase de calidad” en el nombre: borrador = trabajando.get(“borrador”, “”) devuelve “Pase de calidad ligera realizado (títulos normalizados, viñetas alineadas).\n” + borrador devuelve f”Completado: {step.name}\n” def run_llm_step(tarea: str, paso: StepOption, trabajando: Dict[str, Any]) -> str: kind = step.payload.get(“prompt_kind”, “generic”) context =working.get(“draft”, “”) Prompts = { “outline”: f”Cree un esquema nítido y estructurado para la siguiente tarea.\nTASK:\n{tarea}\nDevuelva un esquema numerado.”, “risks”: f”Cree un registro de riesgos para la siguiente tarea. Incluya: Riesgo | Impacto | Probabilidad | Mitigación | Propietario.\nTASK:\n{task}”, “timeline”: f”Crea una línea de tiempo realista con dependencias para la siguiente tarea.\nTASK:\n{task}”, “polish”: f”Reescribe y pule el siguiente borrador para mayor claridad y coherencia.\nDRAFT:\n{context}”, “generic”: f”Ayuda con este paso: {paso.descripción}\nTASK:\n{tarea}\nCURRENT:\n{context}”, } return llm_text(prompts.get(tipo, mensajes[“generic”]), model=”gpt-5″, esfuerzo=”low”) def ejecutar_plan(tarea: str, plan: PlanCandidate) -> Tupla[str, Spend]: trabajando = {“borrador”: “”} real = Gastar() para i, paso en enumerar(plan.steps, 1): t0 = time.time() if step.executor == “llm” y USE_OPENAI: out = run_llm_step(tarea, paso, trabajando) tool_calls = 1 else: out = run_local_step(tarea, paso, trabajando) tool_calls = 0 dt_ms = int((time.time() – t0) * 1000) tok = approx_tokens(out) actual = actual.add(Spend(tokens=tok, latency_ms=dt_ms, tool_calls=tool_calls)) trabajando[“draft”] += f”\n\n### Paso {i}: {step.name}\n{out}\n” vuelve a funcionar[“draft”].strip(), actual TASK = “Redactar una propuesta de proyecto de 1 página para un panel de logística + piloto de optimización de flota, incluido el alcance, el cronograma y los riesgos”. PRESUPUESTO = Presupuesto( max_tokens=2200, max_latency_ms=3500, max_tool_calls=2 ) opciones = generar_step_options(TASK) mejor_plan = plan_under_budget(opciones, PRESUPUESTO, max_steps=6, beam_width=14) print(“=== PLAN SELECCIONADO (consciente del presupuesto) ===”) para s en best_plan.steps: print(f”- {s.name} | est_spend={s.est_spend} | est_value={s.est_value}”) print(“\nGasto estimado:”, best_plan.spend) print(“Presupuesto:”, PRESUPUESTO) print(“\n=== PLAN DE EJECUCIÓN ===”) borrador, real = ejecutar_plan(TASK, best_plan) print(“\n=== BORRADOR DE SALIDA ===\n”) print(borrador[:6000]) print(“\n=== GASTO REAL (aprox.) ===”) print(actual) print(“\n¿Dentro del presupuesto?”, actual.dentro(PRESUPUESTO))
Ejecutamos el plan seleccionado y realizamos un seguimiento del uso real de los recursos paso a paso. Elegimos dinámicamente entre rutas de ejecución local y LLM y agregamos el resultado final en un borrador coherente. Al comparar el gasto estimado y real, demostramos cómo los supuestos de planificación pueden validarse y perfeccionarse en la práctica.
En conclusión, demostramos cómo un agente de planificación consciente de los costos puede razonar sobre su consumo de recursos y adaptar su comportamiento en tiempo real. Ejecutamos solo los pasos que se ajustan a los presupuestos predefinidos y realizamos un seguimiento del gasto real para validar los supuestos de planificación, cerrando el ciclo entre la estimación y la ejecución. Además, destacamos cómo los sistemas de IA agentes pueden volverse más prácticos, controlables y escalables al tratar el costo, la latencia y el uso de herramientas como variables de decisión de primera clase en lugar de consideraciones posteriores.
Consulta los CÓDIGOS COMPLETOS aquí. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 100.000 ML y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.
La publicación ¿Cómo elige un agente de IA qué hacer bajo las restricciones presupuestarias de tokens, latencia y llamadas de herramientas? apareció por primera vez en MarkTechPost.