Linguagem natural para comando de shell com uma API de LLM: JSON e custo
Um script Python que pede a um modelo barato um comando de shell como JSON, recusa ou sinaliza pedidos incertos, nunca executa nada e precifica 1.000 requisições.

Nesta página
Peça ao modelo um único objeto JSON com três campos (type, command, explanation), limite a resposta a algumas centenas de tokens e imprima o comando em vez de executá-lo. Em tokens, uma requisição assim é quase toda prompt com uma resposta curta e previsível: sob as premissas abaixo, 1.000 delas custam $0.012 no DeepSeek-V4-Flash.
Digamos que você esteja adicionando uma caixa "descreva o que você quer" a uma CLI interna. Alguém digita encontre arquivos maiores que 100 MB na minha pasta home, e você quer de volta uma linha que a pessoa possa ler, conferir e colar. Surgem duas perguntas: como impedir que o modelo chute ou faça algo perigoso, e quanto isso custa quando cem pessoas usam o dia todo?
Uma tarefa estreita: por que a resposta é curta#
Em 9 de novembro de 2026, um canal do Telegram repercutiu o model card do dale-0.8b, um modelo aberto que faz exatamente esse único trabalho. Segundo o model card no Hugging Face (verificado em 10 de novembro de 2026), o dale-0.8b é um fine-tune completo do Qwen3.5-0.8B sob Apache-2.0 que transforma um pedido em espanhol ou inglês em um comando bash Linux. O autor, Manuel García, relata 79,4% (374 de 471 casos) no próprio conjunto de teste, contra 62,8% do Qwen3.5-9B base e do Gemma 4 E4B-it sem fine-tuning. São números autorrelatados em um único conjunto de teste, então trate-os como um dado, não como um ranking.
O que é útil para nós é o formato da tarefa, não o modelo. O card descreve um system prompt fixo, uma linha de contexto (SO, distro, gerenciador de pacotes, ferramentas) mais o pedido, o raciocínio desligado e uma resposta limitada a 200 tokens. Esse limite é uma das duas alavancas de custo: quando a resposta é um único objeto JSON curto, o lado da saída é curto e previsível, porque o limite o restringe. A outra alavanca é o prompt. Qual parcela da conta cada lado representa, a tabela abaixo mostra; isso varia de modelo para modelo.
Você não precisa de um modelo 0.8B para isso. O mesmo formato funciona em qualquer modelo de API, e é isso que o restante deste artigo constrói.

O formato da resposta: comando, recusa ou incerteza#
O truque é que o modelo sempre tem uma forma legítima de dizer "não". A resposta é um objeto:
{"type": "command | refuse | unsure", "command": "...", "explanation": "..."}command: um comando bash, mais uma explicação de uma frase sobre o que ele faz.refuse: o pedido é destrutivo ou não tem nada a ver com o shell. Nenhum comando.unsure: o pedido é vago demais ("limpe meu disco"). A explicação contém uma pergunta de esclarecimento.
Sem unsure, um pedido vago força um chute, e um rm chutado é o que você lembra. O formato do card tem os mesmos três tipos; reutilizamos a ideia, não o modelo.
Emparelhar os pares
Relacione cada tipo de resposta ao que o script deve fazer
Toque num item à esquerda, depois no seu par
- commandMostrar o comando e a explicação; a pessoa copia e executa por conta própria
- refuseImprimir a explicação e não oferecer nada para executar
- unsureImprimir a pergunta e deixar o usuário reformular
O script#
Um arquivo, o pacote openai, chave em uma variável de ambiente. A base URL e o id completo do modelo vêm da documentação do CheapTokens; ids curtos não são aceitos, então use deepseek-.
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')Execute como python shellhelp.. Três escolhas importam aqui:
- Nenhum
subprocessem lugar algum. O comando é texto na tela. Executá-lo é decisão sua, depois de lê-lo. max_tokens=200, igual ao limite do card. Um objeto JSON cabe facilmente; uma resposta prolixa é cortada em 200 tokens em vez de continuar às suas custas. O limite se aplica à resposta inteira, então se um modelo escrever seu raciocínio, isso pode consumir o limite. Se alguma resposta terminar comfinish_reasonigual alength, aumente um pouco o limite. Mais sobre isso em cortando tokens de saída com max_tokens.- O formato é pedido no prompt e depois analisado defensivamente.
raw_decodeignora uma cerca de código perdida ou uma frase antes do{. Se quiser ir além, veja JSON válido de uma API compatível com OpenAI e a correção para json.JSONDecodeError em uma resposta.
Antes de executar qualquer coisa, verifique se a ordem das etapas está clara:
Colocar em ordem
Coloque as etapas do script na ordem correta
Arraste os itens ou use as setas
- Montar o prompt: texto de sistema fixo, uma linha de contexto, o pedido
- Chamar a API com um max_tokens pequeno
- Analisar o JSON e verificar se type é um dos três
- Mostrar o comando e esperar que a pessoa o execute
Quanto custam 1.000 requisições#
A última linha do script imprime usage. Esse é o número real, e você deve ler o seu próprio. Até tê-lo, trabalhe com uma premissa: o prompt chega a cerca de 400 tokens de entrada (o prompt puro no script tem aproximadamente 150 tokens por uma contagem aproximada; o resto é espaço para os três ou quatro pares de exemplo de pedido e resposta que você provavelmente adicionaria), e a resposta JSON a cerca de 60 tokens de saída.
Para 1.000 requisições, isso dá 400 × 1.000 = 400.000 tokens de entrada e 60 × 1.000 = 60.000 tokens de saída. A tabela precifica exatamente isso para cada modelo do catálogo, com a coluna do OpenRouter ao lado; os números vêm de preços ao vivo, então permanecem atuais.
| Modelo | Entrada $/1M | Saída $/1M | Por solicitação | 1.000 solicitações | OpenRouter, 1.000 solicitações |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.00001 | $0.0104 | $0.0184 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.000012 | $0.012 | $0.0364 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.000029 | $0.029 | $0.348 |
400 tokens de entrada e 60 tokens de saída por solicitação. Os preços por 1M de tokens são atuais e mudam junto com nossas tarifas; a coluna OpenRouter mostra preços de referência para a mesma carga de trabalho.
No total, somando entrada e saída, o DeepSeek-V4-Flash sai mais barato que o OpenRouter para essa carga de trabalho. Em tokens, a requisição é quase toda entrada e a resposta é curta, mas os dois lados são cobrados, então enxugue o prompt e a resposta igualmente. Os preços por modelo estão na página de preços, e o modelo em si é descrito na página do DeepSeek: contexto de 380K, até 32.768 tokens de saída, um preço a qualquer hora.
Uma forma rápida de sentir as proporções:
Verificação rápida
Qual parte da requisição o max_tokens=200 limita?
Mostrar resposta
Resposta: A resposta
max_tokens limita apenas a resposta; o prompt é cobrado como está, então enxugue-o separadamente.
Quer 100.000 requisições por mês? Multiplique por 100: 40.000.000 de entrada e 6.000.000 de saída. Coloque esses dois números em ::cost da mesma forma e leia a linha.
Teste com seus próprios pedidos#
Os 79,4% do card não dizem nada sobre o seu prompt, seu SO ou o jeito dos seus usuários. Escreva 20 pedidos reais com os comandos que você aceitaria e compare, exatamente como em testando um prompt em um pequeno conjunto de avaliação. Inclua alguns que o script deveria recusar ou sinalizar como incertos, como "apague tudo" ou "deixe meu computador mais rápido".
Quando algo dá errado, o sintoma geralmente aponta para a correção:
Could not read the reply as JSON: imprima o texto bruto uma vez. Um prefixo como "Sure!" é tratado; uma resposta cortada pelo limite não é, então verifiquefinish_reason.401ou404: chave, base URL ou id de modelo errados. A lista está em API 401 ou 404: chave, base URL, id do modelo.- Comandos que servem a outro sistema: a linha de contexto é a causa. Mantenha-a precisa (SO, gerenciador de pacotes, ferramentas); ela custa algumas dezenas de tokens e evita um
apterrado em uma máquina Fedora.
Mesma tarefa, outro modelo? Troque MODEL pelo id do GLM ou MiniMax da documentação, mantenha o prompt e compare as contagens de tokens que o script imprime com a tabela acima.



