Skip to content

Lección 02

Memoria conversacional sin base de datos

El bot tiene que recordar lo que ya ha pasado en la conversación. Te explico cómo el array de messages ES la memoria, y dónde se rompe.


La gran mentira sobre LLMs

“Claude no tiene memoria, hay que dársela explícitamente.”

Verdad. Cada request es independiente. El modelo no recuerda nada entre llamadas. La “memoria” se simula reenviando todo el historial en cada request.

El array es la memoria

const history = [
  { role: "user", content: "Me llamo Ana." },
  { role: "assistant", content: "Encantado, Ana." },
  { role: "user", content: "¿Cómo me llamo?" },
];

const res = await client.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 100,
  messages: history,
});
// → "Te llamas Ana."

Si en la 3ª llamada no le mandas la 1ª y 2ª, el modelo no sabrá tu nombre. Punto.

En el front: estado React

"use client";
import { useState } from "react";

type Msg = { role: "user" | "assistant"; content: string };

export default function Chat() {
  const [messages, setMessages] = useState<Msg[]>([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);

  async function send() {
    if (!input.trim()) return;
    const userMsg: Msg = { role: "user", content: input };
    const next = [...messages, userMsg];
    setMessages(next);
    setInput("");
    setLoading(true);

    const res = await fetch("/api/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ messages: next }),
    });
    const data = await res.json();

    setMessages([...next, { role: "assistant", content: data.text }]);
    setLoading(false);
  }

  return (
    <div className="max-w-2xl mx-auto p-6">
      <div className="space-y-4 mb-6">
        {messages.map((m, i) => (
          <div
            key={i}
            className={`p-3 rounded-lg ${
              m.role === "user" ? "bg-blue-100 ml-12" : "bg-gray-100 mr-12"
            }`}
          >
            <p className="text-xs text-gray-500 mb-1">{m.role}</p>
            <p>{m.content}</p>
          </div>
        ))}
        {loading && <p className="text-gray-400">Escribiendo…</p>}
      </div>
      <div className="flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && send()}
          className="flex-1 border rounded-lg px-3 py-2"
          placeholder="Escribe..."
        />
        <button onClick={send} className="bg-blue-600 text-white px-4 rounded-lg">
          Enviar
        </button>
      </div>
    </div>
  );
}

El problema: el historial crece sin límite

Cada turno añade más tokens. Si el usuario hace 50 mensajes, pagas tokens por toda la conversación entera en cada nueva petición.

Tres estrategias:

1. Ventana deslizante

const recent = history.slice(-10);

Sencillo, brutal, suele bastar para chats casuales.

2. Resumen + ventana

Cuando la conversación pase de N mensajes, pídele al modelo que resuma los primeros, sustitúyelos por el resumen, y conserva los últimos N.

async function compressIfNeeded(messages: Msg[]): Promise<Msg[]> {
  if (messages.length < 20) return messages;

  const toCompress = messages.slice(0, -10);
  const summary = await client.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 300,
    messages: [{
      role: "user",
      content: `Resume esta conversación en 5 frases:\n${JSON.stringify(toCompress)}`,
    }],
  });

  return [
    { role: "user", content: "Resumen previo: " + (summary.content[0] as any).text },
    ...messages.slice(-10),
  ];
}

3. RAG (a partir de cierto volumen)

Si la “memoria” pasa de miles de mensajes, guarda los mensajes en una base vectorial (Postgres+pgvector, Pinecone, etc.) y recupera sólo los relevantes para cada turno. Esto está fuera de este curso, pero es la solución profesional.

Lo que sigue

En la siguiente lección damos al bot superpoderes: tool use. Le enseñamos a llamar a APIs reales cuando le falta información.


NIVEL 2

Reto Pro

Reto Pro de esta lección

Misma idea, sin pistas, evaluada por tests automáticos.

Desbloquear Modo Pro · 29 € Pago único · acceso de por vida · sin suscripción
NIVEL 3

Hard Mode

Hard Mode

Variante extrema del reto. Tiempo límite y restricciones adicionales.

Desbloquear Modo Pro · 29 € Pago único · acceso de por vida · sin suscripción