El proceso de tunning de un RAG (Retrieval Augmented Generation) es una etapa importante (o la más importante, dependiendo de a quien le preguntes) en la creación de un RAG; obtener respuestas correctas es lo que nuestros usuarios siempre esperarán.
En multiples ocasiones te encontrarás con que las imágenes poseen información relevante que puede enriquecer tu base de datos de vectores más allá de la información escrita que posees; y así proveer mejores respuestas a tus usuarios. Ello supondrá un gran desafio si pretendes describir la información de las imágenes a texto, por lo que apoyarte de herramientas de inteligencia artificial hará que de este trabajo algo simple que hoy te quiero mostrar.
Muchos de los LLMs que hoy en día existen en el mercado, gratuito o de pago, tienen la capacidad de analizar imágenes, y a modo de ilustración, hoy utilizaremos la API de OpenAI para hacer este trabajo por nosotros.
Fase de preparación
Crea un nuevo proyecto en tu IDE de preferencia, e iniciemos creando nuestro archivo .env
con el siguiente contenido, reemplazando <TU_API_KEY_VA_AQUÍ>
por el valor de tu API key real:
OPENAI_API_KEY=<TU_API_KEY_VA_AQUÍ>
PlaintextAhora crearemos el archivo requirements.txt
, el cual contendrá un listado de las librerías necesarias para nuestro desarrollo. Al momento de la escritura de este artículo estas son las versiones más recientes; sin embargo, siéntete en libertad de usar las últimas disponibles al momento de tu práctica.
openai==1.77.0
python-dotenv==1.1.0
PlaintextCon esto listo, procedemos a la instalación de paquetes ejecutando el siguiente comando desde la terminal o consola:
pip install -r requirements.txt
BashAhora ya contamos con los pre-requisitos para la ejecución de nuestra lógica, así que procederemos a la fase de desarrollo.
Fase de desarrollo
Inicialmente crearemos una clase de Python para la interacción con la API de OpenAI, en otras palabras un helper. Este helper nos permitirá conectar con la API, autenticarnos, enviar peticiones y obtener respuestas.
El helper se llamará ChatGPT
(solo por conveniencia, podría adoptar cualquier otro nombre), y heredará de la clase OpenAI
de la librería openai
. Le agregaremos un nuevo método llamado describe_images
, el cual recibirá tres parámetros:
model
: de tipoChatModel
, el cual admite solo nombres de aquellos modelos que estan disponibles en OpenAI. Referencia: OpenAI model listprompt_message
: De tipostring
, y contendrá el prompt con las instrucciones que enviaremos al modelo.image_urls
: de tipolist
destr
, y recibirá una lista de URLs del mismo producto.
Con este método controlaremos los reintentos por cuota y excepciones; así mismo, desacoplaremos la respuesta que nos da el modelo.
import time
from typing import List, Union
from openai import OpenAI, RateLimitError
from openai.types.shared.chat_model import ChatModel
from openai.types.chat.chat_completion import ChatCompletion
class ChatGPT(OpenAI):
def describe_images(
self,
model: ChatModel,
prompt_message: str,
image_urls: List[str]
) -> str:
max_retries: int = 5
retry_count: int = 0
while retry_count < max_retries:
try:
response: ChatCompletion = self.chat.completions.create(
model=model,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": prompt_message
},
*[
{
"type": "image_url",
"image_url": {
"url": image_url,
"detail": "high"
}
}
for image_url in image_urls
]
],
}
],
max_tokens=300,
)
return response.choices[0].message.content
except RateLimitError as e:
retry_count += 1
if retry_count >= max_retries:
raise Exception(f"Rate limit exceeded after {max_retries} attempts: {str(e)}")
retry_time = 60
if "try again in" in str(e).lower():
try:
wait_time_str = str(e).split("try again in")[1].split("s.")[0].strip()
retry_time = float(wait_time_str) + 30
except (IndexError, ValueError):
pass
print(f"Rate limit hit. Retrying in {retry_time} seconds... (Attempt {retry_count}/{max_retries})")
time.sleep(retry_time)
except Exception as e:
raise Exception(f"Error processing image description: {str(e)}")
PythonFase de explotación
Ahora llegamos a la etapa de explotación o utilización de nuestro helper, y a modo de prueba he colocado un prompt bastante simple, y enviando unas 6 imágenes del mismo producto.
from dotenv import load_dotenv
from helpers.OpenAIHelper import ChatGPT
if __name__ == "__main__":
load_dotenv()
gpt_client: ChatGPT = ChatGPT()
response = gpt_client.describe_images(
model="gpt-4o-mini",
prompt_message="Describe this product",
image_urls=[
"https://assets.adidas.com/images/h_840,f_auto,q_auto,fl_lossy,c_fill,g_auto/83fedaf912e54f7692085a667bfe1262_9366/Tenis_Gazelle_Indoor_Verde_JI3526_01_standard.jpg",
"https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/278a148fe1c24177884fd0959e0219f1_9366/Tenis_Gazelle_Indoor_Verde_JI3526_02_standard_hover_hover.jpg",
"https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/a18da341a57643c293fd1f599dd4cf59_9366/Tenis_Gazelle_Indoor_Verde_JI3526_03_standard.jpg",
"https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/00ec464270fc4ac7b2273921533e6b2d_9366/Tenis_Gazelle_Indoor_Verde_JI3526_04_standard.jpg",
"https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/3a9702a225a74cbb926f7f3b1fae8928_9366/Tenis_Gazelle_Indoor_Verde_JI3526_41_detail.jpg",
"https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/29d968eb62b84a5b9064217ae59b850a_9366/Tenis_Gazelle_Indoor_Verde_JI3526_42_detail.jpg"
]
)
print(response)
PythonComo resultado, he obtenido el siguiente texto que describe de manera detallada nuestro producto:
The product is a pair of Adidas Gazelle sneakers. Here are some key features:
1. **Design**: The sneakers feature a classic low-top silhouette with a retro aesthetic. The upper is primarily made of suede in a muted green color, complemented by white leather three stripes running along the sides.
2. **Color Scheme**: The shoes combine green and brown tones, with white accents for a fresh, stylish look. The “Gazelle” branding is embroidered in a contrasting gold on the side.
3. **Material**: The upper is made of soft suede, which provides a premium feel and stylish appearance. There’s also a leather heel tab for added durability.
4. **Sole**: The rubber outsole has a classic gum finish, providing traction and a retro vibe, enhancing the overall comfort and grip.
5. **Lacing System**: The shoe features a traditional lace-up closure that allows for a customizable fit.
6. **Versatility**: This model can be styled easily with various outfits, making it a versatile choice for casual wear.
Overall, the Adidas Gazelle combines classic styles with modern comfort, making it a timeless sneaker option.
PlaintextDe esta manera, rápida y fácil hemos podido obtener información adicional sobre nuestro producto, información que podremos almacenar en nuestra base de datos, para posteriormente pasar por un modelo de embedding y almacenarlo en nuestra base de datos vectorial que conecta con nuestros agentes.