Vous avez un agent IA, et vous voulez qu’il s’exécute dans un environnement sécurisé où il a accès aux bonnes ressources comme un système de fichiers et la capacité d’exécuter des commandes (par ex. des commandes shell) sans risque de « tout casser ». Quelles sont vos options ?
La meilleure approche est de donner à votre agent IA un bac à sable. À l’intérieur d’un bac à sable, l’agent peut interagir en toute sécurité avec une machine Linux, travailler avec le système de fichiers et exécuter des commandes spécifiques, tout en étant empêché d’effectuer des opérations potentiellement dangereuses.
Avec cette configuration, nous pouvons créer des applications puissantes. Par exemple, un agent de codage capable de :
- Créer et modifier des fichiers de code dans le système de fichiers
- Exécuter des commandes comme git, python ou node
- Collaborer avec les développeurs en exécutant et testant du code directement dans l’environnement
Dans cet article, nous allons vous guider pas à pas dans la création d’un tel agent de codage. Nous utiliserons les LLMs de Novita avec l’appel de fonctions, associés au bac à sable d’agent Novita comme environnement sécurisé. Pour couronner le tout, nous créerons une interface conviviale avec Gradio, et la déploierons sur Hugging Face Spaces.
C’est parti !
Agent Sandbox
Le bac à sable d’agent de Novita est un environnement d’exécution conçu spécifiquement pour les agents IA. Il fournit une configuration cloud sécurisée et isolée qui fonctionne comme un ordinateur virtuel. Dans cet environnement, les agents peuvent exécuter du code généré en toute sécurité sans risquer d’endommager le système sous-jacent.
Fonctionnalités clés du bac à sable d’agent Novita
- Sécurisé : Le bac à sable est entièrement isolé, donc l’agent n’a accès qu’à ses propres ressources.
- Démarrage rapide : Les nouveaux environnements sont opérationnels en moins de 200 ms.
- Machine virtuelle : Comme le bac à sable se comporte comme une VM, les agents peuvent exécuter du code dans n’importe quel langage de programmation.
- Pause et reprise : Vous pouvez mettre un bac à sable en pause à tout moment et le reprendre plus tard.
- Tâches en arrière-plan : Les agents peuvent exécuter des tâches en arrière-plan et récupérer les résultats de manière asynchrone.
Installation du SDK
Pour utiliser le bac à sable Novita, vous aurez besoin du SDK, qui prend en charge à la fois Python et TypeScript/JavaScript. Pour ce tutoriel, nous utiliserons le SDK Python :
pip install novita-sandbox
Après l’installation, définissez votre clé API Novita comme variable d’environnement :
export NOVITA\_API\_KEY=your\_api\_key\_here
Tester le bac à sable
Une fois tout configuré, créons un bac à sable et exécutons quelques opérations de base :
from novita_sandbox.code_interpreter import Sandbox
sandbox = Sandbox.create()
files = sandbox.files.list("/")
for file in files:
print(file.name)
result = sandbox.commands.run('pwd')
print(result)
sandbox.kill()
Cet exemple montre comment :
- Créer une instance de bac à sable
- Accéder au système de fichiers avec l’objet
files - Exécuter des commandes à l’aide de la méthode
commands.run - Libérer les ressources avec
killune fois que vous avez terminé
Maintenant que nous avons exploré les bases de l’accès au système de fichiers et de l’exécution de commandes, nous sommes prêts à créer notre agent de codage qui utilise ces capacités de bac à sable comme outils.
Créer un agent de codage
Pour créer notre agent de codage, nous avons besoin d’un LLM qui prend en charge l’appel de fonctions. Novita propose plusieurs modèles capables de le faire. Pour que notre agent se comporte comme un assistant de codage, il doit disposer du bon ensemble de fonctions.
Réfléchissons à ce que fait un codeur humain. Il écrit généralement, lit et exécute du code. Notre agent doit donc être capable de :
- Écrire dans un fichier
- Lire un fichier
- Exécuter des commandes
- Écrire dans plusieurs fichiers à la fois
Configuration de l’agent
Comme les modèles de Novita sont compatibles OpenAI, nous pouvons utiliser le SDK OpenAI pour interagir avec eux. Installons-le :
pip install openai
Après l’installation, définissez votre clé API Novita comme variable d’environnement, comme nous l’avons fait précédemment. Une fois cela fait, nous pouvons commencer à coder en ajoutant nos importations :
from openai import OpenAI
import os
import json
from novita_sandbox.code_interpreter import Sandbox
Maintenant, créons notre instance de client OpenAI :
client = OpenAI(
base_url="https://api.novita.ai/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
Ici, nous pointons le client vers l’URL de base de Novita au lieu de celle d’OpenAI, et utilisons notre clé API Novita pour l’authentification.
Ensuite, nous allons créer l’instance de bac à sable que notre agent utilisera :
sandbox = Sandbox.create(timeout=1200)
Le paramètre timeout spécifie la durée pendant laquelle le bac à sable doit rester actif. Dans ce cas, nous l’avons réglé sur 10 minutes.
Définitions des fonctions
Nous pouvons maintenant définir les fonctions que notre agent utilisera.
1. Lire un fichier
Cette fonction prend un chemin de fichier et lit son contenu à l’aide de l’objet files du bac à sable.
def read_file(path: str):
print(f"[DEBUG] read_file called with path: {path}")
try:
content = sandbox.files.read(path)
print(f"[DEBUG] read_file result: {content}")
return content # returns string content
except Exception as e:
print(f"[DEBUG] read_file error: {e}")
return f"Error reading file: {e}"
2. Écrire un fichier
Cette fonction écrit des données vers un chemin de fichier spécifié.
def write_file(path: str, data: str):
print(f"[DEBUG] write_file called with path: {path}")
try:
sandbox.files.write(path, data)
msg = f"File created successfully at {path}"
print(f"[DEBUG] {msg}")
return msg
except Exception as e:
print(f"[DEBUG] write_file error: {e}")
return f"Error writing file: {e}"
3. Écrire plusieurs fichiers
Cette fonction fonctionne exactement comme write_file mais gère plusieurs fichiers à la fois.
def write_files(files: list):
print(f"[DEBUG] write_files called with {len(files)} files")
try:
sandbox.files.write_files(files)
msg = f"{len(files)} file(s) created successfully"
print(f"[DEBUG] {msg}")
return msg
except Exception as e:
print(f"[DEBUG] write_files error: {e}")
return f"Error writing multiple files: {e}"
4. Exécuter des commandes
Cette fonction exécute des commandes shell à l’intérieur du bac à sable et renvoie la sortie standard.
def run_commands(command: str):
print(f"[DEBUG] run_commands called with commands: {command}")
try:
result = sandbox.commands.run(command)
print(f"[DEBUG] run_commands result: {result}")
return result.stdout # returns CommandResult object
except Exception as e:
print(f"[DEBUG] run_commands error: {e}")
return f"Error running commands: {e}"
Enregistrement des outils
Maintenant que nous avons toutes nos fonctions, nous allons les enregistrer en tant qu’outils que le LLM peut appeler lorsque nécessaire. Chaque définition d’outil inclut le nom de la fonction, sa description et le schéma des paramètres.
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read contents of a file inside the sandbox",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path in the sandbox"}
},
"required": ["path"],
},
},
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "Write a single file inside the sandbox",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path in the sandbox"},
"data": {"type": "string", "description": "Content to write"},
},
"required": ["path", "data"],
},
},
},
{
"type": "function",
"function": {
"name": "write_files",
"description": "Write multiple files inside the sandbox",
"parameters": {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {"type": "string"},
"data": {"type": "string"},
},
"required": ["path", "data"],
},
}
},
"required": ["files"],
},
},
},
{
"type": "function",
"function": {
"name": "run_commands",
"description": "Run a single shell command inside the sandbox working directory",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to run, e.g. 'ls' or 'python main.py'",
}
},
"required": ["command"],
},
},
}
]
Avec les outils enregistrés, passons à la création d’une boucle de chat qui utilisera notre agent et tous les outils que nous avons définis.
Boucle de chat
Nous allons maintenant créer une boucle de chat simple qui permet à l’utilisateur d’interagir avec l’agent de codage. La boucle maintiendra une liste de messages et gérera les appels de fonction chaque fois que l’agent en fait la demande.
messages = []
print("💬 Enter your queries (type 'exit' to quit):")
while True:
user_input = input("You: ")
if user_input.lower() == "exit":
break
# Append user message
messages.append({"role": "user", "content": user_input})
# Send to model
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
)
assistant_msg = response.choices[0].message
messages.append(assistant_msg)
if assistant_msg.tool_calls:
print(f"[DEBUG] Assistant requested {len(assistant_msg.tool_calls)} tool call(s).")
for tool_call in assistant_msg.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"[DEBUG] Tool call detected: {fn_name} with args {fn_args}")
if fn_name == "read_file":
fn_result = read_file(**fn_args)
elif fn_name == "write_file":
fn_result = write_file(**fn_args)
elif fn_name == "write_files":
fn_result = write_files(**fn_args)
elif fn_name == "run_commands":
fn_result = run_commands(**fn_args)
else:
fn_result = f"Error: Unknown tool {fn_name}"
print(f"[DEBUG] Unknown tool requested: {fn_name}")
# Append result back
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"content": str(fn_result),
})
# Get model's final answer with tool results
follow_up = client.chat.completions.create(
model=model,
messages=messages,
)
final_answer = follow_up.choices[0].message
messages.append(final_answer)
print("Assistant:", final_answer.content)
else:
print("Assistant:", assistant_msg.content)
sandbox.kill()
print("[DEBUG] Sandbox terminated. 👋")
Cette boucle de chat maintient l’interaction en cours, permet à l’agent d’appeler l’un des outils enregistrés lorsque nécessaire et nettoie le bac à sable lorsque l’utilisateur quitte.
Créer une interface utilisateur avec Gradio
Nous avons maintenant un agent de codage entièrement fonctionnel qui peut discuter avec nous, mais interagir via un REPL n’est pas très excitant. Rendons l’expérience plus engageante en donnant à notre agent une interface Gradio simple.
Créer une interface Gradio est simple. Nous utiliserons gr.ChatInterface pour gérer nos interactions de chat et le lier à la logique que nous avons créée précédemment. Parallèlement, nous inclurons une interface de commandes pour exécuter des commandes shell à l’intérieur du bac à sable, ainsi qu’un menu déroulant qui nous permet de sélectionner le modèle à utiliser.
Pour mettre à jour notre code précédent afin de prendre en charge Gradio, nous allons remplacer la boucle de chat et les deux dernières lignes par ce qui suit :
# --- Persistent chat messages ---
messages = []
# --- Global model setter ---
def set_model(selected_model):
global model
model = selected_model
print(f"[DEBUG] Model switched to: {model}")
return f"✅ Model switched to **{model}**"
def chat_fn(user_message, history):
global messages, model
messages.append({"role": "user", "content": user_message})
# Send to model
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
)
assistant_msg = response.choices[0].message
messages.append(assistant_msg)
output_text = ""
if assistant_msg.tool_calls:
print(f"[DEBUG] Assistant requested {len(assistant_msg.tool_calls)} tool call(s).")
for tool_call in assistant_msg.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"[DEBUG] Tool call detected: {fn_name} with args {fn_args}")
if fn_name == "read_file":
fn_result = read_file(**fn_args)
elif fn_name == "write_file":
fn_result = write_file(**fn_args)
elif fn_name == "write_files":
fn_result = write_files(**fn_args)
elif fn_name == "run_commands":
fn_result = run_commands(**fn_args)
else:
fn_result = f"Error: Unknown tool {fn_name}"
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"content": str(fn_result),
})
follow_up = client.chat.completions.create(
model=model,
messages=messages,
)
final_answer = follow_up.choices[0].message
messages.append(final_answer)
output_text = final_answer.content
else:
output_text = assistant_msg.content
return output_text
# --- Command Interface function ---
def execute_command(command):
if not command.strip():
return "⚠️ Please enter a command."
print(f"[DEBUG] Executing command from interface: {command}")
output = run_commands(command)
return f"```bash\
{output}\
```" if output else "✅ Command executed (no output)."
# --- Gradio UI ---
with gr.Blocks(title="Novita Sandbox App") as demo:
gr.Markdown("## 🧠 Novita Sandbox Agent")
gr.Markdown(
"This app is an AI-powered **code agent** that lets you chat with intelligent assistants backed by **Novita AI LLMs**. These agents can write, read, and execute code safely inside a **Novita sandbox**, providing a secure environment for running commands, testing scripts, and managing files, all through an intuitive chat interface with model selection and command execution built right in."
)
with gr.Row(equal_height=True):
# Left: Chat Interface
with gr.Column(scale=2):
gr.Markdown("### 💬 Chat Interface")
gr.ChatInterface(chat_fn)
# Right: Command Interface
with gr.Column(scale=1):
gr.Markdown("### 💻 Command Interface")
# Model selector
model_selector = gr.Dropdown(
label="Select Model",
choices=[
"meta-llama/llama-3.3-70b-instruct",
"deepseek/deepseek-v3.2-exp",
"qwen/qwen3-coder-30b-a3b-instruct",
"openai/gpt-oss-120b",
"moonshotai/kimi-k2-instruct",
],
value=model,
interactive=True,
)
model_status = gr.Markdown(f"✅ Current model: **{model}**")
model_selector.change(set_model, inputs=model_selector, outputs=model_status)
command_input = gr.Textbox(
label="Command",
placeholder="e.g., ls, python main.py",
lines=1,
)
with gr.Row():
run_btn = gr.Button("Run", variant="primary", scale=0)
command_output = gr.Markdown("Command output will appear here...")
run_btn.click(execute_command, inputs=command_input, outputs=command_output)
# --- Cleanup on exit ---
atexit.register(lambda: (sandbox.kill(), print("[DEBUG] Sandbox terminated. 👋")))
if __name__ == "__main__":
demo.launch()
Dans cette version, la fonction chat_fn gère chaque échange de messages entre l’utilisateur et l’agent. Le gr.ChatInterface prend cette fonction en entrée et gère automatiquement les interactions de l’interface utilisateur.
Lorsque l’application Gradio démarre, elle exécute votre agent dans le navigateur, offrant à l’utilisateur une interface de chat propre et interactive. Enfin, nous enregistrons une routine de nettoyage à l’aide de atexit pour nous assurer que le bac à sable est correctement terminé lorsque l’application s’arrête.
Nous avons maintenant un agent de codage alimenté par l’IA s’exécutant en toute sécurité dans un bac à sable, doté d’une interface de chat Gradio conviviale.
Vous trouverez le code complet sur GitHub.
Tester l’agent de codage
Pour utiliser l’agent, nous devons exécuter notre code Gradio en tant que script.
python gradio_chat.py
Lorsque nous faisons cela, notre application Gradio s’exécute sur localhost. Cela nous permet d’avoir des conversations avec notre agent de codage, et l’agent exécute toutes nos actions dans le bac à sable.
Depuis l’application Gradio, nous ne voyons que les discussions de notre agent, mais si nous allons dans notre terminal, nous pouvons également voir les sorties de débogage : quelle commande la fonction que l’agent a appelée a exécutée pour répondre à la demande de l’utilisateur.
Le fait que nous ayons accès à un outil de fichiers et un outil de commandes signifie qu’il n’y a presque rien que nous ne puissions coder, mais au lieu de coder directement, nous donnons des instructions à notre agent et il se charge d’écrire et d’exécuter le code pour nous.
Déploiement sur Hugging Face Spaces
Nous avons actuellement notre agent de codage qui s’exécute localement sur notre ordinateur. Maintenant, rendons-le accessible au reste du monde en le déployant sur Hugging Face Spaces. Hugging Face Spaces nous permet d’héberger à la fois notre code et notre application au même endroit. C’est parti !
Créer l’espace
Rendez-vous sur Hugging Face et créez un nouvel espace pour votre agent de codage en lui donnant un nom unique.

Ensuite, sélectionnez le SDK pour l’espace, qui dans notre cas est Gradio. Choisissez le modèle Blank (vide) puisque nous avons déjà le code de notre application.

Ensuite, sélectionnez le matériel. Comme notre agent et le bac à sable sont alimentés par Novita, nous n’avons pas besoin de matériel spécialisé. L’option Basic CPU est suffisante. Une fois terminé, cliquez sur Create Space.

Hugging Face créera l’espace avec un fichier README.md et un fichier .gitignore.
Il existe plusieurs façons d’ajouter votre code, mais la plus simple est de cliquer sur Contribute (Contribuer) → Add file (Ajouter un fichier).

Créez un fichier requirements.txt et incluez les dépendances suivantes :
openai
novita-sandbox
Ajouter des variables d’environnement
Avant de pouvoir exécuter notre application, nous devons définir notre NOVITA_API_KEY comme variable d’environnement.
Pour ce faire, accédez aux paramètres de votre espace, faites défiler jusqu’à la section Variables and secrets (Variables et secrets), et ajoutez un nouveau secret nommé NOVITA_API_KEY avec votre clé API comme valeur.
Configurer l’application
Avec la variable d’environnement définie, il est temps de créer notre application.
Créez un nouveau fichier nommé app.py et collez-y l’intégralité du code de l’agent Gradio.

Une fois que vous avez enregistré le fichier, Hugging Face commencera automatiquement à construire votre espace.

Une fois le processus de construction terminé, votre agent de codage sera en ligne et accessible sur Hugging Face Spaces.
Vous pouvez maintenant discuter avec votre agent via l’interface de chat interactive.
De plus, vous pouvez surveiller les journaux pour voir les outils que votre agent appelle pendant l’exécution.
Et c’est tout ! Vous avez maintenant un agent de codage entièrement fonctionnel s’exécutant dans un bac à sable sécurisé, équipé d’une interface Gradio et déployé de manière transparente sur Hugging Face Spaces.
Conclusion
Dans cet article, nous avons exploré comment tirer parti du bac à sable de Novita pour créer un agent de codage entièrement fonctionnel capable de lire et de créer des fichiers, d’exécuter des commandes et de fonctionner en toute sécurité dans un environnement sécurisé.
Ce que nous avons créé ici n’est que le début. Le bac à sable ouvre la porte à d’innombrables possibilités, de la création d’agents de visualisation de données alimentés par l’IA au développement d’agents d’utilisation d’ordinateurs capables d’interagir avec les systèmes de manière intelligente.
Presque tout est possible lorsque vous combinez un agent avec une boîte à outils dédiée comme le bac à sable.
