Zum Hauptinhalt springen
Lektion 18 von 20 · API-Tools: Suche, Dateien und Code Interpreter

Shell: Befehlsausführung

Lektion 18 von 20 Entwickler
Auf dieser Seite
  1. Shell: Befehlsausführung
  2. Konzept und Architektur
  3. Anwendungsfall: Systemdiagnose
  4. Absicherung der Ausführung
  5. Implementierung als benutzerdefinierte Funktion
  6. Automatisierungsworkflows
  7. Kernaussagen

Shell: Befehlsausführung

Dies ist das leistungsfähigste Werkzeug in diesem Kurs und das einzige, bei dem ein Fehler die Macht des Werkzeugs gegen Sie wenden kann. Andere Werkzeuge lesen, berechnen oder suchen; dieses Werkzeug handelt auf einer Maschine. Eine schlecht formulierte Systemdiagnose bleibt lediglich eine Diagnose; ein schlecht formulierter Bereinigungsbefehl löscht Dateien.

In dieser Lektion werden Nutzung und Schutzmaßnahmen gemeinsam behandelt, da sie nicht voneinander getrennt werden können. Die goldene Regel: Das Modell darf niemals einen Befehl ausführen, den Sie nicht ausdrücklich im Voraus autorisiert haben.

Konzept und Architektur

Der Unterschied zum Code Interpreter ist entscheidend und lässt sich auf ein Wort reduzieren: Isolation. Der Code Interpreter läuft in einer wegwerfbaren, netzwerkisolierten Sandbox, in der das Worst-Case-Szenario eine falsche Berechnung ist. Die Shell läuft in einer Umgebung, die Sie konfigurieren – potenziell Ihr lokaler Computer, potenziell ein Server –, in der das Worst-Case-Szenario eine irreversible Operation ist.

Diese Freiheit ist der Zweck: Ohne sie sind automatisierte Bereitstellung und Wartung unmöglich. Sie erfordert jedoch, dass Sie den Ausführungsbereich bewusst wählen. Ein dedizierter Container mit eingeschränkten Berechtigungen ist fast immer die richtige Wahl gegenüber dem direkten Zugriff auf den Host.

from openai import OpenAI

client = OpenAI()

# Das Shell-Werkzeug wird über die Agents SDK verwendet,
# da es eine kontrollierte Ausführungsumgebung erfordert
from agents import Agent, Runner

agent = Agent(
    name="devops-assistant",
    model="gpt-5.6-terra",
    instructions=(
        "Sie sind ein DevOps-Assistent. Sie führen Systembefehle aus, "
        "um Infrastrukturprobleme zu diagnostizieren und zu lösen. "
        "Erklären Sie jeden Befehl, bevor Sie ihn ausführen."
    ),
    tools=[{"type": "shell"}]
)

Anwendungsfall: Systemdiagnose

# Das Agent kann diagnostische Befehle ausführen
agent = Agent(
    name="diagnostic",
    model="gpt-5.6-terra",
    instructions=(
        "Diagnostizieren Sie den Systemzustand, indem Sie geeignete Befehle ausführen. "
        "Beginnen Sie mit grundlegenden Prüfungen (Festplatte, Speicher, CPU) "
        "und gehen Sie dann je nach Symptomen tiefer ein."
    ),
    tools=[{"type": "shell"}]
)

# Das Agent wird Befehle wie diese ausführen:
# df -h          -> Festplattenspeicher
# free -m        -> Speicher
# top -bn1       -> CPU-Last
# systemctl status nginx  -> Dienststatus

Absicherung der Ausführung

Drei Schutzmaßnahmen wirken zusammen, und keine von ihnen ist für sich allein ausreichend. Die Whitelist ist das Fundament: Sie listen auf, was erlaubt ist, statt was verboten ist, da eine Blacklist immer eine Variante verpasst – und es genügt nur eine. Ein Timeout verhindert, dass ein hängender Befehl das System blockiert. Ein eingeschränktes Arbeitsverzeichnis begrenzt den Schaden, wenn ein legitimer Befehl auf den falschen Ort verweist.

Eine vierte Schutzmaßnahme, die nicht technisch ist, aber die anderen drei wert ist: Erfordern Sie menschliche Bestätigung für jede zerstörerische oder irreversible Operation. Ein Modell macht Fehler mit derselben Sicherheit wie wenn es recht hat.

# Muster: Wrapper-Funktion mit Validierung
import subprocess
import shlex

ALLOWED_COMMANDS = {
    "diagnostic": ["df", "free", "top", "uptime", "ps", "netstat", "curl", "ping"],
    "deployment": ["git", "docker", "npm", "pip"],
    "files": ["ls", "cat", "head", "tail", "wc", "grep", "find"],
}

FORBIDDEN_COMMANDS = ["rm", "dd", "mkfs", "shutdown", "reboot", "kill", "passwd"]

def execute_secure_command(command: str, category: str = "diagnostic") -> dict:
    """Führt einen Befehl mit Sicherheitsvalidierung aus."""
    # Befehl parsen
    parts = shlex.split(command)
    executable = parts[0]

    # Verbote prüfen
    if executable in FORBIDDEN_COMMANDS:
        return {
            "error": f"Verbotener Befehl: {executable}",
            "reason": "Dieser Befehl befindet sich auf der Blacklist"
        }

    # Berechtigungen prüfen
    allowed = ALLOWED_COMMANDS.get(category, [])
    if executable not in allowed:
        return {
            "error": f"Befehl nicht erlaubt in Kategorie '{category}'",
            "allowed": allowed
        }

    # Ausführung mit Timeout
    try:
        result = subprocess.run(
            parts,
            capture_output=True,
            text=True,
            timeout=30,
            cwd="/tmp"  # Eingeschränktes Arbeitsverzeichnis
        )
        return {
            "stdout": result.stdout,
            "stderr": result.stderr,
            "return_code": result.returncode
        }
    except subprocess.TimeoutExpired:
        return {"error": "Timeout: Befehl überschritt 30 Sekunden"}

Implementierung als benutzerdefinierte Funktion

Die Implementierung dieser Funktion selbst über Funktionsaufrufe bietet einen Vorteil, den Sie nicht als Fallback betrachten sollten: Die Validierung lebt in Ihrem Code. Sie entscheiden über die Whitelist, das Logging und die Bestätigungsschritte und können alles überprüfen. Bei einem Werkzeug mit diesem Risikograd ist es vorzuziehen, die Kontrolle über den Checkpoint zu behalten, auch wenn eine native Version verfügbar wird.

tools = [
    {
        "type": "function",
        "name": "execute_command",
        "description": (
            "Führt einen Shell-Befehl auf dem Server aus. "
            "Erlaubte Befehle: Systemdiagnose (df, free, top, uptime, ps), "
            "Dateilesen (ls, cat, head, tail, grep), "
            "Netzwerk (curl, ping, netstat). "
            "Zerstörerische Befehle sind verboten."
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "Der Shell-Befehl zur Ausführung"
                },
                "directory": {
                    "type": "string",
                    "description": "Arbeitsverzeichnis (Standard: /tmp)"
                }
            },
            "required": ["command"],
            "additionalProperties": False
        },
        "strict": True
    }
]

import json

def handle_calls(response):
    results = []
    for item in response.output:
        if item.type == "function_call" and item.name == "execute_command":
            args = json.loads(item.arguments)
            result = execute_secure_command(
                args["command"],
                category="diagnostic"
            )
            results.append({
                "type": "function_call_output",
                "call_id": item.call_id,
                "output": json.dumps(result)
            })
    return results

# Vollständiger Zyklus
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Prüfen Sie den Festplattenspeicher und den verfügbaren Speicher",
    tools=tools
)

results = handle_calls(response)
final_response = client.responses.create(
    model="gpt-5.6-terra",
    input=response.output + results,
    tools=tools
)
print(final_response.output_text)

Automatisierungsworkflows

Automatisierte Bereitstellung

Die Bereitstellung ist der wertvollste Anwendungsfall, sofern Sie verstehen, was Sie delegieren. Das Modell erfindet das Verfahren nicht: Es verknüpft Schritte, die Sie definiert haben, überprüft Zwischenoutputs und stoppt bei jeder Anomalie. Es ist ein aufmerksamer Bediener, kein Entscheidungsträger.

tools_deploy = [
    {
        "type": "function",
        "name": "execute_deploy_step",
        "description": (
            "Führt einen Bereitstellungsschritt aus. "
            "Mögliche Schritte: git_pull, tests, build, deploy, rollback. "
            "Jeder Schritt wird vor dem Übergang zum nächsten validiert."
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "step": {
                    "type": "string",
                    "enum": ["git_pull", "tests", "build", "deploy", "rollback"]
                },
                "parameters": {
                    "type": "object",
                    "properties": {
                        "branch": {"type": "string"},
                        "environment": {"type": "string", "enum": ["staging", "production"]}
                    }
                }
            },
            "required": ["step"],
            "additionalProperties": False
        },
        "strict": True
    }
]

def execute_step(step: str, params: dict) -> dict:
    scripts = {
        "git_pull": "cd /app && git pull origin {branch}",
        "tests": "cd /app && npm test",
        "build": "cd /app && npm run build",
        "deploy": "cd /app && ./deploy.sh {environment}",
        "rollback": "cd /app && ./rollback.sh {environment}"
    }
    command = scripts[step].format(**params)
    return execute_secure_command(command, "deployment")

Überwachung und Alarme

Die Überwachung kehrt die Logik des Rests dieser Lektion um: Die Befehle hier sind schreibgeschützt, daher sind sie harmlos, und der Wert liegt in der Interpretation. Ein Modell, das Festplattenspeicher, Last und Fehlerprotokolle liest, erkennt Korrelationen, die eine isolierte Schwelle verpasst – was diesen Punkt zum sichersten Einstieg für den Start mit diesem Werkzeug macht.

def check_system_health() -> dict:
    """Prüft den Systemzustand und gibt einen Bericht zurück."""
    checks = {}

    # Festplattenspeicher
    r = execute_secure_command("df -h /", "diagnostic")
    checks["disk"] = r

    # Speicher
    r = execute_secure_command("free -m", "diagnostic")
    checks["memory"] = r

    # Dienste
    for service in ["nginx", "postgresql", "redis"]:
        r = execute_secure_command(
            f"systemctl is-active {service}",
            "diagnostic"
        )
        checks[f"service_{service}"] = r

    return checks

# Das AI analysiert die Ergebnisse und schlägt Maßnahmen vor
response = client.responses.create(
    model="gpt-5.6-terra",
    input=f"Analysieren Sie diesen Systemgesundheitsbericht und identifizieren Sie Probleme:\n"
          f"{json.dumps(check_system_health(), indent=2)}",
    tools=tools
)

Kernaussagen

  • Das Shell-Werkzeug verbindet das Modell mit Ihrer Infrastruktur
  • Implementieren Sie Whitelists und Blacklists für Befehle
  • Verwenden Sie Timeouts und eingeschränkte Arbeitsverzeichnisse
  • Funktionsaufrufe ermöglichen die Implementierung von Shell als benutzerdefiniertes Werkzeug
  • Kombinieren Sie Shell mit anderen Werkzeugen für vollständige DevOps-Workflows