Skip to content

Latest commit

 

History

History
218 lines (184 loc) · 13.9 KB

File metadata and controls

218 lines (184 loc) · 13.9 KB

Anleitung: Plugins für den Gemini Desktop Agent erstellen

Willkommen zur Plugin-Entwicklungsanleitung für den Gemini Desktop Agent! Plugins sind der Schlüssel, um die Fähigkeiten des Agenten zu erweitern, indem sie ihm Zugriff auf externe Tools, APIs und benutzerdefinierte Logik ermöglichen. Diese Anleitung führt dich durch den Prozess der Plugin-Erstellung, mit besonderem Fokus auf die Integration mit der nativen Function Calling Fähigkeit der Gemini API.

1. Grundkonzept und Architektur

Das Plugin-System des Gemini Desktop Agenten ermöglicht es dem KI-Modell (Gemini), intelligent zu entscheiden, wann eine externe Funktion (dein Plugin) aufgerufen werden soll, um eine Benutzeranfrage besser zu beantworten oder eine Aktion auszuführen.

Der Ablauf ist typischerweise wie folgt:

  1. Der Nutzer stellt eine Anfrage.
  2. Die MainApp sendet diese Anfrage zusammen mit den Deklarationen aller verfügbaren Plugins an die Gemini API.
  3. Gemini analysiert die Anfrage und die Plugin-Deklarationen.
  4. Wenn Gemini entscheidet, ein Plugin zu nutzen, antwortet es nicht mit direktem Text, sondern mit einem strukturierten FunctionCall-Objekt. Dieses Objekt spezifiziert, welches deiner Plugins aufgerufen werden soll und mit welchen Argumenten.
  5. Die MainApp empfängt diesen FunctionCall, identifiziert das entsprechende Plugin und führt dessen Python-Code mit den von Gemini gelieferten Argumenten aus.
  6. Das Ergebnis der Plugin-Ausführung wird von der MainApp zurück an die Gemini API gesendet.
  7. Gemini verwendet dieses Ergebnis, um eine endgültige, menschenlesbare Antwort für den Nutzer zu generieren.

2. Struktur eines Plugins

Jedes Plugin residiert in einem eigenen Unterordner innerhalb des Hauptverzeichnisses plugins/.

Beispiel-Ordnerstruktur:

gemini-desktop-agent/
│
├── plugins/
│   │
│   └── mein_beispiel_plugin/
│       │
│       ├── __init__.py         # Macht den Ordner zu einem Python-Paket
│       ├── manifest.json       # Metadaten und Funktionsdeklaration für Gemini
│       └── plugin_code.py      # Der eigentliche Python-Code des Plugins
│
├── main.py
├── quick_ask_overlay.py
└── ... (andere Kerndateien)

2.1. __init__.py

Diese Datei kann leer sein. Ihre Existenz signalisiert Python, dass der Ordner (mein_beispiel_plugin) als Python-Paket behandelt werden soll, was für korrekte Importe notwendig ist.

2.2. manifest.json (Die Steuerzentrale deines Plugins)

Diese JSON-Datei ist entscheidend. Sie enthält alle Metadaten, die der PluginManager und die Gemini API benötigen, um dein Plugin zu verstehen und zu nutzen.

Felder im Detail:

  • name (String):

    • Ein für Menschen lesbarer Name deines Plugins, wie er z.B. in einer Plugin-Verwaltungs-UI angezeigt werden könnte.
    • Beispiel: "Wettervorhersage"
  • version (String):

    • Die Version deines Plugins, vorzugsweise im Semantic Versioning Format (z.B. "1.0.2").
    • Beispiel: "0.1.0"
  • description (String):

    • Eine kurze, allgemeine Beschreibung, was das Plugin tut. Diese ist primär für menschliche Nutzer oder eine Plugin-Verwaltungs-UI gedacht.
    • Beispiel: "Ruft die aktuelle Wettervorhersage für einen bestimmten Ort ab."
  • module_name (String):

    • Der Dateiname deines Python-Moduls, das die Plugin-Logik enthält (ohne die .py-Endung).
    • Beispiel: "wetter_api_handler" (entspricht wetter_api_handler.py)
  • function_name (String):

    • Der Name der spezifischen Python-Funktion innerhalb deines Moduls (module_name.py), die als Haupteinstiegspunkt für das Plugin dient und von der MainApp aufgerufen wird.
    • Beispiel: "get_weather_for_location"
  • invocation_commands (Array von Strings, veraltet):

    • Dieses Feld wurde vom älteren, Regex-basierten Plugin-System verwendet. Es ist für neue Plugins, die auf Gemini Function Calling setzen, nicht mehr primär relevant, kann aber für Abwärtskompatibilität oder als Fallback noch vorhanden sein.
    • Beispiel: ["wetter", "vorhersage"]
  • parameters (Array von Objekten, veraltet):

    • Ebenfalls Teil des alten Systems, beschreibt Parameter für die Regex-basierte Auslösung. Für neue Plugins, die function_declaration nutzen, ist dies weniger wichtig.
    • Beispiel:
      [
          {
              "name": "location",
              "type": "string",
              "description": "Der Ort für die Wettervorhersage."
          }
      ]
  • function_declaration (Objekt, ESSENZIELL für Gemini Function Calling):

    • Dieses Objekt definiert, wie dein Plugin der Gemini API für native Funktionsaufrufe präsentiert wird. Es folgt einer Teilmenge des OpenAPI-Schemas.
    • name (String):
      • Der Name, den Gemini intern verwenden wird, um diese Funktion/dieses Tool zu identifizieren und anzufordern.
      • Wichtig: Muss den Namenskonventionen für Funktionen folgen (alphanumerisch, Unterstriche erlaubt, z.B. get_weather_forecast, scheduleMeeting). Keine Leerzeichen oder Sonderzeichen.
      • Dieser Name wird von MainApp verwendet, um die korrekte Python-Funktion deines Plugins zuzuordnen. Es ist oft sinnvoll, diesen Namen identisch oder sehr ähnlich zum Python-Funktionsnamen (function_name) zu halten.
      • Beispiel: "get_weather_forecast"
    • description (String):
      • Extrem wichtig! Dies ist die Hauptinformation, die Gemini verwendet, um zu verstehen, was deine Funktion tut und wann sie aufgerufen werden sollte.
      • Sei präzise, detailliert und gib klare Anwendungsfälle oder Beispiele an. Erkläre den Zweck und die Fähigkeiten der Funktion.
      • Beispiel: "Ruft die aktuelle Wettervorhersage (Temperatur, Zustand, Wind) für einen gegebenen Ort ab. Nützlich, wenn der Nutzer nach dem Wetter an einem spezifischen Ort fragt."
    • parameters (Objekt):
      • Definiert die Eingabeparameter, die deine Funktion von Gemini erwartet.
      • type (String): Muss immer OBJECT sein (Großbuchstaben!).
      • properties (Objekt): Ein Objekt, bei dem jeder Schlüssel der Name eines Parameters ist, den deine Python-Funktion als Argument erwartet.
        • Jedes Parameter-Objekt innerhalb von properties hat:
          • type (String): Der Datentyp des Parameters. Gemini unterstützt STRING, INTEGER, NUMBER (für Fließkommazahlen), BOOLEAN, ARRAY. (Immer Großbuchstaben!).
          • description (String): Eine klare Beschreibung dieses spezifischen Parameters, seines Zwecks, seines erwarteten Formats und ggf. Beispiele. Dies hilft Gemini, die richtigen Werte aus der Nutzeranfrage zu extrahieren.
            • Beispiel für einen location-Parameter: "Der Stadt- und optional Ländername für die Wetterabfrage, z.B. 'Berlin, DE' oder 'London'."
          • enum (Array von Strings, optional): Wenn ein Parameter nur eine feste Auswahl von Werten annehmen darf, liste diese hier auf. Dies erhöht die Genauigkeit.
            • Beispiel für einen unit-Parameter: {"type": "STRING", "description": "Temperatureinheit.", "enum": ["celsius", "fahrenheit"]}
      • required (Array von Strings): Eine Liste der Parameternamen aus properties, die zwingend von Gemini bereitgestellt werden müssen, damit deine Funktion korrekt ausgeführt werden kann.
        • Beispiel: ["location"] (wenn location immer benötigt wird)

Beispiel manifest.json für ein Wetter-Plugin:

{
    "name": "Wettervorhersage",
    "version": "1.1.0",
    "description": "Ruft die aktuelle Wettervorhersage ab.",
    "module_name": "wetter_plugin_code",
    "function_name": "hole_wetterdaten",
    "function_declaration": {
        "name": "get_weather_forecast",
        "description": "Ruft die aktuelle Wettervorhersage (Temperatur in Celsius und Wetterbeschreibung) für einen bestimmten Ort ab. Zu verwenden, wenn der Nutzer explizit nach dem Wetter fragt.",
        "parameters": {
            "type": "OBJECT",
            "properties": {
                "location": {
                    "type": "STRING",
                    "description": "Der Name der Stadt (und optional des Landes, z.B. 'Berlin,DE') für die die Wettervorhersage abgerufen werden soll."
                },
                "unit": {
                    "type": "STRING",
                    "description": "Die gewünschte Temperatureinheit.",
                    "enum": ["celsius", "fahrenheit"]
                }
            },
            "required": ["location"]
        }
    }
}

2.3. Python-Modul (<module_name>.py)

Diese Datei enthält die eigentliche Python-Logik deines Plugins.

Beispiel plugins/mein_wetter_plugin/wetter_plugin_code.py:

import random # Für ein Dummy-Beispiel

# Der Name dieser Funktion muss mit "function_name" im Manifest übereinstimmen.
# Die Parameter sollten mit den in "function_declaration.parameters.properties"
# definierten Namen und erwarteten Typen übereinstimmen.
def hole_wetterdaten(location: str, unit: str = "celsius") -> dict:
    """
    Simuliert das Abrufen von Wetterdaten.
    In einer echten Anwendung würde hier ein API-Aufruf zu einem Wetterdienst erfolgen.
    """
    print(f"[WetterPlugin] Wetter für '{location}' in '{unit}' angefragt.")

    # Dummy-Daten für das Beispiel
    if "berlin" in location.lower():
        temp_c = random.randint(5, 25)
        condition = random.choice(["Sonnig", "Wolkig", "Leichter Regen"])
    elif "london" in location.lower():
        temp_c = random.randint(0, 20)
        condition = random.choice(["Bedeckt", "Nieselregen", "Windig"])
    else:
        return {"error": f"Wetterdaten für {location} nicht verfügbar."}

    if unit.lower() == "fahrenheit" and temp_c is not None:
        temp_f = (temp_c * 9/5) + 32
        return {
            "location": location,
            "temperature": round(temp_f, 1),
            "unit": "fahrenheit",
            "condition": condition
        }
    elif temp_c is not None:
        return {
            "location": location,
            "temperature": temp_c,
            "unit": "celsius",
            "condition": condition
        }
    return {"error": "Unbekannter Fehler beim Abrufen der Wetterdaten."}

# Optionaler Testblock
if __name__ == '__main__':
    print(hole_wetterdaten(location="Berlin"))
    print(hole_wetterdaten(location="London", unit="fahrenheit"))
    print(hole_wetterdaten(location="Mond"))

Wichtige Aspekte deiner Python-Funktion:

  • Parameter: Die Funktionssignatur sollte die in function_declaration.parameters.properties definierten Parameter als Argumente haben. Du kannst Standardwerte für optionale Parameter definieren.
  • Typ-Annotationen: Die Verwendung von Typ-Annotationen (z.B. location: str) ist guter Stil und hilft bei der Entwicklung.
  • Logik: Hier implementierst du, was dein Plugin tun soll (API-Aufrufe, Berechnungen, Dateizugriffe etc.).
  • Rückgabewert:
    • Deine Funktion muss ein Ergebnis zurückgeben.
    • Dieses Ergebnis wird von MainApp an Gemini gesendet, damit Gemini eine finale Antwort formulieren kann.
    • Der Rückgabewert sollte für Gemini leicht verständlich sein. Oft ist ein Dictionary (das dann als JSON-Struktur an Gemini geht) eine gute Wahl, besonders wenn mehrere Datenpunkte zurückgegeben werden. Einfache Strings oder Zahlen sind auch möglich.
    • Im Falle eines Fehlers innerhalb deines Plugins, gib eine klare Fehlermeldung zurück, idealerweise als Teil eines Dictionaries mit einem "error"-Schlüssel (z.B. return {"error": "API-Limit erreicht"}). MainApp wird dies dann als Fehler an Gemini melden.
  • Keine direkten UI-Interaktionen: Dein Plugin-Code sollte idealerweise keine direkten UI-Operationen (wie das Anzeigen von QMessageBox) durchführen. Solche Interaktionen sollten von MainApp basierend auf den Ergebnissen des Plugins oder den Antworten von Gemini gesteuert werden. Logge Fehler oder Informationen mit print().

6. Registrierung und Nutzung

  1. Platzierung: Lege deinen Plugin-Ordner (z.B. mein_beispiel_plugin/) in das plugins/ Verzeichnis der Hauptanwendung.
  2. Neustart: Starte die main.py des Gemini Desktop Agenten neu.
  3. Discovery: Der PluginManager wird dein Plugin beim Start automatisch entdecken, das manifest.json lesen und die function_declaration registrieren. Du solltest eine Log-Ausgabe in der Konsole sehen, die dein Plugin erwähnt.
  4. Interaktion: Stelle im Haupt-Chatfenster Anfragen an Gemini, die die Funktionalität deines Plugins erfordern könnten. Wenn deine function_declaration (insbesondere die descriptions) gut formuliert ist, wird Gemini versuchen, dein Plugin über einen FunctionCall aufzurufen.

7. Debugging-Tipps

  • Konsolenausgaben: Achte auf print()-Ausgaben von PluginManager (beim Entdecken von Plugins), MainApp (wenn ein FunctionCall empfangen und verarbeitet wird) und deinem eigenen Plugin-Code.
  • Manifest-Validierung: Stelle sicher, dass dein manifest.json valides JSON ist und alle benötigten Felder (besonders innerhalb der function_declaration) korrekt formatiert sind. Die Typen (OBJECT, STRING, INTEGER etc.) in der Deklaration müssen großgeschrieben sein.
  • Parameter-Übereinstimmung: Die Namen und erwarteten Typen der Parameter in deiner Python-Funktion müssen mit den properties in der function_declaration übereinstimmen.
  • Gemini's Antwort: Wenn Gemini dein Plugin nicht wie erwartet aufruft, experimentiere mit der description deiner Funktion und ihrer Parameter in der function_declaration. Mache sie so klar und unmissverständlich wie möglich.
  • Testen des Plugins isoliert: Nutze den if __name__ == '__main__':-Block in deiner Plugin-Python-Datei, um die Funktion unabhängig von der Hauptanwendung zu testen.

Viel Erfolg bei der Entwicklung deiner Plugins!