Cuando la conversación se hace larga
Recortar el historial de un chat es enviar menos mensajes al modelo para que la lista quepa en su límite. Importa porque un chat largo crece en cada turno, y cada mensaje cuesta tiempo y crédito.
Tras una hora con un chatbot de viajes, las primeras preguntas ya no hacen falta. Quitarlas mantiene el chat rápido y barato.
Al final de esta lección
Nota
Puedo mantener una conversación dentro del límite del modelo.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Token | un trozo pequeño de texto que el modelo cuenta |
| Límite de contexto | el máximo de tokens que un modelo acepta en una llamada |
| Presupuesto | el máximo de tokens que decides enviar |
| Recortar | quitar mensajes viejos hasta que la lista quepa |
| Resumen | un texto corto que sustituye a los mensajes quitados |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Cuenta los tokens antes de enviar
Un modelo cuenta el texto en tokens, y acepta solo un número limitado por llamada. Los recuentos reales los da el tokenizador del modelo. Una estimación aproximada es un token por cada cuatro caracteres.
Esta lección suma cuatro por cada mensaje, por el rol y el envoltorio. Suma todos los mensajes y compara el total con tu presupuesto.
Ejemplo resuelto: contar seis mensajes
| Mensaje | Caracteres | Tokens |
|---|---|---|
| system: Ayudas con viajes en tren. Sé breve. | 36 | 13 |
| user: ¿Cuánto dura el tren de Valencia a Alicante? | 44 | 15 |
| assistant: Unas dos horas. | 15 | 8 |
| user: ¿Y cuánto cuesta un billete? | 28 | 11 |
| assistant: Depende de la hora. | 19 | 9 |
| user: ¿Hay un vagón silencioso? | 25 | 11 |
| Total | 167 | 67 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Quita los más antiguos y conserva las reglas
Para recortar, quita primero los mensajes más antiguos hasta que el total quepa en el presupuesto. Dos mensajes se quedan, diga lo que diga el presupuesto: el mensaje system del principio y el mensaje más reciente del final.
El mensaje system contiene las reglas. El mensaje más reciente es la pregunta que se hace ahora.
Un resumen sustituye lo que se quitó
Quitar mensajes pierde datos. Un resumen conserva los principales en pocas palabras. Pide al modelo que resuma los mensajes quitados y añade el resumen al mensaje system.
Esto cuesta una llamada más cada vez que recortas. En un chat corto, quitar sin más suele bastar.
Un error frecuente
Advertencia
Mucha gente cree que el mensaje más antiguo es siempre el menos importante. A menudo es el mensaje system con las reglas, o el único dato del que depende el chat. Conserva el mensaje system y resume antes de quitar algo que importe.
Escribir la ayuda del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir la ayuda del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' const list = [].concat(sample);' ' const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return list[Math.min(turn, list.length - 1)];' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de recorte, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const BUDGET = 45; // un presupuesto pequeño para que haya recorte' '' 'function tokens(message) { return Math.ceil(message.content.length / 4) + 4; }' 'function total(list) {' ' return list.reduce(function (sum, m) { return sum + tokens(m); }, 0);' '}' > trim2.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de recorte, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function trim(list, budget) {' ' const kept = list.slice();' ' const dropped = [];' ' while (total(kept) > budget && kept.length > 2) {' ' const at = kept[0].role === \"system\" ? 1 : 0;' ' dropped.push(kept.splice(at, 1)[0]);' ' }' ' return { kept: kept, dropped: dropped };' '}' >> trim2.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de recorte, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const history = [' ' { role: \"system\", content: \"Ayudas con viajes en tren. Sé breve.\" },' ' { role: \"user\", content: \"¿Cuánto dura el tren de Valencia a Alicante?\" },' ' { role: \"assistant\", content: \"Unas dos horas.\" },' ' { role: \"user\", content: \"¿Y cuánto cuesta un billete?\" },' ' { role: \"assistant\", content: \"Depende de la hora.\" },' ' { role: \"user\", content: \"¿Hay un vagón silencioso?\" },' '];' >> trim2.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de recorte, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const result = trim(history, BUDGET);' ' console.log(\"Tokens antes: \" + total(history) + \", después: \" + total(result.kept) + \", descartados: \" + result.dropped.length);' >> trim2.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de recorte, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' const old = result.dropped.map(function (m) { return m.role + \": \" + m.content; }).join(\" \");' ' // Muestras grabadas, usadas solo si ningún modelo responde.' ' const first = \"El usuario preguntó cuánto dura el tren de Valencia a Alicante y le dijeron que unas dos horas.\";' ' const summary = await ask([{ role: \"user\", content: \"Resume en una frase corta: \" + old }], first, 50);' ' console.log(\"Resumen:\", summary);' ' const prompt = result.kept.slice();' ' prompt[0] = { role: \"system\", content: prompt[0].content + \" Antes en este chat: \" + summary };' ' console.log(\"Respuesta:\", await ask(prompt, \"Sí, casi todos los trenes tienen un vagón silencioso.\", 50));' '}' 'main();' >> trim2.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Recortar y resumir
docker exec -w /home/node/project agent node trim2.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Una salida posible
| Línea | Lo que puede mostrar |
|---|---|
| Tokens | antes: 67, después: 44, descartados: 2 (siempre igual, nada se adivina) |
| Resumen | Una frase sobre la duración del tren que se quitó |
| Respuesta | Una respuesta corta sobre un vagón silencioso |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.