experimental: expect some crashes due to memory overload
a generated python script to translate the json function calling to python. Allowing tool use with vulkan laama.cpp

Just download .py file and add from extension menu in newelle UI
created by gpt-oss-20b
1️⃣ Objectif
Créer une extension Newelle qui :
Étape | Description |
---|---|
1 | Intercepter les function‑calls que le modèle (LLM) génère dans la conversation. |
2 | Traduire ces appels en requêtes compatibles avec Vulkan Llama.cpp (le moteur de LMStudio). |
3 | Renvoyer la réponse du moteur à Newelle pour qu’elle soit affichée comme un message normal. |
⚠️ Cette extension ne fait pas appel à l’API d’un serveur externe, elle se contente de re‑écrire le texte que le LLM a produit avant qu’il ne soit envoyé au backend.
Ce dont vous avez besoin | Où le trouver |
---|---|
Newelle (≥ v0.9) | https://github.com/qwersyk/Newelle |
Python ≥ 3.10 | Votre environnement d’exécution |
Connaissance de la syntaxe des function‑calls du LLM (ex. {"name":"search","arguments":{"q":"python"} } ) |
Documentation OpenAI / Llama.cpp |
src/
└─ extensions/
├─ __init__.py # déjà présent dans Newelle
└─ llama_vulkan.py # notre fichier d'extension
# -*- coding: utf-8 -*-
"""
Extension : Llama Vulkan Function‑Call Translator
Ce module intercepte les appels de fonction générés par le modèle,
les convertit en requêtes compatibles avec l’API de Vulkan Llama.cpp
et renvoie la réponse au chat.
"""
from .extensions import NewelleExtension
import json
import re
import subprocess # pour appeler le binaire llama.cpp
# ------------------------------------------------------------------
class LlamaVulkanExtension(NewelleExtension):
"""
Extension qui traduit les function‑calls du modèle en requêtes
Vulkan Llama.cpp (LMStudio).
"""
name = "Llama Vulkan Function‑Call Translator"
id = "llama_vulkan"
# ------------------------------------------------------------------
def install(self) -> None:
"""Pas besoin d’installer de dépendances supplémentaires."""
pass
# ------------------------------------------------------------------
def preprocess_history(
self,
history: list[dict],
prompts: list[dict]
) -> tuple[list, list]:
"""
Avant l’appel au LLM : on ne fait rien.
On laisse le texte tel quel pour que le modèle puisse générer
un function‑call.
"""
return history, prompts
# ------------------------------------------------------------------
def postprocess_history(
self,
history: list[dict],
bot_response: str
) -> tuple[list, str]:
"""
Après réception de la réponse du LLM.
1. On cherche un bloc JSON qui représente un function‑call.
Le format attendu est celui d’OpenAI :
{"name":"search","arguments":{"q":"python"}}
2. Si trouvé, on le transforme en requête Vulkan
(exemple : `--function search --query "python"`).
3. On exécute le binaire llama.cpp avec ces arguments.
4. La sortie du binaire est insérée dans la réponse finale.
Si aucun function‑call n’est détecté, on renvoie simplement
la réponse brute.
"""
# 1️⃣ Recherche d’un JSON valide dans bot_response
func_call = self._extract_function_call(bot_response)
if not func_call:
return history, bot_response
# 2️⃣ Traduction en arguments Vulkan
vulkan_args = self._translate_to_vulkan(func_call)
# 3️⃣ Exécution du binaire llama.cpp (exemple simplifié)
result_text = self._run_llama_cpp(vulkan_args)
# 4️⃣ Construction de la réponse finale
final_response = (
f"**Résultat de l’appel fonction‑call :**\n\n{result_text}"
)
return history, final_response
# ------------------------------------------------------------------
def _extract_function_call(self, text: str) -> dict | None:
"""
Cherche un bloc JSON dans le texte. Retourne le dictionnaire
ou None si rien trouvé.
"""
pattern = r'\{.*?\}'
match = re.search(pattern, text, flags=re.DOTALL)
if not match:
return None
try:
obj = json.loads(match.group(0))
# On vérifie qu’il contient bien 'name' et 'arguments'
if "name" in obj and "arguments" in obj:
return obj
except json.JSONDecodeError:
pass
return None
# ------------------------------------------------------------------
def _translate_to_vulkan(self, func: dict) -> list[str]:
"""
Convertit le dictionnaire fonction‑call en liste d’arguments
que llama.cpp accepte via la ligne de commande.
Exemple :
{"name":"search","arguments":{"q":"python"}}
→ ["--function", "search", "--query", "python"]
"""
name = func["name"]
args = func.get("arguments", {})
# On suppose que chaque clé devient un flag `--<key>`
vulkan_args = ["--function", name]
for k, v in args.items():
vulkan_args.extend([f"--{k}", str(v)])
return vulkan_args
# ------------------------------------------------------------------
def _run_llama_cpp(self, args: list[str]) -> str:
"""
Exécute le binaire llama.cpp avec les arguments fournis.
Vous devez adapter le chemin vers votre exécutable et
éventuellement ajouter d’autres options (model path, etc.).
"""
# Chemin vers l’exécutable (à ajuster)
exe_path = "/usr/local/bin/llama" # ou "./llama"
cmd = [exe_path] + args
try:
completed = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30
)
if completed.returncode != 0:
return f"[Erreur] {completed.stderr.strip()}"
return completed.stdout.strip()
except Exception as e:
return f"[Exception] {e}"
# ------------------------------------------------------------------
Étape | Pourquoi c’est important |
---|---|
preprocess_history |
On ne touche pas à la requête envoyée au LLM, on laisse le modèle générer un function‑call. |
postprocess_history |
C’est ici qu’on intercepte la réponse et qu’on y applique notre logique de traduction/exécution. |
_extract_function_call |
Utilise une regex simple pour extraire le JSON. Vous pouvez l’adapter si votre format diffère. |
_translate_to_vulkan |
Construit les arguments que llama.cpp attend (--function , --query , …). |
_run_llama_cpp |
Lance le binaire et récupère la sortie. Vous devez vous assurer que le chemin est correct et que l’exécutable accepte ces flags. |
- Copiez
llama_vulkan.py
dans le répertoiresrc/extensions/
. - Redémarrez Newelle (ou utilisez la fonction “Reload extensions” si disponible).
- Dans les paramètres, activez votre extension (
Llama Vulkan Function‑Call Translator
).
-
Ouvrez un nouveau chat.
-
Demandez au modèle de faire une recherche :
Je veux que tu recherches "Python 3.10 features" dans la documentation officielle.
-
Le LLM devrait générer quelque chose comme :
{"name":"search","arguments":{"q":"Python 3.10 features"}}
-
Votre extension interceptera ce bloc, le traduira en
--function search --query "Python 3.10 features"
et exécutera le binaire llama.cpp. -
La réponse affichée sera :
**Résultat de l’appel fonction‑call :** <sortie du binaire>
Ce que vous pouvez modifier | Exemple |
---|---|
Chemin vers le binaire llama.cpp | exe_path = "/home/user/llama" |
Ajout d’options supplémentaires (ex. modèle, prompt) | cmd.extend(["--model", "models/ggml-model.bin"]) |
Gestion des erreurs plus fine | Retourner un message d’erreur détaillé si le binaire ne répond pas |
- Sécurité : L’exécution de commandes shell peut être dangereuse. Assurez‑vous que les arguments sont bien échappés (ici on passe directement la valeur, mais vous pouvez ajouter
shlex.quote
si besoin). - Performance : Chaque fonction‑call lance un nouveau processus. Si vous avez beaucoup d’appels rapides, envisagez de garder le binaire en mémoire ou d’utiliser une API locale.
- Compatibilité : Vérifiez que votre version de llama.cpp accepte bien les flags
--function
,--query
. Sinon adaptez_translate_to_vulkan
.
Vous avez maintenant :
- Une extension Newelle qui intercepte les function‑calls du modèle.
- Un mécanisme de traduction vers la syntaxe Vulkan Llama.cpp.
- La capacité d’exécuter le moteur localement et de renvoyer le résultat dans le chat.