---
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: <categoria>`.

```{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:**
