Inicio / LLMOps / LLMOps: De Prototipo a Producción / Casos de Uso en Producción

Casos de Uso en Producción

Chatbots RAG, búsqueda semántica, extracción de datos y asistentes.

Avanzado
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Casos de Uso en Producción

Mapa de Casos de Uso

┌─────────────────────────────────────────────────┐
│         CASOS DE USO LLM EN PRODUCCIÓN          │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │
│  │ Chatbot  │  │ Búsqueda │  │ Generación   │  │
│  │ Soporte  │  │ Semántica│  │ de Contenido │  │
│  └──────────┘  └──────────┘  └──────────────┘  │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │
│  │ Resumen  │  │Extracción│  │  Asistente   │  │
│  │Documentos│  │ de Datos │  │  de Código   │  │
│  └──────────┘  └──────────┘  └──────────────┘  │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │
│  │Traducción│  │Clasificac│  │ Agente       │  │
│  │ Técnica  │  │ ión      │  │ Autónomo     │  │
│  └──────────┘  └──────────┘  └──────────────┘  │
└─────────────────────────────────────────────────┘

Chatbot de Soporte con RAG

from openai import OpenAI
import chromadb

client = OpenAI()
chroma = chromadb.HttpClient()
collection = chroma.get_collection("knowledge_base")

class SupportChatbot:
    def __init__(self):
        self.system_prompt = """Eres un agente de soporte técnico.
Responde SOLO basándote en el contexto proporcionado.
Si no encuentras la respuesta en el contexto, di:
"No tengo información sobre eso. ¿Puedo conectarte con un agente humano?"

Formato:
- Sé conciso y directo
- Usa listas para pasos
- Incluye enlaces relevantes del contexto"""
    
    def get_context(self, query: str, n_results: int = 5) -> str:
        results = collection.query(
            query_texts=[query],
            n_results=n_results,
        )
        
        context_parts = []
        for doc, metadata in zip(results["documents"][0], results["metadatas"][0]):
            source = metadata.get("source", "desconocido")
            context_parts.append(f"[Fuente: {source}]\n{doc}")
        
        return "\n\n---\n\n".join(context_parts)
    
    def chat(self, user_message: str, history: list = None) -> dict:
        context = self.get_context(user_message)
        
        messages = [{"role": "system", "content": self.system_prompt}]
        
        # Añadir historial (últimos 5 turnos)
        if history:
            messages.extend(history[-10:])
        
        messages.append({
            "role": "user",
            "content": f"""Contexto relevante:
{context}

Pregunta del usuario: {user_message}"""
        })
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            temperature=0.3,
            max_tokens=500,
        )
        
        answer = response.choices[0].message.content
        
        # Detectar si necesita escalación
        needs_human = any(phrase in answer.lower() for phrase in [
            "agente humano", "no tengo información", "no puedo ayudar"
        ])
        
        return {
            "answer": answer,
            "needs_escalation": needs_human,
            "sources": [m.get("source") for m in results["metadatas"][0]],
            "tokens_used": response.usage.total_tokens,
        }

Búsqueda Semántica

class SemanticSearch:
    """Motor de búsqueda semántica sobre documentos."""
    
    def __init__(self):
        self.collection = chroma.get_or_create_collection(
            "documents",
            metadata={"hnsw:space": "cosine"},
        )
    
    def index_documents(self, documents: list[dict]):
        """Indexar documentos con chunking."""
        for doc in documents:
            chunks = self.chunk_document(doc["content"])
            
            for i, chunk in enumerate(chunks):
                self.collection.add(
                    documents=[chunk],
                    ids=[f"{doc['id']}_chunk_{i}"],
                    metadatas=[{
                        "doc_id": doc["id"],
                        "title": doc["title"],
                        "chunk_index": i,
                        "total_chunks": len(chunks),
                    }],
                )
    
    def search(self, query: str, n_results: int = 10) -> list:
        results = self.collection.query(
            query_texts=[query],
            n_results=n_results,
        )
        
        # Agrupar por documento y re-rankear
        doc_scores = {}
        for doc, metadata, distance in zip(
            results["documents"][0],
            results["metadatas"][0],
            results["distances"][0],
        ):
            doc_id = metadata["doc_id"]
            if doc_id not in doc_scores:
                doc_scores[doc_id] = {
                    "title": metadata["title"],
                    "best_chunk": doc,
                    "score": 1 - distance,
                    "chunks": [],
                }
            doc_scores[doc_id]["chunks"].append(doc)
            doc_scores[doc_id]["score"] = max(
                doc_scores[doc_id]["score"], 1 - distance
            )
        
        # Re-rankear con LLM
        ranked = self.rerank_with_llm(query, doc_scores)
        return ranked
    
    def rerank_with_llm(self, query: str, candidates: dict) -> list:
        prompt = f"Consulta: {query}\n\nDocumentos:\n"
        for i, (doc_id, info) in enumerate(candidates.items()):
            prompt += f"{i+1}. [{info['title']}]: {info['best_chunk'][:200]}\n"
        prompt += "\nOrdena del más al menos relevante (solo números):"
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            max_tokens=50,
        )
        
        return list(candidates.values())
    
    def chunk_document(self, text: str, chunk_size: int = 500, overlap: int = 50) -> list:
        words = text.split()
        chunks = []
        for i in range(0, len(words), chunk_size - overlap):
            chunk = " ".join(words[i:i + chunk_size])
            if chunk:
                chunks.append(chunk)
        return chunks

Extracción Estructurada de Datos

from pydantic import BaseModel, Field

class Invoice(BaseModel):
    invoice_number: str = Field(description="Número de factura")
    date: str = Field(description="Fecha de emisión (YYYY-MM-DD)")
    vendor: str = Field(description="Nombre del proveedor")
    total: float = Field(description="Monto total")
    currency: str = Field(description="Moneda (USD, EUR, MXN)")
    line_items: list[dict] = Field(description="Lista de items")

class DataExtractor:
    def extract_invoice(self, text: str) -> Invoice:
        response = client.beta.chat.completions.parse(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": "Extrae datos de facturas."},
                {"role": "user", "content": f"Extrae los datos:\n\n{text}"},
            ],
            response_format=Invoice,
        )
        return response.choices[0].message.parsed
    
    def extract_batch(self, documents: list[str]) -> list[Invoice]:
        results = []
        for doc in documents:
            try:
                invoice = self.extract_invoice(doc)
                results.append({"status": "ok", "data": invoice})
            except Exception as e:
                results.append({"status": "error", "error": str(e)})
        return results

# Uso
extractor = DataExtractor()
invoice = extractor.extract_invoice("""
FACTURA #INV-2024-001
Fecha: 15 de Enero, 2024
Proveedor: Tech Solutions SA
  
1x Licencia Software: $500.00
2x Consultoría (hora): $300.00

Total: $800.00 USD
""")
print(invoice.model_dump_json(indent=2))

Generación de Código

class CodeAssistant:
    SYSTEM = """Eres un asistente de programación experto.
Genera código limpio, documentado y con manejo de errores.
Sigue las mejores prácticas del lenguaje especificado."""
    
    def generate(self, description: str, language: str = "python") -> dict:
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": self.SYSTEM},
                {"role": "user", "content": f"""
Lenguaje: {language}
Requerimiento: {description}

Responde con:
1. Código completo
2. Explicación breve
3. Tests unitarios
"""}
            ],
            temperature=0.2,
        )
        return {"code": response.choices[0].message.content}
    
    def review(self, code: str) -> dict:
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": "Revisa este código. Identifica bugs, mejoras de rendimiento, y problemas de seguridad."},
                {"role": "user", "content": code},
            ],
            temperature=0.3,
        )
        return {"review": response.choices[0].message.content}
    
    def explain(self, code: str) -> str:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": "Explica este código de forma clara y concisa."},
                {"role": "user", "content": code},
            ],
            temperature=0.3,
        )
        return response.choices[0].message.content

Resumen de Documentos

class DocumentSummarizer:
    """Resumir documentos largos con estrategia map-reduce."""
    
    def summarize(self, text: str, max_length: int = 500) -> str:
        # Si cabe en contexto, resumir directo
        if len(text.split()) < 3000:
            return self._direct_summary(text, max_length)
        
        # Map-reduce para documentos largos
        return self._map_reduce_summary(text, max_length)
    
    def _direct_summary(self, text: str, max_length: int) -> str:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": f"Resume en máximo {max_length} palabras. Mantén los puntos clave."},
                {"role": "user", "content": text},
            ],
            temperature=0.3,
        )
        return response.choices[0].message.content
    
    def _map_reduce_summary(self, text: str, max_length: int) -> str:
        # Map: resumir cada chunk
        chunks = self._chunk_text(text, chunk_size=2000)
        chunk_summaries = []
        
        for chunk in chunks:
            summary = self._direct_summary(chunk, max_length=200)
            chunk_summaries.append(summary)
        
        # Reduce: combinar resúmenes
        combined = "\n\n".join(chunk_summaries)
        return self._direct_summary(combined, max_length)
    
    def _chunk_text(self, text: str, chunk_size: int = 2000) -> list:
        words = text.split()
        return [
            " ".join(words[i:i + chunk_size])
            for i in range(0, len(words), chunk_size)
        ]

# Uso
summarizer = DocumentSummarizer()
resumen = summarizer.summarize(documento_largo)

Patrones Comunes en Producción

# Patrón: Clasificador + Especialista
class SmartRouter:
    """Clasifica la intención y rutea al handler apropiado."""
    
    HANDLERS = {
        "soporte_tecnico": SupportChatbot(),
        "ventas": SalesAgent(),
        "facturacion": BillingAssistant(),
        "general": GeneralAssistant(),
    }
    
    def classify(self, message: str) -> str:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{
                "role": "user",
                "content": f"Clasifica esta consulta: '{message}'\nCategorías: {list(self.HANDLERS.keys())}\nResponde SOLO la categoría."
            }],
            temperature=0,
            max_tokens=20,
        )
        category = response.choices[0].message.content.strip().lower()
        return category if category in self.HANDLERS else "general"
    
    def handle(self, message: str) -> str:
        category = self.classify(message)
        handler = self.HANDLERS[category]
        return handler.process(message)

Resumen

Los casos de uso más comunes de LLMs en producción incluyen: chatbots con RAG para soporte, búsqueda semántica, extracción estructurada de datos, generación y revisión de código, resumen de documentos, y clasificación inteligente. Cada caso requiere patrones específicos de prompt engineering, manejo de errores, y evaluación continua.

🔒

Ejercicio práctico disponible

Router inteligente de intenciones

Desbloquear ejercicios
// Router inteligente de intenciones
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes