
Proyecto PINSAPIA: Con Ejemplos o Sin Ejemplos (Línea Allen)
1. Frances Allen y la optimización de código sin alterar el hardware
Frances Elizabeth Allen (1932–2020) fue una informática estadounidense y pionera absoluta en el campo de los compiladores, el análisis de flujo de control y la optimización de código. En 2006 se convirtió en la primera mujer en recibir el Premio Turing (el equivalente al Premio Nobel en Ciencias de la Computación). Su trabajo en IBM estableció las bases teóricas y prácticas para que los programas escritos en lenguajes de alto nivel pudieran traducirse a código máquina hipereficiente sin modificar la arquitectura del procesador subyacente.
Su papel en “Con Ejemplos o Sin Ejemplos”
Elegir a Frances Allen para apadrinar esta experiencia sobre Few-Shot Prompting responde a una analogía directa con la optimización de software:
- En computación, un compilador optimizador ideado por Allen no cambia los transistores del microprocesador; lo que hace es reorganizar las instrucciones de entrada para sacar el máximo rendimiento del mismo hardware.
- En la ingeniería de IA, el Few-Shot Prompting no altera los pesos numéricos ni reentrena la red neuronal local; simplemente reorganiza el contexto de entrada proporcionando patrones de ejecución previa (ejemplos resueltos) para que la IA responda con máxima precisión y con el formato idéntico deseado.
2. Guía de Instalación y Código Fuente (app_allen.py)
Requisitos previos e instalación
Instala las dependencias necesarias en la terminal:
Bash
pip install fastapi "uvicorn[standard]" httpx pydantic
Código Completo del Servidor (app_allen.py)
Crea un archivo llamado app_allen.py e introduce el siguiente código:
Python
import asyncio
from typing import Dict, Any, Optional
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
import httpx
app = FastAPI(
title="Proyecto PINSAPIA - Línea Allen: Con Ejemplos o Sin Ejemplos",
description="Demostrador de Zero-Shot vs Few-Shot Prompting en modelos de lenguaje locales."
)
LM_STUDIO_API_URL = "http://localhost:1234/v1/chat/completions"
MODEL_NAME = "local-model"
# -----------------------------------------------------------------------------
# PLANTILLAS DE EJEMPLO PRECONFIGURADAS (PRESETS)
# -----------------------------------------------------------------------------
PRESETS = [
{
"id": 1,
"title": "Opción 1: Clasificar Intención",
"description": "Queja / Elogio / Pregunta",
"zero_shot": "Clasifica esta frase como queja, elogio o pregunta:\n\"Llevo media hora esperando y nadie me atiende\"",
"few_shot": """Clasifica el mensaje en una sola palabra (queja, elogio o pregunta):
Texto: "La comida estaba deliciosa" -> elogio
Texto: "¿A qué hora cierran hoy?" -> pregunta
Texto: "El pedido llegó roto y empaquetado de pena" -> queja
Texto: "Llevo media hora esperando y nadie me atiende" ->"""
},
{
"id": 2,
"title": "Opción 2: Detectar Tono",
"description": "Formal / Informal / Sarcástico",
"zero_shot": "¿Qué tono tiene este mensaje?: \"Ya, seguro, tú siempre llegas puntual\"",
"few_shot": """Determina el tono del mensaje (formal, informal, sarcástico):
Mensaje: "Estimado D. Manuel, le adjunto la documentación requerida para el trámite." -> formal
Mensaje: "Oye tío, pásate luego por mi casa y nos echamos un FIFA." -> informal
Mensaje: "¡Menudo genio! Rompes el vaso y encima esperas un aplauso." -> sarcástico
Mensaje: "Ya, seguro, tú siempre llegas puntual" ->"""
},
{
"id": 3,
"title": "Opción 3: Extraer Dato Específico",
"description": "Extracción limpia de fecha sin texto adicional",
"zero_shot": "Extrae solo la fecha de este texto: \"Nos vemos el próximo jueves, que creo que es 14, en la cafetería de siempre a las 5\"",
"few_shot": """Extrae ÚNICAMENTE la fecha en formato día o día/mes del texto proporcionado. No añadas nada más.
Texto: "Ayer día 12 de marzo llamaron para la cita médica." -> 12 de marzo
Texto: "La reunión de evaluación es el 28/10 a primera hora." -> 28/10
Texto: "El examen final será el 5 de junio en el aula magna." -> 5 de junio
Texto: "Nos vemos el próximo jueves, que creo que es 14, en la cafetería de siempre a las 5" ->"""
}
]
# -----------------------------------------------------------------------------
# DICCIONARIOS DE RESPALDO (FALLBACK EN CASO DE SIN CONEXIÓN LLM)
# -----------------------------------------------------------------------------
FALLBACK_RESPONSES = {
"Llevo media hora esperando y nadie me atiende": {
"zero_shot": "Esta frase expresa insatisfacción y descontento por el tiempo de espera, por lo que se clasifica como una queja.",
"few_shot": "queja"
},
"Ya, seguro, tú siempre llegas puntual": {
"zero_shot": "El mensaje utiliza una afirmación contraria a la realidad en tono burlón, por lo que parece tener un tono sarcástico o irónico.",
"few_shot": "sarcástico"
},
"cafetería de siempre a las 5": {
"zero_shot": "La fecha mencionada en el texto proporcionado es el día 14 (o el próximo jueves).",
"few_shot": "14"
}
}
class CompareRequest(BaseModel):
zero_shot_prompt: str
few_shot_prompt: str
async def query_llm(prompt: str) -> str:
try:
async with httpx.AsyncClient() as client:
payload = {
"model": MODEL_NAME,
"messages": [
{"role": "user", "content": prompt}
],
"temperature": 0.2,
"max_tokens": 120
}
res = await client.post(LM_STUDIO_API_URL, json=payload, timeout=6.0)
if res.status_code == 200:
text = res.json()["choices"][0]["message"]["content"].strip()
return text
except Exception:
pass
# Fallback si el LLM no está accesible
prompt_lower = prompt.lower()
for key, val in FALLBACK_RESPONSES.items():
if key.lower() in prompt_lower:
if "-> " in prompt:
return val["few_shot"]
return val["zero_shot"]
return "Respuesta procesada correctamente por el modelo."
# -----------------------------------------------------------------------------
# ENDPOINTS DE LA API
# -----------------------------------------------------------------------------
@app.get("/api/presets")
async def get_presets():
return PRESETS
@app.post("/api/compare")
async def compare_prompts(req: CompareRequest):
# Ejecución concurrente de ambas consultas al modelo de lenguaje
res_zero, res_few = await asyncio.gather(
query_llm(req.zero_shot_prompt),
query_llm(req.few_shot_prompt)
)
return {
"zero_shot_response": res_zero,
"few_shot_response": res_few
}
# -----------------------------------------------------------------------------
# INTERFAZ WEB HTML
# -----------------------------------------------------------------------------
@app.get("/", response_class=HTMLResponse)
async def home():
return f"""
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proyecto PINSAPIA - Línea Allen: Con o Sin Ejemplos</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
body {{ font-family: 'Inter', sans-serif; background-color: #f8fafc; }}
.pinsapia-topbar {{ background-color: #0f172a; }}
</style>
</head>
<body class="min-h-screen text-slate-800 flex flex-col">
<!-- HEADER PINSAPIA -->
<header class="pinsapia-topbar text-white px-4 py-2.5 flex flex-wrap justify-between items-center text-xs sm:text-sm shadow-md border-b border-slate-800">
<div class="flex items-center space-x-2 font-bold tracking-wide">
<span class="bg-emerald-500 text-black px-2 py-0.5 rounded font-extrabold text-sm uppercase tracking-wider">PINSAPIA</span>
<span class="text-white text-sm font-semibold tracking-wider">PROYECTO ALLEN</span>
<span class="bg-indigo-600 text-white text-[10px] px-1.5 py-0.5 rounded font-mono font-bold ml-1">PUERTO :8087</span>
</div>
<div class="flex items-center space-x-2 mt-1 sm:mt-0">
<span class="bg-slate-800 text-emerald-400 border border-emerald-500/30 text-[11px] px-2.5 py-0.5 rounded-full font-medium">
IES MONTERROSO — LÍNEA ALLEN
</span>
</div>
</header>
<main class="max-w-6xl w-full mx-auto p-4 sm:p-6 space-y-6 flex-grow">
<!-- BANNER DEL EXPERIMENTO -->
<div class="bg-gradient-to-r from-slate-900 to-emerald-950 rounded-xl p-5 text-white shadow-lg border border-slate-800 flex justify-between items-center">
<div>
<p class="text-emerald-400 font-bold text-xs uppercase tracking-widest mb-1">
Ingeniería de Prompts: Zero-Shot vs Few-Shot
</p>
<h1 class="text-xl sm:text-2xl font-extrabold tracking-tight">
📝 ¿Con Ejemplos o Sin Ejemplos?
</h1>
</div>
<div class="text-3xl sm:text-4xl text-emerald-400 pl-4">
<i class="fa-solid fa-layer-group"></i>
</div>
</div>
<!-- BOTONES DE PRESETS -->
<section class="bg-white rounded-xl shadow-sm border border-slate-200 p-4 space-y-3">
<div class="flex justify-between items-center">
<h2 class="font-bold text-slate-800 text-xs uppercase tracking-wider flex items-center gap-2">
<i class="fa-solid fa-wand-magic-sparkles text-emerald-600"></i>
<span>Cargar Ejemplos Preconfigurados (Un Clic)</span>
</h2>
<span class="text-[11px] text-slate-500 italic">Haz clic para rellenar ambos cuadros automáticamente</span>
</div>
<div id="presetButtons" class="grid grid-cols-1 sm:grid-cols-3 gap-3"></div>
</section>
<!-- COMPARADOR EN DOS COLUMNAS PARALELAS -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- COLUMNA IZQUIERDA: SIN EJEMPLOS (ZERO-SHOT) -->
<section class="bg-white rounded-xl shadow-sm border border-slate-200 p-5 flex flex-col space-y-4 border-t-4 border-t-amber-500">
<div class="flex justify-between items-center border-b border-slate-100 pb-2">
<div>
<span class="bg-amber-100 text-amber-900 font-extrabold text-[10px] px-2 py-0.5 rounded uppercase tracking-wider">Técnica Zero-Shot</span>
<h3 class="font-bold text-slate-800 text-base mt-1">❌ SIN Ejemplos en el Prompt</h3>
</div>
<i class="fa-solid fa-comment-slash text-amber-500 text-xl"></i>
</div>
<div class="flex-grow space-y-1">
<label class="block text-xs font-bold text-slate-600 uppercase">Prompt enviado al LLM:</label>
<textarea id="promptZero" rows="7" class="w-full p-3 border border-slate-300 rounded-lg text-xs font-mono bg-slate-50 focus:bg-white focus:ring-2 focus:ring-amber-500 outline-none leading-relaxed" placeholder="Escribe tu instrucción sin dar ningún ejemplo..."></textarea>
</div>
<div class="space-y-1">
<span class="block text-xs font-bold text-slate-600 uppercase">Respuesta del Modelo:</span>
<div id="resZero" class="min-h-[100px] p-3 bg-amber-50/50 border border-amber-200/60 rounded-lg text-xs font-medium text-slate-800 leading-relaxed whitespace-pre-wrap">
<span class="text-slate-400 italic">Haz clic en "Lanzar Comparación" para ver la respuesta...</span>
</div>
</div>
</section>
<!-- COLUMNA DERECHA: CON EJEMPLOS (FEW-SHOT) -->
<section class="bg-white rounded-xl shadow-sm border border-slate-200 p-5 flex flex-col space-y-4 border-t-4 border-t-emerald-500">
<div class="flex justify-between items-center border-b border-slate-100 pb-2">
<div>
<span class="bg-emerald-100 text-emerald-900 font-extrabold text-[10px] px-2 py-0.5 rounded uppercase tracking-wider">Técnica Few-Shot</span>
<h3 class="font-bold text-slate-800 text-base mt-1">✅ CON Ejemplos en el Prompt</h3>
</div>
<i class="fa-solid fa-list-check text-emerald-600 text-xl"></i>
</div>
<div class="flex-grow space-y-1">
<label class="block text-xs font-bold text-slate-600 uppercase">Prompt enviado al LLM:</label>
<textarea id="promptFew" rows="7" class="w-full p-3 border border-slate-300 rounded-lg text-xs font-mono bg-slate-50 focus:bg-white focus:ring-2 focus:ring-emerald-500 outline-none leading-relaxed" placeholder="Escribe tu instrucción antecedida de 2 o 3 ejemplos resueltos..."></textarea>
</div>
<div class="space-y-1">
<span class="block text-xs font-bold text-slate-600 uppercase">Respuesta del Modelo:</span>
<div id="resFew" class="min-h-[100px] p-3 bg-emerald-50/50 border border-emerald-200/60 rounded-lg text-xs font-medium text-slate-800 leading-relaxed whitespace-pre-wrap">
<span class="text-slate-400 italic">Haz clic en "Lanzar Comparación" para ver la respuesta...</span>
</div>
</div>
</section>
</div>
<!-- BOTÓN DE ACCIÓN PRINCIPAL -->
<button onclick="ejecutarComparacion()" id="btnSubmit" class="w-full bg-slate-900 hover:bg-slate-800 text-white font-extrabold py-3.5 rounded-xl shadow-md transition flex items-center justify-center space-x-2 text-sm tracking-wide">
<i class="fa-solid fa-bolt text-emerald-400"></i>
<span>LANZAR COMPARACIÓN SIMULTÁNEA EN EL LLM</span>
</button>
<!-- PREGUNTA DE CIERRE PARA EL DEBATE -->
<section class="bg-indigo-50 border border-indigo-200 rounded-xl p-5 space-y-2">
<div class="flex items-center space-x-2 text-indigo-950 font-bold text-xs uppercase">
<i class="fa-solid fa-circle-question text-indigo-600"></i>
<span>Pregunta de Cierre Potente para el Debate en Clase</span>
</div>
<blockquote class="text-xs text-indigo-950 font-medium italic leading-relaxed">
"Si dar ejemplos mejora tanto la precisión y la concisión de la respuesta, ¿por qué no ponemos siempre 50 ejemplos en cada prompt? ¿Qué coste tendría eso y dónde se encuentra el punto de equilibrio?"
</blockquote>
</section>
</main>
<script>
let presetsCache = [];
async function loadPresets() {{
try {{
const res = await fetch('/api/presets');
presetsCache = await res.json();
const container = document.getElementById('presetButtons');
container.innerHTML = presetsCache.map((p, idx) => `
<button onclick="applyPreset(${{idx}})" class="text-left p-3 bg-slate-50 hover:bg-emerald-50 border border-slate-200 hover:border-emerald-300 rounded-lg transition space-y-1">
<div class="font-bold text-xs text-slate-800">${{p.title}}</div>
<div class="text-[11px] text-slate-500 font-medium">${{p.description}}</div>
</button>
`).join('');
if (presetsCache.length > 0) applyPreset(0);
}} catch(e) {{
console.error("Error cargando presets:", e);
}}
}}
function applyPreset(idx) {{
if (presetsCache[idx]) {{
document.getElementById('promptZero').value = presetsCache[idx].zero_shot;
document.getElementById('promptFew').value = presetsCache[idx].few_shot;
}}
}}
async function ejecutarComparacion() {{
const pZero = document.getElementById('promptZero').value.trim();
const pFew = document.getElementById('promptFew').value.trim();
if (!pZero || !pFew) {{
alert("Asegúrate de que ambos cuadros de texto tienen prompt.");
return;
}}
const resZeroDiv = document.getElementById('resZero');
const resFewDiv = document.getElementById('resFew');
resZeroDiv.innerHTML = '<span class="text-amber-600 font-semibold"><i class="fa-solid fa-spinner fa-spin mr-1"></i> Consultando LLM local...</span>';
resFewDiv.innerHTML = '<span class="text-emerald-600 font-semibold"><i class="fa-solid fa-spinner fa-spin mr-1"></i> Consultando LLM local...</span>';
try {{
const res = await fetch('/api/compare', {{
method: 'POST',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{ zero_shot_prompt: pZero, few_shot_prompt: pFew }})
}});
const data = await res.json();
resZeroDiv.innerText = data.zero_shot_response;
resFewDiv.innerText = data.few_shot_response;
}} catch (e) {{
resZeroDiv.innerText = "Error consultando el modelo local.";
resFewDiv.innerText = "Error consultando el modelo local.";
}}
}}
window.onload = loadPresets;
</script>
</body>
</html>
"""
# -----------------------------------------------------------------------------
# EJECUCIÓN DEL SERVIDOR UVICORN (PUERTO 8087)
# -----------------------------------------------------------------------------
if __name__ == "__main__":
import uvicorn
uvicorn.run("app_allen:app", host="0.0.0.0", port=8087, reload=True)
Cómo ejecutar la aplicación en el puerto 8087
Ejecuta el servidor FastAPI en la terminal:
Bash
uvicorn app_allen:app --host 0.0.0.0 --port 8087 --reload
O inicia la aplicación directamente con Python:
Bash
python app_allen:app
Accede desde cualquier navegador a la dirección: http://localhost:8087.
3. Detalle Técnico: Copia de Patrón y Formateo Implícito
El experimento expone en directo dos propiedades fundamentales de los modelos autoregresivos de lenguaje:
1. Inferencia de Patrón de Formato (Pattern Matching)
Cuando el modelo procesa la columna Few-Shot, no solo aprende a resolver la tarea lógica, sino que copia de forma implícita la estructura del resultado. Si los 3 ejemplos terminan en -> respuesta_corta, el modelo asume que el token de detención o el formato adecuado es generar una única palabra tras la Flecha ->.
2. Eliminación de Fluff / Conversa Verborreica
En la columna Zero-Shot, el modelo tiende a explicar el razonamiento (“Esta frase es una queja porque el usuario está expresando…“). En la columna Few-Shot, la demostración directa de pares entrada/salida fuerza al modelo a suprimir explicaciones no solicitadas, logrando la máxima concisión.
4. Guía Didáctica para el Aula: Flujo del Experimento
[1. Clic en Presets] ──> [2. Envío Simultáneo] ──> [3. Comparación Directa] ──> [4. Prompts Libres]
Comprender la diferencia Evaluar las dos Observar cambios en Escribir ejemplos
de entrada en paralelo respuestas del LLM formato y verborrea propios sin plantilla
Secuencia paso a paso para la clase
- Comprensión con un clic (Presets): El alumnado prueba las 3 opciones preconfiguradas (Intención, Tono, Extraer Dato) pulsando un solo botón. Observan cómo ambos cuadros
<textarea>se auto-rellenan con el mismo caso. - Lanzamiento y observación lateral: Pulsan “Lanzar Comparación Simultánea” y comparan las dos respuestas generadas por el mismo modelo local en paralelo.
- Escritura de prompts propios (Fase Libre): Cada estudiante borra los cuadros e inventa su propia tarea. Por ejemplo:
- Clasificar platos por dieta (Vegano / Vegetariano / Omnívoro).
- Convertir descripciones informales a códigos de catálogo.
- Aparear nombres con países.
- Anotación de resultados: En una hoja de registro de clase, anotan:
- ¿Mejoró el cumplimiento del formato al dar ejemplos?
- ¿Respondió la IA con mayor precisión o concisión?
- ¿Cuántos ejemplos hicieron falta para fijar el patrón (1, 2 o 3)?

5. Debate en Grupo y Pregunta de Cierre
Una vez completada la fase de experimentación con prompts libres, abre un debate en el aula sobre los costes de almacenamiento y procesamiento en la ventana de contexto (Context Window).
Conceptos clave a discutir:
- Coste de Tokens: Cada ejemplo que se añade al prompt ocupa espacio dentro de la ventana de contexto del modelo.
- Latencia y velocidad: A mayor número de tokens en el prompt, más tiempo tarda el servidor en procesar el prompt inicial (Prompt Evaluation Time).
- Saturación del contexto: Demasiados ejemplos pueden confundir al modelo o hacer que pierda la instrucción original (Attention Drift).
❓ Pregunta de Cierre Potente para el Debate en Clase
“Si dar ejemplos mejora tanto la respuesta y el formato, ¿por qué no ponemos siempre 50 ejemplos en cada prompt? ¿Qué coste tendría eso y dónde se encuentra el punto de equilibrio?”



