1. Einführung

Du betreibst einen gemütlich-gruseligen Kanal auf VibeTube: jeden Abend bis Halloween ein Short. Die Zuschauer wollen kleine magische Momente in kleinen Räumen und schicken dir alles, was sie können – als Kommentare, Sprachnotizen, Fotos ihrer eigenen verzauberten Schubladen und Clips ihrer Veranda in der Abenddämmerung. Das Backlog ist voller Ideen. Jeden Abend stehen dieselben Aufgaben an: recherchieren, was im Feed gerade angesagt ist, den Backlog durchgehen, die Nachrichten der Zuschauer lesen, eine Richtung wählen, die Richtlinien des Kanals prüfen, das Skript schreiben, das Poster zeichnen, den Clip rendern, veröffentlichen.
Generative Modelle können jede Aufgabe einzeln erledigen. Gemini recherchiert, hört zu, sieht sich Dinge an und schreibt, das Bildmodell zeichnet und Veo rendert. Was sie nicht allein können, ist, die Nacht in der richtigen Reihenfolge zu durchlaufen, für die eine Entscheidung anzuhalten, die Ihnen obliegt, eine schlechte Richtung abzulehnen, bevor sie Geld kostet, Minuten auf ein Rendering zu warten, ohne etwas offen zu halten, und eine Aufzeichnung zu hinterlassen.
In diesem Codelab wird dieser Abend in ein ADK Workflow verwandelt: ein gezeichnetes Diagramm aus Knoten und Kanten, das sich selbst ausführt, Sie nur dann fragt, wenn Ihre Entscheidung erforderlich ist, und jeden Durchlauf mit einem Clip und dem zugehörigen Poster auf VibeTube beendet.
Ein Prompt kann keine Reihenfolge, keinen Stopp und keine Prüfung garantieren. Ein Diagramm kann das.
Lerninhalte
- Ein Agent und das Bildmodell als Tool. Ein
Agentist ein Modell, eine Anweisung und Tools. Das dritte Tool ruft das Bildmodell auf und der erste Durchlauf endet mit einem Poster. Dann der Satz, der den Rest des Labs bestimmt: Ein Prompt kann keine Reihenfolge, keinen Stopp oder keine Prüfung versprechen. - Die Strukturen, aus denen ein Graph besteht. Ein
Workflowist eine Kantenliste. Sie erstellen den Fan-out und den Join (parallele Leser, die sich bei einemJoinNodetreffen) und einen Router, der seinen eigenen Ausgang benennt, mit einem Agenten im Aufgabenmodus hinter dem abgelehnten Ausgang, der eine Richtung in einer Schleife von Toolaufrufen bereinigt und sich wieder der Hauptlinie anschließt. - Eine multimodale Aufnahmepipeline. Vier Arten von Inhalten, die das Publikum gesendet hat, ein Reader pro Art: Gemini transkribiert die Sprachnotizen, fügt den Fotos Untertitel hinzu, sieht sich die Clips an und hört sich die Soundtracks an. Jeder Reader schreibt dieselbe Zeilenform in einen RAG Engine-Korpus. Ein dritter Reader im Fan-out ruft die Medien nach Bedeutung ab und gibt sie mit den Passagen zurück.
- Zwei Arten von Sperrungen, ein Mechanismus. Ein Lauf wird für eine Person (
RequestInput: vier Karten, Sie antworten) und für ein langsames Tool (Veo hinterLongRunningFunctionTool: ein Beleg) gestoppt. Beide sind dasselbe: ein offener Aufruf im Sitzungsspeicher, der über seine ID aus dem Formular oder einem anderen Prozess fortgesetzt wird. Die beiden Zweige des Media Desk teilen sich das Poster nach Name und Status. - Gemerkte Informationen über mehrere Nächte hinweg: Zwei Lebenszyklus-Callbacks in der Memory Bank:
before_model_callbackruft die Vorlieben des Erstellers ab, bevor der Proposer liest, undafter_agent_callbackmerkt sich die Auswahl für heute Abend nach dem Scripter. - Der Release. Das Diagramm in einer App mit einem ADK-
Runner, Server-Sent Events auf der Seite, Cloud Run mit Sitzungsaffinität und einem mehrteiligen POST, der den Clip mit dem Poster als Thumbnail auf VibeTube hochlädt.
Umfang
Eine laufende Anwendung in Ihrem Browser: die VibeStudio Workbench, eine Begleitseite mit einem In-Page-Code-Editor, Prüfern, die die Dateien auf dem Laufwerk und die von Ihren Ausführungen geschriebenen Sitzungen lesen, und einer eingebetteten adk web. Die sieben Sitzungen entsprechen den sieben Seiten dieses Codelabs, mit denselben Nummern und denselben Buchstaben für die Teile, sodass die beiden synchron bleiben. Jede Seite wird mit einem Banner geöffnet, auf dem die Sitzung in einem Satz beschrieben wird und die drei Dinge, die Sie dort tun: bearbeiten, ausführen, prüfen. Lesen Sie einen Abschnitt hier und nehmen Sie dann die entsprechende Änderung in der Workbench vor. Jede Änderung endet mit einem Bestätigungsbereich, in dem Sie sehen, was tatsächlich passiert ist.
Drei Dinge werden ausgeführt: die Workbench (die Seite auf Port 4600, der Editor, die Prüfer), Ihr Backend (die Workflow in agent/ und eine Sandbox-Anwendung pro Sitzung in stage0_prompt/ bis stage6_video/, die von adk web bereitgestellt wird) und Google Cloud (Gemini, das Bildmodell, die RAG Engine, Veo, Memory Bank – alle über Vertex AI mit Ihren eigenen Anmeldedaten erreichbar).
Die sieben Sitzungen
Sitzung | Was Sie erstellen | Meine Änderungen |
1 · Die Geschichte | Noch nichts – die Nacht und das Diagramm, das daraus wird, | – |
2 · Bildgenerierung | Ein Agent zeichnet das erste Poster. |
|
3 · Workflows | Die Nacht als Diagramm: Fan-out und Join, Router und Schleife |
|
4 · Multimodale Aufnahme | Was das Publikum gesendet hat, in einen Korpus eingelesen; der dritte Leser |
|
5 · Videogenerierung | Zwei Arten von Sperren; der Name des Posters wird im Status angezeigt |
|
6 · Memory | Was sich der Kanal zwischen den Nächten merkt |
|
7 · Bereitstellen und veröffentlichen | VibeStudio in Cloud Run; die Nacht auf VibeTube | den Ereigniscode des Raums ein und veröffentlichen Sie ihn. |
Anleitung für dieses Codelab Es ist konzeptorientiert: In jeder Sitzung wird die Idee erklärt, dann der Code so gezeigt, wie er nach der Änderung aussieht, und dann durchgegangen. Sie werden hier nie aufgefordert, eine Datei zu bearbeiten. Die Bearbeitung erfolgt in der Workbench, die Ihnen jeweils eine Bearbeitung, einen Hinweis daneben und einen Bestätigungsbereich darunter anzeigt. Lies dir die Informationen zur Sitzung durch und folge dann den Anweisungen auf der Seite.
Wo sich diese befindet
Die Grafik, das Thema und der Raum auf VibeTube stammen aus Lins Haunted Edition von VibeStudio. Dies ist der multimodale Schnitt, der sich auf die von den Zuschauern gesendeten Inhalte und die beiden Arten von Suspension konzentriert, im einseitigen interaktiven Format der Agent Valley-Labs. Das Studio ist nur die Kulisse. Alle Mechanismen darunter sind die üblichen, die Sie für eine Supportpipeline, einen Ablauf zur Inhaltsprüfung oder alles andere verwenden würden, das für eine Person pausieren und auf einen langsamen Job warten muss.
2. 🧰 Einrichtung
- Was: ein Konto, das bezahlen kann, ein Projekt, das installierte Lab, das Modell, das nachweislich antwortet, und die Workbench, die im Hintergrund auf einem Port ausgeführt wird.
- Warum: Ein Prozess auf einem Port: Die Seite, der Editor, die Prüfer,
/api/*undadk webhaben denselben Ursprung, sodass in diesem Lab kein zweites Terminal oder ein zweiter Port erforderlich ist. - How — clone, two scripts. Im zweiten wird ein echter Modellaufruf durchgeführt, die Workbench gestartet und der RAG-Korpus und die Memory Bank erstellt, während Sie lesen, sodass sie in den Sitzungen 4 und 6 bereit sind.
Alles in diesem Lab wird in Cloud Shell ausgeführt. gcloud, Python, Node, ffmpeg und git sind dort bereits installiert. Gemini, das Bildmodell, Veo, die RAG Engine und die Memory Bank werden alle über Vertex AI mit Ihren eigenen Anmeldedaten aufgerufen. In diesem Lab gibt es keinen API-Schlüssel und Sie müssen nichts auf Ihrem Laptop installieren. Ein Laptop mit dem Google Cloud SDK, Python 3.12 und Node 20+ funktioniert genauso.
Eine Sache kann Ihnen dieses Lab jedoch nicht bieten: ein Google Cloud-Projekt mit aktivierter Abrechnung. In Schritt 1 erhalten Sie ein Konto, über das Sie die Kosten begleichen können. In Schritt 4 wird dieses Konto in ein Projekt umgewandelt. Führen Sie sie in dieser Reihenfolge aus. In jedem Schritt nach Schritt 4 wird das Projekt gelesen, das in Schritt 4 festgelegt wurde.
1 · Konto mit Abrechnung erstellen (~1 Min.)
Melden Sie sich mit einem Google-Konto an, für das die Abrechnung aktiviert ist. Jedes dieser Konten ist geeignet. Wenn Sie noch kein Rechnungskonto haben, starten Sie einen kostenlosen Testzeitraum. Dadurch erhalten Sie ein Rechnungskonto, das für Schritt 4 erforderlich ist. Wenn Sie an einem Lab mit Kursleiter teilnehmen, lösen Sie zuerst das Guthaben für diese Sitzung ein. Dadurch wird das Rechnungskonto für Sie erstellt.
2 · Cloud Shell öffnen (~2 Min.)
👉🖥 Klicken Sie oben in der Google Cloud Console auf Cloud Shell aktivieren. Das ist das Terminalsymbol in der Kopfzeile.

👉🖥 Klicken Sie auf Editor öffnen (das Symbol mit dem geöffneten Ordner und dem Stift). Dadurch wird der Cloud Shell-Editor mit einem Datei-Explorer auf der linken Seite geöffnet. Dort können Sie ganze Dateien lesen.

👉🖥 Wenn Sie heute zu irgendeinem Zeitpunkt zur Autorisierung aufgefordert werden, klicken Sie auf Autorisieren, um fortzufahren.
👉🖥 Öffnen Sie ein Terminal im Editor, also Tab 1. Dort werden alle 👉💻-Schritte ausgeführt. Tab 2 ist die Workbench selbst. Sie öffnen sie in Schritt 7. Wenn das Terminal nicht bereits am unteren Bildschirmrand angezeigt wird, öffnen Sie es mit Ansicht → Terminal:

👉💻 Prüfe, mit welchem Konto du angemeldet bist. Es sollte das Konto aus Schritt 1 sein:
gcloud auth list
3 · Lab starten (~1 Min.)
👉💻 Klonen Sie das Repository:
cd ~
git clone https://github.com/cuppibla/vibe-studio-multimodal
cd vibe-studio-multimodal
4 · Projekt erstellen (~2 Min.)
👉💻 Ein Skript findet Ihr Rechnungskonto, erstellt ein Projekt, verknüpft die Abrechnung und verweist Cloud Shell auf dieses Projekt:
cd ~/vibe-studio-multimodal
./setup_project.sh
Sie erhalten eine ✓ für das ausgewählte Rechnungskonto, eine ✓ für das erstellte Projekt und eine Ausgabe wie die folgende:
Project ready: vibe-studio-4271 Next: ./setup_codelab.sh
vibe-studio-4271 ist Ihre Projekt-ID. Ihre ID hat andere Ziffern. Dort passieren vier Dinge:
billing | Das neueste | |
project |
| |
auswählt | schreibt die ID in | |
waits | fragt das Projekt ab, bis es tatsächlich bereitgestellt werden darf, bis zu zwei Minuten lang | |
👉🖥 Das Projekt ist jetzt in der Console zu sehen. Die ID finden Sie auf der Karte Projektinformationen im Dashboard:

5 · Lab installieren und einrichten (~4 Min.)
👉💻
cd ~/vibe-studio-multimodal
./setup_codelab.sh
Ein Skript, das sicher noch einmal ausgeführt werden kann – bei einer zweiten Ausführung bleiben alle Antworten erhalten, die Sie beim ersten Mal gegeben haben. Zu Beginn werden zwei Fragen gestellt. Mit der Eingabetaste wird für beide die Standardeinstellung übernommen:
Ereigniscode | den VibeTube-Raum, in dem Ihr Video in Sitzung 7 veröffentlicht wird. Geben Sie den Raumcode ein, den Sie von Ihrer Lehrkraft erhalten haben, oder drücken Sie die Eingabetaste für | |
Anzeigename | den Namen, unter dem du im Raum aufgeführt wirst, oder Eingabe für einen Namen, der aus deinem Konto erstellt wurde. | |
Beide werden in .env gespeichert und können später in .env oder auf der Veröffentlichungsseite geändert werden. Den Rest erledigt es dann selbst, und zwar in dieser Reihenfolge:
uv env | installiert uv, falls es fehlt. Anschließend installieren | |
apis | Aktiviert sechs APIs für das in Schritt 4 ausgewählte Projekt und bestätigt jede einzelne, bevor fortgefahren wird: | |
konto | stellt sicher, dass das Standard-Compute-Dienstkonto des Projekts vorhanden ist und die Rollen hat, die für den Build und den Dienst von Sitzung 7 erforderlich sind | |
.env | schreibt | |
veo · room | bestätigt, dass | |
model | führt einen echten Aufruf an Gemini aus, sodass ein fehlerhaftes Projekt hier bei der Einrichtung und nicht innerhalb einer Sitzung fehlschlägt | |
starter | Die neun Dateien, die Sie bearbeiten, werden von | |
page | installiert und erstellt | |
cloud | startet im Hintergrund die Erstellung des RAG Engine-Korpus und der Memory Bank, sodass sie in den Sitzungen 4 und 6 bereit sind. | |
preflight |
| |
6 · Preflight-Prüfung lesen (~1 Min.)
Das Letzte, was in Schritt 5 ausgegeben wurde, ist der Preflight – jede Zeile ist eine echte Prüfung, keine Vermutung:
✓ python 3.12 ✓ auth path A: Vertex via ADC (STUDIO_VERTEX=1) ✓ Google Cloud ADC (project vibe-studio-4271) ✓ Memory Bank SDK ✓ stage0_prompt loads ✓ stage1_fanout loads (0 edges) ✓ stage3_router loads (... edges) ✓ stage4_memory loads (... edges) ✓ stage5_rag loads (... edges) ✓ stage6_video loads (... edges) ✓ node and npm (build the learning center's page) ✓ learning center page built (web/dist) ✓ aiplatform.googleapis.com enabled (Gemini, Veo, Memory Bank, RAG Engine) ... - Memory Bank: still being created in the background - RAG corpus: still being created in the background ✓ session 7 runtime account 123456789-compute@developer.gserviceaccount.com has its roles ✓ learning center running on port 4600 PREFLIGHT GREEN Open session 1 here: https://4600-cs-...cloudshell.dev/step/story
stage1_fanout loads (0 edges) hat recht: Das Diagramm ist leer und wird in Einheit 3 gefüllt. Die beiden --Zeilen sind ebenfalls korrekt. Es dauert ein oder zwei Minuten, bis ein Korpus angezeigt wird, und etwa zwanzig Sekunden, bis die Bank angezeigt wird. Beides benötigen Sie erst ab Sitzung 4.
👉💻 Sie können den Befehl jederzeit wieder ausführen, um zu sehen, wo Sie stehen. Er liest Ihre Dateien, Ihr Projekt und den laufenden Server, nicht eine Checkliste:
cd ~/vibe-studio-multimodal
uv run python scripts/preflight.py
7 · Workbench öffnen (~1 Min.)
Die Workbench wird bereits ausgeführt. In Schritt 5 wurde er im Hintergrund gestartet, ein uvicorn-Prozess auf Port 4600, der die Seite, jede /api/*-Route und adk web bereitstellt, die unter /workbench bereitgestellt wird. Sie müssen nichts starten und werden in diesem Lab auch nicht aufgefordert, etwas neu zu starten. Eine Änderung wird bei der nächsten Ausführung übernommen.
👉🖥 Klicken Sie auf den Link, der im Preflight ausgegeben wurde, oder in Cloud Shell auf Webvorschau → Port ändern → 4600. Nennen Sie diesen Tab 2. Es wird mit Sitzung 1 · Die Geschichte geöffnet.
Die drei Befehle, die Sie später möglicherweise benötigen, alle aus ~/vibe-studio-multimodal:
| beendet die Workbench | |
| startet ihn im Vordergrund; Strg + C beendet ihn und alle von ihm gestarteten Prozesse | |
|
| |
Repository-Layout
vibe-studio-multimodal/
├── agent/ # the workflow and the platform clients
│ ├── graph.py # the graph: three readers, the proposer, the gate, the router, the script, the desk
│ ├── desk.py # render_desk and render_submit, the LongRunningFunctionTool
│ ├── deliver.py # the delivery console: find the pending call, wait for Veo, answer by id
│ ├── schemas.py # Direction, Directions, CleanedDirection, Script, each with a poster idea
│ ├── cleanup_tools.py # find_policy_hits, suggest_replacement: the task agent's tools
│ ├── policy_words.txt # the policy, as data; policy_replacements.txt, the approved stand-ins
│ ├── backlog.txt # the creator's Halloween ideas; comments.md, the audience's comments
│ ├── audience/ # what else the audience sent: voice/*.mp3, photo/*.jpg, clip/*.mp4
│ ├── media/past/ # six past posters and captions.md, what Gemini sees in each
│ └── platform/ # config, audience (the readers), rag (RAG Engine), images, videogen (Veo), memory (Memory Bank)
├── stage0_prompt/ ... stage6_video/ # one sandbox app per session, runnable in adk web
├── server/ & web/ # the VibeStudio Workbench (FastAPI + React, port 4600)
├── vibestudio/ # the app deployed in session 7, with its own copy of agent/
├── checks/ & scripts/ # the hole registry, the verifiers, preflight, start/stop/restart
└── starter/ # the nine files you edit, as students receive them
3. 1 · Die Geschichte – die Nacht und die Grafik, die daraus entsteht
👉🖥 Tab 2 wird auf Session 1 · Die Geschichte geöffnet. Sechs Aufgaben, die sich um eine Person drehen: Trend recherchieren, Backlog durchforsten, Richtung wählen, Richtlinien prüfen, Skript schreiben, Clip rendern – und das jeden Abend. Gemini kann die Recherche, den Backlog und das Skript allein erstellen, Veo kann das Rendering allein übernehmen. Die Auswahl und Überprüfung liegen beim Menschen.
Unter dem Ring befindet sich das Diagramm, das im weiteren Verlauf des Labs erstellt wird. Es besteht aus 13 Knoten, die den Weg von einer Idee zu einem Clip und dem zugehörigen Poster darstellen. Bewegen Sie den Mauszeiger auf einen Knoten, um zu sehen, was er tut und welche Sitzung ihn erstellt. Hier muss nichts bearbeitet werden. Der Button Generate the first poster (Erstes Poster generieren) auf der Seite ist Sitzung 2.
Der Wortschatz, also die nächsten sechs Seiten ohne Unterbrechung:
Wort | was es hier bedeutet |
Agent | ein ADK- |
Knoten | ein Schritt des Graphen – eine Python-Funktion, ein Agent, ein Join, ein Router |
edge | ein Tupel von Knoten, die nacheinander ausgeführt werden. Der Graph ist eine Liste dieser Knoten. Session 3 |
session | Der Datensatz einer Ausführung in |
state | Ein Wörterbuch, das die Knoten nach Schlüsselnamen teilen; ein Knoten schreibt |
Memory | was über eine Sitzung hinaus erhalten bleibt – Memory Bank, über Callbacks. Session 6 |
4. 2 · Bildgenerierung – ein Agent und das Bildmodell als Tool

👉🖥 Öffnen Sie in der Workbench Session 2 · Bildgenerierung. Vor dem Graphen die einfachste Lösung, die funktionieren könnte: ein Agent, dessen Anweisung die gesamte Nacht als Prosa ist, drei Python-Funktionen als seine Hände und ein Durchlauf, der mit einem Bild endet.
Ein KI-Agent besteht aus einem Modell, einer Anweisung und Tools.
stage0_prompt/agent.py definiert root_agent mit drei Feldern eines ADK-Agenten. model ist das Gemini-Modell von agent/platform/config.py. instruction ist der Systemprompt: sechs Aufgaben in einem Atemzug, die letzte davon ein Bild. tools enthält drei Funktionen.
root_agent = Agent(
name="solo_channel", model=config.MODEL,
tools=[check_trends, read_backlog, make_poster],
instruction=(
"You run the creator's cozy-spooky short-video channel, alone, every "
"night until Halloween.\n"
"When the creator gives you an idea (or nothing), do ALL of this:\n"
"check what is trending. look at your backlog of ideas. propose a "
"direction and agree on it with the creator. refuse blacklisted "
"subjects (competitor, hateful, gore). then describe the video you "
"would make: a title (<=60 chars) and 3 shots, one visual sentence "
"each. then draw its poster: call make_poster once with one line that "
"names the single image that sells the video, and tell the creator "
"where the poster is.\n"
"Ask the creator to confirm the direction before describing the "
"video."))
Ein Modell ist eine geschlossene Welt: Es zieht Schlussfolgerungen anhand seiner Gewichtungen und der Tokens in seinem Kontext. Ein Tool ist eine Python-Funktion, die das Modell anfordern kann. Das ADK liest die Signatur und den Docstring jeder Funktion und deklariert sie für das Modell. Wenn das Modell eine Funktion benötigt, gibt es ein function_call-Ereignis aus. Die Laufzeit führt den Python-Code aus und schreibt ein function_response-Ereignis zurück in den Kontext. Das Modell fährt dann mit dem Ergebnis fort.
Drei Funktionen, und eine davon zeichnet
Zwei der drei haben die Studie gelesen. Der dritte Prompt fordert das Bildmodell auf, ein Poster zu erstellen:
def check_trends() -> dict:
"""Ten formats trending on the platform right now, with a heat score each."""
from agent.trends import sample_trends
return {"trends": sample_trends()}
def read_backlog() -> dict:
"""The creator's backlog: ideas they noted down to make someday."""
from agent.graph import backlog_notes
return {"backlog": backlog_notes()}
def make_poster(subject: str) -> dict:
"""Draw tonight's poster: one image of `subject` in the channel's cozy
low-poly style. Returns where the PNG landed (path, url) in a few seconds."""
...
make_poster ruft agent/platform/images.py auf, das Bildmodell, das als einfache Funktion verpackt ist, so wie videogen.py Veo verpackt: ein Präfix im Stil des Hauses (der gemütliche Low-Poly-Halloween-Look des Kanals, Kürbisorange und Kerzenlichtcreme gegen Mitternachtspflaume), das Motiv, ein vertikaler 9:16-Frame, ein PNG unter runs/media/posters, bereitgestellt bei /static/posters. Mit STUDIO_REAL_IMAGES=0 wird ein Platzhalter-Poster kopiert und das Ergebnis mit prebaked gekennzeichnet, damit das Lab überall ausgeführt werden kann.
MODEL = os.environ.get("STUDIO_IMAGE_MODEL", "gemini-2.5-flash-image")
ASPECT = "9:16"
def generate(subject: str, visual_style: str = "", name: str | None = None) -> dict:
"""One poster for `subject`. Returns where it landed: status, path, url,
prompt, and prebaked=True when the stand-in was used instead of the model."""
Derselbe Aufruf ist ein Knoten im Graphen in Sitzung 5.
Praktische Übung: Die eine Bearbeitung
In der Standardkonfiguration gibt make_poster ein Wörterbuch zurück, das besagt, dass das Poster übersprungen wurde. 👉🖥 Sorgen Sie im Workbench-Editor dafür, dass das Bildmodell anstelle der Zeile TODO aufgerufen wird:
from agent.platform import images
return images.generate(subject, name="solo")
Speichern. In den Zeilen zur Überprüfung wird die Datei gelesen: drei Tools auf dem Agent, der Aufruf des Bildmodells ist vorhanden. 👉🖥 Klicken Sie auf Check the workflow loads (Workflow-Auslastung prüfen), dann auf Open adk web (ADK-Web öffnen) und senden Sie Folgendes:
tonight's idea: a tiny ghost learning to haunt a sock drawer
Was Sie erwartet und warum
- Zwei Tool-Ereignisse vor der Antwort, dann eine Frage.
check_trendsundread_backlogwerden ausgeführt, weil die Anweisung lautete, zu prüfen und nachzusehen, und das Modell weder das eine noch das andere in seinen Gewichtungen hatte. In der Antwort wird eine Richtung vorgeschlagen und Sie werden um Bestätigung gebeten, da in der Anweisung zuerst nach einer Vereinbarung gefragt wurde. - Die zweite Nachricht enthält alles. Sende
skip the questions, describe the video and draw the poster. Das Modell beschreibt das Video, ruftmake_postereinmal mit einer selbst geschriebenen Betreffzeile auf und teilt Ihnen mit, wo die PNG-Datei gespeichert wurde. Im Bereich „Überprüfen“ wird das Poster neben den Zeilen angezeigt: eine Halloween-Szene im Stil des Kanals, die in etwa acht Sekunden gezeichnet wurde. - Warum dies kein Kanal ist Die Recherche wird in einen Text eingebunden, der nicht zitiert werden kann. Die Richtlinie wurde von dem Modell zertifiziert, das die Anweisung geschrieben hat, und das Poster wurde gezeichnet, bevor es geprüft wurde. Die Vereinbarung wurde in einer Nachricht ignoriert. Der Prompt-Text ist kein Gate. Im weiteren Verlauf des Labs werden diese sechs Jobs in ein gezeichnetes Diagramm zerlegt.
Die Seite endet mit dem Abschnitt Woraus ein ADK-Agent besteht, einer Referenz, auf die Sie zurückgreifen können: das Modell, der Kontext, mit dem es Schlussfolgerungen zieht, die Tools und Collaborators, über die es agiert, die Callbacks, die jeden Aufruf umschließen, sowie die Sitzung und der Speicher, die außerhalb des Modells existieren.
5. 3 · Workflows – Fan-out und Join, Router und Schleife

👉🖥 Öffnen Sie in der Workbench Session 3 · Workflows, Teil 3a und 3b. Jeder Teil wird in einer Sandbox-Anwendung erstellt, sodass Sie ihn einzeln in adk web ausführen können: den Fan-out in stage1_fanout, den Router und seine Schleife in stage3_router.
Fan-Out und Join (3a)
Ein ADK-Workflow ist eine Kantenliste. Jeder Eintrag ist eine Kette, ein Tupel von Knoten, die nacheinander ausgeführt werden. Zwei Ketten, die START verlassen, werden parallel ausgeführt. Eine Kette, die in einem JoinNode endet, meldet sich bei diesem und der Join wartet auf jede Kette, die dort endet, bevor er fortfährt und ein Wörterbuch übergibt, das nach Knotennamen verschlüsselt ist. Ein als Wörterbuch geschriebenes Ziel ordnet die Routennamen eines Routers Knoten zu. Auf Seite 3a des Arbeitsbuchs werden die Formen gezeichnet und es gibt eine Übung zum Nachzeichnen der Kanten.
Knotenart | Geschrieben als | Funktion |
Funktionsknoten |
| Einfaches Python. Darf auch den freigegebenen Status mit |
Join-Knoten |
| Wartet, bis alle eingehenden Ketten gemeldet wurden, und übergibt dann alle Ausgaben als ein Wörterbuch. |
KI-Agent-Knoten |
| Ein Modellaufruf im |
Router |
| Ein Funktionsknoten, dessen Rückgabe die ausgehende Kante benennt. |
Knoten für menschliche Eingabe |
| Setzt den Graphen aus, bis eine Person ein Formular beantwortet. Session 5. |
Die erste Grafik zeigt die Forschung aus Sitzung 2 als Knoten. scan_trends und read_backlog in agent/graph.py sind die beiden Leser. read_backlog schreibt auch die Idee für heute Abend in den freigegebenen Status. stage1_fanout/agent.py definiert den Join und erstellt die Workflow aus einer Liste namens EDGES, die leer ist (die 0 edges des Preflights). 👉🖥 Füllen Sie sie mit den beiden Ketten:
EDGES = [(START, scan_trends, join_research),
(START, read_backlog, join_research)]
👉🖥 Führen Sie den Befehl in adk web aus. Zwei Knoten leuchten zusammen auf und verbinden sich dann. Öffnen Sie das Ereignis von join_research und lesen Sie ein Wörterbuch mit einem scan_trends- und einem read_backlog-Schlüssel. Es wurde kein Modellaufruf durchgeführt. Das Wörterbuch des Joins wird nicht neu formatiert: Der Knoten danach, in 3b, ist ein Agent, und ein Agentenknoten empfängt sein node_input als Nachricht. Ein Wörterbuch kommt also als JSON an, das das Modell direkt liest.
Router und Schleife (3b)
Ein Agent innerhalb eines Workflow wird im single_turn-Modus ausgeführt: Er empfängt die Ausgabe des vorherigen Knotens als Nachricht, führt einen Modellaufruf durch und antwortet einmal. Mit output_schema wird die Antwort anhand eines Pydantic-Modells validiert, bevor sie an den nächsten Knoten weitergegeben wird. Der Graph übergibt also typisierte Objekte, keine Prosa. propose_directions liest das Forschungsverzeichnis des Joins und schlägt vier Kandidaten vor, die alle eine Halloween-Szene darstellen. Kandidat 4 ist der Köder: Der Titel enthält ein Wort, das der Kanal ablehnt, sodass der Router etwas zu fangen hat. Jeder Kandidat hat eine poster_idea, eine Zeile, in der das einzelne Bild genannt wird, das das Video verkauft. Der Scripter behält sie und der Media Desk zeichnet sie in Sitzung 5.
Ein Router ist ein Funktionsknoten, dessen Rückgabewert die Kante benennt, die der Lauf nimmt. Kein Modellaufruf: policy_check liest agent/policy_words.txt zum Zeitpunkt der Entscheidung, gleicht ganze Wörter mit dem Titel und dem Winkel der Richtung ab und leitet sie weiter, bevor ein Skript geschrieben oder Geld ausgegeben wird. Die Richtlinie besteht aus Daten: Bearbeiten Sie die Datei und sie wird beim nächsten Durchlauf gelesen.
def policy_check(node_input):
"""The policy gate: a router. Refused words in the direction send it to
quarantine; a clean one goes to the scripter. Code, not a model."""
chosen = _as_dict(node_input)
bad = policy_hits(f"{chosen.get('title', '')} {chosen.get('angle', '')}")
return Event(output=node_input, route="BLOCK" if bad else "OK")
stage3_router/agent.py ist der Fan-out plus propose_directions und choose_direction, ein Platzhalter für die menschliche Tür, die Sie in Sitzung 5 erstellen: Es wird der Kandidat ausgewählt, dessen Nummer die heutige Idee (..., pick 4) beendet, andernfalls der erste, und die Auswahl wird in den Status geschrieben. scripter ist ein single_turn-Agent, der eine genehmigte Richtung in ein Script verwandelt: einen Titel, eine Beschreibung, Tags, einen Eröffnungssatz, drei Aufnahmen und eine Posteridee, wobei jede Aufnahme ein Halloween-Bild im Look des Kanals ist.
Die Schleife hinter dem abgelehnten Ausgang. quarantine wird als Agent im Aufgabenmodus bereitgestellt. Ein Agent hat einen mode: chat ist die Konversationsschleife, die ein Root-Agent mit einer Person ausführt, single_turn ist ein Aufruf mit einer strukturierten Antwort und task ist eine Schleife von Toolaufrufen, die der Agent selbst ausführt, bis er das integrierte finish_task aufruft, dessen Argumente das ADK von output_schema ableitet. Quarantine ruft find_policy_hits für die abgelehnte Richtung auf, fragt suggest_replacement nach dem genehmigten Ersatz des Kanals für jedes Wort (agent/policy_replacements.txt, wieder Daten), schreibt um, prüft noch einmal und schließt mit einem typisierten CleanedDirection ab. Lesen Sie den Text auf der Seite, ohne ihn einzutippen.
quarantine = Agent(
name="quarantine",
model=config.MODEL,
mode="task",
instruction=QUARANTINE_INSTRUCTION,
tools=[find_policy_hits, suggest_replacement],
output_schema=CleanedDirection)
In der Standardkonfiguration verläuft die Kette von der Auswahl direkt zum Scripter. 👉🖥 Fügen Sie den Router mit seinen zwei Ausgängen und dem Weg zurück aus der Quarantäne dazwischen ein. Drei Zeilen ersetzen eine:
(join_research, propose_directions, choose_direction, policy_check),
(policy_check, {"OK": scripter, "BLOCK": quarantine}),
(quarantine, scripter),
👉🖥 Führen Sie beide Routen aus. Idee unverändert senden: choose_direction verwendet Kandidat 1, policy_check gibt OK aus und der Scripter schreibt ein strukturiertes Script. Klicken Sie auf Neue Sitzung und senden Sie die Idee noch einmal mit , pick 4 am Ende: Kandidat 4 ist der Köder, policy_check gibt BLOCK aus, die Quarantäne ruft ihre Tools auf, schreibt die abgelehnten Wörter um, ruft finish_task auf und die bereinigte Anweisung wird an den Scripter zurückgegeben. Die Zeilen für die Überprüfung werden beide aus dem Sitzungsspeicher gelesen.
Jeder Satz des Prompts aus Sitzung 2 hat jetzt einen Knoten: Die Recherche besteht aus zwei Lesern und einem Join, die Ablehnung aus einem Router und einem Aufgaben-Agenten, das Skript aus einem Agenten-Knoten. Im gesamten Diagramm sind die Platzhalter zwei Funktionsknoten, direction_gate und persist_direction, die bis zur fünften Sitzung durchlaufen, in der die Tür gestoppt wird. Die Vereinbarung mit dem Creator ist noch ein Platzhalter, ebenso wie die Recherche, bei der die Zielgruppe noch nicht berücksichtigt wurde.
6. 4 · Multimodale Aufnahme – was die Zielgruppe gesendet hat, wird in einen Korpus eingelesen

👉🖥 Öffnen Sie in der Workbench Session 4 · Multimodal ingestion, Teile 4a und 4b. Das Publikum hat nicht nur geschrieben. Ein Reader pro Art wandelt alles, was gesendet wurde, in Text mit derselben Form um, ein RAG Engine-Korpus enthält alles und ein dritter Reader im Fan-out sucht nach Bedeutung und gibt die Medien zurück.
Was die Zuschauer gesendet haben (4a)
agent/comments.md enthält zwölf Kommentare, die unter den sechs Halloween-Videos des Kanals verfasst wurden. agent/audience/ enthält den Rest: acht Sprachnotizen (voice/*.mp3), vierzehn Fotos der eigenen Zimmer der Zuschauer (photo/*.jpg) und sechs Clips (clip/*.mp4), ein langsamer Schwenk über ein Foto mit der Stimme des Zuschauers darüber. Auf der Seite des Pixel 4a wird alles geöffnet: Jede Sprachnotiz wird abgespielt, jedes Foto wird angezeigt und jeder Clip wird wiedergegeben.

Die RAG Engine ruft Text ab. Eine Sprachnotiz, ein Foto oder ein Clip kann nur so in den Korpus aufgenommen werden, wie es die früheren Poster in rag.caption_posters getan haben: Ein Modell wandelt die Datei in eine Textzeile um, die den Dateinamen enthält, die Zeile wird zu einer Passage und wenn die Passage zurückgegeben wird, wird auch die Datei zurückgegeben. agent/platform/audience.py hat einen Leser pro Art. Jede sendet eine Datei und einen Prompt an Gemini und schreibt eine Zeile pro Datei:
VOICE_PROMPT = ("Transcribe this voice note word for word inside double quotes. Then add: mood: and one word. "
"Plain text on one line, nothing else.")
PHOTO_PROMPT = ("Two sentences: what is in the picture, the palette, the mood. Then: tags: and three short tags. "
"Plain text on one line, no headings, no markdown.")
CLIP_PROMPT = ("Describe the shots in two sentences, then quote what you HEAR on the soundtrack as: heard: \"...\". "
"Plain text on one line, no headings, no markdown.")
def _ask(path: pathlib.Path, mime: str, prompt: str) -> str:
"""One media file in, one line of text out."""
from google.genai import types as gt
client = images._client()
resp = client.models.generate_content(
model=config.MODEL,
contents=[gt.Part.from_bytes(data=path.read_bytes(), mime_type=mime), prompt],
config=gt.GenerateContentConfig(automatic_function_calling=gt.AutomaticFunctionCallingConfig(disable=True)))
return " ".join((resp.text or "").split())
def transcribe_voice() -> pathlib.Path:
"""voice note -> text: Gemini listens."""
files = sorted((AUDIENCE / "voice").glob("*.mp3"))
lines = []
for p in files:
said = _ask(p, "audio/mpeg", VOICE_PROMPT)
lines.append(f"- voice: {p.name} — {said}")
return _write(AUDIENCE / "voice" / "transcripts.md", "voice", "What Gemini heard in the audience's voice notes", lines)
caption_photos macht dasselbe mit image/jpeg und PHOTO_PROMPT, watch_clips mit video/mp4 und CLIP_PROMPT. Gemini sieht sich das Bild an und hört den Soundtrack in einem einzigen Aufruf. Jeder Reader schreibt dieselbe Zeilenform, sodass beim Chunking, Einbetten und Abrufen nie bekannt ist, aus welcher Art von Passage ein Abschnitt stammt:
- voice: note-03.mp3 — "I want a whole series of the cat judging costumes ..." mood: delighted
- photo: photo-01-ghost-drawer.jpg — A cream wooden drawer, striped socks, a paper-cup ghost ... tags: drawer, socks, cozy
- clip: clip-05.mp4 — A hallway floor, a snail in a sheet and a round vacuum ... heard: "The snail won."
- poster: sock-ghost.png — A sheet ghost one inch above a striped sock ...

Der Korpus. Die Vertex AI-RAG-Engine ist ein Abruf über Dokumente: Ein Korpus wird in Abschnitte von etwa 120 Tokens aufgeteilt, jeder Abschnitt wird mit text-embedding-005 eingebettet und eine Abfrage wird auf dieselbe Weise eingebettet und mit den Abschnitten beantwortet, die der Bedeutung nach am nächsten liegen, nicht nach gemeinsamen Wörtern. „cozy little hauntings at home“ findet den Kommentar, in dem nach kleiner Magie in kleinen Räumen gefragt wird, die Sprachnotiz über die Fledermaus, die die Glühbirne umarmt, und den Clip, dessen Soundtrack besagt, dass der Geist ein Pappbecher ist.
corpus = rag.create_corpus(
display_name="vibestudio-feedback",
backend_config=rag.RagVectorDbConfig(
rag_embedding_model_config=rag.RagEmbeddingModelConfig(
vertex_prediction_endpoint=rag.VertexPredictionEndpoint(
publisher_model="publishers/google/models/text-embedding-005"))))
rag.upload_file(
corpus_name=corpus.name, path="agent/comments.md", display_name="comments.md",
transformation_config=rag.TransformationConfig(
chunking_config=rag.ChunkingConfig(chunk_size=120, chunk_overlap=20)))
Die Einrichtung hat bereits mit der Erstellung des Korpus begonnen. In der Verbindungszeile auf Seite 4a wird angezeigt, ob er aktiv ist. Wenn die Einrichtung nicht möglich war, wird er erstellt. Über die Schaltflächen der Seite oder das Terminal: python -m agent.platform.rag erstellt den Korpus und zeichnet ihn in runs/ragcorpus.json auf; python -m agent.platform.rag load --audience führt jeden Reader in audience.READERS aus und lädt dann hoch, was sie neben den Kommentaren und den Bildunterschriften der Poster geschrieben haben, jede Datei unter ihrem eigenen Namen, und wartet auf die Antwort des Index; python -m agent.platform.rag query "cozy little hauntings at home" zeigt den Abruf von Passagen, die kein Wort mit der Abfrage gemeinsam haben.
Die Bearbeitung: Im Lieferzustand enthält READERS nur read_comments. Die drei Medienleser befinden sich hinter einem #. 👉🖥 Führen Sie die Aufnahme einmal so aus, wie sie bereitgestellt wird: Die Ausgabe lautet not read (no reader wired in audience.READERS): voice, photo, clip und nur die Kommentare und Untertitel werden aufgenommen. Löschen Sie dann die # :
transcribe_voice, caption_photos, watch_clips,
👉🖥 Speichern Sie die Datei und führen Sie die Aufnahme noch einmal aus. Die Lesegeräte benötigen etwa drei Minuten für die 40 Elemente. In der Konsole werden heard, saw und watched Zeile für Zeile angezeigt und jede Karte im Raster wird mit ihrer Zeile gefüllt. In den Zeilen zur Überprüfung werden die Datei, die Ausgabe der Lesegeräte und der Korpus gelesen: vier Lesegeräte verbunden, acht Sprachnotizen gehört, vierzehn Fotos gesehen, sechs Clips angesehen, fünf Dateien im Korpus.
Der dritte Leser (4b)
Das Feedback des Publikums ist eine Recherche, die bei jedem Durchlauf geteilt wird. Es wird also dort in das Diagramm eingefügt, wo auch die Recherche eingefügt wird: als dritter Leser in den Join. read_feedback in agent/graph.py nimmt die Idee von heute Abend, fragt den Korpus nach den nächstgelegenen Passagen und meldet sich bei join_research, das auf drei wartet. Die Kante (START, read_feedback, join_research) wird in stage5_rag/agent.py versendet, die Form, die in Sitzung 3 gelehrt wurde. Ihre Bearbeitung befindet sich innerhalb des Knotens. Abruf, kein Modellaufruf und deterministisch: Dieselbe Idee findet dieselben Passagen, während der Proposer variiert.
def read_feedback(node_input):
"""The third reader: what the audience sent - comments, and what Gemini
heard and saw in their voice notes, photos and clips - the passages
nearest to tonight's idea, and the media those passages came with.
Retrieval, not a model call."""
from .platform import rag
idea = idea_text(node_input)
query = idea or "what viewers liked and what they complained about"
try:
hits = rag.retrieve(query)
except Exception as e:
print(f" [rag] feedback unavailable ({str(e)[:80]})")
return Event(output={"query": query, "feedback": [], "media": [],
"note": "no corpus connected - run: python -m agent.platform.rag"})
return Event(output={"query": query, "feedback": [h["text"] for h in hits]}) # TODO: RAG_MEDIA
Jede Zeile im Korpus, die aus einer Sprachnotiz, einem Foto, einem Clip oder einem Poster stammt, enthält den Namen der Datei. rag.media_in(hits) liest diese Namen aus den Passagen und wandelt sie in URLs um, die auf der Seite angezeigt und abgespielt werden können. 👉🖥 Geben Sie die Medien zusammen mit den Textpassagen zurück, damit die von den Zuschauern gesendeten Inhalte mit ihren Aussagen verknüpft sind:
return Event(output={"query": query, "feedback": [h["text"] for h in hits],
"media": rag.media_in(hits)})
👉🖥 Lassen Sie stage5_rag eine Idee ausarbeiten. Öffnen Sie read_feedback: Die Abfrage ist Ihre Idee, die Ausgabe enthält die fünf am nächsten gelegenen Passagen und eine media-Liste mit einer URL pro Datei. join_research öffnen: drei Schlüssel. Öffnen Sie propose_directions: Die Kandidaten 1 bis 3 zitieren feedback in ihren Belegen. Im Bereich „Überprüfen“ wird angezeigt, was sich der Leser unter den Zeilen angesehen hat: einen Clip, drei Sprachnotizen und ein Poster für den Geist im Kühlschranklicht.

7. 5 · Videogenerierung – zwei Arten von Aufhängung, ein Mechanismus

👉🖥 Öffnen Sie in der Workbench Sitzung 5 – Videogenerierung, Teile 5a und 5b, erstellt in stage6_video, den gesamten Graphen. Der Clip dauert Minuten und das Poster Sekunden. Der Lauf wird auf dem Weg zweimal angehalten: einmal für Sie und einmal für Veo. Die beiden Haltestellen sind ein Mechanismus und die beiden Zweige des Schreibtischs teilen das, was sie gemacht haben, nach Namen.
Zwei Arten von Sperrungen (5a)
Nichts wartet. Ein Lauf, der beendet werden muss, schreibt einen offenen Aufruf in seine Sitzung und wird beendet. Wer auch immer diesen Aufruf beantwortet, eine Person oder ein Prozess, setzt ihn fort. Die Seite wird mit einer Tabelle geöffnet:
für eine Person · | für ein langsames Tool · | |
wer den Lauf stoppt |
|
|
worauf es wartet | eine Person: eine von vier Karten | ein langsamer Job: Veo-Vorgang, Minuten |
was in der Sitzung enthalten ist | einen offenen | einen offenen |
wie sie fortgesetzt wird | ein | ein |
wer sie sendet |
|
|
wo es inzwischen lebt |
| dieselbe Zeile, dieselbe ID – nichts wird ausgeführt |

Für eine Person In einem einzelnen Prompt ist „den Creator um Bestätigung bitten“ ein Hinweis, der in Sitzung 2 in einer Nachricht ignoriert wurde. In einem Workflow ist die Pause die der Engine. Ein Knoten, der RequestInput liefert, unterbricht die Ausführung: ADK schreibt einen offenen Aufruf namens adk_request_input mit Ihren message, response_schema und payload in die Sitzung, weist ihm eine interrupt_id zu und kehrt zum Aufrufer zurück. Keine Thread-Wartezeiten. Der einzige Weg, den Workflow fortzusetzen, ist ein function_response mit dieser ID, der anhand Ihres Schemas validiert wird. Eine Chatnachricht an den Workflow ist ein neuer Turn, keine Antwort. Das Schema ist auch das Formular: adk web rendert es und die VibeStudio-App rendert dasselbe Schema als vier Karten. direction_gate schreibt die Kandidaten bereits in den Status. 👉🖥 So beenden Sie die Wiedergabe:
yield RequestInput(
message="Pick tonight's direction: 1, 2, 3 or 4.",
response_schema={
"type": "object",
"properties": {
"pick": {"type": "string", "enum": ["1", "2", "3", "4"]}}},
payload={"candidates": cands})
Ihre Antwort kommt bei persist_direction als node_input an, ein Wörterbuch mit einem Schlüssel, pick. Die vier Kandidaten kommen auf andere Weise an: Das Gate hat sie unter candidates in den freigegebenen Zustand geschrieben und das ADK bindet einen Zustandsschlüssel an einen Parameter mit demselben Namen. Der Knoten löst die Auswahl in einen Kandidaten auf und schreibt direction, angle, hook und user:prefs mit Event(state=...) zurück. Schlüssel mit dem Präfix user: bleiben für diese Nutzerin bzw. diesen Nutzer über alle Sitzungen hinweg erhalten.
Für ein langsames Tool. Das Rendern mit Veo dauert Minuten und ein Diagramm sollte nicht so lange warten. render_submit in agent/desk.py startet den Job und gibt sofort einen Beleg mit dem Status pending zurück:
def render_submit(prompt: str) -> dict:
"""Submit one Veo render of `prompt`. Returns at once with a pending
receipt; the clip is delivered later, to this call, by id."""
receipt = videogen.start(f"{prompt} {videogen.NO_TEXT}")
return {"status": "pending", "operation": receipt["operation"], "prompt": receipt["prompt"]}
In LongRunningFunctionTool verpackt, bedeutet dieser Beleg etwas für das ADK: Der Zug des Agenten endet, der Workflow wird bei render_desk angehalten und der offene Aufruf mit seiner ID und dem Beleg ist eine Zeile in runs/sessions.db. 👉🖥 In stage6_video/agent.py das Tool verpacken:
tools=[LongRunningFunctionTool(render_submit)])
Fortsetzen nach ID, von einem anderen Prozess. agent/deliver.py findet den ausstehenden Aufruf im Sitzungsspeicher, fragt Veo ab, bis der Clip vorhanden ist, schreibt das Ergebnis in runs/state.json und sendet der Sitzung eine function_response mit der ID dieses Aufrufs. 👉🖥 Reaktion erstellen:
part = Part(function_response=FunctionResponse(
id=row["call_id"], name=row["name"], response=response))
👉🖥 Führe stage6_video aus, bis es zweimal stoppt. Idee senden: Der Lauf wird bei einem Formular angehalten, das aus Ihrem Schema erstellt wurde, und die vier Kandidaten in der Nutzlast. Geben Sie 2 in pick ein und senden Sie die Eingabe ab. Das Diagramm wird bei persist_direction fortgesetzt. Probieren Sie den Kill-Test hier aus: Unterbrechen Sie die Ausführung beim Formular, beenden Sie den Server (scripts/stop.sh auf Tab 1), starten Sie ihn neu (scripts/restart.sh) und öffnen Sie die Sitzung wieder. Die Form ist immer noch vorhanden, da eine Pause eine Reihe und kein Faden ist. Nach script_ready ruft render_desk render_submit auf, erhält den Beleg, antwortet WAITING und die Ausführung endet. Die Sitzung behält die Aufruf-ID bei. Führen Sie die Auslieferung über die Konsole der Seite oder Tab 1 aus:
cd ~/vibe-studio-multimodal
uv run python -m agent.deliver status # the pending renders, by session, call id and operation
uv run python -m agent.deliver # wait for Veo, answer the newest pending call by id
👉🖥 Klicken Sie auf Refresh adk web: function_response folgt dem ausstehenden Aufruf, join_media wird freigegeben und store_release wird ausgeführt. Acht Prüfzeilen lesen die beiden Stopps aus der Sitzung: den Aufruf des Gates und Ihre Antwort, den Empfang des Renderers und seine Übermittlung.
Bundesland und Schreibtisch (5b)
Der Media Desk hat wieder die Struktur von 3a, ein Fan-out und ein Join, mit einem langsamen Zweig. script_ready sendet das Script auf zwei Arten. render_desk sendet den Clip und die Ausführung wird angehalten. make_poster ist das Tool aus Sitzung 2 als Knoten: Das poster_idea des Skripts, der Stil des Kanals und das, was Memory Bank über den Geschmack des Creators bei Postern weiß (aus dem Status als memory_facts, Sitzung 6 gebunden), werden an das Bildmodell gesendet und innerhalb von Sekunden wird ein PNG im Format 9:16 zurückgegeben. join_media wartet auf den langsamen und store_release schreibt das Ergebnis in den Status.
Nach Namen freigeben. store_release erhält das Poster nie. Es wird ein Parameter namens poster_url deklariert, den das ADK aus dem freigegebenen Status bindet, da ein Knoten im anderen Zweig diesen Schlüssel geschrieben hat. Es wird keine Nutzlast zwischen den Zweigen übertragen:
def store_release(node_input, poster_url: str = "", poster_path: str = ""):
"""`poster_url` and `poster_path` are not passed by anyone: ADK binds them
from shared STATE because the parameter names match the keys make_poster
wrote (the STATE_SHARE edit). The two branches of the desk never hand each
other payloads; they share what they made by name."""
render = state.load().get("render") or {} # the delivered clip, written by deliver
url, status = render.get("url") or "", render.get("status", "")
...
if not poster_url:
said += " · poster: not in state (did make_poster write poster_url to state? the STATE_SHARE edit)"
yield Event(output={"status": status, "url": url, "poster_url": poster_url},
state={"render_url": url, "render_status": status, "poster_url": poster_url},
message=f"video: {said} · poster: {poster_url or 'none'}")
In der Standardkonfiguration gibt make_poster das Poster als Ausgabe und eine Nachricht aus, und store_release sagt poster: not in state. 👉🖥 Fügen Sie den Schreibvorgang für den Zustand zur endgültigen Ausgabe hinzu:
state={"poster_url": rec.get("url", ""), "poster_path": rec.get("path", "")},
message=f"poster: {said}")
👉🖥 Führen Sie den Code noch einmal aus und liefern Sie das Ergebnis. Öffne das Ereignis von make_poster: die URL des Posters in der Ausgabe und ein Statusdelta mit poster_url. Öffne store_release: In der Nachricht wird der Name des Posters genannt. Im Block Sitzung dieser Ausführung der Seite wird derselbe Tab „Status“ der Zeile adk web angezeigt: die Sitzungs-ID, die für eine Fortsetzung erforderlich ist, der offene Aufruf und die Schlüssel, die die Knoten geschrieben haben, darunter poster_url. Im Bereich „Überprüfen“ werden der Clip und das Poster nebeneinander angezeigt.

8. 6 · Memory – was sich der Kanal zwischen den Nächten merkt

👉🖥 Öffnen Sie in der Workbench Session 6 · Memory, Teile 6a und 6b, erstellt in stage4_memory. In Sitzung 5 ging es um den Status eines Laufs. In dieser Sitzung geht es darum, was sich der Kanal zwischen den Ausführungen merkt: was der Graph von außen über zwei Callbacks hört.
Memory Bank (6a)
Vertex AI Memory Bank ist ein verwalteter Langzeitspeicher für Informationen über eine Person. Fakten werden unter einem Bereich, dem App-Namen und der Nutzer-ID sowie unter von Ihnen definierten Themen gespeichert. Wenn Sie ihm einen Austausch mit memories.generate übergeben, extrahiert es, was zu einem Thema passt, bettet es ein und konsolidiert es mit dem, was es bereits hat: Ein neuer Fakt wird erstellt, ein geänderter wird aktualisiert, eine Wiederholung wird zusammengeführt. memories.retrieve gibt die Fakten für einen Bereich zurück. Beide Anrufe werden in agent/platform/memory.py angezeigt.
SCOPE = {"app_name": config.APP, "user_id": config.USER}
TOPICS = {
"CREATOR_TASTE": "Which video directions this creator picks and passes on, "
"and how that preference changes over time.",
"CHANNEL_RULES": "Standing instructions the creator states for every video "
"(style, subjects to avoid, format rules).",
"VISUAL_STYLE": "What this creator approves and rejects in a poster: palettes, "
"framing, props and mood of the posters they shipped.",
}
Das dritte Thema ist der Geschmack des Kanals bei Bildern. In Sitzung 5 wurde ein Poster in einem Satz beschrieben und gespeichert. Das nächste Poster wurde dann mit diesem Satz im Prompt gezeichnet.
Die Einrichtung hat bereits mit dem Erstellen der Bank begonnen. In der Zeile „Verbinden“ auf Seite 6a wird angezeigt, ob sie aktiv ist. Über die Schaltflächen der Seite oder das Terminal: python -m agent.platform.bank erstellt die Bank und ihre Themen und speichert den Ressourcennamen im Cache in runs/memorybank.json; python -m agent.platform.bank load füllt vier vergangene Sitzungen (zwei Tier-Epochen mit einer Stilregel, eine Gadget-Epoche, eine aktuelle Fantasy-Epoche); python -m agent.platform.bank list zeigt, was aus den Transkripten geworden ist: konsolidierte Tatsachenberichte, keine Transkripte.
Callbacks (6b)
Memory ist kein Knoten. Ein Callback ist eine Funktion, die Sie an einen Agent übergeben. Das ADK ruft sie zu einem bestimmten Zeitpunkt mit dem Live-Kontext auf. Wenn None zurückgegeben wird, wird der normale Vorgang ausgeführt. Drei Paare: um den Turn des Agenten (before_agent_callback, after_agent_callback, mit einem CallbackContext), um jeden Modellaufruf (before_model_callback mit dem LlmRequest, after_model_callback mit dem LlmResponse), um jeden Toolaufruf. Guardrails, Telemetrie, Caches und Kontext-Injection werden hier ohne Knoten im Graphen eingefügt.
In stage4_memory/agent.py liest propose_directions die Bank vor dem Modellaufruf: recall_taste ruft die Fakten ab, hängt sie als MEMORY-Block an die Anfrage an (älteste zuerst) und weist das Modell an, die Kandidaten 1 bis 3 an den aktuellen Geschmack anzupassen und die Regeln als Einschränkungen zu behandeln. Außerdem werden die Fakten als memory_facts in den Status geschrieben, wo make_poster sie findet. 👉🖥 Kabelgebunden:
output_schema=Directions,
before_model_callback=recall_taste)
Der Scripter schreibt, was der Creator nach seinem Zug ausgewählt hat: remember_pick liest direction aus dem Status, schreibt einen Satz über die Auswahl und übergibt ihn an memories.generate. 👉🖥 Kabelgebunden:
output_schema=Script,
after_agent_callback=remember_pick)
👉🖥 Führe den Befehl mit einer leeren Nachricht aus und wähle eine Karte aus. Öffnen Sie das Ereignis propose_directions: Die Anfrage enthält die drei Epochen und die Kandidaten 1 bis 3 tendieren zum aktuellen Geschmack, während die Trends etwas anderes vermuten lassen. Klicken Sie nach dem Scripter auf Show the bank (Bank anzeigen): Eine gemerkte Information wurde geändert oder für die Auswahl heute Abend hinzugefügt. Führe eine zweite Nacht aus: Sie erinnert sich an die erste.
9. 7 · Bereitstellen und veröffentlichen – das Diagramm in einer App, die Nacht auf VibeTube

👉🖥 Öffnen Sie in der Workbench Session 7 · Deploy and publish, Teile 7a und 7b.
VibeStudio bereitstellen (7a)
In den Sandboxes wurde das Diagramm von adk web gesteuert. VibeStudio nutzt dafür das Runner von ADK in einem Arbeitsthread mit einer eigenen Ereignisschleife und fasst jedes Ereignis in einem Ausführungsstatus zusammen, den die Seite über Server-Sent Events liest: die Leser, Ihre Auswahl von vier Karten, das Poster, wenn es landet, und das Rendering, wenn es geliefert wird. Die Antwort des Gate und die Bereitstellung des Renderings erfolgen im selben Aufruf, einem function_response-Teil, und die App fragt Veo selbst ab, anstatt auf die Console zu warten.
self._svc = DatabaseSessionService(db_url=config.DB_URL)
self._runner = Runner(app_name=config.APP, agent=wf, session_service=self._svc)
async for ev in self._runner.run_async(user_id=config.USER, session_id=run_id, new_message=message):
self._absorb(ev) # fold the ADK event into the run state, publish one app event
part = Part(function_response=FunctionResponse(id=call_id, name=name, response=response))
vibestudio/server/agent/ ist eine Byte-für-Byte-Kopie der agent/ des Labs, die von checks/verify_app.py geprüft wird. Die vollständige Edge-Liste in agent/graph.py enthält alle Strukturen dieses Codelabs an einem Ort, dreizehn Knoten. Sie wird ausgefüllt geliefert, damit Sie sie lesen und nicht eingeben müssen:
(START, scan_trends, join_research),
(START, read_backlog, join_research),
(START, read_feedback, join_research),
(join_research, propose_directions, direction_gate,
persist_direction, policy_check),
(policy_check, {"OK": scripter, "BLOCK": quarantine}),
(quarantine, scripter),
(scripter, script_ready),
(script_ready, render_desk, join_media),
(script_ready, make_poster, join_media),
(join_media, store_release),
👉🖥 Stellen Sie die Anwendung über den Button Bereitstellen in der Workbench bereit oder 👉💻 über Tab 1:
cd ~/vibe-studio-multimodal
uv run python vibestudio/deploy.py
die ausgeführt wird, wobei die Werte aus Ihrer .env ausgefüllt werden:
gcloud run deploy vibestudio --source vibestudio \
--project $GOOGLE_CLOUD_PROJECT --region us-central1 \
--labels dev-tutorial-codelab=vibetube --allow-unauthenticated \
--memory 2Gi --cpu 2 --timeout 3600 --concurrency 40 \
--max-instances 1 --min-instances 1 --session-affinity \
--set-env-vars GOOGLE_CLOUD_PROJECT=...,STUDIO_VERTEX=1,STUDIO_MEMORY_BANK=...,STUDIO_RAG_CORPUS=...,VIBETUBE_URL=...,VIBETUBE_EVENT=...,VIBETUBE_NAME=...,VIBETUBE_PROJECT=...
--source erstellt den Container mit Cloud Build und stellt ihn in einem Schritt bereit. Der erste Build dauert vier oder fünf Minuten. --session-affinity mit einer warmen Instanz behält eine gesperrte Ausführung auf der Instanz bei, die sie enthält: noch einmal der Sitzungsspeicher von Sitzung 5. In einer größeren Bereitstellung wird der Sitzungsspeicher zu VertexAiSessionService verschoben und die Affinität entfällt. Der Build und der Dienst werden als Compute-Standarddienstkonto des Projekts ausgeführt, dem bei der Einrichtung die erforderlichen Rollen zugewiesen wurden.
👉🖥 Öffnen Sie die Dienst-URL und führen Sie einen Durchlauf in der App aus: eine Idee, eine Auswahl, warten Sie auf den Clip und das Poster. Auf dem Tab „Verlauf“ werden beide gespeichert.
Veröffentlichen (7b)
Die Veröffentlichung erfolgt über einen mehrteiligen POST von der App an den Endpunkt des Raums auf VibeTube in vibestudio/server/platform/publish.py. Der Clip ist das Video, das Poster ist das Thumbnail und das Profil nennt den Raum und Sie:
url = f"{prof['platform_url']}/api/events/{prof['event_code']}/videos"
data = {"title": state.script["title"], "description": state.script["description"],
"duration": "0:08", "displayName": prof["display_name"],
"projectId": prof["project_id"]} # one video per project and room: publishing again replaces it
files = {"videoFile": (clip.name, clip.open("rb"), "video/mp4"),
"thumbnailFile": (poster.name, poster.open("rb"), "image/png")} # the poster, not a frame
r = httpx.post(url, data=data, files=files, timeout=120)
video_id = r.json()["id"] # watch it at {platform}/e/{event}?v={video_id}
VibeTube speichert ein Video pro projectId und Raum. Das Projekt, das die App sendet, ist Ihr Google Cloud-Projekt. Wenn Sie das Video also noch einmal veröffentlichen, wird es ersetzt, anstatt ein neues hinzuzufügen. Es wird nie ein Platzhalter-Poster hochgeladen. Das echte Poster ist das Thumbnail, Byte für Byte.
Die letzte Änderung ist eine Konfiguration, keine Codezeile. 👉🖥 Auf der Seite 7b: die VibeStudio-URL (aus der Bereitstellung in 7a), den Ereigniscode des Raums, genau wie er in VibeTube geschrieben wird (den Sie bei der Einrichtung angegeben haben, es sei denn, Ihre Lehrkraft hat einen anderen geöffnet), und Ihren Namen. Klicke auf Veröffentlichen. Die Workbench leitet den Klick an die App weiter, die App postet die Nacht und die Zeilen darunter lesen das Ergebnis aus der App und von VibeTube: Die App antwortet, die Nacht ist mit einem echten Poster abgeschlossen, der Codename nennt einen offenen Raum, VibeTube hat eine Video-ID zurückgegeben, das Video ist an der Wand und die Miniaturansicht ist dein Poster. Auf der Ergebniskarte wird die Miniaturansicht auf VibeTube neben dem Poster in der App angezeigt.

10. Was Sie erstellt haben
👉🖥 Am Ende der Seite 7b wird der gesamte Graph gezeichnet, dreizehn Knoten von einer Idee bis zu einem Clip und seinem Poster. Bewegen Sie den Mauszeiger auf einen Knoten, um zu sehen, was er tut und in welcher Sitzung er erstellt wurde.
Sitzung | Was es gelehrt hat | Geschrieben als |
2 · Bildgenerierung | Ein Agent: Modell, Anleitung, Tools; das Bildmodell als Tool; was ein Prompt nicht versprechen kann |
|
3 · Workflows | Die Edge-Liste, der Fan-out und der Join, ein Router mit einer Task-Modus-Schleife dahinter |
|
4 · Multimodale Aufnahme | Ein Leser pro Art, eine Linienform, ein Korpus; ein dritter Leser nach Bedeutung, mit den Medien |
|
5 · Videogenerierung | Zwei Arten der Sperrung, ein Mechanismus; der Sitzungsspeicher; das Poster, das namentlich geteilt wird |
|
6 · Memory | Gemerkte Informationen über mehrere Nächte, zwei Callbacks |
|
7 · Bereitstellen und veröffentlichen | Das Diagramm in einer App in Cloud Run; die Nacht auf VibeTube |
|
Fünf Regeln, die das Diagramm befolgt:
- Anhalten statt warten. Eine Pause ist eine Zeile im Sitzungsspeicher, kein Thread.
- Alles auf die gleiche Weise fortsetzen – anhand der ID des offenen Anrufs, unabhängig davon, ob eine Person oder ein Prozess antwortet.
- Geben Sie Daten nach Name im Status frei, anstatt sie durch jede Nutzlast zu leiten.
- Entscheiden Sie sich mit Code, bevor Sie für ein Modell bezahlen. Die Richtlinie ist eine Textdatei und ein Router und wird vor dem Rendern ausgeführt.
- Lesen Sie alles, was kein Text ist, in Text ein, bevor es den Korpus erreicht. Der Dateiname muss in der Zeile enthalten sein, damit die Datei zurückgegeben werden kann.
Ein Prompt kann keine Reihenfolge, keinen Stopp und keine Prüfung garantieren. Ein Diagramm kann das.
Nächste Schritte
VertexAiSessionServicefür Sitzungen, die eine Instanz überdauern, und--session-affinityverwerfen.- Ein Webhook, der den Aufruf des Renderers anstelle eines Pollers beantwortet: gleicher
FunctionResponse(id=...), anderer Absender. - Ein zweites
RequestInputvor der Veröffentlichung – das Schema ist das Formular, also ein Knoten und kein Seitencode. - Die neuen Kommentare, Sprachnotizen und Clips der Fans werden nach jeder Veröffentlichung dem Korpus hinzugefügt, zusammen mit dem Poster, unter dem sie gesendet wurden.
Bereinigen
Alles, was in diesem Lab erstellt wurde, befindet sich in dem Projekt, das Sie in Schritt 4 erstellt haben. Die günstigste Bereinigungsmethode ist daher, das Projekt zu löschen. In Cloud Shell:
gcloud projects delete $(cat ~/project_id.txt)
Wenn Sie das Projekt behalten und nur die kostenpflichtigen Teile entfernen möchten: den Cloud Run-Dienst (gcloud run services delete vibestudio --region us-central1), den RAG-Korpus und die Memory Bank (ihre Ressourcennamen sind in runs/ragcorpus.json und runs/memorybank.json enthalten und werden auf den Seiten „Agent Engine“ und „RAG Engine“ der Console gelöscht) sowie das Container-Image in Artifact Registry.