Edge AI Agents: Cómo reemplazar tu CMS tradicional con un agente autónomo (sin quemar tu presupuesto ni tu SEO)

¿Cuánto pagas al mes por mantener un CMS tradicional que 90% del tiempo solo sirve HTML estático? Los Edge AI Agents prometen eliminar esa infraestructura pesada, generando contenido personalizado directamente en el edge, cerca del usuario y sin servidores que administrar. Pero antes de migrar tu blog corporativo o tu sitio de documentación, necesitas saber dónde está el truco: esta arquitectura no es gratis ilimitado, no es determinista, y definitivamente no sirve para todo.

La arquitectura real: cómo funciona sin mentirte sobre la latencia

Un Edge AI Agent en Cloudflare (la opción más madura actualmente) combina tres piezas: Workers (cómputo edge), Vectorize (base de datos vectorial) y Workers AI (inferencia de modelos locales). La idea es simple: en lugar de consultar una API de CMS, tu Worker recibe la petición, recupera contexto relevante de Vectorize mediante búsqueda semántica, y genera la respuesta HTML usando un modelo como Llama 3.2 3B directamente en la red de Cloudflare.

Pero hablemos claro sobre los números. Generar una respuesta completa con retrieval incluido no te dará un TTFB (Time to First Byte) de menos de 100ms. Estás hablando de 200ms a 800ms dependiendo de la complejidad del prompt y el tamaño del contexto. Esto es aceptable para contenido dinámico personalizado, pero mortal si pretendes reemplazar la home de un e-commerce con esto.

Otro error común: no puedes usar SQLite-vec dentro de un Worker. Los Workers de Cloudflare corren en V8 sin filesystem persistente. Para el vector search debes usar Vectorize nativo (que sí persiste índices en la red edge) o hacer llamadas a una API externa. Si ves ejemplos con Python y SQLite local, eso es solo para prototipar en tu laptop, no para producción edge.

Cálculo de costos: el plan gratuito tiene techo (y bajo)

La documentación de Cloudflare suena generosa, pero los límites del plan gratuito son estrictos:

  • Workers AI: 100,000 tokens de LLM por día (no 1 millón). Un artículo promedio de 800 palabras consume ~1,200 tokens entre prompt y completación. Haces 80 publicaciones al día y se acabó.
  • Vectorize: 1 millón de consultas vectoriales al día en el tier gratuito (suficiente para MVP, pero no para tráfico viral).
  • KV: Operaciones de lectura ilimitadas, pero escrituras con consistencia eventual (hasta 60 segundos de propagación global). No sirve para contenido que «cambia cada minuto» ni para contadores en tiempo real.
  • En producción real, los números cambian:

  • Workers AI (pago): ~$0.011 por 1,000 tokens para Llama 3.2 3B.
  • Un sitio con 10,000 visitas diarias donde cada página genera 1,500 tokens = 15M tokens/día = ~$165 mensuales solo en inferencia.
  • Esto sin contar el costo oculto: desarrollo, mantenimiento de prompts, debugging de alucinaciones a las 3 AM, y la curva de aprendizaje de una arquitectura eventualmente consistente.

    El problema del SEO y las alucinaciones

    Aquí está el riesgo que pocos mencionan: el contenido generado por LLM en cada request es no-determinista. Google puede indexar tu página sobre «mejores frameworks JavaScript» hoy y mañana, para la misma URL, el agente genera contenido diferente o peor: información contradictoria. Esto crea riesgo de contenido duplicado, cannibalización de keywords y penalizaciones por «thin content» cambiante.

    Estrategias de mitigación obligatorias:

  • Temperatura 0: Fuerza al modelo a elegir siempre el token más probable, reduciendo variabilidad.
  • Few-shot prompting: Incluye ejemplos exactos del formato de salida deseado en el system prompt.
  • Caché agresiva: Usa Cloudflare Cache API para almacenar el HTML generado durante horas (o días) si el contenido no es personalizado por usuario.
  • SSG híbrido: Genera artículos base estáticamente (usando el agente en build time) y reserva el edge AI solo para personalización de componentes (recomendaciones, resúmenes contextuales).
  • // Ejemplo: Worker con mitigación de alucinaciones y caché
    export default {
      async fetch(request, env) {
        const cache = caches.default;
        const cached = await cache.match(request);
        if (cached) return cached;
        // Retrieval desde Vectorize (NO sqlite-vec)
        const query = new URL(request.url).searchParams.get('q');
        const vectors = await env.VECTORIZE_INDEX.query(
          await env.AI.run('@cf/baai/bge-base-en-v1.5', { text: query }),
          { topK: 3 }
        );
        // Generación con temperatura 0 y few-shot
        const response = await env.AI.run('@cf/meta/llama-3.2-3b-instruct', {
          messages: [
            { role: 'system', content: 'Eres un editor técnico. Responde SOLO con HTML válido. Ejemplo: <article><h1>...</h1></article>' },
            { role: 'user', content: `Contexto: ${JSON.stringify(vectors)}. Pregunta: ${query}` }
          ],
          temperature: 0.0, // Determinismo máximo
          max_tokens: 1000
        });
        const html = response.response;
        // Cachear por 1 hora para evitar regeneración costosa
        const resp = new Response(html, { headers: { 'Content-Type': 'text/html', 'Cache-Control': 'public, max-age=3600' } });
        cache.put(request, resp.clone());
        return resp;
      }
    };
    

    Limitaciones críticas: cuando NO usar Edge AI Agents

    No todo se soluciona con magia edge. Evita esta arquitectura si:

  • E-commerce crítico: Un error de alucinación en el precio o disponibilidad de stock te expone a demandas. Los CMS tradicionales tienen auditoría de contenido; un LLM generativo no garantiza consistencia legal.
  • Alto volumen de escritura concurrente: KV no soporta transacciones ACID. Si dos usuarios editan «el último post» simultáneamente, perderás datos.
  • Requisitos de auditoría: ¿Necesitas saber exactamente qué texto mostraste a un usuario específico el 15 de marzo a las 3 PM? La generación dinámica dificulta el versionado forense.
  • SEO puro: Si tu negocio depende del ranking orgánico, el riesgo de variabilidad semántica no vale la pena frente a HTML estático pre-renderizado.

Conclusión: herramienta, no religión

Los Edge AI Agents son extraordinarios para personalización de contenido existente (adaptar un artículo técnico al nivel del lector, generar resúmenes contextuales, responder preguntas sobre documentación). Son terribles como reemplazo 1:1 de un CMS editorial tradicional donde el control, la auditoría y el SEO determinista son requisitos.

Mi recomendación práctica: usa esta arquitectura para el 10% de tu sitio que necesita verdadera personalización inteligente, manteniendo el 90% como contenido estático generado en build time. Así pagas centavos por la magia, no miles de dólares por alucinaciones en producción.

¿Listo para experimentar? Empieza con un proyecto paralelo de bajo riesgo (un FAQ interno o un recomendador de contenido), mide tus costos reales durante 30 días, y valida que Google indexa correctamente antes de tocar tu sitio principal. La infraestructura edge es poderosa, pero solo si conoces exactamente dónde están sus límites.

¿Has intentado migrar un CMS tradicional a IA generativa? Cuéntame en los comentarios qué rompió primero: el presupuesto o el SEO.

Model Context Protocol (MCP): Estandarización de herramientas para agentes de IA en 2025

El principal cuello de botella en arquitecturas de agentes de IA no es la capacidad del modelo, sino la integración con sistemas externos. Cada nueva herramienta—desde una base de datos hasta una API interna—requiere código específico que acopla al agente con la implementación concreta. Model Context Protocol (MCP), propuesto por Anthropic en noviembre de 2024 y actualmente en evolución constante, busca eliminar esa fricción mediante un protocolo abierto que estandariza cómo los agentes descubren y ejecutan herramientas externas. No se trata de un estándar formal ratificado por organismos como IETF o W3C, sino de un proyecto open source (licencia MIT) que ofrece una capa de abstracción práctica y está ganando tracción rápida en el ecosistema de desarrollo de agentes.

El problema de la integración ad-hoc

Antes de MCP, integrar un agente con herramientas externas implicaba escribir código específico por cada servicio. Si tu agente necesitaba consultar PostgreSQL, interactuar con Slack y leer archivos de S3, debías implementar tres adaptadores distintos, manejar autenticaciones particulares y mapear manualmente los esquemas de entrada/salida. Este enfoque genera:

  • Acoplamiento excesivo: Cambios en la API de la herramienta obligan a modificar la lógica del agente.
  • Duplicación de esfuerzos: Cada equipo reimplementa conectores similares.
  • Dificultad para escalar: Añadir una nueva capacidad requiere tocar el código core del agente.
  • MCP aborda esto mediante un modelo cliente-servidor donde las herramientas se exponen como servicios independientes que cualquier agente con capacidad MCP puede consumir, siempre y cuando implemente la capa cliente correspondiente.

    Arquitectura y componentes clave

    El protocolo define tres elementos fundamentales que operan sobre JSON-RPC 2.0:

    1. Servidores MCP

    Son procesos independientes que exponen recursos (datos contextuales) y herramientas (funciones ejecutables). Un servidor PostgreSQL, por ejemplo, expone operaciones de consulta sin revelar detalles de conexión al agente.

    2. Clientes MCP

    Implementaciones integradas en el agente o en el framework que orquesta la ejecución (como LangChain o la SDK de Anthropic). El cliente gestiona el descubrimiento de herramientas, el paso de parámetros y la serialización de resultados.

    3. Capa de transporte

    Actualmente soporta:

  • stdio: Comunicación via streams estándar (ideal para servidores locales).
  • SSE (Server-Sent Events): Para servidores remotos sobre HTTP.
  • Esta separación permite que un mismo servidor MCP sirva a múltiples agentes, y que un agente consuma múltiples servidores sin código específico por integración.

    Implementación práctica: un servidor PostgreSQL

    A continuación, un servidor MCP funcional que expone consultas a PostgreSQL. Nota importante: utilizamos asyncpg para mantener la compatibilidad asíncrona con el loop de eventos de MCP, evitando el bloqueo que causaría psycopg2.

    # server.py
    import asyncpg
    import asyncio
    from mcp.server import Server
    from mcp.types import Tool, TextContent
    
    app = Server("postgres-server")
    
    @app.list_tools()
    async def list_tools() -> list[Tool]:
        return [
            Tool(
                name="query_database",
                description="Ejecuta consultas SELECT seguras",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "sql": {"type": "string", "description": "Consulta SQL"}
                    },
                    "required": ["sql"]
                }
            )
        ]
    
    @app.call_tool()
    async def call_tool(name: str, arguments: dict) -> list[TextContent]:
        if name != "query_database":
            raise ValueError(f"Herramienta desconocida: {name}")
        
        # Uso correcto de librería asíncrona
        conn = await asyncpg.connect(
            "postgresql://user:pass@localhost/db",
            min_size=1, max_size=10
        )
        try:
            rows = await conn.fetch(arguments["sql"])
            result = "n".join([str(dict(row)) for row in rows])
            return [TextContent(type="text", text=result)]
        finally:
            await conn.close()
    
    async def main():
        from mcp.server.stdio import stdio_server
        async with stdio_server() as (read_stream, write_stream):
            await app.run(
                read_stream, 
                write_stream,
                app.create_initialization_options()
            )
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Para simplificar el desarrollo en Python, existe FastMCP, un SDK de alto nivel sobre la implementación oficial que reduce la verbosidad:

    from fastmcp import FastMCP
    import asyncpg
    
    mcp = FastMCP("postgres-server")
    
    @mcp.tool()
    async def query_database(sql: str) -> str:
        """Ejecuta consultas SELECT seguras"""
        conn = await asyncpg.connect("postgresql://user:pass@localhost/db")
        try:
            rows = await conn.fetch(sql)
            return "n".join([str(dict(row)) for row in rows])
        finally:
            await conn.close()
    
    if __name__ == "__main__":
        mcp.run()
    

    Limitaciones y consideraciones actuales (Enero 2025)

    Si bien MCP representa un avance significativo, es crucial entender su estado y restricciones:

  • No es plug-and-play universal: Para que un agente consuma un servidor MCP, debe implementar explícitamente un cliente MCP. No funciona automáticamente con agentes existentes que no tengan esta capa.
  • Especificación en evolución: La versión actual (2024-11-05) cambia rápidamente. Lo que funciona hoy puede requerir ajustes menores en meses venideros.
  • Gestión de estado: El protocolo es stateless por diseño. Si necesitas sesiones persistentes o gestión compleja de estado entre llamadas, deberás implementarla a nivel de aplicación.
  • Seguridad: El servidor MCP tiene acceso directo a tus sistemas. Debes implementar validación de inputs, rate limiting y autenticación en la capa de transporte, ya que el protocolo base no prescribe mecanismos de seguridad específicos.

Conclusión

Model Context Protocol ofrece una ruta concreta para desacoplar agentes de IA de sus herramientas, promoviendo una arquitectura modular donde los desarrolladores pueden añadir capacidades sin modificar el core del sistema. Sin embargo, su adopción requiere una inversión inicial: implementar la capa cliente en tu agente y adaptar tus herramientas al formato de servidor MCP.

Aprendizajes clave:

  • MCP reduce la duplicación de código de integración, pero introduce una dependencia arquitectónica (el cliente MCP).
  • Usa siempre librerías asíncronas compatibles (asyncpg, aiohttp) en servidores MCP para no bloquear el event loop.
  • Evalúa FastMCP si buscas prototipar rápidamente en Python, pero migra a la SDK oficial si necesitas control granular del protocolo.
  • Llamada a la acción: Identifica una herramienta interna que actualmente integres con código específico en tus agentes. Intenta encapsularla en un servidor MCP durante esta semana. La fricción inicial es real, pero el desacoplamiento posterior justifica el esfuerzo cuando tu stack de herramientas comience a crecer exponencialmente.

    Arquitectura de memoria para Agentes de IA: Estrategias para mantener contexto persistente sin costos exponenciales

    El problema no es que tu agente olvide lo que dijo hace cinco minutos, es que recordarlo todo le cuesta 40 dólares por hora a tu empresa. La arquitectura de memoria para Agentes de IA se ha convertido en el diferenciador técnico entre un prototipo de juguete y un sistema escalable en producción. Mientras los modelos multiplican sus ventanas de contexto hasta 128k o incluso 200k tokens, enviar la conversación completa en cada llamada es económicamente suicida. Aquí no vamos a reinventar LangChain: te explico cómo funciona la memoria bajo el capó para que optimices lo que ya usas o construyas tu propia solución sin caer en trampas de diseño que duplican tus costos de inferencia.

    El problema real: obesidad del contexto y «lost in the middle»

    Los grandes modelos de lenguaje sufren de dos dolencias crónicas cuando les envías demasiado texto. La primera es económica: los precios de API escalan linealmente con los tokens de entrada. Si mantienes un historial de 20 mensajes de 500 tokens cada uno, estás pagando por 10k tokens de «contexto muerto» antes de que el modelo escriba una sola palabra.

    La segunda es de atención: el fenómeno «lost in the middle» demuestra que la precisión del modelo decae dramáticamente cuando la información relevante está en el medio de un contexto masivo, incluso con ventanas de 128k tokens. Más contexto no es mejor contexto; es solo contexto más caro y más ruidoso.

    Jerarquías de memoria: diseño en capas

    Un agente productivo no tiene una sola «memoria», sino tres sistemas especializados que operan a diferentes escalas temporales y económicas.

    Working Memory: ventana deslizante por tokens, no por mensajes

    El error clásico es usar deque(maxlen=10) y luego recortar por tokens. Es contradictorio: si limitas físicamente a 10 mensajes, ¿por qué validar tokens? Si cada mensaje tiene 2k tokens (aproximadamente 1,500 palabras en español), esos «solo 10 mensajes» explotan tu presupuesto.

    La solución es un límite exclusivo por tokens, sin restricción de cantidad de mensajes. En español, un token aproxima a una palabra (no uses la regla inglesa de 0.75 palabras/token ni la obsoleta de 4 caracteres/token). Para precisión quirúrgica, usa tiktoken o el tokenizer específico de tu modelo.

    import tiktoken
    from typing import List, Dict
    
    class WorkingMemory:
        def __init__(self, max_tokens: int = 4000, model: str = "gpt-4"):
            self.messages: List[Dict] = []  # Lista simple, sin maxlen arbitrario
            self.max_tokens = max_tokens
            self.encoding = tiktoken.encoding_for_model(model)
        
        def add_message(self, role: str, content: str):
            self.messages.append({"role": role, "content": content})
            self._enforce_token_limit()
        
        def _enforce_token_limit(self):
            """Recorta desde el inicio manteniendo los mensajes más recientes."""
            total = 0
            for i, msg in enumerate(reversed(self.messages)):
                # Conteo real de tokens, no aproximación por caracteres
                tokens = len(self.encoding.encode(msg["content"]))
                total += tokens
                if total > self.max_tokens:
                    cutoff = len(self.messages) - i - 1
                    self.messages = self.messages[cutoff:]
                    break
        
        def get_context(self) -> List[Dict]:
            return self.messages
    

    Este diseño garantiza que nunca excedas los 4k tokens de ventana reciente, sin importar si fueron 3 mensajes largos o 50 mensajes cortos.

    Memoria Semántica: retrieval cuando el pasado importa

    No todo lo antiguo merece estar en el prompt activo. Para conocimiento factual persistente (preferencias del usuario, documentos subidos, historial de compras), usa una base vectorial. Convierte interacciones pasadas a embeddings y recupera solo los top-k relevantes vía búsqueda por similitud cuando el agente lo solicite explícitamente.

    Esta estrategia transfiere el costo de O(n) tokens a O(1) tokens + costo fijo de embedding (barato) y retrieval (casi gratuito).

    Memoria Episódica: compresión progresiva

    Cuando la conversación es larga pero no puedes descartarla (ej. negociaciones complejas), implementa summarization condensado. Cada N interacciones, genera un resumen de los puntos clave y descarta el raw text. Tu Working Memory mantiene solo el resumen + los últimos mensajes crudos.

    class EpisodicMemory:
        def __init__(self):
            self.summaries: List[str] = []
            self.recent_raw: WorkingMemory = WorkingMemory(max_tokens=2000)
        
        def compress_if_needed(self, llm_client):
            if self.recent_raw.token_count > 3000:
                summary = llm_client.summarize(self.recent_raw.get_context())
                self.summaries.append(summary)
                self.recent_raw = WorkingMemory(max_tokens=2000)
        
        def get_full_context(self):
            return {
                "historical_summaries": self.summaries,
                "recent_messages": self.recent_raw.get_context()
            }
    

    Estimación de tokens: precisión vs velocidad

    Nunca uses len(text) // 4 para estimar tokens en español. Un texto técnico en español genera aproximadamente 1 token por palabra (ligeramente más si hay muchos signos de puntuación o código). Para producción, integra siempre el tokenizer oficial:

    • OpenAI: tiktoken
    • Anthropic: API de conteo propia o heurística de 1.2 tokens/palabra
    • Open source (Llama, Mistral): transformers.AutoTokenizer
    • Un error común es asumir que «40k tokens por interacción» es normal. En escenarios conversacionales reales, un mensaje de usuario rara vez supera los 200-500 tokens. Si estás procesando documentos masivos, hazlo por chunks, no cargues PDFs completos en el contexto de chat.

      En producción: ¿Construir o adoptar?

      No reinventes la rueda a menos que tengas restricciones de latencia extremas o lógica de negocio muy específica.

    • LangChain ofrece ConversationBufferWindowMemory (limita por mensajes) y ConversationSummaryMemory (limita por tokens con resumen). Úsalos como punto de partida.
    • LlamaIndex es superior para arquitecturas RAG complejas donde la memoria semántica es el componente dominante.
    • Custom (como el código anterior) solo cuando necesites control granular sobre el recorte (ej. priorizar mensajes del sistema sobre mensajes del usuario) o cuando estés optimizando costos en escala masiva donde cada token cuenta.

    Conclusión

    La arquitectura de memoria eficiente se reduce a tres principios: recorta por tokens reales, no por cantidad de mensajes; mueve el conocimiento histórico a sistemas de retrieval, no al prompt; y comprime progresivamente lo que no cabe en tu ventana de trabajo. Los modelos con 200k tokens de contexto son una trampa seductora: te permiten ser perezoso, pero te cobran por esa pereza en cada inferencia.

    Antes de optimizar código, mide tu consumo real de tokens por sesión de usuario. Si estás por encima de los 8k tokens de entrada promedio, tienes un problema de arquitectura, no de modelo.

    ¿Dónde está el cuello de botella en tu agente? Revisa tu última factura de API y dime cuántos tokens estás enviando de contexto histórico vs. cuántos generas de respuesta. Si la proporción está desbalanceada, es hora de rediseñar tu memoria.

    Cómo evitar que tu agente LLM olvide conversaciones críticas: guía de memoria a largo plazo en producción

    Tu agente recuerda perfectamente lo que le dijiste hace cinco minutos, pero ignora instrucciones clave del onboarding que hiciste ayer. Si te suena familiar, estás enfrentando el problema de contexto limitado inherente a los transformers. Cuando implementamos patrones de memoria a largo plazo en producción, no estamos solo guardando historiales de chat: estamos diseñando sistemas que evitan la degradación del rendimiento por recuperación de documentos irrelevantes y el fenómeno documentado como lost in the middle (donde los LLMs ignoran información ubicada en el centro de contextos extensos).

    El problema real: más allá del límite de tokens

    Los modelos actuales soportan ventanas de 128k o 200k tokens, pero la capacidad efectiva de razonamiento sobre todo ese contexto no escala linealmente. Investigaciones de Stanford y Anthropic demuestran que el accuracy cae significativamente cuando la información relevante se encuentra en medio de un contexto extenso, especialmente al superar los 20k tokens.

    Esto se agrava cuando tu sistema RAG recupera documentos semánticamente similares pero conceptualmente irrelevantes. Un retrieval naive puede saturar el contexto con ruido, haciendo que el modelo «olvide» restricciones críticas de seguridad o preferencias del usuario establecidas en interacciones previas.

    Arquitectura de tres capas: más allá del simple historial

    Basándonos en taxonomías formalizadas por proyectos como [MemGPT](https://memgpt.ai/) (Packer et al., 2023) y los módulos de memoria de LangChain, una arquitectura robusta de producción separa la memoria en tres capas distintas:

    Memoria semántica (conocimiento externo)

    Vector stores como Pinecone, Weaviate o pgvector almacenan embeddings de conversaciones pasadas, documentos y hechos persistentes. Sin embargo, el simple cosine similarity no es suficiente: necesitas estrategias de reranking (como Cohere Rerank o cross-encoders) para filtrar falsos positivos semánticos antes de inyectar al prompt.

    Memoria procedural (aprendizajes codificados)

    Son reglas explícitas derivadas del comportamiento del usuario que modifican la ejecución del agente. A diferencia de la semántica, aquí almacenamos código ejecutable o configuraciones estructuradas:

    def actualizar_estilo_usuario(user_id: str, feedback: dict) -> None:
        """
        Actualiza preferencias de comunicación basadas en interacciones previas.
        """
        preferencias = {
            "tono": feedback.get("formalidad", "neutral"),
            "longitud_respuesta": feedback.get("verbosity", "conciso"),
            "tecnologias_frecuentes": feedback.get("stack", [])
        }
        
        prompt_sistema = f"""
        Eres un asistente técnico. Reglas estrictas para este usuario:
        - Tono: {preferencias['tono']}
        - Longitud: {preferencias['longitud_respuesta']}
        - Contexto técnico priorizado: {', '.join(preferencias['tecnologias_frecuentes'])}
        - NUNCA sugieras soluciones fuera de su stack sin consultar primero
        """
        
        guardar_en_redis(user_id, prompt_sistema, ttl=86400)
    

    Memoria episódica (historial reciente)

    El buffer de ventana deslizante de las últimas N interacciones. Crítico para mantener coherencia en la conversación actual, pero volátil por diseño.

    Estrategias de recuperación inteligente

    Filtrado estricto por usuario (context contamination)

    Un error común en producción es no aislar los vectores por user_id o session_id. Recuperar memorias de usuario A cuando conversas con usuario B genera context contamination, filtraciones de privacidad y alucinaciones cruzadas. Implementa metadata filtering obligatorio en tus queries vectoriales:

    # Query segura con namespace isolation
    results = index.query(
        vector=embedding,
        filter={"user_id": {"$eq": current_user_id}},
        top_k=5,
        namespace=f"user_{current_user_id}"
    )
    

    Reranking antes del prompt

    Incluso con buenos embeddings, recuperar 10 documentos donde solo 2 son relevantes satura el contexto. Utiliza modelos de reranking para reordenar por relevancia real:

    import cohere
    
    co = cohere.Client(api_key)
    
    def rerank_documents(query, documents, top_n=3):
        results = co.rerank(
            model="rerank-english-v2.0",
            query=query,
            documents=documents,
            top_n=top_n
        )
        return [documents[r.index] for r in results.results]
    

    Esto mitiga el lost in the middle al reducir la cantidad de ruido que llega al modelo.

    Implementación práctica y costos

    Para la capa semántica, Pinecone en modo serverless es una opción popular, aunque debes verificar precios actualizados según tu región AWS/GCP y volumen de consultas. Nota: Los precios varían significativamente entre modo serverless (por GB almacenado y consultas) versus pods dedicados (por hora). La cifra orientativa de ~$0.10 por millón de vectores puede fluctuar según dimensiones y región; valida siempre la calculadora oficial antes de escalar.*

    Patrón de sincronización híbrida

    Combina las tres capas en tu pipeline:

  • Pre-procesamiento: Recupera preferencias procedurales de Redis/PostgreSQL (sub-10ms)
  • Retrieval semántico: Query vectorial con metadata filtering estricto
  • Reranking: Filtra a máximo 3-5 documentos altamente relevantes
  • Construcción de prompt: Inyecta memoria procedural primero (reglas duras), luego contexto episódico reciente, finalmente documentos rerankeados
  • async def build_context(user_id: str, current_message: str):
        # 1. Memoria procedural (alta prioridad)
        system_rules = await redis.get(f"user:{user_id}:preferences")
        
        # 2. Memoria episódica (últimos 3 turnos)
        recent_chat = await get_recent_history(user_id, limit=3)
        
        # 3. Memoria semántica con reranking
        raw_docs = await vector_search(current_message, user_id=user_id, top_k=10)
        relevant_docs = rerank_documents(current_message, raw_docs, top_n=3)
        
        return {
            "system": system_rules,
            "context": recent_chat + relevant_docs
        }
    

    Conclusión

    Implementar memoria a largo plazo no es solo «guardar todo en una base vectorial». Requiere arquitectura disciplinada: aislamiento estricto de usuarios para evitar contaminación, reranking para combatir el ruido del retrieval, y separación clara entre conocimiento factual (semántico), reglas de ejecución (procedural) y contexto conversacional (episódico).

    Aprendizaje clave: Un agente con 100k tokens de contexto pero sin filtrado inteligente performa peor que uno con 8k tokens y recuperación curada. La calidad del contexto siempre supera a la cantidad.

    ¿Ya implementaste algún patrón de memoria avanzada? Comparte tu arquitectura en los comentarios o sígueme para el próximo post sobre evaluación automatizada de retrieval en RAG pipelines.

    Fundamentos de la tecnología blockchain

    ¿Cómo llegamos a pagar millones por un png/jpeg virtual? ¿Cómo llegamos a que una moneda virtual costara $20,000. USD?

    El contexto lo es todo, y aquí lo hablaremos.

    Sabías que aproximadamente solo el 8% del dinero del mundo existe de manera física, es decir billetes y monedas, el resto ya es digital, de manera electrónica

    Pero primero entandamos que es el dinero.

    ¿Qué es el dinero?

    El dinero son varias cosas: lo primero es que el dinero es una herramienta para transferir valor y sirve como medio de intercambio para mover o intercambiar valor entre personas, adicionalmente el dinero es también una unidad de cuenta, el dinero es la forma como se puede medir el valor de las cosas por eso las cosas tienen precio y finalmente el dinero sirve como depósito de valor, sirve para guardar valor pensando en el futuro

    Bitcoin es la primera aplicación a la tecnología blockchain y bitcoin pretende ser una nueva forma de dinero, ahí está la conexión entre blockchain y dinero. Bitcoin es una red de ordenadores, bitcoin es un software que se instala en estos ordenadores y bitcoin es una criptomoneda que es una nueva forma de transferir valor por internet.

    Breve historia del dinero

    El trueque

    En épocas antiguas la humanidad usaba el trueque para realizar actividades comerciales e intercambiar valor entre personas. En su forma básica consiste en el intercambio de bienes y servicios generados por dos personas.

    El trueque permite que las dos partes involucradas en una transacción se vieran beneficiadas, pues cada parte obtenía algo que deseaba a cambio de algo que tenía en exceso o de una actividad que estaba dispuesto a realizar, que hubiera doble coincidencia de deseos

    Dinero primitivo

    Para solucionar el problema de la doble coincidencia de deseos, los seres humanos descubrieron que había unos bienes intermedios (que vamos a llamar dinero primitivo) por los que había una demanda continua entre los miembros de la comunidad y que podían conservarse para hacer intercambios más adelante. Conformado principalmente por materias primas y artículos coleccionables, que conservaban su valor por cierto periodo y podían ser almacenados de manera fácil para ser intercambiados por bienes y servicios en el futuro.

    El dinero primitivo podía tomar diferentes formas en diferentes lugares, como el ganado, la sal, las plumas de animales exóticos, el tabaco. Piedras rai, que son piedras calizas de forma circular con un agujero en el centro que no se pueden encontrar de forma natural en la isla de yap, se pueden encontrar en una isla cerca y es necesario transportarla en balsas hasta la isla . Deseados por su rareza, escasez y gran esfuerzo para llevarla a la isla. 

    Si bien el dinero primitivo solucionaba algunos de los problemas originales del trueque, todos estos bienes presentaban varios inconvenientes para funcionar como dinero, la mayoría no eran fungibles, eran difíciles de dividir, difíciles de transportar y su valor no necesariamente se podría conservar en el tiempo.

    Fungible: es una propiedad del dinero que se tiene cuando una cantidad de dinero es igual a la misma cantidad del mismo dinero representado en otra forma.

    Direcciones en Solidity

    El blockchain de Ethereum esta construido de cuentas, que puedes razonar como cuentas de bancos. Una cuenta tiene balance de Ether, la moneda usada en el blockchain de Ethereum. Puedes utilizar estas cuentas para enviar y recibir Ether, así como puedes hacerlo con cuentas de bancos que transfieren dinero a otras cuentas de banco.

    Cada cuenta tiene una dirección, exactamente igual que las cuentas de banco. Es un identificador único que apunta a tu cuenta, y se así:

    0x1B0fcE686a0A3d46c7838d5a3fb39A506e40Bf6f

    (Esta dirección es mía, puedes enviar Ether si quieres 😉 )

    Estas direcciones pertenecen a un usuario único (o un contrato inteligente).

    Source: https://cryptozombies.io/en/lesson/2/chapter/2

    También te puede interesar

    Eventos: https://alanhurtarte.com/eventos-en-solidity/

    Funciones puras: https://alanhurtarte.com/funciones-puras-en-solidity/

    Eventos en solidity

    Los eventos en solidity son como en cualquier otro lenguaje, emisor y receptor. Se usan para comunicar que algo paso en el blockchain hacia tu frontend, el cual estará «escuchando» para ciertos eventos y tomara acción cuando suceda.

    Por ejemplo:

    // declarando el evento
    event EnterosSumados(uint x, uint y, uint resultado);
    
    function sumar(uint _x, uint _y) public returns (uint) {
      uint result = _x + _y;
      // disparar el evento hacia el app
      emit EnterosSumados(_x, _y, result);
      return result;
    }
    

    Tu frontend podría estar escuchando el evento. Una implementación en Javascript podría verse así:

    TuContrato.EnterosSumados(function(error, result) {
      // haz algo con el resultado
    })

    Source: https://cryptozombies.io/en/lesson/1/chapter/13

    También te puede interesar:

    Funciones puras en solidity: https://alanhurtarte.com/funciones-puras-en-solidity/

    Funciones puras en Solidity

    En solidity hay funciones llamadas puras o pure en ingles, que es cuando no se accede al app ni se escribe sobre ella, solo se usa lo que se mande por parámetros.

    Por ejemplo

    function _multiply(uint a, uint b) private pure returns (uint) {
      return a * b;
    }

    Esta función ni siquiera lee el estado del app. El valor a retornar depende solo de los parámetros de la función.

    Cabe mencionar que puede ser difícil de recordar cuando marcar una función como pure/view. Por suerte el compilador es bueno para recordar con warning de cuándo deberías usar uno de estos modificadores.

    Source: https://cryptozombies.io/en/lesson/1/chapter/10

    Lo que aprendí del Toptal JS Speeding Challenge

    El toptal js speeding challenge concluyo ya hace unas semanas, y tenía premios geniales como $1000 gift card en amazon y unos airpods para el primer lugar. Lo que aprendí yc de cómo podrías ganarlo.

    Muchos como a mí les habra salido la publicidad del challenge de toptal, apelaban al orgullo de si podías resolver algo con js rápido, y no me convencían, paso una semana con estos anuncios constantes hasta que decidí participar. Lo curioso es que la publicidad que funciono fue una que apelaba a mi nacionalismo. Algo tipo «ayuda a que tu país rankee mas alto». Me parece curioso que eso me llevo a la acción.

    El primer intento

    Bueno dije yo, participe y resultaron ser los típicos ejercicios de JS que hacen en las entrevistas laboras. Algunos mas fáciles como retorno un numero al cubo, o sacar la raíz cuadrada de un número. Otros un poco más complejos como encontrar anagramas y palíndromos.

    En mi primer intento saque 15 puntos, fueron 3 ejercicios acertados en 2:30 minutos. Me pareció muy poco tiempo pero pensé, de esto se ha de tratar, quién typea mas rapido. Al terminar mi tiempo me llevo a mi scoreboard nacional, solo habían unos 8 participantes y estaba sexto en el listado de Guatemala, pero el primer lugar tenía 4500 puntos. Eso es imposible dije yo, o logro hackear el timer o esta haciendo algo más, porque no hay manera de escribir tan rápido. Luego vi el score global y era lo mismo, arriba de 4000 puntos.

    Lo que necesitaba era automatización

    Después de ver los scores más altos encontré que esto tenía que ser un bot, o algo similar. Investigue un poco y decidí probar https://github.com/puppeteer/puppeteer. Resulta que esta herramienta se utiliza para automatizar pruebas y acciones en un navegador tal y como la haría una persona, pero mucho más rápido. Pase dos días perdido probando e investigando como niño que descubrió algo nuevo.

    Resulto que el Toptal JS Speeding Challenge resulto ser mas un concurso de velocidad de automatización que de Javascript.

    Solución

    Ya con esta solución solo fue cuestión de tiempo para encontrar el patrón y automatizar todas las respuestas. Yo entre el penúltimo día de la competencia, logre una puntuación alta, mas no perfecta. Logre quedar en el puesto 15 de América y como en el 97 a nivel global. Mi solución puede encontrarse aquí https://github.com/kenny08gt/toptal-speed-coding-challenge-js

    Leaderboard en América
    Leaderboard en Americas (mejor posición que llegue, luego quede 15)

    Que queda por mejorar y aprendí

    Realmente creo que mis soluciones se pueden mejorar para dar un mejor tiempo de respuesta, sin embargo creo que es más fundamental encontrarse en un lugar con poca latencia. En un concurso tan competido, donde el tiempo es la medida de calificación, sería perfecto tener una conexión con poca latencia. Donde en vez de tardarse 300ms la conexión pudiera darse en menos de 100ms. Multiplica eso por 36 ejercicios y ya son mas de 3 segundos menos, por dar un ejemplo. Esta podría ser la clave para ganarlo o rankear mas alto.

    No te olvides de pasar otros de mis posts

    La importancia de tu primer trabajo y como define tu carrera

    El primer trabajo definirá tu carrera por los siguientes años, y si no peleas por el cambio, por el resto de tu vida.

    Yo tuve mi primer trabajo a los 17 años, salí del bachillerato, listo para enfrentar al mundo laboral, no había logrado entrar a la universidad así decidí buscar trabajo para no perder un año sin hacer nada en mi casa. Me tope con que nadie contrata a menores de edad. Supongo que por posibles implicaciones legales y demás. Busque varios procesos y el ultimo que me quedó donde si contrataban menores fue pizza hut.

    Trabaje un año en pizza hut, de mesero, de lavaplatos, de limpia mesas, de hacer masa, de hacer pizza, de «barista», de hacer el corte y servir los platos, de armar pasta. Pase por casi todas las areas. Fue duro el trabajo, tocaba trabajar días de feriados o descanso que mi familia tenía y yo no. Era trabajar más de 12 horas seguidas los fines de semana. Si te toca cierre era trabajar hasta la 1 o 2 de la mañana.

    En esta etapa fue la primera vez que lo escuche de un compañero: «El problema que ahora solo de esto vas a poder trabajar» me dijo, y yo con un futuro totalmente diferente planeado en mi cabeza le pregunte porque. Me respondió: «si porque de esto tienes experiencia, si vas a buscar de bodeguero o en oficina te van a decir que no tienes experiencia, que mejor aplique a servicio al cliente, si vas a un Mcdonalds o algo así si te darán porque ya tienes esta experiencia«

    En ese momento solo pensé, hell no, mi futuro tiene que ser mas que esto. Ganar Q700 quincenalesNo que tenga nada de malo trabajar en un restaurante. Respeto mucho la profesión y conocí grandes amigos y personas ahí. Pero yo soñaba con más, con mucho más.

    Seguía sin ganar la entrada a la universidad, por lo que entre a un programa para prepararme para entrar. Otro año mas, aprendiendo lo que tuve que haber aprendido hace años. Tuve la fortuna de poder entrar a la universidad el siguiente año. Pasaron los 5 años, largos y tormentosos por momentos. Hasta que tuve que volver a solicitar trabajo. Ahora como futuro ingeniero en sistemas.

    El primer trabajo en tecnología

    Mi familia no estaba pasando por un mal momento financiero así que me apresure a buscar trabajo, pero no era una búsqueda enfocada, era lo primero que saliera y me aceptaran. No tenía experiencia laboral. Era un trabajo de Jr. por lo tanto es complicado también. Un día un amigo me dice yo tenía entrevista aquí pero ya me salió trabajo, prueba aplicar. Lo hice y me aceptaron.

    Este trabajo era usando PHP un lenguaje de programación que es usado por el 70% de la web, pero que tiene mala fama entre programadores y mamadores de la tecnología. A mí no me importo, trabajo es trabajo dije yo.

    php for web development
    https://w3techs.com/technologies/details/pl-php

    Pasaron los año ya, tengo 5 años de estar trabajando profesionalmente con Php y otras tecnologías y ya tengo una experiencia que podría decirse ser de Sr. Pero esta experiencia solo aplica a trabajo en Php, si quisiera un mejor sueldo en otra tecnología, no tengo la experiencia para aplicar en ellos, en especial como Sr pues piden experiencia.

    Viendo para atrás debí escoger mi primer trabajo con mas cuidado. Puede que tengas suerte y te toque una empresa o un jefe que te ponga el path para trabajar en diferentes areas y logres experiencia en otras. Puede que tengas suerte y te puedas mover entre areas internas de la empresa sin mayor problema. Pero también puede ser que no, y te quedes atorado en la misma tecnología, por varios años si no estas consciente de ello.

    También puedes leer https://alanhurtarte.com/eso-nunca-funcionara-la-historia-de-netflix/

    LinkedIn
    Share
    Instagram
    WhatsApp