Alles, was Sie zum Erstellen von Agents mit langer Laufzeit in Google Cloud benötigen

1. Hinweis

Auf den meisten Ticket-Websites werden Käufer in eine Warteschlange gestellt, um Serverabstürze zu vermeiden und die hohe Nachfrage zu bewältigen. Sie beginnen mit dem Kauf und stellen fest,dass Sie an 14.000. Stelle in der Warteschlange stehen. Die Warteschlange bewegt sich 40 Minuten lang. Wenn Sie an der Reihe sind, haben Sie nur wenig Zeit, um Plätze auszuwählen und zu bezahlen. Wenn du diese Zeit verpasst, wird dein Platz in der Warteschlange an jemand anderen vergeben.

Viele Aufgaben in der Praxis folgen diesem Muster: Eine Aufgabe, die viel länger als eine einzelne Unterhaltung dauert und in einem externen System ausgeführt wird, das Sie nicht kontrollieren. Beispiele hierfür sind das Warten auf die Wiederauffüllung durch einen Lieferanten, das Warten auf die Genehmigung eines Anspruchs oder das Warten in einer Warteschlange.

Standard-KI-Agents sind auf einen einzelnen Chat-Turn beschränkt, der nur wenige Sekunden dauert und rein reaktiv ist. Sie können nicht 40 Minuten warten oder autonom handeln. Um dieses Problem zu lösen, benötigen Sie einen Langzeit-Agent: ein System, das während langer Wartezeiten inaktiv bleibt, den Kontext über Prozessneustarts hinweg beibehält und automatisch fortgesetzt wird, wenn es durch einen externen Trigger aktiviert wird.

In diesem Workshop erfahren Sie, wie Sie einen Agenten für die Warteschlange für Konzerttickets erstellen. Dabei werden die wichtigsten Architekturmuster für Agents mit langer Laufzeit in Google Cloud behandelt.

Aufgaben

Was möchten Sie erstellen?

In diesem Workshop erstellen Sie einen Agenten für die Buchung von Tickets, der lange ausgeführt wird, und lernen dabei wichtige Softwaremuster für Agenten kennen:

  • Unterbrechung und Fortsetzung der Verarbeitung: Parken Sie den Aufruf eines Agenten während langer Wartezeiten und setzen Sie die Ausführung über externe Trigger fort.
  • Durable State Storage: Der Konversationsstatus und Artefakte werden mithilfe von SQLite/Cloud SQL und Cloud Storage prozessübergreifend beibehalten.
  • Staleness Guards und Idempotenz: Überprüfen Sie dynamische Daten (z. B. die Verfügbarkeit von Sitzplätzen), bevor Sie Maßnahmen ergreifen, und verwenden Sie Idempotenzschlüssel, um doppelte Transaktionen zu verhindern.
  • ADK-Workflow-Diagramme: Strukturieren Sie die Ausführung mithilfe von deterministischen Codeknoten für prozedurale Regeln und Agentenknoten für kontextbezogene Entscheidungsfindung.
  • Genehmigung durch den Nutzer: Die Ausführung wird angehalten, um explizite Ausgabenlimits vom Nutzer zu erfassen und zu speichern.
  • Cloud-Bereitstellung: Containerisieren und Bereitstellen des Agents in Cloud Run mit Cloud Scheduler- und Pub/Sub-Triggern.

Einrichten

Bereiten Sie zuerst Ihre Umgebung vor und sorgen Sie dafür, dass Sie Zugriff auf Google Cloud haben. Ich empfehle, diesen Befehl in Cloud Shell auszuführen, da dort bereits alle erforderlichen Tools vorhanden sind. Prüfen Sie einfach, ob Sie genügend Speicherplatz haben und ob mit Ihrem Konto Projekte erstellt werden können.

👉💻 Klonen Sie das Repository und führen Sie die Einrichtung aus:

cd ~
git clone https://github.com/gca-americas/monstertix
cd ~/monstertix
./setup.sh

Sie werden einmal gefragt:

  Which Google Cloud project should this workshop use?
  Find yours at https://console.cloud.google.com  (top-left picker)

  Project id [YOUR_PROJECT_ID]:

Anschließend werden die folgenden Schritte in der angegebenen Reihenfolge ausgeführt:

virtualenv

Erstellt .venv und installiert ADK 2 darin.

Anmeldung

gcloud auth application-default login, wenn Sie noch keine Anmeldedaten haben

project

verweist gcloud auf Ihr Projekt und legt es als Kontingentprojekt fest.

APIs

ermöglicht die zehn erforderlichen Schritte. aiplatform, run, cloudbuild, artifactregistry, storage, pubsub, cloudscheduler, cloudtrace, logging und sqladmin.

.env

schreibt Ihre Projekt-, Regions- und Modell-ID, sodass jedes Skript und adk web sie aufnimmt.

Modellprüfung

führt einen echten Aufruf an Gemini aus. Eine falsche Projekt- oder Modell-ID führt hier zu einem Fehler.

Ausgangsprompt

Erstellt eine Sitzung, die bereits zwei Tage alt ist. Schritt 3 wird geöffnet.

Cloud SQL

startet eine Postgres-Instanz im Hintergrund

Veranstaltungsort

stellt Ihren eigenen Mock-Ticketverkäufer in Cloud Run bereit

👉💻 Aktivieren Sie die Umgebung:

cd ~/monstertix
source .venv/bin/activate

Ihr Prompt beginnt jetzt mit (.venv).

Szenarioübersicht

In diesem Workshop-Szenario bucht Ihr Agent zwei Tickets für ein Konzert („The Midnight Signal“ in Amsterdam). Die im Verlaufsspeicher gespeicherten Einstellungen des Nutzers umfassen:

  • Es muss eine Wochenendshow sein (Sam kann an Wochentagen nicht teilnehmen).
  • Vermeiden Sie Sitzplätze im oberen Rang des Ziggo Dome (schlechte Sicht).
  • Der Zielpreis liegt bei etwa 200 $ pro Ticket, mit einem Höchstlimit von 250 $.

The Midnight Signal, auf Tour

Installation prüfen

👉💻 Ein Befehl, acht Ticks:

./verify.sh
  Checking the rig

  ✓ adk installed                      adk, version 2.6.2
  ✓ adk 2 apis                         App + ResumabilityConfig
  ✓ workflow graphs                    Workflow + node
  ✓ async sqlite drivers               sqlalchemy + aiosqlite + greenlet
  ✓ project                            your-project-id
  ✓ vertex ai                          no API keys anywhere
  ✓ seeded session                     13 events, two days old
  ✓ venue                              https://venue-you-xxxx.us-central1.run.app

  Ready. Cloud SQL is still building in the background and nothing
  before step 10 needs it.

👉 Zweiten Browsertab öffnen:

Dabei gilt:

Beschreibung

$VENUE_URL/panel

Das Bedienfeld, Ihre Tasten. setup.sh hat diese URL gedruckt und in .env eingefügt.

2. Mit Prompts zur Autonomie?

Prompts können nicht von selbst gestartet werden

Ein Prompt ist einfach Text, der bei einem Aufruf an ein KI-Modell übergeben wird. Es kann nicht von selbst ausgeführt werden, sich selbst aktivieren oder einen neuen Anruf starten.

Wenn Sie einem Agenten in seinem Prompt sagen, er solle sich in 10 Minuten wieder melden, passiert nach dem Ende der aktuellen Runde nichts. Das Modell hat keine interne Uhr oder Schleife. Damit ein Agent autonom agieren kann, muss er von etwas außerhalb des Agents geweckt und aufgerufen werden.

Wo dieser Schritt stattfindet

Rig starten

Wir beginnen mit dem Testen der Entwicklertools. Mit adk web wird die ADK-Web-UI gestartet, die Entwicklungsoberfläche, die mit dem ADK ausgeliefert wird. So können Sie mit dem Agenten, den Sie erstellen, sprechen, ohne zuerst ein Frontend zu schreiben. Außerdem sehen Sie Dinge, die in einem Chatfenster nicht angezeigt werden, z. B. jeden Tool-Aufruf und seine Argumente, den Sitzungsstatus, wenn er sich ändert, die Artefakte, die der Agent gespeichert hat, und die zugrunde liegenden Rohereignisse. In der Produktionsumgebung würden Sie sie nicht verfügbar machen. Es ist das beste Fenster in einen laufenden Agenten, während Sie lernen. In Schritt 3 erstellen Sie das Frontend, das Sie tatsächlich bereitstellen würden, und im letzten Schritt stellen Sie es bereit.

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Terminal 1: Laden Sie den Code für diesen Schritt und starten Sie die ADK-Web-UI:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 2 --force
adk web agent --port 8000 --allow_origins "*"

Mit use-solution.sh wird der fertige Code eines Schritts in agent/ kopiert, wo Sie arbeiten. Jeder Schritt beginnt damit und es wird ausgegeben, welche Dateien geändert wurden, damit Sie wissen, was Sie lesen müssen.

Das ist Ihre Ausgangssituation:

Datei

Beschreibung

agent.py

Der KI-Agent – ein Name, ein Modell, eine Anleitung, eine Liste von Tools

tools.py

search_events und get_seatmap

venue.py

ein HTTP-Client für den Veranstaltungsort. Nichts Interessantes

config.py

welches Modell verwendet werden soll, und eine Prüfung, ob Ihre Anmeldedaten funktionieren.

. ./set_env.sh aktiviert die virtuelle Umgebung, lädt Ihr Projekt und Modell und exportiert $WORKSHOP – den absoluten Pfad zu ~/monstertix. Führen Sie den Befehl in jedem Terminal aus, das Sie öffnen. Es wird ausgegeben, was festgelegt wurde:

  folder   /home/you/monstertix
  project  your project id
  model    gemini-2.5-flash (We'll avoid using 3.x for now to avoid high usage demands)
  venue    https://venue-yourname-xxxx.run.app

👉 Erste Browsertabs öffnen:

Dabei gilt:

Beschreibung

ADK-Web-UI

localhost:8000 – Ihr Agent sowie die Tabs „Status“, „Ereignisse“ und „Artefakte“

Mit ihr sprechen

👉✨ Geben Sie in der ADK-Webbenutzeroberfläche Folgendes ein:

I want to see The Midnight Signal. I'm in Amsterdam, going with Sam, budget around $200 each.

Das Tool durchsucht die Tour, liest Sitzpläne und berücksichtigt Ihr Budget und Ihr Unternehmen. Das ist ein wirklich leistungsfähiger Assistent. Wenn der Vorverkauf um 10:00 Uhr beginnt, wird der Agent jedoch nicht automatisch ausgeführt, da kein Prozess oder Hintergrundtrigger aktiv ist.

Lesen, was gerade passiert ist

Die Mitte des Bildschirms ist nicht nur ein Chatprotokoll. Jeder Schritt des Zuges ist nummeriert, einschließlich der Tool-Aufrufe:

  ▸ I want to see The Midnight Signal. I'm in Amsterdam...   ← user input
  ⚡ search_events({"city": "Amsterdam"})                     ← tool execution
  ✓ search_events                                            ← venue API response
    Two Amsterdam shows: Tuesday 10 Nov and Saturday 14 ...   ← model response

👉 Klicken Sie auf eine Zeile mit ⚡. Im linken Bereich sehen Sie die Argumente, die das Modell ausgewählt hat. Klicken Sie darunter auf das ✓, um den zurückgegebenen JSON-Code zu sehen.

Die Informationen zur Tour sind nicht in den Trainingsdaten oder im Prompt des Modells enthalten. Der Agent hat diese Daten durch Ausführen des Tools search_events abgerufen, mit dem die Venue API abgefragt wurde.

Gerade ausgeführten Code öffnen

Sie haben gesehen, dass es funktioniert, und den Trace gesehen. Sehen Sie sich nun den Code an.

agent/concert/
├── agent.py     the agent — about twenty lines
├── tools.py     what it can do
├── venue.py     an httpx wrapper, nothing interesting
└── config.py    which model, and a credentials check

👉💻 Öffne agent/concert/agent.py. Der gesamte KI-Agent befindet sich am Ende der Datei:

INSTRUCTION = """
You help someone plan a trip to see a band on tour.

You can search tour dates and read seat maps. Be concrete: name the show, the
city, the section, and the price...
"""

root_agent = Agent(
    name="concert",
    model=MODEL,
    instruction=INSTRUCTION,
    tools=[search_events, get_seatmap],
)

Einen Namen, ein Modell, eine Beschreibung des Verhaltens des KI-Agents und eine Liste von Python-Funktionen. Das ist alles, was ein Agent ist. adk web hat es gefunden, weil die Datei agent.py heißt und die Variable root_agent.

👉💻 Jetzt agent/concert/tools.py:

def search_events(city: str = "", weekday: str = "") -> dict:
    """List tour dates. Call with no arguments to see the whole tour.

    This venue sells one artist and one tour, so there is nothing to search by
    name — never ask the user which artist they mean.

    Args:
        city: Optional. Filter to one city, e.g. "Amsterdam".
        weekday: Optional. Filter to one weekday, e.g. "Saturday".
    """
    return venue.get("/events", city=city, weekday=weekday)

Eine gewöhnliche Funktion. Kein Decorator, keine Registrierung, kein Schema zum Schreiben. Das ADK liest die Signatur und den Docstring und erstellt die Tool-Definition, die das Modell sieht. Deshalb ist der Docstring so wichtig und deshalb hat sich das Verhalten des Modells geändert, als ein Argument gelöscht wurde.

Das Modell führt Ihren Code nie aus. Als Sie search_events({"city": "Amsterdam"}) gesehen haben, hat das Modell nach diesem Aufruf gefragt. Das ADK hat die Funktion ausgeführt, das Ergebnis zurückgegeben und das Modell hat einen Satz dazu geschrieben. Die Schleife lautet:

👉 Mit den Symbolen ganz links können Sie ändern, was im Bereich angezeigt wird. Das zweite ist das Agent-Diagramm. Wenn Sie es ausprobieren, sollten Sie zwei Tools sehen:

Agentengraph mit den Tools „search_events“ und „get_seatmap“

Sag ihm also, dass es nicht schlafen soll.

Was würden Sie tun, um das Problem zu beheben? Greife zuerst nach dem, was die meisten Leute als Erstes nehmen. Rufen Sie INSTRUCTION aus Schritt 1 auf, den Text, der dem Modell bei jedem Zug übergeben wird, und schreiben Sie ihn neu.

👉💻 Öffne agent/concert/agent.py. Unter INSTRUCTION finden Sie ein zweites, das bereits für Sie geschrieben wurde. Sie können die Antwort auch selbst ändern:

PROACTIVE_INSTRUCTION = """
You are a proactive ticket-buying assistant.

Monitor the presale for The Midnight Signal. The moment it opens at 10:00 on
Tuesday, buy two tickets for the Amsterdam Saturday show — do not wait to be
asked, and do not wait for me to say anything. Act on your own.

Keep checking until the tickets are bought.
"""
# agent/concert/agent.py
root_agent = Agent(
    name="concert",
    model=MODEL,
    instruction=PROACTIVE_INSTRUCTION,     # ← was INSTRUCTION
    tools=[search_events, get_seatmap],
)

👉💻 Starte in Terminal 1 neu:

cd ~/monstertix
adk web agent --port 8000 --allow_origins "*"

👉✨ Erkläre den Plan so, wie du ihn einer Person erklären würdest:

I want to see The Midnight Signal in Amsterdam with Sam. The presale opens at 10:00 on Tuesday. Grab us two tickets the moment it opens.

Es wird zustimmen und es wird so klingen, als ob es das auch so meint.

👉 Warten Sie nun. Geben Sie nichts anderes ein. Podiumsdiskussion ansehen

Das kann eine Weile dauern. ES PASSIERT NICHTS!

Warum dieser Prompt nie funktionieren konnte

Lies es noch einmal mit frischen Augen. Monitor. Sobald es geöffnet wird. Sehen Sie weiterhin regelmäßig nach. Jede dieser Situationen beschreibt etwas, das im Laufe der Zeit passiert.

instruction= ist ein String, der dem Modell als Teil der Anfrage übergeben wird, wenn jemand eine Nachricht sendet. Zwischen den Zügen wird nichts gelesen.

# agent/concert/agent.py
root_agent = Agent(
    instruction=PROACTIVE_INSTRUCTION,   # read on invocation
    ...                                  # ...and there was no invocation
)

Eine Anweisung kann die Funktion, in der sie geschrieben ist, nicht aufrufen.

Fehlerhafte Prompt-Variationen

Der Versuch, die autonome Ausführung über Prompt-Anweisungen zu erzwingen, schlägt unabhängig von der Formulierung fehl. Häufige Versuche mit Prompts, z. B.:

  • „Stell einen Timer für Dienstag um 10:00 Uhr und kauf dann die Tickets.“
  • „Prüfe alle fünf Minuten, ob der Vorverkauf begonnen hat.“
  • „Autonom handeln, ohne auf Nutzereingaben zu warten.“

Alle schlagen aus demselben Grund fehl: Prompts haben Lesezugriff, wenn ein Aufruf erfolgt. Ohne externen Aufrufer wird zwischen den Zügen kein Code ausgeführt.

3. Ereignisgesteuerte Inaktivität

Agents bleiben nicht für immer aktiv

Wenn ein Prozess kontinuierlich ausgeführt wird, wird unnötig Arbeitsspeicher verbraucht, es entstehen Kosten und der Prozess stürzt ab, wenn Ihr Laptop in den Ruhemodus wechselt oder neu gestartet wird.

Anstatt einen Agenten während des Wartens ununterbrochen auszuführen, sollte ein Agent mit langer Laufzeit vollständig inaktiv werden. Ein externes Ereignis (z. B. ein Timer, ein Webhook oder eine Nachricht) aktiviert den Agent nur, wenn tatsächlich etwas zu tun ist.

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Code für diesen Schritt laden:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 3 --force

Sehen Sie sich an, was installiert wurde: monstertix/, der Server, die Uhr und noch etwas, das Sie noch nicht gesehen haben. Prüfen Sie, ob agent/ unverändert geblieben ist, da sich in diesem Schritt nichts am Agenten ändert. Das ist der Punkt.

Datei

Beschreibung

monstertix/server.py

etwas Neues. Ein Webserver mit einem Runner dahinter

monstertix/clock.py

etwas Neues. Etwas, das die Zeit kennt und den Server aufruft

Die beiden Hälften werden in zwei Dateien gespeichert, die in der Cloud verbleiben: Trigger und Auslöser werden getrennt.

wird in Schritt 10 zu

Trigger

einen Endpunkt, über den der Agent ausgeführt werden kann

Cloud Run

der Auslöser

etwas mit einer Uhr, die das aufruft

Cloud Scheduler

Der Trigger: Ihr eigener Webserver

Ja, ein Webserver. Etwas muss auf Anfragen warten, bevor es aufgerufen werden kann. Die ADK-Web-UI aus den vorherigen Schritten ist eine Entwicklungsschnittstelle, die zufällig einen Agenten ausführen kann.

👉💻 monstertix/server.py öffnen:

from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from concert.agent import root_agent

session_service = InMemorySessionService()
runner = Runner(
    agent=root_agent,
    app_name="concert",
    session_service=session_service,
)

@app.post("/wake")
async def wake(payload: dict = Body(default={})):
    session_id = f"wake-{uuid.uuid4().hex[:8]}"          # brand new, every time
    await session_service.create_session(...)

    async for event in runner.run_async(
        user_id=USER_ID, session_id=session_id,
        new_message=types.Content(role="user",
                                  parts=[types.Part(text=payload["message"])]),
    ):
        ...

Eine Agent wird nicht automatisch ausgeführt. Es handelt sich um eine Beschreibung: einen Namen, ein Modell, eine Anleitung und eine Liste von Tools. Es muss etwas geben, das eine eingehende Nachricht entgegennimmt, die richtige Sitzung findet, dem Modell die bisherige Unterhaltung übergibt, alle Tools ausführt, die es anfordert, die Ergebnisse zurückgibt, so lange wiederholt, bis keine Tools mehr aufgerufen werden, und jeden Schritt in den Speicher schreibt. Das ist die Runner.

Was ein Runner in Bezug auf Ihren Agent tut

Die ADK-Web-UI hat eine Standardeinstellung für Sie. Hier erstellen Sie es selbst. Das ist der einzige Unterschied zwischen der ADK-Web-UI und einer Bereitstellung.

👉💻 Terminal 2 – starten Sie es:

Starten Sie den Runner.

cd ~/monstertix
. ./set_env.sh
python -m monstertix.server
[server] agent concert · gemini-x-flash
[server] listening on http://xxxx_URL/wake
[server] nothing will happen until something calls it. Ctrl-C to stop.

Lies die letzte Zeile vor. Der Trigger wird ausgeführt und der Agent wird geladen, aber es passiert nichts. Trennen Sie die beiden Ideen: erreichbar sein und angerufen werden.

Und eine Haustür, wenn wir schon dabei sind

adk web ist zwar ein großartiges Entwicklungstool, sollte aber nicht in der ausgelieferten Version enthalten sein. Sie sollten sie nicht aktivieren, wenn Sie die App in der Produktion bereitstellen, und Sie würden einen Kunden nicht darauf verweisen, wenn Sie es doch tun. Derselbe Server stellt also auch eine Seite bereit.

👉 Öffnen Sie http://127.0.0.1:8090 in einem Browser – dieselbe Adresse.

Das ist MonsterTix. Ein Chatfenster, ein Name in der Ecke, zwei Schaltflächen. Es wird ein POST-Vorgang an /wake gesendet und die Antwort gerendert. Der Code würde unverändert für jeden Endpunkt funktionieren, der auf dieselbe Weise antwortet.

Auf der Seite

Was darunter liegt

die Unterhaltung

POST /wake, eine Nachricht nach der anderen

+ Neue Sitzung

eine neue Sitzungs-ID – neue Unterhaltung und später ein neues Budget

Speicher zurücksetzen

löscht jede Sitzung und die Datei mit den gemerkten Informationen. Sie werden zuerst gefragt und es wird Ihnen genau angezeigt, wo sich dieser Status befindet.

👉✨ Sagen Sie etwas zu What Amsterdam dates are there? und sehen Sie sich Terminal 2 an. Dasselbe Runner, derselbe Agent, dieselben Logzeilen. Nur der Anrufer hat sich geändert.

Der Triggerer – etwas, das die Zeit kennt

👉💻 Öffne monstertix/clock.py. Sehen Sie sich den Code an.

time.sleep(args.delay)                                    # wait
while True:
    client.post(f"{TRIGGER_URL}/wake",                    # knock
                json={"message": args.message})
    if not args.every:
        return                                            # once, unless told otherwise
    time.sleep(args.every)                                # or again, forever

Das ist ein ganz einfacher Mechanismus: schlafen, POST, vielleicht wiederholen. Jeder von Ihnen verwendete Scheduler macht dasselbe, plus Wiederholungen, Zeitzonen und eine Garantie, dass er einen Neustart übersteht.

👉💻 Terminal 3 – einmal auslösen, in 10 Sekunden:

cd ~/monstertix
. ./set_env.sh
python -m monstertix.clock --in 10
[clock] target   http://127.0.0.1:8090/wake
[clock] first fire in 60s, once only
[clock] go and watch the other terminal — do not touch the keyboard

👉 Hören Sie jetzt mit dem Tippen auf. mühelos. Sehen Sie sich das Terminal 2 an.

Eine Minute später, ohne dass jemand an der Tastatur sitzt:

[clock]  firing → POST /wake
[server] woken · session wake-23930983
[server]   called search_events
[server]   called purchase
[server]   said   I've purchased 2 tickets for The Midnight Signal in
                  Amsterdam on Saturday, section A. Your total is 420.
[clock]  done. the agent ran and you were not involved.

Das ist die Antwort auf Schritt 2. Etwas außerhalb des Agent hat ihn aufgerufen. Dieses Etwas war ein sleep und eine HTTP-Anfrage.

👉💻 Ctrl-C, wenn du es dir mehrmals angesehen hast. Verwenden Sie zuerst --every 30, wenn Sie die Wiederholung sehen möchten.

Lies jetzt, was dort steht.

„Ich habe zwei Tickets für ‚The Midnight Signal‘ in Amsterdam, Bereich A, für 420 $ gekauft.“

👉🔴 Prüfe das Panel für den Veranstaltungsort. Dort sollte das gekaufte Ticket angezeigt werden.

Was ist also noch falsch?

Es hat etwas gekauft. Sehen Sie sich an, was das Modell erraten musste. Es hat Amsterdam und Abschnitt A ausgewählt, weil das die ersten Ergebnisse waren, die es gefunden hat. Außerdem haben Sie dem KI-Agenten gesagt, dass er die Tickets direkt kaufen soll. Der KI-Agent weiß nicht, dass Sam abends nicht kann. Ein Agent, der um 3 Uhr morgens auf Vermutungen reagiert, ist schlimmer als ein Agent, der nichts tut.

Das Aufwachen ist der einfache Teil. Ein Agent, der ohne alles aufwacht – ohne Verlauf, ohne Einstellungen, ohne Zuhörer – ist es noch nicht wert, geweckt zu werden.

4. Kontextlebensdauer verwalten

Informationen sind zu unterschiedlichen Zeiten an unterschiedlichen Orten verfügbar

Ein Agent speichert Fakten an verschiedenen Stellen: im Chatverlauf, im Sitzungsstatus, in Dateien (Artefakten) oder im Langzeitgedächtnis des Nutzers. Jeder Ort ist unterschiedlich lange verfügbar.

Wenn Sie Informationen an der falschen Stelle eingeben:

  • Es verschwindet zu schnell: Wichtige Details werden gelöscht, wenn der Chat beendet wird.
  • Es bleibt zu lange bestehen: Temporäre Fakten verunreinigen das Langzeitgedächtnis und verwirren zukünftige Unterhaltungen.

Sie müssen jede Information dem richtigen Speicherort und der richtigen Lebensdauer zuordnen.

Code laden

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN.

👉💻 Führen Sie im Terminal 1 Folgendes aus:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 4 --force

Was hat sich seit dem letzten Schritt geändert?

Datei

Was hat sich geändert?

tools.py

note_companion wurde hinzugefügt. get_seatmap speichert den Sitzplan jetzt in einer Datei

memory.py

etwas Neues. Unser eigener Speicher

agent.py

Drei weitere Tools in der Liste und die Anweisung, dass der Agent sie verwenden soll

👉💻 Starten Sie den Agent in Terminal 1 neu:

cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

Zwei Flags, die Sie noch nicht verwendet haben:

Flag

Beschreibung

session_service_uri

Das ADK schreibt jede Nachricht, jeden Tool-Aufruf und jede Statusänderung als Zeilen in die Datenbank (SQLite-Datenbank).

artifact_service_uri

Dateien, die der Agent speichert.(Dateien)

Ohne sie speichert das ADK alles im Arbeitsspeicher und verwirft es, wenn Sie den Prozess beenden. Diese beiden Einstellungen sorgen dafür, dass die Änderungen beibehalten werden.

Die Datenbank sessions.db enthält eine simulierte frühere Unterhaltung (two-days-ago) mit 13 aufgezeichneten Ereignissen:

you    We're thinking about seeing The Midnight Signal. Me, Sam, and maybe Priya.
you    Sam can't do weeknights though, bailed on every single one.
you    Priya's out, she's away that weekend. Just me and Sam.
agent  Two it is. Still Saturday the 14th, still Amsterdam.
you    You said the upper bowl at Ziggo was bad last time?

Eine Erinnerungsdatei mit drei vergangenen Buchungen, memory/userx.md, die Sie in einem Editor öffnen können:

- Sam bails on weeknights. Every weeknight show we booked, Sam cancelled.
- Hates the upper bowl at Ziggo Dome, "couldn't see a thing" from section B.
- Comfortable spend is around $200 per ticket. $250 is the hard ceiling.
- Always books two seats, always together.

Deshalb sind die bernsteinfarbenen Zeilen an Wochentagen so wichtig. Eine Vorstellung am Abend unter der Woche ist 10 $ pro Platz günstiger, es sind noch bessere Plätze verfügbar und sie steht ganz oben auf der Liste. Trotzdem ist sie die falsche Antwort für diese Person. Der einzige Grund, warum der KI-Agent das wissen kann, ist, dass er eine Datei nach der letzten Buchung geschrieben hat. Ob diese Tatsache bestehen bleibt, wird in den Schritten 4 und 5 geprüft.

Die drei neuen Tools

Die drei Tools und wo sie jeweils schreiben

note_companion erfasst, wer kommt und was sie einschränkt:

# agent/concert/tools.py
def note_companion(name: str, constraint: str, tool_context: ToolContext) -> dict:
    prefs = dict(tool_context.state.get("user:prefs", {}))
    prefs[name] = constraint
    tool_context.state["user:prefs"] = prefs
    return {"companions": prefs}

Füge sie hinzu, weil „Sam kann abends unter der Woche nicht“ auch nächste Woche noch zutreffen muss. Schreiben Sie es in user:prefs, damit das passiert, und sehen Sie zu, wie der user:-Teil des Namens die Arbeit erledigt. Dazu kommen wir unten noch einmal.

recall

und

remember: Eine Textdatei, memory/userx.md, wird gelesen und geschrieben. Halte sie einen Moment lang.

👉 Sehen Sie sich nun die beiden Stellen an, an denen der Agent seinen Kontext speichert:

Standort

Inhalt

Bundesland

den Speicher dieser Sitzung als Schlüssel/Wert-Paare – hier sind Sie in Schritt 4

Artefakte

Dateien, die der Agent gespeichert hat, die aber nie in den Prompt aufgenommen werden

Änderungen in ansehen

get_seatmap

. Früher wurde die gesamte Sitzplatzkarte zurückgegeben. Die Sitzplatzkarte wird nun in einer Datei gespeichert und es wird eine kurze Zusammenfassung zurückgegeben:

# agent/concert/tools.py
async def get_seatmap(event_id: str, tool_context: ToolContext) -> dict:
    seatmap = venue.get(f"/events/{event_id}/seatmap")       # about 6 KB of JSON

    part = types.Part(inline_data=types.Blob(
        mime_type="application/json",
        data=json.dumps(seatmap, indent=2).encode()))
    await tool_context.save_artifact(f"seatmap_{event_id}.json", part)

    summary = {"event_id": ..., "sections": [...], "captured_at": ...}
    tool_context.state["temp:seatmap"] = summary
    return summary

Wir erklären, warum diese Änderung wichtig ist.

Die Nutzlast jeder Tool-Antwort wird an den Unterhaltungsverlauf angehängt und in allen nachfolgenden Zügen an das Modell zurückgesendet. Wenn Sie große JSON-Objekte (z. B. einen Sitzplan mit 6 KB) zurückgeben, erhöht sich der Tokenverbrauch bei jedem nachfolgenden Zug.

Speichern Sie es stattdessen in einer Datei und bezahlen Sie einen Dateinamen. Lassen Sie den Kundenservicemitarbeiter mit load_artifact nach den Details fragen, wenn er sie benötigt. In den meisten Fällen wird das jedoch nicht der Fall sein.

Speicherort der Datei: der Artefaktdienst

Sehen Sie sich die Zeile in get_seatmap noch einmal an, in agent/concert/tools.py:

await tool_context.save_artifact(f"seatmap_{event_id}.json", part)

tool_context stammt aus dem ADK, das es als Argument an Ihr Tool übergibt, und save_artifact ist eine Methode dafür. In dieser Zeile übergibt Ihr Tool die Byte an das ADK.

Das ADK schreibt sie dann mit dem Artefaktdienst, mit dem es gestartet wurde. Sie haben das mit dem Flag ausgewählt:

--artifact_service_uri="file://$WORKSHOP/artifacts"

Das ADK bietet drei Speicherorte:

URI

Wo die Byte landen

memory://

RAM Verschwindet, wenn Sie den Vorgang beenden. Dies ist die Standardeinstellung, wenn Sie kein Flag übergeben.

file://...

einen Ordner auf der Festplatte. Was Sie derzeit verwenden

gs://bucket

Daten in Cloud Storage zu übertragen. Was der bereitgestellte Agent im letzten Schritt verwendet

👉💻 Hier wählen wir Dateien aus:

ls -R ~/monstertix/artifacts

Wenn Sie die Daten von Ihrem Laptop in Cloud Storage verschieben möchten, ändern Sie file:// in gs://. (Das sehen wir uns in Schritt 10 an.)

Unser Erinnerungsspeicher

Speicher als Schnittstelle mit einer Datei dahinter

Das ADK bietet eine Schnittstelle für den Langzeitspeicher namens BaseMemoryService. Sie hat zwei Methoden:

# google.adk.memory.BaseMemoryService — ADK's interface, not your file
async def add_session_to_memory(session)                   # take a conversation in
async def search_memory(*, app_name, user_id, query)       # hand memories back

Das ADK enthält drei Implementierungen:

Klasse

URI

Voraussetzungen

InMemoryMemoryService

memory://

Es geschieht nichts. Verliert alles, wenn Sie den Vorgang beenden

VertexAiRagMemoryService

rag://

ein Vertex AI-RAG-Korpus

VertexAiMemoryBankService

agentengine://

eine Agent Engine-Ressource

Wir schreiben unsere eigenen für diesen Workshop. Schließen Sie InMemoryMemoryService aus, da dort alles vergessen wird, wenn Sie den Vorgang beenden. Schließen Sie auch die anderen beiden aus, da sie Cloud-Ressourcen benötigen, deren Erstellung mehrere Minuten dauert. Wählen Sie stattdessen eine Datei aus, da Sie sie im Voraus mit Daten füllen und in einem Editor lesen können. memory.py ist also Ihre eigene Implementierung. Wir speichern alles in einer Markdown-Datei:

class MarkdownMemoryService(BaseMemoryService):

    async def search_memory(self, *, app_name, user_id, query) -> SearchMemoryResponse:
        path = self._path(user_id)                      # memory/userx.md
        return SearchMemoryResponse(memories=[
            MemoryEntry(content=types.Content(
                role="user", parts=[types.Part(text=path.read_text())]))])

SearchMemoryResponse und MemoryEntry sind ADK-Typen und Memory Bank gibt dieselben Objekte zurück. Hinter einem steht ein verwalteter Dienst. Unseres hat eine Datei.

Sie können eine Datei aus einem bestimmten Grund auswählen: Sie können sie öffnen und lesen. Das gesamte Langzeitgedächtnis Ihres Agents ist eine Textseite.

Wohin richten Sie sie also?

In dieser Workshop-Implementierung werden zwei der drei Dienste in der Befehlszeile ausgewählt. Für die dritte haben wir aus dem oben genannten Grund eine eigene Implementierung vorgenommen:

Dienst

Wo Sie die Einstellung vornehmen

Dieser Workshop

Sitzung

--session_service_uri auf adk web

sqlite+aiosqlite:///$WORKSHOP/sessions.db

artifact

--artifact_service_uri auf adk web

file://$WORKSHOP/artifacts

Arbeitsspeicher

nirgends in der Befehlszeile

MEMORY_DIR, in agent/concert/memory.py lesen

memory.py erstellt den Dienst also selbst aus einer Umgebungsvariable:

# agent/concert/memory.py
MEMORY_DIR = pathlib.Path(os.environ.get("MEMORY_DIR", "./memory"))
MEMORY_USER = os.environ.get("MEMORY_USER", "userx")
memory_service = MarkdownMemoryService(MEMORY_DIR)

. ./set_env.sh exportiert beides. Mit „Get MEMORY_DIR“ wird festgelegt, wo der Agent seinen Speicher liest, und mit MEMORY_USER wird die Datei bestimmt.

Orte finden

Sie haben jetzt vier Orte, an denen Sie eine Fakteninformation unterbringen können. Sehen Sie sich alle vier an.

Zuerst Ihre eigene Sitzung

👉 Klicken Sie in der oberen Leiste auf Neue Sitzung.

👉✨ Sag drei Dinge nacheinander:

Sam can't do weeknights, by the way.

Neue Sitzung, erster Zug

Show me the seat map for the Amsterdam Saturday show.

Neue Sitzung, zweiter Zug

Actually, hold that thought — what else is on in Amsterdam?

Diese dritte Nachricht ist wichtig. Dadurch wird der Zug beendet, in dem die Sitzplatzkarte gelesen wurde. Wenn ein Zug beendet wird, verschwindet temp:.

Drei Dinge wurden an drei verschiedenen Orten gespeichert:

Was wurde gespeichert?

Welches Tool hat

Wo ist es hin?

1

Sam kann abends unter der Woche nicht

note_companion

user:prefs, im Sitzungsstatus

2

den vollständigen Sitzplan (6 KB)

get_seatmap über save_artifact

den Artifact Service

3

eine einzeilige Zusammenfassung des Sitzplans

get_seatmap, in temp:seatmap

nirgends

👉 Jetzt prüfen Der Tab State (Status) und der Tab Artifacts (Artefakte) befinden sich links von adk web.

Tab „Bundesstaat“

Tab „Artefakte“

Auf dem Tab State (Status) sehen Sie, dass temp:seatmap ausgelassen wurde. Zustandsschlüssel mit dem Präfix temp: sind nur während des aktiven Aufrufs vorhanden und werden vom ADK automatisch entfernt, bevor der Sitzungsstatus in der Datenbank gespeichert wird.

Danach die ältere Unterhaltung

👉 Wechseln Sie oben im Drop-down-Menü für die Sitzung zu two-days-ago.

Diese Sitzung enthält einen vorab ausgefüllten Unterhaltungsverlauf in der Datenbank.

Sitzung vor zwei Tagen

👉 Sehen Sie sich den Ereignisstream (mittlere Spalte) an. Ältere Abbiegungen wurden durch einen zusammengefassten Ereignisblock ersetzt.

Verdichtete Züge

👉 Sehen Sie sich den Tab Status an. Dort befinden sich drei Arten von Schlüsseln:

target_event_id   ms-ams-01                      ← plain: this booking only
party_size        2                              ← plain: this booking only
user:prefs        {"excluded_weekdays": [...]}   ← the person, every session

Mit dem Präfix user: (z.B. user:prefs) wird der Status für alle Sitzungen eines bestimmten Nutzers festgelegt. Im Gegensatz dazu Schlüssel ohne Präfix (

target_event_id

,

party_size

) stellen den einfachen Sitzungsstatus dar, der streng auf die aktive Sitzung beschränkt ist und gelöscht wird, wenn die Sitzung endet.

👉 Vergleichen Sie nun die beiden Tabs „Status“. Während user:prefs und gespeicherte Artefakte erhalten bleiben, ist temp:seatmap in beiden Sitzungen nicht vorhanden. Status-Schlüssel mit dem Präfix temp: sind ausschließlich temporär und werden nie in der Datenbank gespeichert.

Dann die Datei

👉💻 Im Editor öffnen:

cloudshell edit ~/monstertix/memory/userx.md

Drei Buchungen, die bis in den Januar zurückreichen. Der Agent liest sie, wenn er recall() aufruft. Sie ist auch dann noch vorhanden, wenn die Sitzungsdaten verloren gegangen sind.

Die vier Orte

Wo befindet sich die Funktion?

Wird die nächste Nachricht übermittelt?

Wird ein Neustart überstanden?

Bleibt die Einstellung erhalten, wenn die Sitzung gelöscht wird?

temp:seatmap

no

Nein

Nein

party_size – einfacher Sitzungsstatus in two-days-ago

Ja

Ja

Nein

user:prefs

Ja

Ja

ja

memory/userx.md

Ja

Ja

Ja, und sie bleibt auch nach dem Löschen der Datenbank erhalten.

ein Artefakt

Ja

Ja

abhängig vom Dienst

In dieser Tabelle geht es um die Container, nicht um ihren Inhalt.memory/userx.md übersteht alles und enthält nur das, was remember()dort hineingetan hat. Ihre aktuelle Unterhaltung wird nicht in den Kontext aufgenommen. Dass eine Datei eine Sitzung überdauert, ist also nicht dasselbe wie wenn sich der Agent daran erinnert, worüber Sie gerade gesprochen haben. In Schritt 5 wird dieser Unterschied deutlich.

Fünf Orte, an denen Fakten gespeichert werden können, und wie lange sie jeweils verfügbar sind

Was der Zustand ist

Stellen Sie sich den Zustand als Dictionary vor, das mit der Sitzung übertragen wird. Sie können die Notiz über tool_context.state in Ihren Tools lesen und schreiben lassen und das ADK speichert sie zusammen mit der Unterhaltung.

Das ADK weist den Präfixen die Bedeutung zu. Sie werden nirgends definiert. Das ADK liest den ersten Teil des Schlüsselnamens und entscheidet anhand dessen, wohin der Wert geht und wie lange er gültig ist:

Ein Schlüssel namens

ADK speichert sie

Dauer

party_size

mit der Sitzung

bis die Sitzung gelöscht wird

user:prefs

gegen den Nutzer

in jeder Sitzung, die der Nutzer hat

app:tour_id

gegen die App

für alle Nutzer

temp:seatmap

nirgends

ein Aufruf

Ein Aufruf ist ein einzelner Anfrage-Antwort-Zyklus, der alle Modellüberlegungen, die Ausführung von Tools und die endgültige Antwort umfasst.

Wenn ein Aufruf beendet wird, schreibt das ADK den neuen Status in die Datenbank und entfernt dabei alle temp:-Schlüssel.

Eine Sache ist noch ungeklärt

In two-days-ago haben Sie eine Zusammenfassung gesehen, die an der Stelle angezeigt wurde, an der sich früher die ersten Züge einer Person befanden. Das ADK hat die Zusammenfassung automatisch erstellt, als die Unterhaltung lang genug wurde.

Fakten haben eine begrenzte Gültigkeit

5. Kontextverlust und Verdichtung

Zusammenfassungen vergessen die Details

Wenn eine Unterhaltung zu lang wird, fasst das System ältere Nachrichten zusammen, um Speicherplatz und Tokens zu sparen.

Zusammenfassungen eignen sich gut, um den allgemeinen Tenor zu erfassen und Token zu sparen.

Die Verdichtung erfolgt in der Sitzung. Memory ist ein separater Speicher

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Zum Code dieses Schritts wechseln:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 5 --force

Was hat sich seit dem letzten Schritt geändert?

Datei

Was hat sich geändert?

agent.py

Der Agent ist in App eingeschlossen und die Komprimierung ist aktiviert. Ein zweiter Agent, budget_split, führt Ticketberechnungen durch.

Alles andere

nicht geändert

agent.py endet nicht mehr mit root_agent. Sie endet so:

app = App(
    name="concert",
    root_agent=root_agent,
    events_compaction_config=EventsCompactionConfig(
        compaction_interval=3,   # summarise after every 3 turns
        overlap_size=1,          # re-read 1 turn either side when summarising
    ),
)

adk web sucht zuerst nach einer Variablen namens app und greift dann auf root_agent zurück. Schließen Sie den Agenten in App ein, wenn Sie etwas benötigen, das sich auf die gesamte Anwendung und nicht auf einen einzelnen Agenten bezieht, z. B. die Komprimierung hier und die Wiederaufnahmefähigkeit in den nächsten Schritten.

compaction_interval=3 bedeutet, dass ADK nach jeweils drei Zügen zusammenfasst. Ein Turn ist, wenn Sie etwas sagen und der Agent seine Antwort beendet, unabhängig davon, wie viele Tools er auf dem Weg dorthin aufgerufen hat. Drei ist eine kleine Zahl, daher wird die Benachrichtigung während eines Workshops ausgelöst. Eine echte App würde eine viel größere Zahl verwenden.

overlap_size=1 bedeutet, dass jede Zusammenfassung eine Runde früher beginnt als unbedingt nötig.

Das ADK fasst die Züge 1, 2 und 3 zusammen. Drei Züge später fasst es den nächsten Batch zusammen. Mit overlap_size=1 beginnt die zweite Zusammenfassung wieder bei Zug 3 und nicht bei Zug 4:

Zwei Zusammenfassungen, die sich um eine Runde überschneiden

Beachten Sie, dass Runde 3 zweimal zusammengefasst wird. Das ist Absicht. Wenn Sie in Runde 3 eine Frage gestellt haben und der Kundenservicemitarbeiter in Runde 4 geantwortet hat, wird bei einer sauberen Aufteilung die Frage in einer Zusammenfassung und die Antwort in der anderen platziert. Beide sind dann verwirrend. Das Wiederholen einer Äußerung ist eine kostengünstige Versicherung gegen das Abschneiden eines Gedankens in der Mitte.

Legen Sie beide Felder fest. Für beide gibt es im ADK keinen Standardwert.

Kompaktierung und Ihr Speicherdienst treffen sich nie

Das sollte explizit erwähnt werden, da die beiden Begriffe ähnlich klingen, die Systeme aber nicht.

EventsCompactionConfig befindet sich auf der App in agent.py, neben root_agent. Es ist eine Eigenschaft der Anwendung, nicht eines Dienstes:

# agent/concert/agent.py
app = App(
    name="concert",
    root_agent=root_agent,
    events_compaction_config=EventsCompactionConfig(
        compaction_interval=3,
        overlap_size=1,
    ),
)

Bei einer Zusammenfassung wird die Unterhaltung neu formuliert. Die Datei wird nicht neu geschrieben.

Das ist der einzige Grund, warum remember() als bewusster Aufruf existiert. Alles, was im Transkript verbleibt, wird schließlich als Paraphrase gelesen, die jemand anderes geschrieben hat. Alles, was in die Datei geschrieben wird, wird morgen und nächste Woche genau so gelesen, wie es gespeichert wurde. Am Ende dieses Schritts sehen Sie, wie sich der Unterschied auswirkt.

Wo wird die Zusammenfassung gespeichert?

In sessions.db wird in der Tabelle events ein Verdichtungsdatensatz in eine Ereigniszeile geschrieben. Er hat einen Zeitstempel wie alle anderen Turn-Ereignisse.

Ein normales Ereignis enthält das Feld content und die Zusammenfassung befindet sich unter actions.compaction.

👉💻 In Ihrer eigenen Datenbank ansehen:

cd ~/monstertix
. ./set_env.sh
sqlite3 sessions.db \
  "select json_extract(event_data,'\$.actions.compaction.compacted_content.parts[0].text')
   from events where event_data like '%compaction%';"

So sieht die Form des Ereignisses aus:

events row
└── event_data  (JSON)
    ├── id, author, timestamp, invocation_id      ← like every other event
    ├── content                                    ← ABSENT on this one
    └── actions
        └── compaction
            ├── start_timestamp                    ← the window it replaced
            ├── end_timestamp
            └── compacted_content.parts[0].text    ← the summary itself

Der Ereignisstream bezieht jedes Ereignis aus seinem Inhalt. Die Zusammenfassung in der Datenbank wird bei jedem Zug an das Modell gesendet.

Der zweite Agent

budget_split ist ein separater Agent und kein Tool:

# agent/concert/agent.py
budget_split = Agent(
    name="budget_split",
    model=MODEL,
    include_contents="none",      # sees none of the conversation
    instruction="You do ticket arithmetic and nothing else...", #just the budget
    output_key="budget_plan",     # its answer goes into state
)

root_agent = Agent(
    ...,
    tools=[..., AgentTool(agent=budget_split)],
)

Warum ein KI-Agent und nicht eine einfache Funktion? Weil für die Arbeit ein Modell erforderlich ist. „Welche dieser Abschnitte passen zu einem Budget von 200 € für vier Personen und wie hoch ist die Gesamtsumme?“ ist ein Beispiel für Arithmetik, die in eine Entscheidung eingebettet ist. Die Antwort muss für Menschen lesbar sein. Lassen Sie eine Python-Funktion multiplizieren und bitten Sie sie nicht, zu entscheiden, welche Optionen erwähnenswert sind.

Warum sollte das nicht der Hauptagent übernehmen? In der nächsten Zeile include_contents="none" bedeutet das, dass dieser Agent keinen Teil der Unterhaltung erhält. Kein Gruppenchat, keine Vorlieben, keine 40 Tourdaten. Sie erhält die Anfrage und nichts weiter. Dieses Flag ist nur in einem Agent vorhanden. Das ist der eigentliche Grund, warum es sich um einen Agenten und nicht um eine Funktion handelt.

Ein untergeordneter Agent, der nur die Anfrage sieht

Bevorzugen Sie den kurzen Prompt: Er ist günstiger, schneller und es ist viel schwieriger, ihn zu entgleisen. Die Arithmetik ist immun gegen etwas, das vor 20 Zügen gesagt wurde, weil sie es nie sieht.

Warum?

AgentTool

? Umschließen Sie den Agenten damit, damit der Haupt-Agent ihn wie jedes andere Tool aufrufen kann. Aus Sicht des Modells ist budget_split identisch mit search_events.

Und

Mit output_key="budget_plan" wird die Antwort unter diesem Namen im Sitzungsstatus gespeichert, anstatt sie lose in der Unterhaltung zu lassen. Hier erfahren Sie, warum das wichtig ist.

Ausführen

👉💻 Drücken Sie in Terminal 1 Strg+C und starten Sie den Server mit denselben Flags neu:

cd ~/monstertix
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

👉 Neue Sitzung starten und sich unterhalten Fünf oder sechs Umdrehungen. Das funktioniert:

hello
what's on in Amsterdam?
Sam is coming with us, and he can't do weeknights
there are 4 of us and I can spend about $200 a head. What fits?

👉 Öffnen Sie den Tab Status.

Dort finden Sie budget_plan mit der vollständigen Antwort des Arithmetik-Agents.

budget_plan nach der ersten Budgetfrage

👉✨ Ändere nun die Zahlen:

what if 10 people come and I can only do $120 each?

👉 Sieh dir budget_plan noch einmal an. Sie wurde überschrieben, nicht angehängt. Das liegt daran, dass output_key die neueste Version enthält.

budget_plan nach der zweiten Budgetfrage

Was Sie nicht sehen können: die Zusammenfassung

Irgendwann hat ADK die vorherigen zusammengefasst.

👉💻 Führen Sie in einem neuen Terminal folgenden Befehl aus, um die Datei direkt zu lesen:

cd ~/monstertix
. ./set_env.sh
./show-compaction.sh

Antwort lesen

Hier ist ein echtes Beispiel in gekürzter Form:

── summary 1 ──────────────────────────────────────────────────────

Conversation Language: English

  **User Request:**
  The user initially wanted to know what concerts are playing in Amsterdam.
  Subsequently, the user provided a constraint regarding a companion, Sam.

  **Context Summary:**
  The user is planning a concert trip. The agent identified two events in
  Amsterdam: "The Midnight Signal" at the Ziggo Dome on Tuesday, November
  10 and Saturday, November 14, 2026. The user then informed the agent that a
  companion named "Sam" will be joining, but "he can't do weeknights." The
  agent has successfully noted this constraint for Sam.

  **Key Decisions/Information Obtained:**
  *   **City of interest:** Amsterdam
  *   **Events found:** "The Midnight Signal" at the Ziggo Dome, Tuesday
  10 November and Saturday 14 November 2026.
  *   **Companion:** Sam
  *   **Companion constraint:** Sam cannot attend concerts on weeknights.

  **Unresolved Questions/Tasks:**
  *   The agent has not explicitly confirmed if the found event (which is
  on a Saturday) is suitable given Sam's constraint, nor has it asked the
  user for the next steps regarding this event or further event searches.

  **Tools Used:**
  *   `recall`
  *   `search_events`
  *   `note_companion`

Die Preise, Abschnitte, Städte und Präferenzen wurden beibehalten und es wurde korrekt darauf hingewiesen, dass noch nichts entschieden wurde.

Daraus ergeben sich drei Dinge:

Es handelt sich um eine Paraphrase. Nichts davon hat irgendjemand gesagt. Es ist eine Darstellung dessen, was gesagt wurde, die durch denselben Prozess erstellt wird, der gelegentlich auch für alles andere verantwortlich ist, was das Modell falsch macht.

Hinweis: Zusammenfassungsstrings werden in internen Sitzungsereignisdatensätzen gespeichert und nicht direkt als separate UI-Tabs angezeigt.

Alle drei Züge in einer Unterhaltung, die teilweise aus früheren Zusammenfassungen besteht.

Versuchen Sie, zu buchen, ohne sich zu wiederholen.

In einer neuen Sitzung: Starten Sie keine neue Sitzung. Die Zusammenfassung, die Sie gerade gelesen haben, basiert auf den Ereignissen dieser Sitzung. Das ist der einzige Grund, warum der Agent noch etwas über Amsterdam weiß.

👉✨ Gib ihm nichts Neues:

Book us something.

Sehen Sie sich an, wie der Agent fragt, welche Show, welcher Abschnitt und wie viele Personen. Markieren Sie das als richtig: In der Zusammenfassung steht, dass der Nutzer nichts bestätigt hat, daher hat der Agent nachgefragt. Kompaktierung berücksichtigen

Auch wenn die Verdichtung erfolgreich ist, kann die Zusammenfassung vorheriger Züge zu Unklarheiten führen, die es erforderlich machen, dass der Agent die Einschränkungen mit dem Nutzer noch einmal bestätigt.

👉✨ Antworte und lasse die Buchung durchgehen:

Amsterdam Saturday, section C, 8 people

Die Bestellung wird noch einmal gelesen, bevor etwas ausgegeben wird:

„Du möchtest 8 Tickets für The Midnight Signal in Amsterdam am Samstag, 14. November, im Bereich C buchen. Die Gesamtkosten belaufen sich auf 760 $. Soll ich die Tickets kaufen?"

👉✨ Bestätigen:

yes

Jetzt wird angezeigt, dass es funktioniert hat:

„Ihr Kauf von 8 Tickets für The Midnight Signal in Amsterdam am Samstag, 14. November, im Block C zum Gesamtpreis von 760 $ ist abgeschlossen. Ihre Bestell-ID lautet ord_227877fe.“

Prüfen Sie, ob das der Fall ist.

👉 Wechseln Sie zum Veranstaltungsort-Bereich. Verlassen Sie sich nicht auf die Aussagen des Kundenservicemitarbeiters.

Die Reihenfolge im Bereich „Veranstaltungsort“

Auf dem Kanal

Was Sie sehen sollten

Gekaufte Tickets

1 und richtig – eine Buchung, eine Bestellung

die darunterliegende Bestellung

ord_xxxxxxxx — 8× section C · 760

Die Tour, Amsterdam, Samstag

Abschnitt C ist um 8 Zeilen nach unten verschoben

Aktivität

ord_xxxxxxxx — 8x section C @ 95 = 760

Im Bereich finden Sie Informationen zum Ticketverkauf.

Warum user:prefs die richtige Wahl war

Erinnern Sie sich an note_companion, in dem Sams Einschränkung in user:prefs geschrieben wurde, und an den offensichtlichen Einwand: Warum sollte man sich mit einem Tool beschäftigen, wenn der Agent sich einfach daran erinnern könnte?

Das hat folgende Gründe: Der Status wird nicht in der Unterhaltung berücksichtigt und auch nicht in der Zusammenfassung. Prüfen Sie user:prefs und budget_plan und stellen Sie fest, dass sie unverändert sind. Sehen Sie sich dann das Transkript an.

Bei einer Zusammenfassung bleibt die allgemeine Form erhalten, die Details gehen jedoch verloren. Speichern Sie alles, was Sie nicht verlieren möchten, an einem anderen Ort als in der Unterhaltung.

6. Fortsetzung der Verarbeitung

40 Minuten warten, ohne online zu bleiben

Wenn ein Kundenservicemitarbeiter in einer 40-minütigen Ticketwarteschlange warten muss, ist es schwierig und teuer, einen Python-Prozess am Laufen zu halten.

So gehen Sie mit langen Wartezeiten richtig um:

  1. Status in einer Datenbank speichern: Speichern Sie, wo der Agent pausiert wurde, damit der Prozess sicher heruntergefahren werden kann.
  2. Auf Abruf reaktivieren: Wenn die Wartezeit abgelaufen ist, wird der Agent durch ein externes Signal neu gestartet und die Ausführung wird an der Stelle fortgesetzt, an der sie unterbrochen wurde.

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Zum Code dieses Schritts wechseln:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 6 --force

Ausführen

Was hat sich seit dem letzten Schritt geändert?

Datei

Was hat sich geändert?

tools.py

hat join_queue und check_queue hinzugefügt

agent.py

join_queue ist in LongRunningFunctionTool eingeschlossen und App aktiviert ResumabilityConfig.

join_queue gibt sofort ein Ticket zurück, anstatt zu blockieren, und ResumabilityConfig lässt den Lauf pausieren und später fortsetzen.

Vorteile der Wiederaufnahmefunktion

👉💻 Terminal 1 – der unveränderte Agent:

cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

👉✨ KI-Agenten bitten, etwas zu kaufen:

Get us two tickets to the Amsterdam show.

Es ruft recall, dann search_events und dann join_queue auf und antwortet mit etwas in dieser Art:

You are in the queue for The Midnight Signal at Ziggo Dome, Amsterdam, on Saturday, November 14. Your position is 14203.

Beachten Sie, dass Samstag ausgewählt wurde, ohne dass Sie gefragt wurden. Es gibt zwei Shows in Amsterdam. Die am 17. ist an einem Dienstag und in der Erinnerungsdatei aus dem letzten Schritt steht, dass Sam nicht an Wochentagen auftritt. Das ist eine Tatsache aus einer früheren Buchung, die eine Frage in dieser Buchung beantwortet.

Außerdem hat es sich bevor nach einem einzelnen Sitzplatz oder Preis gesucht wurde in die Warteschlange eingereiht und nichts dazu gesagt. Das ist die Regel des Veranstaltungsorts. Du kaufst die Tickets, bis du an der Reihe bist. Das Einzige, was sich lohnt, ist, früh da zu sein, um in der Schlange einen guten Platz zu bekommen. Welche Plätze wir nehmen, ist eine Frage für in 40 Minuten.

👉 Sehen Sie sich das Steuerfeld an. Das Banner lautet:

Die Warteschlange bewegt sich weiter, obwohl nichts von dir läuft

Agent is waiting in line — #14,203
About 2400s at 1×. Safe to kill the agent right now — it will still be here.

Die Warteschlange ist wirklich 40 Minuten lang und wird während der Arbeit nicht kürzer.

👉💻 Agent beenden:

Ctrl-C

👉💻 Starten Sie es noch einmal in Terminal 1:

cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

👉✨ Fragen Sie den KI-Agenten in derselben Sitzung, wo er sich in der Zeile befindet:

Where am I in line?
You are currently at position 12982 in the queue. It is not your turn yet.

Dasselbe Ticket nach dem Neustart

Vergleiche das Ticket und beobachte, wie die Position immer weiter sinkt, obwohl nichts ausgeführt wurde. Der Agent wusste nicht, dass er beendet wurde. Es wurde check_queue mit dem Ticket aufgerufen, das vor dem Neustart abgerufen wurde, da dieses Ticket auf der Festplatte gespeichert war.

👉💻 Gründe:

cd ~/monstertix
sqlite3 sessions.db "select name from pragma_table_info('events');"
id
app_name
user_id
session_id
invocation_id
timestamp
event_data

Jeder Zug jeder Unterhaltung ist eine Zeile. Der Zug selbst, wer ihn gesagt hat, welches Tool aufgerufen wurde, was zurückgegeben wurde und der Hinweis, dass es sich um JSON innerhalb von event_data handelt.

👉💻 So finden Sie Ihr Warteschlangenticket:

sqlite3 sessions.db "select event_data from events;" \
  | grep -o '"ticket": "q_[a-z0-9]*", "position": [0-9]*' | tail -1
"ticket": "q_8cc2c6f9", "position": 14203

Wenn join_queue zurückgegeben wird, wird das Ticket direkt in der Datenbank gespeichert. Beim Neustart lädt ADK die vorherigen Züge und Tool-Ergebnisse von der Festplatte, sodass der Agent dort weitermachen kann, wo er aufgehört hat.

Achten Sie darauf, was nicht neu begonnen wurde. Der Agent wird direkt fortgesetzt, anstatt den Workflow von Anfang an neu zu starten. Da LongRunningFunctionTool und ResumabilityConfig den Ausführungsstatus auf der Festplatte speichern, weiß ADK, dass der Agent bereits in der Warteschlange ist. So wird verhindert, dass er noch einmal nach Tourdaten sucht oder ein Duplikatticket anfordert.

Dasselbe Ticket für einen Prozess, der beendet wurde

Warum join_queue und nicht check_queue?

Beide Funktionen geben Millisekunden zurück. Der Unterschied besteht darin, ob der zugrunde liegende Job abgeschlossen ist, wenn die Funktion zurückgegeben wird.

Was wird zurückgegeben?

Ist die Arbeit erledigt?

join_queue

Ticket in der Warteschlange und erste Position

Nein. Die Verarbeitung wird asynchron im externen System fortgesetzt.

check_queue

Aktueller Status der Warteschlange

Ja. Gibt eine sofortige Antwort zu einem bestimmten Zeitpunkt zurück.

LongRunningFunctionTool ist für Vorgänge konzipiert, die ein Handle für ausstehende Aufgaben zurückgeben. Durch das Umschließen von join_queue kann der KI-Agent die Ausführung unterbrechen, während die externe Warteschlange voranschreitet.

check_queue ist dagegen synchron und vollständig. Wenn der Agent den Vorgang umschließt, würde er die Ausführung jedes Mal fälschlicherweise unterbrechen, wenn er seinen Status prüft – auch wenn er an der Reihe ist und den Kauf sofort abschließen muss.

Welcher Anruf ist noch ausstehend, wenn er zurückgegeben wird?

Was hat funktioniert?

Drei Komponenten ermöglichen das Aussetzen und Wiederaufnehmen der Ausführung:

# agent/concert/agent.py
LongRunningFunctionTool(func=join_queue)      # the tool returns without finishing
ResumabilityConfig(is_resumable=True)         # the run may pause and be picked up
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db"
  • LongRunningFunctionTool: Markiert ein Tool als nicht blockierend und gibt ein aktives Handle zurück, damit der Agent warten kann, ohne den Thread aufzuhängen.
  • ResumabilityConfig: Weist die Laufzeit an, den Ausführungslebenszyklus bei einer unvollständigen Aufgabe zu pausieren und vom letzten aufgezeichneten Status aus fortzusetzen.
  • Session Storage URI (--session_service_uri): Der Status wird in einem externen Speicher (z.B. SQLite, Cloud SQL) und nicht im Prozessspeicher gespeichert, um Datenverlust bei Neustarts zu verhindern.

7. Mit veralteten Daten arbeiten

Informationen sind veraltet, während Sie warten

Der gespeicherte Unterhaltungsverlauf ist kein Echtzeitstatus. Bei langen Wartezeiten können sich externe Daten wie Ticketverfügbarkeit oder Preise ändern, ohne dass der Kundenservicemitarbeiter davon weiß.

Ein Agent mit fehlenden Informationen pausiert, um um Klärung zu bitten. Ein Agent mit veralteten Daten reagiert jedoch sofort auf der Grundlage veralteter Annahmen. Um ungültige Aktionen zu verhindern, müssen zeitkritische Daten unmittelbar vor der Ausführung wichtiger Vorgänge noch einmal überprüft werden.

Laden Sie noch keinen neuen Code. Bleiben Sie beim Agenten des vorherigen Schritts (er kann Tickets kaufen, hat aber derzeit keine Sicherheitsvorkehrungen für veraltete Daten oder Idempotenz).

👉🔴 Drücke auf ORT ZURÜCKSETZEN und starte dann eine neue Sitzung in adk web – mit der Schaltfläche + Neue Sitzung über dem Chat.

Starten Sie eine neue Sitzung, um das vorherige Warteschlangenticket und den Status „Auftrag wird bearbeitet“ zu löschen. Für diesen Schritt ist ein sauberer Ausführungskontext erforderlich, um die Verarbeitung von veralteten Daten richtig zu demonstrieren.

Der Snapshot liegt

👉✨ Nach derselben Sendung wie zuvor fragen:

Get us two tickets to the Amsterdam show.

👉✨ Während das Tool in der Warteschlange ist, kannst du nach Sitzplätzen fragen:

How much are the seats, and what's still available?
Section A: $210 per ticket, with 400 seats available.
Section B: $145 per ticket, with 900 seats available.
Section C: $95 per ticket, with 1200 seats available.

👉🔴 Klicke auf DIE BESTEN PLÄTZE VERKAUFEN. Abschnitt A fällt auf null.

👉🔴 Klicke auf SKIP THE WAIT (WARTEN ÜBERSPRINGEN), um den Kundenservicemitarbeiter an den Anfang der Warteschlange zu stellen.

Am Anfang der Warteschlange

👉✨ Sagen Sie, dass Sie etwas kaufen möchten:

Buy the two section A seats.

purchase(section="A") wird ausgeführt, ohne die Verfügbarkeit von Sitzplätzen noch einmal zu prüfen. Die Venue API gibt daher eine Fehlermeldung zurück:

{"error": "sold_out", "available": 0,
 "message": "those seats are gone — re-fetch the seatmap before buying"}
Oh no! It looks like the Section A tickets just sold out. Let me get an
updated seat map to see what's still available.

Fehler in der Venue API: Abschnitt A ist ausverkauft

Der Agent hat den Sitzplan erst nach Erhalt einer API-Fehlerantwort noch einmal angefragt. Die Ursache ist ein veralteter Kontext: Zu Beginn der Sitzung hat der Agent „Abschnitt A: 400 verfügbar“ aufgezeichnet und dieser Zug blieb im Konversationsverlauf, der an das Modell übergeben wurde.

Machen Sie deutlich, wo die alten Informationen gespeichert sind: Sie bleiben im Unterhaltungsverlauf erhalten, der durch Statuspräfixe wie temp: nicht überschrieben werden kann.

Der Sitzplan, mit dem der Kundenservicemitarbeiter noch arbeitet

Die Lösung

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Laden Sie jetzt den Code für diesen Schritt:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 7 --force

👉💻 Drücken Sie in Terminal 1 Strg+C und starten Sie den Vorgang neu:

cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

Änderungen:

Datei

Was hat sich geändert?

fence.py

etwas Neues. Wird unmittelbar vor einem Kauf ausgeführt und kann ihn blockieren

agent.py

before_tool_callback=refresh_before_purchase

tools.py

purchase sendet jetzt einen Idempotency-Key-Header

fence.py ist ein Callback, der vom ADK unmittelbar vor einem Tool-Aufruf ausgeführt wird:

root_agent = Agent(
    ...,
    before_tool_callback=refresh_before_purchase,   # re-read inventory, then decide
)

Das will ich sehen

👉🔴 VENUE ZURÜCKSETZEN Stelle dich in der Warteschlange an, drücke während des Wartens auf DIE GUTEN PLÄTZE VERKAUFEN und sage dann, dass du Bereich A kaufen möchtest.

Dieses Mal stoppt es von selbst. Der Agent sagt dann zum Beispiel:

„Es sieht so aus, als wäre Abschnitt A gerade ausverkauft. Ich muss mir den Sitzplan noch einmal ansehen.“

Sehen Sie dann zu, wie get_seatmap noch einmal aufgerufen wird, ohne dass Sie dazu aufgefordert werden, und wie stattdessen Abschnitt C angeboten wird.

👉🔴 Lies den Aktivitätsfeed des Veranstaltungsorts sorgfältig, da er beide Hälften anzeigt:

error  purchase failed: Section A has 0 seats left, 2 requested. Re-read the
       seat map and pick again.
agent  calling purchase

Der Veranstaltungsort-Feed: der Versuch und die Ablehnung

Im Dashboard wird ein Anruf angezeigt, aber der Zähler für Tickets bleibt bei null. Das liegt daran, dass fence.py als Schutz vor der Ausführung des Tools dient:

  1. Der Agent versucht, das Kauf-Tool aufzurufen.
  2. fence.py fängt die Anfrage zuerst ab und prüft die Verfügbarkeit von Live-Plätzen.
  3. Da die Plätze ausverkauft sind, gibt fence.py direkt einen Fehler zurück und bricht den Kauf ab, bevor die API aufgerufen wird.

Durch Prompt-Anweisungen kann nicht garantiert werden, dass ein LLM nicht versucht, ausverkaufte Plätze zu kaufen. Code-Guards (before_tool_callback) sorgen dafür, dass der betrügerische Kauf nie in der realen Welt stattfindet.

👉🔴 Klicke auf ORT ZURÜCKSETZEN und dann auf NÄCHSTEN KAUF ABBRECHEN.

👉✨ Bitten Sie den Assistenten, den Artikel zu kaufen, und lassen Sie ihn es so oft wie nötig versuchen.

Es handelt sich weiterhin um eine Bestellung, unabhängig davon, wie oft Sie es versuchen.

Die Anzahl der Bestellungen bleibt bei 1, da der Veranstaltungsort bei der Wiederholung denselben Idempotenzschlüssel erhalten und die ursprüngliche Bestätigung zurückgegeben hat, anstatt eine doppelte Bestellung zu erstellen.

8. Autonomer Workflow

LLMs können starre Regeln nicht immer einhalten

LLMs eignen sich hervorragend für Entscheidungen, z. B. für die Auswahl einer Serie oder Sendung basierend auf den Nutzerpräferenzen. Wenn Sie sich jedoch darauf verlassen, dass ein LLM strikte Schritt-für-Schritt-Regeln befolgt (z. B. „immer der Warteschlange beitreten, bevor Sie die Sitzplätze prüfen“), werden gelegentlich Schritte übersprungen oder die Reihenfolge durcheinandergebracht.

Um das Problem zu beheben, teilen Sie den Workflow in zwei Arten von Schritten auf:

  • Feste Regeln (Code-Funktionen): Schritte, die jedes Mal auf genau dieselbe Weise ausgeführt werden müssen (z. B. ein Warteschlangenticket ziehen, Position prüfen), werden als normale Codeknoten geschrieben.
  • Entscheidungen (Agent-Knoten): Schritte, die eine Entscheidung erfordern (z. B. Auswahl von Sitzplatzbereichen, Überprüfung von Nutzerpräferenzen), werden dem KI-Agenten überlassen.

Sehen Sie sich an, was tatsächlich in diesem Ablauf enthalten ist. Einige davon enthalten überhaupt keine Wertung:

Schritt

Urteil?

genau ein Warteschlangenticket ziehen

Nein. Immer nur eine und immer vor dem Kauf

Prüfen, ob Sie an der Reihe sind

Nein. Frage stellen, Antwort vorlesen

Briefing für den Käufer erstellen

Nein. Immer derselbe Satz, immer dieselbe Form.

Welche Show, welcher Abschnitt

Ja. Preis, Tag, wer kommt, was sie hassen

Nutzerpräferenz und Einschränkung

Ja.

Für strikte Verfahren sollte kein LLM verwendet werden, da probabilistische Modelle ohne Überwachung irgendwann starre Sequenzen nicht mehr einhalten.

Das Workflow des ADK trennt die deterministische Ausführung von der KI-Schlussfolgerung:

  • Funktionsknoten erzwingen jedes Mal konsistent feste Regeln, z. B. das Eintreten in eine Warteschlange oder das Prüfen der Position.
  • Agent-Knoten verarbeiten kontextbezogene Entscheidungen, z. B. die Auswahl von Sitzplätzen basierend auf Nutzerpräferenzen.
  • Wait-Knoten unterbrechen die Ausführung, ohne einen Prozess im Arbeitsspeicher aktiv zu halten.

Der ursprüngliche Agent wird nicht ersetzt. Er wird zum letzten Knoten im Workflow-Diagramm und behält seine Tools, Aktualitätsprüfungen und Idempotenzschutzmaßnahmen bei. Das Diagramm steuert die Ausführungsreihenfolge, während der KI-Agent die Kaufentscheidung trifft.

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN.

👉💻 Zum Code dieses Schritts wechseln:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 8 --force

Ausführen

👉💻 Drücken Sie in Terminal 1 Strg+C und starten Sie neu. Der Befehl ist wie immer:

cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

Was hat sich seit dem letzten Schritt geändert?

Datei

Was hat sich geändert?

nightly.py

etwas Neues. Die unbeaufsichtigte Ausführung als ADK 2-Workflow-Diagramm

agent.py

root_agent ist jetzt das Diagramm. Der KI-Agent, mit dem Sie gesprochen haben, ist weiterhin vorhanden, wurde in buyer_agent umbenannt und ist ein Knoten darin.

agent.py

Außerdem hat er join_queue und check_queue verloren – die Warteschlange gehört jetzt dem Diagramm.

Diagramm ausführen – drei Nachrichten in der richtigen Reihenfolge

Führen Sie alle folgenden Aktionen in der Nightly-Python-Version aus (dort befindet sich das Diagramm). Senden Sie in jedem Zug dieselbe Eingabe, um zu sehen, wie der Workflow-Graph die verschiedenen Status durchläuft.

1. Achten Sie darauf, mit wem Sie gerade sprechen.

👉 Laden Sie die ADK-Web-UI neu.

# concert/agent.py, at the bottom

buyer_agent = Agent(name="concert", ...)   # ← everything you built, renamed

from .nightly import nightly
root_agent = nightly                       # ← what adk web actually runs

adk web wird für das Objekt ausgeführt, das root_agent zugewiesen ist. Hier verweist root_agent auf das nightly-Workflowdiagramm, wobei buyer_agent als letzter Knoten im Diagramm konfiguriert ist.

2. Starten

👉✨ Geben Sie Folgendes ein und drücken Sie die Eingabetaste:

go

Hinweis: In einem automatisierten Workflowdiagramm beginnt die erste Ausführung, ohne auf Nutzereingaben zu warten. Wenn Sie eine Nachricht in adk web senden, wird die erste Ausführung ausgelöst.

👉 Sehen Sie sich nun den Webbrowser an. Im linken Bereich wird das Diagramm gezeichnet und die Knoten werden während der Ausführung eingefärbt. Im rechten Bereich sehen Sie den Ereignisstream und können nachvollziehen, wie die Entscheidungen getroffen werden:

Das Diagramm, das in check_front gespeichert ist

queue_up schreibt queue_ticket und queue_event_id in den Sitzungsstatus.

Das Diagramm, das in check_front gespeichert ist

Sie sollten einen Funktionsaufruf mit dem Namen adk_request_input sehen, der auf eine Antwort wartet. Dann wird es still und der Lauf wird beendet. Die Grafik hat check_front erreicht, Sie wurden auf Platz 14.203 gefunden und die Suche wurde beendet.

👉 Prüfe das Bereich „Veranstaltungsort“: Es sollte nur ein Warteschlangenticket vorhanden sein und nichts ausgeführt werden.

Das Diagramm, das in check_front gespeichert ist

3. Aktivieren Sie es – und achten Sie darauf, in welches Feld Sie schreiben

Scrollen Sie im Ereignisstream zum Ereignis adk_request_input und suchen Sie das Eingabefeld, das mit diesem Ereignis verknüpft ist:

Das Diagramm, das in check_front gespeichert ist

Es gibt jetzt zwei Stellen, an denen Sie Text eingeben können, und sie haben unterschiedliche Funktionen.

Wo Sie Text eingeben

Was passiert dann?

das Feld Enter your response... für das Ereignis adk_request_input

auf die Unterbrechung reagiert. Der geparkte Lauf wird ab check_front fortgesetzt.

das Hauptchatfeld unten

startet einen neuen Aufruf. Die Grafik wird ab START wiedergegeben, es wird eine Sendung ausgewählt und ein zweites Warteschlangenticket wird erstellt.

👉✨ Geben Sie etwas in das Feld Enter your response... ein und drücken Sie den Pfeil.

👉 Terminal 1:

[check_front] #14,197 — not yet. Pausing.

Eine Zeile: Es wurde keine zweite Show ausgewählt und es wurde kein zweites Warteschlangenticket erstellt, wodurch du am Ende einer Warteschlange mit 14.203 Personen gestanden hättest.

Wenn [open] und [pick_show] noch einmal ausgeführt werden, haben Sie etwas in das Chatfeld eingegeben. Drücken Sie auf ORT ZURÜCKSETZEN und beginnen Sie den Schritt noch einmal.

👉 Sehen Sie sich das Venue-Panel noch einmal an: Es ist immer noch ein Ticket und die Position ist etwas gesunken, weil die Uhr des Veranstaltungsorts weitergelaufen ist, während Ihre Uhr stehen geblieben ist.

Einmal geweckt, aber immer noch ein Ticket

4. Bis zum Anfang gehen und dann fertigstellen

👉🔴 Drücke im Bereich für den Veranstaltungsort auf WARTEN ÜBERSPRINGEN.

Das fertige Diagramm mit allen grünen Knoten

👉✨ Antworte ein letztes Mal auf das neueste adk_request_input-Ereignis – wieder in seinem Feld, nicht im Chatfeld. Das fertige Diagramm mit allen grünen Knoten

Dieses Mal ist check_front vorne und fällt durch. Sehen Sie zu, wie die beiden letzten Knoten im linken Bereich aufleuchten brief und concert.

Das fertige Diagramm mit allen grünen Knoten

👉 Browser ansehen Hier wird der Lauf beendet. Es gibt vier Dinge, die es sich anzusehen lohnt.

Das fertige Diagramm mit allen grünen Knoten

Das Diagramm ist vollständig grün und die Kanten werden gezählt. brief und concert sind jetzt beleuchtet und die Kanten zu pick_show, queue_up und check_front haben jetzt ein Symbol.

2x

ist die Anzahl der Durchläufe des Graphen entlang dieser Kante, einmal pro Aktivierung. Die beiden oben genannten sind der Preis für das Fortsetzen: Der Scheduler tritt jedes Mal von Anfang an wieder in das Diagramm ein. Das bedeutet nicht, dass die Arbeit dreimal ausgeführt wurde. Das Veranstaltungsort-Panel ist der Beweis: ein Warteschlangenticket, nicht zwei. @node(rerun_on_resume=False) ist der Grund dafür.

Die Grafik

Der Graph: Funktionen für Regeln, Agenten für die Entscheidungsfindung

# agent/concert/nightly.py
nightly = Workflow(
    name="concert_nightly",
    edges=[(START, open_the_night, pick_show, queue_up,
            check_front, brief, buyer_agent)],
)

Beachten Sie, dass zwei Knoten Agents und zwei Funktionen sind. Die Frage, welcher Knoten was ist, ist die gesamte Designfrage.

Knoten

Warum diese Form?

pick_show

agent. Die Auswahl einer Serie aus dem Verlauf einer Person ist eine Entscheidung. Es ruft recall auf, liest die Tour und gibt ein validiertes Plan zurück.

queue_up

Funktion Das Hinzufügen zu einer Warteschlange ist eine Regel. Eine Regel sollte nicht um 3 Uhr morgens ausgewürfelt werden.

check_front

Funktion „Bin ich vorne?“ hat eine richtige Antwort und der Veranstaltungsort hat sie

buy_it

agent. Es ist buyer_agent – das, was root_agent früher war, unverändert. Abschnitt A ist während der Wartezeit ausverkauft. Soll ich Abschnitt B oder C nehmen oder nichts? Das ist die Bewertung, die Sie dem Modell in Modul 4 beigebracht haben.

Sehen Sie sich den letzten Knoten an und suchen Sie nach dem Agent, den Sie erstellt haben. Keine Kopie, keine Umformulierung:

# agent/concert/nightly.py
from .agent import buyer_agent

Alles ist dabei: jedes Tool, die erneute Lektüre des Sitzplans in fence.py und der Idempotenzschlüssel in purchase. Es gibt nur ein Exemplar.

Warum wurde die Wiedergabe beendet, anstatt zu warten?

Hier ist die Version, die niemand schreiben sollte:

# what NOT to write — this is nobody's file
while not ready:          # never do this
    time.sleep(1)

Es hält einen Prozess 40 Minuten lang am Laufen, ohne etwas zu erreichen, wird mit dem Terminal beendet und kann nicht fortgesetzt werden. check_front macht Folgendes:

# agent/concert/nightly.py — check_front
if not status["ready"]:
    return RequestInput(message=f"Still at #{position}. Wake me and I'll check.")

RequestInput ist eine Unterbrechung. Der Graph wird angehalten, die Anfrage wird an den Sitzungsspeicher gesendet und der Prozess kann beendet werden.

Der Lauf kostet nichts, während er wartet, und er endet an genau einer Stelle: gekaufte Tickets. Es kann aber auch die ganze Nacht und mehrere Wachphasen dauern, bis du dort ankommst.

9. Mit einem Mitarbeiter sprechen

Geben Sie kein Geld aus, ohne sich vorher mit einer Person abzustimmen.

Ein autonomer Agent, der über Nacht ausgeführt wird, sollte keine teuren finanziellen Entscheidungen auf der Grundlage von Vermutungen oder mehrdeutigen Präferenzen aus dem Chatverlauf treffen.

Bevor der Agent risikoreiche Aktionen ausführt (z. B. Geld für Tickets ausgibt), muss er pausieren, den Nutzer bitten, sein Budget laut zu bestätigen, und diese ausdrückliche Bestätigung speichern, bevor er Käufe tätigt.

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Zum Code dieses Schritts wechseln:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 9 --force

Ausführen

👉💻 Drücken Sie in Terminal 1 Strg+C und starten Sie neu. Gleicher Befehl wie zuvor:

cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
  --session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
  --artifact_service_uri="file://$WORKSHOP/artifacts"

Was hat sich seit dem letzten Schritt geändert?

Datei

Was hat sich geändert?

budget.py

Enthält das, was die Person vereinbart hat, als den Satz, den sie gesagt hat. load() liest den Text vor und someone_is_there() antwortet, ob jemand in der Nähe ist, den man fragen kann.

tools.py

set_budget hinzugefügt, damit der Agent ein vereinbartes Limit in den Sitzungsstatus schreiben kann, sobald eine Person es bestätigt hat

nightly.py

ein neuer erster Knoten, agree_budget, der vor allen anderen Aktionen fragt und zurückliest. Es erfasst das Budget und den Rest der Anfrage sowohl als Freitext als auch SEATS als Konstante.

Nicht modellieren. Speichern Sie den Satz.

Öffnen Sie agent/concert/budget.py und lesen Sie den gesamten Text:

def load(state) -> str:
    """What they agreed, verbatim. Empty string means nobody has said."""
    return str(state.get("budget") or "")
state["budget"] = "up to $100 for the upper bowl or general admission, up to
                   $250 for the lower bowl, and up to $300 for a Saturday show"

Im Knoten wird geprüft, ob die Person das gesagt hat und ob sie es bestätigt hat.

Anschließend wird der Satz im Sitzungsstatus und nicht unter user: gespeichert. Für eine Buchung wird ein Budget vereinbart. „250 $, wenn sie gut sind“ wurde an diesem Abend über diese Band gesagt und hat nichts mit der Ticketbuchung zu tun.

Das gilt auch für alles andere, was sie gesagt haben.

Nicht beim Budget aufhören. Nutzer ändern ihre Meinung während der Entscheidungsfindung. Erfassen Sie daher alles, wonach sie fragen, anstatt sich darauf zu verlassen, was der Kundenservicemitarbeiter sich merkt.

"I live in NYC, I can only do weekends, I have 10 people coming with me"
# agent/concert/nightly.py
def what_they_asked_for(ctx) -> str:
    """Everything this person typed, joined, in their own words."""
    said = []
    for event in (ctx.session.events or []):
        if event.author != "user":
            continue
        for part in (event.content.parts if event.content else []) or []:
            text = (getattr(part, "text", None) or "").strip()
            if text and text not in said:
                said.append(text)
    return "\n".join(said)

Bei Nur von Nutzer erstellte Ereignisse werden nur

text

Teile aus dem function_response, damit die Frage und Antwort zum Budget hier nicht wiederholt werden. Keine Duplikate:

Hinweis: agree_budget ruft die Funktion bei jedem Lauf auf, nicht nur beim ersten. Eine Korrektur, die eingegeben wird, während der Agent in der Warteschlange steht, wird also auch berücksichtigt. open_the_night Gib dem Picker dann beide Sätze, die Anfrage und das Budget, und lass ihn sie vorlesen.

Das Diagramm, ein Knoten länger

Beginnen wir mit einer Unterhaltung:

# agent/concert/nightly.py
nightly = Workflow(
    name="concert_nightly",
    edges=[(START, agree_budget, open_the_night, pick_show, queue_up,
            check_front, brief, buyer_agent)],
)

Dasselbe Diagramm, aber mit „agree_budget“ vorangestellt

Zwei Knoten halten an und warten. Es handelt sich um denselben Mechanismus. check_front wartet auf einen Ort und wird von einer Uhr beantwortet. agree_budget wartet auf eine Person und kann nur von einer Person beantwortet werden. Diese Differenz ist der gesamte Schritt 10.

Schrittweise ausführen

1. Starten

👉 Laden Sie die ADK-Web-UI neu und wählen Sie den concert-Agenten aus.

👉✨ Sagen Sie, was Sie wirklich möchten:

Get us two tickets to the Amsterdam show.

Der Lauf wird beendet und Sie werden gefragt, was Sie ausgeben möchten.

In Schritt 8 gab es keinen Nutzer im Diagramm. Daher wurde alles, was Sie eingegeben haben, verworfen und go hat genauso gut abgeschnitten wie alles andere. Hier ruft agree_budget in der ersten Zeile what_they_asked_for(ctx) auf, wodurch alles, was Sie eingegeben haben, erfasst und in state["request"] gespeichert wird. open_the_night übergibt es dann zusammen mit dem Budget an den Kommissionierer.

Du hast keinen Tag angegeben und in Amsterdam gibt es zwei Shows. Achte also darauf, dass die Auswahl auf recall() zurückgesetzt wird, suche nach „Sam bails on weeknights“ und wähle den Samstag aus.

Im Browser wird der Lauf bei einem adk_request_input-Ereignis beendet:

Ein Diagramm hat Ihnen gerade eine Frage gestellt. Die Unterbrechung ist dieselbe wie beim Warten in der Warteschlange in Schritt 8, aber jetzt wird eine Person genannt, die die Frage beantworten soll.

2. Antworten Sie so, wie Menschen tatsächlich antworten.

👉✨ Geben Sie im Feld Enter your response... (nicht im Chatfeld unten) Folgendes ein:

About a 100 for the cheap seats. 250 if they're the good ones.

So können Sie sehen, dass die Wiedergabe beendet wird:

[agree_budget] reading back: About a hundred for the cheap seats. Two-fifty
               if they're the good ones.

⚡ adk_request_input
   So: About a hundred for the cheap seats. Two-fifty if they're the good
   ones. Have I got that right?

Die Nummer wird vor der Bestätigung noch einmal vorgelesen.

3. Bestätigen

👉✨ Gleicher Karton:

Yes, that's right.
[agree_budget] agreed: About a hundred for the cheap seats. Two-fifty if
               they're the good ones.
[open]         Budget: About a hundred for the cheap seats. Two-fifty if
               they're the good ones.
[pick_show]    → {"section": "A", "reason": "..."}
[queue_up]     ms-ams-01 section A — ticket q_5924edc2 at #14,203
[check_front]  #14,203 — not yet. Pausing.

Vier Knoten wurden mit einem Wort ausgeführt. 👉 Öffnen Sie den Tab State (Status) und suchen Sie nach budget – Ihrem Satz im Wortlaut.

Budget auf dem Tab „Bundesstaat“ in Ihren eigenen Worten

Kein Präfix. Das ist der Sitzungsstatus. Wenn Sie eine neue Sitzung starten und das Budget aufgebraucht ist, werden Sie noch einmal gefragt.

4. An die Spitze kommen und das Rennen beenden

👉🔴 Drücke im Bereich für den Veranstaltungsort auf WARTEN ÜBERSPRINGEN.

👉✨ Beantworte die neueste adk_request_input – wieder im entsprechenden Feld:

go

Wird wieder aktiviert und fragt nicht zweimal

👉 Prüfen Sie den Bereich „Veranstaltungsort“: eine Bestellung zu einem Preis, der dem vereinbarten Preis für diese Art von Sitzplatz entspricht.

Eine Bestellung, die den vereinbarten Bedingungen entspricht

Wie ein Diagramm einer Person eine Frage stellt

Öffnen Sie nightly.py und lesen Sie agree_budget. Wenn drei Dinge gleichzeitig zutreffen und bei jedem einzelnen das gleiche Problem auftritt, nämlich dass die Anfrage nicht beantwortet wird,

@node(rerun_on_resume=True)              # 1. run again when woken
def agree_budget(node_input, ctx):
    answers = ctx.resume_inputs or {}    # 3. where the answer arrives

    said = answers.get(ASK_BUDGET)
    if not said:
        return RequestInput(
            interrupt_id=ASK_BUDGET,     # 2. STABLE id, not the default
            message="What are you willing to spend?",
        )
    ...

rerun_on_resume=True

Andernfalls wird der Knoten als erledigt behandelt und übersprungen.

eine stabile interrupt_id

Standardmäßig wird für jeden Lauf eine neue UUID generiert. Es gibt also nichts, wonach gesucht werden kann.

ctx.resume_inputs[id]

wo die Antwort landet. Nicht der Rückgabewert von RequestInput: Das ADK durchläuft einen Knoten, anstatt ihn zu senden.

Grafik

Es ist das Diagramm aus Schritt 8 mit einem zusätzlichen Knoten am Anfang. Dasselbe Diagramm, aber mit „agree_budget“ vorangestellt

Vom Nutzer bestätigte Einschränkungen gelten nur auf Sitzungsebene und laufen ab, wenn die Unterhaltung beendet wird. Wenn temporäre Limits sitzungsübergreifend beibehalten werden, besteht das Risiko, dass einmalige Entscheidungen zu unbeabsichtigten permanenten Standardeinstellungen werden.

10. Der Cloud-Stack

Laptops: Ruhezustand und lokale Dateien verschwinden

Auf Ihrem Laptop ist ein Agent davon abhängig, dass Ihr Computer aktiv bleibt, lokale SQLite-Datenbanken und lokale Dateien vorhanden sind. Wenn Sie den Laptopdeckel schließen oder in einem Cloud-Container bereitstellen, der neu gestartet wird, gehen lokale Dateien und der Arbeitsspeicher verloren.

Damit sie zuverlässig in der Produktion ausgeführt werden können:

  1. Cloud Compute: Führen Sie den Agent in einem Container (z. B. Cloud Run) aus, der online bleibt.
  2. Durable Cloud Databases: Ersetzen Sie lokale Dateien und SQLite durch Cloud-Dienste (Cloud SQL für Sitzungsdaten, Cloud Storage für Dateien).
  3. Cloud-Timer: Verwenden Sie verwaltete Scheduler wie Cloud Scheduler und Pub/Sub, damit der Agent auch dann aktiviert wird, wenn Ihr Laptop ausgeschaltet ist.

👉🔴 Drücken Sie im Bereich „Veranstaltungsort“ auf VERANSTALTUNGSORT ZURÜCKSETZEN. Jeder Schritt beginnt in derselben Welt: 8 Shows, alle Plätze verfügbar, Uhr auf 1×.

👉💻 Zum endgültigen Code wechseln:

cd ~/monstertix
. ./set_env.sh
./use-solution.sh 10 --force

Datei

Was hat sich geändert?

main.py

etwas Neues. Ein FastAPI, das die Chatseite und den Endpunkt bereitstellt, den ein Scheduler aufruft

Dockerfile

etwas Neues. Gewöhnliches python-slim und uvicorn. Nichts davon ist ADK-spezifisch.

monstertix/

kopiert, sodass die Seite im selben Container wie der Agent ausgeliefert wird.

concert/

unverändert. Byte für Byte der Ordner, mit dem Sie Schritt 9 abgeschlossen haben

Die Seite, die Sie bereits erstellt haben

Die** ADK-Web-UI** ist ausschließlich eine lokale Debugging-Schnittstelle zum Prüfen von Status, Ereignisstreams und Workflow-Diagrammen während der Entwicklung. Es ist nicht für die Bereitstellung in der Produktion vorgesehen.

Verwenden Sie für die Produktion das eigenständige Chat-Frontend, das in Schritt 3 erstellt wurde. Über den /wake-Endpunkt werden Nutzernachrichten und Interaktionsunterbrechungen verarbeitet, ohne dass interne Debugging-Steuerelemente verfügbar sind.

Lokalen Status zu Clouddiensten migrieren

Halten Sie den Server an und sehen Sie sich an, was er geschrieben hat:

sessions.db          a SQLite file next to the code
memory/userx.md      a markdown file you can open in an editor
artifacts/           seat maps written to a folder

Lokale Dateien eignen sich zwar gut für die Entwicklung, aber nicht für die Produktion, da Cloud Run-Container flüchtigen, instanzlokalen Speicher verwenden.

Bei der Ausführung in Cloud Run gilt Folgendes:

  • Instanzen werden auf null skaliert:Wenn der Traffic ausbleibt, wird der Container beendet und sein lokales Dateisystem wird gelöscht.
  • Instanzen werden unabhängig horizontal skaliert:Mehrere gleichzeitige Instanzen teilen sich keinen Speicherplatz. Jede wird mit einem eigenen isolierten Dateisystem ausgeführt.

Wenn ein Kundenservicemitarbeiter ein Warteschlangenticket oder einen Sitzungsstatus in einer lokalen SQLite-Datei (sessions.db) speichert, wird bei nachfolgenden Anfragen, die an eine neue oder andere Containerinstanz weitergeleitet werden, eine leere Datenbank gefunden. Damit der Status beim Skalieren und Neustarten erhalten bleibt, müssen lokale Dateien durch verwaltete externe Dienste ersetzt werden.

Stattdessen benötigen Sie einen Status, der länger als ein einzelner Container bestehen bleibt. Drei Dienste, einer für jede Datei:

Auf dem Laptop

In der Cloud

Beschreibung

sessions.db

Cloud SQL

einer verwalteten Postgres-Datenbank. Sie ist unabhängig davon vorhanden, ob etwas ausgeführt wird.

memory/userx.md

Cloud Storage

einem Bucket. Dateien als Objekte, die von jeder Instanz aus gleichzeitig erreichbar sind

artifacts/

Cloud Storage

im selben Bucket

clock.py

Cloud Scheduler

ein Cron-Job, der von Google ausgeführt wird. Ihr Terminal ist kein Wecker

Erstellen

Stellen Sie eine PostgreSQL-Datenbank, einen Datenbanknutzer und einen Cloud Storage-Bucket bereit. Das Erstellen der Cloud SQL-Instanz dauert einige Minuten und wurde im Hintergrund während setup.sh initiiert.

👉💻 Sammle es und mache den Rest:

cd ~/monstertix
./setup-cloud-state.sh
→ instance  RUNNABLE
→ database  adk created
→ user      adk created
→ bucket    created
→ memory    memory/userx.md → gs://your-project-agent-you/memory/
✓ durable state ready. Written to .env

Durch das Skript wurden vier Elemente erstellt, eines pro Zeile der Tabelle oben, und zwei URIs wurden in .env geschrieben:

CLOUD_SESSION_SERVICE_URI=postgresql+asyncpg://adk:...@/adk?host=/cloudsql/proj:region:instance
CLOUD_ARTIFACT_SERVICE_URI=gs://your-project-agent-you/artifacts

Agent mit verwalteten geplanten Triggern bereitstellen

Wenn die Dienste bereit sind, geben Sie dem Agenten sein permanentes Zuhause.

👉💻 Drei bis vier Minuten. So gehts:

cd ~/monstertix
. ./set_env.sh
./deploy-agent.sh

Das Script erstellt ein Container-Image aus agent/, stellt es in Cloud Run bereit und stellt die unterstützende ereignisgesteuerte Infrastruktur bereit.

Die vollständige Bereitstellung besteht aus fünf verschiedenen Komponenten:

1. gcloud run deploy       the container: your agent, the graph, and the page
2. a Pub/Sub topic         a queue for messages
3. a service account       an identity, so the message is allowed in
4. a push subscription     topic ──► /trigger/wake
5. a Cloud Scheduler job   the alarm clock, on a cron

Cloud Scheduler als Triggerquelle

Cloud Scheduler ersetzt lokale Ausführungstimer (clock.py) durch einen verwalteten Cron-Dienst, der unabhängig von lokalen Terminalsitzungen bestehen bleibt, automatische Wiederholungen verarbeitet und die Planung nach Zeitzone unterstützt.

Pub/Sub als Message-Entkopplungsebene

Wenn Sie ein Pub/Sub-Thema zwischen Cloud Scheduler und Cloud Run platzieren, ergeben sich drei architektonische Vorteile:

  • Nachrichtensicherheit: Eingehende Triggerereignisse werden in die Warteschlange gestellt, wenn der Cloud Run-Dienst kalt gestartet, neu bereitgestellt oder vorübergehend nicht verfügbar ist.
  • Integrierte Wiederholungsrichtlinien: Vorübergehende Übermittlungsfehler werden automatisch wiederholt, ohne dass eine benutzerdefinierte Wiederholungslogik auf Anwendungsebene erforderlich ist.
  • Fan-Out-Skalierbarkeit: Ermöglicht es mehreren Downstream-Diensten, dieselben Zeitplanereignisse zu abonnieren, ohne dass der Scheduler neu konfiguriert werden muss.

Alt-Text

Durch die Konfiguration von trigger_sources=["pubsub"] wird ein Push-Endpunkt registriert, der mit Standard-Pub/Sub-Envelope-Nutzlasten kompatibel ist. Eingehende Nachrichten werden direkt in Agent-Aufrufereignisse übersetzt.

→ agent    https://concert-you-xxxx.run.app
→ topic    presale-you created
→ iam      concert-trigger@your-project... may invoke concert-you
→ push     → https://concert-you-xxxx.run.app/trigger/wake
→ schedule 0 3 * * *

Mit ihr sprechen

👉 Öffnen Sie die URL des Agents in einem Browser. 👉✨ Bitten Sie Gemini, eine Buchung vorzunehmen:

Book me 3 tickets for The Midnight Signal in Tokyo. Best seats in the house,
I don't want the cheap ones.

Sie sollten gefragt werden, wie viel Sie ausgeben möchten.

👉✨ Antworte so, wie du es laut tun würdest, und bestätige dann:

up to 250 a seat for the good ones

Der Lauf wird derzeit in der Warteschlange gespeichert.

👉🔴 Drücke im Bereich für den Veranstaltungsort auf WARTEN ÜBERSPRINGEN, damit die Warteschlange bereit ist, wenn die Nachricht eingeht.

Damit der Job nach einem echten Zeitplan ausgeführt wird, muss er aktiviert sein. jobs run wird für einen pausierten Job mit Job.state must be ENABLED abgelehnt:

gcloud scheduler jobs resume presale-$(gcloud config get-value account | cut -d@ -f1) \
  --location=$GOOGLE_CLOUD_REGION

👉 Innerhalb weniger Sekunden führt der Agent den Kauf innerhalb der bestätigten Budgetgrenzen aus und gibt automatisch eine Bestellbestätigung zurück.

👉🔴 Prüfen Sie den Bereich „Veranstaltungsort“. Eine Bestellung.

So können Sie festlegen, dass die Funktion alle 5 Minuten ausgeführt wird:

gcloud scheduler jobs update pubsub presale-$(gcloud config get-value account | cut -d@ -f1) \
  --location=$GOOGLE_CLOUD_REGION --schedule="*/5 * * * *"

Bereinigen

Es werden zwei Dinge bereitgestellt: der Veranstaltungsort und der Agent daneben. Beide werden auf null skaliert, sodass für eine im Leerlauf befindliche Instanz keine Kosten anfallen. Löschen Sie sie trotzdem.

./destroy-agent.sh          # service, topic, subscription, scheduler job
./destroy-venue.sh

Cloud SQL und der Bucket werden bewusst nicht berücksichtigt, da sie Ihre Daten enthalten und beide abgerechnet werden. Wenn Sie sicher sind, dass Sie fertig sind:

./destroy-agent.sh --all

Alles andere ist lokal. Löschen Sie den Ordner, wenn Sie fertig sind, oder behalten Sie ihn bei und führen Sie ihn so lange wie gewünscht für Ihr eigenes Projekt aus.

Der Agent, seine Tools, seine Rückrufe und das Budget haben sich nicht geändert. Zwei URIs und eine Umgebungsvariable.

11. Systemübersicht und Anleitung zur Fehlerbehebung

Umgebungsunterschiede: Lokale Architektur im Vergleich zur Cloud-Architektur

Auf Ihrem Laptop

In der Cloud

Was hat sich geändert?

--session_service_uri=sqlite+aiosqlite:///...

Cloud SQL

Verbindungsstring

--artifact_service_uri=file://...

gs://bucket

ein URI

memory/userx.md

gs://bucket/memory/

ein Pfad

adk web

ein Cloud Run-Dienst

Dockerfile und main.py

monstertix/clock.py

Cloud Scheduler → Pub/Sub

trigger_sources=["pubsub"]

Sie an der Tastatur

someone_is_there()

einen Standardwert, der pro Anfrage überschrieben wird

Die Kernimplementierung des Agents, einschließlich root_agent, Tool-Definitionen, Callback-Guards und Budgetdurchsetzung, bleibt unverändert. Der Vergleich von solutions/step10_deploy/concert/ mit Schritt 9 zeigt eine genaue Byte-für-Byte-Übereinstimmung. Dies bestätigt, wie durch die Dienstabstraktion des ADK die Infrastrukturkonfiguration von der Agent-Logik getrennt wird.

Wenn Sie nicht weiterkommen

Sie können in jedem Schritt auf den vollständigen funktionierenden Code zugreifen:

./use-solution.sh N --force          # any step. resets memory + sessions + artifacts
./use-solution.sh N --force --keep   # code only, leave state alone

Wenn etwas kaputt geht

Symptom

Problembehebung

✗ Cannot access project

Falsche ID. rm ~/project_id.txt && ./setup.sh

Modell gibt 404 zurück

-latest-Aliasse sind nur in AI Studio verfügbar. ADK_MODEL=gemini-latest-flash verwenden

address already in use

Etwas hat bereits Port 8000 oder 8090. lsof -ti:8000 | xargs kill -9

Der KI-Agent kann den Veranstaltungsort nicht erreichen

curl $VENUE_URL/health oder ./deploy-venue.sh

Warteschlange wird nicht fortgesetzt

Drücken Sie auf SKIP THE WAIT (Warten überspringen). Bei 1× erreicht die Warteschlange nicht von selbst den Anfang.

Ein zweites Warteschlangenticket wird im Bereich angezeigt.

Sie haben im Chatfeld anstelle des Felds Enter your response... beim Ereignis adk_request_input geantwortet. Im Chatfeld wird ein neuer Lauf gestartet

Das Drop-down-Menü ist leer oder die App wird nicht geladen

adk web behandelt jeden Ordner unter agent/ als App und gibt für die gesamte Liste einen Fehler zurück, wenn dies nicht der Fall ist. In ls agent/ sollte nur concert/ angezeigt werden. Wenn Sie Vorgänge in agent/ ausführen, kann es passieren, dass ein memory/ zurückbleibt. ./use-solution.sh 8 --force räumt auf

[EXPERIMENTAL] Warnungen

Erwartet. EventsCompactionConfig und ResumabilityConfig sind in Version 2.6.2 beide Vorabversionen.

Alles ist seltsam

Drücken Sie auf ORT ZURÜCKSETZEN und dann auf rm sessions.db.

Speicherdatei mit doppelten Buchungen

remember() wird bei jedem Lauf angehängt. ./use-solution.sh N --force setzt sie zurück oder cp seed/memory/default.md memory/userx.md

adk: command not found

source .venv/bin/activate – jeder neue Tab benötigt sie

Zeitüberschreitung bei Cloud Shell

Starten Sie adk web noch einmal. sessions.db hat überlebt – das ist Schritt 6, der für Sie kostenlos erfolgt.