Google Workspace CLI: Baue einen RAG-Assistenten für deine Drive-Dateien
Lerne, wie du Googles neue KI-optimierte CLI installierst, authentifizierst und mit einer Gemini-basierten RAG-Pipeline verbindest, die Fragen zu deinen eigenen Drive-Dateien beantwortet.
Google hat etwas wirklich Spannendes für Entwickler veröffentlicht: Eine Google Workspace Kommandozeilenoberfläche, die dir strukturierten, skriptfähigen Zugriff auf dein gesamtes Google Workspace gibt – inklusive Drive, Gmail, Calendar, Sheets, Docs, Chat und mehr – alles in einem Tool.
Der Code ist fast vollständig in Rust (99,3%) geschrieben, wird aber über npm als vorkompilierte Native-Binaries ausgeliefert, sodass du keine Rust-Toolchain installieren musst.
In diesem Tutorial richtest du die Google Workspace CLI von Grund auf ein, authentifizierst sie und baust einen Gemini-basierten RAG-Assistenten, mit dem du deine Drive-Dateien in natürlicher Sprache abfragen kannst. Am Ende hast du:
gws installiert und mit deinem Drive authentifiziert
Eine Python-Umgebung mit ChromaDB, Sentence Transformers und dem Gemini SDK
Eine funktionierende lokale RAG-Pipeline, die Dateien über gws abruft, lokal mit einem Modell embeddert und Antworten mit Gemini 2.5 Flash per Stream ausgibt
Hinweis: Das ist kein offiziell unterstütztes Google-Produkt.
Was ist die Google Workspace CLI?
Die Google Workspace CLI (gws) ist ein KI-optimiertes Kommandozeilentool für dein gesamtes Google-Workspace-Ökosystem. Das macht sie besonders:
Dynamische Befehlsentdeckung: Statt einen fest verdrahteten Befehlssatz auszuliefern, liest gws zur Laufzeit den Discovery Service von Google. Wenn Google neue API-Endpunkte hinzufügt, übernimmt die CLI sie automatisch – ganz ohne Versionssprung.
Agent-natives Design: Die CLI bringt über 100 vorgefertigte Agent-Skills und 50 kuratierte Rezepte für gängige Workflows mit – E-Mails zusammenfassen, Drive-Dateien verschieben, Kalendereinträge erstellen und mehr. Jeder Output ist strukturiertes JSON, damit LLMs ihn direkt verarbeiten können.
Native MCP-Server-Unterstützung: Starte gws als lokalen MCP-Server und gib deinem KI-Agenten vollen Zugriff auf Workspace-Tools – ganz ohne eigenen API-Client.
Model-Armor-Integration: Für produktive Agenten, die nutzergenerierte Inhalte lesen, kannst du API-Antworten durch Google Cloud Model Armor leiten, um sie vor Prompt-Injection zu schützen, bevor dein LLM sie sieht.
Wenn gws nach der Installation nicht gefunden wird, stelle sicher, dass dein npm global-Bin-Verzeichnis in deinem PATH liegt.
Schritt 2: Projekt in Google Cloud anlegen
Die CLI authentifiziert sich über den OAuth-Client eines GCP-Projekts, daher brauchst du eines, bevor du dich einloggst. So erstellst oder wählst du ein Projekt in GCP aus:
Klicke auf die Projektauswahl und wähle Neues Projekt
Gib ihm einen Namen und notiere dir die automatisch generierte Projekt-ID, die wir im nächsten Schritt verwenden
Richte OAuth-Zugangsdaten ein:
Wechsle zu APIs & Dienste und wähle Zugangsdaten
Klicke auf Zugangsdaten erstellen und wähle OAuth 2.0-Client-ID
Wähle Desktop-App als Anwendungstyp
Notiere dir die Client-ID und das Client-Secret, die wir für gws auth setup verwenden.
Die CLI schließt OAuth ab, indem sie einen temporären Localhost-Server startet. Desktop-App-Clients handhaben beliebige Localhost-Ports automatisch. Bei Web-Application-Clients musst du jeden http://localhost:PORT manuell zu den autorisierten Redirect-URIs hinzufügen.
Wenn du einen Web-Application-Client nutzt und die Meldung „Zugriff blockiert: Die Anfrage dieser App ist ungültig“ erhältst, gehe zu Zugangsdaten, klicke auf den OAuth-Client, dann auf Autorisierte Weiterleitungs-URIs und füge die im Fehler angezeigte http://localhost:PORT-URL exakt hinzu.
Schritt 3: Authentifizieren
Um gws mit deinem GCP-Projekt zu verbinden, exportiere zuerst deine OAuth-Zugangsdaten als Umgebungsvariablen, damit der Setup-Assistent sie automatisch übernimmt:
exportGOOGLE_WORKSPACE_CLI_CLIENT_ID="CLIENT_ID"exportGOOGLE_WORKSPACE_CLI_CLIENT_SECRET="CLIENT_SECRET"Bereitgestellt von
Das zeigt das authentifizierte Konto, das verknüpfte GCP-Projekt und die aktiven Scopes. Wenn etwas nicht stimmt, führe gws auth setup mit den richtigen Flags erneut aus, statt die Konfigurationsdateien manuell zu bearbeiten.
Schritt 4: Drive-Zugriff testen
Bevor wir Python schreiben, lohnt sich ein schneller Check direkt im Terminal, um sicherzustellen, dass die CLI authentifiziert ist und mit deinem Drive sprechen kann. Liste dazu kurz deine fünf aktuellsten Drive-Dateien auf:
Das Flag --params akzeptiert einen JSON-String, der direkt auf die Abfrageparameter der Drive API abbildet. Hier begrenzt pageSize: 5 die Ausgabe auf fünf Ergebnisse – genug für einen Konnektivitätscheck, ohne deinen gesamten Drive auszugeben.
Wenn die Authentifizierung korrekt funktioniert, erhältst du eine strukturierte JSON-Antwort wie diese:
{"files":[{"id":"1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms","name":"Q3 Strategy Doc","mimeType":"application/vnd.google-apps.document"},
...
]}Bereitgestellt von
Jede Antwort von gws ist strukturiertes JSON – kein Parsen, kein Scrapen, keine fragilen Textausgaben.
Wenn du stattdessen einen „403-Fehler“ mit serviceusage.services.use siehst, sind deine OAuth-Zugangsdaten mit einem anderen GCP-Projekt verknüpft als dem mit aktivierter Abrechnung. Führe das Setup mit der korrekten Projekt-ID erneut aus, um alles neu zu verknüpfen:
Mit bestätigtem Drive-Zugriff können wir die API aktivieren und mit dem Python-Teil der Pipeline beginnen.
Schritt 5: Google Drive API aktivieren
An diesem Punkt kann sich gws zwar gegenüber deinem GCP-Projekt authentifizieren, aber noch keine Drive-Inhalte lesen. GCP trennt Authentifizierung (wer du bist) von API-Aktivierung (welche Dienste dein Projekt nutzt). Selbst mit gültigen OAuth-Daten führt jeder Abruf von Dateiinhalten zu 403 accessNotConfigured, bis die Drive API im Projekt explizit aktiviert wird.
Öffne dazu diese URL im Browser, ersetze [PROJECT_ID] durch deine Projekt-ID und klicke auf Enable:
Alternativ kannst du, falls die gcloud-CLI installiert ist, die Aktivierung direkt im Terminal vornehmen:
gcloud services enable drive.googleapis.com --project YOUR_PROJECT_IDBereitgestellt von
Beide Methoden tun dasselbe. Das Ausführen auf einer bereits aktivierten API ist unkritisch, da der Vorgang idempotent ist und keine bestehende Konfiguration zurücksetzt.
Nach der Aktivierung kann es ein paar Sekunden dauern, bis GCP die Änderung propagiert. Wenn du sofort gws drive files list ausführst und noch einen 403 siehst, warte 30 Sekunden und versuche es erneut.
Schritt 6: Abhängigkeiten installieren
Mit der vollständig konfigurierten CLI richten wir nun die Python-Umgebung ein, die das Embedding- und Retrieval-Handling übernimmt. Die RAG-Pipeline ist bewusst schlank: vier Pakete, keine GPU nötig, und alle Embeddings laufen lokal auf deinem Rechner.
Erstelle zunächst eine virtuelle Umgebung, indem du diese Befehle nacheinander ausführst:
cd /path/to/your/project
python3 -m venv .venv
source .venv/bin/activate
python -m pip install--upgrade pip
pip install-r requirements.txtBereitgestellt von
Installiere dann die Abhängigkeiten aus requirements.txt:
.txt
chromadb==1.5.5
google-genai==1.70.0
langchain-text-splitters==1.1.1
sentence-transformers==5.3.0Bereitgestellt von
Das steuern die Pakete zur Pipeline bei:
chromadb: Lokale Vektordatenbank, die Dokument-Embeddings auf der Festplatte unter ./chroma_db speichert und bei späteren Läufen lädt, damit die App bereits verarbeitete Dateien nicht erneut embedden muss.
google-genai: Offizielles Python-SDK für Gemini. Die App nutzt es, um den abgerufenen Kontext und die Nutzerfrage an Gemini 2.5 Flash zu senden und die Antwort tokenweise zu streamen.
langchain-text-splitters: Enthält den RecursiveCharacterTextSplitter, der große Dokumente vor dem Embedding in überlappende Abschnitte zerlegt. Die Überlappung stellt sicher, dass Sätze an Abschnittsgrenzen nicht aus dem Kontext gerissen werden.
sentence-transformers: Führt das Embedding-Modell lokal über ChromaDBs DefaultEmbeddingFunction aus. Jeder Dokumentabschnitt wird also ohne externe API-Aufrufe direkt auf deiner Maschine eingebettet.
Hinweis: Das Embedding-Modell (~90 MB) wird beim ersten Gebrauch automatisch heruntergeladen und lokal zwischengespeichert.
Mit der fertigen Python-Umgebung fehlt vor dem Coden nur noch dein Gemini-API-Schlüssel.
Schritt 7: Gemini API Key einrichten
Die RAG-Pipeline nutzt Gemini als Antwortschicht, weil es die abgerufenen Dokumentabschnitte und die Nutzerfrage nimmt und eine fundierte Antwort zurückstreamt. Um Gemini zu verbinden, brauchst du einen API-Schlüssel aus dem Google AI Studio.
Melde dich mit deinem Google-Konto an, klicke auf Get API Key und dann Create API Key. Wähle bei Aufforderung dein GCP-Projekt aus, damit Kontingente und Abrechnung greifen. Verknüpfe ein Abrechnungskonto im GCP-Dashboard unter Abrechnung und wähle dann Link a billing account.
Sobald du deinen Schlüssel hast, exportiere ihn als Umgebungsvariable:
Um ihn über Terminal-Sitzungen hinweg zu behalten, führe aus:
echo'export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"'>> ~/.zshrc
source ~/.zshrcBereitgestellt von
Die App liest diesen Schlüssel beim Start über os.getenv("GEMINI_API_KEY"). Wenn die Variable nicht gesetzt ist, wirst du aufgefordert, den Schlüssel interaktiv einzufügen.
Modellhinweis: Standard ist gemini-2.5-flash. Du kannst dies via GEMINI_MODEL="gemini-2.5-pro" überschreiben, wenn du bei komplexen Dokumenten stärkere Reasoning-Fähigkeiten willst. Beachte, dass gemini-2.0-flash für neue API-Schlüssel eingestellt wurde und hier nicht verwendet werden sollte.
Mit Auth, Drive API, Abhängigkeiten und Gemini-Schlüssel ist alles bereit. Bauen wir die Pipeline.
Schritt 8: RAG-Pipeline bauen
Jetzt verdrahten wir alles. Wir arbeiten mit vier Dateien: fetcher.py, vector_store.py, main.py und requirements.txt. Jede Datei hat eine klar umrissene Aufgabe: fetcher.py kümmert sich um alle CLI-Interaktionen, vector_store.py übernimmt Embedding und Retrieval, und main.py orchestriert den gesamten Ablauf von der Ingestion bis zum Streaming-Chat.
Beim ersten Lauf holt gws deine Drive-Dateien, der Inhalt wird in Abschnitte zerlegt und lokal eingebettet, und der Vektorindex wird unter ./chroma_db auf die Festplatte geschrieben. Bei späteren Läufen lädt ChromaDB von der Platte, nur neue Dateien werden geholt und eingebettet, und die Chat-Schleife startet sofort ohne erneute Verarbeitung.
Gehen wir die Dateien durch.
Schritt 8.1: fetcher.py — Drive-Zugriff über gws
Dieses Modul ist die Brücke zwischen der gws-CLI und Python. Alle Drive-Aktionen wie Dateien auflisten, Inhalte exportieren und Auth prüfen laufen über Subprozesse mit gws, und der strukturierte JSON-Output wird in Python-Dicts geparst. Dadurch muss sich der Python-Code nie direkt mit OAuth-Tokens, HTTP-Clients oder API-Response-Schemata befassen – gws übernimmt alles und liefert sauberes JSON zurück.
def_gws_json_stdout(stdout:str)->str:ifnot stdout or"{"notin stdout:return stdout or""return stdout[stdout.find("{"):]Bereitgestellt von
Der Helper _gws_json_stdout löst ein subtiles Problem: gws gibt manchmal Keyring- oder Log-Zeilen vor der JSON-Nutzlast aus. Ein direktes json.loads(result.stdout) würde dann scheitern. Die Funktion schneidet alles vor dem ersten { ab.
defcheck_auth()->bool:
cmd =["gws","drive","about","get","--params",'{"fields": "user"}']
result = subprocess.run(cmd, capture_output=True, text=True)
payload = _gws_json_stdout(result.stdout)try:
data = json.loads(payload)if payload.strip()else{}except json.JSONDecodeError:print("Error checking authentication status (could not parse gws output).")returnFalseif"error"in data or result.returncode !=0:
handle_error_output(result.stdout, result.stderr)returnFalsereturnTrueBereitgestellt von
Die Funktion check_auth() wird beim Start in main.py aufgerufen. Sie macht einen leichten Drive-API-Call und prüft die Antwort. Wenn Auth kaputt ist oder Scopes fehlen, beendet sich die App sauber, bevor sie mit der Ingestion beginnt.
Der Error-Handler fängt zudem typische Fehlerbilder ab und gibt konkrete Hinweise aus:
Der Dokumentlisten-Abruf zielt nur auf MIME-Typen, die sich als Text exportieren lassen:
deffetch_document_list(limit=10)->list[dict]:
q =("(mimeType='text/plain' or mimeType='application/vnd.google-apps.document' ""or mimeType='application/vnd.google-apps.spreadsheet') and trashed=false")
cmd =["gws","drive","files","list","--params",
json.dumps({"q": q,"pageSize": limit,"orderBy":"modifiedTime desc"}),]Bereitgestellt von
Die Abfrage filtert auf Plain-Text, Google Docs und Google Sheets, jeweils sortiert nach letzter Änderung. Binärdateien (PDFs, Bilder) sind ausgeschlossen, da sie sich auf diesem Weg nicht sauber als Text exportieren lassen.
Das Herunterladen erfolgt je nach MIME-Typ unterschiedlich:
gws schreibt exportierte Dateibytes per -o auf die Platte, nicht auf stdout. Stdout enthält JSON-Metadaten. Der Code nutzt eine temporäre Datei für die Binärausgabe, liest sie ein und räumt im finally-Block auf, damit temporäre Dateien auch bei Fehlern immer entfernt werden.
Schritt 8.2: vector_store.py — Lokale Embeddings mit ChromaDB
Dieses Modul zerlegt eingehenden Text in Abschnitte, generiert Embeddings lokal mit Sentence Transformers, persistiert sie über ChromaDB auf der Festplatte und stellt eine einfache query()-Methode bereit, die die Chat-Schleife für jede Nutzerfrage aufruft.
PersistentClient schreibt den Vektorindex nach ./chroma_db und lädt ihn bei späteren Läufen – kein Neuimport nötig, solange du das Verzeichnis nicht löschst. Die DefaultEmbeddingFunction nutzt Sentence Transformers lokal, die Embeddings entstehen also auf deiner Maschine ohne Zusatzkosten.
Der Splitter nutzt chunk_size=1000 mit chunk_overlap=200, damit Kontext an Abschnittsgrenzen nicht verloren geht, wenn Sätze über zwei Abschnitte laufen.
defadd_document(self, file_id:str, filename:str, text:str):
chunks = self.text_splitter.split_text(text)
ids =[f"{file_id}_{i}"for i inrange(len(chunks))]
metadatas =[{"source": filename,"file_id": file_id}for _ in chunks]
self.collection.upsert(documents=chunks, metadatas=metadatas, ids=ids)print(f"-> Added {len(chunks)} chunks for {filename}")Bereitgestellt von
upsert bedeutet hier: Beim erneuten Durchlauf für bereits eingelesene Dateien werden Abschnitte aktualisiert, statt dass ein Fehler wegen doppelter IDs entsteht.
defdocument_exists(self, file_id:str)->bool:try:
results = self.collection.get(where={"file_id": file_id}, limit=1)returnlen(results["ids"])>0except Exception:returnFalseBereitgestellt von
Dieser schnelle Vorab-Check in der Ingestion-Schleife von main.py vermeidet erneutes Herunterladen bereits eingebetteter Dateien und macht spätere Läufe sofort startklar.
Schritt 8.3: main.py — Ingestion, Retrieval und Streaming-Chat
Die Datei main.py führt Auth-Checks aus, steuert die Ingestion-Schleife, initialisiert den Gemini-Client und betreibt die interaktive Chat-Schleife. Sie bleibt bewusst schlank, da die Schwerarbeit in fetcher.py und vector_store.py steckt.
defsetup_gemini():
api_key = os.getenv("GEMINI_API_KEY")ifnot api_key:print("Please enter your GEMINI_API_KEY (get one from Google AI Studio).")
api_key =input("Key: ").strip()return genai.Client(api_key=api_key)Bereitgestellt von
Wenn GEMINI_API_KEY nicht gesetzt ist, fragt die App interaktiv nach, statt abzustürzen. Du kannst deinen GEMINI_API_KEY wie im vorherigen Schritt setzen.
Der document_exists-Check ist das inkrementelle Ingestion-Verfahren: Pro Lauf werden nur neue Dateien heruntergeladen und eingebettet. Die Chat-Schleife holt die drei semantisch ähnlichsten Abschnitte und versieht jeden mit seinem Quell-Dateinamen:
defchat_loop(model, store):whileTrue:
query =input("\nYou: ")
results = store.query(query, n_results=3)
documents = results.get("documents",[[]])[0]
metadatas = results.get("metadatas",[[]])[0]
context_parts =[]for doc, meta inzip(documents, metadatas):
source = meta.get("source","Unknown")
context_parts.append(f"Source: {source}\nText:\n{doc}\n")
full_context ="\n---\n".join(context_parts)
prompt =f"""You are a helpful assistant. Answer the user's question based ONLY on the following context from their Google Drive. If you cannot answer from the context, say \"I don't know based on your documents.\"\nContext:\n{full_context}\nQuestion: {query}\n"""
model_name = os.getenv("GEMINI_MODEL","gemini-2.5-flash")
response = model.models.generate_content_stream(model=model_name, contents=prompt)for chunk in response:if chunk.text:print(chunk.text, end="", flush=True)print()Bereitgestellt von
Der Prompt ist strikt geerdet: Das LLM wird angewiesen, ausschließlich aus dem bereitgestellten Kontext zu antworten und „I don't know based on your documents.“ zu sagen, wenn die Antwort nicht enthalten ist. So werden Halluzinationen vermieden. Die Antworten streamen Token für Token via generate_content_stream, sodass die Ausgabe sofort erscheint.
Schritt 9: Die App starten
Wenn alle vier Dateien bereit sind, startest du die App mit:
Beim ersten Lauf prüft die App die Authentifizierung, initialisiert ChromaDB, lädt das Embedding-Modell, falls es noch nicht zwischengespeichert ist, holt bis zu 10 Drive-Dateien, embeddert und speichert sie und wechselt dann in die Chat-Schleife. Du siehst den Fortschritt für jede Datei während der Ingestion:
Bei späteren Läufen lädt ChromaDB den Index von der Platte, bereits eingelesene Dateien werden übersprungen, und du landest fast sofort am Chat-Prompt.
Wenn du hingegen alles neu einlesen willst – etwa nachdem du neue Drive-Dateien hinzugefügt hast –, lösche das ChromaDB-Verzeichnis und starte erneut:
Das führt check_auth() aus und gibt eine Liste deiner Drive-Dateien aus – so stellst du sicher, dass CLI, OAuth und Drive API funktionieren, bevor du eine vollständige Ingestion startest.
Fazit
Der mit der Google Workspace CLI gebaute RAG-Assistent ist bewusst minimal: gws holt die Daten, Sentence Transformers embedden lokal, ChromaDB speichert, Gemini streamt die Antwort. Vier Dateien, null Cloud-Embedding-Kosten, inkrementelle Ingestion bei jedem Lauf. Die Architektur skaliert sauber, wenn du Gmail und Calendar als zusätzliche Quellen hinzufügen, ein größeres Gemini-Modell einsetzen oder die Chat-Schleife durch einen LangGraph-Agenten ersetzen willst. Das Fundament bleibt gleich.
Ich bin Google Developers Expertin für ML (Gen AI), dreifache Kaggle-Expertin und Women-Techmakers-Botschafterin mit über drei Jahren Erfahrung in der Tech-Branche. 2020 habe ich ein Health-Tech-Startup mitgegründet und absolviere derzeit einen Master in Informatik an der Georgia Tech mit Schwerpunkt Machine Learning.
Google Workspace CLI: Häufige Fragen
Brauche ich eine Rust-Toolchain, um gws zu installieren?⌄
Nein. Die CLI wird als npm-Paket mit vorkompilierten Binaries für jedes OS und jede Architektur ausgeliefert. npm install -g @googleworkspace/cli ist der gesamte Vorgang.
Warum brauche ich ein GCP-Projekt?⌄
OAuth-Zugriff auf Google APIs für Entwickler erfordert ein GCP-Projekt, das die OAuth-Client-Zugangsdaten hält und das API-Kontingent nachverfolgt. Führe gws auth setup aus, um die Konfiguration vorzunehmen, aber das Projekt muss zuvor in der GCP Console existieren.
Ich erhalte einen 403 serviceusage.services.use Fehler.⌄
Deine OAuth-Zugangsdaten zeigen auf ein anderes GCP-Projekt als das mit aktivierter Abrechnung und API-Zugriff. Führe gws auth setup --project YOUR_PROJECT_ID erneut aus.
Wie füge ich Gmail oder Calendar als Dokumentquellen hinzu?⌄
Führe gws gmail messages list und gws calendar events list aus, um strukturiertes JSON zu erhalten. Ergänze neue Fetch-Funktionen in fetcher.py für jeden Dienst und rufe sie aus ingest_documents() auf.
Kann ich das mit einem Google-Workspace-Organisationskonto verwenden?⌄
Ja, aber deine Workspace-Adminperson muss die OAuth-App ggf. freigeben. Wenn der Auth-Flow „Access blocked by your organization“ zurückgibt, kläre mit der Adminperson die Freischaltung des OAuth-Clients.
Lerne die wichtigsten Arten von KI-Agenten kennen, wie sie mit ihrer Umgebung interagieren und wie sie in verschiedenen Branchen eingesetzt werden. Verstehe einfache reflexive, modellbasierte, zielbasierte, nutzenbasierte, lernende Agenten und mehr.
Keine Hacks, keine Tricks. Schüler/innen und Lehrer/innen, lest weiter, um zu erfahren, wie ihr die Datenerziehung, die euch zusteht, kostenlos bekommen könnt.
Bist du gerade auf der Suche nach einem Job, der Snowflake nutzt? Bereite dich mit diesen 20 besten Snowflake-Interview-Fragen vor, damit du den Job bekommst!
Google hat etwas wirklich Spannendes für Entwickler veröffentlicht: Eine Google Workspace Kommandozeilenoberfläche, die dir strukturierten, skriptfähigen Zugriff auf dein gesamtes Google Workspace gibt – inklusive Drive, Gmail, Calendar, Sheets, Docs, Chat und mehr – alles in einem Tool.
Der Code ist fast vollständig in Rust (99,3%) geschrieben, wird aber über npm als vorkompilierte Native-Binaries ausgeliefert, sodass du keine Rust-Toolchain installieren musst.
In diesem Tutorial richtest du die Google Workspace CLI von Grund auf ein, authentifizierst sie und baust einen Gemini-basierten RAG-Assistenten, mit dem du deine Drive-Dateien in natürlicher Sprache abfragen kannst. Am Ende hast du:
gwsinstalliert und mit deinem Drive authentifiziertEine Python-Umgebung mit ChromaDB, Sentence Transformers und dem Gemini SDK
Eine funktionierende lokale RAG-Pipeline, die Dateien über
gwsabruft, lokal mit einem Modell embeddert und Antworten mit Gemini 2.5 Flash per Stream ausgibtHinweis: Das ist kein offiziell unterstütztes Google-Produkt.
Was ist die Google Workspace CLI?
Die Google Workspace CLI (
gws) ist ein KI-optimiertes Kommandozeilentool für dein gesamtes Google-Workspace-Ökosystem. Das macht sie besonders:Dynamische Befehlsentdeckung: Statt einen fest verdrahteten Befehlssatz auszuliefern, liest
gwszur Laufzeit den Discovery Service von Google. Wenn Google neue API-Endpunkte hinzufügt, übernimmt die CLI sie automatisch – ganz ohne Versionssprung.Agent-natives Design: Die CLI bringt über 100 vorgefertigte Agent-Skills und 50 kuratierte Rezepte für gängige Workflows mit – E-Mails zusammenfassen, Drive-Dateien verschieben, Kalendereinträge erstellen und mehr. Jeder Output ist strukturiertes JSON, damit LLMs ihn direkt verarbeiten können.
Native MCP-Server-Unterstützung: Starte
gwsals lokalen MCP-Server und gib deinem KI-Agenten vollen Zugriff auf Workspace-Tools – ganz ohne eigenen API-Client.Model-Armor-Integration: Für produktive Agenten, die nutzergenerierte Inhalte lesen, kannst du API-Antworten durch Google Cloud Model Armor leiten, um sie vor Prompt-Injection zu schützen, bevor dein LLM sie sieht.
Die vollständige Skill-Bibliothek findest du hier: github.com/googleworkspace/cli/blob/main/docs/skills.md.
Tutorial: Baue einen RAG-Assistenten mit Google Workspace CLI und Gemini
In diesem Tutorial bauen wir den RAG-Assistenten Schritt für Schritt. Auf hoher Ebene macht die App Folgendes:
Authentifiziert sich über die
gws-CLI gegenüber deinem Google Drive und holt deine zuletzt bearbeiteten Docs, Sheets und TextdateienTeilt jedes Dokument in Abschnitte und embeddert es lokal mit Sentence Transformers über ChromaDB
Speichert den Vektorindex auf der Festplatte, sodass spätere Durchläufe bereits eingelesene Dateien überspringen und direkt in den Chat gehen
Akzeptiert eine Frage in natürlicher Sprache, ruft die drei relevantesten Abschnitte aus ChromaDB ab und baut daraus einen fundierten Kontext-Prompt
Streamt eine zitierte, rein kontextbasierte Antwort von Gemini 2.5 Flash Token für Token direkt in deinem Terminal
Lass uns das Schritt für Schritt aufbauen.
Vollständiger Tutorial-Code: https://github.com/AashiDutt/Google-Workspace-CLI-Demo
Schritt 1: CLI installieren
Die CLI wird als
npm-Paket mit vorkompilierten Rust-Binaries ausgeliefert. Keine Rust-Toolchain erforderlich.Überprüfe die Installation, indem du folgende Befehle nacheinander ausführst:
Wenn
gwsnach der Installation nicht gefunden wird, stelle sicher, dass deinnpm global-Bin-Verzeichnis in deinemPATHliegt.Schritt 2: Projekt in Google Cloud anlegen
Die CLI authentifiziert sich über den OAuth-Client eines GCP-Projekts, daher brauchst du eines, bevor du dich einloggst. So erstellst oder wählst du ein Projekt in GCP aus:
Richte OAuth-Zugangsdaten ein:
Wechsle zu APIs & Dienste und wähle Zugangsdaten
Klicke auf Zugangsdaten erstellen und wähle OAuth 2.0-Client-ID
Wähle Desktop-App als Anwendungstyp
Notiere dir die Client-ID und das Client-Secret, die wir für
gws auth setupverwenden.Die CLI schließt OAuth ab, indem sie einen temporären Localhost-Server startet. Desktop-App-Clients handhaben beliebige Localhost-Ports automatisch. Bei Web-Application-Clients musst du jeden
http://localhost:PORTmanuell zu den autorisierten Redirect-URIs hinzufügen.Wenn du einen Web-Application-Client nutzt und die Meldung „Zugriff blockiert: Die Anfrage dieser App ist ungültig“ erhältst, gehe zu Zugangsdaten, klicke auf den OAuth-Client, dann auf Autorisierte Weiterleitungs-URIs und füge die im Fehler angezeigte
http://localhost:PORT-URL exakt hinzu.Schritt 3: Authentifizieren
Um
gwsmit deinem GCP-Projekt zu verbinden, exportiere zuerst deine OAuth-Zugangsdaten als Umgebungsvariablen, damit der Setup-Assistent sie automatisch übernimmt:Führe dann aus:
Der Setup-Assistent wird:
Nach Abschluss siehst du: Setup complete!
Zugangsdaten werden in
~/.config/gws/gespeichert. Um zu prüfen, ob alles korrekt verbunden ist, führe aus:Das zeigt das authentifizierte Konto, das verknüpfte GCP-Projekt und die aktiven Scopes. Wenn etwas nicht stimmt, führe
gws auth setupmit den richtigen Flags erneut aus, statt die Konfigurationsdateien manuell zu bearbeiten.Schritt 4: Drive-Zugriff testen
Bevor wir Python schreiben, lohnt sich ein schneller Check direkt im Terminal, um sicherzustellen, dass die CLI authentifiziert ist und mit deinem Drive sprechen kann. Liste dazu kurz deine fünf aktuellsten Drive-Dateien auf:
Das Flag
--paramsakzeptiert einen JSON-String, der direkt auf die Abfrageparameter der Drive API abbildet. Hier begrenztpageSize: 5die Ausgabe auf fünf Ergebnisse – genug für einen Konnektivitätscheck, ohne deinen gesamten Drive auszugeben.Wenn die Authentifizierung korrekt funktioniert, erhältst du eine strukturierte JSON-Antwort wie diese:
Jede Antwort von
gwsist strukturiertes JSON – kein Parsen, kein Scrapen, keine fragilen Textausgaben.Wenn du stattdessen einen „403-Fehler“ mit
serviceusage.services.usesiehst, sind deine OAuth-Zugangsdaten mit einem anderen GCP-Projekt verknüpft als dem mit aktivierter Abrechnung. Führe das Setup mit der korrekten Projekt-ID erneut aus, um alles neu zu verknüpfen:Mit bestätigtem Drive-Zugriff können wir die API aktivieren und mit dem Python-Teil der Pipeline beginnen.
Schritt 5: Google Drive API aktivieren
An diesem Punkt kann sich
gwszwar gegenüber deinem GCP-Projekt authentifizieren, aber noch keine Drive-Inhalte lesen. GCP trennt Authentifizierung (wer du bist) von API-Aktivierung (welche Dienste dein Projekt nutzt). Selbst mit gültigen OAuth-Daten führt jeder Abruf von Dateiinhalten zu403 accessNotConfigured, bis die Drive API im Projekt explizit aktiviert wird.Öffne dazu diese URL im Browser, ersetze
[PROJECT_ID]durch deine Projekt-ID und klicke auf Enable:https://console.developers.google.com/apis/api/drive.googleapis.com/overview?project=[PROJECT_ID]Alternativ kannst du, falls die
gcloud-CLI installiert ist, die Aktivierung direkt im Terminal vornehmen:Beide Methoden tun dasselbe. Das Ausführen auf einer bereits aktivierten API ist unkritisch, da der Vorgang idempotent ist und keine bestehende Konfiguration zurücksetzt.
Nach der Aktivierung kann es ein paar Sekunden dauern, bis GCP die Änderung propagiert. Wenn du sofort
gws drive files listausführst und noch einen 403 siehst, warte 30 Sekunden und versuche es erneut.Schritt 6: Abhängigkeiten installieren
Mit der vollständig konfigurierten CLI richten wir nun die Python-Umgebung ein, die das Embedding- und Retrieval-Handling übernimmt. Die RAG-Pipeline ist bewusst schlank: vier Pakete, keine GPU nötig, und alle Embeddings laufen lokal auf deinem Rechner.
Erstelle zunächst eine virtuelle Umgebung, indem du diese Befehle nacheinander ausführst:
Installiere dann die Abhängigkeiten aus
requirements.txt:Das steuern die Pakete zur Pipeline bei:
chromadb: Lokale Vektordatenbank, die Dokument-Embeddings auf der Festplatte unter./chroma_dbspeichert und bei späteren Läufen lädt, damit die App bereits verarbeitete Dateien nicht erneut embedden muss.google-genai: Offizielles Python-SDK für Gemini. Die App nutzt es, um den abgerufenen Kontext und die Nutzerfrage anGemini 2.5 Flashzu senden und die Antwort tokenweise zu streamen.langchain-text-splitters: Enthält denRecursiveCharacterTextSplitter, der große Dokumente vor dem Embedding in überlappende Abschnitte zerlegt. Die Überlappung stellt sicher, dass Sätze an Abschnittsgrenzen nicht aus dem Kontext gerissen werden.sentence-transformers: Führt das Embedding-Modell lokal über ChromaDBsDefaultEmbeddingFunctionaus. Jeder Dokumentabschnitt wird also ohne externe API-Aufrufe direkt auf deiner Maschine eingebettet.Hinweis: Das Embedding-Modell (~90 MB) wird beim ersten Gebrauch automatisch heruntergeladen und lokal zwischengespeichert.
Mit der fertigen Python-Umgebung fehlt vor dem Coden nur noch dein Gemini-API-Schlüssel.
Schritt 7: Gemini API Key einrichten
Die RAG-Pipeline nutzt Gemini als Antwortschicht, weil es die abgerufenen Dokumentabschnitte und die Nutzerfrage nimmt und eine fundierte Antwort zurückstreamt. Um Gemini zu verbinden, brauchst du einen API-Schlüssel aus dem Google AI Studio.
Melde dich mit deinem Google-Konto an, klicke auf Get API Key und dann Create API Key. Wähle bei Aufforderung dein GCP-Projekt aus, damit Kontingente und Abrechnung greifen. Verknüpfe ein Abrechnungskonto im GCP-Dashboard unter Abrechnung und wähle dann
Link a billing account.Sobald du deinen Schlüssel hast, exportiere ihn als Umgebungsvariable:
Exportiere den Schlüssel in deinem Terminal:
Um ihn über Terminal-Sitzungen hinweg zu behalten, führe aus:
Die App liest diesen Schlüssel beim Start über
os.getenv("GEMINI_API_KEY"). Wenn die Variable nicht gesetzt ist, wirst du aufgefordert, den Schlüssel interaktiv einzufügen.Modellhinweis: Standard ist
gemini-2.5-flash. Du kannst dies viaGEMINI_MODEL="gemini-2.5-pro"überschreiben, wenn du bei komplexen Dokumenten stärkere Reasoning-Fähigkeiten willst. Beachte, dassgemini-2.0-flashfür neue API-Schlüssel eingestellt wurde und hier nicht verwendet werden sollte.Mit Auth, Drive API, Abhängigkeiten und Gemini-Schlüssel ist alles bereit. Bauen wir die Pipeline.
Schritt 8: RAG-Pipeline bauen
Jetzt verdrahten wir alles. Wir arbeiten mit vier Dateien:
fetcher.py,vector_store.py,main.pyundrequirements.txt. Jede Datei hat eine klar umrissene Aufgabe:fetcher.pykümmert sich um alle CLI-Interaktionen,vector_store.pyübernimmt Embedding und Retrieval, undmain.pyorchestriert den gesamten Ablauf von der Ingestion bis zum Streaming-Chat.Beim ersten Lauf holt
gwsdeine Drive-Dateien, der Inhalt wird in Abschnitte zerlegt und lokal eingebettet, und der Vektorindex wird unter./chroma_dbauf die Festplatte geschrieben. Bei späteren Läufen lädt ChromaDB von der Platte, nur neue Dateien werden geholt und eingebettet, und die Chat-Schleife startet sofort ohne erneute Verarbeitung.Gehen wir die Dateien durch.
Schritt 8.1:
fetcher.py— Drive-Zugriff übergwsDieses Modul ist die Brücke zwischen der
gws-CLI und Python. Alle Drive-Aktionen wie Dateien auflisten, Inhalte exportieren und Auth prüfen laufen über Subprozesse mitgws, und der strukturierte JSON-Output wird in Python-Dicts geparst. Dadurch muss sich der Python-Code nie direkt mit OAuth-Tokens, HTTP-Clients oder API-Response-Schemata befassen –gwsübernimmt alles und liefert sauberes JSON zurück.Der Helper
_gws_json_stdoutlöst ein subtiles Problem:gwsgibt manchmal Keyring- oder Log-Zeilen vor der JSON-Nutzlast aus. Ein direktesjson.loads(result.stdout)würde dann scheitern. Die Funktion schneidet alles vor dem ersten{ab.Die Funktion
check_auth()wird beim Start inmain.pyaufgerufen. Sie macht einen leichten Drive-API-Call und prüft die Antwort. Wenn Auth kaputt ist oder Scopes fehlen, beendet sich die App sauber, bevor sie mit der Ingestion beginnt.Der Error-Handler fängt zudem typische Fehlerbilder ab und gibt konkrete Hinweise aus:
Der Dokumentlisten-Abruf zielt nur auf MIME-Typen, die sich als Text exportieren lassen:
Die Abfrage filtert auf Plain-Text, Google Docs und Google Sheets, jeweils sortiert nach letzter Änderung. Binärdateien (PDFs, Bilder) sind ausgeschlossen, da sie sich auf diesem Weg nicht sauber als Text exportieren lassen.
Das Herunterladen erfolgt je nach MIME-Typ unterschiedlich:
gwsschreibt exportierte Dateibytes per-oauf die Platte, nicht auf stdout. Stdout enthält JSON-Metadaten. Der Code nutzt eine temporäre Datei für die Binärausgabe, liest sie ein und räumt imfinally-Block auf, damit temporäre Dateien auch bei Fehlern immer entfernt werden.Schritt 8.2:
vector_store.py— Lokale Embeddings mit ChromaDBDieses Modul zerlegt eingehenden Text in Abschnitte, generiert Embeddings lokal mit Sentence Transformers, persistiert sie über ChromaDB auf der Festplatte und stellt eine einfache
query()-Methode bereit, die die Chat-Schleife für jede Nutzerfrage aufruft.PersistentClientschreibt den Vektorindex nach./chroma_dbund lädt ihn bei späteren Läufen – kein Neuimport nötig, solange du das Verzeichnis nicht löschst. DieDefaultEmbeddingFunctionnutzt Sentence Transformers lokal, die Embeddings entstehen also auf deiner Maschine ohne Zusatzkosten.Der Splitter nutzt
chunk_size=1000mitchunk_overlap=200, damit Kontext an Abschnittsgrenzen nicht verloren geht, wenn Sätze über zwei Abschnitte laufen.upsertbedeutet hier: Beim erneuten Durchlauf für bereits eingelesene Dateien werden Abschnitte aktualisiert, statt dass ein Fehler wegen doppelter IDs entsteht.Dieser schnelle Vorab-Check in der Ingestion-Schleife von
main.pyvermeidet erneutes Herunterladen bereits eingebetteter Dateien und macht spätere Läufe sofort startklar.Schritt 8.3:
main.py— Ingestion, Retrieval und Streaming-ChatDie Datei
main.pyführt Auth-Checks aus, steuert die Ingestion-Schleife, initialisiert den Gemini-Client und betreibt die interaktive Chat-Schleife. Sie bleibt bewusst schlank, da die Schwerarbeit infetcher.pyundvector_store.pysteckt.Wenn
GEMINI_API_KEYnicht gesetzt ist, fragt die App interaktiv nach, statt abzustürzen. Du kannst deinenGEMINI_API_KEYwie im vorherigen Schritt setzen.Der
document_exists-Check ist das inkrementelle Ingestion-Verfahren: Pro Lauf werden nur neue Dateien heruntergeladen und eingebettet. Die Chat-Schleife holt die drei semantisch ähnlichsten Abschnitte und versieht jeden mit seinem Quell-Dateinamen:Der Prompt ist strikt geerdet: Das LLM wird angewiesen, ausschließlich aus dem bereitgestellten Kontext zu antworten und „I don't know based on your documents.“ zu sagen, wenn die Antwort nicht enthalten ist. So werden Halluzinationen vermieden. Die Antworten streamen Token für Token via
generate_content_stream, sodass die Ausgabe sofort erscheint.Schritt 9: Die App starten
Wenn alle vier Dateien bereit sind, startest du die App mit:
Beim ersten Lauf prüft die App die Authentifizierung, initialisiert
ChromaDB, lädt das Embedding-Modell, falls es noch nicht zwischengespeichert ist, holt bis zu 10 Drive-Dateien, embeddert und speichert sie und wechselt dann in die Chat-Schleife. Du siehst den Fortschritt für jede Datei während der Ingestion:Bei späteren Läufen lädt
ChromaDBden Index von der Platte, bereits eingelesene Dateien werden übersprungen, und du landest fast sofort am Chat-Prompt.Wenn du hingegen alles neu einlesen willst – etwa nachdem du neue Drive-Dateien hinzugefügt hast –, lösche das ChromaDB-Verzeichnis und starte erneut:
Um die Auth unabhängig zu prüfen, ohne die ganze Pipeline zu starten, führe
fetcher.pydirekt aus:Das führt
check_auth()aus und gibt eine Liste deiner Drive-Dateien aus – so stellst du sicher, dass CLI, OAuth und Drive API funktionieren, bevor du eine vollständige Ingestion startest.Fazit
Der mit der Google Workspace CLI gebaute RAG-Assistent ist bewusst minimal:
gwsholt die Daten, Sentence Transformers embedden lokal, ChromaDB speichert, Gemini streamt die Antwort. Vier Dateien, null Cloud-Embedding-Kosten, inkrementelle Ingestion bei jedem Lauf. Die Architektur skaliert sauber, wenn du Gmail und Calendar als zusätzliche Quellen hinzufügen, ein größeres Gemini-Modell einsetzen oder die Chat-Schleife durch einen LangGraph-Agenten ersetzen willst. Das Fundament bleibt gleich.