library(ellmer)
library(tidyverse)
library(jsonlite)
set.seed(2026)
# Verificar que la API key está configurada
stopifnot(Sys.getenv("OPENROUTER_API_KEY") != "")
# Cargar los textos del Día 3
textos <- read_csv("datos/textos_politicos.csv",
show_col_types = FALSE)
# Modelo por defecto (cambiar si OpenRouter está saturado)
MODELO <- "nvidia/nemotron-3-super-120b-a12b:free"Tarea 7: Primeros pasos con ellmer
IA para Científicos Sociales - UCU
1 Instrucciones
Esta tarea aplica los conceptos del Laboratorio 7. Responder cada pregunta escribiendo código R en los bloques indicados. Cuando se pida una respuesta escrita, usar texto normal debajo del bloque de código.
Para trabajar en esta tarea:
- Descarguen este archivo
.qmdy el datasettextos_politicos.csv(enclases/dia-04/datos/) de la página del curso, o clonen el repositorio completo congit clone https://github.com/danilofreire/introduccion-ia-ucu.git - Necesitan Quarto instalado junto con RStudio. Las versiones recientes de RStudio (>= 2022.07) ya incluyen Quarto
- Necesitan una API key de OpenRouter configurada como
OPENROUTER_API_KEYen.Renviron. Alternativa: instalar Ollama y usarchat_ollama()en lugar dechat_openrouter() - Abrir este archivo
.qmden RStudio y ejecutar los bloques de código con Ctrl+Enter (Cmd+Enter en Mac)
1.1 Configuración
2 Configuración y chat básico
2.1 Pregunta 1: El system prompt como formato
Crear un chat con chat_openrouter() usando MODELO y un system prompt que obligue al modelo a responder siempre con exactamente tres viñetas, en lenguaje sencillo y sin jerga técnica. Pedirle que explique qué es el sesgo de selección. ¿Respetó el formato? ¿El lenguaje quedó realmente accesible?
Pista: el system prompt es un argumento del constructor chat_openrouter(), no algo que se envía después. La interfaz devuelve un objeto R6 con un método para enviar mensajes.
# Escriban su código aquíRespuesta:
2.2 Pregunta 2: Historial de conversación
Usando el mismo objeto chat de la Pregunta 1, hacer una segunda pregunta que dependa de la primera (por ejemplo, “¿Y cómo se puede evitar en una encuesta?”). Verificar que la respuesta hace referencia al concepto introducido antes. Explicar por qué chat$chat() “recuerda” la conversación.
# Escriban su código aquíRespuesta:
2.3 Pregunta 3: El system prompt como barrera
Los asistentes que se usan en investigación suelen estar restringidos a su dominio. Crear un chat cuyo system prompt lo limite a responder solo preguntas sobre política latinoamericana y a rechazar amablemente cualquier otro tema. Probarlo con dos preguntas: una sobre política latinoamericana y otra claramente fuera de tema (por ejemplo, una receta de cocina). ¿El modelo respetó la barrera? Propongan además una pregunta “fronteriza” (mitad dentro, mitad fuera del dominio) y comenten qué hizo el modelo con ella.
# Escriban su código aquíRespuesta:
3 Clasificación y extracción
3.1 Pregunta 4: Clasificar una dimensión sin etiquetas
El dataset trae la columna tema (que ya clasificamos en el laboratorio), pero no dice nada del tono de cada texto. Escribir una función clasificar_tono(texto) que devuelva exactamente una de tres etiquetas: “positivo”, “negativo” o “neutral”. Probarla con los textos 2, 21 y 41 del dataset. Como acá no hay etiqueta de referencia, el control de calidad son ustedes: lean cada texto y digan si están de acuerdo con el modelo.
Pista: dentro de la función conviene crear un chat nuevo cada vez para evitar que el historial sesgue la siguiente clasificación.
# Escriban su código aquíRespuesta:
3.2 Pregunta 5: Salida estructurada
Una clasificación devuelve una sola etiqueta; muchas veces queremos varios campos a la vez. Usar chat_structured() con un type_object que tenga tres campos: tema (una de las cinco categorías), menciona_gobierno (TRUE/FALSE) y urgencia (baja / media / alta). Aplicarlo a tres textos del dataset y mostrar los resultados en una tabla.
Pista: los campos de un type_object() pueden ser de distinto tipo: type_enum(), type_boolean(), etc. chat$chat_structured(texto, type = ...) devuelve una lista con esos campos; unnest_wider() la convierte en columnas.
# Escriban su código aquíRespuesta:
4 Robustez y sensibilidad
4.1 Pregunta 6: ¿El modelo da siempre lo mismo?
Los LLMs no son deterministas: la misma pregunta puede dar respuestas distintas. Tomar UN texto del dataset cuyo tono sea ambiguo (que mezcle una buena y una mala noticia) y clasificarlo cinco veces (creando un chat nuevo cada vez). Contar cuántas veces salió cada etiqueta. ¿Qué implica esto para la reproducibilidad de una investigación?
Pista: map_chr(1:5, \(i) clasificar_tono(texto)) repite la clasificación; table() cuenta las respuestas.
# Escriban su código aquíRespuesta:
4.2 Pregunta 7: Sensibilidad al prompt
El resultado depende de cómo se escribe el prompt. Escribir dos versiones del system prompt para clasificar por tema: una breve (solo las cinco categorías de textos_politicos.csv) y una detallada (con una regla extra, por ejemplo “si el texto toca dos temas, elegí el más concreto”). Aplicar ambas a los mismos cinco textos y mostrar en qué textos difieren.
Pista: definir dos funciones que solo cambien en el system_prompt, y filter() las filas donde las dos clasificaciones no coinciden.
# Escriban su código aquíRespuesta:
4.3 Pregunta 8: Sensibilidad a los ejemplos
El few-shot es sensible a QUÉ ejemplos se eligen. Escribir dos clasificadores few-shot: uno con ejemplos típicos (uno por categoría, muy claros) y otro con ejemplos ambiguos (más sutiles o con vocabulario distinto). Aplicar ambos a una muestra estratificada de diez textos y comparar la accuracy. ¿Importa la elección de los ejemplos?
Pista: los ejemplos van dentro del system prompt. Una muestra estratificada (group_by(tema) |> slice_sample(n = 2)) cubre las cinco categorías.
# Escriban su código aquíRespuesta:
5 Más allá de la clasificación
5.1 Pregunta 9: Extracción de entidades
Usar chat_structured() para extraer entidades nombradas de un texto político. Definir un type_object con tres type_array: personas, organizaciones y lugares. Aplicarlo a un texto (puede ser uno que escriban ustedes) y mostrar las entidades.
Pista: type_array(type_string(), "...") representa una lista de strings de largo variable (cero, una o varias entidades).
# Escriban su código aquíRespuesta:
5.2 Pregunta 10: Razonamiento paso a paso
Tomar un texto ambiguo y clasificarlo de dos formas: (a) directa (solo la categoría) y (b) con cadena de pensamiento, pidiéndole al modelo que explique su razonamiento en una o dos oraciones ANTES de dar la categoría. ¿Cambia la respuesta? ¿Cuándo conviene pedir el razonamiento paso a paso?
Pista: la diferencia está solo en el system prompt: en (b) pídanle que razone primero y termine con una línea del tipo Categoría: <categoria>.
# Escriban su código aquíRespuesta:
6 Reflexión
6.1 Pregunta 11: Costo de procesar el dataset completo
textos_politicos.csv tiene 60 textos. Si quisieran clasificar los 60 con un modelo pago (por ejemplo, Claude 3.5 Haiku, que cuesta aproximadamente USD 0,80 por millón de tokens de entrada y USD 4,00 por millón de tokens de salida), estimar el costo total. Suponer 200 tokens de prompt + 5 tokens de respuesta por texto.
Pista: la fórmula es (tokens_entrada * precio_entrada + tokens_salida * precio_salida) / 1.000.000.
# Escriban su código aquíRespuesta:
6.2 Pregunta 12: Recomendación para una investigación real
Imaginen que un colega tiene 5.000 tweets de campañas electorales latinoamericanas y necesita clasificarlos por tema en una semana, con presupuesto ajustado. ¿Le recomendarían LDA, LLM zero-shot, LLM few-shot, o un híbrido (por ejemplo, usar el LLM para anotar 500 tweets y entrenar un clasificador clásico con esas etiquetas)? Justifiquen considerando precisión, costo y reproducibilidad.
Respuesta: