Du langage naturel à la commande shell avec une API LLM : JSON et coût
Un script Python qui demande à un modèle bon marché une commande shell en JSON, refuse ou signale les demandes incertaines, n'exécute jamais rien et chiffre 1 000 requêtes.

Sur cette page
Demandez au modèle un objet JSON avec trois champs (type, command, explanation), limitez la réponse à quelques centaines de tokens et affichez la commande au lieu de l'exécuter. En tokens, une requête de ce type est surtout du prompt avec une réponse courte et prévisible : dans les hypothèses ci-dessous, 1 000 d'entre elles coûtent $0.012 sur DeepSeek-V4-Flash.
Imaginez que vous ajoutiez une zone « décrivez ce que vous voulez » à un CLI interne. Quelqu'un tape trouve les fichiers de plus de 100 Mo dans mon dossier personnel, et vous voulez en retour une ligne qu'il peut lire, vérifier et coller. Deux questions suivent : comment empêcher le modèle de deviner ou de faire quelque chose de dangereux, et combien cela coûte quand cent personnes l'utilisent toute la journée ?
Une tâche étroite : pourquoi la réponse est courte#
Le 9 novembre 2026, un canal Telegram a relayé la fiche modèle de dale-0.8b, un modèle ouvert qui fait exactement ce seul travail. D'après la fiche modèle sur Hugging Face (vérifiée le 10 novembre 2026), dale-0.8b est un fine-tune complet de Qwen3.5-0.8B sous Apache-2.0 qui transforme une demande en espagnol ou en anglais en une commande bash Linux. L'auteur, Manuel García, rapporte 79,4 % (374 cas sur 471) sur son propre jeu de test, contre 62,8 % pour Qwen3.5-9B base et pour Gemma 4 E4B-it sans fine-tuning. Ce sont des chiffres auto-déclarés sur le jeu de test d'une seule personne : à traiter comme un point de données, pas comme un classement.
Ce qui nous est utile, c'est la forme de la tâche, pas le modèle. La fiche décrit un prompt système fixe, une ligne de contexte (OS, distribution, gestionnaire de paquets, outils) plus la demande, la réflexion désactivée et une réponse plafonnée à 200 tokens. Ce plafond est l'un des deux leviers sur le coût : quand la réponse est un court objet JSON, le côté sortie est court et prévisible, car le plafond le limite. L'autre levier est le prompt. La part de chacun dans la facture, le tableau ci-dessous la montre ; elle varie d'un modèle à l'autre.
Vous n'avez pas besoin d'un modèle 0.8B pour cela. Le même format fonctionne sur n'importe quel modèle d'API, et c'est ce que construit le reste de cet article.

Le format de réponse : command, refuse ou unsure#
L'astuce, c'est que le modèle a toujours un moyen légal de dire « non ». La réponse est un objet :
{"type": "command | refuse | unsure", "command": "...", "explanation": "..."}command: une commande bash, plus une explication en une phrase de ce qu'elle fait.refuse: la demande est destructrice ou n'a rien à voir avec le shell. Pas de commande.unsure: la demande est trop vague (« nettoie mon disque »). L'explication contient une question de clarification.
Sans unsure, une demande vague force à deviner, et un rm deviné est celui dont on se souvient. Le format de la fiche a les mêmes trois types ; on réutilise l'idée, pas le modèle.
Associer les paires
Associez chaque type de réponse à ce que le script doit faire
Touchez un élément à gauche, puis sa paire
- commandAfficher la commande et l'explication ; la personne la copie et l'exécute elle-même
- refuseAfficher l'explication et ne rien proposer à exécuter
- unsureAfficher la question et laisser l'utilisateur reformuler
Le script#
Un fichier, le paquet openai, la clé dans une variable d'environnement. L'URL de base et l'id complet du modèle viennent de la doc CheapTokens ; les ids courts ne sont pas acceptés, utilisez donc 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')Lancez-le avec python shellhelp.. Trois choix comptent ici :
- Aucun
subprocessnulle part. La commande est du texte à l'écran. L'exécuter est votre décision, après l'avoir lue. max_tokens=200, comme le plafond de la fiche. Un objet JSON tient facilement ; une réponse qui divague est coupée à 200 tokens au lieu de continuer à vos frais. Le plafond s'applique à toute la réponse, donc si un modèle écrit son raisonnement, cela peut manger la limite. Si une réponse se termine avecfinish_reasonégal àlength, augmentez un peu le plafond. Plus de détails dans réduire les tokens de sortie avec max_tokens.- Le format est demandé dans le prompt, puis analysé de façon défensive.
raw_decodesaute une clôture de code parasite ou une phrase avant le{. Pour aller plus loin, voir du JSON valide depuis une API compatible OpenAI et le correctif pour json.JSONDecodeError sur une réponse.
Avant d'exécuter quoi que ce soit, vérifiez que l'ordre des étapes est clair :
Remettre en ordre
Remettez les étapes du script dans le bon ordre
Faites glisser les éléments ou utilisez les flèches
- Construire le prompt : texte système fixe, une ligne de contexte, la demande
- Appeler l'API avec un petit max_tokens
- Analyser le JSON et vérifier que type est l'un des trois
- Afficher la commande et attendre que la personne l'exécute
Ce que coûtent 1 000 requêtes#
La dernière ligne du script affiche usage. C'est le vrai chiffre, et vous devriez lire le vôtre. En attendant de l'avoir, partez d'une hypothèse : le prompt fait environ 400 tokens d'entrée (le prompt nu du script fait à peu près 150 tokens au compteur ; le reste est de la place pour les trois ou quatre paires exemple-demande-réponse que vous ajouteriez probablement), et la réponse JSON environ 60 tokens de sortie.
Pour 1 000 requêtes, cela fait 400 × 1 000 = 400 000 tokens d'entrée et 60 × 1 000 = 60 000 tokens de sortie. Le tableau chiffre exactement cela pour chaque modèle du catalogue, avec la colonne OpenRouter à côté ; les chiffres viennent des prix en direct, donc ils restent à jour.
| Modèle | Entrée $/1M | Sortie $/1M | Par requête | 1 000 requêtes | OpenRouter, 1 000 requêtes |
|---|---|---|---|---|---|
| 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 d'entrée et 60 tokens de sortie par requête. Les prix par million de tokens sont actuels et évoluent avec nos tarifs ; la colonne OpenRouter indique des prix de référence pour la même charge de travail.
Au total, entrée et sortie comptées ensemble, DeepSeek-V4-Flash revient moins cher qu'OpenRouter pour cette charge. En tokens, la requête est presque toute en entrée et la réponse est courte, mais les deux côtés sont facturés : allégez donc le prompt comme la réponse. Les prix par modèle sont sur la page de tarifs, et le modèle lui-même est décrit sur la page DeepSeek : contexte de 380K, jusqu'à 32 768 tokens de sortie, un seul prix à toute heure.
Un moyen rapide de sentir les proportions :
Vérification rapide
Quelle partie de la requête max_tokens=200 limite-t-il ?
Afficher la réponse
Réponse: La réponse
max_tokens ne limite que la réponse ; le prompt est facturé tel quel, allégez-le séparément.
Vous voulez 100 000 requêtes par mois ? Multipliez par 100 : 40 000 000 en entrée et 6 000 000 en sortie. Mettez ces deux nombres dans ::cost de la même façon et lisez la ligne.
Vérifiez-le sur vos propres requêtes#
Les 79,4 % de la fiche ne disent rien de votre prompt, de votre OS ni du vocabulaire de vos utilisateurs. Écrivez 20 vraies requêtes avec les commandes que vous accepteriez et comparez, exactement comme dans tester un prompt sur un petit jeu d'éval. Incluez-en quelques-unes que le script devrait refuser ou signaler comme incertaines, comme « supprime tout » ou « rends mon ordinateur plus rapide ».
Quand quelque chose cloche, le symptôme indique généralement le correctif :
Could not read the reply as JSON: affichez une fois le texte brut. Un préfixe comme « Bien sûr ! » est géré ; une réponse coupée par le plafond ne l'est pas, vérifiez doncfinish_reason.401ou404: mauvaise clé, URL de base ou id de modèle. La liste est dans API 401 ou 404 : clé, URL de base, id de modèle.- Des commandes adaptées à un autre système : la ligne de contexte en est la cause. Gardez-la exacte (OS, gestionnaire de paquets, outils) ; elle coûte quelques dizaines de tokens et évite un mauvais
aptsur une machine Fedora.
Même tâche, autre modèle ? Remplacez MODEL par l'id GLM ou MiniMax de la doc, gardez le prompt, et comparez les comptes de tokens affichés par le script avec le tableau ci-dessus.



