0Pricing
AI Agents · Lección

Python asíncrono para desarrolladores de agentes

Conceptos básicos de asyncio, async def, await y el bucle de eventos: el modelo mental de la asincronía.

Python asíncrono para desarrolladores de agentes es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Agents, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Agents incluye 4 lecciones en total.

¿Por qué usar programación asíncrona en los agentes?

Los agentes realizan muchas llamadas limitadas por E/S: API de LLM, solicitudes web y consultas a bases de datos. El código síncrono permanece inactivo mientras se completan estas llamadas. El código asíncrono ejecuta otro trabajo durante esas esperas, lo que mejora notablemente el rendimiento.

Fundamentos de la programación asíncrona: async def y await

async def declara una función corrutina. await suspende la ejecución hasta que se completa la operación esperada, lo que permite que el bucle de eventos ejecute otras corrutinas mientras tanto.

import asyncio

async def fetch_data(source: str) -> str:
    print(f'Starting fetch from {source}')
    await asyncio.sleep(1)  # Simulates a network call
    print(f'Finished fetch from {source}')
    return f'Data from {source}'

async def main():
    # Sequential: takes 2 seconds total
    result1 = await fetch_data('source-A')
    result2 = await fetch_data('source-B')
    print('Sequential results:', result1, result2)

asyncio.run(main())

# asyncio.run() starts the event loop and runs main()
# It is the entry point for async programs

El bucle de eventos

El bucle de eventos es el núcleo de asyncio. Gestiona una cola de corrutinas y callbacks de E/S, y los ejecuta cuando están listos. Todo el código asíncrono se ejecuta dentro del bucle de eventos en un único hilo.

import asyncio

async def task_a():
    print('Task A: start')
    await asyncio.sleep(2)
    print('Task A: done')

async def task_b():
    print('Task B: start')
    await asyncio.sleep(1)
    print('Task B: done')

async def main():
    # asyncio.gather runs both tasks concurrently
    # Total time: ~2 seconds (not 3)
    await asyncio.gather(task_a(), task_b())
    print('Both tasks complete')

# Expected output order:
# Task A: start
# Task B: start
# Task B: done   <- after 1s
# Task A: done   <- after 2s
# Both tasks complete
asyncio.run(main())

Corrutinas frente a hilos

Las corrutinas son cooperativas: ceden el control explícitamente mediante await. Los hilos son preventivos: el sistema operativo puede cambiar de uno a otro en cualquier momento. Las corrutinas son más ligeras, no tienen problemas con el GIL para operaciones de E/S y son más fáciles de razonar.

import asyncio
import threading
import time

# Thread approach: multiple OS threads
def thread_worker(name):
    print(f'Thread {name}: start')
    time.sleep(1)  # Blocks the thread
    print(f'Thread {name}: done')

threads = [threading.Thread(target=thread_worker, args=(i,)) for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print('---')

# Coroutine approach: single-threaded event loop
async def coro_worker(name):
    print(f'Coro {name}: start')
    await asyncio.sleep(1)  # Suspends, does NOT block other coroutines
    print(f'Coro {name}: done')

async def main():
    await asyncio.gather(*[coro_worker(i) for i in range(3)])

asyncio.run(main())
# Both approaches run 3 tasks in ~1 second total, but coroutines use one thread

Punto de entrada asyncio.run()

asyncio.run() crea un nuevo bucle de eventos, ejecuta la corrutina indicada hasta que se completa y cierra el bucle. Es el punto de entrada estándar para los programas asíncronos en Python 3.7 y posteriores.

import asyncio

async def agent_main():
    print('Agent starting')
    # All agent async work goes here
    results = await asyncio.gather(
        asyncio.sleep(0.1),  # Simulated LLM call
        asyncio.sleep(0.1),  # Simulated DB query
    )
    print('Agent done')
    return 'complete'

# Run the agent
result = asyncio.run(agent_main())
print('Result:', result)

# WRONG: calling asyncio.run() inside an already-running event loop
# In Jupyter notebooks, use: await agent_main() directly
# Or: nest_asyncio.apply() then asyncio.run()

Error común: bloquear en un contexto asíncrono

Nunca llame a funciones bloqueantes (time.sleep, requests.get o E/S de archivos síncrona) dentro de código asíncrono. Esto bloquea todo el bucle de eventos y elimina cualquier posibilidad de concurrencia.

import asyncio
import time
import httpx

# WRONG: blocks the event loop
async def bad_agent_step():
    time.sleep(2)          # Blocks all other coroutines
    # requests.get(url)    # Also blocks - do NOT use requests in async code
    return 'done'

# RIGHT: use async equivalents
async def good_agent_step():
    await asyncio.sleep(2)  # Suspends, other coroutines can run
    
    async with httpx.AsyncClient() as client:
        response = await client.get('https://api.example.com/data')
    return response.text

# For CPU-intensive work: use run_in_executor
import concurrent.futures

async def cpu_intensive_step(data: str):
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, expensive_cpu_fn, data)
    return result

def expensive_cpu_fn(data):
    # CPU-bound work runs in separate process
    return data.upper()

print('Blocking vs non-blocking patterns demonstrated')

Olvidar await: error silencioso

Olvidar await no produce un error: devuelve un objeto corrutina en lugar del resultado. Es un error silencioso que provoca errores posteriores o resultados vacíos.

import asyncio

async def get_answer() -> str:
    await asyncio.sleep(0.1)
    return 'The answer is 42'

async def bad_call():
    result = get_answer()   # WRONG: forgot await
    print(type(result))     # <class 'coroutine'> - not a string!
    # Using result as a string here causes AttributeError or wrong behavior
    return result

async def good_call():
    result = await get_answer()  # CORRECT
    print(type(result))           # <class 'str'>
    return result

async def main():
    bad = await bad_call()
    print('Bad result:', bad)    # coroutine object, not the string
    
    good = await good_call()
    print('Good result:', good)  # 'The answer is 42'
    
    # Clean up the uncollected coroutine
    if asyncio.iscoroutine(bad):
        bad.close()

asyncio.run(main())

Problema de los bucles de eventos anidados

Llamar a asyncio.run() dentro de un bucle de eventos que ya está en ejecución (por ejemplo, en Jupyter o FastAPI) genera un RuntimeError. Soluciones: use await directamente o use nest_asyncio en notebooks.

import asyncio

async def my_agent_coroutine():
    await asyncio.sleep(0.1)
    return 'done'

# In FastAPI or other async frameworks, the event loop is already running
# Use await directly in async endpoints:
async def fastapi_endpoint():
    # WRONG inside async context:
    # result = asyncio.run(my_agent_coroutine())  # RuntimeError!
    
    # CORRECT: just await
    result = await my_agent_coroutine()
    return result

# In Jupyter notebooks: install nest_asyncio
# import nest_asyncio
# nest_asyncio.apply()
# Then asyncio.run() works

# Detect if running in event loop:
def run_agent(coro):
    try:
        loop = asyncio.get_running_loop()
        # Already in async context
        import concurrent.futures
        with concurrent.futures.ThreadPoolExecutor() as pool:
            future = pool.submit(asyncio.run, coro)
            return future.result()
    except RuntimeError:
        # No running loop
        return asyncio.run(coro)

print('Nested event loop solution defined')

asyncio.create_task

Use asyncio.create_task() para programar la ejecución de una corrutina sin esperar a que se ejecute de inmediato. Esto permite iniciar varias tareas y esperar a que se completen más adelante.

import asyncio

async def background_job(job_id: int) -> str:
    await asyncio.sleep(0.5)
    return f'Job {job_id} completed'

async def main():
    # Start all tasks without waiting
    task1 = asyncio.create_task(background_job(1))
    task2 = asyncio.create_task(background_job(2))
    task3 = asyncio.create_task(background_job(3))
    
    # Do other work while tasks run
    print('Tasks started, doing other work...')
    await asyncio.sleep(0.1)
    print('Other work done')
    
    # Now wait for all tasks
    results = await asyncio.gather(task1, task2, task3)
    print('All results:', results)
    
    # Or wait for the first to complete
    task_a = asyncio.create_task(background_job(4))
    task_b = asyncio.create_task(background_job(5))
    done, pending = await asyncio.wait([task_a, task_b], return_when=asyncio.FIRST_COMPLETED)
    for t in pending:
        t.cancel()  # Cancel remaining tasks
    print('First result:', done.pop().result())

asyncio.run(main())

Gestores de contexto asíncronos

Muchas bibliotecas asíncronas utilizan gestores de contexto asíncronos con async with. Esto garantiza la correcta configuración y liberación de conexiones y recursos en código asíncrono.

import asyncio
import httpx

async def fetch_multiple_urls(urls: list) -> list:
    # async with ensures the client is properly closed
    async with httpx.AsyncClient(timeout=10.0) as client:
        # Fetch all URLs concurrently
        tasks = [client.get(url) for url in urls]
        responses = await asyncio.gather(*tasks, return_exceptions=True)
        
        results = []
        for url, response in zip(urls, responses):
            if isinstance(response, Exception):
                results.append({'url': url, 'error': str(response)})
            else:
                results.append({'url': url, 'status': response.status_code})
        return results

# Async generators for streaming
async def stream_agent_events():
    events = ['thinking', 'searching', 'generating', 'done']
    for event in events:
        await asyncio.sleep(0.2)  # Simulate event arrival
        yield event

async def consume_stream():
    async for event in stream_agent_events():
        print(f'Event: {event}')

asyncio.run(consume_stream())

Gestión de errores en código asíncrono

Use asyncio.gather(..., return_exceptions=True) para capturar los fallos de tareas individuales sin interrumpir todo el lote. Compruebe el tipo de excepción de cada resultado.

import asyncio

async def might_fail(task_id: int) -> str:
    await asyncio.sleep(0.1)
    if task_id == 2:
        raise ValueError(f'Task {task_id} failed')
    return f'Task {task_id} succeeded'

async def robust_gather():
    tasks = [might_fail(i) for i in range(1, 5)]
    
    # return_exceptions=True: exceptions are returned as values, not raised
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    successes = []
    failures = []
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            failures.append({'task': i + 1, 'error': str(result)})
        else:
            successes.append(result)
    
    print(f'Succeeded: {len(successes)}, Failed: {len(failures)}')
    print('Failures:', failures)
    return successes, failures

asyncio.run(robust_gather())

Comprobación de conocimientos: Python asíncrono

Ponga a prueba sus conocimientos sobre Python asíncrono para el desarrollo de agentes.

Resumen de Python asíncrono

Reglas fundamentales de Python asíncrono para desarrolladores de agentes: use async def/await para todas las operaciones limitadas por E/S; nunca llame a funciones bloqueantes en un contexto asíncrono; use asyncio.gather() para la ejecución en paralelo; use return_exceptions=True para llamadas paralelas tolerantes a fallos; use async with para gestionar recursos; ejecute el trabajo con un uso intensivo de CPU en un ejecutor de grupo de procesos.

Preguntas frecuentes

¿La lección «Python asíncrono para desarrolladores de agentes» es gratis?

Sí — el texto completo de «Python asíncrono para desarrolladores de agentes» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Agents, actualiza a CoddyKit PRO. El curso de AI Agents incluye 4 lecciones en total.

¿Qué aprenderé en «Python asíncrono para desarrolladores de agentes»?

Conceptos básicos de asyncio, async def, await y el bucle de eventos: el modelo mental de la asincronía. Practicas AI Agents con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Agents?

No se requiere experiencia previa. AI Agents en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «Python asíncrono para desarrolladores de agentes»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Agents?

Sí. Cada lección de AI Agents incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Python asíncrono para desarrolladores de agentes
  2. Colas de eventos y message brokers
  3. Ejecución paralela no bloqueante de herramientas
  4. Frameworks asíncronos para agentes: LangChain y más
← Volver a AI Agents