Stell dir vor, du durchsuchst Dokumentationsseiten oder Coding-Tutorials und fühlst dich nie allein. Statt alles alleine zu navigieren, hast du einen KI-Assistenten, der dich von Seite zu Seite begleitet. Er ist nicht an eine einzelne Webseite gebunden, sondern immer direkt bei dir, um dir zu helfen. Er kann Code, auf den du online stößt, sicher ausführen, Erklärungen liefern und dir genau dann Einblicke geben, wenn du sie brauchst.
Wie bringst du also dieses Erlebnis zum Leben? Indem du eine Browser-Erweiterung erstellst. Diese Erweiterung enthält einen KI-Agenten, mit dem der Benutzer chatten kann, und der Agent hat Zugriff auf eine sichere Sandbox-Umgebung, in der er Code ausführen und andere Operationen sicher durchführen kann.
Um dieses System zu erstellen, bauen wir eine Chrome-Erweiterung, verwenden ein Novita-Modell, das Tool-Nutzung unterstützt, und integrieren die Novita Sandbox als sichere Laufzeitumgebung für den Agenten. In diesem Artikel führen wir dich durch den gesamten Erstellungsprozess.

Am Ende dieses Tutorials lernst du:
- Wie du eine Chrome-Erweiterung erstellst, die mit einem KI-Agenten integriert ist
- Wie du Novitas agentische LLMs nutzt
- Wie du Novita Sandbox als sichere Umgebung für den Browser-Agenten einrichtest
- Wie du die Erweiterung in Echtzeit mit dem Agenten kommunizieren lässt
Eine Sandbox: Das einzige Tool, das du brauchst
Die Chrome-Erweiterung, die wir erstellen, basiert auf einem KI-Agenten, der den Benutzer unterstützt. Da diese Erweiterung als Coding-Assistent fungieren soll, muss der Agent in der Lage sein, Code auszuführen, Dateien zu erstellen, Ausgaben zu inspizieren und alle typischen Aufgaben durchzuführen, die ein Entwickler möglicherweise erledigt. Du könntest erwarten, dass dafür eine lange Liste an Tools erforderlich ist, aber in Wirklichkeit braucht es nur eines: eine Sandbox.
Eine Sandbox gibt dem Agenten Zugriff auf eine Linux-Umgebung, in der er Befehle ausführen, Dateien erstellen und bearbeiten sowie alle Operationen durchführen kann, die du normalerweise in einem Terminal ausführst. Für dieses Projekt verwenden wir die Novita Sandbox.
Um sie einzurichten, installierst du zuerst das Novita Sandbox-Paket:
pip install novita-sandbox
Als Nächstes setzt du die Umgebungsvariable NOVITA_API_KEY auf deinen API-Schlüssel. Sobald das erledigt ist, kannst du eine Sandbox wie folgt erstellen und verwenden:
from novita_sandbox.code_interpreter import Sandbox
sandbox = Sandbox.create()
result = sandbox.commands.run('ls -l')
print(result)
sandbox.kill()
Dieser Codeausschnitt erstellt eine Sandbox, führt den Befehl ls -l aus, gibt die Ausgabe aus und schaltet die Sandbox anschließend wieder ab. Dieser einfache Workflow ist die Grundlage dafür, wie unser Browser-Assistent die Sandbox nutzt, um Benutzern zu helfen.
Nun wenden wir dieses Prinzip auf die vollständige Erweiterung an.
Architektur des Browser-Assistenten
Die Architektur dieses Projekts folgt einem Client-Server-Modell. Die Chrome-Erweiterung fungiert als Client, während ein dedizierter Backend-Server sowohl den KI-Agenten als auch die Sandbox-Umgebung hostet.
Die Erweiterung kommuniziert über eine WebSocket-Verbindung mit dem Server. Dies ermöglicht Echtzeit-Bidirektionale Nachrichtenübermittlung, sodass die Anfragen des Benutzers und die Antworten des Agenten sofort und ohne spürbare Verzögerung fließen. Der Server wiederum kommuniziert mit den Novita-APIs, die den Modell-Endpunkt und den Sandbox-Dienst umfassen.
Zusammen bilden die Erweiterung und das Backend einen intelligenten Browser-Assistenten, der in der Lage ist, Code sicher auszuführen, Informationen schnell zu verarbeiten und hilfreiche Erklärungen direkt im Rahmen des Browser-Erlebnisses des Benutzers zu liefern.
Erstellen der Erweiterung
Nun da wir die Gesamtarchitektur verstehen, können wir mit der Implementierung der Erweiterung selbst beginnen. Wir starten mit dem Plugin-Server.
Der Erweiterungs-Server
Der Erweiterungs-Server ist ein einfacher WebSocket-Dienst mit einem einzelnen Endpunkt /ws. Dieser Endpunkt empfängt Nachrichten vom Benutzer und gibt die Antworten des LLMs in Echtzeit zurück. Er verarbeitet zudem Tool-Aufrufe, indem er die Sandbox aufruft, sobald der Agent Code ausführen oder eine Operation durchführen muss.
Abhängigkeiten
Der Server basiert auf drei Kernbibliotheken:
- FastAPI: Das HTTP-Framework, das die WebSocket-Implementierung bereitstellt
- OpenAI: Das SDK, das zur Kommunikation mit Novita-Modellen verwendet wird
- Novita Sandbox: Die sichere Umgebung, in der Code sicher ausgeführt wird
Installiere sie mit:
pip install novita-sandbox "fastapi[standard]" openai
Setze deinen Novita-API-Schlüssel als Umgebungsvariable:
export NOVITA_API_KEY = sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Schreiben des Server-Codes
Beginne mit dem Import der erforderlichen Module:
import os
import json
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from novita_sandbox.code_interpreter import Sandbox
from openai import OpenAI
Initialisieren des LLM-Clients
Als Nächstes erstellst du einen OpenAI-Client, der auf die Novita-API verweist. In diesem Beispiel verwenden wir das Modell llama-3.3-70b-instruct, aber jedes Novita-Modell, das Tool-Aufrufe unterstützt, funktioniert.
client = OpenAI(
base_url="https://api.novita.ai/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
model = "meta-llama/llama-3.3-70b-instruct"
Definieren des Tool-Schemas
Der Agent verwendet vier Tools, die jeweils mit der Sandbox interagieren:
- read_file: Liest den Inhalt einer Datei
- write_file: Erstellt eine einzelne Datei und schreibt Daten in sie
- write_files: Erstellt mehrere Dateien und schreibt Daten in sie
- run_commands: Führt Shell-Befehle innerhalb der Sandbox aus
Hier ist das vollständige Tool-Schema:
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read contents of a file inside the sandbox",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "Write a single file inside the sandbox",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"data": {"type": "string"},
},
"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 shell command inside the sandbox working directory",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string"},
},
"required": ["command"],
},
},
},
]
Konfigurieren des HTTP-Servers
Richte FastAPI ein und aktiviere CORS, damit die Chrome-Erweiterung Anfragen an den Server senden kann.
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
Implementieren der Tools
Definiere eine Handler-Funktion, die jedes Tool implementiert und die Tool-Aufrufe des Agenten an die Sandbox weiterleitet:
def make_tool_handlers(sandbox):
def read_file(path: str):
print(f"[LOG] read_file called with path: {path}")
try:
content = sandbox.files.read(path)
print(f"[LOG] read_file result: {content}")
return content
except Exception as e:
return f"Error reading file: {e}"
def write_file(path: str, data: str):
print(f"[LOG] write_file called with path: {path}")
try:
sandbox.files.write(path, data)
return f"File created successfully at {path}"
except Exception as e:
return f"Error writing file: {e}"
def write_files(files: list):
print(f"[LOG] write_files called with {len(files)} files")
try:
sandbox.files.write_files(files)
return f"{len(files)} file(s) created successfully"
except Exception as e:
return f"Error writing multiple files: {e}"
def run_commands(command: str):
print(f"[LOG] run_commands called with command: {command}")
try:
result = sandbox.commands.run(command)
return result.stdout
except Exception as e:
return f"Error running command: {e}"
return {
"read_file": read_file,
"write_file": write_file,
"write_files": write_files,
"run_commands": run_commands,
}
Erstellen des WebSocket-Endpunkts
Nun implementieren wir den WebSocket-Endpunkt. Wenn sich der Benutzer mit dem Endpunkt verbindet, wird eine neue Sandbox-Instanz erstellt.
Die gesamte Kommunikation zwischen Benutzer und Agenten fließt über diese Verbindung. Wenn der Benutzer den Agenten auffordert, ein Tool zu verwenden, wählt der Agent das entsprechende Tool über die Handler-Funktion aus und führt es aus.
Wenn die Verbindung geschlossen wird, wird die Sandbox beendet.
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
print("\
[WS] Client connected")
# Create sandbox per connection
sandbox = Sandbox.create(timeout=1200)
print("[WS] Sandbox created")
tools_exec = make_tool_handlers(sandbox)
messages = [] # persistent inside this websocket
try:
while True:
data = await ws.receive_text()
print(f"[WS] Received message: {data}")
# Add user message
messages.append({"role": "user", "content": data})
# LLM call
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
)
assistant_msg = response.choices[0].message
messages.append(assistant_msg)
# If LLM wants to call tools
if assistant_msg.tool_calls:
print(f"[WS] Assistant requested {len(assistant_msg.tool_calls)} tool call(s).")
results = []
for tool_call in assistant_msg.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"[WS] Tool call: {fn_name} args={fn_args}")
if fn_name in tools_exec:
result = tools_exec[fn_name](**fn_args)
else:
result = f"Error: Unknown tool {fn_name}"
results.append(result)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"content": str(result),
})
# Follow-up model call
follow_up = client.chat.completions.create(
model=model,
messages=messages,
)
final_answer = follow_up.choices[0].message
messages.append(final_answer)
await ws.send_json({
"reply": final_answer.content,
"tool_output": results,
})
else:
# Simple model text output
await ws.send_json({"reply": assistant_msg.content})
except WebSocketDisconnect:
print("[WS] Client disconnected")
finally:
sandbox.kill()
print("[WS] Sandbox terminated")
Starten des Servers
Verwende abschließend Uvicorn, um den Dienst zu starten:
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Damit ist die Server-Komponente abgeschlossen.
Die Erweiterung
Die Erweiterung ist die Oberfläche, mit der der Benutzer interagiert. Sie besteht aus einer kleinen Sammlung von Dateien, die zusammenarbeiten, um auf jeder Webseite zu laufen. Sobald sie aktiviert ist, kann der Benutzer in Echtzeit direkt von der Seite, die er durchsucht, mit dem Erweiterungs-Server kommunizieren.
Die Erweiterung enthält die folgenden Dateien:
- manifest.json: Definiert die Konfiguration und Berechtigungen der Erweiterung
- background.js: Enthält die Service-Worker-Logik und verarbeitet Kontextmenü-Aktionen
- content.js: Verwaltet Interaktionen auf der Seite und zeigt den Assistenten-Dialog an
- styles.css: Stellt das Styling für das Assistenten-Fenster auf der Seite bereit
Jede Datei hat eine eigene Aufgabe, und zusammen bilden sie eine vollständige, funktionale Erweiterung.
Funktionsweise der Erweiterung
Bevor wir die Dateien implementieren, werfen wir einen kurzen Blick darauf, wie die Erweiterung aus Benutzersicht funktioniert:

- Der Benutzer klickt mit der rechten Maustaste auf eine beliebige Webseite, und das Kontextmenü erscheint.
- Er wählt „Agent Sandbox“ aus dem Menü aus.
- Die Erweiterung öffnet den Assistenten-Dialog auf der Seite.
- Der Benutzer klickt auf Connect, um eine Verbindung zum Erweiterungs-Server herzustellen.
- Sobald die Verbindung hergestellt ist, kann der Benutzer direkt in das Nachrichtenfeld tippen.
- Nach der Eingabe seiner Nachricht klickt er einfach auf Send, um sie an den Server zu übermitteln.
- Wenn er dem Agenten Seitenkontext zur Verfügung stellen möchte, kann er auf Extract klicken, um den gesamten sichtbaren Inhalt der Webseite zu erfassen.
- Er kann auch zusätzlichen Kontext manuell hinzufügen, bevor er erneut auf Send klickt.

Nun da wir den Workflow verstehen, können wir mit der Implementierung der Erweiterungsdateien beginnen.
manifest.json
Die erste Datei, die wir erstellen, ist die manifest.json, die die Berechtigungen, Hintergrundlogik und Content-Skripte der Erweiterung konfiguriert.
{
"manifest_version": 3,
"name": "Agent Sandbox",
"version": "1.0",
"description": "Chat with your Novita AI sandbox agent over WebSocket.",
"permissions": [
"contextMenus",
"activeTab",
"scripting"
],
"host_permissions": [
"ws://localhost:8000/*",
"http://localhost:8000/*",
"https://localhost:8000/*"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_icon": "icon.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["styles.css"]
}
]
}
Diese Datei teilt Chrome mit, welche Skripte geladen werden sollen, welche Berechtigungen wir benötigen und welche Datei als Service-Worker fungiert.
background.js
Das Hintergrundskript ist der Service-Worker, der im Hintergrund läuft. Es ist dafür verantwortlich, unsere Erweiterung zum Chrome-Kontextmenü hinzuzufügen und auf Benutzerinteraktionen zu lauschen. Wenn der Benutzer unsere Menüoption auswählt, sendet das Hintergrundskript eine Nachricht an das Content-Skript, das daraufhin den Erweiterungs-Dialog aktiviert.
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "ask-assistant",
title: "Agent Sandbox",
contexts: ["all"]
});
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
chrome.scripting.executeScript(
{
target: { tabId: tab.id },
files: ["content.js"]
},
() => {
chrome.tabs.sendMessage(tab.id, {
type: "OPEN_PANEL"
});
}
);
});
content.js
Das Content-Skript ist dafür verantwortlich, den Erweiterungs-Dialog innerhalb der Webseite anzuzeigen. Wenn es eine Nachricht vom Hintergrundskript empfängt, öffnet es den Dialog. Dieses Skript ist einfaches JavaScript. Es verwaltet die Benutzeroberfläche über Standard-DOM-Operationen und verwendet die WebSocket-API, um mit dem Erweiterungs-Server zu kommunizieren.
let socket = null;
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === "OPEN_PANEL") {
openPanel();
}
});
// -------------------------------------------------
// WEBPAGE TEXT EXTRACTOR
// -------------------------------------------------
function extractWebpageContent() {
const cloned = document.cloneNode(true);
cloned.querySelectorAll("script, style, iframe, noscript").forEach(e => e.remove());
let main =
cloned.querySelector("article") ||
cloned.querySelector("main") ||
cloned.querySelector("#content") ||
cloned.body;
const text = Array.from(main.querySelectorAll("h1, h2, h3, p"))
.map(el => el.innerText.trim())
.filter(Boolean)
.join("\
\
");
return text;
}
// -------------------------------------------------
// PANEL UI
// -------------------------------------------------
function openPanel() {
const old = document.getElementById("assistant-box");
if (old) old.remove();
const box = document.createElement("div");
box.id = "assistant-box";
box.innerHTML = `
<div id="assistant-container">
<div id="assistant-header">
<h3>Code Assistant</h3>
<button id="assistant-close">×</button>
</div>
<textarea id="assistant-input" placeholder="Message Agent..."></textarea>
<div class="btn-row">
<button id="connect-btn">Connect</button>
<button id="disconnect-btn">Disconnect</button>
<button id="send-btn">Send</button>
<button id="extract-btn">Extract</button>
</div>
<div id="assistant-result">Not connected.</div>
</div>
`;
document.body.appendChild(box);
document.getElementById("assistant-close").onclick = () => box.remove();
const resultBox = document.getElementById("assistant-result");
const inputBox = document.getElementById("assistant-input");
// -------------------------------------------------
// TEXT SELECTION LISTENER
// -------------------------------------------------
document.addEventListener("mouseup", () => {
const selection = window.getSelection().toString().trim();
if (!selection) return;
// Append selected text to message box
inputBox.value += (inputBox.value ? "\
\
" : "") + selection;
// Scroll text area to bottom
inputBox.scrollTop = inputBox.scrollHeight;
});
// -------------------------------------------------
// CONNECT
// -------------------------------------------------
document.getElementById("connect-btn").onclick = () => {
if (socket && socket.readyState === WebSocket.OPEN) {
resultBox.innerText = "Already connected.";
return;
}
socket = new WebSocket("ws://localhost:8000/ws");
socket.onopen = () => {
resultBox.innerText = "Connected to WebSocket.";
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
resultBox.innerText += "\
\
Assistant:\
" + data.reply;
resultBox.scrollTop = resultBox.scrollHeight;
};
socket.onerror = () => {
resultBox.innerText = "WebSocket error.";
};
socket.onclose = () => {
resultBox.innerText = "Disconnected.";
};
};
// -------------------------------------------------
// DISCONNECT
// -------------------------------------------------
document.getElementById("disconnect-btn").onclick = () => {
if (socket) socket.close();
};
// -------------------------------------------------
// SEND MESSAGE
// -------------------------------------------------
document.getElementById("send-btn").onclick = () => {
const context = inputBox.value;
if (!socket || socket.readyState !== WebSocket.OPEN) {
resultBox.innerText = "Not connected.";
return;
}
socket.send(JSON.stringify({ message: context }));
inputBox.value = "";
resultBox.innerText += "\
\
You:\
" + context;
};
// -------------------------------------------------
// EXTRACT PAGE → ADD TO MESSAGE BOX
// -------------------------------------------------
document.getElementById("extract-btn").onclick = () => {
const extracted = extractWebpageContent();
if (!extracted || extracted.length < 10) {
resultBox.innerText = "Could not extract useful content.";
return;
}
// Add extracted text to input box (not sent automatically)
inputBox.value += (inputBox.value ? "\
\
" : "") + extracted;
// Scroll text area
inputBox.scrollTop = inputBox.scrollHeight;
resultBox.innerText = "📄 Extracted content added to message box.";
};
}
styles.css
Dann verwenden wir die Datei styles.css, um die Oberfläche zu gestalten und zu steuern, wie die Erweiterung auf der Seite angezeigt wird.
#assistant-box {
position: fixed;
top: 10%;
right: 10%;
width: 350px;
background: #000;
border: 1px solid #00ff7f;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,255,127,0.5);
z-index: 999999;
font-family: monospace;
color: #00ff7f;
}
#assistant-container {
padding: 12px;
}
#assistant-header {
display: flex;
justify-content: space-between;
align-items: center;
}
#assistant-close {
background: transparent;
border: none;
font-size: 20px;
cursor: pointer;
padding: 0 5px;
font-weight: bold;
color: #00ff7f;
}
#assistant-container textarea {
width: 100%;
height: 60px;
margin-top: 8px;
background: #0d0d0d;
color: #00ff7f;
border: 1px solid #00ff7f;
border-radius: 4px;
padding: 6px;
resize: vertical;
outline: none;
}
#send-btn {
width: 100%;
margin-top: 10px;
background: #00ff7f;
border: none;
padding: 10px;
color: #000;
font-weight: bold;
cursor: pointer;
border-radius: 4px;
transition: 0.2s;
}
#send-btn:hover {
background: #00e66a;
}
#assistant-box pre {
background: #0a0a0a;
padding: 8px;
border-radius: 4px;
max-height: 120px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
margin-top: 5px;
}
#assistant-result {
margin-top: 12px;
background: #0d0d0d;
padding: 8px;
border-radius: 5px;
white-space: pre-wrap;
max-height: 150px;
overflow: auto;
}
Einrichten der Chrome-Erweiterung
Nun da alle erforderlichen Dateien vorhanden sind, ist der nächste Schritt, die Erweiterung in Chrome zu laden. Folge diesen Schritten:
- Speichere den Erweiterungs-Server-Code in einer Python-Datei.
- Installiere alle Abhängigkeiten und richte die erforderlichen Umgebungsvariablen für den Server ein.
- Erstelle einen neuen Ordner auf deinem Computer namens code-assistant-extension für den Erweiterungs-Client.
- Füge die folgenden Dateien in den Ordner ein:
- manifest.json
- background.js
- content.js
- styles.css
- Öffne Chrome und gehe zu:
chrome://extensions/ - Aktiviere den Entwicklermodus oben rechts.
- Klicke auf Entpackt laden.
- Wähle den Ordner aus, der deine Erweiterungsdateien enthält.
- Die Erweiterung erscheint in der Symbolleiste.
- Klicke mit der rechten Maustaste auf eine beliebige Textauswahl auf einer Webseite, um Agent Sandbox im Kontextmenü zu sehen.
Deine Erweiterung ist nun einsatzbereit.
Repo - KI-Code-Assistent Browser-Erweiterung
Fazit
In diesem Artikel haben wir eine Chrome-Erweiterung erstellt, die sich mit einem Novita Sandbox-basierten Backend verbindet, sodass ein KI-Agent Code sicher ausführen und Benutzer beim Surfen unterstützen kann. Dieses Muster geht über Coding-Hilfe hinaus; es kann interaktive Lernwerkzeuge, Debugging-Assistenten, Dokumentationserweiterungen und mehr antreiben.
Die Architektur ist browserunabhängig, was bedeutet, dass der gleiche Ansatz mit minimalen Änderungen an jeden modernen Browser angepasst werden kann. Von hier aus kannst du die Fähigkeiten des Assistenten erweitern, die Benutzeroberfläche verfeinern oder neue Sandbox-Tools hinzufügen. Diese Grundlage öffnet die Tür für die Erstellung leistungsstarker, intelligenter Browser-Begleiter.
Novita AI ist eine führende KI-Cloud-Plattform, die Entwicklern benutzerfreundliche APIs sowie erschwingliche, zuverlässige GPU-Infrastruktur zum Erstellen und Skalieren von KI-Anwendungen bietet
