Sebastian Gomez
Construyendo un Agente de Razonamiento con Gemini y Vertex AI 🧠💡
Muchos de los modelos de lenguaje de razonamiento más potentes hoy en día son propietarios y de código cerrado. Esto limita el acceso para los desarrolladores 👩💻 y frena la investigación 🔬 en estas capacidades cruciales. ¡Pero no te preocupes! En este artículo, construiremos nuestro propio agente de razonamiento usando Gemini 2.5 Flash de Google, ajustándolo (haciendo fine-tuning) con un conjunto de datos disponible públicamente. Nuestro objetivo es replicar el razonamiento de "cadena de pensamiento" (chain-of-thought) que a menudo se encuentra en los modelos cerrados. Este método consiste en que el modelo explica su proceso de pensamiento paso a paso antes de proporcionar la respuesta final 🤔➡️💡, haciendo esta poderosa técnica mucho más accesible para todos.
¿Por qué es importante el razonamiento explícito? 🤔
Que un modelo "piense en voz alta" no es solo un truco interesante. Tiene beneficios concretos:
- Resultados más precisos: Al seguir una lógica paso a paso, es menos probable que el modelo cometa errores o salte a conclusiones incorrectas ✅.
- Transparencia: Nos permite entender cómo llegó el modelo a una respuesta, lo cual es vital para la confianza y la depuración 🔍. Podemos ver su "proceso de pensamiento".
- Mejor generalización: Un modelo que aprende a razonar puede aplicar esa habilidad a problemas nuevos y desconocidos de manera más efectiva 💪.
Lograremos esto usando Vertex AI de Google Cloud Platform (GCP) ☁️, demostrando el flujo de trabajo completo desde la preparación de los datos hasta el despliegue del modelo ajustado.
Creando un Bucket de Almacenamiento 🪣
Antes de poder entrenar nuestro modelo, necesitamos un lugar seguro y accesible para guardar nuestros datos de entrenamiento. Usaremos Google Cloud Storage (GCS) para esto. Piensa en GCS como un disco duro gigante y seguro en la nube ☁️💾 donde puedes almacenar archivos de cualquier tipo.
Un "bucket" en GCS es esencialmente un contenedor principal para tus datos. Es similar a una carpeta en tu computadora, pero a una escala mucho mayor y optimizado para la nube. ¿Por qué lo necesitamos? Porque Vertex AI (la plataforma donde entrenaremos el modelo) necesita poder acceder a los datos de entrenamiento desde una ubicación centralizada y eficiente. Un bucket de GCS proporciona una forma confiable, escalable y perfectamente integrada con otros servicios de GCP para poner nuestro conjunto de datos a disposición del proceso de entrenamiento de Vertex AI.
Pasos para crear un bucket:
- ✔️ Abre la Consola de Google Cloud: Asegúrate de haber iniciado sesión con la cuenta de Google que deseas usar para este proyecto y de haber seleccionado el proyecto correcto en el menú desplegable superior.
- 🖱️ Navega a Cloud Storage: En el menú de navegación de la izquierda (a veces llamado menú "hamburguesa" ☰), busca Cloud Storage y haz clic en Buckets. Es posible que necesites desplazarte hacia abajo o usar la barra de búsqueda.
- ➕ Haz clic en + CREAR: Se abrirá un formulario para configurar tu nuevo bucket.
- 📝 Elige un nombre único globalmente: ¡Este nombre debe ser único en todo Google Cloud, no solo en tu proyecto! Algo como
bucket-dataset-razonamiento-mi-proyectopodría funcionar (reemplazami-proyectocon algo único para ti). - ⚙️ Deja las otras configuraciones por defecto: Para este tutorial, las opciones predeterminadas suelen ser suficientes (ubicación, clase de almacenamiento, etc.).
- ✅ Haz clic en "CREAR". ¡Y listo! Ya tienes tu bucket.
Configuración de IAM (Identidad y Gestión de Acceso) 🔑
Para permitir que nuestro código (que correrá, por ejemplo, en un cuaderno de Colab o en una instancia de Vertex AI) interactúe de forma segura con Vertex AI y Cloud Storage, necesitamos los permisos adecuados.
💡 Recomendación de seguridad (actualizada): En lugar de descargar una clave JSON de cuenta de servicio de larga duración —un patrón hoy desaconsejado por Google—, usaremos Application Default Credentials (ADC). Es más seguro, no deja secretos en tu disco ni en tu notebook, y funciona igual de bien en Colab, Vertex AI Workbench o tu máquina local. Si despliegas en infraestructura de GCP, prefiere Workload Identity.
Concepto: la cuenta de servicio. Una cuenta de servicio es como una identidad especial de Google que usan las aplicaciones o servicios 🤖, en lugar de usuarios individuales 👤. Esto es crucial por seguridad: en lugar de usar tus credenciales personales en el código (¡nunca hagas eso!), le damos a nuestro código su propia identidad con permisos específicos y limitados.
Pasos para conceder los permisos necesarios:
- ▶️ Navega a IAM: Abre la Consola de Google Cloud, selecciona tu proyecto y ve a IAM y Administración > IAM.
- 📜 Otorga los roles a la identidad que usarás (tu usuario para ADC, o una cuenta de servicio dedicada como
finetune-gemini-agentsi trabajas en infraestructura):
- Usuario de Vertex AI (`roles/aiplatform.user`): Para crear y gestionar trabajos de entrenamiento e inferencia en Vertex AI. - Administrador de objetos de Storage (`roles/storage.objectAdmin`): Para leer el dataset del bucket y escribir resultados si fuera necesario.
- ✅ Guarda los cambios.
Con ADC, no necesitas descargar ni manejar ningún archivo de clave: te autenticas una sola vez con gcloud auth application-default login (lo veremos en el siguiente paso) y el SDK toma tus credenciales automáticamente. 🔒
Instalar e Importar Librerías y Configuración ⚙️🐍
Antes de empezar a trabajar con el dataset y Vertex AI, necesitamos instalar e importar las librerías de Python necesarias. ¡Manos a la obra!
🔁 Nota de migración: Este tutorial ahora usa el Google Gen AI SDK (google-genai), la librería oficial y vigente para los modelos Gemini. Reemplaza al antiguo vertexai.generative_models / vertexai.tuning, que quedó obsoleto.
# Instalar las librerías necesarias
!pip install datasets --quiet
!pip install google-cloud-storage --quiet
!pip install --upgrade google-genai --quiet
# Autenticar con Application Default Credentials (ADC).
# Esto puede abrir una ventana de login en Colab/notebooks.
!gcloud auth application-default login# Importar las librerías
import datasets
import json
import time # Para pausas y monitoreo
import os
from copy import deepcopy # Para copiar estructuras de datos complejas
from google.cloud import storage
from google import genai
from google.genai import typesAhora definiremos algunas variables constantes que usaremos a lo largo del cuaderno. Con ADC ya no necesitas subir ni referenciar ningún archivo de clave: basta con ajustar el ID de tu proyecto, la región y el nombre de tu bucket.
# --- CONFIGURACIÓN ---
# ¡¡¡CAMBIA ESTOS VALORES POR LOS TUYOS!!!
PROJECT_ID = "tu-proyecto-gcp-id" # ⚠️ Reemplaza con el ID de tu proyecto GCP
LOCATION = "us-central1" # Región compatible con Vertex AI y Gemini
BUCKET_NAME = "reasoning_dataset_bucket" # ⚠️ Reemplaza con el nombre de TU bucket
# Modelo base de Gemini a ajustar.
# ⚠️ Verifica en la documentación de Vertex AI cuál es el modelo Flash afinable vigente.
MODEL_BASE = "gemini-2.5-flash" # Usamos la versión Flash por eficiencia y costo
# Nombre que le daremos a nuestro modelo ajustado en Vertex AI
FINETUNED_MODEL_NAME = "gemini_flash_razonador_ajustado_v1"
# --- FIN CONFIGURACIÓN ---
# Cliente del Gen AI SDK apuntando a Vertex AI.
# Toma automáticamente las credenciales de ADC; no se necesita ninguna clave JSON.
client = genai.Client(vertexai=True, project=PROJECT_ID, location=LOCATION)
print("✅ Configuración lista y cliente de Gen AI inicializado.")Descargar el Conjunto de Datos y Convertir el Formato 📊➡️📄
Ahora descargaremos el conjunto de datos de razonamiento y lo transformaremos al formato específico JSON Lines (JSONL) requerido por Vertex AI para el fine-tuning. JSONL es simplemente un archivo de texto donde cada línea es un objeto JSON válido. Este formato es eficiente para procesar grandes cantidades de datos línea por línea.
Aquí usaremos un dataset de Hugging Face 🤗 llamado `KingNish/reasoning-base-20k`.
# Descargar el dataset desde Hugging Face
dataset_hf = datasets.load_dataset("KingNish/reasoning-base-20k")
print("Dataset descargado:")
print(dataset_hf)Este dataset está diseñado específicamente para entrenar modelos a razonar paso a paso sobre problemas, de forma similar a como lo haría un humano 🧑🔬. Contiene una diversidad de problemas (ciencia, código, matemáticas, lógica general) junto con una detallada cadena de pensamiento (CoT) que lleva a la respuesta correcta. Crucialmente para nuestro objetivo, el dataset ya separa explícitamente la etapa de "pensamiento" (reasoning) de la "respuesta" final (assistant). ¡Perfecto para lo que queremos!
Para ajustar Gemini en Vertex AI, necesitamos que nuestros datos sigan una estructura JSON específica por cada ejemplo de entrenamiento. La estructura esperada es la siguiente (pseudo-JSON, los comentarios // son solo explicativos):
// Estructura de ejemplo para UNA línea en el archivo JSONL
{
"systemInstruction": { // Opcional: instrucción general para el modelo
"role": "system",
"parts": [{ "text": "Eres un modelo experto en razonamiento..." }]
},
"contents": [ // Lista de turnos de conversación
{
"role": "user", // Lo que el usuario pregunta
"parts": [{ "text": "{pregunta_del_dataset}" }]
},
{
"role": "model", // Lo que el modelo debe responder
"parts": [{ "text": "<think>{razonamiento}</think>\n<answer>{respuesta}</answer>" }]
}
]
}⚠️ Verifica el esquema: El formato exacto del JSONL para SFT (nombres de campos como contents / messages y systemInstruction) depende de la versión de la API de tuning de Vertex AI. Confirma el esquema vigente en la documentación antes de lanzar el trabajo.
Vamos a definir una instrucción de sistema estándar y una plantilla para formatear nuestros datos:
# Instrucción de sistema que guiará a nuestro modelo ajustado
instruction = """
Eres un modelo de lenguaje excepcional especializado en razonamiento lógico.
Siempre debes pensar paso a paso antes de proporcionar una respuesta final.
Encierra tu proceso de pensamiento dentro de etiquetas <think> </think>.
Después de pensar, resume tu razonamiento y presenta la respuesta final clara y
concisa dentro de etiquetas <answer> </answer>.
"""
# Plantilla base para cada ejemplo JSONL
template_format = {
"contents": [
{"role": "user", "parts": [{"text": "{question}"}]}, # Marcador para la pregunta
{"role": "model", "parts": [{"text": "{answer}"}]}, # Respuesta formateada
]
}
# Función para preprocesar cada ejemplo del dataset de Hugging Face
def preproc_train_data(example):
now_input = example["user"]
now_reasoning = example["reasoning"]
now_assistant_answer = example["assistant"]
# Construir la salida formateada para el modelo (pensamiento + respuesta)
now_output = (
f"<think>\n{now_reasoning}\n</think>\n"
f"<answer>\n{now_assistant_answer}\n</answer>"
)
template_now = deepcopy(template_format)
template_now["contents"][0]["parts"][0]["text"] = now_input
template_now["contents"][1]["parts"][0]["text"] = now_output
# Cada línea del JSONL debe ser una cadena JSON
return {"data_jsonl": json.dumps(template_now)}
# Aplicar el preprocesamiento a todo el dataset ('map' usa múltiples procesos si hay)
dataset_processed = dataset_hf.map(preproc_train_data, num_proc=os.cpu_count())
# Nos enfocamos en el split 'train' para este ejemplo
training_data_jsonl_strings = dataset_processed["train"]["data_jsonl"]
print(f"📄 Preprocesados {len(training_data_jsonl_strings)} ejemplos de entrenamiento.")
print("Ejemplo de una línea JSONL formateada:")
print(training_data_jsonl_strings[0])Subir el Dataset al Bucket 📤☁️
Ahora que hemos preparado nuestro dataset en el formato JSONL correcto, necesitamos subirlo al bucket de Google Cloud Storage que creamos antes. Esto hace que los datos sean accesibles para Vertex AI durante el fine-tuning.
Usaremos la librería google-cloud-storage. Como ya estamos autenticados con ADC, el cliente de GCS toma las credenciales automáticamente —sin archivos de clave—:
def upload_jsonl_to_gcs(bucket_name, destination_blob_name, json_string_list):
"""Sube una lista de cadenas JSON a GCS como un archivo JSON Lines (.jsonl).
Args:
bucket_name (str): Nombre del bucket de GCS.
destination_blob_name (str): Nombre del archivo destino (debe terminar en .jsonl).
json_string_list (list): Lista donde cada elemento es una cadena JSON válida.
"""
try:
# Cliente de GCS usando ADC (sin clave JSON)
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(destination_blob_name)
# Unir las líneas JSON con saltos de línea para formar el contenido JSONL
jsonl_content = "\n".join(json_string_list)
# 'application/jsonl' no es un MIME registrado; usamos uno convencional.
blob.upload_from_string(jsonl_content, content_type="application/x-ndjson")
gcs_uri = f"gs://{bucket_name}/{destination_blob_name}"
print(f"✅ Archivo '{destination_blob_name}' subido a {gcs_uri}")
return gcs_uri
except Exception as e:
print(f"❌ Error al subir el archivo JSON Lines: {e}")
return None
TRAINING_DATA_GCS_FILENAME = "training_dataset_reasoning_gemini.jsonl"
training_data_gcs_uri = upload_jsonl_to_gcs(
BUCKET_NAME,
TRAINING_DATA_GCS_FILENAME,
training_data_jsonl_strings,
)
if training_data_gcs_uri:
print(f"URI del dataset de entrenamiento en GCS: {training_data_gcs_uri}")
else:
print("⚠️ Hubo un problema al subir el dataset. Revisa los mensajes de error.")Puedes verificar la carga yendo a la sección de Cloud Storage en la Consola de GCP y navegando a tu bucket. ¡Deberías ver tu archivo .jsonl allí!
Ajuste Fino (Fine-tuning) de Gemini ✨🚀
¡Ahora viene el núcleo del proceso! Ajustar el modelo base de Gemini (nuestro MODEL_BASE) con nuestro dataset preparado. Con el Gen AI SDK, lanzamos un trabajo de ajuste supervisado (SFT) a través de client.tunings.
⚠️ Verifica la API de tuning: Los nombres exactos de método (client.tunings.tune), parámetros (epoch_count, adapter_size, learning_rate_multiplier) y la forma de pasar el dataset evolucionan. Confirma la firma vigente en la documentación de tuning de Vertex AI antes de ejecutar.
print(f"🚀 Iniciando el trabajo de ajuste fino para el modelo base: {MODEL_BASE}")
print(f"Usando dataset de entrenamiento en: {training_data_gcs_uri}")
if not training_data_gcs_uri:
raise ValueError("La URI del dataset de GCS no está definida. No se puede iniciar el entrenamiento.")
tuning_job = client.tunings.tune(
base_model=MODEL_BASE,
training_dataset=types.TuningDataset(gcs_uri=training_data_gcs_uri),
config=types.CreateTuningJobConfig(
tuned_model_display_name=FINETUNED_MODEL_NAME,
epoch_count=2, # 2-4 es un buen punto de partida
adapter_size="ADAPTER_SIZE_FOUR", # LoRA: más grande = más capacidad (y costo)
learning_rate_multiplier=1.0,
# validation_dataset=types.TuningValidationDataset(gcs_uri="gs://.../validation.jsonl"),
),
)
print("✅ Trabajo de ajuste enviado exitosamente a Vertex AI.")
print(f"Nombre del trabajo: {tuning_job.name}")
print("Puedes monitorear el progreso en la sección 'Tuning' de Vertex AI en la consola de GCP.")Monitoreo del Trabajo:
El ajuste fino puede llevar tiempo ⏳ (desde menos de una hora hasta varias horas, dependiendo del tamaño del dataset, el modelo base y parámetros como epoch_count y adapter_size). El siguiente código revisa el estado del trabajo cada 60 segundos hasta que termina:
import time
print("🕒 Esperando a que el trabajo de ajuste fino finalice... (Esto puede tardar)")
# Estados que indican que el trabajo sigue en curso
running_states = {
"JOB_STATE_PENDING",
"JOB_STATE_RUNNING",
"JOB_STATE_QUEUED",
}
while tuning_job.state in running_states:
time.sleep(60)
tuning_job = client.tunings.get(name=tuning_job.name) # Refrescar el estado
print(f"Estado actual del trabajo ({time.strftime('%H:%M:%S')}): {tuning_job.state}")
print("\n🎉 ¡El trabajo de ajuste ha finalizado!")
if tuning_job.state == "JOB_STATE_SUCCEEDED":
print("✅ El trabajo se completó exitosamente.")
# El modelo ajustado queda listo para inferencia mediante su endpoint
tuned_endpoint = tuning_job.tuned_model.endpoint
print(f"Endpoint del modelo ajustado: {tuned_endpoint}")
else:
print(f"❌ El trabajo falló o fue cancelado. Estado final: {tuning_job.state}")
print(f"Error (si está disponible): {getattr(tuning_job, 'error', None)}")💡 Buena noticia: Con el Gen AI SDK, un trabajo de tuning exitoso registra el modelo ajustado y expone un endpoint listo para inferencia (tuning_job.tuned_model.endpoint), sin el paso manual de despliegue que requería el flujo antiguo. Confirma este comportamiento para tu modelo y región en la documentación.
Inferencia (Usando el Modelo Ajustado) 🤖💡
¡Felicidades! Si llegaste hasta aquí, has ajustado tu propio modelo Gemini. Ahora podemos enviarle prompts (preguntas o instrucciones) y recibir respuestas que demuestren sus nuevas capacidades de razonamiento, siguiendo el formato <think>...</think><answer>...</answer> que le enseñamos.
Apuntaremos al endpoint del modelo ajustado y reforzaremos el comportamiento con la misma instrucción de sistema. También configuramos algunos parámetros de generación (usamos una temperature baja para favorecer el razonamiento determinista):
# Endpoint del modelo ajustado. Si estás en una sesión nueva, puedes obtenerlo así:
# tuning_job = client.tunings.get(name="projects/.../tuningJobs/...")
# tuned_endpoint = tuning_job.tuned_model.endpoint
TUNED_MODEL = tuned_endpoint # del paso anterior
system_instruction_inference = """
Eres un modelo de lenguaje excepcional especializado en razonamiento lógico.
Siempre debes pensar paso a paso antes de proporcionar una respuesta final.
Encierra tu proceso de pensamiento dentro de etiquetas <think> </think>.
Después de pensar, resume tu razonamiento y presenta la respuesta final clara y
concisa dentro de etiquetas <answer> </answer>.
"""
def generate_with_tuned_model(prompt_text):
print(f"\n🤔 Enviando prompt al modelo ajustado: '{prompt_text}'")
try:
response = client.models.generate_content(
model=TUNED_MODEL,
contents=prompt_text,
config=types.GenerateContentConfig(
system_instruction=system_instruction_inference,
temperature=0.3, # Bajo = más determinista, ideal para razonar
top_p=0.95,
max_output_tokens=2048,
),
)
print("✅ Respuesta recibida.")
return response
except Exception as e:
print(f"❌ Error durante la inferencia: {e}")
return None
# --- ¡Probemos el modelo! ---
question = "¿Qué pasa si no tengo suficiente dinero para pagar GCP y no he vinculado mi tarjeta de crédito?"
full_response = generate_with_tuned_model(question)
# Procesar la respuesta para extraer las partes de pensamiento y respuesta
if full_response and full_response.text:
full_text = full_response.text
# Extraer el bloque <think>...</think>
thinking_part = ""
if "<think>" in full_text and "</think>" in full_text:
thinking_part = full_text.split("<think>", 1)[1].split("</think>", 1)[0].strip()
# Extraer el bloque <answer>...</answer> (con fallbacks)
if "<answer>" in full_text and "</answer>" in full_text:
answer_part = full_text.split("<answer>", 1)[1].split("</answer>", 1)[0].strip()
elif "</think>" in full_text:
answer_part = full_text.split("</think>", 1)[1].strip()
else:
answer_part = full_text.strip()
print("\n--- Proceso de Pensamiento del Modelo --- 🤔")
print(thinking_part or "(No se encontró la etiqueta <think>)")
print("\n--- Respuesta Final del Modelo --- 💡")
print(answer_part or "(No se encontró la etiqueta <answer>)")
else:
print("No se recibió una respuesta válida del modelo.")Conclusión ✅🎉
En este artículo, hemos recorrido el camino completo para ajustar con éxito un modelo Gemini Flash de Google y convertirlo en un agente de razonamiento capaz de demostrar su proceso de "cadena de pensamiento".
Comenzamos con un problema común: la opacidad de muchos modelos potentes. Nuestra solución fue tomar un modelo accesible (Gemini Flash), un dataset público diseñado para razonar (KingNish/reasoning-base-20k), y usar las herramientas de Google Cloud Platform —específicamente Vertex AI y Google Cloud Storage— para:
- Preparar y formatear los datos (JSONL).
- Almacenarlos de forma segura y accesible (bucket de GCS).
- Configurar los permisos necesarios de forma segura (IAM + ADC).
- Lanzar y monitorear un trabajo de ajuste fino supervisado (Vertex AI SFT).
- Realizar inferencia para observar el razonamiento explícito (el famoso
<think>...</think><answer>...</answer>).
Lo que podría parecer una tarea desalentadora —construir un modelo que no solo responde, sino que explica cómo llegó a la respuesta— es sorprendentemente accesible gracias a plataformas modernas como Google Cloud 🛠️. El proceso, aunque involucra varios pasos, es bastante directo cuando se desglosa.
El Gen AI SDK, en particular, simplifica enormemente la gestión de infraestructura y el entrenamiento de modelos grandes, permitiéndote concentrarte en lo central: enseñarle a tu modelo a pensar y razonar mejor para tus necesidades específicas. ¡Ahora tienes el poder de crear modelos más transparentes y confiables!
Eso es todo, espero que este post te sea de utilidad. Si tienes dudas o quieres compartir lo que construiste, déjamelo en los comentarios. ¡Buena suerte! 🚀
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.