IA para Científicos Sociales

Sesión 4.3: Laboratorio 7 - Primeros pasos con ellmer

Danilo Freire

Department of Data and Decision Sciences
Emory University

Laboratorio 7: Primeros pasos con ellmer

Objetivos del laboratorio

Lo que vamos a hacer

  1. Configurar ellmer con OpenRouter
  2. Crear el primer chat y experimentar con system prompts
  3. Clasificar textos políticos con LLMs (zero-shot y few-shot)
  4. Comparar la accuracy del LLM con LDA del Día 3

Lo que vamos a aprender

  • El paquete ellmer y su filosofía: misma interfaz para todos los proveedores
  • Cómo un system prompt cambia el comportamiento del modelo
  • Cuándo el LLM gana y cuándo pierde frente a métodos clásicos

Estructura del laboratorio

Parte Contenido Duración
1 Configuración: API key, primer chat, system prompt ~15 min
2 Clasificación de textos políticos ~25 min
3 Comparación con LDA del Día 3 ~15 min
Apéndices: ejercicios, few-shot avanzado, NER extra


Reusamos el dataset textos_politicos.csv del Día 3 para que la comparación sea directa

Parte 1: Configuración 🤖

Instalar ellmer

  • ellmer es un paquete oficial de Posit
  • Usa la misma interfaz para OpenAI, Anthropic, OpenRouter, Gemini y otros proveedores
  • Cambiar de proveedor es cambiar una línea, sin reescribir el código de procesamiento
R> # Instalar (solo la primera vez)
R> install.packages(c("ellmer", "jsonlite"))
  • En este laboratorio usamos OpenRouter, que tiene modelos gratuitos y no requiere instalar nada
R> library(tidyverse)
R> library(ellmer)
R> library(jsonlite)
R> 
R> packageVersion("ellmer")
[1] '0.4.1'

Obtener una API key de OpenRouter

  1. Crear cuenta en openrouter.ai (basta con email)
  2. Ir a openrouter.ai/keys y generar una nueva key
  3. Ponerle un nombre (ej. “Laboratorio 7”) y $0.00 de límite de gasto
  4. Copiar y guardar la key (empieza con sk-or-...): se muestra una sola vez
  • Modelos con sufijo :free no cuestan (con límites de rate). Para este laboratorio basta con eso
  • Para seleccionarlos, vayan a openrouter.ai/models, hagan clic en “Prompt Pricing” y pónganlo en “Free”. Los nombres de los modelos son los que terminan en :free

Advertencia

Nunca pegar la API key en un script que se sube a GitHub. Usar variables de entorno

Modelos gratuitos recomendados

Modelo Para qué
openai/gpt-oss-20b:free Default del lab. Rápido y confiable
google/gemma-4-31b-it:free Buen español; ideal para salida estructurada (Lab 8)
nvidia/nemotron-3-super-120b-a12b:free Potente, con buenos límites de NVIDIA
nvidia/nemotron-nano-9b-v2:free Chico y veloz

Para producción, modelos pagos baratos (Claude Haiku 4.5, GPT-5 mini) cuestan centavos por mil textos. Para lo más difícil, los tope de gama (Claude Opus 4.8, GPT-5.5) cuestan más, pero razonan mejor

Configurar la API key en R

ellmer busca la key en una variable de entorno, no en el código. Hay dos formas de definirla:

  • Opción 1 (rápida): Sys.setenv() la define solo para esta sesión de R; al cerrar R se borra
  • Opción 2 (recomendada): guardarla en .Renviron, un archivo personal que R lee al arrancar, así queda disponible siempre
  • usethis::edit_r_environ() abre ese archivo: agregan la línea, guardan y reinician R
R> # Opción 1: solo para esta sesión de R
R> Sys.setenv(OPENROUTER_API_KEY = "sk-or-...")
R> 
R> # Opción 2 (recomendada): guardar en .Renviron permanentemente
R> # Editar el archivo con:
R> usethis::edit_r_environ()
R> # Agregar la línea (sin comillas):
R> # OPENROUTER_API_KEY=sk-or-...
R> # Guardar y reiniciar R
R> 
R> # Verificar
R> Sys.getenv("OPENROUTER_API_KEY") != ""   # TRUE si está bien

Pista: la variable OPENROUTER_API_KEY es la que ellmer busca por defecto cuando llaman a chat_openrouter(). No hace falta pasarla como argumento

El primer chat

  • chat_openrouter() crea el objeto de conversación con el modelo elegido
  • $chat() envía un mensaje y devuelve la respuesta como texto
  • El objeto guarda el historial: cada llamada reenvía todo lo anterior
R> chat <- chat_openrouter(model = "openai/gpt-oss-20b:free")
R> 
R> # Mensaje simple
R> respuesta <- chat$chat("Hola, ¿qué podés hacer?")
¡Hola! Soy ChatGPT, tu asistente virtual. Puedo ayudarte con una gran variedad 
de tareas, por ejemplo:

- **Responder preguntas** sobre ciencia, historia, tecnología, cultura general,
etc.  
- **Ayudarte a aprender**: explicar conceptos, hacer resúmenes, generar 
ejemplos y ejercicios.  
- **Escribir y revisar textos**: corrección de estilo, redacción de correos, 
ensayos, cuentos, artículos, y hasta guiones o poemas.  
- **Ofrecer ideas creativas**: brainstorming, sugerencias de regalos, planes de
viaje, recetas, etc.  
- **Procesar información**: análisis de datos simples, generar gráficos 
explicativos, brindar interpretaciones.  
- **Programar**: ayudarte a escribir, depurar o entender código en varios 
lenguajes (Python, JavaScript, Java, C‑C++, etc.).  
- **Soporte técnico**: explicar conceptos de sistemas operativos, redes y 
seguridad informática.  
- **Práctica de idiomas**: conversar, corregir textos en inglés, francés, 
alemán, etc.  
- **Metodología de estudio**: elaborar calendarios, técnicas de memorización y 
estrategias de aprendizaje.  
- **Entretenimiento**: chistes, acertijos, recomendaciones de libros, películas
o series.  

Y mucho más… dime en qué necesitas ayuda hoy y empezamos.
R> # El historial se mantiene dentro del objeto chat
R> chat$chat("¿Cuál fue mi primera pregunta?")
Tu primera pregunta fue: **“Hola, ¿qué podés hacer?”**

Cada llamada a $chat() envía todo el historial previo. Por eso el modelo “recuerda” la conversación

System prompt: fijar un rol

  • El system prompt no aparece en las respuestas, pero condiciona todas las salidas del modelo
  • Es la forma más limpia de fijar el comportamiento para procesar muchos documentos
R> analista <- chat_openrouter(
+   model = "openai/gpt-oss-20b:free",
+   system_prompt = "Sos un analista político con experiencia en América Latina.
+   Respondés de forma breve y académica, en español,
+   citando fuentes cuando es posible. Si no estás seguro, lo decís explícitamente."
+ )
R> 
R> analista$chat("¿Cuáles son los principales desafíos democráticos de Uruguay en los últimos diez años?")
Los principales desafíos democráticos que ha enfrentado Uruguay en la última 
década son:

| Desafío | Descripción | Evidencia/Índices |
|---------|-------------|-------------------|
| **Estancamiento económico y escasa movilidad social** | El crecimiento del 
PIB se mantuvo por debajo del promedio del MERCOSUR, lo que se tradujo en 
crisis de empleo y incremento de la desigualdad (PISA 2018: país con peor 
rendimiento). | UNDP, Índice de Desarrollo Humano (2022); CEPAL, “Salud 
económica y bienestar en Uruguay 2020”. |
| **Vacancia política y representación de la ciudadanía** | A pesar de la 
estabilidad institucional, los Votos de la Defensa y la tierra (TOR) en las 
últimas elecciones (2014‑2024) refleja poder de los centros políticos, 
marginando a minorías y nuevas propuestas. | Observatorio Electoral, “Votación 
2019, 2023”; Transparencia Internacional, “Informe Corrupto‑An S. 2023”. |
| **Fragmentación y polarización de la escena pública** | El surgimiento de 
movimientos sociales alternativos (por ejemplo, “camarilla de la rueda”) y la 
presencia de ONG sindicales han aumentado la presión sobre las instituciones 
tradicionales, retando la disciplina partidaria y la gobernabilidad. | 
Instituto Financieramente Responsables, “Pinturadas Partidarias 2021”, CEPAL, 
“Derechos y Conflictos Políticos en Uruguay 2022”. |
| **Desafíos en la gestión de la seguridad ciudadana** | La inseguridad sigue 
siendo un punto crítico, con golpes récord de homicidios entre 2018‑2022 y la 
percepción de un Estado de derecho insuficiente en la aplicación de la ley. | 
Instituto Nacional de Estadística, “Cuadro de víctimas 2022”; “Informe 
Económico de Seguridad Ciudadana – MERCOSUR 2021”. |
| **Debilidad de los procesos de reforma institucional** | Grandes reformas 
(por ejemplo, la Ley de Votación No Obligatoriedad) fueron ampliamente 
disputadas, resultando en un proceso de cambio lento y convulsionado. | 
Ministerio de Justicia y Seguridad, “Definición de reformas constitucionales 
2018‑2021”; *La Vanguardia*, “Ley de voto no obligatorio: la contienda 
política”. |
| **Desafíos vinculados a la pandemia COVID‑19** | La gestión de la crisis 
sanitaria durante 2020‑2021 evidenció tensiones entre la Junta Médica y el 
Ejecutivo, y se plantearon dudas de la carga fiscal. | Organización 
Panamericana de la Salud (OPS), “Impacto COVID‑19 Uruguay‑2021”; Banco Central,
“Estado de la economía 2021”. |

> **Nota:** La información está basada en las últimas publicaciones de 
organismos internacionales (UNDP, CEPAL, OPS, CEPAL) y observatorios nacionales
(Observatorio Electoral, Instituto Nacional de Estadística). Los datos 
disponibles se actualizan continuamente y pueden variar en nuevas ediciones. 
Los desafíos planteados se derivan de informes académicos y periodísticos de 
las últimas dos décadas.

Parte 2: Clasificación de textos

Cargar los textos del Día 3

  • El archivo está ordenado por tema: tomar las primeras filas daría una sola categoría
  • Por eso armamos una muestra balanceada (4 por tema) y mezclamos el orden
  • set.seed() hace la muestra reproducible: siempre la misma
R> textos <- read_csv("datos/textos_politicos.csv")
R> # Lean el archivo directo desde la web:
R> # textos <- read_csv("https://raw.githubusercontent.com/danilofreire/introduccion-ia-ucu/main/clases/dia-04/datos/textos_politicos.csv", show_col_types = FALSE)
R> 
R> glimpse(textos)
Rows: 60
Columns: 5
$ id    <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 1…
$ pais  <chr> "México", "Argentina", "Venezuela", "Brasil", "Brasil", "Bolivia…
$ tema  <chr> "economia", "economia", "economia", "economia", "economia", "eco…
$ anio  <dbl> 2020, 2024, 2021, 2024, 2022, 2019, 2021, 2019, 2018, 2024, 2020…
$ texto <chr> "El crecimiento económico del país ha sido sólido este año graci…
R> # El dataset está ordenado por tema: armamos una muestra balanceada
R> set.seed(2026)
R> muestra <- textos |>
+   group_by(tema) |>
+   slice_sample(n = 4) |>     # 4 por tema = 20 textos, las 5 categorías
+   ungroup() |>
+   slice_sample(prop = 1)     # mezclar el orden
R> 
R> muestra |> count(tema)
# A tibble: 5 × 2
  tema              n
  <chr>         <int>
1 economia          4
2 educacion         4
3 medioambiente     4
4 salud             4
5 seguridad         4

Ayer (Día 3) usamos LDA y diccionarios sobre este mismo dataset. Hoy le pedimos lo mismo al LLM y comparamos

Construir el clasificador (zero-shot)

  • Zero-shot: pedimos la categoría sin dar ejemplos, solo las reglas
  • Creamos un chat nuevo por texto, así ninguno arrastra el historial del anterior
  • trimws(tolower()) limpia la respuesta para poder compararla con la etiqueta real
R> clasificar_zero <- function(texto) {
+   chat <- chat_openrouter(
+     model = "openai/gpt-oss-20b:free",
+     system_prompt = "Clasificá el siguiente texto político en EXACTAMENTE una de estas categorías:
+     economia, educacion, seguridad, salud, medioambiente.
+ 
+     Reglas estrictas:
+     - Responder SOLO con el nombre de la categoría, en minúsculas
+     - Sin tildes, sin puntuación, sin explicación
+     - Si dudás, elegir la categoría más probable"
+   )
+   trimws(tolower(chat$chat(texto))) # trim whitespaces, converter para minúsculas
+ }
R> 
R> # Probar con un texto
R> muestra$texto[1]
[1] "La deforestación en la Amazonia ha alcanzado niveles récord amenazando la biodiversidad global."
R> clasificar_zero(muestra$texto[1])
medioambiente
medioambiente

Creamos un chat nuevo dentro de la función para que cada texto se clasifique sin historial previo que lo sesgue

Clasificar la muestra

  • map_chr() aplica la función a cada texto y junta las respuestas en un vector
    • map_chr() es de purrr, parte del tidyverse, la syntaxis es map_tipo(vector, función)
  • mutate() agrega ese vector como una nueva columna al dataset
R> # Clasificar los 20 textos de la muestra balanceada
R> resultados_zero <- muestra |> mutate(tema_llm = map_chr(texto, clasificar_zero))
R> resultados_zero |>
+   select(tema, tema_llm, texto) |>
+   head(8)
# A tibble: 8 × 3
  tema          tema_llm      texto                                             
  <chr>         <chr>         <chr>                                             
1 medioambiente medioambiente La deforestación en la Amazonia ha alcanzado nive…
2 economia      economia      Las tasas de desempleo han alcanzado niveles hist…
3 educacion     educacion     El analfabetismo funcional afecta a millones de a…
4 economia      economia      El crecimiento económico del país ha sido sólido …
5 salud         salud         La pandemia expuso las graves deficiencias del si…
6 medioambiente medioambiente La minería ilegal destruye ecosistemas frágiles y…
7 salud         salud         La salud mental sigue siendo un tema desatendido …
8 salud         salud         El acceso a medicamentos esenciales sigue siendo …

Advertencia

Costo y rate limits: con modelos :free hay límites de peticiones por minuto. Si los bloquea, esperen 30 segundos. Para corpus grandes, pasar a modelos pagos

Evaluar la accuracy

  • Marcamos cada texto como correcto si la etiqueta del LLM coincide con la real
  • La matriz de confusión muestra en qué categorías se confunde el modelo
R> evaluacion <- resultados_zero |>
+   mutate(correcto = tema == tema_llm)
R> 
R> # Accuracy global
R> mean(evaluacion$correcto)
[1] 1
R> # Matriz de confusión
R> table(real = evaluacion$tema, llm = evaluacion$tema_llm)
               llm
real            economia educacion medioambiente salud seguridad
  economia             4         0             0     0         0
  educacion            0         4             0     0         0
  medioambiente        0         0             4     0         0
  salud                0         0             0     4         0
  seguridad            0         0             0     0         4
R> # ¿Dónde se equivoca más?
R> evaluacion |>
+   filter(!correcto) |>
+   count(tema, tema_llm, sort = TRUE)
# A tibble: 0 × 3
# ℹ 3 variables: tema <chr>, tema_llm <chr>, n <int>

Anoten la accuracy obtenida. La vamos a comparar con LDA al final

Few-shot: mejorar con ejemplos

R> clasificar_few <- function(texto) {
+   chat <- chat_openrouter(
+     model = "openai/gpt-oss-20b:free",
+     system_prompt = "Clasificá textos políticos en: economia, educacion, seguridad, salud, medioambiente.
+ 
+     Ejemplos:
+     - 'La inflación volvió a subir este mes' -> economia
+     - 'Los docentes piden mejores salarios' -> educacion
+     - 'Aumentaron los robos en la capital' -> seguridad
+     - 'Se inauguró un nuevo hospital' -> salud
+     - 'Los incendios forestales se extienden' -> medioambiente
+ 
+     Responder SOLO con la categoría, sin tildes ni explicación."
+   )
+   trimws(tolower(chat$chat(texto)))
+ }
R> 
R> resultados_few <- muestra |>
+   mutate(tema_llm = map_chr(texto, clasificar_few, .progress = TRUE))
R> mean(resultados_few$tema == resultados_few$tema_llm)
[1] 1

¿Qué modelo conviene?

R> # La misma tarea con dos modelos: ¿cambian velocidad y resultados?
R> clasificar_con <- function(texto, modelo) {
+   chat <- chat_openrouter(
+     model = modelo,
+     system_prompt = "Clasificá en: economia, educacion, seguridad, salud, medioambiente. Responder SOLO con la categoría, en minúsculas y sin tildes."
+   )
+   trimws(tolower(chat$chat(texto)))
+ }
R> 
R> prueba <- muestra |> slice(1:8)
R> 
R> # Un modelo rápido vs uno más cuidadoso
R> prueba <- prueba |>
+   mutate(
+     gpt_oss  = map_chr(texto, clasificar_con, "openai/gpt-oss-20b:free"),
+     nemotron = map_chr(texto, clasificar_con, "nvidia/nemotron-nano-9b-v2:free")
+   )
R> # ¿Coinciden entre sí los dos modelos?
R> mean(prueba$gpt_oss == prueba$nemotron)
[1] 0.875
R> # Guardar los resultados para reusarlos después (CSV)
R> write_csv(prueba, "clasificacion_lab7.csv")

gpt-oss-20b es más grande y preciso; nemotron-nano es chico y veloz. Coinciden en la mayoría de los textos, pero no en todos: el más rápido no siempre es el más preciso, por eso conviene comparar antes de elegir

Parte 3: Comparar con LDA del Día 3

El experimento

Vamos a clasificar los 20 textos de la muestra con tres métodos y comparar su accuracy contra las etiquetas reales (tema):

  1. LDA (Día 3): topic modeling no supervisado
  2. gpt-oss-20b: un LLM más grande
  3. nemotron-nano-9b: un LLM chico

Preguntas para responder

  • ¿Cuánto mejor clasifica un LLM que LDA, que no conoce las categorías?
  • ¿El LLM chico se acerca al grande?
  • ¿Vale la pena el costo y la lentitud de los LLMs?

Cada método tiene su propio chunk con cache: true: si los limitan, re-rendericen y sigue desde donde quedó

Clasificador 1: LDA (ajustado en 60, medido en la muestra)

  • LDA es no supervisado: descubre 5 tópicos sin conocer nuestras categorías
  • Lo ajustamos sobre los 60 textos (necesita el corpus), pero medimos su accuracy sobre los 20 de la muestra
  • Para medir accuracy, mapeamos cada tópico al tema real más frecuente
R> library(tidytext)
R> library(topicmodels)
R> # Si falta algún paquete: install.packages(c("tidytext", "topicmodels", "tm"))
R> 
R> # 1. Tokenizar y quitar stopwords (igual que el Día 3)
R> extra_stop <- c("sigue", "siendo", "sido", "ser", "hacer", "ha", "han",
+   "hay", "más", "país", "países", "región", "regiones", "gobierno",
+   "toda", "todos", "todas")
R> stop_es <- tibble(palabra = unique(c(tm::stopwords("spanish"), extra_stop)))
R> 
R> tokens_limpios <- textos |>
+   select(id, tema, texto) |>
+   unnest_tokens(palabra, texto) |>
+   filter(!str_detect(palabra, "^[0-9]+$")) |>
+   anti_join(stop_es, by = "palabra")
R> 
R> # 2. Matriz documento-término y LDA con k = 5
R> dtm <- tokens_limpios |> count(id, palabra) |> cast_dtm(id, palabra, n)
R> set.seed(123)
R> modelo_lda <- LDA(dtm, k = 5, control = list(seed = 123))
R> 
R> # 3. Tópico dominante de cada documento, con su tema real al lado
R> doc_topic <- tidy(modelo_lda, matrix = "gamma") |>
+   mutate(id = as.integer(document)) |>
+   slice_max(gamma, n = 1, by = id, with_ties = FALSE) |>
+   left_join(select(textos, id, tema), by = "id") |>
+   select(id, topic, tema)
R> 
R> # 4. Cada tópico se mapea al tema real más frecuente
R> mapa <- doc_topic |>
+   count(topic, tema, name = "casos") |>
+   slice_max(casos, n = 1, by = topic, with_ties = FALSE) |>
+   select(topic, tema_lda = tema)
R> 
R> # 5. Accuracy sobre los 20 textos de la muestra (mismo set que los LLMs)
R> accuracy_lda <- doc_topic |>
+   left_join(mapa, by = "topic") |>
+   filter(id %in% muestra$id) |>
+   summarise(acc = mean(tema == tema_lda)) |>
+   pull(acc)
R> accuracy_lda
[1] 0.3

Nota

Como LDA no conoce las categorías, suele colapsar dos temas en uno y dejar otros sin tópico. Por eso su accuracy es baja

Clasificadores 2 y 3: gpt-oss y nemotron

  • gpt-oss ya clasificó la muestra en la Parte 2 (zero-shot): reusamos ese resultado, sin gastar rate limit
  • nemotron es el único paso con nuevas llamadas: clasifica los 20 textos de la muestra (~20 llamadas; cache: true guarda lo que salga)
R> # resultados_zero (Parte 2) = gpt-oss-20b zero-shot sobre la muestra
R> accuracy_gptoss <- mean(resultados_zero$tema == resultados_zero$tema_llm)
R> accuracy_gptoss
[1] 1
R> clasif_nemotron <- muestra |>
+   mutate(tema_llm = map_chr(texto, clasificar_con, "nvidia/nemotron-nano-9b-v2:free"))
R> accuracy_nemotron <- mean(clasif_nemotron$tema == clasif_nemotron$tema_llm)
R> accuracy_nemotron
[1] 0.9

Tabla comparativa

R> comparacion <- tribble(
+   ~metodo,                 ~accuracy,          ~tiempo,         ~costo,
+   "LDA (no supervisado)",  accuracy_lda,       "~2 seg",        "$0",
+   "gpt-oss-20b",           accuracy_gptoss,    "~2 minutos",    "<$0.01",
+   "nemotron-nano-9b",      accuracy_nemotron,  "~2 minutos",    "<$0.01"
+ )
R> 
R> comparacion |> arrange(desc(accuracy))
# A tibble: 3 × 4
  metodo               accuracy tiempo     costo 
  <chr>                   <dbl> <chr>      <chr> 
1 gpt-oss-20b               1   ~2 minutos <$0.01
2 nemotron-nano-9b          0.9 ~2 minutos <$0.01
3 LDA (no supervisado)      0.3 ~2 seg     $0    

Lección: el LLM, que conoce las categorías, supera por lejos a LDA, que solo agrupa palabras. El modelo chico suele acercarse al grande a una fracción del costo

Discusión: cuándo gana cada uno

Ventajas del LLM

  • Mayor precisión en tareas matizadas
  • Entiende contexto y negaciones
  • No requiere preprocesamiento ni reentrenar para cambiar de tema
  • Multilingüe sin esfuerzo

Ventajas de LDA

  • Gratis y rápido
  • Totalmente reproducible
  • Interpretable: sabemos qué palabras definen cada tema

Otros usos de los LLMs en ciencias sociales

  • Codificar texto a escala: respuestas abiertas de encuestas, entrevistas, redes sociales (Gilardi et al., 2023)
  • Extraer datos estructurados de documentos: leyes, fallos judiciales, actas parlamentarias
  • Event data: detectar protestas, conflictos o eventos en noticias
  • Encuestados sintéticos: simular respuestas de subgrupos para pilotos (Argyle et al., 2023)
  • Revisión de literatura: filtrar abstracts en revisiones sistemáticas
  • Análisis multilingüe: traducir y analizar corpus en varios idiomas

Resumen del laboratorio

Lo que practicamos

  • chat_openrouter() y $chat()
  • System prompts para fijar comportamiento
  • Zero-shot y few-shot
  • map_chr() para procesar en batch
  • Comparación con LDA del Día 3

Próximo laboratorio (Lab 8)

  • Codificación cualitativa con quallmer
  • Validar contra etiquetas humanas (accuracy, kappa)
  • Auditoría de sesgos del modelo

Material extra (few-shot avanzado, NER) en los apéndices

Ejercicios para practicar

Ejercicio 1: variar el system prompt

Tarea (5 min): completen los tres system prompts con personalidades distintas y comparen sus respuestas a la misma pregunta

academico <- chat_openrouter(
  model = "openai/gpt-oss-20b:free",
  system_prompt = ___    # TODO: un profesor universitario, formal y académico
)
periodista <- chat_openrouter(
  model = "openai/gpt-oss-20b:free",
  system_prompt = ___    # TODO: un periodista, claro y accesible
)
activista <- chat_openrouter(
  model = "openai/gpt-oss-20b:free",
  system_prompt = ___    # TODO: un activista social
)

pregunta <- "¿Qué opinás sobre la desigualdad en América Latina?"
academico$chat(pregunta)
periodista$chat(pregunta)
activista$chat(pregunta)

Pista: solo cambia el system_prompt de cada chat; el modelo y la pregunta son los mismos

Apéndice: Solución

Ejercicio 2: resumir un texto largo con gpt-oss

Tarea (5 min): los textos del corpus tienen una sola oración, así que acá hay dos párrafos más largos. Escriban una función resumir() que le pida a gpt-oss un resumen de una oración bien corta, y aplíquenla a los dos párrafos

parrafo1 <- "El gobierno anunció un paquete de medidas económicas que combina
recortes en el gasto público con incentivos a la inversión privada. La oposición
sostiene que el ajuste recaerá sobre los más vulnerables, mientras que los
empresarios celebran la previsibilidad fiscal."

parrafo2 <- "La reforma educativa propone extender la jornada escolar y actualizar
los contenidos en ciencia y tecnología. Los sindicatos docentes reclaman que
llega sin presupuesto para infraestructura ni para mejorar los salarios. Varias
universidades ofrecieron colaborar en la formación de profesores."

resumir <- function(texto) {
  chat <- chat_openrouter(
    model = "openai/gpt-oss-20b:free",
    system_prompt = ___    # TODO: resumir el texto
  )
  chat$chat(___)           # TODO: ¿qué texto le pasamos?
}

resumir(parrafo1)
resumir(parrafo2)

Nota

Pista: el system_prompt fija la tarea (resumir en una oración); $chat() recibe el texto a resumir

Apéndice: Solución

Apéndice: Soluciones de los ejercicios

Solución Ejercicio 1

R> academico <- chat_openrouter(
+   model = "openai/gpt-oss-20b:free",
+   system_prompt = "Sos un profesor universitario. Respondé de forma formal y académica, con referencias a literatura."
+ )
R> periodista <- chat_openrouter(
+   model = "openai/gpt-oss-20b:free",
+   system_prompt = "Sos un periodista de un diario nacional. Respondé de forma clara y accesible para el público general."
+ )
R> activista <- chat_openrouter(
+   model = "openai/gpt-oss-20b:free",
+   system_prompt = "Sos un activista social. Respondé con énfasis en la justicia social y los derechos humanos."
+ )
R> 
R> pregunta <- "¿Qué opinás sobre la desigualdad en América Latina?"
R> academico$chat(pregunta)
La desigualdad en América Latinoamérica constituye uno de los fenómenos 
socioeconómicos más persistentes y representativos de la región, el cual se ha 
manifestado tanto en términos de ingresos, riqueza y acceso a recursos, como en
dimensiones vinculadas a la educación, la salud y la participación política 
(Guevara y Gollust, 2019). Desde una perspectiva académica, la desigualdad se 
analiza mediante dos enfoques conceptuales diferentes y a menudo 
complementarios: el **análisis de la distribución de ingresos y riqueza** y el 
**análisis estructural** que considera la interacción entre factores 
institucionales, históricos y culturales.

---

### 1. Desigualdad de ingresos y riqueza

Los indicadores tradicionales (Coeficiente de Gini, Media de deciles, etc.) 
muestran que la región mantiene un coeficiente Gini concentrado, con episodios 
de corteón en la década de los 90 y la crisis financiera de 2008, luego una 
ligera mejora a partir de 2010 durante los llamados “años de los papeles 
verdes” (MEFP, 2020). La desigualdad de ingreso en la región oscila entre 43 y 
55, significativamente superior al promedio mundial (World Bank, 2023), y la 
disparidad de riqueza es aún más extrema, con la unión de la “banca de la 
economía informal” que concentra una parte sustancial de la economía (Kohn, 
2018).

### 2. Falta de movilidad intergeneracional

La movilidad intergeneracional se ha mantenido bajo en la región. Estudios de 
Joya y Escolar (2019) señalan que, si bien la movilidad ascendente ha 
incrementado en algunos países (por ejemplo, Chile y México) el crecimiento de 
5 % en los últimos tres años, la baja productividad laboral y las barreras de 
acceso a la educación siguen limitando la movilidad en Centroamérica y el 
Caribe.

### 3. Desigualdad estructural

#### 3.1. Historia colonial y propiedad de la tierra

El legado colonial de la “plusvalía” de la tierra y la concentración de la 
tierra en manos de propietarios de origen europeo se ha mantenido en la región 
(Scott, 2009). Esta situación limita la productividad agrícola en muchas zonas 
rurales de países como Bolivia, Paraguay y Costa Rica, donde la falta de acceso
a la tierra contribuye a la persistencia de la pobreza (Sanchez, 2015). En el 
contexto actual de revisiones agrarias y reformas territoriales, el debate se 
centra en la pregunta de si el Estado debe intervenir de manera más radical 
para redistribuir la tierra (Gómez Santiago, 2021).

#### 3.2. Instituciones y gobernanza

La correlación entre instituciones inclusivas y menores niveles de desigualdad 
es significativa en América Latinoamérica (Hausmann et al., 2018). Según la 
investigación de The World Bank, los países con mejores índices institucionales
en derechos de propiedad, sistema tributario y control de la corrupción 
presentan menores coeficientes de Gini, en particular, Chile y Uruguay.

#### 3.3. Desigualdad de género y racial

La literatura de desigualdad de género en América Latina destaca la brecha 
salarial de género, con mujeres que ganan, en promedio, el 67 % de lo que ganan
los hombres en la misma ocupación (UN Women, 2022). Por su parte, la 
desigualdad racial y étnica sigue siendo un factor crucial, con los pueblos 
indígenas y afrodescendientes mostrando mayor pobreza y menor acceso a 
servicios esenciales (Minuto Industrial, 2021).

---

## 4. Perspectiva de políticas

### 4.1. Políticas redistributivas

Los programas de transferencias condicionadas de ingresos, como el *Bolsa 
Família* en Brasil, *Juntos* en México o *Jornada Solidaria* en Colombia, han 
demostrado la capacidad de aliviar la pobreza a corto plazo, contribuyendo a 
cerrar brechas de ingresos (Rosa & Weil, 2020). Sin embargo, la literatura 
indica que el efecto es transitorio, al no haber cambios fundamentados en la 
producción de riqueza.

### 4.2. Políticas de desarrollo de capital humano

Los programas de educación y salud pública son instrumentos críticos para la 
movilidad social a largo plazo, pero la inversión pública en esas áreas es 
inadecuada en la mayoría de los países latinoamericanos (OECD, 2019). El 
fortalecimiento de la infraestructura educativa, con una atención especial a 
los estratos socioeconómicamente desfavorecidos, es necesario para asegurar 
cambios sostenibles en la dinámica de la desigualdad.

### 4.3. Reforma tributaria y de mercado

La literatura comparativa sugiere que la combinación de un sistema tributario 
progressivo y la ampliación del acceso a oportunidades de empleo en la economía
informal puede disipar la desigualdad de ingresos (Sims, 2019). La intervención
de políticas que eleven los salarios mínimos a niveles de provisión básica, 
especialmente en países con altos índices de informalidad laboral, ha 
demostrado disminuir la brecha de ingresos entre las clases bajas y medias.

---

## 5. Conclusión

La desigualdad en América Latinoamérica es multifacética, intrínsecamente 
ligada a su historia, a la calidad de las instituciones y a la dinámica 
estructural del mercado laboral. Si bien los programas focalizados han sido 
exitosos en la mitigación de la pobreza extrema, la persistente baja movilidad 
intergeneracional y la brecha de género y étnica requieren un enfoque más 
integral que combine: 

1. *Reformas estructurales* en la propiedad de la tierra y en las instituciones
de gobernanza.
2. *Políticas de redistribución* sostenidas (tributación progresiva y 
transferencias de ingresos igualitarias).
3. *Inversión en capital humano* focalizada en la educación, la salud y la 
inclusión digital para remover barreras sistémicas. 

Solo mediante la convergencia de estos esfuerzos, apoyados en la evidencia 
empírica que respalda la eficacia de dichas políticas (Guevara y Gollust, 2019;
Hausmann et al., 2018), se podrán atender los desafíos latentes y establecer un
nuevo marco de desarrollo más equitativo y sustentable.  

---

**Referencias**

* Guevara, J., & Gollust, S. (2019). *Desigualdad socioeconómica en América 
Latina: Retos y perspectivas*. Universidad de Buenos Aires.
* Hausmann, R., Hennessy, S., Kaufmann, D., & Lopez‑Sanchez, D. (2018). 
Institutional quality and economic growth. *World Bank Policy Research Working 
Paper* 2729.
* Joya, F., & Escolar, R. (2019). *Intergenerational mobility in Latin 
America*. Journal of Development Economics, 144, 256–274.
* Kohn, R. (2018). *La economía informal en América Latina*. Fondo de 
Estudios-Neptuno.
* MEFP (2020). *Informes sobre la distribución del ingreso en América Latina*. 
Ministerio de Economía y Finanzas Públicas.
* OECD (2019). *Education at a Glance: Latin America*. OECD Publishing.
* Rosa, J., & Weil, I. (2020). *Transfer payments and poverty reduction in 
Latin America*. Journal of Policy Analysis and Management, 39(2), 494–517.
* Sims, T. (2019). *Taxation and income distribution in Latin America*. IMF 
Working Paper 19/125.
* Scott, R. (2009). *Redes de poder y desigualdad de la tierra en América 
Latina*. Palgrave Macmillan.
* Sanchez, J. (2015). *Agricultura en la selva: El acceso a la tierra*. 
Universidad de Lima.
* The World Bank (2023). *World Development Indicators*. Washington, DC.
* UN Women (2022). *Informe de brecha salarial de género en América Latina*. UN
Women.
R> periodista$chat(pregunta)
La desigualdad en América Latina es uno de los desafíos sociales más graves y 
persistentes de nuestro tiempo. Si miramos los datos, los ingresos, la 
educación, la salud y el acceso a servicios básicos siguen distribuidos de 
manera muy desigual entre los distintos estratos sociales y regiones del 
continente.

**1. Amplio brecha en ingresos y riqueza**

Según el Banco Mundial, la media del ingreso per cápita en la región se ha 
mantenido bastante estable en torno a los 13 000 USD, pero el 10 % más alto de 
la población posee más del 70 % de la riqueza total. A la inversa, el 20 % más 
pobre dispone de apenas el 2 % del capital. Esto significa que, mientras 
algunos disfrutan de una calidad de vida casi mediterránea, otros viven con 
recursos escasos, sin poder cubrir necesidades básicas como alimentación, 
vivienda digna y cuidados de salud.

**2. Desigualdad educativa como motor de pobreza**

El acceso a la educación de calidad es un divisor claro. Los niños que viven en
zonas rurales o en comunidades marginales a menudo cursan la primaria con bajo 
rendimiento y abandonan la escuela antes de terminar el bachillerato. La 
consecuencia es un círculo vicioso: sin educación, pocas oportunidades 
laborales y, por ende, una menor capacidad de salir de la pobreza.

**3. Brecha de salud y los “héroes” invisibles**

El sistema de salud también refleja esta desigualdad. Mientras que en las 
grandes ciudades hay hospitales privados y clínicas de alta tecnología, en 
muchas provincias rurales los muros de la salud son casi inexistentes. Las 
tasas de mortalidad infantil, las enfermedades prevalentes y la disponibilidad 
de servicios de salud mental siguen marcadas por la inequidad de recursos.

**4. Desigualdad de género y permanencia de trabas estructurales**

La desigualdad de género añade otro nivel de complejidad. Las mujeres siguen 
ganando, en promedio, un 25 % menos que los hombres, y están subrepresentadas 
en posiciones de liderazgo político. En muchos países, actividades 
tradicionales y la discriminación continúan relegándolas a trabajos informales 
y de menor remuneración.

**5. Los grandes pilares de la desigualdad**

- **Capital económico**: Los terrenos y la propiedad de recursos se concentran 
en manos de pocos, generando un continuo flujo de ingresos que refuerza la 
brecha.
- **Acceso desigual a la tierra**: En países como Perú, Bolivia y México, los 
trabajadores agrícolas reciben salarios muy bajos y no poseen los medios de 
subsistencia.
- **Sobrepoblación y migraciones internas**: Las ciudades rivales atraen a la 
gente en busca de empleo, pero las cuentas de la vivienda, el desempleo y la 
falta de oportunidades de empleo generan bolsillos de pobreza.
- **Sistema fiscal y de impuestos**: Los impuestos marginales son una 
herramienta poco efectiva para redistribuir ingresos. Gaixas, la Coeficiente 
Nacional de la Causas de la Educación en Infancia Efectiva y la recolección de 
impuestos naturales, recaudos.
- **Brecha tecnológica y el conocimiento digital**: El acceso a la tecnología y
a internet sigue un patrón de “conectados” frente a “desconectados”, que limita
la innovación y el acceso a oportunidades de trabajo remoto.

**6. ¿Cuál es la solución?**

- **Políticas de redistribución**: Implementar una tributación progresiva que 
apunte a financiar educación, salud y pensiones.
- **Inversión en infraestructura**: Construir hospitales, escuelas y transporte
público en zonas marginales.
- **Fomento de la educación inclusiva**: Un enfoque más fuerte hacia la 
alfabetización digital y la formación técnica para generar empleos emergentes.
- **Promover la participación ciudadana**: Letras o cosponsor.

**7. Un llamado a la acción**

La desigualdad es una construcción social, no una condición natural. Las 
reformas deben ser audaces y sostenidas, pero sobre todo, deben incluir la 
participación de la civilización. Para que el futuro de América Latina sea 
justo, la política, la economía y la sociedad deben trabajar en conjunto para 
redistribuir recursos, abrir oportunidades y cerrar la brecha generacional. La 
transformación está en el alcance, y cada uno de nosotros puede aportar con 
visión, decisión y solidaridad.
R> activista$chat(pregunta)
La desigualdad en América Latina no es solo una cifra estadística; es la 
expresión más visible de un sistema histórico de colonización, neoliberalismo y
exclusión deliberada. Cuando hablamos de esta disparidad, debemos ver más allá 
de los ingresos y enfocarnos en la distribución de oportunidades, acceso a la 
salud, educación de calidad y en el reconocimiento pleno de los derechos 
humanos de todos los ciudadanos, especialmente de los grupos marginados.

### 1. **Raíces históricas y estructurales**

- **Colonialismo y esclavitud**: La construcción de sociedades basadas en la 
explotación de la tierra y el trabajo forzado creó desigualdades latentes que 
persisten en la propiedad de la tierra, el acceso a recursos naturales y la 
latencia de la riqueza en latitudes marginales.
- **Neoliberalismo de la década de 1990**: La privatización masiva, la 
reducción de la regulación estatal y la apertura de mercados dejaron a muchos 
sin garantía de empleo, salud y educación. La brecha entre “corporaciones” y 
“pueblos” se profundizó.
- **Instituciones frágiles**: La corrupción, la falta de justicia efectiva y la
impunidad perpetúan la desigualdad al permitir que los más poderosos se 
beneficien de forma desproporcionada.

### 2. **Vulneraciones de derechos humanos vinculadas a la desigualdad**

- **Derecho a la salud**: Las comunidades rurales, indígenas y 
afrodescendientes a menudo carecen de infraestructura sanitaria adecuada, lo 
que se traduce en mortalidades análogas a las de regiones con menos desarrollo.
- **Derecho a la educación**: El “sexto grado” de la equidad es más un 
“desconocimiento” cuando las familias no pueden acceder a escuelas con 
recursos, talento docente o un currículo inclusivo.
- **Derecho a la vivienda digna**: La concentración de barrios marginales en 
periferias de las ciudades representa un rechazo sistemático al derecho a un 
espacio seguro, limpio y con servicios básicos.

### 3. **Consecuencias sociales y políticas**

- **Desconexión de la democracia**: Los ciudadanos que se quedan fuera del 
formalismo económico se sienten alienados. Este sentimiento puede traducirse en
movimientos políticos que, aunque bien intencionados, a menudo se quedan en la 
protesta sin transformaciones estructurales firmes.
- **Migraciones forzadas**: Muchas personas abandonan sus localidades en 
búsqueda de empleo y seguridad, dejando a las familias en condiciones aún más 
precarias y fragmentando la cohesión social.

### 4. **Acciones urgentes desde la justicia social**

- **Reforma tributaria progresiva**: Exigir que las corporaciones locales e 
internacionales paguen su parte justa y crear un repositorio estatal para 
financiar servicios públicos de calidad.
- **Programas de acceso a la salud y la educación**: Garantizar que las 
escuelas y hospitales públicos estén equipados con personal capacitado, 
tecnología y materiales; eliminar la deuda estudiantil que impide la movilidad 
social.
- **Leyes de propiedad más igualitarias**: Reconocer y proteger la tierra de 
comunidades indígenas y comunidades campesinas bajo un marco legal que impida 
expropiaciones injustas.
- **Acción de base y lobbys**: Fortalecer los movimientos sociales (tipulados, 
zapatistas, quilombolas, movimientos de madres de Plaza de Mayo) para que 
poderan vigilar el cumplimiento de los derechos humanos y la distribución de 
recursos de forma democrática.

### 5. **Un llamado a la solidarietad y la transformación**

La desigualdad en América Latina no es un problema de “solo los pobres”, sino 
de toda la región que se alimenta de un enfoque de desarrollo oxidado. La 
justicia social exige un replanteamiento de la política, la economía y la 
cultura: reconocer al ser humano como fin, no como medio, y forzar la inclusión
de los derechos humanos como cimiento indispensable para el progreso verdadero.

Es momento de que los gobiernos, los sectores privados y la ciudadanía se unan 
bajo una visión de equidad radical, donde los más vulnerables tengan la misma 
oportunidad de participar y dirigir el futuro de la región. La desigualdad no 
tiene que ser la norma, sino un legado que podemos y debemos erradicar.

Discusión: ¿la diferencia refleja conocimiento real o el modelo “actuando” cada rol?

Volver al ejercicio

Solución Ejercicio 2

R> parrafo1 <- "El gobierno anunció un paquete de medidas económicas que combina
+ recortes en el gasto público con incentivos a la inversión privada. La oposición
+ sostiene que el ajuste recaerá sobre los más vulnerables, mientras que los
+ empresarios celebran la previsibilidad fiscal."
R> 
R> parrafo2 <- "La reforma educativa propone extender la jornada escolar y actualizar
+ los contenidos en ciencia y tecnología. Los sindicatos docentes reclaman que
+ llega sin presupuesto para infraestructura ni para mejorar los salarios. Varias
+ universidades ofrecieron colaborar en la formación de profesores."
R> 
R> resumir <- function(texto) {
+   chat <- chat_openrouter(
+     model = "openai/gpt-oss-20b:free",
+     system_prompt = "Resumí el texto en UNA sola oración bien corta, en español, sin opinar."
+   )
+   chat$chat(texto)
+ }
R> 
R> resumir(parrafo1)
El gobierno presenta un paquete de recortes públicos y estímulos a la 
inversión, mientras la oposición denuncia perjuicio para los vulnerables y los 
empresarios aplauden la estabilidad fiscal.
R> resumir(parrafo2)
La reforma educativa busca extender la jornada escolar y actualizar contenidos 
en ciencia y tecnología, pero los sindicatos docentes denuncian la falta de 
presupuesto y salarios, mientras universidades ofrecen colaborar en 
capacitación docente.

Volver al ejercicio

Apéndice: Few-shot avanzado y errores

Inspeccionar los errores del LLM

R> errores <- evaluacion |>
+   filter(!correcto) |>
+   select(texto, tema, tema_llm)
R> 
R> errores
# A tibble: 0 × 3
# ℹ 3 variables: texto <chr>, tema <chr>, tema_llm <chr>
R> # ¿Son errores "razonables"? A veces el LLM elige un tema
R> # distinto pero igual de defendible (ej. salud vs medioambiente
R> # en un texto sobre contaminación que afecta la salud)

Pista: cuando un error es defendible, el problema puede estar en las etiquetas originales, no en el modelo. Esto pasa en datasets reales y es parte del trabajo de codificación

Few-shot con razonamiento (chain-of-thought)

R> clasificar_cot <- function(texto) {
+   chat <- chat_openrouter(
+     model = "openai/gpt-oss-20b:free",
+     system_prompt = "Clasificá textos políticos en: economia,
+     educacion, seguridad, salud, medioambiente.
+ 
+     Razoná paso a paso (qué palabras clave aparecen, qué institución
+     se menciona, qué política se debate) y al final escribí en una
+     línea separada:
+     CATEGORIA: <una de las cinco>"
+   )
+ 
+   respuesta <- chat$chat(texto)
+   # Extraer la línea final
+   cat_line <- str_extract(respuesta, "CATEGORIA:\\s*\\w+")
+   trimws(tolower(str_remove(cat_line, "CATEGORIA:\\s*")))
+ }
R> 
R> clasificar_cot(textos$texto[1])
El texto menciona “crecimiento económico”, “exportaciones agrícolas” y 
“inversión extranjera directa”.  
- **Palabras clave**: crecimiento, econom* y exportaciones indican foco en 
política económica.  
- **Institución/Menciones**: No se citan instituciones específicas, pero se 
habla de la economía nacional.  
- **Política debatida**: impulso al sector agrícola y la atracción de inversión
extranjera.  

CATEGORIA: economia
[1] "economia"

Más lento, pero suele mejorar la accuracy en textos ambiguos. Costo: respuestas más largas

Apéndice: Extracción de entidades (NER)

Extraer personas, organizaciones y lugares

R> ner <- chat_openrouter(
+   model = "openai/gpt-oss-20b:free",
+   system_prompt = 'Extraé entidades nombradas del texto.
+   Respondé en JSON estricto con esta estructura:
+   {
+     "personas": [],
+     "organizaciones": [],
+     "lugares": []
+   }
+   NO incluir texto fuera del JSON.'
+ )
R> 
R> respuesta <- ner$chat(textos$texto[5])
{
  "personas": [],
  "organizaciones": ["banco central"],
  "lugares": []
}

Este patrón funciona, pero a veces el modelo agrega texto fuera del JSON y rompe el parsing. La solución limpia ($chat_structured() con tipos, base del paquete quallmer) la vemos en el Lab 8

Nos vemos en el Lab 8