Criando um Agente de Codificação com o Agent Sandbox da Novita

Criando um Agente de Codificação com o Agent Sandbox da Novita

Você tem um agente de IA e quer que ele execute em um ambiente seguro com acesso aos recursos certos, como sistema de arquivos e capacidade de executar comandos (ex: comandos shell) sem o risco de “quebrar nada”. Então, quais são suas opções?

A melhor abordagem é dar ao seu agente de IA um sandbox. Dentro de um sandbox, o agente pode interagir com segurança com uma máquina Linux, trabalhar com o sistema de arquivos e executar comandos específicos, enquanto é impedido de executar operações potencialmente prejudiciais.

Com essa configuração, podemos construir aplicativos poderosos. Por exemplo, um agente de codificação que pode:

  • Criar e editar arquivos de código no sistema de arquivos
  • Executar comandos como git, python ou node
  • Colaborar com desenvolvedores executando e testando código diretamente no ambiente

Neste artigo, vamos passo a passo na construção de um agente de codificação como esse. Usaremos os LLMs da Novita com chamada de funções, combinados com o sandbox de agentes da Novita como nosso ambiente seguro. Para completar, criaremos uma interface amigável com Gradio e a implantaremos no Hugging Face Spaces.

Vamos começar!

Agent Sandbox

O Agent Sandbox da Novita (Agent Sandbox) é um ambiente de execução projetado especialmente para agentes de IA. Ele fornece uma configuração de nuvem segura e isolada que funciona como um computador virtual. Dentro desse ambiente, os agentes podem executar código gerado com segurança, sem arriscar o sistema subjacente.

Principais Características do Agent Sandbox da Novita

  • Seguro: O sandbox é totalmente isolado, então o agente tem acesso apenas aos seus próprios recursos.
  • Inicialização rápida: Novos ambientes são criados em menos de 200 ms.
  • Máquina virtual: Como o sandbox se comporta como uma VM, os agentes podem executar código em qualquer linguagem de programação.
  • Pausar e retomar: Você pode pausar um sandbox a qualquer momento e retomá-lo mais tarde.
  • Tarefas em segundo plano: Os agentes podem executar tarefas em segundo plano e recuperar resultados de forma assíncrona.

Instalando o SDK

Para usar o Sandbox da Novita, você precisará do SDK, que suporta Python e TypeScript/JavaScript. Para este tutorial, usaremos o SDK Python:

pip install novita-sandbox

Após a instalação, defina sua chave de API da Novita como uma variável de ambiente:

export NOVITA\_API\_KEY=your\_api\_key\_here

Testando o Sandbox

Com tudo configurado, vamos criar um sandbox e executar algumas operações 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 exemplo mostra como:

  • Criar uma instância de sandbox
  • Acessar o sistema de arquivos com o objeto files
  • Executar comandos usando o método commands.run
  • Liberar recursos com o método kill quando terminar

Agora que exploramos o básico de acesso ao sistema de arquivos e execução de comandos, estamos prontos para construir nosso agente de codificação que usa essas capacidades do sandbox como ferramentas.

Construindo um Agente de Codificação

Para construir nosso agente de codificação, precisamos de um LLM que suporte chamada de funções. A Novita oferece vários modelos que fazem isso. Para que nosso agente se comporte como um assistente de codificação, ele deve ter o conjunto correto de funções.

Vamos pensar no que um programador humano faz. Normalmente ele escreve, lê e executa código. Então nosso agente deve ser capaz de:

  • Escrever em um arquivo
  • Ler de um arquivo
  • Executar comandos
  • Escrever em vários arquivos de uma vez

Configurando o Agente

Como os modelos da Novita são compatíveis com a OpenAI, podemos usar o SDK da OpenAI para interagir com eles. Vamos instalá-lo:

pip install openai

Após a instalação, defina sua chave de API da Novita como uma variável de ambiente, assim como fizemos anteriormente. Depois disso, podemos começar a codificar adicionando nossas importações:

from openai import OpenAI
import os
import json
from novita_sandbox.code_interpreter import Sandbox

Agora, vamos criar nossa instância de cliente OpenAI:

client = OpenAI(
    base_url="https://api.novita.ai/openai",  
    api_key=os.environ["NOVITA_API_KEY"],
)

Aqui, estamos apontando o cliente para a URL base da Novita em vez da da OpenAI e usando nossa chave de API da Novita para autenticação.

Em seguida, criaremos a instância de sandbox que nosso agente usará:

sandbox = Sandbox.create(timeout=1200)

O parâmetro timeout especifica por quanto tempo o sandbox deve permanecer ativo. Neste caso, definimos como 10 minutos.

Definições de Funções

Agora podemos definir as funções que nosso agente usará.

1. Ler Arquivo

Esta função recebe um caminho de arquivo e lê seu conteúdo usando o objeto files do 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. Escrever Arquivo

Esta função grava dados em um caminho de arquivo especificado.

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. Escrever Vários Arquivos

Esta função funciona exatamente como write_file, mas lida com vários arquivos de uma 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. Executar Comandos

Esta função executa comandos shell dentro do sandbox e retorna a saída padrão.

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 Ferramentas

Agora que temos todas as nossas funções, vamos registrá-las como ferramentas que o LLM pode chamar quando necessário. Cada definição de ferramenta inclui o nome da função, descrição e 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"],

           },

       },

   }

]

Com as ferramentas registradas, vamos seguir para a criação de um loop de chat que usará nosso agente e todas as ferramentas que definimos.

Loop de Chat

Agora criaremos um loop de chat simples que permite ao usuário interagir com o agente de codificação. O loop manterá uma lista de mensagens e lidará com chamadas de função sempre que o agente solicitar uma.

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 loop de chat mantém a interação em execução, permite que o agente chame qualquer uma das ferramentas registradas quando necessário e limpa o sandbox quando o usuário sai.

Criando uma Interface com Gradio

Agora temos um agente de codificação totalmente funcional que pode conversar conosco, mas interagir por meio de um REPL não é muito empolgante. Vamos tornar a experiência mais envolvente dando ao nosso agente uma interface Gradio simples.

Criar uma interface Gradio é simples. Usaremos o gr.ChatInterface para gerenciar nossas interações de chat e vinculá-lo à lógica que construímos anteriormente. Além disso, incluiremos uma interface de comandos para executar comandos shell dentro do sandbox, além de um menu suspenso que nos permite selecionar qual modelo usar.

Para atualizar nosso código anterior para suportar o Gradio, substituiremos o loop de chat e as duas últimas linhas pelo seguinte:

# --- 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()

Nesta versão, a função `chat_fn` lida com cada troca de mensagens entre o usuário e o agente. O `gr.ChatInterface` usa essa função como entrada e gerencia as interações da interface automaticamente.

Quando o aplicativo Gradio é iniciado, ele executa seu agente dentro do navegador, oferecendo ao usuário uma interface de chat limpa e interativa. Por fim, registramos uma rotina de limpeza usando o `atexit` para garantir que o sandbox seja encerrado corretamente quando o aplicativo parar.

Agora temos um agente de codificação alimentado por IA executando com segurança em um sandbox, completo com uma interface de chat Gradio amigável.

Você pode encontrar o [código completo no GitHub](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/sandbox-chat-agent).

## Testando o Agente de Codificação

Para usar o agente, precisamos executar nosso código gradio como um script:

python gradio_chat.py


Quando fizermos isso, teremos nosso aplicativo gradio rodando no localhost. Com isso, podemos ter conversas com nosso agente de codificação e o agente executará todas as nossas ações dentro do sandbox.

No aplicativo gradio, tudo o que vemos são as conversas do nosso agente, mas se formos ao terminal, também podemos ver as saídas de depuração de qual comando a função que o agente chamou para atender à solicitação do usuário.

O fato de termos acesso a uma ferramenta de arquivo e uma ferramenta de comando significa que quase não há nada que não possamos codificar, mas em vez de codificar diretamente, estamos dando instruções ao nosso agente e ele escreve e executa os códigos para nós.

## Implantando no Hugging Face Spaces

Atualmente temos nosso agente de codificação rodando localmente em nosso computador. Agora, vamos torná-lo acessível para o resto do mundo implantando-o no Hugging Face Spaces. O Hugging Face Spaces nos permite hospedar nosso código e aplicativo em um só lugar. Vamos começar.

### Criar o Space

Acesse o [Hugging Face](https://huggingface.co/spaces) e crie um novo Space para seu agente de codificação, dando a ele um nome exclusivo.

![](https://ajldkp7ny4bysrxe.public.blob.vercel-storage.com/uploads/2025/12/Agent-Sandbox-and-HF-Spaces_Img0.webp)

Em seguida, selecione o SDK para o Space, que no nosso caso é o Gradio. Escolha o modelo Blank, pois já temos o código do nosso aplicativo.

![](https://ajldkp7ny4bysrxe.public.blob.vercel-storage.com/uploads/2025/12/Agent-Sandbox-and-HF-Spaces_Img1.webp)

Depois, selecione o hardware. Como nosso agente e sandbox são alimentados pela Novita, não precisamos de hardware especializado. A opção Basic CPU é suficiente. Depois de pronto, clique em *Create Space*.

![](https://ajldkp7ny4bysrxe.public.blob.vercel-storage.com/uploads/2025/12/Agent-Sandbox-and-HF-Spaces_Img6.webp)

O Hugging Face criará o Space com um arquivo `README.md` e um arquivo `.gitignore`.

Há várias maneiras de adicionar seu código, mas a mais simples é clicar em *Contribute* → *Add file*.

![](https://ajldkp7ny4bysrxe.public.blob.vercel-storage.com/uploads/2025/12/Agent-Sandbox-and-HF-Spaces_Img3.webp)

Crie um arquivo `requirements.txt` e inclua as seguintes dependências:

openai

novita-sandbox


### Adicionar Variáveis de Ambiente

Antes de executar nosso aplicativo, precisamos definir nossa `NOVITA_API_KEY` como uma variável de ambiente.

Para fazer isso, acesse as Configurações do seu Space, role até a seção *Variables and secrets* e adicione um novo segredo chamado `NOVITA_API_KEY` com sua chave de API como valor.

### Configurar o Aplicativo

Com a variável de ambiente definida, é hora de criar nosso aplicativo.

Crie um novo arquivo chamado `app.py` e cole nosso código completo do agente Gradio nele.

![](https://ajldkp7ny4bysrxe.public.blob.vercel-storage.com/uploads/2025/12/Agent-Sandbox-and-HF-Spaces_Img4.webp)

Depois de salvar o arquivo, o Hugging Face iniciará automaticamente a construção do seu Space.

![](https://ajldkp7ny4bysrxe.public.blob.vercel-storage.com/uploads/2025/12/Agent-Sandbox-and-HF-Spaces_Img5.webp)

Depois que o processo de construção for concluído, seu agente de codificação estará ativo e acessível no Hugging Face Spaces.

Agora você pode conversar com seu agente por meio da interface de chat interativa.

Além disso, você pode monitorar os logs para ver as ferramentas que seu agente chama durante a execução.

E é isso, agora você tem um agente de codificação totalmente funcional rodando em um sandbox seguro, equipado com uma interface Gradio e implantado de forma perfeita no Hugging Face Spaces.

## Conclusão

Neste artigo, exploramos como usar o Sandbox da Novita para construir um agente de codificação totalmente funcional, capaz de ler e criar arquivos, executar comandos e operar com segurança em um ambiente seguro.

O que construímos aqui é apenas o começo. O sandbox abre portas para inúmeras possibilidades, desde a criação de agentes de visualização de dados alimentados por IA até o desenvolvimento de agentes de uso de computador que podem interagir com sistemas de forma inteligente.

Quase tudo é possível quando você combina um agente com um conjunto de ferramentas dedicado como o sandbox.