Mejores Prácticas de Ingeniería de Prompts para LLMs en Producción
Resumen
La ingeniería de prompts en producción requiere formatos estructurados, ejemplos few-shot, restricciones explícitas y evaluación continua. Avanza más allá de prompts ad-hoc hacia pipelines sistemáticos con control de versiones y pruebas A/B.
La ingeniería de prompts ha evolucionado de una curiosidad a una disciplina de ingeniería crítica. En sistemas de producción, la diferencia entre un buen y mal prompt puede significar la diferencia entre 95% de precisión y 60% de precisión. Esta guía comparte patrones prácticos que funcionan.
La Mentalidad de Producción
La mayoría de tutoriales de ingeniería de prompts se enfocan en escenarios de chat interactivo. La producción es diferente. Necesitas:
- Consistencia: La misma entrada debe producir salidas similares
- Medibilidad: Debes poder evaluar la calidad a escala
- Mantenibilidad: Los prompts evolucionan y necesitan control de versiones
Insight Clave
Trata los prompts como código. Necesitan control de versiones, pruebas, documentación y procesos de revisión como cualquier otro artefacto de producción.
Formatos de Salida Estructurados
Los sistemas de producción necesitan salidas parseables. Siempre especifica el formato exacto que esperas.
Modo JSON
SYSTEM_PROMPT = """Eres un asistente de extracción de datos médicos.
Extrae la siguiente información de la nota clínica y devuelve SOLO JSON válido.
Campos requeridos:
- patient_age: entero o null
- chief_complaint: string
- medications: array de strings
- allergies: array de strings
Ejemplo de salida:
{
"patient_age": 45,
"chief_complaint": "dolor de pecho",
"medications": ["aspirina", "lisinopril"],
"allergies": ["penicilina"]
}
"""Capa de Validación
Nunca confíes ciegamente en las salidas del LLM:
from pydantic import BaseModel, validator
from typing import Optional
class ClinicalExtraction(BaseModel):
patient_age: Optional[int]
chief_complaint: str
medications: list[str]
allergies: list[str]
@validator('patient_age')
def age_must_be_reasonable(cls, v):
if v is not None and (v < 0 or v > 150):
raise ValueError('La edad debe estar entre 0 y 150')
return v
def extract_clinical_data(note: str) -> ClinicalExtraction:
response = llm.invoke(SYSTEM_PROMPT, note)
return ClinicalExtraction.model_validate_json(response)Patrones de Aprendizaje Few-Shot
La investigación de Brown et al. (2020) demostró que el prompting few-shot mejora dramáticamente el rendimiento en tareas. Así es como implementarlo efectivamente:
Selección Dinámica de Ejemplos
from sentence_transformers import SentenceTransformer
import numpy as np
class FewShotSelector:
def __init__(self, examples: list[dict]):
self.examples = examples
self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
self.embeddings = self.encoder.encode([e['input'] for e in examples])
def select_examples(self, query: str, k: int = 3) -> list[dict]:
"""Selecciona los k ejemplos más similares a la consulta."""
query_embedding = self.encoder.encode(query)
similarities = np.dot(self.embeddings, query_embedding)
top_indices = np.argsort(similarities)[-k:][::-1]
return [self.examples[i] for i in top_indices]Error Común
Los ejemplos few-shot estáticos pueden desorientar al modelo cuando la consulta es diferente. Siempre usa similitud semántica para seleccionar ejemplos relevantes dinámicamente.
Prompting de Cadena de Pensamiento
Wei et al. (2022) mostraron que pedir a los modelos que muestren su razonamiento mejora significativamente la precisión en tareas complejas:
REASONING_PROMPT = """
Analiza el siguiente ticket de soporte al cliente y determina la acción apropiada.
Piensa en esto paso a paso:
1. Primero, identifica el problema central del cliente
2. Luego, evalúa el nivel de urgencia (bajo, medio, alto, crítico)
3. Después, determina el departamento apropiado
4. Finalmente, sugiere la respuesta inicial
Ticket: {ticket_text}
Déjame trabajar esto paso a paso:
"""Cadena de Pensamiento Estructurada
Para producción, captura el razonamiento en un formato estructurado:
class TicketAnalysis(BaseModel):
reasoning_steps: list[str]
core_issue: str
urgency: Literal["bajo", "medio", "alto", "crítico"]
department: str
suggested_response: str
COT_PROMPT = """
Analiza el ticket y devuelve tu análisis como JSON.
{
"reasoning_steps": ["Paso 1: ...", "Paso 2: ...", ...],
"core_issue": "...",
"urgency": "bajo|medio|alto|crítico",
"department": "...",
"suggested_response": "..."
}
"""Plantillas de Prompts y Control de Versiones
Mantén los prompts en un formato estructurado y versionado:
# prompts/clinical_extraction_v2.yaml
name: clinical_extraction
version: "2.1.0"
description: "Extraer datos estructurados de notas clínicas"
model: gpt-4-turbo
temperature: 0
max_tokens: 1000
system_prompt: |
Eres un asistente de extracción de datos médicos especializado en...
user_template: |
Extrae información de la siguiente nota clínica:
{note_content}
Devuelve solo JSON válido que coincida con el esquema.
schema:
type: object
required: [patient_age, chief_complaint]
properties:
patient_age:
type: integer
minimum: 0
maximum: 150Evaluación y Optimización
Construyendo Suites de Pruebas
class PromptTestSuite:
def __init__(self, prompt_template: str):
self.prompt = prompt_template
self.test_cases = []
def add_test(self, input_data: dict, expected: dict, tags: list[str] = []):
self.test_cases.append({
"input": input_data,
"expected": expected,
"tags": tags
})
def run_evaluation(self) -> dict:
results = []
for case in self.test_cases:
response = self.invoke(case["input"])
score = self.evaluate(response, case["expected"])
results.append({"case": case, "response": response, "score": score})
return {
"total": len(results),
"passed": sum(1 for r in results if r["score"] >= 0.9),
"average_score": sum(r["score"] for r in results) / len(results),
"by_tag": self._aggregate_by_tag(results)
}Pruebas A/B de Prompts
class PromptABTest:
def __init__(self, prompt_a: str, prompt_b: str, split: float = 0.5):
self.prompts = {"A": prompt_a, "B": prompt_b}
self.split = split
self.results = {"A": [], "B": []}
def get_prompt(self, request_id: str) -> tuple[str, str]:
"""Asignación determinista basada en ID de solicitud."""
variant = "A" if hash(request_id) % 100 < self.split * 100 else "B"
return variant, self.prompts[variant]
def record_result(self, variant: str, success: bool, latency: float):
self.results[variant].append({"success": success, "latency": latency})Guardarraíles de Producción
Validación de Entrada
def validate_input(text: str, max_tokens: int = 4000) -> str:
"""Sanitiza y valida la entrada antes de enviar al LLM."""
# Eliminar intentos potenciales de inyección
text = re.sub(r'<\|.*?\|>', '', text)
# Truncar si es muy largo
tokens = tokenizer.encode(text)
if len(tokens) > max_tokens:
text = tokenizer.decode(tokens[:max_tokens])
return textGuardarraíles de Salida
class OutputGuardrails:
def __init__(self):
self.sensitive_patterns = [
r'\b\d{3}-\d{2}-\d{4}\b', # SSN
r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', # Email
]
def check(self, output: str) -> tuple[bool, list[str]]:
issues = []
for pattern in self.sensitive_patterns:
if re.search(pattern, output):
issues.append(f"Patrón de datos sensibles detectado: {pattern}")
return len(issues) == 0, issuesOptimización de Costos
Los costos de LLM en producción pueden escalar rápidamente. Implementa estas estrategias:
Capa de Caché
from functools import lru_cache
import hashlib
class PromptCache:
def __init__(self, redis_client):
self.redis = redis_client
self.ttl = 3600 # 1 hora
def get_cache_key(self, prompt: str, params: dict) -> str:
content = f"{prompt}:{json.dumps(params, sort_keys=True)}"
return hashlib.sha256(content.encode()).hexdigest()
async def get_or_compute(self, prompt: str, params: dict, compute_fn):
key = self.get_cache_key(prompt, params)
cached = await self.redis.get(key)
if cached:
return json.loads(cached)
result = await compute_fn(prompt, params)
await self.redis.setex(key, self.ttl, json.dumps(result))
return resultMonitoreo y Observabilidad
Rastrea estas métricas para prompts en producción:
class PromptMetrics:
def record(self, prompt_id: str, response: dict, metadata: dict):
metrics = {
"prompt_id": prompt_id,
"timestamp": datetime.utcnow(),
"latency_ms": metadata["latency_ms"],
"input_tokens": metadata["input_tokens"],
"output_tokens": metadata["output_tokens"],
"cost_usd": self.calculate_cost(metadata),
"success": metadata.get("success", True),
"model": metadata["model"]
}
self.emit(metrics)Conclusión
La ingeniería de prompts en producción requiere tratar los prompts como artefactos de ingeniería de primera clase. Puntos clave:
- Estructura tus salidas - Usa esquemas JSON y validación
- Versiona los prompts - Rastrea cambios como código
- Construye suites de evaluación - Mide la calidad sistemáticamente
- Implementa guardarraíles - Valida entradas y salidas
- Optimiza costos - Cachea y minimiza tokens
- Monitorea todo - Rastrea latencia, costo y calidad
El campo continúa evolucionando rápidamente. Mantente actualizado con la investigación y evalúa continuamente nuevas técnicas contra tus líneas base de producción.
Referencias
Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J., Dhariwal, P., Neelakantan, A., Shyam, P., Sastry, G., Askell, A., Agarwal, S., Herbert-Voss, A., Krueger, G., Henighan, T., Child, R., Ramesh, A., Ziegler, D. M., Wu, J., Winter, C., ... Amodei, D. (2020). Language models are few-shot learners. Advances in Neural Information Processing Systems, 33, 1877-1901. https://arxiv.org/abs/2005.14165
Wei, J., Wang, X., Schuurmans, D., Bosma, M., Ichter, B., Xia, F., Chi, E., Le, Q., & Zhou, D. (2022). Chain-of-thought prompting elicits reasoning in large language models. Advances in Neural Information Processing Systems, 35, 24824-24837. https://arxiv.org/abs/2201.11903
OpenAI. (2024). GPT-4 technical report. https://openai.com/research/gpt-4
Anthropic. (2024). Claude model card and prompt engineering guide. https://docs.anthropic.com/claude/docs/prompt-engineering
¿Quieres discutir estrategias de ingeniería de prompts? Contáctame o explora mis proyectos de ingeniería de IA.
Frequently Asked Questions
Osvaldo Restrepo
Senior Full Stack AI & Software Engineer. Building production AI systems that solve real problems.