AI 에이전트가 있고, 이 에이전트가 파일 시스템과 같은 적절한 리소스에 접근하고 명령어(예: 셸 명령어)를 실행할 수 있는 안전한 환경에서 실행되길 원하지만, ‘무언가를 망가뜨릴’ 위험은 없어야 한다고 생각해 본 적이 있나요? 그렇다면 어떤 옵션이 있을까요?
가장 좋은 방법은 AI 에이전트에 샌드박스를 제공하는 것입니다. 샌드박스 안에서 에이전트는 Linux 머신과 안전하게 상호작용하고, 파일 시스템을 다루며, 특정 명령어를 실행할 수 있지만 잠재적으로 해로운 작업은 제한됩니다.
이 설정을 통해 강력한 애플리케이션을 구축할 수 있습니다. 예를 들어, 다음과 같은 작업을 수행할 수 있는 코딩 에이전트를 만들 수 있습니다:
- 파일 시스템에 코드 파일 생성 및 편집
- git, python, node와 같은 명령어 실행
- 환경에서 직접 코드를 실행하고 테스트하여 개발자와 협업
이 글에서는 이러한 코딩 에이전트를 구축하는 과정을 단계별로 살펴보겠습니다. Novita의 LLM과 함수 호출 기능을 사용하고, Novita Agent Sandbox를 안전한 환경으로 활용할 것입니다. 마지막으로 Gradio로 사용자 친화적인 인터페이스를 만들고 Hugging Face Spaces에 배포할 예정입니다.
자, 시작해 봅시다!
Agent Sandbox
Novita의 Agent Sandbox는 AI 에이전트를 위해 특별히 설계된 런타임 환경입니다. 가상 컴퓨터처럼 작동하는 안전하고 격리된 클라우드 설정을 제공합니다. 이 환경 내에서 에이전트는 생성된 코드를 기본 시스템에 위험을 초래하지 않고 안전하게 실행할 수 있습니다.
Novita Agent Sandbox의 주요 기능
- 안전함: 샌드박스가 완전히 격리되어 있으므로 에이전트는 자신의 리소스에만 접근할 수 있습니다.
- 빠른 시작: 새로운 환경이 200ms 이내에 시작됩니다.
- 가상 머신: 샌드박스가 VM처럼 동작하므로 에이전트는 모든 프로그래밍 언어로 코드를 실행할 수 있습니다.
- 일시 중지 및 재개: 언제든지 샌드박스를 일시 중지했다가 나중에 다시 시작할 수 있습니다.
- 백그라운드 작업: 에이전트가 백그라운드에서 작업을 실행하고 결과를 비동기적으로 검색할 수 있습니다.
SDK 설치
Novita Sandbox를 사용하려면 SDK가 필요합니다. SDK는 Python과 TypeScript/JavaScript를 지원합니다. 이번 워크스루에서는 Python SDK를 사용하겠습니다:
pip install novita-sandbox
설치 후, Novita API 키를 환경 변수로 설정하세요:
export NOVITA_API_KEY=your_api_key_here
샌드박스 테스트
모든 설정이 완료되었으면, 샌드박스를 생성하고 몇 가지 기본 작업을 실행해 보겠습니다:
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()
이 예제는 다음을 보여줍니다:
- 샌드박스 인스턴스 생성
- files 객체로 파일 시스템 접근
- commands.run 메서드를 사용하여 명령어 실행
- 작업 완료 후 kill로 리소스 해제
이제 파일 시스템 접근과 명령어 실행의 기본을 살펴보았으니, 이러한 샌드박스 기능을 도구로 사용하는 코딩 에이전트를 구축할 준비가 되었습니다.
코딩 에이전트 구축하기
코딩 에이전트를 구축하려면 함수 호출을 지원하는 LLM이 필요합니다. Novita는 이 기능을 제공하는 여러 모델을 보유하고 있습니다. 에이전트가 코딩 어시스턴트처럼 동작하려면 적절한 함수 세트가 있어야 합니다.
인간 코더가 무엇을 하는지 생각해 봅시다. 보통 코드를 작성하고, 읽고, 실행합니다. 따라서 우리의 에이전트는 다음 작업을 수행할 수 있어야 합니다:
- 파일에 쓰기
- 파일에서 읽기
- 명령어 실행
- 여러 파일에 동시에 쓰기
에이전트 설정
Novita의 모델은 OpenAI와 호환되므로, OpenAI SDK를 사용하여 상호작용할 수 있습니다. 설치해 봅시다:
pip install openai
설치 후, 앞서 했던 것처럼 Novita API 키를 환경 변수로 설정하세요. 완료되면 임포트를 추가하여 코딩을 시작할 수 있습니다:
from openai import OpenAI
import os
import json
from novita_sandbox.code_interpreter import Sandbox
이제 OpenAI 클라이언트 인스턴스를 생성합니다:
client = OpenAI(
base_url="https://api.novita.ai/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
여기서는 클라이언트가 OpenAI 대신 Novita의 기본 URL을 가리키도록 하고 인증에 Novita API 키를 사용합니다.
다음으로, 에이전트가 사용할 샌드박스 인스턴스를 생성합니다:
sandbox = Sandbox.create(timeout=1200)
timeout 매개변수는 샌드박스가 활성 상태를 유지할 시간을 지정합니다. 여기서는 10분으로 설정했습니다.
함수 정의
이제 에이전트가 사용할 함수를 정의할 수 있습니다.
1. 파일 읽기 (Read File)
이 함수는 파일 경로를 받아 샌드박스의 files 객체를 사용하여 내용을 읽습니다.
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. 파일 쓰기 (Write File)
이 함수는 지정된 파일 경로에 데이터를 씁니다.
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. 여러 파일 쓰기 (Write Multiple Files)
이 함수는 write_file과 동일하게 작동하지만 여러 파일을 한 번에 처리합니다.
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. 명령어 실행 (Run Commands)
이 함수는 샌드박스 내에서 셸 명령어를 실행하고 표준 출력을 반환합니다.
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}"
도구 등록
이제 모든 함수를 정의했으므로, 필요할 때 LLM이 호출할 수 있도록 도구로 등록하겠습니다. 각 도구 정의에는 함수 이름, 설명 및 매개변수 스키마가 포함됩니다.
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"],
},
},
}
]
도구를 등록했으니 이제 에이전트와 정의된 모든 도구를 활용하는 채팅 루프를 만들어 보겠습니다.
채팅 루프
이제 사용자가 코딩 에이전트와 상호작용할 수 있는 간단한 채팅 루프를 만듭니다. 루프는 메시지 목록을 유지하고 에이전트가 함수 호출을 요청할 때마다 처리합니다.
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. 👋")
이 채팅 루프는 상호작용을 유지하며, 필요할 때 에이전트가 등록된 도구 중 하나를 호출할 수 있게 하고, 사용자가 종료하면 샌드박스를 정리합니다.
Gradio로 UI 만들기
이제 완전히 작동하는 코딩 에이전트가 있어 채팅이 가능하지만, REPL을 통해 상호작용하는 것은 그리 흥미롭지 않습니다. 에이전트에 간단한 Gradio 인터페이스를 제공하여 경험을 더 매력적으로 만들어 보겠습니다.
Gradio UI를 만드는 것은 간단합니다. gr.ChatInterface를 사용하여 채팅 상호작용을 관리하고, 앞서 구축한 로직과 연결할 것입니다. 또한 샌드박스 내에서 셸 명령어를 실행하기 위한 명령 인터페이스와 사용할 모델을 선택할 수 있는 드롭다운 메뉴도 포함할 것입니다.
이전 코드를 Gradio를 지원하도록 업데이트하려면, 채팅 루프와 마지막 두 줄을 다음으로 대체합니다:
# --- 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()
이 버전에서 chat_fn 함수는 사용자와 에이전트 간의 각 메시지 교환을 처리합니다. gr.ChatInterface는 이 함수를 입력으로 받아 UI 상호작용을 자동으로 관리합니다.
Gradio 앱이 시작되면 브라우저 내에서 에이전트가 실행되어 사용자에게 깔끔하고 상호작용 가능한 채팅 인터페이스를 제공합니다. 마지막으로 atexit을 사용하여 정리 루틴을 등록하여 앱이 중지될 때 샌드박스가 올바르게 종료되도록 합니다.
이제 친숙한 Gradio 채팅 인터페이스를 갖춘 AI 기반 코딩 에이전트가 샌드박스 내에서 안전하게 실행되고 있습니다.
전체 코드는 GitHub에서 확인할 수 있습니다.
코딩 에이전트 테스트
에이전트를 사용하려면 gradio 코드를 스크립트로 실행해야 합니다.
python gradio_chat.py
실행하면 Gradio 앱이 localhost에서 실행됩니다. 이제 코딩 에이전트와 대화를 나눌 수 있으며, 에이전트는 샌드박스 내에서 모든 작업을 실행합니다.
Gradio 애플리케이션에서는 에이전트의 채팅만 볼 수 있지만, 터미널로 가면 에이전트가 사용자의 요청을 돕기 위해 호출한 함수와 명령어의 디버그 출력도 확인할 수 있습니다.
파일 도구와 명령어 도구에 접근할 수 있다는 것은 거의 모든 것을 코딩할 수 있음을 의미하지만, 직접 코딩하는 대신 에이전트에게 지시를 내리면 에이전트가 코드를 작성하고 실행해 줍니다.
Hugging Face Spaces에 배포하기
현재 코딩 에이전트는 로컬 컴퓨터에서 실행 중입니다. 이제 Hugging Face Spaces에 배포하여 전 세계에서 접근할 수 있도록 만들어 보겠습니다. Hugging Face Spaces를 사용하면 코드와 애플리케이션을 한 곳에서 호스팅할 수 있습니다. 시작해 봅시다.
Spaces 생성
Hugging Face로 이동하여 코딩 에이전트를 위한 새 Space를 만들고 고유한 이름을 지정하세요.

다음으로, Space에 사용할 SDK를 선택합니다. 우리의 경우 Gradio입니다. 이미 애플리케이션 코드가 있으므로 Blank 템플릿을 선택하세요.

그런 다음 하드웨어를 선택합니다. 에이전트와 샌드박스가 Novita에서 구동되므로 특수 하드웨어가 필요하지 않습니다. Basic CPU 옵션으로 충분합니다. 완료되면 Create Space를 클릭하세요.

Hugging Face는 README.md와 .gitignore 파일이 있는 Space를 생성합니다.
코드를 추가하는 방법은 여러 가지가 있지만, 가장 간단한 방법은 Contribute → Add file을 클릭하는 것입니다.

requirements.txt 파일을 만들고 다음 종속성을 포함합니다:
openai
novita-sandbox
환경 변수 추가
애플리케이션을 실행하기 전에 NOVITA_API_KEY를 환경 변수로 설정해야 합니다.
이를 위해 Space의 Settings로 이동하여 Variables and secrets 섹션으로 스크롤한 다음, NOVITA_API_KEY라는 이름의 새로운 secret을 추가하고 API 키를 값으로 입력합니다.
애플리케이션 설정
환경 변수가 설정되었으니 이제 애플리케이션을 만들 차례입니다.
app.py라는 새 파일을 만들고 전체 Gradio 에이전트 코드를 붙여넣습니다.

파일을 저장하면 Hugging Face가 자동으로 Space 빌드를 시작합니다.

빌드 프로세스가 완료되면 코딩 에이전트가 Hugging Face Spaces에서 라이브로 제공됩니다.
이제 대화형 채팅 인터페이스를 통해 에이전트와 채팅할 수 있습니다.
또한 로그를 모니터링하여 에이전트가 실행 중에 호출하는 도구를 확인할 수 있습니다.
이것으로 끝입니다. 이제 안전한 샌드박스에서 실행되고 Gradio 인터페이스를 갖추며 Hugging Face Spaces에 원활하게 배포된 완전한 기능의 코딩 에이전트를 갖게 되었습니다.
결론
이 글에서는 Novita의 Sandbox를 활용하여 파일 읽기 및 생성, 명령어 실행, 안전한 환경 내에서의 운영이 가능한 완전한 기능의 코딩 에이전트를 구축하는 방법을 살펴보았습니다.
여기서 구축한 것은 시작에 불과합니다. 샌드박스는 AI 기반 데이터 시각화 에이전트부터 시스템과 지능적으로 상호작용하는 컴퓨터 사용 에이전트 개발에 이르기까지 무한한 가능성의 문을 열어줍니다.
에이전트를 샌드박스와 같은 전용 도구 세트와 결합하면 거의 모든 것이 가능해집니다.
