Calcula bien los límites primero
Tres números lo determinan todo:
- 100,000 tokens totales: prompt + completion.
max_tokenses 2048 por defecto y tiene un máximo de 16,000 por petición.- El cuerpo de la petición no supera 8 MB; este límite rara vez se alcanza con texto puro, el que se llena primero es el de tokens.
La fórmula de presupuesto es simple: tokens de prompt disponibles = 100000 - max_tokens. Si quieres 4000 tokens de salida, el prompt máximo es 60,000. Si excedes, la API devuelve 400; no corta automáticamente. No es estrategia, es azar.
Otro error común: pensar que un max_tokens más grande es más seguro. En realidad es espacio reservado para la salida; si pones 16,000, el prompt solo tendrá 48,000. Configúralo según necesites, no lo pongas al máximo sin pensar.
Ejemplo: tienes un texto de 300,000 palabras. Estimando 1.3 tokens por palabra, son ~390,000 tokens, más de seis veces el límite. Si la salida requiere 800 tokens, cada bloque de entrada puede tener un máximo de 63,000. Con un margen de seguridad del 10%, te quedan algo más de 50,000. Pero esto no es óptimo: con bloques grandes, el modelo presta menos atención al centro. La solución es dividir los bloques, aunque requiera más llamadas.
Estimación de tokens: algoritmo aproximado
Cuando no hay tokenizador local, usa estos valores aproximados para estimar. Estos valores son aproximados, no exactos, y pueden variar entre un 10% y un 20% según el texto:
| Tipo de texto | Conversión aproximada |
|---|---|
| Texto en chino | Aproximadamente de 1 a 1.5 tokens por carácter |
| Inglés | Aproximadamente 1 token cada 4 caracteres, o unos 1.3 tokens por palabra |
| Código, JSON y texto denso en símbolos | Consume más tokens que el texto normal; estima usando el valor alto conservador |
Una función de estimación suficiente es la siguiente, junto con el cálculo del presupuesto con margen:
CONTEXT = 100000
def est_tokens(text: str) -> int:
"""粗估:中文按每字 1.3 token,其余字符按每 4 个字符 1 token。仅为近似。"""
zh = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
other = len(text) - zh
return int(zh * 1.3 + other / 4) + 1
def max_prompt_budget(max_tokens: int, margin: float = 0.1) -> int:
"""给定计划的 max_tokens,返回 prompt 最多能用多少 token(留 margin 余量)。"""
return int((CONTEXT - max_tokens) * (1 - margin))
# 例:计划让模型写 3000 token,prompt 预算
print(max_prompt_budget(3000)) # 54900Método de calibración: envía una petición con una entrada típica, lee usage.prompt_tokens de la respuesta, compáralo con tu estimación y calcula el multiplicador real para tus datos. Usa la estimación solo para decidir si fragmentar; para conciliación, usa usage.
Resumen de documentos largos: fragmentación y fusión
Cuando el documento excede el presupuesto, el método estándar es map-reduce: primero resume por fragmentos y luego fusiona los resúmenes en uno general. Ten en cuenta estos detalles:
- Corta por límites de párrafo, no por un número fijo de caracteres, para no cortar frases a la mitad.
- Deja suficiente presupuesto por bloque; se recomienda no superar un tercio del límite para dejar espacio a la instrucción y la salida.
- Los resúmenes de fragmentos deben conservar nombres, números y eventos clave, o se perderá información al fusionar.
- Si el resumen del resumen sigue siendo demasiado largo, aplica una capa de recursión.
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.wushenchaapi.com/v1", api_key=os.environ["API_KEY"])
def split_paragraphs(text, budget):
"""按段落切块,每块估算不超过 budget token。"""
chunks, cur, used = [], [], 0
for p in text.split("\n"):
t = int(len(p) * 1.3) + 1 # 中文粗估
if used + t > budget and cur:
chunks.append("\n".join(cur))
cur, used = [], 0
cur.append(p)
used += t
if cur:
chunks.append("\n".join(cur))
return chunks
def ask(prompt, max_tokens=800):
r = client.chat.completions.create(
model="uncensored",
temperature=0.3,
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content
def summarize_long(text, budget=12000):
parts = split_paragraphs(text, budget)
partial = [ask("用不超过 200 字概括下面这段,保留人名和关键事件:\n\n" + p) for p in parts]
merged = "\n".join(f"第{i+1}部分:{s}" for i, s in enumerate(partial))
return ask("下面是分段摘要,请合并成一份 400 字以内的整体摘要:\n\n" + merged, max_tokens=900)Aquí temperature se fija en 0.3 para mayor estabilidad en tareas de resumen. Las llamadas por fragmento son independientes y pueden ser concurrentes; ten en cuenta el límite de 300 peticiones por minuto por clave, que no se alcanza con documentos de decenas de fragmentos.
No hay una respuesta estándar para el tamaño de los bloques. Como referencia, usa bloques de 10,000 a 15,000 tokens para equilibrar la retención de detalles y el número de llamadas. Ejecuta una muestra con bloques de 5000, 12000 y 25000 tokens y compara cuántos hechos clave faltan en el resumen para elegir el valor adecuado. Para documentos con alta densidad de información, como contratos o técnicos, usa bloques más pequeños; para registros de conversación con mucha redundancia, puedes usar bloques más grandes.
Continuación de novelas: ventana deslizante y resumen dinámico
Al llegar al décimo capítulo, ya no cabe todo el texto y tampoco es necesario. La idea es usar dos niveles de memoria:
- Primer plano: Los últimos dos o tres capítulos en texto original para mantener coherencia en el estilo, el ritmo del diálogo y los detalles de la escena.
- Contexto lejano: los capítulos anteriores se comprimen en un esquema, conservando relaciones entre personajes, pistas y tramas pendientes.
Al terminar un capítulo, pide al modelo que lo resuma en tres o cinco líneas y añádelo al esquema. Si el esquema supera los 3000 tokens, comprímelo nuevamente.
def continue_story(chapters, outline, new_hint, keep_last=3):
"""chapters: 已写章节列表。只带最近 keep_last 章原文,更早的用 outline(滚动大纲)代替。"""
recent = "\n\n".join(chapters[-keep_last:])
prompt = (
f"【全书大纲(早期章节摘要)】\n{outline}\n\n"
f"【最近章节原文】\n{recent}\n\n"
f"【下一章要求】\n{new_hint}\n\n请写下一章,约 2000 字。"
)
return ask(prompt, max_tokens=4000)Las tramas pendientes son lo más fácil de perder. Añade una sección "tramas pendientes sin resolver" en el esquema y exige que se aborden en la continuación. Incluye nombres de personajes y lugares en una lista fija en el system prompt para evitar que el modelo los cambie al salir de la ventana.
Otro problema al continuar es la deriva de estilo: el modelo se alinea con los capítulos recientes. Si el estilo varía, se amplifica. Solución: añade una muestra de estilo en el system prompt, un fragmento de texto original que te guste, como ancla fija que no se desplace con la ventana.
max_tokens y truncamiento
Hay dos escenarios para que la respuesta se trunque, debes distinguirlos:
finish_reasoneslength: has alcanzado el max_tokens configurado. Aumenta el max_tokens o haz que el modelo escriba por fragmentos: «detente aquí y continúa cuando yo lo indique».- Petición 400 directa: prompt + max_tokens supera 100,000. Solución: acorta la entrada o reduce max_tokens.
Una tabla de referencia útil para el presupuesto:
| Tarea | max_tokens sugerido | prompt disponible (aprox.) |
|---|---|---|
| resumen, extracción | 800 | 63,200 |
| Continuación de un capítulo (aprox. 2000 palabras) | 4000 | 60,000 |
| Salida larga de miles de palabras | 16000 (límite) | 48,000 |
Las salidas largas también se ven afectadas por el tiempo de respuesta. Se recomienda usar streaming para mostrar el contenido mientras se genera.
Cuando la continuación se corta, no envíes simplemente el fragmento al modelo diciendo "continúa". Una opción más robusta es incluir el texto generado como mensaje assistant y añadir un mensaje user: "continúa después de la última frase, sin repetir". Esto asegura una transición natural. Ten en cuenta que esto alarga el prompt y debes revisar el presupuesto.
Lista de verificación para poner en producción contextos largos
- Antes de cada petición, usa la función de estimación para calcular el prompt. Si excede el presupuesto, usa la rama de segmentación.
- Registra el usage en cada respuesta para calibrar continuamente los coeficientes de estimación.
- Coloca el system prompt y el esquema al principio del contenido. Repite los requisitos clave al final.
- Verifica finish_reason. Si es length, emite una alerta o continúa la escritura.
- Establece un límite de retención para el historial de conversaciones. Al excederlo, descarta los mensajes más antiguos en lugar de esperar a que la API devuelva 400.
Contenido relacionado: Estructura del prompt en Cómo escribir prompts, resolución de errores en Manual de códigos de error, y precios en Página de precios.
Recuerda: la ventana de contexto no es memoria a largo plazo. Cada petición lee desde cero; no recuerda la anterior. Si necesitas continuidad, debes enviar el historial tú mismo.