Módulo 1: Fundamentos de Prompt Engineering
8. Proyecto: Prompt Analyzer
Descripción del proyecto
En este proyecto construirás un Prompt Analyzer: un sistema que recibe cualquier prompt, lo clasifica por técnica (zero-shot, few-shot, CoT, mixto), identifica sus componentes (instrucción, contexto, input, output format), puntúa las dimensiones CRISPE presentes, y genera sugerencias de mejora específicas y accionables. El output es JSON estructurado con un schema definido y validado con Pydantic.
Este proyecto integra todo lo aprendido en el Módulo 1: anatomía de prompts, roles, parámetros, el framework CRISPE, la diferencia entre prompts casuales y engineered, y diferencias entre proveedores. No es un ejercicio teórico — es una herramienta que usarás a lo largo de toda la guía: cuando diseñes un prompt de CoT en el Módulo 4, puedes pasarlo por el Analyzer para verificar que tiene todos los componentes.
Objetivo: Construir un sistema funcional de análisis de prompts con output estructurado, que demuestre que los prompts tienen estructura analizable de forma sistemática.
Especificaciones Técnicas
Estructura del proyecto
prompt-analyzer/
├── analyzer.py # Lógica principal de análisis
├── schemas.py # Pydantic models para input/output
├── main.py # CLI e interface de prueba
├── test_cases.py # Casos de prueba con prompts de ejemplo
├── .env # API keys (no commitear)
└── requirements.txt # Dependencias
Dependencias
# requirements.txt
openai>=1.0.0
anthropic>=0.25.0
python-dotenv>=1.0.0
pydantic>=2.0.0
tiktoken>=0.5.0
Schema de salida
El Prompt Analyzer devuelve un JSON con cuatro secciones:
{
"clasificacion": {
"tecnica": "zero-shot|few-shot|chain-of-thought|mixto",
"confianza": 0.0-1.0,
"razon": "Explicación de por qué se clasificó así"
},
"componentes": {
"instruccion": "presente|ausente|implicito",
"contexto": "presente|ausente|implicito",
"input": "presente|ausente|implicito",
"output_format": "presente|ausente|implicito"
},
"crispe": {
"capacity": "presente|ausente|implicito",
"role": "presente|ausente|implicito",
"insight": "presente|ausente|implicito",
"statement": "presente|ausente|implicito",
"personality": "presente|ausente|implicito",
"experiment": "presente|ausente|implicito",
"score": 0-6
},
"sugerencias": [
"Sugerencia 1 específica y accionable",
"Sugerencia 2 específica y accionable"
],
"calidad": {
"nivel": "casual|basico|engineered|avanzado",
"puntuacion": 0-100,
"resumen": "Evaluación en 1 frase"
},
"metadata": {
"longitud_caracteres": 0,
"estimacion_tokens": 0,
"tiene_system_prompt": true|false
}
}
Implementación Paso a Paso
Paso 1: schemas.py — Definir los modelos Pydantic
# schemas.py
from pydantic import BaseModel, Field
from typing import Literal
EstadoComponente = Literal["presente", "ausente", "implicito"]
TecnicaPrompt = Literal["zero-shot", "few-shot", "chain-of-thought", "mixto"]
NivelCalidad = Literal["casual", "basico", "engineered", "avanzado"]
class Clasificacion(BaseModel):
tecnica: TecnicaPrompt = Field(description="Técnica de prompting detectada")
confianza: float = Field(ge=0.0, le=1.0, description="Confianza en la clasificación")
razon: str = Field(description="Por qué se clasificó con esta técnica")
class Componentes(BaseModel):
instruccion: EstadoComponente = Field(description="Instrucción o tarea principal")
contexto: EstadoComponente = Field(description="Contexto o información de fondo")
input: EstadoComponente = Field(description="El input que procesará el modelo")
output_format: EstadoComponente = Field(description="Formato de salida esperado")
class CRISPE(BaseModel):
capacity: EstadoComponente
role: EstadoComponente
insight: EstadoComponente
statement: EstadoComponente
personality: EstadoComponente
experiment: EstadoComponente
score: int = Field(ge=0, le=6, description="Cuántos componentes CRISPE están presentes")
class Calidad(BaseModel):
nivel: NivelCalidad
puntuacion: int = Field(ge=0, le=100)
resumen: str = Field(description="Evaluación en 1 frase")
class Metadata(BaseModel):
longitud_caracteres: int
estimacion_tokens: int
tiene_system_prompt: bool
class PromptAnalysis(BaseModel):
clasificacion: Clasificacion
componentes: Componentes
crispe: CRISPE
sugerencias: list[str] = Field(min_length=1, max_length=6)
calidad: Calidad
metadata: Metadata
Paso 2: analyzer.py — Lógica principal
# analyzer.py
import json
import re
import tiktoken
from openai import OpenAI
from dotenv import load_dotenv
from schemas import PromptAnalysis, Metadata
load_dotenv()
client = OpenAI()
ANALYZER_SYSTEM = """
Eres un analizador experto de prompts para sistemas de IA. Analiza el prompt proporcionado
y devuelve un JSON estructurado con el análisis completo.
DEFINICIONES:
TÉCNICAS DE PROMPTING:
- zero-shot: Solo instrucción, sin ejemplos, sin razonamiento explícito
- few-shot: Incluye ejemplos input→output antes de la tarea real
- chain-of-thought: Incluye o solicita razonamiento paso a paso explícito
- mixto: Combina varias técnicas
COMPONENTES DEL PROMPT:
- instruccion: La tarea o acción principal que debe ejecutar el modelo
- contexto: Información de fondo, definiciones de dominio, reglas del negocio
- input: El dato o texto que el modelo debe procesar (puede ser un placeholder)
- output_format: Especificación del formato de salida (JSON, lista, formato específico)
DIMENSIONES CRISPE:
- capacity: Capacidad/acción principal (verbo de la tarea)
- role: Rol, expertise o persona que adopta el modelo
- insight: Contexto o información de fondo necesaria
- statement: Instrucción concreta y específica
- personality: Tono, estilo, restricciones de comportamiento
- experiment: Formato de salida, ejemplos
CALIDAD:
- casual: Sin estructura, sin restricciones, output no controlado
- basico: Tiene instrucción clara pero sin formato ni restricciones
- engineered: Tiene instrucción + formato + restricciones + system prompt o roles
- avanzado: engineered + CRISPE ≥4 + ejemplos o CoT + manejo de edge cases
PUNTUACIÓN DE CALIDAD (0-100):
- 0-25: Casual (prompt sin estructura)
- 26-50: Básico (instrucción clara, poco más)
- 51-75: Engineered (estructura clara, formato, restricciones)
- 76-100: Avanzado (CRISPE completo, ejemplos, edge cases, producción-ready)
SUGERENCIAS ESPECÍFICAS (no genéricas):
- ❌ Malo: "Mejora el prompt"
- ✅ Bueno: "Falta output_format. Añade: 'Responde en JSON: {\"campo\": \"valor\"}'"
- ✅ Bueno: "Falta Role (R en CRISPE). Añade: 'Eres un [expert] especializado en [dominio]'"
- ✅ Bueno: "Sin manejo de edge cases: añade 'Si el input está vacío, responde {\"error\": \"input_vacio\"}'"
Responde ÚNICAMENTE con JSON válido que siga exactamente el schema proporcionado.
"""
SCHEMA_PROMPT = """
{
"clasificacion": {
"tecnica": "zero-shot|few-shot|chain-of-thought|mixto",
"confianza": 0.0-1.0,
"razon": "string"
},
"componentes": {
"instruccion": "presente|ausente|implicito",
"contexto": "presente|ausente|implicito",
"input": "presente|ausente|implicito",
"output_format": "presente|ausente|implicito"
},
"crispe": {
"capacity": "presente|ausente|implicito",
"role": "presente|ausente|implicito",
"insight": "presente|ausente|implicito",
"statement": "presente|ausente|implicito",
"personality": "presente|ausente|implicito",
"experiment": "presente|ausente|implicito",
"score": 0-6
},
"sugerencias": ["string", "string"],
"calidad": {
"nivel": "casual|basico|engineered|avanzado",
"puntuacion": 0-100,
"resumen": "string"
},
"metadata": {
"longitud_caracteres": 0,
"estimacion_tokens": 0,
"tiene_system_prompt": true|false
}
}
"""
def estimar_tokens(texto: str, model: str = "gpt-4o-mini") -> int:
"""Cuenta tokens usando tiktoken. Fallback a aproximación si falla."""
try:
encoding = tiktoken.encoding_for_model(model)
return len(encoding.encode(texto))
except Exception:
return len(texto) // 4 # Aprox: 4 chars ≈ 1 token
def detectar_system_prompt(prompt: str) -> bool:
"""Heurística: detecta si el prompt parece un system prompt."""
indicadores = [
"eres un", "you are a", "actúa como", "act as",
"tu rol es", "your role is", "## capacity", "## role"
]
lower = prompt.lower()
return any(ind in lower for ind in indicadores)
def limpiar_json(raw: str) -> str:
"""Limpia respuesta del modelo para extraer JSON válido."""
# Remover markdown code blocks
if "```" in raw:
match = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', raw, re.DOTALL)
if match:
return match.group(1).strip()
# Buscar primer objeto JSON { ... }
start = raw.find('{')
if start != -1:
depth = 0
for i, char in enumerate(raw[start:], start):
if char == '{':
depth += 1
elif char == '}':
depth -= 1
if depth == 0:
return raw[start:i+1]
return raw.strip()
def analyze_prompt(prompt: str) -> PromptAnalysis:
"""
Analiza un prompt y devuelve análisis estructurado.
Args:
prompt: El prompt a analizar (puede ser solo user o system+user)
Returns:
PromptAnalysis con clasificación, componentes, CRISPE, sugerencias, calidad
"""
tokens = estimar_tokens(prompt)
tiene_system = detectar_system_prompt(prompt)
user_content = f"""Analiza este prompt:
---INICIO DEL PROMPT---
{prompt}
---FIN DEL PROMPT---
Datos conocidos para metadata:
- longitud_caracteres: {len(prompt)}
- estimacion_tokens: {tokens}
- tiene_system_prompt: {tiene_system}
Responde con JSON que siga exactamente este schema:
{SCHEMA_PROMPT}
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": ANALYZER_SYSTEM},
{"role": "user", "content": user_content}
],
temperature=0,
max_tokens=800
)
raw = response.choices[0].message.content.strip()
json_str = limpiar_json(raw)
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
raise ValueError(f"El modelo no devolvió JSON válido: {e}\nRaw: {raw[:200]}")
# Asegurar que metadata tiene los valores correctos
if "metadata" in data:
data["metadata"]["longitud_caracteres"] = len(prompt)
data["metadata"]["estimacion_tokens"] = tokens
data["metadata"]["tiene_system_prompt"] = tiene_system
return PromptAnalysis(**data)
Paso 3: main.py — Interface de prueba y CLI
# main.py
import json
from analyzer import analyze_prompt
def imprimir_reporte(analysis, prompt_preview: str):
"""Imprime un reporte legible del análisis."""
a = analysis
print("=" * 60)
print(f"PROMPT ANALIZADO: {prompt_preview[:50]}...")
print("=" * 60)
print(f"\n📊 CALIDAD: {a.calidad.nivel.upper()} ({a.calidad.puntuacion}/100)")
print(f" {a.calidad.resumen}")
print(f"\n🏷️ TÉCNICA: {a.clasificacion.tecnica.upper()} (confianza: {a.clasificacion.confianza:.0%})")
print(f" {a.clasificacion.razon}")
print("\n🧩 COMPONENTES:")
for comp, estado in a.componentes.model_dump().items():
icon = "✅" if estado == "presente" else ("⚠️" if estado == "implicito" else "❌")
print(f" {icon} {comp}: {estado}")
print(f"\n🎯 CRISPE ({a.crispe.score}/6):")
for dim, estado in a.crispe.model_dump().items():
if dim == "score":
continue
icon = "✅" if estado == "presente" else ("⚠️" if estado == "implicito" else "❌")
print(f" {icon} {dim}: {estado}")
print("\n💡 SUGERENCIAS:")
for i, sug in enumerate(a.sugerencias, 1):
print(f" {i}. {sug}")
print(f"\n📏 METADATA:")
print(f" Caracteres: {a.metadata.longitud_caracteres}")
print(f" Tokens est.: {a.metadata.estimacion_tokens}")
print(f" System prompt: {'Sí' if a.metadata.tiene_system_prompt else 'No'}")
print()
# Casos de prueba con prompts de distintos niveles de calidad
PROMPTS_TEST = {
"casual": "Resume esto.",
"basico": "Resume el siguiente artículo en 3 puntos principales.",
"engineered": """Eres un editor técnico especializado en artículos de IA.
Resume el siguiente artículo en exactamente 3 puntos clave.
FORMATO:
1. [Punto en máximo 20 palabras]
2. [Punto en máximo 20 palabras]
3. [Punto en máximo 20 palabras]
Sin introducción ni conclusión.""",
"few_shot": """Clasifica el sentimiento del texto.
Ejemplos:
- "Me encantó el producto" → POSITIVO
- "Horrible experiencia" → NEGATIVO
- "Normal, sin destacar" → NEUTRO
Texto a clasificar: [INPUT]""",
"cot": """Resuelve este problema matemático paso a paso.
Piensa en voz alta mostrando cada paso del razonamiento.
Verifica tu respuesta al final.
Formato:
Paso 1: [razonamiento]
Paso 2: [razonamiento]
...
Respuesta final: [resultado]
Verificación: [comprobación]
Problema: [INPUT]""",
"crispe_completo": """## Capacity
Clasificar tickets de soporte en categorías predefinidas.
## Role
Eres un clasificador automático para el sistema de soporte técnico de empresa SaaS.
## Insight
Categorías:
- TECNICO: Errores de software, fallos de funcionalidad, rendimiento
- FACTURACION: Cobros, facturas, planes, reembolsos
- GENERAL: Preguntas de uso, features, onboarding
Prioridad: TECNICO > FACTURACION > GENERAL si hay ambigüedad.
## Statement
Clasifica el siguiente ticket en exactamente UNA categoría.
## Personality
- Solo el nombre de la categoría en mayúsculas
- Sin explicación ni puntuación
## Experiment
TECNICO → Para: "Error 404 al acceder al dashboard"
FACTURACION → Para: "Mi factura tiene un cargo incorrecto"
GENERAL → Para: "¿Cómo exporto mis datos?"
"""
}
if __name__ == "__main__":
print("🔍 PROMPT ANALYZER — Demo de casos\n")
for nombre, prompt in PROMPTS_TEST.items():
print(f"\n{'='*60}")
print(f"CASO: {nombre.upper()}")
try:
analysis = analyze_prompt(prompt)
imprimir_reporte(analysis, prompt)
except Exception as e:
print(f"Error analizando '{nombre}': {e}")
Paso 4: test_cases.py — Pruebas de clasificación
# test_cases.py
"""
Ejecuta el Prompt Analyzer con casos conocidos y verifica que clasifica correctamente.
No usa pytest — es validación manual.
"""
from analyzer import analyze_prompt
CASOS_CONOCIDOS = [
{
"nombre": "zero_shot_simple",
"prompt": "Traduce al inglés: 'El perro come'",
"tecnica_esperada": "zero-shot",
"calidad_min": 10,
"calidad_max": 50
},
{
"nombre": "few_shot_con_ejemplos",
"prompt": """Clasifica el email.
SPAM: "Has ganado un premio, haz click aquí"
LEGITIMO: "Tu factura de marzo está disponible"
SPAM: "Oferta exclusiva solo por hoy!!!"
Email a clasificar: "Reunión de equipo el lunes a las 10am"
""",
"tecnica_esperada": "few-shot",
"calidad_min": 40,
"calidad_max": 80
},
{
"nombre": "cot_explicito",
"prompt": """Resuelve paso a paso:
Piensa en cada paso antes de dar la respuesta final.
Muestra tu razonamiento completo.
Pregunta: Si una tienda tiene 120 productos y vende el 30%, ¿cuántos quedan?
""",
"tecnica_esperada": "chain-of-thought",
"calidad_min": 30,
"calidad_max": 70
}
]
def run_tests():
print("🧪 Ejecutando casos de prueba...\n")
resultados = []
for caso in CASOS_CONOCIDOS:
print(f"Caso: {caso['nombre']}")
analysis = analyze_prompt(caso['prompt'])
# Verificar técnica
tecnica_correcta = analysis.clasificacion.tecnica == caso['tecnica_esperada']
# Verificar calidad en rango
calidad_correcta = (
caso['calidad_min'] <= analysis.calidad.puntuacion <= caso['calidad_max']
)
estado = "✅ PASS" if (tecnica_correcta and calidad_correcta) else "❌ FAIL"
print(f" {estado}")
print(f" Técnica: {analysis.clasificacion.tecnica} (esperado: {caso['tecnica_esperada']}) {'✅' if tecnica_correcta else '❌'}")
print(f" Calidad: {analysis.calidad.puntuacion}/100 (rango: {caso['calidad_min']}-{caso['calidad_max']}) {'✅' if calidad_correcta else '❌'}")
print(f" CRISPE score: {analysis.crispe.score}/6\n")
resultados.append(tecnica_correcta and calidad_correcta)
total = len(resultados)
pasados = sum(resultados)
print(f"\n📊 RESULTADO FINAL: {pasados}/{total} casos pasaron")
return pasados == total
if __name__ == "__main__":
run_tests()
Criterios de Éxito
Al completar el proyecto, verifica que:
-
analyze_prompt("Resume esto.")devuelve técnicazero-shot, calidadcasual(<50 puntos), y al menos 2 sugerencias específicas con qué añadir -
analyze_promptcon un prompt que incluye ejemplos clasifica comofew-shot -
analyze_promptcon un prompt que pide razonamiento paso a paso clasifica comochain-of-thought - El CRISPE score del prompt CRISPE_completo del test es ≥5/6
- Todos los outputs validan con Pydantic sin errores (
PromptAnalysis(**data)) -
test_cases.pypasa los 3 casos conocidos - Las sugerencias son específicas: mencionan qué añadir exactamente, no "mejora el prompt"
- El código ejecuta sin errores con
python main.py
Ejemplo de Ejecución Completa
Input (prompt casual):
Resume este artículo en 3 puntos.
Output del Analyzer:
============================================================
PROMPT ANALIZADO: Resume este artículo en 3 puntos....
============================================================
📊 CALIDAD: BASICO (35/100)
Instrucción clara pero sin formato ni restricciones de output
🏷️ TÉCNICA: ZERO-SHOT (confianza: 95%)
Solo instrucción directa, sin ejemplos, sin razonamiento explícito
🧩 COMPONENTES:
✅ instruccion: presente
❌ contexto: ausente
⚠️ input: implicito
❌ output_format: ausente
🎯 CRISPE (1/6):
❌ capacity: implicito
❌ role: ausente
❌ insight: ausente
✅ statement: presente
❌ personality: ausente
❌ experiment: ausente
💡 SUGERENCIAS:
1. Falta output_format: añade 'Formato: 1. [punto] 2. [punto] 3. [punto]'
2. Falta Role (R): añade 'Eres un editor técnico' para calibrar vocabulario
3. Input implícito: añade 'Artículo: [texto]' para dejar claro qué procesar
4. Sin restricciones de longitud: añade 'máximo 20 palabras por punto'
📏 METADATA:
Caracteres: 35
Tokens est.: 10
System prompt: No
Extensiones Opcionales
Extensión 1: Análisis de batch
def analyze_batch(prompts: list[str]) -> list[dict]:
"""Analiza múltiples prompts y genera reporte comparativo."""
resultados = []
for prompt in prompts:
analysis = analyze_prompt(prompt)
resultados.append({
"preview": prompt[:50],
"tecnica": analysis.clasificacion.tecnica,
"calidad": analysis.calidad.puntuacion,
"crispe_score": analysis.crispe.score,
"sugerencias_count": len(analysis.sugerencias)
})
return resultados
# Uso
prompts = [p for p in PROMPTS_TEST.values()]
reporte = analyze_batch(prompts)
for r in reporte:
print(f"{r['calidad']:3d}/100 | CRISPE {r['crispe_score']}/6 | {r['tecnica']:18} | {r['preview']}")
Extensión 2: Detección de portabilidad multi-proveedor
def analizar_portabilidad(prompt: str) -> dict:
"""
Analiza si el prompt usa features no portables entre proveedores.
"""
issues = []
# Features específicas de OpenAI
if "response_format" in prompt:
issues.append("Usa 'response_format' (solo OpenAI). Para Anthropic: instrucción de JSON en texto")
# Features específicas de Anthropic
if "<output>" in prompt or "</output>" in prompt:
issues.append("XML tags personalizados — funcionan en todos pero son idiomáticos de Anthropic")
# Bien portable
if '"format":' in prompt.lower() or "json" in prompt.lower():
pass # JSON en texto es portable
return {
"es_portable": len(issues) == 0,
"issues": issues,
"recomendacion": "Portable" if not issues else f"Ajustar: {'; '.join(issues)}"
}
Extensión 3: Comparar versiones de un prompt
def comparar_versiones(prompt_v1: str, prompt_v2: str) -> dict:
"""Compara dos versiones del mismo prompt y muestra qué mejoró."""
a1 = analyze_prompt(prompt_v1)
a2 = analyze_prompt(prompt_v2)
return {
"calidad_delta": a2.calidad.puntuacion - a1.calidad.puntuacion,
"crispe_delta": a2.crispe.score - a1.crispe.score,
"tecnica_cambio": f"{a1.clasificacion.tecnica} → {a2.clasificacion.tecnica}",
"mejoras": [
s for s in a1.sugerencias
if not any(comp in str(a2.componentes) for comp in ["presente"])
]
}
Troubleshooting
Problema 1: El modelo no devuelve JSON válido
Causa: A veces incluye texto antes del JSON ("Aquí está el análisis:") o usa json ... .
Solución: La función limpiar_json() ya maneja esto. Si aún falla:
# Añadir al system prompt
ANALYZER_SYSTEM += """
CRÍTICO: Tu respuesta debe empezar DIRECTAMENTE con { y terminar con }.
Sin "```json", sin texto introductorio, sin explicaciones.
"""
Problema 2: Sugerencias genéricas ("mejora el prompt")
Causa: El system prompt no especifica con suficiente claridad qué es una sugerencia específica.
Solución: Añadir más ejemplos negativos al system prompt:
# Añadir al ANALYZER_SYSTEM
"""
SUGERENCIAS: Deben mencionar QUÉ AÑADIR EXACTAMENTE con un ejemplo de texto a agregar.
- ❌ "Añade más contexto"
- ✅ "Falta Insight: añade 'Prioridades: urgente > normal > bajo' para guiar la clasificación"
- ❌ "Especifica el formato"
- ✅ "Falta Experiment: añade 'Responde en JSON: {\"categoria\": \"TECNICO|FACTURACION|GENERAL\"}'"
"""
Problema 3: Classificación incorrecta few-shot vs zero-shot
Causa: El modelo confunde ejemplos en el contexto con few-shot real.
Solución: Añadir definición más precisa al system prompt:
"""
CRÍTICO para clasificación:
- zero-shot: SOLO instrucción. Sin ejemplos input→output antes de la tarea.
- few-shot: Prompt INCLUYE ejemplos en formato "Input: X → Output: Y" o similar, ANTES de pedir la clasificación del caso actual.
- chain-of-thought: Prompt INCLUYE o SOLICITA explícitamente razonamiento paso a paso ("piensa paso a paso", "muestra tu razonamiento", etc.)
"""
Problema 4: Error de validación Pydantic
Causa: El JSON del modelo no cumple con los tipos del schema (ej: confianza fuera de 0-1, score fuera de 0-6).
Solución: Añadir sanitización antes de validar:
def sanitizar_data(data: dict) -> dict:
"""Corrige valores fuera de rango antes de validar con Pydantic."""
if "clasificacion" in data:
conf = data["clasificacion"].get("confianza", 0.5)
data["clasificacion"]["confianza"] = max(0.0, min(1.0, float(conf)))
if "crispe" in data:
score = data["crispe"].get("score", 0)
data["crispe"]["score"] = max(0, min(6, int(score)))
if "calidad" in data:
pts = data["calidad"].get("puntuacion", 0)
data["calidad"]["puntuacion"] = max(0, min(100, int(pts)))
return data
# En analyze_prompt():
data = json.loads(json_str)
data = sanitizar_data(data)
return PromptAnalysis(**data)
Problema 5: tiktoken no reconoce el modelo
Causa: Si usas un modelo nuevo que tiktoken aún no tiene en su registro.
Solución: La función estimar_tokens ya tiene fallback. Si quieres más precisión:
def estimar_tokens(texto: str, model: str = "gpt-4o-mini") -> int:
try:
encoding = tiktoken.encoding_for_model(model)
return len(encoding.encode(texto))
except KeyError:
# Fallback: usar codificación cl100k_base (GPT-4 family)
encoding = tiktoken.get_encoding("cl100k_base")
return len(encoding.encode(texto))
Resumen del Proyecto
En este proyecto construiste:
- Schema Pydantic completo con validación de tipos, rangos, y literales — base para todo output estructurado de la guía
- Sistema de análisis con LLM que usa un model bien instruido para analizar otro prompt — técnica de LLM-as-judge que verás en detalle en el Módulo 7
- Parser robusto de JSON que maneja los formatos variables que los modelos pueden devolver
- Framework de casos de prueba sin pytest, pero con verificación sistemática de resultados esperados
- Detectores heurísticos (system prompt, portabilidad) que complementan el análisis del LLM con lógica determinística
Cómo usarás este Analyzer en los módulos siguientes:
- Módulo 2: Verificar que tus prompts few-shot tienen el formato correcto en "experiment"
- Módulo 4: Verificar que tu CoT tiene "statement" con instrucción de razonamiento paso a paso
- Módulo 6: Verificar que cada prompt en una cadena tiene sus componentes bien definidos
- Módulo 7: El Prompt Analyzer es un precursor del evaluation framework con LLM-as-judge
Recursos adicionales
- OpenAI Structured Outputs — JSON mode y JSON Schema en OpenAI, base para el
response_formatque usas en el extractor - Pydantic v2 Validators — Validators avanzados para el schema del Analyzer (útil si añades más validaciones)
- tiktoken GitHub — Librería para contar tokens exactamente; crucial para optimizar costos en producción
- OpenAI Evals — Framework para evaluar prompts a escala; lo que construiste manualmente aquí, pero con automatización
- Pydantic BaseModel — Documentación de BaseModel con todos los field types y validadores disponibles