Volver al blog

Optimización de costosGuía

Recorta el historial del chat para bajar el coste: ventana deslizante o resumen

Limita lo que reenvías en cada turno: conserva los últimos mensajes o un resumen breve. Aritmética de tokens para un chat de 50 turnos y un helper en Python.

Por CheapTokens TeamPublicado el 7 min de lectura

Portada: mantén barato un chat largo, con el helper trim_history
En esta página
  1. Qué envía realmente el turno 50
  2. Opción 1: una ventana deslizante
  3. Opción 2: un resumen de los turnos antiguos
  4. Cuánto cuestan las tres configuraciones
  5. Cuál elegir

Tu bot de soporte lleva 50 turnos conversando, y cada mensaje nuevo cuesta más que el anterior. La API no recuerda nada, así que tu código reenvía toda la conversación cada vez. La solución es limitar lo que envías: conserva el system prompt y los últimos turnos (una ventana deslizante), o sustituye los turnos antiguos por un resumen breve. A continuación tienes la aritmética de un chat de 50 turnos y un helper que puedes copiar.

La guía de chat multironda de DeepSeek (api-docs.deepseek.com, consultada el 1 de noviembre de 2026) lo dice claramente: el servidor no registra el contexto de las peticiones anteriores, así que el cliente envía la lista completa de messages en cada llamada. El formato compatible con OpenAI en CheapTokens funciona igual. Si esto es nuevo para ti, empieza por por qué un chat largo cuesta más y cómo funciona el array de mensajes.

Qué envía realmente el turno 50#

Supón que tienes un bot de soporte con un system prompt de 400 tokens. En un turno típico el usuario escribe unos 60 tokens y el bot responde con unos 190. Un turno completo añade 250 tokens al historial. Son suposiciones: sustitúyelas por los números del campo usage de tus propias respuestas.

En el turno 50 la petición lleva el system prompt, 49 turnos anteriores y el mensaje nuevo: 400 + 49 × 250 + 60 = 12,710 tokens de entrada, para obtener una respuesta de 190 tokens. Suma todo el chat. El system prompt y el mensaje nuevo se envían siempre: 50 × 460 = 23,000. El historial repetido añade 250 × 1,225 = 306,250. En total: 23,000 + 306,250 = 329,250 tokens de entrada, frente a 50 × 190 = 9,500 tokens de salida.

El historial de cada petición crece un turno, así que el total crece con el cuadrado de la longitud del chat. Pasa de 50 a 100 turnos y la parte de historial repetido se multiplica aproximadamente por cuatro.

Pruébalo tú mismoPruébalo: si enviaras solo el system prompt, los últimos 4 turnos y el mensaje nuevo, ¿cuántos tokens de entrada llevaría el turno 50?Mostrar respuesta

1.460 (400 + 4 × 250 + 60), frente a 12.710 con el historial completo. La misma pregunta, la misma longitud de respuesta, una fracción de la entrada.

Opción 1: una ventana deslizante#

La idea: enviar siempre el system prompt y descartar todo excepto los últimos N turnos. Conservas el historial completo en tu propio almacenamiento y recortas solo la copia que envías.

python
def trim_history(messages, keep_pairs=4):
    """System prompt + the last keep_pairs turns + the new user message."""
    system = [m for m in messages if m["role"] == "system"]
    rest = [m for m in messages if m["role"] != "system"]
    return system + rest[-(keep_pairs * 2 + 1):]

Llámalo después de añadir el mensaje nuevo del usuario. Con keep_pairs=4 son 4 pares usuario/asistente más el mensaje nuevo, 9 mensajes, y el slice siempre empieza en un mensaje de usuario. Aquí está integrado en una llamada real:

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"

history = [{"role": "system", "content": "You are a concise support assistant."}]

def ask(text):
    history.append({"role": "user", "content": text})
    resp = client.chat.completions.create(
        model=MODEL,
        messages=trim_history(history),
        max_tokens=500,
    )
    answer = resp.choices[0].message.content
    history.append({"role": "assistant", "content": answer})
    print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
    return answer

La línea print es la útil: fíjate en el primer número. Sin recorte sube en cada turno. Con la ventana deja de crecer en cuanto el chat pasa del turno 4.

Helper de Python trim_history: system prompt, últimos 4 turnos, mensaje nuevo
El helper de recorte de este artículo

En números: a partir del turno 5, cada petición es de 400 + 4 × 250 + 60 = 1,460 tokens de entrada. Los primeros cuatro turnos siguen creciendo con normalidad y suman 3.340. El chat completo: 3,340 + 46 × 1,460 = 70,500 tokens de entrada, aproximadamente una quinta parte de los 329.250 que envías con el historial completo. La salida se mantiene en 50 × 190 = 9,500.

Petición del turno 50: historial completo frente a una ventana de 4 turnos
Partes de tokens de la petición del turno 50, según las suposiciones anteriores

En código, todo el cambio es un argumento en la llamada:

Comparar

El cambio de una línea

Antes

python
messages=history

Después

python
messages=trim_history(history)

El inconveniente: una ventana olvida. Si el usuario pregunta en el turno 40 "¿cuál era mi número de pedido?" y el número estaba en el turno 3, el modelo nunca lo ha visto. Dos soluciones baratas. Extrae los datos estables (nombre, número de pedido, plan) del chat en tu propio código y añádelos al system prompt, donde la ventana nunca los corta. O añade un resumen, que es la siguiente opción.

Opción 2: un resumen de los turnos antiguos#

En lugar de descartar los turnos antiguos, los comprimes. Cada pocos turnos, envía al modelo los turnos que acaban de salir de la ventana, junto con el resumen actual, y pídele un resumen nuevo y breve. Luego pon ese resumen en el mensaje de sistema.

python
def summarize(summary, old_messages):
    transcript = "\n".join(f"{m['role']}: {m['content']}" for m in old_messages)
    prompt = (
        f"Summary so far:\n{summary}\n\nNew turns:\n{transcript}\n\n"
        "Rewrite the summary in under 200 words. "
        "Keep names, numbers, decisions and open questions."
    )
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=400,
    )
    return resp.choices[0].message.content

def build_messages(history, summary, keep_pairs=4):
    system = history[0]["content"]
    if summary:
        system += "\n\nEarlier in this chat: " + summary
    recent = history[1:][-(keep_pairs * 2 + 1):]
    return [{"role": "system", "content": system}] + recent

El resumen no es gratis: es una llamada extra al modelo. Supón un resumen de unos 300 tokens, actualizado cada 5 turnos (9 veces en 50 turnos), donde cada actualización lee unos 1.600 tokens (el resumen anterior más cinco turnos más la instrucción) y escribe 300. Para ir sobre seguro, cuenta el resumen en cada petición a partir del turno 5 (en realidad aparece tras la primera actualización, así que esto es una ligera sobreestimación): 400 + 300 + 4 × 250 + 60 = 1,760 tokens de entrada.

Totales del chat: peticiones principales 3,340 + 46 × 1,760 = 84,300 tokens de entrada, llamadas de resumen 9 × 1,600 = 14,400 de entrada y 9 × 300 = 2,700 de salida. En conjunto, 84,300 + 14,400 = 98,700 tokens de entrada y 9,500 + 2,700 = 12,200 de salida.

El resumen cuesta un poco más que la ventana simple y, a cambio, el modelo sigue sabiendo qué pasó en el turno 3.

Cuánto cuestan las tres configuraciones#

Aquí está el mismo chat de 50 turnos en DeepSeek-V4-Flash, con precios en vivo a partir de los totales anteriores:

Un chat de 50 turnos en DeepSeek-V4-Flash
  • Historial completo (329.250 entrada, 9.500 salida)$0.0059
  • Ventana más resumen (98.700 entrada, 12.200 salida)$0.0027
  • Ventana deslizante, 4 turnos (70.500 entrada, 9.500 salida)$0.002

Los precios vienen de la lista de precios en vivo, así que los números se actualizan cuando cambia la tarifa. El orden no: la ventana envía lo mínimo, el resumen queda en medio, el historial completo es lo más caro.

Para un chat como este, el modelo de CheapTokens con el que empezar es DeepSeek-V4-Flash. Tiene una ventana de contexto de 380K (un chat de 50 turnos cabe incluso sin recortar), hasta 32.768 tokens de salida por respuesta, un precio fijo a cualquier hora del día y, en total, contando entrada y salida juntas, es más barato que OpenRouter. El id del modelo es deepseek-ai/DeepSeek-V4-Flash-0731. Si quieres comparar modelos con la configuración de ventana (1.410 tokens de entrada por petición de media, 190 de salida, 50 peticiones), aquí tienes la tabla en vivo:

Modelo Por solicitud 50 solicitudes OpenRouter, 50 solicitudes
GLM-5.3-Flash $0.000036 $0.0018 $0.0032
DeepSeek-V4-Flash $0.00004 $0.002 $0.0063
MiniMax-M2.7 $0.000099 $0.005 $0.0594

1410 tokens de entrada y 190 tokens de salida por solicitud. Los precios por 1M de tokens son actuales y cambian junto con nuestras tarifas; la columna de OpenRouter muestra precios de referencia para la misma carga de trabajo.

GLM-5.3-Flash tiene un contexto de 390K y MiniMax-M2.7 tiene 180K, suficiente para un chat recortado. Los precios actuales están en la página de precios, y la documentación muestra cómo enviar la primera petición.

Cuál elegir#

Una ventana es la opción por defecto: tres líneas de código, sin llamadas extra, sin modo de fallo adicional. Úsala para bots de soporte, tutores y role-play, donde los últimos turnos llevan la conversación y el system prompt lleva las reglas. Añade el resumen cuando los usuarios se refieran a cosas dichas hace mucho, como en un asistente personal con una conversación interminable. Mantén la ventana lo bastante pequeña para que el modelo siga viendo el contexto inmediato, y lo bastante grande para contener un intercambio completo de pregunta y respuesta. Cuánto cabe en total es un límite aparte, tratado en qué es una ventana de contexto.

Verificación rápida

El usuario pregunta en el turno 40 por un número de pedido dado en el turno 3. Usas una ventana de 4 turnos sin resumen. ¿Qué ocurre?

Mostrar respuesta

Respuesta: El modelo no puede verlo, porque el turno 3 se cortó de la petición

La API no tiene estado. El modelo solo ve lo que está en la petición, así que los datos que aún necesites deben quedarse en el system prompt o en un resumen.