--- title: "Tarea 7: Primeros pasos con ellmer" subtitle: "IA para Científicos Sociales - UCU" author: "Danilo Freire" date: "2026-04-15" format: html: toc: true toc-depth: 2 number-sections: true code-fold: false lang: es engine: knitr --- ## 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:** 1. Descarguen este archivo `.qmd` y el dataset `textos_politicos.csv` (en `clases/dia-04/datos/`) de la [página del curso](https://danilofreire.github.io/introduccion-ia-ucu/laboratorios.html), o clonen el repositorio completo con `git clone https://github.com/danilofreire/introduccion-ia-ucu.git` 2. Necesitan [Quarto](https://quarto.org/docs/get-started/) instalado junto con RStudio. Las versiones recientes de RStudio (>= 2022.07) ya incluyen Quarto 3. Necesitan una API key de [OpenRouter](https://openrouter.ai/keys) configurada como `OPENROUTER_API_KEY` en `.Renviron`. Alternativa: instalar [Ollama](https://ollama.com/) y usar `chat_ollama()` en lugar de `chat_openrouter()` 4. Abrir este archivo `.qmd` en RStudio y ejecutar los bloques de código con Ctrl+Enter (Cmd+Enter en Mac) ### Configuración ```{r setup, message=FALSE, warning=FALSE} 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" ``` ## Configuración y chat básico ### 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]{.alert}, 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. ```{r p1} # Escriban su código aquí ``` **Respuesta:** ### 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. ```{r p2} # Escriban su código aquí ``` **Respuesta:** ### 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]{.alert} 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. ```{r p3} # Escriban su código aquí ``` **Respuesta:** ## Clasificación y extracción ### 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]{.alert} 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. ```{r p4} # Escriban su código aquí ``` **Respuesta:** ### 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. ```{r p5} # Escriban su código aquí ``` **Respuesta:** ## Robustez y sensibilidad ### 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. ```{r p6} # Escriban su código aquí ``` **Respuesta:** ### 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. ```{r p7} # Escriban su código aquí ``` **Respuesta:** ### 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. ```{r p8} # Escriban su código aquí ``` **Respuesta:** ## Más allá de la clasificación ### 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). ```{r p9} # Escriban su código aquí ``` **Respuesta:** ### 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: `. ```{r p10} # Escriban su código aquí ``` **Respuesta:** ## Reflexión ### 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`. ```{r p11} # Escriban su código aquí ``` **Respuesta:** ### 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:**