Tienes un agente de IA y quieres que se ejecute en un entorno seguro donde tenga acceso a los recursos adecuados, como un sistema de archivos y la capacidad de ejecutar comandos (por ejemplo, comandos de shell) sin el riesgo de “romper nada”. Entonces, ¿cuáles son tus opciones?
El mejor enfoque es proporcionar a tu agente de IA un sandbox. Dentro de un sandbox, el agente puede interactuar de forma segura con una máquina Linux, trabajar con el sistema de archivos y ejecutar comandos específicos, mientras está restringido para no realizar operaciones potencialmente dañinas.
Con esta configuración, podemos construir aplicaciones potentes. Por ejemplo, un agente de codificación que pueda:
- Crear y editar archivos de código en el sistema de archivos
- Ejecutar comandos como git, python o node
- Colaborar con desarrolladores ejecutando y probando código directamente en el entorno
En este artículo, analizaremos la construcción de dicho agente de codificación. Usaremos los LLM de Novita con llamadas a funciones, junto con Novita agent sandbox como nuestro entorno seguro. Para rematar, crearemos una interfaz fácil de usar con Gradio y la desplegaremos en Hugging Face Spaces.
¡Manos a la obra!
Agent Sandbox
El Agent Sandbox de Novita es un entorno de ejecución diseñado específicamente para agentes de IA. Proporciona una configuración en la nube segura y aislada que funciona como una computadora virtual. Dentro de este entorno, los agentes pueden ejecutar de forma segura el código generado sin arriesgar el sistema subyacente.
Características clave de Novita Agent Sandbox
- Seguro: El sandbox está completamente aislado, por lo que el agente solo tiene acceso a sus propios recursos.
- Inicio rápido: Los nuevos entornos se inician en menos de 200 ms.
- Máquina virtual: Dado que el sandbox se comporta como una VM, los agentes pueden ejecutar código en cualquier lenguaje de programación.
- Pausa y reanudación: Puedes pausar un sandbox en cualquier momento y reanudarlo después.
- Tareas en segundo plano: Los agentes pueden ejecutar tareas en segundo plano y recuperar los resultados de forma asíncrona.
Instalación del SDK
Para usar Novita Sandbox, necesitarás el SDK, que es compatible tanto con Python como con TypeScript/JavaScript. Para este tutorial, usaremos el SDK de Python:
pip install novita-sandbox
Después de instalar, configura tu clave API de Novita como variable de entorno:
export NOVITA_API_KEY=tu_clave_api_aquí
Probando el Sandbox
Con todo configurado, creemos un sandbox y ejecutemos algunas operaciones básicas:
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()
Este ejemplo muestra cómo:
- Crear una instancia de sandbox
- Acceder al sistema de archivos con el objeto files
- Ejecutar comandos usando el método commands.run
- Liberar recursos con kill una vez que hayas terminado
Ahora que hemos explorado los conceptos básicos de acceso al sistema de archivos y ejecución de comandos, estamos listos para construir nuestro agente de codificación que utilizará estas capacidades del sandbox como herramientas.
Construyendo un agente de codificación
Para construir nuestro agente de codificación, necesitamos un LLM que admita llamadas a funciones. Novita proporciona varios modelos que pueden hacerlo. Para que nuestro agente se comporte como un asistente de codificación, debe tener el conjunto correcto de funciones.
Pensemos en lo que hace un codificador humano. Normalmente escriben, leen y ejecutan código. Por lo tanto, nuestro agente debería poder:
- Escribir en un archivo
- Leer de un archivo
- Ejecutar comandos
- Escribir en varios archivos a la vez
Configurando el agente
Dado que los modelos de Novita son compatibles con OpenAI, podemos usar el SDK de OpenAI para interactuar con ellos. Instalémoslo:
pip install openai
Después de instalar, configura tu clave API de Novita como variable de entorno, tal como hicimos antes. Una vez hecho esto, podemos empezar a programar agregando nuestras importaciones:
from openai import OpenAI
import os
import json
from novita_sandbox.code_interpreter import Sandbox
Ahora, creemos nuestra instancia del cliente OpenAI:
client = OpenAI(
base_url="https://api.novita.ai/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
Aquí, estamos apuntando el cliente a la URL base de Novita en lugar de la de OpenAI y usando nuestra clave API de Novita para la autenticación.
A continuación, crearemos la instancia del sandbox que usará nuestro agente:
sandbox = Sandbox.create(timeout=1200)
El parámetro timeout especifica cuánto tiempo debe permanecer activo el sandbox. En este caso, lo hemos configurado en 10 minutos.
Definiciones de funciones
Ahora podemos definir las funciones que usará nuestro agente.
1. Leer archivo
Esta función toma una ruta de archivo y lee su contenido usando el objeto files del sandbox.
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. Escribir archivo
Esta función escribe datos en una ruta de archivo especificada.
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. Escribir varios archivos
Esta función funciona igual que write_file, pero maneja varios archivos a la vez.
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. Ejecutar comandos
Esta función ejecuta comandos de shell dentro del sandbox y devuelve la salida estándar.
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}"
Registro de herramientas
Ahora que tenemos todas nuestras funciones, las registraremos como herramientas que el LLM puede llamar cuando sea necesario. Cada definición de herramienta incluye el nombre de la función, la descripción y el esquema de parámetros.
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"],
},
},
}
]
Con las herramientas registradas, pasemos a crear un bucle de chat que utilice nuestro agente y todas las herramientas que hemos definido.
Bucle de chat
Ahora crearemos un bucle de chat simple que permita al usuario interactuar con el agente de codificación. El bucle mantendrá una lista de mensajes y manejará las llamadas a funciones cada vez que el agente solicite una.
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. 👋")
Este bucle de chat mantiene la interacción en ejecución, permite que el agente llame a cualquiera de las herramientas registradas cuando sea necesario y limpia el sandbox cuando el usuario sale.
Creando una UI con Gradio
Ahora tenemos un agente de codificación completamente funcional que puede chatear con nosotros, pero interactuar a través de un REPL no es muy emocionante. Hagamos la experiencia más atractiva proporcionando a nuestro agente una interfaz Gradio simple.
Crear una UI de Gradio es sencillo. Usaremos gr.ChatInterface para gestionar nuestras interacciones de chat y vincularlo con la lógica que construimos anteriormente. Junto a eso, incluiremos una interfaz de comandos para ejecutar comandos de shell dentro del sandbox, así como un menú desplegable que nos permita seleccionar qué modelo usar.
Para actualizar nuestro código anterior y que sea compatible con Gradio, reemplazaremos el bucle de chat y las dos últimas líneas con lo siguiente:
# --- 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()
En esta versión, la función chat_fn maneja cada intercambio de mensajes entre el usuario y el agente. El gr.ChatInterface toma esta función como entrada y gestiona las interacciones de la UI automáticamente.
Cuando la aplicación Gradio se inicia, ejecuta tu agente dentro del navegador, proporcionando al usuario una interfaz de chat limpia e interactiva. Finalmente, registramos una rutina de limpieza usando atexit para asegurarnos de que el sandbox se termine correctamente cuando la aplicación se detenga.
Ahora tenemos un agente de codificación impulsado por IA que se ejecuta de forma segura en un sandbox, completo con una interfaz de chat Gradio amigable.
Puedes encontrar el código completo en GitHub.
Probando el agente de codificación
Para usar el agente, necesitamos ejecutar nuestro código de gradio como un script.
python gradio_chat.py
Cuando lo hagamos, nuestra aplicación gradio se ejecutará en localhost. Con esto, podemos tener conversaciones con nuestro agente de codificación y el agente ejecutará todas nuestras acciones dentro del sandbox.
Desde la aplicación gradio solo podemos ver los chats de nuestro agente, pero si vamos a nuestra terminal también podemos ver las salidas de depuración: qué comando o función llamó el agente para ayudar al usuario.
El hecho de que tengamos acceso a una herramienta de archivos y una herramienta de comandos significa que casi no hay nada que no podamos codificar, pero en lugar de codificar directamente, estamos dando instrucciones a nuestro agente y él escribe y ejecuta el código por nosotros.
Desplegando en Hugging Face Spaces
Actualmente tenemos nuestro agente de codificación ejecutándose localmente en nuestra computadora. Ahora, hagámoslo accesible para el resto del mundo desplegándolo en Hugging Face Spaces. Hugging Face Spaces nos permite alojar tanto nuestro código como nuestra aplicación en un solo lugar. Empecemos.
Crear el Space
Dirígete a Hugging Face y crea un nuevo Space para tu agente de codificación dándole un nombre único.

A continuación, selecciona el SDK para el Space, que en nuestro caso es Gradio. Elige la plantilla Blank, ya que ya tenemos nuestro código de aplicación.

Luego, selecciona el hardware. Dado que nuestro agente y sandbox funcionan con Novita, no necesitamos hardware especializado. La opción Basic CPU es suficiente. Una vez hecho, haz clic en Create Space.

Hugging Face creará el Space con un archivo README.md y un .gitignore.
Hay varias formas de agregar tu código, pero la más sencilla es hacer clic en Contribute → Add file.

Crea un archivo requirements.txt e incluye las siguientes dependencias:
openai
novita-sandbox
Agregar variables de entorno
Antes de ejecutar nuestra aplicación, necesitamos configurar nuestra variable NOVITA_API_KEY como variable de entorno.
Para hacer esto, navega a la sección Settings de tu Space, desplázate hasta Variables and secrets y agrega un nuevo secreto llamado NOVITA_API_KEY con tu clave API como valor.
Configurar la aplicación
Con la variable de entorno configurada, es hora de crear nuestra aplicación.
Crea un nuevo archivo llamado app.py y pega en él el código completo de nuestro agente Gradio.

Una vez que guardes el archivo, Hugging Face comenzará automáticamente a construir tu Space.

Después de que el proceso de construcción se complete, tu agente de codificación estará en vivo y accesible en Hugging Face Spaces.
Ahora puedes chatear con tu agente a través de la interfaz de chat interactiva.
Además, puedes monitorear los logs para ver las herramientas que tu agente llama durante la ejecución.
Y eso es todo, ahora tienes un agente de codificación completamente funcional ejecutándose en un sandbox seguro, equipado con una interfaz Gradio y desplegado sin problemas en Hugging Face Spaces.
Conclusión
En este artículo, exploramos cómo aprovechar el Sandbox de Novita para construir un agente de codificación completamente funcional capaz de leer y crear archivos, ejecutar comandos y operar de forma segura dentro de un entorno protegido.
Lo que hemos construido aquí es solo el comienzo. El sandbox abre la puerta a un sinfín de posibilidades, desde la creación de agentes de visualización de datos impulsados por IA hasta el desarrollo de agentes de uso informático que pueden interactuar con sistemas de manera inteligente.
Casi todo es posible cuando combinas un agente con un conjunto de herramientas dedicado como el sandbox.
