Connect with us

Llamadas a la API de LLM Asincrónicas en Python: Una Guía Completa

Inteligencia artificial

Llamadas a la API de LLM Asincrónicas en Python: Una Guía Completa

mm
Asynchronous LLM API Calls in Python: A Comprehensive Guide

Como desarrolladores y científicos de datos, a menudo nos encontramos necesitando interactuar con estos potentes modelos a través de API. Sin embargo, a medida que nuestras aplicaciones crecen en complejidad y escala, la necesidad de interacciones de API eficientes y de alto rendimiento se vuelve crucial. Es aquí donde la programación asincrónica brilla, lo que nos permite maximizar el rendimiento y minimizar la latencia al trabajar con API de LLM.

En esta guía completa, exploraremos el mundo de las llamadas a la API de LLM asincrónicas en Python. Cubriremos todo, desde los conceptos básicos de la programación asincrónica hasta técnicas avanzadas para el manejo de flujos de trabajo complejos. Al final de este artículo, tendrá una comprensión sólida de cómo aprovechar la programación asincrónica para potenciar sus aplicaciones impulsadas por LLM.

Antes de sumergirnos en los detalles específicos de las llamadas a la API de LLM asincrónicas, establezcamos una base sólida en los conceptos de programación asincrónica.

La programación asincrónica permite que se ejecuten varias operaciones de forma concurrente sin bloquear el hilo principal de ejecución. En Python, esto se logra principalmente a través del módulo asyncio, que proporciona un marco para escribir código concurrente utilizando coroutines, bucles de eventos y futuros.

Conceptos clave:

  • Coroutines: Funciones definidas con async def que se pueden pausar y reanudar.
  • Bucle de eventos: El mecanismo de ejecución central que gestiona y ejecuta tareas asincrónicas.
  • Objetos esperables: Objetos que se pueden usar con la palabra clave await (coroutines, tareas, futuros).

Aquí hay un ejemplo sencillo para ilustrar estos conceptos:

import asyncio

async def saludar(nombre):
await asyncio.sleep(1) # Simula una operación de E/S
print(f"Hola, {nombre}!")

async def main():
await asyncio.gather(
saludar("Alice"),
saludar("Bob"),
saludar("Charlie")
)

asyncio.run(main())

En este ejemplo, definimos una función asincrónica saludar que simula una operación de E/S con asyncio.sleep(). La función main utiliza asyncio.gather() para ejecutar múltiples saludos de forma concurrente. A pesar del retraso de la pausa, los tres saludos se imprimirán después de aproximadamente 1 segundo, demostrando el poder de la ejecución asincrónica.

La necesidad de asincronía en las llamadas a la API de LLM

Al trabajar con API de LLM, a menudo nos encontramos con escenarios en los que debemos realizar múltiples llamadas a la API, ya sea en secuencia o en paralelo. El código sincrónico tradicional puede generar cuellos de botella de rendimiento significativos, especialmente al tratar con operaciones de alta latencia como las solicitudes de red a los servicios de LLM.

Consideremos un escenario en el que debemos generar resúmenes para 100 artículos diferentes utilizando una API de LLM. Con un enfoque sincrónico, cada llamada a la API bloquearía hasta que reciba una respuesta, lo que podría tomar varios minutos para completar todas las solicitudes. Un enfoque asincrónico, por otro lado, nos permite iniciar múltiples llamadas a la API de forma concurrente, reduciendo dramáticamente el tiempo de ejecución total.

Configuración del entorno

Para comenzar con las llamadas a la API de LLM asincrónicas, necesitará configurar su entorno de Python con las bibliotecas necesarias. Aquí está lo que necesitará:

  • Python 3.7 o superior (para soporte nativo de asyncio)
  • aiohttp: Una biblioteca de cliente HTTP asincrónica
  • openai: El cliente de Python oficial de OpenAI (si está utilizando los modelos GPT de OpenAI)
  • langchain: Un marco para construir aplicaciones con LLM (opcional, pero recomendado para flujos de trabajo complejos)

Puede instalar estas dependencias utilizando pip:

pip install aiohttp openai langchain
<div class="relative flex flex-col rounded-lg">

Llamadas básicas a la API de LLM asincrónicas con asyncio y aiohttp

Comencemos haciendo una llamada asincrónica simple a una API de LLM utilizando aiohttp. Utilizaremos la API GPT-3.5 de OpenAI como ejemplo, pero los conceptos se aplican a otras API de LLM también.

import asyncio
import aiohttp
from openai import AsyncOpenAI

async def generar_texto(prompt, client):
respuesta = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}]
)
return respuesta.choices[0].message.content

async def main():
prompts = [
"Explica la computación cuántica en términos sencillos.",
"Escribe un haikú sobre la inteligencia artificial.",
"Describe el proceso de la fotosíntesis."
]

async with AsyncOpenAI() as client:
tareas = [generar_texto(prompt, client) for prompt in prompts]
resultados = await asyncio.gather(*tareas)

for prompt, resultado in zip(prompts, resultados):
print(f"Prompt: {prompt}\nRespuesta: {resultado}\n")

asyncio.run(main())

En este ejemplo, definimos una función asincrónica generar_texto que hace una llamada a la API de OpenAI utilizando el cliente AsyncOpenAI. La función main crea múltiples tareas para diferentes prompts y utiliza asyncio.gather() para ejecutarlas de forma concurrente.

Este enfoque nos permite enviar múltiples solicitudes a la API de LLM simultáneamente, reduciendo significativamente el tiempo total necesario para procesar todos los prompts.

Técnicas avanzadas: agrupación y control de concurrencia

Si bien el ejemplo anterior demuestra los conceptos básicos de las llamadas a la API de LLM asincrónicas, las aplicaciones del mundo real a menudo requieren enfoques más sofisticados. Exploraremos dos técnicas importantes: agrupación de solicitudes y control de concurrencia.

Agrupación de solicitudes: al tratar con una gran cantidad de prompts, a menudo es más eficiente agruparlos en grupos en lugar de enviar solicitudes individuales para cada prompt. Esto reduce la sobrecarga de múltiples llamadas a la API y puede conducir a un mejor rendimiento.

import asyncio
from openai import AsyncOpenAI

async def procesar_lote(lote, client):
respuestas = await asyncio.gather(*[
client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}]
) for prompt in lote
])
return [respuesta.choices[0].message.content for respuesta in respuestas]

async def main():
prompts = [f"Cuéntame un hecho sobre el número {i}" for i in range(100)]
tamaño_lote = 10

async with AsyncOpenAI() as client:
resultados = []
for i in range(0, len(prompts), tamaño_lote):
lote = prompts[i:i+tamaño_lote]
resultados_lote = await procesar_lote(lote, client)
resultados.extend(resultados_lote)

for prompt, resultado in zip(prompts, resultados):
print(f"Prompt: {prompt}\nRespuesta: {resultado}\n")

asyncio.run(main())

Control de concurrencia: si bien la programación asincrónica permite la ejecución concurrente, es importante controlar el nivel de concurrencia para evitar abrumar al servidor de la API o exceder los límites de velocidad. Podemos utilizar asyncio.Semaphore para este propósito.

import asyncio
from openai import AsyncOpenAI

async def generar_texto(prompt, client, semáforo):
async with semáforo:
respuesta = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}]
)
return respuesta.choices[0].message.content

async def main():
prompts = [f"Cuéntame un hecho sobre el número {i}" for i in range(100)]
solicitudes_concurrentes_máximas = 5
semáforo = asyncio.Semaphore(solicitudes_concurrentes_máximas)

async with AsyncOpenAI() as client:
tareas = [generar_texto(prompt, client, semáforo) for prompt in prompts]
resultados = await asyncio.gather(*tareas)

for prompt, resultado in zip(prompts, resultados):
print(f"Prompt: {prompt}\nRespuesta: {resultado}\n")

asyncio.run(main())

En este ejemplo, utilizamos un semáforo para limitar el número de solicitudes concurrentes a 5, asegurando que no abrumemos al servidor de la API.

Manejo de errores y reintentos en llamadas a la API de LLM asincrónicas

Al trabajar con API externas, es crucial implementar un manejo de errores y mecanismos de reintento robustos. Mejoremos nuestro código para manejar errores comunes e implementar un reintento exponencial.

import asyncio
import random
from openai import AsyncOpenAI
from tenacity import retry, stop_after_attempt, wait_exponential

class APIError(Exception):
pass

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def generar_texto_con_reintento(prompt, client):
try:
respuesta = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}]
)
return respuesta.choices[0].message.content
except Exception as e:
print(f"Error ocurrió: {e}")
raise APIError("Falló al generar texto")

async def procesar_prompt(prompt, client, semáforo):
async with semáforo:
try:
resultado = await generar_texto_con_reintento(prompt, client)
return prompt, resultado
except APIError:
return prompt, "Falló al generar respuesta después de múltiples intentos."

async def main():
prompts = [f"Cuéntame un hecho sobre el número {i}" for i in range(20)]
semáforo = asyncio.Semaphore(5)

async with AsyncOpenAI() as client:
tareas = [procesar_prompt(prompt, client, semáforo) for prompt in prompts]
resultados = await asyncio.gather(*tareas)

for prompt, resultado in resultados:
print(f"Prompt: {prompt}\nRespuesta: {resultado}\n")

asyncio.run(main())

Esta versión mejorada incluye:

  • Una excepción personalizada APIError para errores relacionados con la API.
  • Una función generar_texto_con_reintento decorada con @retry de la biblioteca tenacity, implementando un reintento exponencial.
  • Manejo de errores en la función procesar_prompt para capturar y informar fallas.

Optimización del rendimiento: transmisión de respuestas

Para la generación de contenido de larga forma, la transmisión de respuestas puede mejorar significativamente el rendimiento percibido de su aplicación. En lugar de esperar a que toda la respuesta esté disponible, puede procesar y mostrar fragmentos de texto a medida que están disponibles.

import asyncio
from openai import AsyncOpenAI

async def transmitir_texto(prompt, client):
flujo = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
stream=True
)

respuesta_completa = ""
async for fragmento in flujo:
if fragmento.choices[0].delta.content is not None:
contenido = fragmento.choices[0].delta.content
respuesta_completa += contenido
print(contenido, end='', flush=True)

print("\n")
return respuesta_completa

async def main():
prompt = "Escribe una historia corta sobre un científico que viaja en el tiempo."

async with AsyncOpenAI() as client:
resultado = await transmitir_texto(prompt, client)

print(f"Respuesta completa:\n{resultado}")

asyncio.run(main())

Este ejemplo demuestra cómo transmitir la respuesta desde la API, imprimiendo cada fragmento a medida que llega. Este enfoque es particularmente útil para aplicaciones de chat o cualquier escenario en el que desee proporcionar retroalimentación en tiempo real al usuario.

Construcción de flujos de trabajo asincrónicos con LangChain

Para aplicaciones de LLM más complejas, el marco de LangChain proporciona una abstracción de alto nivel que simplifica el proceso de encadenar múltiples llamadas a la API de LLM y la integración de otras herramientas. Veamos un ejemplo de cómo utilizar LangChain con capacidades asincrónicas:

Este ejemplo muestra cómo LangChain se puede utilizar para crear flujos de trabajo más complejos con transmisión y ejecución asincrónica. El AsyncCallbackManager y StreamingStdOutCallbackHandler permiten la transmisión en tiempo real del contenido generado.

import asyncio
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.callbacks.manager import AsyncCallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

async def generar_historia(tema):
llm = OpenAI(temperature=0.7, streaming=True, callback_manager=AsyncCallbackManager([StreamingStdOutCallbackHandler()]))
prompt = PromptTemplate(
input_variables=["tema"],
template="Escribe una historia corta sobre {tema}."
)
cadena = LLMChain(llm=llm, prompt=prompt)
return await cadena.arun(tema=tema)

async def main():
temas = ["un bosque mágico", "una ciudad futurista", "una civilización submarina"]
tareas = [generar_historia(tema) for tema in temas]
historias = await asyncio.gather(*tareas)

for tema, historia in zip(temas, historias):
print(f"\nTema: {tema}\nHistoria: {historia}\n{'='*50}\n")

asyncio.run(main())

Servicio de aplicaciones de LLM asincrónicas con FastAPI

Para hacer que su aplicación de LLM asincrónica esté disponible como un servicio web, FastAPI es una excelente opción debido a su soporte nativo para operaciones asincrónicas. Aquí hay un ejemplo de cómo crear un punto de conexión de API simple para la generación de texto:

from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from openai import AsyncOpenAI

app = FastAPI()
client = AsyncOpenAI()

class SolicitudGeneración(BaseModel):
prompt: str

class RespuestaGeneración(BaseModel):
texto_generado: str

@app.post("/generar", response_model=RespuestaGeneración)
async def generar_texto(solicitud: SolicitudGeneración, tareas_en_segundo_plano: BackgroundTasks):
respuesta = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": solicitud.prompt}]
)
texto_generado = respuesta.choices[0].message.content

# Simula algún procesamiento posterior en segundo plano
tareas_en_segundo_plano.add_task(registrar_generación, solicitud.prompt, texto_generado)

return RespuestaGeneración(texto_generado=texto_generado)

async def registrar_generación(prompt: str, texto_generado: str):
# Simula registro o procesamiento adicional
await asyncio.sleep(2)
print(f"Registrado: Prompt '{prompt}' generó texto de longitud {len(texto_generado)}")

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

Esta aplicación FastAPI crea un punto de conexión /generar que acepta un prompt y devuelve texto generado. También demuestra cómo utilizar tareas en segundo plano para procesamiento adicional sin bloquear la respuesta.

Prácticas recomendadas y errores comunes

Al trabajar con API de LLM asincrónicas, tenga en cuenta estas prácticas recomendadas:

  1. Utilice agrupación de conexiones: cuando realice múltiples solicitudes, reutilice conexiones para reducir la sobrecarga.
  2. Implemente un manejo de errores adecuado: siempre tenga en cuenta problemas de red, errores de API y respuestas inesperadas.
  3. Respete los límites de velocidad: utilice semáforos o otros mecanismos de control de concurrencia para evitar abrumar al servidor de la API.
  4. Monitoree y registre: implemente un registro exhaustivo para rastrear el rendimiento y identificar problemas.
  5. Utilice transmisión para contenido de larga forma: mejora la experiencia del usuario y permite el procesamiento temprano de resultados parciales.

He pasado los últimos cinco años sumergiéndome en el fascinante mundo del Aprendizaje Automático y el Aprendizaje Profundo. Mi pasión y experiencia me han llevado a contribuir a más de 50 proyectos de ingeniería de software diversos, con un enfoque particular en AI/ML. Mi curiosidad continua también me ha llevado hacia el Procesamiento de Lenguaje Natural, un campo que estoy ansioso por explorar más a fondo.