Tarea 7: Primeros pasos con ellmer

IA para Científicos Sociales - UCU

Autor/a

Danilo Freire

Fecha de publicación

15 de abril de 2026

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:

  1. Descarguen este archivo .qmd y el dataset textos_politicos.csv (en clases/dia-04/datos/) de la página del curso, o clonen el repositorio completo con git clone https://github.com/danilofreire/introduccion-ia-ucu.git
  2. Necesitan Quarto instalado junto con RStudio. Las versiones recientes de RStudio (>= 2022.07) ya incluyen Quarto
  3. Necesitan una API key de OpenRouter configurada como OPENROUTER_API_KEY en .Renviron. Alternativa: instalar Ollama 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)

1.1 Configuración

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"

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:

Volver arriba