1. Wprowadzenie

Prowadzisz na VibeTube kanał o przytulno-upiornej tematyce: co wieczór publikujesz jeden film Short aż do Halloween. Odbiorcy chcą małej magii w małych pokojach i wysyłają ją do Ciebie na wszystkie możliwe sposoby – jako komentarze, notatki głosowe, zdjęcia własnych nawiedzonych szuflad i klipy z ganku o zmierzchu. Zaległości są pełne pomysłów. Każdy wieczór poświęca na te same zadania: sprawdza, co jest popularne na karcie, przegląda zaległe materiały, czyta wiadomości od widzów, wybiera kierunek, sprawdza, czy jest on zgodny z zasadami kanału, pisze scenariusz, rysuje plakat, renderuje klip i publikuje.
Modele generatywne mogą wykonywać każde zadanie samodzielnie. Gemini wyszukuje, słucha, patrzy i pisze, model obrazu rysuje, a Veo renderuje. Nie potrafią jednak samodzielnie przeprowadzić nocy w odpowiedniej kolejności, zatrzymać się, aby podjąć decyzję, która należy do Ciebie, odrzucić złego kierunku, zanim będzie kosztował pieniądze, poczekać kilka minut na renderowanie bez pozostawiania niczego otwartego i pozostawić zapisu.
W tym ćwiczeniu z programowania ten wieczór zamieni się w ADK Workflow: narysowany wykres węzłów i krawędzi, który działa sam, pyta Cię tylko o to, gdzie potrzebna jest Twoja ocena, i kończy każde uruchomienie klipem i jego plakatem na VibeTube.
Prompt nie może obiecywać zamówienia, zatrzymania ani czeku. Wykres może.
Czego się nauczysz
- 1 agent i model obrazu jako narzędzie.
Agentto model, instrukcje i narzędzia. Trzecie narzędzie wywołuje model obrazu, a pierwsze uruchomienie kończy się plakatem. Następnie zdanie, które jest podstawą reszty modułu: prompt nie może obiecywać zamówienia, zatrzymania ani sprawdzenia. - Struktury, z których składa się graf.
Workflowto lista krawędzi. Tworzysz rozgałęzienie i połączenie (równoległe czytniki spotykające się wJoinNode) oraz router, który nazywa własne wyjście, z agentem w trybie zadania za odrzuconym wyjściem, który czyści kierunek w pętli wywołań narzędzi i ponownie dołącza do głównej linii. - Potok przetwarzania multimodalnego. 4 rodzaje treści wysłanych przez odbiorców, po jednym czytniku na każdy rodzaj: Gemini transkrybuje notatki głosowe, dodaje napisy do zdjęć, ogląda klipy i słucha ich ścieżek dźwiękowych, a każdy czytnik zapisuje ten sam kształt linii w jednym korpusie silnika RAG. Trzeci czytnik w procesie fan-out pobiera informacje według znaczenia i przekazuje multimedia z fragmentami.
- Dwa rodzaje zawieszenia, jeden mechanizm Bieg zatrzymuje się, gdy pojawia się osoba (
RequestInput: 4 karty, odpowiadasz) i gdy narzędzie działa wolno (Veo zaLongRunningFunctionTool: paragon). Oba te elementy są tym samym: otwartym wywołaniem w magazynie sesji, wznowionym przez jego identyfikator, z formularza lub z innego procesu. Dwa oddziały biura medialnego udostępniają plakat według nazwy w stanie. - Pamięć w ciągu nocy. Dwa wywołania zwrotne cyklu życia w Banku zapamiętanych informacji:
before_model_callbackprzywołuje preferencje twórcy, zanim Proponujący je odczyta, aafter_agent_callbackzapamiętuje dzisiejszy wybór po Scenarzystce. - Wersja. Wykres w aplikacji z ADK
Runner, zdarzenia wysyłane przez serwer na stronę, Cloud Run z koligacją sesji i jedno wieloczęściowe żądanie POST, które umieszcza klip na VibeTube z plakatem jako miniaturą.
Co utworzysz
aplikację działającą w przeglądarce: VibeStudio Workbench, stronę towarzyszącą z edytorem kodu na stronie, weryfikatory, które odczytują pliki na dysku i sesje zapisane przez uruchomienia, oraz wbudowany adk web. Siedem sesji odpowiada siedmiu stronom tego ćwiczenia, z tymi samymi numerami i literami części, dzięki czemu oba te elementy są ze sobą zsynchronizowane. Każda strona otwiera się na pasku, który zawiera jedno zdanie o sesji i 3 czynności, które można w niej wykonać: edytować, uruchomić i zweryfikować. Przeczytaj sekcję, a następnie wprowadź zmiany w obszarze roboczym. Każda zmiana kończy się panelem weryfikacji, który odczytuje, co się stało.
Uruchamiane są 3 elementy: Workbench (strona na porcie 4600, edytor, weryfikatory), Twój backend (Workflow w agent/ i 1 aplikacja piaskownicy na sesję w stage0_prompt/ do stage6_video/, którą obsługuje adk web) oraz Google Cloud (Gemini, model obrazu, RAG Engine, Veo, bank pamięci – wszystkie dostępne przez Vertex AI z Twoimi danymi logowania).
7 sesji,
Sesja | Co utworzysz | Twoje zmiany |
1 · Historia | Jeszcze nic – noc i graf, który z niej powstaje | – |
2 · Generowanie obrazów | Jeden agent rysuje pierwszy plakat |
|
3 · Przepływy pracy | Noc jako wykres: rozgałęzienie i złączenie, router i jego pętla |
|
4 · Przetwarzanie multimodalne | To, co wysłali odbiorcy, odczytane w jednym korpusie; trzeci czytelnik |
|
5 · Generowanie filmów | Dwa rodzaje zawieszenia; plakat udostępniony według nazwy w stanie |
|
6 · Pamięć | Co kanał zapamiętuje między nocami |
|
7. Wdrażanie i publikowanie | VibeStudio w Cloud Run; noc na VibeTube | kod wydarzenia w pokoju i opublikuj go. |
Jak korzystać z tego ćwiczenia. Najpierw przedstawiamy koncepcję: każda sesja wyjaśnia ideę, a następnie pokazuje kod po wprowadzeniu zmiany i omawia go. Nigdy nie prosi o edytowanie pliku tutaj – edytowanie odbywa się w obszarze roboczym, który wyświetla po jednej zmianie naraz, ze wskazówką obok i panelem weryfikacji poniżej. Przeczytaj sesję, a następnie postępuj zgodnie z instrukcjami na stronie.
Gdzie znajduje się ta funkcja
Wykres, motyw i pokój w VibeTube pochodzą z edycji Lin's Haunted Edition w VibeStudio. To multimodalna wersja, która skupia się na tym, co wysłali widzowie, i na 2 rodzajach zawieszenia, w jednostronicowym interaktywnym formacie laboratoriów Doliny Agentów. Studio to tylko scenografia. Każdy mechanizm pod nim jest zwykłym mechanizmem, którego używasz w potoku pomocy, procesie sprawdzania treści lub w jakimkolwiek innym procesie, który musi zostać wstrzymany, aby człowiek mógł wykonać powolne zadanie.
2. 🧰 Konfiguracja
- Co – konto, które może płacić, projekt, zainstalowane laboratorium, model, który może odpowiadać, i środowisko robocze działające w tle na jednym porcie.
- Dlaczego – jeden proces na jednym porcie: strona, edytor, weryfikatory,
/api/*iadk webmają wspólne źródło, więc nic w tym module nie wymaga drugiego terminala ani drugiego portu. - How – klonowanie, 2 skrypty. Drugi wykonuje rzeczywiste wywołanie modelu, uruchamia środowisko robocze i rozpoczyna tworzenie korpusu RAG i banku zapamiętanych informacji, podczas gdy Ty czytasz, dzięki czemu sesje 4 i 6 są gotowe.
Wszystko w tym module jest uruchamiane w Cloud Shell – gcloud, Python, Node, ffmpeg i git są już tam zainstalowane, a Gemini, model obrazu, Veo, mechanizm RAG i bank pamięci są dostępne przez Vertex AI przy użyciu Twoich danych logowania. W tym module nie ma żadnego klucza interfejsu API i nie musisz niczego instalować na laptopie. Laptop z (pakietem) SDK Google Cloud, Pythonem 3.12 i Node 20+ działa tak samo.
Jest jednak jedna rzecz, której nie możemy Ci zapewnić w tym module: projekt Google Cloud z włączonymi płatnościami. Krok 1 pozwala uzyskać konto, które może za nie zapłacić, a krok 4 przekształca to konto w projekt. Wykonaj je w tej kolejności – każdy krok po kroku 4 odczytuje projekt, który został ustalony w kroku 4.
1. Uzyskanie konta z rozliczeniami (~1 min)
Zaloguj się na konto Google, na którym włączone są płatności. Możesz użyć dowolnego z nich. Jeśli nie, rozpocznij bezpłatny okres próbny, który zapewnia konto rozliczeniowe, czyli wszystko, czego potrzebujesz w kroku 4. Jeśli uczestniczysz w module prowadzonym przez instruktora, najpierw wykorzystaj środki na tę sesję – w ten sposób utworzysz konto rozliczeniowe.
2. Otwórz Cloud Shell (~2 min)
👉🖥 Kliknij Aktywuj Cloud Shell u góry konsoli Google Cloud – to ikona w kształcie terminala w nagłówku.

👉🖥 Kliknij Otwórz edytor (ikona otwartego folderu z ołówkiem). Spowoduje to wyświetlenie edytora Cloud Shell z eksploratorem plików po lewej stronie – tam możesz przejść, gdy chcesz odczytać cały plik.

👉🖥 Jeśli w którymś momencie pojawi się prośba o autoryzację, kliknij Autoryzuj, aby przejść dalej.
👉🖥 Otwórz terminal w edytorze – to karta 1, na której będą wykonywane wszystkie kroki oznaczone ikoną 👉💻. Karta 2 to sam obszar roboczy, który otworzysz w kroku 7. Jeśli terminal nie jest jeszcze widoczny u dołu ekranu, otwórz go, wybierając Widok → Terminal:

👉💻 Sprawdź, na które konto jesteś zalogowany(-a) – powinno to być konto z kroku 1:
gcloud auth list
3. Pobierz moduł (~1 min)
👉💻 Sklonuj repozytorium:
cd ~
git clone https://github.com/cuppibla/vibe-studio-multimodal
cd vibe-studio-multimodal
4. Utwórz projekt (~2 min)
👉💻 Jeden skrypt znajduje Twoje konto rozliczeniowe, tworzy w nim projekt, łączy rozliczenia i wskazuje ten projekt w Cloud Shell:
cd ~/vibe-studio-multimodal
./setup_project.sh
Powinien pojawić się komunikat ✓ dotyczący wybranego konta rozliczeniowego, komunikat ✓ dotyczący utworzonego projektu oraz zakończenie podobne do tego:
Project ready: vibe-studio-4271 Next: ./setup_codelab.sh
vibe-studio-4271 to identyfikator projektu – Twój będzie miał inne cyfry. W tym miejscu dzieją się 4 rzeczy:
billing | wybiera najnowsze konto | |
project | tworzy | |
selects | zapisuje identyfikator w | |
waits | odpytuje, dopóki projekt nie będzie mógł być wyświetlany (maksymalnie przez 2 minuty); | |
👉🖥 Ten sam projekt jest teraz w konsoli – jego identyfikator znajduje się na karcie Informacje o projekcie w panelu:

5. Zainstaluj i skonfiguruj moduł (~4 min)
👉💻
cd ~/vibe-studio-multimodal
./setup_codelab.sh
Jeden skrypt, który można bezpiecznie uruchomić ponownie – drugie uruchomienie zachowuje wszystkie odpowiedzi udzielone za pierwszym razem. Na początku pojawią się 2 pytania. W obu przypadkach naciśnij Enter, aby wybrać domyślną odpowiedź:
kod zdarzenia | pokój VibeTube, w którym opublikowano Twój film w sesji 7. Wpisz kod sali otrzymany od nauczyciela lub naciśnij Enter, aby wejść do pokoju | |
display name | nazwę, pod którą pokój Cię wymienia, lub naciśnij Enter, aby wybrać nazwę utworzoną na Twoim koncie. | |
Oba te elementy zostaną umieszczone w .env i będzie można je później zmienić w .env lub na stronie publikowania. Następnie wykonuje pozostałe czynności w tej kolejności:
uv env | instaluje uv, jeśli go brakuje, a następnie | |
apis | włącza 6 interfejsów API w projekcie wybranym w kroku 4 i potwierdza, że każdy z nich odpowiada, zanim przejdzie dalej: | |
account | sprawdza, czy domyślne konto usługi Compute projektu istnieje i ma role wymagane przez kompilację i usługę z sesji 7; | |
.env | zapisuje plik | |
veo · room | potwierdza, że reklama | |
model | wykonuje jedno prawdziwe wywołanie Gemini, dzięki czemu nieprawidłowy projekt nie powiedzie się podczas konfiguracji, a nie w trakcie sesji; | |
starter | umieszcza 9 plików, które będziesz edytować, w miejscu z | |
page | instaluje i kompiluje | |
cloud | rozpoczyna tworzenie korpusu RAG Engine i banku zapamiętanych informacji w tle, dzięki czemu sesje 4 i 6 będą mogły z nich korzystać. | |
preflight | uruchamia | |
6. Przeczytaj informacje o kontroli wstępnej (~1 min)
Ostatnia rzecz, która została wydrukowana w kroku 5, to sprawdzenie wstępne – każda linia to prawdziwa sonda, a nie domysł:
✓ 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) ma rację: ten wykres jest pusty, a sesja 3 to miejsce, w którym go wypełniasz. Dwa wiersze - też są prawidłowe – korpus pojawia się po minucie lub dwóch, a bank po około 20 sekundach. Żaden z nich nie będzie potrzebny przed sesją 4.
👉💻 Możesz uruchomić go ponownie w dowolnym momencie, aby sprawdzić, na jakim etapie jesteś. Odczytuje on Twoje pliki, projekt i uruchomiony serwer, a nie listę kontrolną:
cd ~/vibe-studio-multimodal
uv run python scripts/preflight.py
7. Otwórz środowisko robocze (~1 min)
Środowisko pracy jest już uruchomione. W kroku 5 uruchomiono go w tle – 1 proces uvicorn na porcie 4600, który obsługuje stronę, każdą trasę /api/* i adk web zamontowane w /workbench. Nie ma nic do uruchomienia i nie musisz niczego restartować – zmiana zostanie wprowadzona przy następnym uruchomieniu.
👉🖥 Kliknij link wyświetlony przez proces wstępny lub w Cloud Shell: Podgląd w przeglądarce → Zmień port → 4600. Nazwij ją tab 2. Otworzy się Sesja 1 – Historia.
Trzy polecenia, które mogą Ci się później przydać, wszystkie z ~/vibe-studio-multimodal:
| zatrzymuje workbench, | |
| uruchamia go na pierwszym planie; Ctrl+C zatrzymuje go i każdy uruchomiony przez niego proces | |
|
| |
Układ repozytorium
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 · Historia – noc i wykres, który z niej powstaje
👉🖥 Karta 2 otworzy się na stronie Sesja 1 · Historia. Przeczytaj: 6 zadań ułożonych w pierścień wokół jednej osoby – zbadaj trend, przejrzyj zaległości, wybierz kierunek, sprawdź zasady, napisz skrypt, wyrenderuj klip – i pierścień powtarza się co noc. Gemini może samodzielnie przeprowadzić analizę, przygotować zaległe zadania i napisać skrypt. Veo może samodzielnie wyrenderować film. Wybór i sprawdzenie należą do człowieka.
Pod pierścieniem znajduje się wykres, który jest tworzony w pozostałej części modułu. Składa się on z 13 węzłów – od pomysłu do klipu i plakatu. Najedź kursorem na węzeł, aby zobaczyć, co robi i w której sesji jest tworzony. Nie musisz niczego edytować. Przycisk Wygeneruj pierwszy plakat na stronie to sesja 2.
Słownictwo, więc następne 6 stron czyta się bez zatrzymywania:
słowo | co to oznacza w tym przypadku. |
agent | ADK |
node | one step of the graph – a Python function, an agent, a join, a router |
edge | krotka węzłów, które działają w określonej kolejności; graf jest ich listą. Sesja 3 |
session | rekord jednego uruchomienia w |
state | słownik, który węzły udostępniają według nazwy klucza; węzeł zapisuje |
pamięć | co jest zachowywane po zakończeniu sesji – Bank zapamiętanych informacji, za pomocą wywołań zwrotnych. Sesja 6 |
4. 2. Generowanie obrazów – 1 agent i model obrazu jako narzędzie

👉🖥 W środowisku roboczym otwórz Sesja 2 · Generowanie obrazów. Zanim powstał wykres, najprostsze rozwiązanie, które mogło zadziałać: jeden Agent, którego instrukcja to cała noc w formie prozy, 3 funkcje Pythona jako jego ręce i uruchomienie, które kończy się obrazem.
Agent to model, instrukcja i narzędzia.
stage0_prompt/agent.py definiuje root_agent za pomocą 3 pól agenta ADK. model to model Gemini od agent/platform/config.py. instruction to prompt systemowy: 6 zadań w jednym, z czego ostatnie to obraz. tools ma 3 funkcje.
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."))
Model to zamknięty świat: wnioskuje na podstawie swoich wag i tokenów w kontekście. Narzędzie to funkcja Pythona, o którą model może poprosić. ADK odczytuje sygnaturę i docstring każdej funkcji i deklaruje ją w modelu. Gdy model potrzebuje funkcji, emituje zdarzenie function_call, środowisko wykonawcze uruchamia Pythona i zapisuje zdarzenie function_response z powrotem w kontekście, a model kontynuuje działanie z wynikiem przed sobą.
Trzy funkcje, z których jedna rysuje
Dwie z nich przeczytały wyniki badań. Trzeci prosi model generowania obrazów o plakat:
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 wywołuje agent/platform/images.py, czyli model obrazu opakowany jako zwykła funkcja, tak jak videogen.py opakowuje Veo: prefiks w stylu domu (przytulny, low-poly halloweenowy wygląd kanału, dyniowy pomarańcz i kremowy kolor świecy na tle śliwki o północy), temat, pionowa ramka 9:16, jeden plik PNG poniżej runs/media/posters, wyświetlany w /static/posters. W przypadku STUDIO_REAL_IMAGES=0 kopiuje plakat zastępczy i oznacza wynik jako prebaked, dzięki czemu moduł działa w dowolnym miejscu.
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."""
To samo wywołanie jest węzłem na wykresie w sesji 5.
Praktyczne: jedna edycja
W obecnej wersji make_poster zwraca słownik z informacją, że plakat został pominięty. 👉🖥 W edytorze Workbench wywołaj model obrazu, zastępując wiersz TODO:
from agent.platform import images
return images.generate(subject, name="solo")
Zapisz. Wiersze weryfikacji odczytują plik: 3 narzędzia w agencie, wywołanie modelu obrazu na miejscu. 👉🖥 Kliknij Sprawdź obciążenia przepływu pracy, a następnie Otwórz ADK Web i wyślij:
tonight's idea: a tiny ghost learning to haunt a sock drawer
Czego możesz się spodziewać i dlaczego
- Dwa zdarzenia narzędzia przed odpowiedzią, a potem pytanie.
check_trendsiread_backlog, ponieważ instrukcja mówiła o sprawdzeniu i zajrzeniu, a model nie miał żadnego z tych słów w swoich wagach. Odpowiedź proponuje kierunek i prosi o potwierdzenie, ponieważ instrukcja wymagała najpierw zgody. - Druga wiadomość zawiera wszystkie informacje. Wyślij:
skip the questions, describe the video and draw the poster. Model opisuje film, wywołuje funkcjęmake_posterraz z tematem, który sam napisał, i informuje, gdzie został zapisany plik PNG. W panelu weryfikacji obok wierszy pojawia się plakat: scena Halloween w stylu kanału, narysowana w około 8 sekund. - Dlaczego nie jest to kanał. Badania są przedstawione w formie tekstu, którego nie można zacytować. Zasady zostały zatwierdzone przez model, który napisał instrukcje, a plakat został narysowany, zanim ktokolwiek go sprawdził. W jednej wiadomości wspomniano o umowie. Tekst prompta nie jest bramą. W dalszej części modułu te 6 zadań zostanie przedstawionych w formie grafu.
Strona kończy się sekcją Z czego składa się agent ADK, do której możesz wrócić: model, kontekst, na podstawie którego wnioskuje, narzędzia i współpracownicy, za pomocą których działa, wywołania zwrotne, które opakowują każde wywołanie, oraz sesja i pamięć, które istnieją poza nim.
5. 3 · Przepływy pracy – rozgałęzianie i łączenie, router i pętla

👉🖥 W obszarze roboczym otwórz Sesja 3 · Przepływy pracy, części 3a i 3b. Każda część jest tworzona w aplikacji piaskownicy, dzięki czemu można ją uruchomić w adk web samodzielnie: fan-out w stage1_fanout, router i jego pętlę w stage3_router.
Rozgałęzianie i scalanie (3a)
ADK Workflow to lista krawędzi. Każdy wpis to łańcuch, czyli krotka węzłów, które są uruchamiane w określonej kolejności. Dwa łańcuchy opuszczające START działają równolegle. Łańcuch, który kończy się w węźle JoinNode, raportuje do niego, a połączenie czeka na każdy łańcuch, który kończy się w tym miejscu, zanim przejdzie dalej, przekazując jeden słownik z kluczem według nazwy węzła. Cel zapisany jako słownik mapuje nazwy tras routera na węzły. Na stronie 3a w zeszycie ćwiczeń znajdują się kształty i ćwiczenie polegające na rysowaniu krawędzi.
Rodzaj węzła | Zapisane jako | Działanie |
Węzeł funkcji |
| Zwykły Python. Może też zapisywać stan udostępniony za pomocą |
Węzeł złączenia |
| Czeka, aż wszystkie przychodzące łańcuchy zostaną zgłoszone, a następnie przekazuje wszystkie ich dane wyjściowe jako jeden słownik. |
Węzeł agenta |
| Jedno wywołanie modelu w trybie |
Router |
| Węzeł funkcji, którego zwracana wartość określa nazwę krawędzi wychodzącej. |
Węzeł danych wejściowych od człowieka |
| Wstrzymuje wykres, dopóki ktoś nie odpowie na formularz. Sesja 5. |
Pierwszy wykres przedstawia badania z sesji 2 jako węzły. scan_trends i read_backlog w agent/graph.py to 2 czytniki. read_backlog zapisuje też dzisiejszy pomysł w stanie udostępnionym. stage1_fanout/agent.py definiuje połączenie i tworzy Workflow z listy o nazwie EDGES, która jest pusta (0 edges kontroli wstępnej). 👉🖥 Wypełnij ją 2 łańcuchami:
EDGES = [(START, scan_trends, join_research),
(START, read_backlog, join_research)]
👉🖥 Uruchom go w adk web. Dwa węzły zapalają się razem, a następnie łączą. Otwórz zdarzenie join_research i odczytaj jeden słownik z kluczem scan_trends i kluczem read_backlog. Nie nastąpiło wywołanie modelu. Nic nie zmienia formatu słownika węzła łączenia: węzeł po nim, w kroku 3b, jest agentem, a węzeł agenta otrzymuje node_input jako wiadomość, więc słownik dociera jako JSON, który model odczytuje bezpośrednio.
Router i pętla (3b)
Węzeł Agent wewnątrz węzła Workflow działa w trybie single_turn: otrzymuje dane wyjściowe poprzedniego węzła jako wiadomość, wykonuje jedno wywołanie modelu i odpowiada raz. W przypadku output_schema odpowiedź jest weryfikowana pod kątem modelu Pydantic, zanim zobaczy ją następny węzeł, więc graf przekazuje obiekty typowane, a nie tekst. propose_directions odczytuje słownik wyszukiwania i proponuje 4 kandydatów, z których każdy jest sceną halloweenową. Kandydat 4 to przynęta: jego tytuł zawiera słowo, którego kanał nie akceptuje, więc router ma coś do wychwycenia. Każdy kandydat ma poster_idea, czyli jeden wiersz z nazwą pojedynczego obrazu, który sprzedaje film. Scenarzysta go zachowuje, a zespół ds. multimediów rysuje go podczas sesji 5.
Router to węzeł funkcji, którego zwracana wartość określa krawędź, którą przyjmuje uruchomienie. Brak wywołania modelu: policy_check odczytuje agent/policy_words.txt w momencie podejmowania decyzji, dopasowuje całe słowa do tytułu i kąta kierunku oraz kieruje je, zanim zostanie napisany skrypt lub wydane pieniądze. Zasada to dane: edytuj plik, a następne uruchomienie go odczyta.
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 to fan-out plus propose_directions i choose_direction, zastępujący ludzkie drzwi, które tworzysz w sesji 5: wybiera kandydata, którego numer kończy dzisiejszy pomysł (..., pick 4), w przeciwnym razie wybiera pierwszego i zapisuje wybór w stanie. scripter to agent single_turn, który przekształca zatwierdzony kierunek w Script: tytuł, opis, tagi, zdanie otwierające, 3 ujęcia i pomysł na plakat. Każde ujęcie to obraz Halloween w stylu kanału.
Pętla za odmową wyjścia. quarantine jest dostarczany jako agent w trybie zadania. Agent ma mode: chat to pętla konwersacyjna, którą agent główny uruchamia z użytkownikiem, single_turn to jedno wywołanie z ustrukturyzowaną odpowiedzią, a task to pętla wywołań narzędzi, którą agent uruchamia samodzielnie, dopóki nie wywoła wbudowanego finish_task, którego argumenty ADK pobiera z output_schema. Kwarantanna wywołuje funkcję find_policy_hits w odrzuconym kierunku, prosi suggest_replacement o zatwierdzony zamiennik kanału dla każdego słowa (agent/policy_replacements.txt, ponownie dane), przepisuje, sprawdza ponownie i kończy wpisanym CleanedDirection. Przeczytaj go na stronie, nie wpisuj go.
quarantine = Agent(
name="quarantine",
model=config.MODEL,
mode="task",
instruction=QUARANTINE_INSTRUCTION,
tools=[find_policy_hits, suggest_replacement],
output_schema=CleanedDirection)
W ustawieniach domyślnych łańcuch prowadzi od wyboru bezpośrednio do skryptu. 👉🖥 Umieść między nimi router z 2 wyjściami i ścieżką powrotną z kwarantanny. 3 linie zastępują 1:
(join_research, propose_directions, choose_direction, policy_check),
(policy_check, {"OK": scripter, "BLOCK": quarantine}),
(quarantine, scripter),
👉🖥 Uruchom obie trasy. Wyślij pomysł w obecnej postaci: choose_direction pobiera kandydata 1, policy_check generuje OK, a scenarzysta pisze ustrukturyzowany skrypt. Kliknij Nowa sesja i wyślij pomysł ponownie z , pick 4 na końcu: kandydat 4 to przynęta, policy_check emituje BLOCK, kwarantanna wywołuje swoje narzędzia, przepisuje odrzucone słowa, wywołuje finish_task, a oczyszczone polecenie wraca do skryptu. Wiersze weryfikacji odczytują oba uruchomienia z magazynu sesji.
Każde zdanie prompta z sesji 2 ma teraz węzeł: badania to 2 czytniki i połączenie, odmowa to router i agent zadania, a skrypt to węzeł agenta. Na całym wykresie zastępczy wybór to 2 węzły funkcji, direction_gate i persist_direction, które będą działać, dopóki sesja 5 nie zatrzyma drzwi. Umowa z twórcą jest nadal tylko wstępna, podobnie jak badania, które nie uwzględniają jeszcze opinii odbiorców.
6. 4 · Przetwarzanie multimodalne – co wysłali odbiorcy, odczytane w jednym korpusie

👉🖥 W środowisku roboczym otwórz Sesja 4 · Pozyskiwanie danych multimodalnych, części 4a i 4b. Odbiorcy nie tylko pisali. Po jednym czytniku na każdy rodzaj danych przekształca wszystko, co zostało wysłane, w tekst o tym samym kształcie. Jeden korpus silnika RAG przechowuje wszystkie te dane, a trzeci czytnik w procesie rozsyłania przeszukuje je według znaczenia i zwraca multimedia.
Co wysłali odbiorcy (4a)
agent/comments.md zawiera 12 komentarzy napisanych pod 6 filmami o Halloween na kanale. W agent/audience/ znajdują się pozostałe elementy: 8 notatek głosowych (voice/*.mp3), 14 zdjęć pokoi widzów (photo/*.jpg) i 6 klipów (clip/*.mp4) – powolne przesuwanie po zdjęciu z głosem widza w tle. Na stronie 4a otwierają się wszystkie te elementy: odtwarzają się wszystkie notatki głosowe, wyświetlają się wszystkie zdjęcia i uruchamiają się wszystkie klipy.

RAG Engine pobiera tekst. Notatka głosowa, zdjęcie lub klip mogą dołączyć do korpusu tylko w taki sposób, jak wcześniejsze posty w rag.caption_posters: model przekształca je w wiersz tekstu, który nazywa plik, wiersz staje się fragmentem, a gdy fragment wraca, wraca z nim plik. agent/platform/audience.py ma po jednym czytniku każdego rodzaju. Każdy z nich wysyła do Gemini 1 plik i 1 prompta oraz zapisuje 1 wiersz na plik:
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 robi to samo z image/jpeg i PHOTO_PROMPT, watch_clips z video/mp4 i CLIP_PROMPT, a Gemini ogląda obraz i słucha ścieżki dźwiękowej w jednym wywołaniu. Każdy czytnik zapisuje ten sam kształt linii, więc fragmentowanie, umieszczanie i pobieranie nigdy nie wie, z jakiego rodzaju pochodzi fragment:
- 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 ...

Korpus. Mechanizm RAG w Vertex AI to wyszukiwanie w dokumentach: korpus jest dzielony na fragmenty o długości około 120 tokenów, każdy fragment jest osadzany za pomocą text-embedding-005, a zapytanie jest osadzane w ten sam sposób i odpowiada na nie najbliższymi fragmentami według znaczenia, a nie według wspólnych słów. „przytulne małe nawiedzenia w domu” znajduje komentarz z prośbą o małą magię w małych pokojach, notatkę głosową o nietoperzu przytulającym żarówkę i klip, którego ścieżka dźwiękowa mówi, że duch to papierowy kubek.
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)))
Konfiguracja rozpoczęła już tworzenie korpusu. Wiersz połączenia na stronie 4a informuje, czy korpus jest aktywny, i tworzy go, jeśli konfiguracja nie mogła tego zrobić. Z przycisków na stronie lub terminala: python -m agent.platform.rag tworzy korpus i zapisuje go w runs/ragcorpus.json; python -m agent.platform.rag load --audience uruchamia każdy czytnik w audience.READERS, a następnie przesyła to, co zapisał, obok komentarzy i podpisów autorów, każdy plik pod własną nazwą, i czeka na odpowiedź indeksu; python -m agent.platform.rag query "cozy little hauntings at home" pokazuje pobieranie, znajdując fragmenty, które nie mają żadnego wspólnego słowa z zapytaniem.
Zmiana. W konfiguracji fabrycznej READERS zawiera tylko read_comments. Trzy czytniki multimediów znajdują się za jednym #. 👉🖥 Uruchom przetwarzanie raz, tak jak jest: dane wyjściowe to not read (no reader wired in audience.READERS): voice, photo, clip, a przetwarzane są tylko komentarze i napisy. Następnie usuń # :
transcribe_voice, caption_photos, watch_clips,
👉🖥 Zapisz i ponownie uruchom przetwarzanie. Odczytanie 40 elementów zajmuje czytnikom około 3 minut. Konsola wyświetla po kolei heard, saw i watched, a każda karta w siatce wypełnia się odpowiednim wierszem. Wiersze weryfikacji odczytują plik, dane wyjściowe czytników i korpus: 4 czytniki podłączone, 8 odsłuchanych notatek głosowych, 14 obejrzanych zdjęć, 6 obejrzanych klipów, 5 plików w korpusie.
Trzeci czytnik (4b)
Opinie odbiorców to badania, które są udostępniane przez każde uruchomienie, więc trafiają do wykresu tam, gdzie badania: jako trzeci czytnik w połączeniu. read_feedback w agent/graph.py pobiera pomysł na dzisiejszy wieczór, prosi korpus o najbliższe fragmenty i zgłasza je do join_research, który czeka na 3 fragmenty. Krawędź (START, read_feedback, join_research) jest dostarczana w stage5_rag/agent.py, kształt sesji 3; Twoja zmiana znajduje się wewnątrz węzła. Pobieranie, a nie wywołanie modelu, i deterministyczne: ten sam pomysł znajduje te same fragmenty, podczas gdy propozycja się zmienia.
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
Każdy wiersz w korpusie, który pochodzi z notatki głosowej, zdjęcia, klipu lub plakatu, zawiera nazwę pliku. rag.media_in(hits) odczytuje te nazwy z fragmentów i zamienia je na adresy URL, które strona może wyświetlać i odtwarzać. 👉🖥 Przekaż multimedia z powrotem wraz z fragmentami, aby treści wysłane przez odbiorców były powiązane z ich wypowiedziami:
return Event(output={"query": query, "feedback": [h["text"] for h in hits],
"media": rag.media_in(hits)})
👉🖥 Uruchom stage5_rag, podając pomysł. Otwórz read_feedback: zapytanie to Twój pomysł, a wynik zawiera 5 najbliższych fragmentów i listę media z adresem URL każdego pliku. Otwórz join_research: 3 klawisze. Otwórz propose_directions: kandydaci od 1 do 3 cytują feedback w swoich dowodach. W panelu weryfikacji widać, co czytelnik oglądał pod wierszami: klip, 3 notatki głosowe i plakat przedstawiający ducha w świetle lodówki.

7. 5 · Generowanie filmów – 2 rodzaje zawieszenia, 1 mechanizm

👉🖥 W obszarze roboczym otwórz Sesję 5 – Generowanie filmów, części 5a i 5b, utworzone w stage6_video, cały wykres. Klipy zajmują minuty, a plakaty sekundy, a proces zatrzymuje się 2 razy: raz dla Ciebie, raz dla Veo. Te 2 przystanki to jeden mechanizm, a 2 gałęzie biurka mają wspólną nazwę.
Dwa rodzaje zawieszenia (5a)
Nie ma żadnych oczekujących elementów. Uruchomienie, które musi się zatrzymać, zapisuje otwarte wywołanie w sesji i kończy się. Każdy, kto odpowie na to wywołanie, osoba lub proces, kontynuuje je. Strona otworzy się na jednej tabeli:
dla osoby · | powolne narzędzie · | |
kto zatrzymał uruchomienie, |
|
|
na co czeka, | osoba: jedna z 4 kart, | powolne zadanie: działanie Veo, minuty |
co znajduje się w sesji, | otwarte wywołanie | otwarte wywołanie |
jak jest wznawiana |
| obiekt |
kto ją wysyła, |
|
|
gdzie mieszka w międzyczasie, |
| ten sam wiersz, ten sam identyfikator – nic się nie uruchamia |

Osoba W jednym prompcie „poproś twórcę o potwierdzenie” jest poradą, a w sesji 2 została ona pominięta w jednej wiadomości. W przepływie pracy pauza jest wykonywana przez wyszukiwarkę. Węzeł, który zwraca RequestInput, zawiesza działanie: ADK zapisuje otwarte wywołanie o nazwie adk_request_input w sesji z message, response_schema i payload, nadaje mu interrupt_id i wraca do elementu wywołującego. Brak oczekiwania na wątek. Jedyną rzeczą, która może wznowić działanie, jest function_response z tym identyfikatorem, zweryfikowany pod kątem schematu. Wiadomość na czacie wysłana do przepływu pracy to nowa tura, a nie odpowiedź. Schemat jest też formularzem: adk web renderuje go, a aplikacja VibeStudio renderuje ten sam schemat jako 4 karty. direction_gate już zapisuje kandydatów do stanu. 👉🖥 Jak to zatrzymać:
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})
Twoja odpowiedź dociera do persist_direction jako node_input, słownik z jednym kluczem, pick. Cztery kandydatury docierają w inny sposób: brama zapisała je w stanie udostępnionym w candidates, a ADK wiąże klucz stanu z parametrem o tej samej nazwie. Węzeł wybiera jednego kandydata i zapisuje z powrotem direction, angle, hook i user:prefs z Event(state=...). Klucze z prefiksem user: są zachowywane między sesjami danego użytkownika.
W przypadku wolnego narzędzia. Renderowanie Veo trwa kilka minut, a wykres nie powinien czekać tak długo. render_submit w agent/desk.py uruchamia zadanie i natychmiast zwraca potwierdzenie, którego stan to pending:
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"]}
Potwierdzenie opakowane w LongRunningFunctionTool ma znaczenie dla ADK: tura agenta się kończy, przepływ pracy zostaje zawieszony w render_desk, a otwarte wywołanie z identyfikatorem i potwierdzeniem jest wierszem w runs/sessions.db. 👉🖥 W stage6_video/agent.py opakuj narzędzie:
tools=[LongRunningFunctionTool(render_submit)])
Wznów według identyfikatora, z innego procesu. agent/deliver.py znajduje oczekujące wywołanie w magazynie sesji, odpytuje Veo, dopóki klip nie zostanie utworzony, zapisuje wynik w runs/state.json i wysyła do tej samej sesji function_response z identyfikatorem tego wywołania. 👉🖥 Utwórz część odpowiedzi:
part = Part(function_response=FunctionResponse(
id=row["call_id"], name=row["name"], response=response))
👉🖥 Uruchom stage6_video, aż zatrzyma się 2 razy. Wyślij pomysł: uruchomienie zatrzymuje się na formularzu utworzonym na podstawie schematu, a w ładunku znajdują się 4 kandydaci. Wpisz 2 w pick i prześlij. Wykres zostanie wznowiony w persist_direction. Wypróbuj test zatrzymania: zawieś na formularzu, zatrzymaj serwer (scripts/stop.sh na karcie 1), uruchom go ponownie (scripts/restart.sh), otwórz ponownie sesję. Formularz nadal stoi, ponieważ pauza to rząd, a nie wątek. Po script_ready render_desk wywołuje render_submit, otrzymuje potwierdzenie, odpowiada WAITING i uruchomienie się kończy. Sesja zachowuje identyfikator wywołania. Uruchom wyświetlanie z konsoli lub karty 1 strony:
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
👉🖥 Kliknij Odśwież adk web: function_response następuje po oczekującym wywołaniu, join_media zostaje zwolniony, a store_release zostaje uruchomiony. 8 wierszy weryfikacji odczytuje 2 przystanki z sesji: wywołanie bramy i odpowiedź, potwierdzenie renderowania i jego dostarczenie.
Stan i biurko (5b)
Biuro medialne ma strukturę podobną do tej z przykładu 3a, czyli rozgałęzienie i połączenie z jedną wolną gałęzią. script_ready wysyła skrypt na 2 sposoby. render_desk przesyła klip, a bieg zostaje zawieszony. make_poster to narzędzie z sesji 2 jako węzeł: skrypt poster_idea, styl kanału i to, co bank pamięci wie o guście twórcy w kwestii plakatów (powiązane ze stanem jako memory_facts, sesja 6), trafiają do modelu obrazu, a po kilku sekundach otrzymujemy plik PNG w formacie 9:16. join_media czeka na wolniejszą, a store_release zapisuje znalezione dane w stanie.
Udostępnianie według nazwy. store_release nigdy nie otrzymuje plakatu. Deklaruje parametr o nazwie poster_url, a ADK wiąże go ze stanem udostępnionym, ponieważ węzeł w drugiej gałęzi zapisał ten klucz. Żadne ładunki nie przechodzą między gałęziami:
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'}")
W obecnej postaci make_poster zwraca plakat i wiadomość, a store_release wyświetla komunikat poster: not in state. 👉🖥 Dodaj zapis stanu do ostatecznego wyniku:
state={"poster_url": rec.get("url", ""), "poster_path": rec.get("path", "")},
message=f"poster: {said}")
👉🖥 Uruchom jeszcze raz i dostarcz. Otwórz zdarzenie make_poster: adres URL plakatu w danych wyjściowych i delta stanu z poster_url. Otwórz store_release: w wiadomości podana jest nazwa plakatu. Blok Sesja tego uruchomienia na stronie pokazuje te same informacje co karta Stan wiersza adk web: identyfikator sesji, którego potrzebuje wznowienie, otwarte wywołanie i klucze zapisane przez węzły, w tym poster_url. W panelu weryfikacji klip i plakat są wyświetlane obok siebie.

8. 6 · Pamięć – co kanał zapamiętuje między nocami

👉🖥 W obszarze roboczym otwórz Sesję 6 · Pamięć, części 6a i 6b, utworzone w stage4_memory. Sesja 5 dotyczyła stanu jednego uruchomienia. Ta sesja dotyczy tego, co kanał zapamiętuje między uruchomieniami: co wykres słyszy z zewnątrz, za pomocą 2 wywołań zwrotnych.
Bank zapamiętanych informacji (6a)
Bank zapamiętanych informacji Vertex AI to zarządzana pamięć długoterminowa dotycząca danej osoby. Fakty są przechowywane w zakresie, nazwie aplikacji i identyfikatorze użytkownika oraz w zdefiniowanych przez Ciebie tematach. Gdy przekazujesz mu wymianę zdań z użytkownikiem memories.generate, wyodrębnia on to, co pasuje do tematu, osadza to i łączy z tym, co już ma: tworzony jest nowy fakt, zmieniony jest aktualizowany, a powtórzenie jest scalane. memories.retrieve zwraca fakty dotyczące zakresu. Oba wywołania znajdują się w pliku agent/platform/memory.py.
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.",
}
Trzeci temat to gust kanału w zakresie zdjęć. W sesji 5 model zapisał w niej informacje: wysłany plakat został opisany w jednym zdaniu i zapamiętany, a następny plakat został narysowany z użyciem tego zdania w prompcie.
Konfiguracja rozpoczęła już tworzenie banku. Wiersz połączenia na stronie 6a informuje, czy jest on aktywny. Za pomocą przycisków na stronie lub terminala: python -m agent.platform.bank tworzy bank i jego tematy oraz zapisuje nazwę zasobu w pamięci podręcznej w runs/memorybank.json; python -m agent.platform.bank load generuje 4 poprzednie sesje (2 epoki zwierząt z regułą stylu, epokę gadżetów, niedawną epokę fantasy); python -m agent.platform.bank list pokazuje, czym stały się transkrypcje: skonsolidowanymi stwierdzeniami faktów, a nie transkrypcjami.
Wywołania zwrotne (6b)
Pamięć nie jest węzłem. Wywołanie zwrotne to funkcja przekazywana do Agent. Pakiet ADK wywołuje ją w ustalonym momencie z kontekstem na żywo, a zwrócenie None umożliwia normalne działanie. 3 pary: wokół tury agenta (before_agent_callback, after_agent_callback, z CallbackContext), wokół każdego wywołania modelu (before_model_callback z LlmRequest, after_model_callback z LlmResponse), wokół każdego wywołania narzędzia. W tym miejscu znajdują się zabezpieczenia, dane telemetryczne, pamięci podręczne i wstrzykiwanie kontekstu, bez węzła na wykresie.
W stage4_memory/agent.py propose_directions odczytuje bank przed wywołaniem modelu: recall_taste pobiera fakty, dołącza je do żądania jako blok MEMORY (najstarsze najpierw) i nakazuje modelowi, aby kandydaci od 1 do 3 byli zgodni z bieżącymi preferencjami, a reguły były traktowane jako ograniczenia. Zapisuje też fakty w stanie jako memory_facts, gdzie make_poster może je znaleźć. 👉🖥 Połączenie przewodowe:
output_schema=Directions,
before_model_callback=recall_taste)
Scenarzysta zapisuje to, co twórca wybrał po swojej turze: remember_pick odczytuje direction ze stanu, pisze jedno zdanie o wyborze i przekazuje je do memories.generate. 👉🖥 Połączenie przewodowe:
output_schema=Script,
after_agent_callback=remember_pick)
👉🖥 Uruchom z pustą wiadomością i wybierz kartę. Otwórz wydarzenie propose_directions: prośba zawiera 3 epoki, a kandydaci od 1 do 3 skłaniają się ku obecnemu gustowi, podczas gdy trendy sugerują coś innego. Po skrypcie kliknij Pokaż bank: pamięć została zmieniona lub dodana do dzisiejszego wyboru. Uruchom drugą noc: pamięta pierwszą.
9. 7 · Wdrażanie i publikowanie – graf w aplikacji, noc na VibeTube

👉🖥 W obszarze roboczym otwórz Sesję 7. Wdrażanie i publikowanie, części 7a i 7b.
Wdrażanie VibeStudio (7a)
W środowiskach testowych adk web napędzało wykres. VibeStudio obsługuje go za pomocą Runner ADK w wątku instancji roboczej z własną pętlą zdarzeń i łączy każde zdarzenie w jeden stan uruchomienia, który strona odczytuje za pomocą zdarzeń wysyłanych przez serwer: czytniki, Twój wybór jako 4 karty, plakat po wylądowaniu, renderowanie po dostarczeniu. Odpowiedź bramki i dostarczenie renderowania to to samo wywołanie, część function_response, a aplikacja sama odpytuje Veo, zamiast czekać na konsolę.
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/ to kopia bajt po bajcie pliku agent/ laboratorium, sprawdzona przez checks/verify_app.py. Pełna lista krawędzi w pliku agent/graph.py zawiera wszystkie struktury z tego ćwiczenia w jednym miejscu, czyli 13 węzłów. Jest ona wypełniona, więc nie musisz jej wpisywać:
(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),
👉🖥 Przeprowadź wdrożenie za pomocą przycisku Wdróż w obszarze roboczym lub 👉💻 z karty 1:
cd ~/vibe-studio-multimodal
uv run python vibestudio/deploy.py
który działa z wartościami z Twojego .env:
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 tworzy kontener za pomocą Cloud Build i wdraża go w jednym kroku. Pierwsza kompilacja trwa 4–5 minut. --session-affinity z jedną instancją ciepłą utrzymuje zawieszony przebieg w instancji, która go przechowuje: ponownie w magazynie sesji 5. W przypadku większego wdrożenia magazyn sesji jest przenoszony do VertexAiSessionService, a powiązanie znika. Kompilacja i usługa działają jako domyślne konto usługi Compute Engine projektu, któremu konfiguracja przyznała wymagane role.
👉🖥 Otwórz adres URL usługi i wygeneruj wieczór w aplikacji: pomysł, wybór, poczekaj na klip i plakat. Na karcie Historia zachowywane są obie te informacje.
Publikowanie (7b)
Publikowanie to jedno wieloczęściowe żądanie POST z aplikacji do punktu końcowego pokoju w VibeTube, w formacie vibestudio/server/platform/publish.py. Klip to film, plakat to miniatura, a profil zawiera nazwę pokoju i Twoje imię i nazwisko:
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 przechowuje 1 film na projectId i pokój, a projekt, który wysyła aplikacja, to Twój projekt Google Cloud, więc ponowne opublikowanie zastępuje film, zamiast dodawać nowy. Plakat zastępczy nigdy nie jest przesyłany. Miniatura jest identyczna z prawdziwym plakatem.
Ostatnia zmiana to konfiguracja, a nie linijka kodu. 👉🖥 Na stronie 7b: adres URL VibeStudio (wypełniony z wdrożenia 7a), kod wydarzenia pokoju dokładnie taki, jak w VibeTube (ten, który został podany podczas konfiguracji, chyba że instruktor otworzył inny), oraz Twoje imię i nazwisko. Kliknij Opublikuj. Środowisko robocze przekazuje kliknięcie do aplikacji, aplikacja publikuje wieczór, a wiersze poniżej odczytują wynik z aplikacji i VibeTube: aplikacja odpowiada, wieczór kończy się prawdziwym plakatem, kryptonim nazywa otwarty pokój, VibeTube zwraca identyfikator filmu, film jest na ścianie, a jego miniaturą jest Twój plakat. Karta wyników pokazuje miniaturę w VibeTube obok plakatu w aplikacji.

10. Co zostało utworzone
👉🖥 Na końcu strony 7b znajduje się cały wykres – 13 węzłów od pomysłu do klipu i plakatu. Najedź kursorem na węzeł, aby zobaczyć, co robi i w której sesji został utworzony.
Sesja | Czego się nauczyliśmy | Zapisane jako |
2 · Generowanie obrazów | Agent: model, instrukcje, narzędzia; model obrazu jako narzędzie; czego prompt nie może obiecać |
|
3 · Przepływy pracy | Lista krawędzi, rozgałęzienie i połączenie, router z pętlą trybu zadania za nim |
|
4 · Przetwarzanie multimodalne | One reader per kind, one line shape, one corpus; a third reader by meaning, with the media |
|
5 · Generowanie filmów | Dwa rodzaje zawieszenia, jeden mechanizm; magazyn sesji; plakat udostępniony według nazwy |
|
6 · Pamięć | Pamięć z kilku nocy, 2 wywołania zwrotne |
|
7. Wdrażanie i publikowanie | wykres w aplikacji w Cloud Run; noc w VibeTube |
|
5 zasad, których przestrzega wykres:
- Zawieś zamiast czekać. Wstrzymanie to wiersz w magazynie sesji, a nie wątek.
- Wznów wszystko w ten sam sposób – za pomocą identyfikatora otwartego połączenia, niezależnie od tego, czy odpowiada osoba czy proces.
- Udostępniaj dane według nazwy w stanie, a nie przez przekazywanie ich w każdym ładunku.
- Zanim zapłacisz za model, przetestuj go za pomocą kodu. Zasada to plik tekstowy i router, który działa przed renderowaniem.
- Odczytaj wszystko, co nie jest tekstem, jako tekst, zanim trafi do korpusu, z nazwą pliku w wierszu, aby plik mógł wrócić.
Prompt nie może obiecywać zamówienia, zatrzymania ani czeku. Wykres może.
Co dalej
VertexAiSessionServicew przypadku sesji, które trwają dłużej niż instancja, i usuń--session-affinity.- Webhook, który odpowiada na wywołanie renderowania zamiast mechanizmu odpytywania: ten sam
FunctionResponse(id=...), inny nadawca. - A second
RequestInputbefore publishing – the schema is the form, so it is one node and no page code. - Nowe komentarze, notatki głosowe i klipy od odbiorców są dodawane do korpusu po każdej premierze wraz z plakatem, pod którym zostały wysłane.
Czyszczenie danych
Wszystkie zasoby utworzone w tym module znajdują się w projekcie utworzonym w kroku 4, więc najtańszym sposobem na ich usunięcie jest usunięcie projektu. W Cloud Shell:
gcloud projects delete $(cat ~/project_id.txt)
Aby zachować projekt i usunąć tylko płatne elementy: usługę Cloud Run (gcloud run services delete vibestudio --region us-central1), korpus RAG i bank pamięci (ich nazwy zasobów znajdują się w runs/ragcorpus.json i runs/memorybank.json, a strony Agent Engine i RAG Engine w konsoli je usuwają) oraz obraz kontenera w Artifact Registry.