Inicio / Artículos / El bucle de siguiente token: un modelo mental de los LLM antes de interactuar con agentes

El bucle de siguiente token: un modelo mental de los LLM antes de interactuar con agentes

Aprenda cómo funcionan los tokens, las ventanas de contexto, el muestreo y el bucle de generación, a través de pequeños ejemplos en Python sin conexión que explican por qué existen RAG, ReAct y LangGraph.

1143 palabras

Los ingenieros que interactúan con los LLM a través de un framework como LangChain a menudo no pueden determinar, cuando algo falla, si el problema radica en el framework, en la API que lo rodea o en el modelo en sí. La solución es contar con un modelo mental claro: un LLM es una función de predicción del siguiente token que se llama repetidamente en un bucle. Con esta visión, RAG es una forma de elegir qué se introduce en la función, ReAct es un método para analizar lo que sale y LangGraph es una forma de orquestar múltiples llamadas. Los ejemplos a continuación se ejecutan localmente con Python y una biblioteca pequeña; no se necesita clave de API.

La generación consiste en la predicción del siguiente token en un bucle

Dada una secuencia de tokens, el modelo devuelve una probabilidad para cada token siguiente posible. Se elige uno, se agrega y la secuencia más larga vuelve a introducirse en el proceso. Esto se repite hasta que aparece un token de detención o se alcanza un límite de longitud. La salida en flujo simplemente muestra este bucle token por token.

Se derivan dos consecuencias:

  • No existe un plan. Cada token depende únicamente de los tokens que lo preceden.
  • No hay memoria entre llamadas. Cada solicitud comienza exactamente con los tokens que envías, y esa es la razón por la cual existe la generación mejorada con recuperación de información.

Los tokens son fragmentos subpalabras, no palabras

Un modelo percibe números enteros, no texto. Un tokenizador mapea cadenas de texto a IDs de tokens y viceversa. Las palabras frecuentes suelen convertirse en un único token, mientras que las palabras poco comunes y el código se dividen en varios. Una estimación útil: un token equivale aproximadamente a tres cuartos de una palabra.

El fragmento carga o200k_base, la codificación de la familia gpt-4o, a través de tiktoken; codifica una oración y muestra cada ID junto con su fragmento de texto. Observa que “LangGraph” se convierte en dos tokens y que los espacios al inicio pertenecen al token siguiente.

import tiktoken

# tokenizer of the gpt-4o model family
enc = tiktoken.get_encoding("o200k_base")

ids = enc.encode("LangGraph orchestrates agents.")
print(ids)
# [30741, 9922, 109873, 1381, 19297, 13]

for i in ids:
    print(i, repr(enc.decode([i])))
# 'Lang' 'Graph' ' orchestr' 'ates' ' agents' '.'

El entrenamiento y la inferencia son fases separadas

El entrenamiento ajusta los pesos al predecir los tokens siguientes a partir de grandes cantidades de texto. La inferencia ejecuta el modelo ya confeccionado. Tu prompt nunca modifica los pesos, por lo que la información que le falta al modelo debe incluirse en el prompt en el momento de la inferencia. Eso es exactamente lo que hace RAG.

La ventana de contexto es todo el mundo del modelo

Una sola llamada solo puede procesar una cierta cantidad de tokens, y ese límite, la ventana de contexto, abarca tanto el prompt como la salida generada. Todo lo que está fuera de ella no existe para esa llamada. La conversación parece continua solo porque la aplicación vuelve a enviar los mensajes anteriores cada vez, y RAG existe porque las colecciones de documentos no caben en la ventana, por lo que se recuperan solo algunos fragmentos relevantes.

El presupuesteo es una aritmética sencilla. La herramienta cuenta los tokens de entrada con el tokenizador mencionado anteriormente y los resta, además de una reserva para la respuesta, de un rango de 128,000 tokens, que es el valor indicado para gpt-4o-mini. Los límites difieren entre modelos y versiones, así que consulte la documentación actual de su proveedor.

def count_tokens(text: str) -> int:
    return len(enc.encode(text))

prompt = "Summarize the attached design doc."
window = 128_000     # e.g. gpt-4o-mini
reserve = 1_000      # room for the answer
used = count_tokens(prompt)
print("left:", window - reserve - used)

La temperatura controla cómo se elige el siguiente token

El muestreo selecciona un token de la distribución del modelo, y la temperatura modifica esa distribución. Cerca de cero, casi siempre gana el token más probable. A 1.0 o más, los tokens menos probables tienen una verdadera oportunidad. Utilice una temperatura de 0 para la extracción, la salida estructurada y los agentes que llaman a herramientas, y aproximadamente de 0.7 a 1.0 para la redacción y el brainstorming. Nuestra guía sobre la temperatura, top-k y top-p aborda los otros parámetros de muestreo.

Construyendo el bucle en miniatura

El siguiente ejemplo combina un tokenizador real con un modelo falso creado a partir de una tabla de consulta. Es muy pequeño, pero sigue el mismo ciclo que un LLM en producción: codificar, predecir, añadir y detenerse.

Primero, se tokeniza un pequeño corpus y se cuenta cada par adyacente de IDs. Para cada token, la tabla solo conserva su sucesor más frecuente, lo que convierte a este en un predictor codicioso, equivalente a una temperatura de cero.

CORPUS = (
    "the agent calls the model. "
    "the model returns a token. "
    "the agent calls the tool. "
    "the tool returns a result."
)

ids = enc.encode(CORPUS)
counts = {}
for a, b in zip(ids, ids[1:]):
    counts.setdefault(a, {})
    counts[a][b] = counts[a].get(b, 0) + 1

# greedy "model": token -> likeliest successor
table = {
    a: max(s, key=s.get)
    for a, s in counts.items()
}

El bucle codifica una prompt, busca el sucesor más probable del último token, lo añade y muestra el texto decodificado. Si falta un sucesor, se devuelve None, que sirve como token de detención. La línea clave es la adición: la salida se convierte en entrada.

seq = enc.encode("the agent calls")
for _ in range(3):
    nxt = table.get(seq[-1])
    if nxt is None:    # our toy "stop token"
        break
    seq.append(nxt)    # output becomes input
    print(enc.decode(seq))

La secuencia crece un token por paso:

the agent calls the
the agent calls the model
the agent calls the model.

Un detalle es ilustrativo. En el corpus, “the” va seguido por “model” y “tool” con la misma frecuencia, y max elige al que ve primero. Los modelos reales se enfrentan constantemente a tales empates, por lo que los ajustes de muestreo son importantes.

Ejecutar los ejemplos

Reúna los fragmentos en un único script, por ejemplo examples/part01_tokens.py, junto con un requirements.txt que incluya tiktoken. Este script imprime las divisiones de tokens, realiza los cálculos relacionados con el presupuesto y ejecuta el bucle de ejemplo en aproximadamente un segundo, de forma offline una vez que los datos del tokenizador están almacenados en caché. Luego cambie el corpus y observe cómo varía la salida.

pip install -r requirements.txt
python examples/part01_tokens.py

Conclusiones clave

  • Un LLM es una función fija que convierte una secuencia de tokens en una distribución de tokens siguientes, ejecutada dentro de un bucle de retroalimentación.
  • Solo conoce sus pesos más la ventana de contexto y nunca aprende de su prompt.
  • La variedad proviene del muestreo, controlado por la temperatura.
  • Las técnicas de agente deciden qué tokens se incluyen y qué ocurre con los tokens que salen.
  • Lecturas relacionadas