Journal 'gégé' : enfin de l'IA dans ton shell !

Posté par  (Mastodon) . Licence CC By‑SA.
11
24
juil.
2026

Sommaire

Salut les moule·e·s

Introduction

Je suis en train de passer un nouveau step dans mon parcours initiatique sur l'IA en jouant plus sérieusement avec les IA locales. Après mes balbutiements avec ollama, j'ai été convaincu par les différents journaux de Jérôme (merci !), notamment celui-ci de passer à l'étape d'après : llama.cpp et llama-swap.

On peut commencer à découvrir et apprendre sans aucun setup particulier : un modèle comme Qwen2.5-0.5B pèse quelque chose comme 500Mo en RAM et peut tourner sur un CPU ordinaire. L'avantage d'un modèle aussi petit c'est qu'il est bourré de défauts, et que c'est via les paramètres qu'on donnera à llama.cpp qu'on va essayer de les contenir au mieux. C'est vraiment intéressant à faire, je conseille à tous les curieux de l'IA de faire l'expérience.

Le standard OpenAI

Les façons de parler à un LLM (localement hébergé ou grassement payé à distance) sont plus ou moins standardisées (ce sont plutôt des standards de fait apportés par les acteurs les plus endettés principaux du domaine), et llama-swap propose l'API de OpenAI (ChatGPT). En gros tu prépares un fichier JSON avec la requête et quelques paramètres, tu envoies ça via une requête HTTP POST, et tu récupères un JSON de résultat. Pas plus compliqué que ça.

Par exemple une simple commande curl permet d'interroger son LLM :

curl http://localhost:8080/v1/chat/completions\
    -H "Content-Type: application/json"\
    -d '{
        "model": "small",
        "messages": [
        {"role": "user", "content": "récite un poème à propos d'\''une moule et d'\''un clavier BEPO"}
        ]
    }'

En retour on va recevoir quelque-chose comme (j'ai abrégé la réponse, ne vous inquiétez pas vous ne ratez rien):

{"choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"Dans les bras de la machine qui ne sait pas de langue,\nL'oreille s'épanouit sous l'action du clavier.\n[...]"}}],"created":1784911562,"model":"/disk/ia/models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf","system_fingerprint":"b10099-1a064ab09","object":"chat.completion","usage":{"completion_tokens":232,"prompt_tokens":48,"total_tokens":280,"prompt_tokens_details":{"cached_tokens":47}},"id":"chatcmpl-7tqzmDMzb8SKBr7xjSxUE4yvUrxvUhIz","timings":{"cache_n":47,"prompt_n":1,"prompt_ms":11.56,"prompt_per_token_ms":11.56,"prompt_per_second":86.50519031141869,"predicted_n":232,"predicted_ms":1101.727,"predicted_per_token_ms":4.748823275862069,"predicted_per_second":210.5784826912656}}

Script Python

Toujours dans le but de mettre les mains dans le cambouis, j'ai eu l'idée de faire un petit script Python qui gérerait ce petit manège question/réponse, et qui s’insérerait dans un flot de pipe shell. Il prend du contenu sur le stdin, il prend un prompt en paramètre, envoie tout ça à l'inférence locale, et affiche le résultat. C'est tellement simple que j'ai pu le faire sans aucune dépendance Python, au prix d'un code un poil plus moche toutefois : il existe des modules python dédiés qui vous feront faire la requête en une seule ligne, alors que là je la monte à la mimine (tout comme la mayonnaise) (j'ai d'ailleurs une méthode inratable, faudra que j'en fasse un journal tiens).

L'idée c'est de se faire un alias avec le petit nom que vous voulez. Par exemple alias gégé="python <chemin>/ia.py" et voilà, vous avez gégé dans votre shell qui vous résoudra tous vos problèmes, travaillera à votre place, et fera de vous une personne riche et respectée, achetez mon livre.

Exemples d'utilisation

Et le pire c'est que j'ai trouvé des exemples (presque) utiles:

  • cat l_albatros.txt | gégé "traduis en Espagnol" > el_albatros.txt
  • curl -fsSL https://random.malware.com/install.sh | gégé "est-ce que ce script est dangereux ?"
  • cat /proc/cpuinfo | gégé "quelles sont les failles connues de mon CPU ?"
  • cat ia.py | gégé "écris un script bash équivalent"

Même avec le tout petit Qwen2.5-0.7B on arrive à quelque chose. Certaines de ces requêtes marchent plutôt bien, d'autres ont un résultat farfelu, je vous laisse expérimenter ;).

Le script

import sys
import json
import argparse
import urllib.request

def main():
    parser = argparse.ArgumentParser(description='Send stdin and a prompt to an OpenAI-compatible API')
    parser.add_argument('prompt', nargs='?', help='The prompt to send')
    parser.add_argument('--model', default='small', help='Model name to use')
    args = parser.parse_args()

    if not args.prompt:
        print("Usage: cat <file> | ai [--model MODEL] '<prompt>'", file=sys.stderr)
        sys.exit(1)

    prompt = args.prompt
    input_content = sys.stdin.read()
    model = args.model

    payload = {
        "model": model,
        "messages": [{"role": "user", "content": f"{input_content}\n\n{prompt}"}],
        "stream": False
    }

    request = urllib.request.Request(
        "http://localhost:8080/v1/chat/completions",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
        method="POST"
    )
    with urllib.request.urlopen(request, timeout=600) as response:
        data = json.load(response)
    print(data["choices"][0]["message"]["content"])

if __name__ == "__main__":
    main()

Et c'est tout.

Pour tout dire mon script actuel est beaucoup plus évolué que ça, mais je ne le partage pas, ça n'a pas vraiment d'intérêt, il vaut vraiment mieux se le bricoler au fur-et-à-mesure des idées (et si vous avez les moyen de faire tourner un Qwen3.6-9B ou mieux en 27B, il pourra même vous y aider !). Par exemple dans les dernières versions je cherche des magic en début de flot stdin pour détecter des images. À ce moment je bascule sur un autre LLM qui sait prendre des images en entrée, et j'ai donc accès à des commandes comme cat chat_qui_pilote_une_formule1.png | gégé "est-ce que cette photo a été générée par IA ?". Je gère un prompt système (du style "réponds brièvement"), j'affiche aussi quelques statistiques de la requête sur stderr etc.

La prochaine étape est d'entrer dans l'agentique : le script va pouvoir exécuter les commandes envoyées par le LLM (avec tout le danger qui va avec bien évidemment !). Mais du coup je pourrai bien voir comment marche un agent au moins pour la base.

Conclusion

Héberger une IA locale c'est aujourd'hui facile, et ça permet vraiment de toucher du doigt toutes les horreurs imperfections du domaine. On comprend aussi très vite le côté "potion magique" des différents paramètres utilisés : tu essaies un truc parce que tu l'as lu sur Reddit, tu regardes si c'est mieux (c'est quoi au juste "mieux" ?), tu essaies autre chose etc.

Je n'ai pas donné non plus la configuration llama-swap associée (pas vraiment d'intérêt non plus), mais je dois en être à une dizaine de paramètres que je comprends, sans toutefois les maîtriser. Mais pour ça j'attends les prochains journaux de Jérôme :)

En attendant si vous voulez vous amuser et comprendre, bidouiller ce petit bout de script Python est, je pense, une bonne entrée en matière.

  • # llama.cpp router mode

    Posté par  (site web personnel) . Évalué à 4 (+3/-0).

    Intéressant article, merci pour le partage du script.

    Une remarque technique : ton stack avec llama-swap est un peu lourde pour l'usage que tu en fais. llama-server dispose depuis quelques versions d'un mode router intégré (--router) qui gère le chargement/déchargement dynamique des modèles sans redémarrage, sans dépendance externe.

    Pour ton cas d'usage (un modèle, usage local, expérimentation), un simple :

    llama-server --models-dir /chemin/vers/models --host 127.0.0.1 --port 8080 --models-max 1

    suffirait … ou via un fichier preset INI avec --models-preset.

    Ton script gégé fonctionne identiquement sur le port 8080, avec un seul processus et aucune couche intermédiaire.

    llama-swap apporte de la valeur quand on mixe plusieurs backends (vLLM, SGLang, etc.) ou qu'on a besoin de supervision de processus en production. Pour bricoter et apprendre, ça ajoute une abstraction inutile qui complexifie le débug.

    wind0w$ suxX, GNU/Linux roxX!

    • [^] # Re: llama.cpp router mode

      Posté par  (Mastodon) . Évalué à 5 (+2/-0).

      Alors justement j'utilise plusieurs modèles en parallèle (pas tous en même temps, mais d'une requête à l'autre c'est pas forcément le même que je vais utiliser). Un tout petit (le qwen 0.7B) sur ma carte graphique, le même en pure CPU (histoire de pas raconter des craques), et un plus large sur la CG (histoire d'avoir un truc réellement utilisable).

      Donc pour ce style d'utilisation (mono utilisateur, mono modèle à un instant T, mais une base de plusieurs modèles), llama-server suffirait ?

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

      • [^] # Re: llama.cpp router mode

        Posté par  (site web personnel) . Évalué à 6 (+5/-0).

        Oui, c'est comme ça que je l'utilise avec un fichier .ini :

        La commande pour lancer llama.cpp en mode routeur :

        ./build/bin/llama-server \
          --log-colors off --log-verbosity 4 \
          --log-file /tmp/llama.cpp-router.log \
          --models-max 1 \
          --models-preset llama.cpp-router-config-llm.ini \
          --parallel 1 \
          --threads 10 \
          --threads-batch 10 \
          --batch-size 4096 \
          --ubatch-size 1024 \
          --main-gpu 0 \
          --n-gpu-layers -1 \
          --spec-draft-ngl -1 \
          --host 127.0.0.1 \
          --port 5000 \
          --flash-attn on \
          --sleep-idle-seconds 60 \
          --no-mmap \
          --mlock

        Points clés :
        - Binaire : ./build/bin/llama-server
        - Config preset : llama.cpp-router-config-llm.ini (définit les modèles disponibles)
        - Mode routeur : activé via --models-preset avec un fichier INI listant les modèles
        - GPU : toutes les couches sur GPU (-1), GPU principal 0
        - Flash attention : activée
        - Cache KV : type-k q8_0, type-v q5_1, réutilisation 256 (dans les versions commentées)
        - Mise en veille : 60s d'inactivité avant libération
        - Logs : verbosité 4, couleurs désactivées, écrit dans /tmp/llama.cpp-router.log

        Le fichier .ini :

        ; https://github.com/ggml-org/llama.cpp/pull/17859
        version = 1
        
        ; Global presets
        [*]
        fit = on
        jinja = on
        cpu-moe = false
        spec-draft-cpu-moe = false
        no-cache-idle-slots = true
        keep = 4096
        ctx-checkpoints = 12
        checkpoint-min-step = 1024
        spec-draft-type-k = q8_0
        spec-draft-type-v = q5_1
        cache-type-k = q8_0
        cache-type-v = q5_1
        kv-unified = true
        reasoning-budget = 2048
        reasoning-budget-message = ... okay, now here is the answer.
        
        ; Preset for Qwen3.5-4B-MTP-Q4_K_M.gguf
        [Qwen3.5-4B-MTP-Q4_K_M]
        model = /chemin/vers/Qwen3.5-4B-MTP-Q4_K_M.gguf
        fit-ctx = 0
        temp = 0.7
        top-p = 0.95
        top-k = 20
        min-p = 0.0
        presence-penalty = 1.5
        repeat-penalty = 1.0
        spec-type = draft-mtp
        spec-draft-n-max = 2
        chat-template-file = /chemin/vers/Qwen3.5-4B.jinja
        
        ; Preset for Qwen3.6-35B-A3B-MXFP4.gguf
        [Qwen3.6-35B-A3B-MXFP4]
        model = /chemin/vers/Qwen3.6-35B-A3B-MXFP4.gguf
        fit-ctx = 0
        temp = 0.6
        top-p = 0.95
        top-k = 20
        min-p = 0.0
        presence-penalty = 0.9
        repeat-penalty = 1.0
        no-context-shift = true
        chat-template-kwargs = "{'preserve_thinking': true}"
        chat-template-file = /chemin/vers/Qwen3.6-35B-A3B.jinja
        
        ; Preset for Qwen3.6-27B-Q4_K_M.gguf
        [Qwen3.6-27B-Q4_K_M]
        model = /chemin/vers/Qwen3.6-27B-Q4_K_M.gguf
        fit-ctx = 131072
        temp = 0.6
        top-p = 0.95
        top-k = 20
        min-p = 0.0
        presence-penalty = 0.9
        repeat-penalty = 1.0
        no-context-shift = true
        spec-type = none
        chat-template-kwargs = "{'preserve_thinking': true}"
        chat-template-file = /chemin/vers/Qwen3.6-27B.jinja

        La documentation sur le mode routeur se trouve principalement dans ces fichiers :

        Fichier Contenu
        docs/preset.md Format INI des presets ([*], [model_name], etc.)
        tools/server/README.md (§ Router mode) Mode routeur : lancement, routing des requêtes, --models-preset, --models-max, --models-dir, API /v1/models, etc.
        tools/ui/docs/flows/data-flow-simplified-router-mode.md Flux de données en mode routeur (côté UI)

        Les options principales :
        - --models-preset PATH — fichier INI avec les presets modèles
        - --models-dir PATH — dossier contenant les GGUF (auto-découverte)
        - --models-max N — nombre max de modèles chargés simultanément
        - --models-autoload — auto-chargement des modèles (activé par défaut)

        wind0w$ suxX, GNU/Linux roxX!

    • [^] # Re: llama.cpp router mode

      Posté par  (Mastodon) . Évalué à 3 (+0/-0).

      Je viens de jouer avec llama-server, et c'est vrai qu'il fait parfaitement le taff. merci pour l'info !

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

    • [^] # Re: llama.cpp router mode

      Posté par  (site web personnel) . Évalué à 3 (+1/-0).

      "llama-server dispose depuis quelques versions d'un mode router intégré (--router) qui gère le chargement/déchargement dynamique des modèles sans redémarrage, sans dépendance externe"

      Sympa, j'ai commenté un article là dessus sur hackernews et Nvidia:

      https://news.ycombinator.com/item?id=48845518

  • # Tools

    Posté par  . Évalué à 6 (+4/-0).

    La prochaine étape est d'entrer dans l'agentique : le script va pouvoir exécuter les commandes envoyées par le LLM (avec tout le danger qui va avec bien évidemment !). Mais du coup je pourrai bien voir comment marche un agent au moins pour la base.

    Si tu veut quelque chose de plus sûr au lieu de lui faire générer des commandes shell, tu peut lui donner des outils.

    Dans la requête de prompt, tu déclare des tools.

    {
      "messages": [{"role": "user", "content": "Coucou"}],
      "tools": [
        {
          "type": "function",
          "function": {
            "name": "get_list_files",
            "description": "Get the list of files of a folder",
            "parameters": {
              "type": "object",
              "properties": {
                "folder": {"type": "string", "description": "The folder"}
              },
              "required": ["folder"]
            }
          }
        }
      ],
      "tool_choice": "auto"
    }

    Et quand tu reçoit une réponse qui demande un tool call, tu peut exécuter et lui répondre. Il faut que ce soit supporté par le modèle par contre. C’est un peu contraignant, mais c’est le plus sûr.

    Sinon tu peut utiliser un shell sécurisé comme lshell.

    https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Tools

      Posté par  (Mastodon) . Évalué à 4 (+1/-0). Dernière modification le 25 juillet 2026 à 13:47.

      J'avais bien en tête le mécanisme de lui déclarer les outils, mais justement, le faire uniquement passer par lshell est une très bonne idée (2e lame, toussa…). Je le note, merci !

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.