Volver al blog

Optimización de costosGuía

Lenguaje natural a comando de shell con una API de LLM: JSON y coste

Un script de Python que pide a un modelo barato un comando de shell como JSON, rechaza o marca las peticiones dudosas, nunca ejecuta nada y pone precio a 1.000 peticiones.

Por CheapTokens TeamPublicado el 7 min de lectura

Una petición en lenguaje natural convertida en un comando de shell dentro de un objeto JSON con los tipos command, refuse y unsure
En esta página
  1. Una tarea acotada: por qué la respuesta es corta
  2. El formato de respuesta: comando, rechazo o duda
  3. El script
  4. Cuánto cuestan 1.000 peticiones
  5. Compruébalo con tus propias peticiones

Pide al modelo un único objeto JSON con tres campos (type, command, explanation), limita la respuesta a un par de cientos de tokens e imprime el comando en lugar de ejecutarlo. En tokens, una petición así es sobre todo prompt con una respuesta corta y predecible: bajo los supuestos de abajo, 1.000 de ellas cuestan $0.012 en DeepSeek-V4-Flash.

Supón que estás añadiendo un cuadro de "describe lo que quieres" a una CLI interna. Alguien escribe encuentra archivos de más de 100 MB en mi carpeta personal, y quieres de vuelta una línea que pueda leer, comprobar y pegar. Se plantean dos preguntas: cómo evitas que el modelo adivine o haga algo peligroso, y cuánto cuesta esto cuando cien personas lo usan todo el día.

Una tarea acotada: por qué la respuesta es corta#

El 9 de noviembre de 2026 un canal de Telegram recontó la model card de dale-0.8b, un modelo abierto que hace exactamente este único trabajo. Según la model card en Hugging Face (consultada el 10 de noviembre de 2026), dale-0.8b es un fine-tune completo de Qwen3.5-0.8B bajo Apache-2.0 que convierte una petición en español o inglés en un único comando bash de Linux. El autor, Manuel García, reporta un 79,4% (374 de 471 casos) en su propio conjunto de test, frente al 62,8% de Qwen3.5-9B base y de Gemma 4 E4B-it sin fine-tuning. Son cifras autoinformadas sobre el conjunto de test de una persona, así que tómalas como un dato, no como un ranking.

Lo que nos resulta útil es la forma de la tarea, no el modelo. La card describe un system prompt fijo, una línea de contexto (SO, distro, gestor de paquetes, herramientas) más la petición, el thinking desactivado y una respuesta limitada a 200 tokens. Ese límite es una de las dos palancas del coste: cuando la respuesta es un objeto JSON corto, el lado de la salida es corto y predecible, porque el límite lo acota. La otra palanca es el prompt. Qué parte de la factura aporta cada lado lo muestra la tabla de abajo; varía de un modelo a otro.

No necesitas un modelo de 0,8B para eso. El mismo formato funciona en cualquier modelo de API, y eso es lo que construye el resto de este artículo.

El recorrido de una petición: entra el prompt, sale JSON, decide una persona
El flujo de la petición en el script: no se ejecuta nada hasta que lo hagas tú mismo.

El formato de respuesta: comando, rechazo o duda#

El truco es que el modelo siempre tiene una forma legítima de decir "no". La respuesta es un objeto:

json
{"type": "command | refuse | unsure", "command": "...", "explanation": "..."}
  • command: un comando bash, más una explicación de una frase sobre lo que hace.
  • refuse: la petición es destructiva o no tiene nada que ver con el shell. Sin comando.
  • unsure: la petición es demasiado vaga ("limpia mi disco"). La explicación contiene una pregunta aclaratoria.

Sin unsure, una petición vaga obliga a adivinar, y un rm adivinado es el que se recuerda. El formato de la card tiene los mismos tres tipos; reutilizamos la idea, no el modelo.

Emparejar los pares

Empareja cada tipo de respuesta con lo que debe hacer el script

  • commandMuestra el comando y la explicación; la persona lo copia y lo ejecuta por su cuenta
  • refuseImprime la explicación y no ofrece nada que ejecutar
  • unsureImprime la pregunta y deja que el usuario la reformule

El script#

Un archivo, el paquete openai, la clave en una variable de entorno. La URL base y el id completo del modelo vienen de la documentación de CheapTokens; los ids cortos no se aceptan, así que usa deepseek-ai/DeepSeek-V4-Flash-0731.

python
import json
import os
import sys

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'

SYSTEM = '''You turn a request into ONE Linux bash command.
Reply with a single JSON object and nothing else:
{"type": "command" | "refuse" | "unsure", "command": "<one command, or empty>", "explanation": "<one short sentence>"}
Use "refuse" when the request is destructive or not about the shell.
Use "unsure" when it is too vague, and put a clarifying question in "explanation".'''

CONTEXT = 'os: Ubuntu 24.04; package manager: apt; tools: git, curl, jq'


def parse(text):
    start = text.find('{')
    if start == -1:
        return None
    try:
        obj, _ = json.JSONDecoder().raw_decode(text[start:])
    except json.JSONDecodeError:
        return None
    if not isinstance(obj, dict) or obj.get('type') not in ('command', 'refuse', 'unsure'):
        return None
    return obj


def ask(request):
    resp = client.chat.completions.create(
        model=MODEL,
        max_tokens=200,
        temperature=0,
        messages=[
            {'role': 'system', 'content': SYSTEM},
            {'role': 'user', 'content': f'Context: {CONTEXT}\nRequest: {request}'},
        ],
    )
    return parse(resp.choices[0].message.content), resp.usage


if __name__ == '__main__':
    result, usage = ask(' '.join(sys.argv[1:]))
    if result is None:
        print('Could not read the reply as JSON. Try again.')
    elif result['type'] == 'command':
        print(result['explanation'])
        print('\n    ' + result['command'] + '\n')
        print('Read it, then copy it yourself. This script never runs it.')
    else:
        print(f"[{result['type']}] {result['explanation']}")
    print(f'tokens: {usage.prompt_tokens} in, {usage.completion_tokens} out')

Ejecútalo como python shellhelp.py find files bigger than 100 MB in my home folder. Aquí importan tres decisiones:

  • Ningún subprocess en ningún sitio. El comando es texto en pantalla. Ejecutarlo es decisión tuya, después de leerlo.
  • max_tokens=200, igual que el límite de la card. Un objeto JSON cabe de sobra; una respuesta divagante se corta a los 200 tokens en lugar de seguir a tu costa. El límite se aplica a toda la respuesta, así que si un modelo escribe su razonamiento, eso puede comerse el límite. Si alguna respuesta termina con finish_reason igual a length, sube un poco el límite. Más sobre esto en recortar tokens de salida con max_tokens.
  • El formato se pide en el prompt y luego se parsea de forma defensiva. raw_decode se salta un bloque de código suelto o una frase antes del {. Si quieres ir más allá, consulta JSON válido desde una API compatible con OpenAI y la solución para json.JSONDecodeError en una respuesta.

Antes de ejecutar nada, comprueba que el orden de los pasos está claro:

Poner en orden

Pon los pasos del script en el orden correcto

  1. Construir el prompt: texto de sistema fijo, una línea de contexto, la petición
  2. Llamar a la API con un max_tokens pequeño
  3. Parsear el JSON y comprobar que type es uno de los tres
  4. Mostrar el comando y esperar a que la persona lo ejecute

Cuánto cuestan 1.000 peticiones#

La última línea del script imprime usage. Ese es el número real, y deberías leer el tuyo. Hasta que lo tengas, trabaja con un supuesto: el prompt viene a ser unos 400 tokens de entrada (el prompt pelado del script son aproximadamente 150 tokens según un cálculo aproximado; el resto es margen para los tres o cuatro pares de ejemplo petición-respuesta que probablemente añadirías), y la respuesta JSON unos 60 tokens de salida.

Para 1.000 peticiones eso son 400 × 1.000 = 400.000 tokens de entrada y 60 × 1.000 = 60.000 tokens de salida. La tabla pone precio exactamente a eso para cada modelo del catálogo, con la columna de OpenRouter al lado; las cifras vienen de precios en vivo, así que se mantienen al día.

Modelo Por solicitud 1000 solicitudes OpenRouter, 1000 solicitudes
GLM-5.3-Flash $0.00001 $0.0104 $0.0184
DeepSeek-V4-Flash $0.000012 $0.012 $0.0364
MiniMax-M2.7 $0.000029 $0.029 $0.348

400 tokens de entrada y 60 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.

En total, contando entrada y salida juntas, DeepSeek-V4-Flash sale más barato que OpenRouter para esta carga de trabajo. En tokens la petición es casi toda entrada y la respuesta es corta, pero ambos lados se facturan, así que recorta el prompt y la respuesta por igual. Los precios por modelo están en la página de precios, y el modelo en sí se describe en la página de DeepSeek: 380K de contexto, hasta 32.768 tokens de salida, un único precio a cualquier hora.

Una forma rápida de captar las proporciones:

Verificación rápida

¿Qué parte de la petición limita max_tokens=200?

Mostrar respuesta

Respuesta: La respuesta

max_tokens limita solo la respuesta; el prompt se factura tal cual, así que recórtalo por separado.

¿Quieres 100.000 peticiones al mes? Multiplica por 100: 40.000.000 de entrada y 6.000.000 de salida. Mete esos dos números en ::cost de la misma forma y lee la fila.

Compruébalo con tus propias peticiones#

El 79,4% de la card no dice nada sobre tu prompt, tu SO ni el lenguaje de tus usuarios. Escribe 20 peticiones reales con los comandos que aceptarías y compara, exactamente como en probar un prompt con un conjunto de eval pequeño. Incluye algunas que el script debería rechazar o marcar como dudosas, como "borra todo" o "haz mi ordenador más rápido".

Cuando algo falla, el síntoma suele señalar la solución:

  • Could not read the reply as JSON: imprime el texto en bruto una vez. Un prefijo como "¡Claro!" se maneja; una respuesta cortada por el límite no, así que revisa finish_reason.
  • 401 o 404: clave, URL base o id de modelo incorrectos. La lista está en API 401 o 404: clave, URL base, id de modelo.
  • Comandos que encajan con otro sistema: la línea de contexto es la causa. Mantenla precisa (SO, gestor de paquetes, herramientas); cuesta unas pocas docenas de tokens y evita un apt equivocado en una máquina Fedora.

¿Misma tarea, otro modelo? Cambia MODEL por el id de GLM o MiniMax de la documentación, mantén el prompt y compara los recuentos de tokens que imprime el script con la tabla de arriba.