Tool Orchestration en AI Agents: El 90% Ejecuta Tool Calls Como si Fuera 1999
El 90% de AI Agents ejecutan tool calls secuenciales. Aprende el Patrón de Orquestación en 3 Fases: DAG, paralelización y ejecución topológica para agents eficientes.
El 90% de los AI Agents que Ves en GitHub Ejecutan Tool Calls Como si Fuera 1999
En secuencia. Uno detrás de otro. Esperando a que cada llamada termine para empezar la siguiente.
*Y funcionan. Pero funcionan mal, lento, y con un coste de contexto que se come tu presupuesto de tokens. *
El 90% de las implementaciones tratan el agent loop como una `lista de tareas` secuencial.
El problema es que deberían tratarlo como un `grafo de dependencias`.
No es teoría. Es la diferencia entre un agente que tarda 45 segundos en dar una respuesta vs uno que tarda 8. Es la diferencia entre un contexto de 12.000 tokens vs uno de 4.000. Es la diferencia entre un agente que entra en bucle infinito y uno que termina limpiamente.
La comunidad cree que el problema de los AI Agents es el modelo. Mejor LLM → mejor agente.
O la conexión a herramientas. MCP, function calling, APIs.
*La realidad: ambos problemas están resueltos. El cuello de botella real es la orquestación. *
Cómo decides qué tool call ejecutar cuándo, en qué orden, y en paralelo.
---
El Patrón Que Llevamos Décadas Resolviendo (y Que Hemos Olvidado)
La ironía es que esto no es nuevo en computación.
El patrón de 3 fases que te voy a enseñar — Análisis → DAG → Ejecución topológica — es el mismo que usan los build systems como Make, Bazel o Turborepo.
Llevamos décadas resolviendo el problema de paralelizar tareas con dependencias.
*Y al llegar a los AI Agents, lo hemos olvidado. *
Un agent loop secuencial es equivalente a compilar un proyecto con un solo core en 2026.
Tiene sentido cuando tienes 2-3 herramientas secuenciales. Pero cuando tu agente ejecuta 5+ tool calls por turno con múltiples herramientas independientes, el loop plano se convierte en tu cuello de botella más caro.
---
Los 3 Problemas del Loop Secuencial Plano
Antes de ver la solución, entendamos qué se rompe cuando ejecutas tool calls en secuencia plana.
1. Latencia desperdiciada
Tienes 5 tool calls. Las primeras 3 son independientes entre sí: buscar tendencias en Google Trends, analizar competidores en Crunchbase, extraer reviews de Trustpilot. Ninguna necesita el resultado de la otra.
Con un loop secuencial: llamada 1 → esperar → llamada 2 → esperar → llamada 3 → esperar.
*Has perdido 2/3 del tiempo en espera que podrías haber paralelizado. *
2. Pérdida de contexto entre llamadas
Cada tool call devuelve datos que se inyectan en el prompt del LLM en la siguiente iteración.
Con 5-10 tool calls secuenciales, el contexto se duplica o triplica con datos intermedios que ya no necesitas. El LLM tiene que procesar información irrelevante, aumenta el coste de tokens, y la calidad de las respuestas se degrada.
El problema más silencioso de los agents secuenciales no es la velocidad. Es la degradación del contexto.
3. Bucles infinitos
Cuando un agente ejecuta tool calls en un loop plano y una llamada devuelve un resultado inesperado, el LLM no tiene suficiente información para decidir si debe parar o seguir.
No es culpa del LLM. Es culpa de la orquestación.
Con un grafo de dependencias, tienes un estado explícito del progreso: sabes qué nodos se completaron, cuáles fallaron, y cuáles están pendientes. Puedes detectar bucles (mismo nodo ejecutándose repetidamente) y aplicar políticas de terminación claras.
---
❌/✅ El Anti-Patrón vs El Patrón Real
❌ Lo que el 90% hace: Loop secuencial plano
```python
Anti-patrón: ejecución secuencial plana
El 90% de los agents en GitHub hacen esto
def agent_loop_secuencial(prompt, tools):
messages = [{"role": "user", "content": prompt}]
while True:
response = llm.chat(messages)
tool_calls = extract_tool_calls(response)
if not tool_calls:
return response.content
for tool_call in tool_calls:
UNO DETRÁS DE OTRO ⚠️
result = execute_tool(tool_call)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
Siguiente iteración del bucle — el LLM recibe TODO el contexto acumulado
```
Este código funciona. Para 2-3 tool calls secuenciales, es suficiente.
*Pero en cuanto llegas a 5+ llamadas, el coste de contexto explota y la latencia se triplica. *
✅ Lo que deberías hacer: Grafo de dependencias con ejecución topológica
```python
Patrón correcto: orquestación basada en grafo de dependencias
from collections import deque
from concurrent.futures import ThreadPoolExecutor, as_completed
class ToolCallNode:
def __init__(self, tool_call, deps=None):
self.tool_call = tool_call
self.dependencies = deps or [] # IDs de nodos de los que depende
self.result = None
self.status = "pending" # pending | running | completed | failed
class DependencyGraph:
def __init__(self):
self.nodes = {} # node_id -> ToolCallNode
self.adjacency = {} # node_id -> [dependent_node_ids]
def add_node(self, node_id, tool_call, deps=None):
self.nodes[node_id] = ToolCallNode(tool_call, deps)
self.adjacency[node_id] = []
for dep_id in (deps or []):
if dep_id not in self.adjacency:
self.adjacency[dep_id] = []
self.adjacency[dep_id].append(node_id)
def get_ready_nodes(self):
"""Nodos sin dependencias pendientes — candidatos a ejecución paralela"""
ready = []
for node_id, node in self.nodes.items():
if node.status != "pending":
continue
all_deps_done = all(
self.nodes[dep].status == "completed"
for dep in node.dependencies
)
if all_deps_done:
ready.append(node_id)
return ready
def execute_graph(graph, max_workers=4):
"""Ejecuta el grafo por niveles — cada nivel en paralelo"""
with ThreadPoolExecutor(max_workers=max_workers) as executor:
while any(n.status == "pending" for n in graph.nodes.values()):
ready = graph.get_ready_nodes()
if not ready:
raise RuntimeError("Deadlock detected: circular dependency")
futures = {}
for node_id in ready:
node = graph.nodes[node_id]
node.status = "running"
future = executor.submit(execute_tool, node.tool_call)
futures[future] = node_id
for future in as_completed(futures):
node_id = futures[future]
node = graph.nodes[node_id]
try:
node.result = future.result()
node.status = "completed"
except Exception as e:
node.status = "failed"
Propagación selectiva: solo fallan los dependientes
propagate_failure(graph, node_id, e)
```
---
El Framework de 3 Fases para Orquestación de Tool Calls
Llamémoslo como es: El Patrón de Orquestación en 3 Fases. No es complejo. Es estructurado. Y cada fase resuelve un problema concreto del loop secuencial.
Fase 1: Análisis de dependencias
Cuando el LLM devuelve múltiples tool calls, no las ejecutes inmediatamente.
Primero, analiza los parámetros de entrada de cada una para identificar dependencias de datos entre ellas.
Pregunta clave: ¿La tool B necesita el output de la tool A para ejecutarse?
```python
Fase 1: Analizar dependencias entre tool calls
def analyze_dependencies(tool_calls):
"""
Analiza los parámetros de entrada de cada tool call
para identificar dependencias de datos
"""
dependency_map = {tc.id: [] for tc in tool_calls}
tool_outputs = {} # Mapa de tool_call_id -> {campos de salida}
for tc in tool_calls:
for param_name, param_value in tc.parameters.items():
¿El valor del parámetro hace referencia al output de otra tool?
if isinstance(param_value, str) and param_value.startswith("$"):
ref_tool_id = param_value.split(".")[0].replace("$", "")
if ref_tool_id in tool_outputs:
dependency_map[tc.id].append(ref_tool_id)
return dependency_map
```
En la práctica, esta fase suele ser más sencilla: el propio LLM puede etiquetar las dependencias si se lo pides explícitamente en el system prompt. Pero tener el análisis programático como respaldo evita que el LLM se equivoque.
Fase 2: Construcción del DAG
Genera un grafo dirigido acíclico donde los nodos son tool calls y las aristas son dependencias.
Identifica los nodos sin dependencias (fuentes) como candidatos a ejecución paralela inmediata.
```python
Fase 2: Construir el DAG
def build_dag(tool_calls, dependency_map):
graph = DependencyGraph()
Validar que no haya dependencias circulares
(el LLM a veces genera bucles)
if has_cycles(dependency_map):
raise ValueError("Circular dependency detected in tool calls")
for tc in tool_calls:
deps = dependency_map.get(tc.id, [])
graph.add_node(tc.id, tc, deps=deps)
return graph
```
El check de ciclos es importante. El LLM puede generar tool calls que se refieran unas a otras creando un bucle. Detectarlo en la Fase 2 evita que el agente se cuelgue.
Fase 3: Ejecución con planificación topológica
Ejecuta los nodos fuente en paralelo, recolecta resultados, desbloquea nodos dependientes, y repite hasta completar el grafo.
*Cada iteración del bucle principal ejecuta UN NIVEL completo del grafo, no UNA tool call. *
```python
Fase 3: Ejecutor con planificación topológica
def orchestrator_loop(prompt, tools):
messages = [{"role": "user", "content": prompt}]
while True:
response = llm.chat(messages)
tool_calls = extract_tool_calls(response)
if not tool_calls:
return response.content
Fase 1 + 2: Analizar y construir grafo
dep_map = analyze_dependencies(tool_calls)
graph = build_dag(tool_calls, dep_map)
Fase 3: Ejecutar por niveles
level_results = execute_graph(graph)
Solo inyectar los resultados relevantes
(no todo el historial de llamadas)
concise_context = summarize_results(level_results)
messages.append({
"role": "user",
"content": f"Resultados de herramientas:\n{concise_context}"
})
```
---
Manejador de Errores Inteligente: Propagar Solo lo Necesario
El beneficio más infravalorado del grafo de dependencias es el manejo de errores.
En un loop secuencial, si la tool call #3 falla, tienes dos opciones: reintentar todo desde el principio o ignorar el error y seguir. Ambas son malas.
Con un grafo de dependencias, puedes propagar el fallo solo a los nodos dependientes:
```python
def propagate_failure(graph, failed_node_id, error):
"""Propaga el fallo solo a los nodos que dependen del nodo fallido"""
queue = deque([failed_node_id])
visited = set()
while queue:
current = queue.popleft()
if current in visited:
continue
visited.add(current)
Marcar como fallido
if current != failed_node_id:
graph.nodes[current].status = "failed"
graph.nodes[current].result = {"error": str(error)}
Propagar a dependientes
for dependent_id in graph.adjacency.get(current, []):
if dependent_id not in visited:
queue.append(dependent_id)
```
Esto significa que si una tool falla, solo se detienen las que necesitaban su resultado. El resto del grafo sigue ejecutándose. El agente puede tomar decisiones parciales con los datos que sí consiguió.
---
Caso Real: Agente de Investigación de Mercado en 12 Segundos
Trabajo con un agente que investiga mercados. Necesita:
1. Buscar tendencias en Google Trends
2. Analizar 5 competidores en Crunchbase
3. Extraer reviews de Trustpilot
4. Sintetizar un informe
Con ejecución secuencial:
8-12 llamadas secuenciales
~45 segundos de latencia total
Contexto masivo con datos intermedios de cada paso
Con grafo de dependencias:
Fase 1: Ejecuta (1) y (2) en paralelo — 3 source calls simultáneas
Fase 2: Ejecuta (3) y (4) con datos de fase 1
Total: ~12 segundos
Contexto: 40% menos tokens porque solo pasas datos relevantes entre niveles
*La diferencia no es incremental. Es estructural. *
---
Cuándo Usar Cada Patrón (Sé Honesto)
No te voy a vender que el grafo de dependencias es siempre la solución.
Usa loop secuencial plano si:
Tu agente ejecuta 2-3 tool calls por turno
Todas las herramientas son estrictamente secuenciales (cada una necesita el resultado de la anterior)
El tiempo de respuesta no es crítico
Usa el Patrón de Orquestación en 3 Fases si:
Tu agente ejecuta 5+ tool calls por turno
Tienes herramientas independientes que se pueden paralelizar
La latencia importa (UX en tiempo real, asistentes conversacionales, automatización)
El coste de contexto empieza a dispararse
El umbral está claro: 5 tool calls. Por debajo, el overhead del grafo no compensa. Por encima, el loop secuencial te está costando tiempo, tokens, y calidad.
---
¿Y MCP? No Resuelve Esto
MCP resuelve el problema de "cómo conecto mi agente a 20 APIs diferentes". Es un problema de protocolo.
Pero una vez que tienes 20 herramientas conectadas, el problema pasa a ser "en qué orden las llamo y cuáles puedo llamar simultáneamente".
*MCP no toca ese problema. La orquestación es el layer que falta entre MCP y el agent loop. *
Si solo implementas MCP y dejas el agent loop secuencial, tienes herramientas ilimitadas conectadas a un pipeline que las ejecuta una a una. Como tener 20 carriles en una autopista que se reduce a un solo carril en cada peaje.
---
Lo Que Te Llevas
El 90% de los AI Agents en GitHub ejecutan tool calls secuenciales. Y funcionan. Pero funcionan mal, lento, y caros.
El salto de calidad real no está en el modelo. No está en MCP. Está en cómo orquestas las llamadas.
Tres fases: analiza dependencias, construye el DAG, ejecuta por niveles topológicos. Cada fase resuelve un problema concreto del loop secuencial.
La herramienta para construir el agente es secundaria. La arquitectura de orquestación es primaria.
*El mejor modelo con mala orquestación pierde siempre contra un modelo decente con buena orquestación. *
La próxima vez que construyas un AI Agent, no empieces por el LLM. Empieza por el grafo.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

