Google Cloud पर लंबे समय तक चलने वाले एजेंट बनाने के लिए ज़रूरी सभी चीज़ें

1. शुरू करने से पहले

ज़्यादातर टिकट बेचने वाली साइटें, खरीदारों को एक लाइन में लगा देती हैं. ऐसा सर्वर क्रैश होने से बचाने और ज़्यादा माँग को मैनेज करने के लिए किया जाता है. आपने खरीदारी शुरू की और आपको पता चला कि आप 14,000 लोगों से पीछे हैं. यह लाइन 40 मिनट तक चलती है. जब आपकी बारी आती है, तो आपके पास सीटें चुनने और पेमेंट करने के लिए कम समय होता है. अगर आपने इस समयसीमा में शामिल होने का अनुरोध नहीं किया, तो आपको लाइन के आखिर में खड़ा होना होगा.

असल दुनिया में कई टास्क इसी पैटर्न को फ़ॉलो करते हैं: ऐसा टास्क जिसे पूरा करने में एक बातचीत से ज़्यादा समय लगता है और जिसे किसी ऐसे बाहरी सिस्टम के साथ चलाया जाता है जिसे कंट्रोल करने का अधिकार आपके पास नहीं होता. उदाहरण के लिए, किसी सप्लायर के पास सामान के फिर से उपलब्ध होने का इंतज़ार करना, दावे को मंज़ूरी मिलने का इंतज़ार करना या लाइन में इंतज़ार करना.

स्टैंडर्ड एआई एजेंट, चैट के एक ही टर्न में काम करते हैं. यह टर्न कुछ सेकंड तक चलता है और पूरी तरह से रिएक्टिव होता है. ये 40 मिनट तक इंतज़ार नहीं कर सकते या अपने-आप काम नहीं कर सकते. इस समस्या को हल करने के लिए, आपको लंबे समय तक काम करने वाले एजेंट की ज़रूरत होती है. यह एक ऐसा सिस्टम होता है जो लंबे समय तक इंतज़ार करने के दौरान बंद रहता है. साथ ही, प्रोसेस के रीस्टार्ट होने पर भी कॉन्टेक्स्ट को बनाए रखता है. इसके अलावा, बाहरी ट्रिगर से चालू होने पर अपने-आप शुरू हो जाता है.

इस वर्कशॉप में, आपको कॉन्सर्ट के टिकट की बुकिंग के लिए एक एजेंट बनाने का तरीका बताया गया है. इसमें, Google Cloud पर लंबे समय तक काम करने वाले एजेंट के लिए ज़रूरी मुख्य आर्किटेक्चरल पैटर्न के बारे में बताया गया है.

आपको क्या बनाना है

आपको क्या बनाना है

इस वर्कशॉप में, आपको टिकट बुक करने वाले एक ऐसे एजेंट को बनाने का तरीका सिखाया जाएगा जो लंबे समय तक काम कर सकता है. साथ ही, आपको एजेंटिक सॉफ़्टवेयर के मुख्य पैटर्न के बारे में भी जानकारी मिलेगी:

  • प्रोसेस को रोकना और फिर से शुरू करना: लंबे इंतज़ार के दौरान, एजेंट को कॉल करने की प्रोसेस को रोकें और बाहरी ट्रिगर के ज़रिए इसे फिर से शुरू करें.
  • Durable State Storage: SQLite/Cloud SQL और Cloud Storage का इस्तेमाल करके, प्रोसेस रीस्टार्ट होने पर भी बातचीत की स्थिति और आर्टफ़ैक्ट बनाए रखें.
  • पुराने डेटा से बचाव और एक ही लेन-देन को कई बार पूरा होने से रोकना: कार्रवाई करने से पहले, डाइनैमिक डेटा (जैसे, सीट की उपलब्धता) की पुष्टि करें. साथ ही, डुप्लीकेट लेन-देन को रोकने के लिए, एक ही लेन-देन को कई बार पूरा होने से रोकने वाली कुंजियों का इस्तेमाल करें.
  • ADK वर्कफ़्लो ग्राफ़: प्रक्रिया से जुड़े नियमों के लिए, डिटरमिनिस्टिक कोड नोड और कॉन्टेक्स्ट के हिसाब से फ़ैसले लेने के लिए, एजेंट नोड का इस्तेमाल करके एक्ज़ीक्यूशन को स्ट्रक्चर करें.
  • इंसान की निगरानी में अनुमति: उपयोगकर्ता से खर्च की सीमाएं इकट्ठा करने और उन्हें सेव करने के लिए, कार्रवाई को रोकें.
  • क्लाउड डिप्लॉयमेंट: एजेंट को कंटेनर में बदलें और Cloud Run पर डिप्लॉय करें. इसके लिए, Cloud Scheduler और Pub/Sub ट्रिगर का इस्तेमाल करें.

सेट अप करें

सबसे पहले, अपना एनवायरमेंट तैयार करें. साथ ही, पक्का करें कि आपके पास Google Cloud का ऐक्सेस हो. हमारा सुझाव है कि इसे Cloud Shell में चलाएं. इसमें पहले से ही वे सभी टूल मौजूद हैं जिनकी आपको ज़रूरत है. बस यह देख लें कि आपके पास ज़रूरत के मुताबिक स्टोरेज हो और आपके खाते से प्रोजेक्ट बनाए जा सकते हों.

👉💻 रेपो को क्लोन करें और सेटअप चलाएं:

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

यह आपसे एक बार यह सवाल पूछेगा:

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

  Project id [YOUR_PROJECT_ID]:

इसके बाद, यह इन कार्रवाइयों को इस क्रम में करता है:

virtualenv

.venv बनाता है और उसमें ADK 2 इंस्टॉल करता है

साइन-इन

अगर आपके पास अब तक कोई क्रेडेंशियल नहीं है, तो gcloud auth application-default login

प्रोजेक्ट

पॉइंट gcloud आपके प्रोजेक्ट पर ले जाता है और उसे कोटा प्रोजेक्ट के तौर पर सेट करता है

एपीआई

इससे वर्कशॉप के लिए ज़रूरी दस सुविधाएं चालू होती हैं. aiplatform, run, cloudbuild, artifactregistry, storage, pubsub, cloudscheduler, cloudtrace, logging और sqladmin.

.env

यह कुकी, आपके प्रोजेक्ट, क्षेत्र, और मॉडल आईडी को लिखती है, ताकि हर स्क्रिप्ट और adk web उन्हें चुन सके

मॉडल की जांच करना

Gemini को एक कॉल करता है, इसलिए यहां गलत प्रोजेक्ट या मॉडल आईडी काम नहीं करता

seed

यह दो दिन पहले शुरू हुए सेशन को फिर से शुरू करता है. इसे तीसरे चरण में खोला जाता है

Cloud SQL

यह कमांड, Postgres इंस्टेंस को बैकग्राउंड में शुरू करती है

जगह

यह Cloud Run पर, टिकट बेचने वाले अपने मॉक सेलर को डिप्लॉय करता है

👉💻 आस-पास की आवाज़ के हिसाब से, ऑडियो अडजस्ट करने की सुविधा चालू करें:

cd ~/monstertix
source .venv/bin/activate

अब आपका प्रॉम्प्ट (.venv) से शुरू होता है.

अलग-अलग स्थिति के लिए बजट प्लान की खास जानकारी

इस वर्कशॉप के उदाहरण में, आपका एजेंट कॉन्सर्ट ("द मिडनाइट सिग्नल" इन एम्सटर्डम) के लिए दो टिकट बुक करता है. उपयोगकर्ता की प्राथमिकताओं को पुरानी मेमोरी में सेव किया जाता है. इनमें ये शामिल हैं:

  • यह शो वीकेंड पर होना चाहिए, क्योंकि सैम वीकनाइट पर नहीं आ सकता.
  • ज़िगो डोम में सबसे ऊपर की सीट न लें, क्योंकि वहां से ठीक से नहीं दिखता.
  • टारगेट कीमत, हर टिकट के लिए करीब 200 डॉलर है. इसकी ज़्यादा से ज़्यादा सीमा 250 डॉलर है.

The Midnight Signal, ऑन टूर

अपने इंस्टॉलेशन की पुष्टि करना

👉💻 एक निर्देश, आठ टिक:

./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.

👉 दूसरे ब्राउज़र टैब खोलें:

कहां

यह क्या है

$VENUE_URL/panel

कंट्रोल पैनल, आपके बटन. setup.sh ने इस यूआरएल को प्रिंट करके .env में रखा

2. क्या प्रॉम्प्ट के ज़रिए अपनी मर्ज़ी से काम करने की आज़ादी पाई जा सकती है?

प्रॉम्प्ट अपने-आप शुरू नहीं होते

कॉल के दौरान, एआई मॉडल को भेजे गए टेक्स्ट को प्रॉम्प्ट कहा जाता है. यह अपने-आप नहीं चल सकता, अपने-आप नहीं चालू हो सकता या नया कॉल शुरू नहीं कर सकता.

अगर आपने प्रॉम्प्ट में किसी एजेंट को "10 मिनट बाद फिर से जांच करने" के लिए कहा है, तो मौजूदा बातचीत खत्म होने के बाद कुछ नहीं होगा. मॉडल में कोई इंटरनल क्लॉक या लूप नहीं है. किसी एजेंट को ऑटोनॉमस बनाने के लिए, एजेंट के बाहर की किसी चीज़ को उसे चालू करना होगा और उसे कॉल करना होगा.

यह चरण कहां दिखता है

रिग शुरू करना

हम डेवलपर टूलिंग की जांच करके शुरुआत करेंगे. adk web, ADK के वेब यूज़र इंटरफ़ेस (यूआई) को शुरू करता है. यह डेवलपमेंट इंटरफ़ेस, ADK के साथ शिप होता है. इसकी मदद से, फ़्रंटएंड लिखे बिना ही, बनाए जा रहे एजेंट से बात की जा सकती है. साथ ही, इसमें आपको वह जानकारी दिखती है जो चैट विंडो में नहीं दिखती. जैसे, हर टूल कॉल और उसके तर्क, सेशन की स्थिति में होने वाले बदलाव, एजेंट के सेव किए गए आर्टफ़ैक्ट, और नीचे दिए गए रॉ इवेंट. इसे प्रोडक्शन में नहीं दिखाया जाएगा. यह आपको एजेंट के बारे में सबसे अच्छी जानकारी देता है, ताकि आप उसे बेहतर तरीके से समझ सकें. तीसरे चरण में, आपको वह फ़्रंटएंड बनाना होता है जिसे आपको लोगों के लिए उपलब्ध कराना है. इसके बाद, आखिरी चरण में उसे डिप्लॉय करना होता है.

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 टर्मिनल 1 — इस चरण का कोड लोड करें और ADK वेब यूज़र इंटरफ़ेस (यूआई) शुरू करें:

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

use-solution.sh, किसी चरण के पूरे हो चुके कोड को agent/ में कॉपी करता है. वह जगह है जहां आपको काम करना है. हर चरण इसी से शुरू होता है. साथ ही, यह उन फ़ाइलों को प्रिंट करता है जिनमें बदलाव किया गया है, ताकि आपको पता चल सके कि आपको क्या पढ़ना है.

इनसे शुरुआत करें:

फ़ाइल

यह क्या है

agent.py

एजेंट — नाम, मॉडल, निर्देश, टूल की सूची

tools.py

search_events और get_seatmap

venue.py

जगह के लिए एचटीटीपी क्लाइंट. कुछ भी दिलचस्प नहीं है

config.py

किस मॉडल का इस्तेमाल करना है और यह जांच करना कि आपके क्रेडेंशियल काम कर रहे हैं

. ./set_env.sh वर्चुअल एनवायरमेंट को चालू करता है, आपके प्रोजेक्ट और मॉडल को लोड करता है, और $WORKSHOP को एक्सपोर्ट करता है. यह ~/monstertix का पूरा पाथ होता है. इसे खोले गए हर टर्मिनल में चलाएं. यह सेट की गई वैल्यू को प्रिंट करता है:

  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

👉 पहले ब्राउज़र टैब खोलें:

कहां

यह क्या है

ADK का वेब यूज़र इंटरफ़ेस (यूआई)

localhost:8000 — आपका एजेंट, साथ ही इसके स्टेट / इवेंट / आर्टफ़ैक्ट टैब

इससे बात करना

👉✨ ADK के वेब यूज़र इंटरफ़ेस (यूआई) में, यह टाइप करें:

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

यह टूर खोजता है, सीट के मैप पढ़ता है, और आपके बजट और आपकी कंपनी के बारे में जानकारी देता है. यह वाकई एक बेहतरीन असिस्टेंट है. हालांकि, जब सुबह 10:00 बजे प्रीसेल शुरू होती है, तो एजेंट अपने-आप काम नहीं करता. ऐसा इसलिए होता है, क्योंकि कोई प्रोसेस या बैकग्राउंड ट्रिगर चालू नहीं होता.

जानें कि अभी क्या हुआ

स्क्रीन के बीच में सिर्फ़ चैट लॉग नहीं होता. जवाब देने के हर चरण को नंबर दिया जाता है. इसमें टूल कॉल भी शामिल हैं:

  ▸ 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

👉 ⚡ वाली किसी लाइन पर क्लिक करें. बाईं ओर मौजूद पैनल में, मॉडल के चुने गए तर्क दिखते हैं. वापस लाए गए JSON के लिए, उसके नीचे मौजूद ✓ पर क्लिक करें.

मॉडल के ट्रेनिंग डेटा या प्रॉम्प्ट में टूर की जानकारी मौजूद नहीं है. एजेंट ने search_events टूल का इस्तेमाल करके यह डेटा हासिल किया. इस टूल ने जगह के एपीआई से क्वेरी की थी.

अभी-अभी चलाया गया कोड खोलें

आपने इसे काम करते हुए और ट्रेस को देखा है. अब कोड को देखें.

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

👉💻 agent/concert/agent.py खोलें. पूरा एजेंट, फ़ाइल के सबसे नीचे होता है:

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],
)

नाम, मॉडल, एजेंट को कैसे काम करना चाहिए, इसके बारे में कुछ जानकारी, और Python फ़ंक्शन की सूची. Agent यही है. adk web को यह इसलिए मिला, क्योंकि फ़ाइल का नाम agent.py है और वैरिएबल का नाम root_agent है.

👉💻 अब 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)

एक सामान्य फ़ंक्शन. कोई डेकोरेटर नहीं है, कोई रजिस्ट्रेशन नहीं है, और लिखने के लिए कोई स्कीमा नहीं है. ADK, हस्ताक्षर और docstring को पढ़ता है. साथ ही, मॉडल के लिए टूल की परिभाषा बनाता है. इसलिए, docstring बहुत ज़रूरी है. साथ ही, किसी एक तर्क को मिटाने से मॉडल के व्यवहार में बदलाव होता है.

मॉडल, आपके कोड को कभी नहीं चलाता. जब आपको search_events({"city": "Amsterdam"}) दिखा, तब मॉडल उस कॉल के लिए अनुरोध कर रहा था. ADK ने फ़ंक्शन चलाया और नतीजे वापस दिए. इसके बाद, मॉडल ने इसके बारे में एक वाक्य लिखा. लूप यह है:

👉 एकदम बाईं ओर मौजूद आइकॉन से, पैनल में दिखने वाली जानकारी को बदला जा सकता है. दूसरा, एजेंट ग्राफ़ है. इसे आज़माएं. आपको दो टूल दिखेंगे.:

search_events और get_seatmap टूल दिखाने वाला एजेंट ग्राफ़

इसलिए, इसे स्लीप मोड में न जाने दें

इसे ठीक करने के लिए, आपको क्या करना चाहिए? सबसे पहले, वह तरीका आज़माएं जिसे ज़्यादातर लोग आज़माते हैं. पहले चरण में दिए गए INSTRUCTION को याद करें. यह वह गद्य है जो हर बार मॉडल को दिया जाता है. इसे फिर से लिखें.

👉💻 agent/concert/agent.py खोलें. यहाँ INSTRUCTION के नीचे, दूसरा जवाब दिया गया है. यह जवाब आपके लिए पहले से लिखा गया है. अगर आपको यह पसंद नहीं है, तो इसे खुद बदलकर देखें:

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],
)

👉💻 टर्मिनल 1 में, इसे रीस्टार्ट करें:

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

👉✨ इसे ठीक उसी तरह प्लान के बारे में बताएं जिस तरह किसी व्यक्ति को बताया जाता है:

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.

यह आपकी बात से सहमत होगा और ऐसा लगेगा कि यह वाकई में आपकी बात से सहमत है.

👉 अब इंतज़ार करें. कोई और टेक्स्ट टाइप न करें. पैनल देखें.

कुछ समय इंतज़ार करें. कुछ नहीं होता!

वह प्रॉम्प्ट कभी काम क्यों नहीं करेगा

इसे फिर से पढ़ें. मॉनिटर करें. जैसे ही यह खुलता है. देखते रहें. इनमें से हर एक, समय के साथ होने वाली किसी गतिविधि के बारे में बताता है.

अब याद करें कि instruction= क्या है. यह एक स्ट्रिंग है, जिसे अनुरोध के हिस्से के तौर पर मॉडल को दिया जाता है. जब कोई व्यक्ति मैसेज भेजता है, तब यह स्ट्रिंग मॉडल को दी जाती है. बारी के बीच में कोई भी इसे नहीं पढ़ता है.

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

कोई निर्देश, उस फ़ंक्शन को लागू नहीं कर सकता जिसके अंदर उसे लिखा गया है.

प्रॉम्प्ट के ऐसे वर्शन जो काम नहीं करते

प्रॉम्प्ट में दिए गए निर्देशों के ज़रिए, अपने-आप कार्रवाई करने की सुविधा को लागू करने की कोशिश करने पर, कार्रवाई नहीं होती. भले ही, प्रॉम्प्ट में इस्तेमाल किए गए शब्दों का मतलब कुछ भी हो. प्रॉम्प्ट के लिए सामान्य तौर पर किए जाने वाले अनुरोध, जैसे कि:

  • "मंगलवार को सुबह 10:00 बजे का टाइमर सेट करो और फिर टिकट खरीदो."
  • "हर पांच मिनट में यह जांच करो कि प्रीसेल शुरू हुई है या नहीं."
  • "उपयोगकर्ता के इनपुट का इंतज़ार किए बिना, अपने-आप कार्रवाई करें."

ये सभी एक ही वजह से काम नहीं करते: प्रॉम्प्ट सिर्फ़ तब पढ़े जाते हैं, जब उन्हें चालू किया जाता है. बाहरी कॉलर के बिना, टर्न के बीच कोई कोड नहीं चलाया जाता.

3. इवेंट के आधार पर चैनल ऐक्टिव नहीं है

एजेंट हमेशा के लिए चालू नहीं रहते

किसी प्रोसेस को लगातार पोलिंग करते हुए छोड़ने से, मेमोरी की खपत होती है और बैटरी खत्म होती है. साथ ही, अगर आपका लैपटॉप स्लीप मोड में चला जाता है या रीस्टार्ट हो जाता है, तो प्रोसेस क्रैश हो जाती है.

जब कोई एजेंट किसी काम के पूरा होने का इंतज़ार कर रहा हो, तो उसे लगातार चालू रखने के बजाय, लंबे समय तक चलने वाले एजेंट को पूरी तरह से बंद कर देना चाहिए. कोई बाहरी इवेंट (जैसे कि टाइमर, वेबहुक या मैसेज) एजेंट को सिर्फ़ तब चालू करता है, जब उसे कोई काम करना होता है.

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 इस चरण का कोड लोड करें:

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

ध्यान दें कि इसमें क्या-क्या इंस्टॉल किया गया है: monstertix/, सर्वर, घड़ी, और एक और चीज़ जो आपने अब तक नहीं देखी है. देखें कि इससे agent/ पर कोई असर न पड़ा हो, क्योंकि इस चरण में एजेंट के बारे में कोई बदलाव नहीं किया जाता. इसे ध्यान में रखें, यह ज़रूरी है.

फ़ाइल

यह क्या है

monstertix/server.py

नया. एक वेब सर्वर, जिसके पीछे रनर है

monstertix/clock.py

नया. ऐसी चीज़ जो समय के बारे में जानती है और सर्वर को कॉल करती है

इसके दो हिस्से होते हैं और ये दोनों हिस्से क्लाउड तक दो फ़ाइलों में रहते हैं: ट्रिगर और ट्रिगर करने वाले को अलग-अलग रखा गया है

के बराबर हो जाता है.

ट्रिगर

ऐसा एंडपॉइंट जो एजेंट को चला सके

Cloud Run

ट्रिगर करने वाला

घड़ी की सुविधा वाला कोई डिवाइस, जो इसे

Cloud Scheduler

ट्रिगर — आपका अपना वेब सर्वर

हां, एक वेब सर्वर. किसी भी डिवाइस को कॉल करने से पहले, यह ज़रूरी है कि कोई डिवाइस सुन रहा हो. पिछले चरणों में इस्तेमाल किया गया ADK वेब यूज़र इंटरफ़ेस, डेवलपमेंट इंटरफ़ेस है. यह एजेंट को चलाने में मदद करता है.

👉💻 monstertix/server.py खोलें:

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"])]),
    ):
        ...

Agent अपने-आप नहीं चलता. यह एक ब्यौरा है: नाम, मॉडल, निर्देश, टूल की सूची. किसी सिस्टम को यह काम करना होता है: आने वाले मैसेज को लेना, सही सेशन ढूंढना, मॉडल को अब तक की बातचीत सौंपना, मॉडल के अनुरोध किए गए टूल को लागू करना, नतीजों को वापस भेजना, टूल को कॉल करना बंद करने तक लूप करना, और उस प्रोसेस के हर चरण को स्टोरेज में लिखना. वह Runner है.

रनर, एजेंट के आस-पास क्या करता है

ADK के वेब यूज़र इंटरफ़ेस (यूआई) में आपके लिए डिफ़ॉल्ट तौर पर एक उपलब्ध होता है. यहां आपको इसे खुद बनाना होता है. ADK वेब यूज़र इंटरफ़ेस (यूआई) और डिप्लॉय की जा सकने वाली किसी चीज़ के बीच यही अंतर है.

👉💻 टर्मिनल 2 — इसे शुरू करें:

Start the 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.

उस आखिरी लाइन को पढ़ो. ट्रिगर चालू है और एजेंट लोड हो गया है, लेकिन कुछ नहीं हो रहा है. कॉल किए जाने और कॉल करने की सुविधा, इन दोनों आइडिया को अलग-अलग करें.

और सामने का दरवाज़ा, जब हम यहां हैं

adk web एक बेहतरीन डेवलपमेंट टूल है. हालांकि, इसे शिप नहीं किया जाना चाहिए. प्रोडक्शन में डिप्लॉय करते समय, इसे चालू नहीं करना चाहिए. अगर ऐसा होता है, तो आपको किसी ग्राहक को इसकी जानकारी नहीं देनी चाहिए. इसलिए, एक ही सर्वर पेज भी दिखाता है.

👉 ब्राउज़र में http://127.0.0.1:8090 खोलें. यह वही पता है.

MonsterTix के बारे में जानकारी. एक चैट विंडो, कोने में एक नाम, दो बटन. यह /wake पर पोस्ट करता है और जवाब को रेंडर करता है. साथ ही, यह उसी तरह काम करेगा जैसे किसी ऐसे एंडपॉइंट के ख़िलाफ़ काम करता है जो इसी तरह जवाब देता है.

पेज पर

इसके नीचे क्या है

बातचीत

POST /wake, एक बार में एक मैसेज

+ नया सेशन

नया सेशन आईडी — नई बातचीत और बाद में नया बजट

मेमोरी रीसेट करें

यह कुकी, हर सेशन और मेमोरी फ़ाइल को मिटा देती है. यह पहले पूछता है और आपको बताता है कि वह राज्य कहां है

👉✨ इससे कुछ कहो — What Amsterdam dates are there? — और टर्मिनल 2 देखो. एक जैसा Runner, एक जैसा एजेंट, और एक जैसी लॉग लाइनें. सिर्फ़ कॉल करने वाले का नाम बदला गया है.

ट्रिगर करने वाला — कुछ ऐसा जिसे समय के बारे में पता हो

👉💻 monstertix/clock.py खोलें. कोड देखें.

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

यह बहुत आसान तरीका है: सोएं, पोस्ट करें, शायद दोहराएं. आपने जिस भी शेड्यूलर का इस्तेमाल किया है वह यही काम करता है. साथ ही, इसमें फिर से कोशिश करने, समय क्षेत्र, और रीस्टार्ट होने के बाद भी काम करने की गारंटी होती है.

👉💻 टर्मिनल 3 — अभी से 10 सेकंड बाद एक बार फ़ायर करें:

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

👉 अब टाइप करना बंद करें. हाथ नीचे करो. टर्मिनल 2 पर जाएं.

एक मिनट बाद, जब कीबोर्ड पर कोई नहीं था, तब:

[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.

यह दूसरे चरण का जवाब है. इसे एजेंट के बाहर से कॉल किया गया था. यह कॉल, sleep और एचटीटीपी अनुरोध के ज़रिए किया गया था.

👉💻 Ctrl-C दोनों तब दिखते हैं, जब आपने वीडियो को कुछ बार देखा हो. अगर आपको इसे फिर से देखना है, तो पहले --every 30 का इस्तेमाल करें.

अब पढ़ो कि उसने क्या कहा

"मैंने एम्सटर्डम में होने वाले द मिडनाइट सिग्नल के दो टिकट खरीदे हैं. ये टिकट सेक्शन A के लिए हैं और इनकी कीमत 420 डॉलर है."

👉🔴 वेन्यू पैनल देखें. आपको दिखेगा कि टिकट खरीदा जा चुका है.

अब भी क्या गलत है

इसने कुछ खरीदा. देखें कि इसे क्या अनुमान लगाना पड़ा. इसने एम्स्टर्डम और सेक्शन A को चुना, क्योंकि ये विकल्प इसे सबसे पहले मिले थे. आपने एजेंट को सीधे तौर पर इसे खरीदने के लिए कहा था. साथ ही, इसे यह नहीं पता कि सैम वीकनाइट में नहीं जा सकता. रात 3 बजे अनुमान के आधार पर काम करने वाला एजेंट, कुछ भी न करने वाले एजेंट से ज़्यादा खराब होता है.

नींद से जागना आसान है. जिस एजेंट के पास कोई इतिहास नहीं है, कोई प्राथमिकताएं नहीं हैं, और जिसे कोई नहीं सुन रहा है उसे अभी तक चालू करने लायक नहीं माना जाता.

4. कॉन्टेक्स्ट के लाइफ़टाइम को मैनेज करना

जानकारी अलग-अलग समय के लिए अलग-अलग जगहों पर सेव होती है

एजेंट, तथ्यों को कई जगहों पर सेव करता है: चैट बातचीत में, सेशन की स्थिति में, फ़ाइलों (आर्टफ़ैक्ट) में या उपयोगकर्ता की लॉन्ग-टर्म मेमोरी में. हर जगह अलग-अलग समय तक रहती है.

अगर आपने जानकारी को गलत जगह पर डाला है, तो:

  • यह बहुत जल्दी गायब हो जाती है: चैट खत्म होने पर, अहम जानकारी मिट जाती है.
  • यह जानकारी लंबे समय तक बनी रहती है: कुछ समय के लिए उपलब्ध जानकारी, लंबे समय तक सेव रहने वाली मेमोरी में शामिल हो जाती है. इससे आने वाले समय में होने वाली बातचीत में भ्रम पैदा होता है.

आपको हर जानकारी को सही स्टोरेज प्लेस और लाइफ़टाइम से मैच करना होगा.

कोड लोड करना

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं.

👉💻 टर्मिनल 1 में, यह कमांड चलाएं:

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

पिछले चरण के बाद क्या बदलाव हुआ:

फ़ाइल

क्या बदलाव हुए

tools.py

note_companion को जोड़ा गया. get_seatmap अब सीट मैप को किसी फ़ाइल में सेव करता है

memory.py

नया. हमारा मेमोरी स्टोर

agent.py

सूची में तीन और टूल हैं. साथ ही, निर्देश में एजेंट को उनका इस्तेमाल करने के लिए कहा गया है

👉💻 टर्मिनल 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"

ऐसे दो फ़्लैग जिनका आपने पहले इस्तेमाल नहीं किया है:

झंडा

यह क्या है

session_service_uri

ADK, हर मैसेज, टूल कॉल, और स्थिति में हुए बदलाव को पंक्तियों के तौर पर लिखता है. यह SQLite डेटाबेस होता है.

artifact_service_uri

वे फ़ाइलें जिन्हें एजेंट सेव करता है.(फ़ाइलें)

इनके बिना, ADK सभी चीज़ों को मेमोरी में सेव करके रखता है. जब प्रोसेस बंद हो जाती है, तब वह उन्हें मिटा देता है. इन दो सेटिंग की वजह से, यह कुकी बनी रहती है.

डेटाबेस sessions.db में, पहले से सेव की गई पिछली बातचीत (two-days-ago) शामिल है. इसमें 13 रिकॉर्ड किए गए इवेंट हैं:

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?

पिछली तीन बुकिंग की मेमोरी फ़ाइल, memory/userx.md, जिसे एडिटर में खोला जा सकता है:

- 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.

इसलिए, रात में कम रोशनी वाली एंबर रंग की लाइटिंग की अहमियत बढ़ जाती है. वीकनाइट शो की टिकट, सीट के हिसाब से 10 डॉलर सस्ती है. इसमें बेहतर सीटें उपलब्ध हैं और यह सूची में सबसे ऊपर है. हालांकि, यह व्यक्ति के लिए सही जवाब नहीं है. एजेंट को इस बारे में सिर्फ़ इसलिए पता चल सकता है, क्योंकि उसने पिछली बुकिंग के बाद एक फ़ाइल लिखी थी. चौथे और पांचवें चरण में यह तय किया जाता है कि उस तथ्य को शामिल किया जाए या नहीं.

हमने तीन टूल जोड़े हैं

तीनों टूल और उनके इस्तेमाल से जनरेट होने वाला कॉन्टेंट

note_companion रिकॉर्ड करता है कि कौन आ रहा है और उसे क्या सीमित करता है:

# 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}

इसे जोड़ें, क्योंकि "सैम सोमवार से शुक्रवार की शाम को नहीं आ सकता" यह बात अगले हफ़्ते भी सही होनी चाहिए. ऐसा करने के लिए, इसे user:prefs में लिखें. इसके बाद, नाम के user: हिस्से को काम करते हुए देखें. इसके बारे में यहां और बताया गया है.

recall

और

remember टेक्स्ट फ़ाइल को पढ़ने और लिखने की अनुमति, memory/userx.md. इन पोज़िशन को कुछ देर तक होल्ड करें.

👉 अब उन दो जगहों को देखें जहां एजेंट, कॉन्टेक्स्ट को सेव करता है:

जगह

इसमें क्या-क्या शामिल होता है

राज्य

इस सेशन की मेमोरी, की/वैल्यू पेयर के तौर पर — आपको यहां चौथे चरण में रहना है

आर्टफ़ैक्ट

वे फ़ाइलें जिन्हें एजेंट ने सेव किया है. ये फ़ाइलें कभी भी प्रॉम्प्ट में शामिल नहीं होती हैं

देखें कि

get_seatmap

. इससे पहले, यह फ़ंक्शन सीट का पूरा मैप दिखाता था. अब यह सीट मैप को किसी फ़ाइल में सेव करता है और इसकी खास जानकारी देता है:

# 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

इस बदलाव के बारे में यहां बताया गया है.

हर टूल के जवाब का पेलोड, बातचीत के इतिहास में जोड़ दिया जाता है. इसके बाद, बातचीत के हर अगले चरण में इसे मॉडल को फिर से भेजा जाता है. बड़े JSON ऑब्जेक्ट (जैसे, 6 केबी का सीट मैप) वापस भेजने से, हर बार टोकन का इस्तेमाल बढ़ जाता है.

इसे किसी फ़ाइल में सेव करें और एक फ़ाइल नाम के लिए पेमेंट करें. अगर एजेंट को जानकारी की ज़रूरत है, तो उसे load_artifact के साथ जानकारी वापस मांगने दें. हालांकि, ज़्यादातर मामलों में ऐसा नहीं होगा.

फ़ाइल कहां जाती है: आर्टफ़ैक्ट सेवा

agent/concert/tools.py में, get_seatmap में मौजूद लाइन को फिर से देखें:

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

tool_context, ADK से मिलता है. यह इसे आपके टूल में एक तर्क के तौर पर पास करता है. साथ ही, save_artifact इस पर एक तरीका है. इस लाइन में, आपका टूल ADK को बाइट भेजता है.

इसके बाद, ADK उन्हें लिखता है. इसके लिए, वह उसी आर्टफ़ैक्ट सेवा का इस्तेमाल करता है जिससे इसे शुरू किया गया था. आपने फ़्लैग करके यह विकल्प चुना था:

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

ADK में, इन्हें सेव करने के लिए तीन जगहें होती हैं:

यूआरआई

बाइट कहां जाते हैं

memory://

रैम. प्रोसेस बंद करने पर यह हट जाता है. अगर कोई फ़्लैग पास नहीं किया जाता है, तो यह डिफ़ॉल्ट रूप से लागू होता है

file://...

डिस्क पर मौजूद फ़ोल्डर. फ़िलहाल, इस्तेमाल की जा रही सुविधा

gs://bucket

Cloud Storage. डिप्लॉय किया गया एजेंट, आखिरी चरण में किस चीज़ का इस्तेमाल करता है

👉💻 यहां फ़ाइलें चुनी जाती हैं:

ls -R ~/monstertix/artifacts

लैपटॉप से Cloud Storage पर फ़ाइलें ट्रांसफ़र करने के लिए, file:// को gs:// पर बदलें. (हम इसे 10वें चरण में देखेंगे)

हमारी मेमोरी स्टोर करने की सुविधा

इंटरफ़ेस के तौर पर मेमोरी, जिसके पीछे एक फ़ाइल है

ADK, आपको BaseMemoryService नाम का एक इंटरफ़ेस देता है. इसका इस्तेमाल, लंबे समय तक याद रखने वाली मेमोरी के लिए किया जाता है. इसके दो तरीके हैं:

# 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

ADK, इसके तीन वर्शन उपलब्ध कराता है:

कक्षा

यूआरआई

इसके लिए क्या ज़रूरी है

InMemoryMemoryService

memory://

कुछ नहीं. प्रोसेस रोकने पर, सब कुछ मिट जाता है

VertexAiRagMemoryService

rag://

Vertex AI RAG कॉर्पस

VertexAiMemoryBankService

agentengine://

एजेंट इंजन संसाधन

हम इस वर्कशॉप के लिए, खुद ही प्रॉम्प्ट लिखते हैं. InMemoryMemoryService को हटा दें, क्योंकि प्रोसेस बंद होने पर यह सब कुछ भूल जाता है. साथ ही, अन्य दो को भी हटा दें, क्योंकि इन्हें क्लाउड संसाधनों की ज़रूरत होती है. इन संसाधनों को बनाने में कुछ मिनट लगते हैं. इसके बजाय, कोई फ़ाइल चुनें. ऐसा इसलिए, क्योंकि इसे पहले से ही सीड किया जा सकता है और एडिटर में पढ़ा जा सकता है. इसलिए, memory.py को अपने हिसाब से लागू करें. हम सभी चीज़ों को Markdown फ़ाइल में सेव कर रहे हैं:

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 और MemoryEntry, ADK के टाइप हैं. साथ ही, मेमोरी बैंक एक ही ऑब्जेक्ट दिखाता है. एक के पीछे मैनेज की जा रही सेवा है. हमारे पास एक फ़ाइल है.

किसी फ़ाइल को चुनने की एक वजह यह है कि उसे खोला जा सकता है और पढ़ा जा सकता है. आपके एजेंट की पूरी लॉन्ग-टर्म मेमोरी, टेक्स्ट का एक पेज होती है.

तो इसे कहां पॉइंट किया जाता है?

इस वर्कशॉप में, कमांड लाइन पर तीन में से दो सेवाओं को चुना गया है. तीसरे के लिए, हमने ऊपर बताई गई वजह से, खुद ही इसे लागू किया:

सेवा

इसे कहाँ सेट किया जाता है

इस वर्कशॉप में

सेशन

adk web पर --session_service_uri

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

आर्टफ़ैक्ट

adk web पर --artifact_service_uri

file://$WORKSHOP/artifacts

मेमोरी

कमांड लाइन में कहीं भी नहीं

MEMORY_DIR, agent/concert/memory.py में पढ़ा गया

इसलिए, memory.py एनवायरमेंट वैरिएबल से सेवा बनाता है:

# 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 एक्सपोर्ट किए गए दोनों आइटम. MEMORY_DIR सेट करें, ताकि एजेंट को पता चले कि उसे अपनी मेमोरी से क्या पढ़ना है. साथ ही, MEMORY_USER तय करें कि कौनसी फ़ाइल पढ़नी है.

जाकर देखें कि चीज़ें कहां हैं

अब आपके पास चार ऐसी जगहें हैं जहां तथ्य को रखा जा सकता है. इन चारों को देखें.

सबसे पहले, अपना सेशन

👉 सबसे ऊपर मौजूद बार में, नया सेशन पर क्लिक करें.

👉✨ एक के बाद एक तीन चीज़ों के नाम बोलें:

Sam can't do weeknights, by the way.

नया सेशन, पहला टर्न

Show me the seat map for the Amsterdam Saturday show.

नया सेशन, दूसरा टर्न

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

तीसरा मैसेज अहम है. इससे सीट मैप पढ़ने वाला टर्न खत्म हो जाता है. टर्न खत्म होने पर ही temp: दिखता है.

तीन चीज़ें तीन अलग-अलग जगहों पर सेव की गईं:

क्या सेव किया गया था

किस टूल ने ऐसा किया

यह कहां गया

1

सैम, हफ़्ते के दिनों में नहीं आ सकता

note_companion

user:prefs, सेशन की स्थिति में

2

सीट का पूरा मैप, 6 केबी

get_seatmap, save_artifact के ज़रिए

आर्टफ़ैक्ट सेवा

3

सीट के मैप की एक लाइन में दी गई खास जानकारी

get_seatmap, temp:seatmap में

कहीं नहीं

👉 अब देखें. स्टेट टैब और आर्टफ़ैक्ट टैब, adk web के बाईं ओर मौजूद हैं.

राज्य टैब

आर्टफ़ैक्ट टैब

स्टेट टैब में, ध्यान दें कि temp:seatmap को शामिल नहीं किया गया है. temp: प्रीफ़िक्स वाली स्टेट की, सिर्फ़ चालू इनवोकेशन के दौरान मौजूद होती हैं. साथ ही, सेशन की स्थिति को डेटाबेस में सेव करने से पहले, ADK इन्हें अपने-आप हटा देता है.

इसके बाद, पुरानी बातचीत

👉 सबसे ऊपर मौजूद सेशन के ड्रॉपडाउन में जाकर, two-days-ago पर स्विच करें.

इस सेशन में, डेटाबेस में पहले से मौजूद बातचीत का इतिहास शामिल है.

दो दिन पहले का सेशन

👉 इवेंट स्ट्रीम (बीच का कॉलम) देखें. ध्यान दें कि पुरानी बारी की जानकारी को, खास जानकारी वाले इवेंट ब्लॉक से बदल दिया गया है.

जवाब को छोटा करना

👉 स्टेट टैब देखें. वहां तीन तरह की कुंजियां एक साथ रखी गई हैं:

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

user: प्रीफ़िक्स (जैसे कि user:prefs) से, किसी उपयोगकर्ता के सभी सेशन के लिए स्टेट को स्कोप किया जाता है. इसके उलट, बिना प्रीफ़िक्स वाली कुंजियां (

target_event_id

,

party_size

) से सेशन की सामान्य स्थिति का पता चलता है. यह सिर्फ़ चालू सेशन के लिए होता है और सेशन खत्म होने पर मिट जाता है.

👉 अब दोनों राज्यों के टैब की तुलना करें. user:prefs और सेव किए गए आर्टफ़ैक्ट मौजूद रहते हैं, जबकि temp:seatmap दोनों सेशन में मौजूद नहीं होता. temp: प्रीफ़िक्स वाली स्टेट कुंजियां, सिर्फ़ कुछ समय के लिए उपलब्ध होती हैं. इन्हें डेटाबेस में कभी सेव नहीं किया जाता.

इसके बाद, फ़ाइल

👉💻 इसे एडिटर में खोलें:

cloudshell edit ~/monstertix/memory/userx.md

जनवरी से अब तक तीन बुकिंग की गई हैं. जब एजेंट recall() को कॉल करता है, तब वह इसे पढ़ता है. सेशन का डेटा मिट जाने के बाद भी यह मौजूद रहेगा.

चार जगहें

यह कहां दिखता है

क्या यह कुकी, अगला मैसेज मिलने तक सेव रहती है?

क्या रीस्टार्ट करने के बाद भी यह कुकी मौजूद रहती है?

क्या सेशन मिटाने के बाद भी यह कुकी मौजूद रहती है?

temp:seatmap

नहीं

नहीं

नहीं

party_size — सामान्य सेशन की स्थिति, two-days-ago में

हां

हां

नहीं

user:prefs

हां

हाँ

हाँ

memory/userx.md

हाँ

हां

हां, और डेटाबेस मिटाने के बाद भी मौजूद रहता है

आर्टफ़ैक्ट

हां

हां

यह सेवा पर निर्भर करता है

उस टेबल को कंटेनर के बारे में पढ़ें, न कि उसमें मौजूद चीज़ों के बारे में.memory/userx.md हर तरह की स्थिति में सुरक्षित रहता है. इसमें सिर्फ़ वही चीज़ें होती हैं जिन्हें remember()ने डाला है. इसमें आपकी मौजूदा बातचीत को नहीं लिखा जाता. इसलिए, किसी फ़ाइल का सेशन से ज़्यादा समय तक मौजूद रहना, इस बात से अलग है कि एजेंट को यह याद रहे कि आपने अभी-अभी किस बारे में बात की थी. पांचवें चरण में, इस अंतर के बारे में बताया गया है.

तथ्य को पांच जगहों पर दिखाया जा सकता है. साथ ही, यह भी बताया गया है कि हर जगह पर तथ्य कितने समय तक दिखता है

What state is

स्टेट को एक डिक्शनरी के तौर पर समझें, जो सेशन के साथ चलती है. tool_context.state के ज़रिए, इसे अपने टूल से पढ़ें और लिखें. साथ ही, ADK को इसे बातचीत के साथ सेव करने दें.

ADK, प्रीफ़िक्स का मतलब तय करता है. आपने उन्हें कहीं भी तय नहीं किया है. ADK, मुख्य नाम के पहले हिस्से को पढ़ता है. इसके बाद, इसका इस्तेमाल यह तय करने के लिए करता है कि वैल्यू कहां जाएगी और वह कितनी देर तक रहेगी:

नाम की एक कुंजी

ADK इसे सेव करता है

अवधि

party_size

सेशन के साथ

जब तक सेशन मिटाया नहीं जाता

user:prefs

उपयोगकर्ता के ख़िलाफ़

उपयोगकर्ता के हर सेशन में

app:tour_id

ऐप्लिकेशन के ख़िलाफ़

हर उपयोगकर्ता के लिए

temp:seatmap

कहीं नहीं

एक बार शुरू करना

इनवोकेशन, अनुरोध-जवाब का एक साइकल होता है. इसमें मॉडल की सभी तर्क क्षमताएं, टूल का इस्तेमाल, और फ़ाइनल जवाब शामिल होता है.

इनवोकेशन खत्म होने पर, ADK नई स्थिति को डेटाबेस में लिखता है और रास्ते में मौजूद हर temp: कुंजी को हटा देता है.

एक बात के बारे में जानकारी नहीं दी गई है

two-days-ago में आपको एक खास जानकारी दिखती है. इसमें किसी व्यक्ति की शुरुआती कुछ बारी की जानकारी होती है. बातचीत लंबी होने पर, ADK ने इसे अपने-आप लिख दिया.

हर तथ्य की एक समयसीमा होती है

5. कॉन्टेक्स्ट की क्वालिटी कम होना और जानकारी को छोटा करना

खास जानकारी में, जानकारी को भूल जाना

जब कोई बातचीत बहुत लंबी हो जाती है, तो सिस्टम पुराने मैसेज की खास जानकारी जनरेट करता है, ताकि स्पेस और टोकन बचाए जा सकें.

जवाबों की खास जानकारी, किसी विषय के बारे में सामान्य जानकारी पाने के लिए अच्छी होती है. इससे टोकन के इस्तेमाल को कम करने में मदद मिलती है.

कंपैक्शन, सेशन पर काम करता है. मेमोरी एक अलग स्टोर है

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 इस चरण के कोड पर जाएं:

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

पिछले चरण के बाद क्या बदलाव हुआ:

फ़ाइल

क्या बदलाव हुए

agent.py

एजेंट को App में रैप किया गया है और कंपैक्शन चालू है. दूसरा एजेंट, budget_split, टिकट की संख्या के हिसाब से हिसाब-किताब करता है

बाकी सब कुछ

अपरिवर्तित

agent.py अब root_agent से खत्म नहीं होता. यह इससे खत्म होता है:

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 सबसे पहले app नाम के वैरिएबल को खोजता है और अगर वह नहीं मिलता है, तो root_agent का इस्तेमाल करता है. जब आपको किसी ऐसी चीज़ की ज़रूरत हो जो किसी एक एजेंट के बजाय पूरे ऐप्लिकेशन पर लागू होती हो, तो एजेंट को App में रैप करें. जैसे, यहां कंपैक्शन और आने वाले चरणों में फिर से शुरू करने की सुविधा.

compaction_interval=3 का मतलब है कि एडीके, हर तीन टर्न के बाद जवाब की खास जानकारी देता है. एक टर्न का मतलब है कि आपने कुछ कहा और एजेंट ने उसका जवाब दिया. भले ही, इस दौरान एजेंट ने कितने भी टूल का इस्तेमाल किया हो. तीन छोटा है, इसलिए यह वर्कशॉप के दौरान ट्रिगर होता है. असल ऐप्लिकेशन में, इससे ज़्यादा संख्या का इस्तेमाल किया जाता है.

overlap_size=1 का मतलब है कि हर जवाब, ज़रूरी समय से एक टर्न पहले शुरू हो जाता है.

ADK, पहले, दूसरे, और तीसरे मोड़ के बारे में खास जानकारी देता है. तीन टर्न के बाद, यह अगले बैच की खास जानकारी देता है. overlap_size=1 के साथ, दूसरी खास जानकारी तीसरे टर्न से फिर से शुरू होती है, न कि चौथे टर्न से:

दो जवाब, जो एक-दूसरे से एक मोड़ पर ओवरलैप हो रहे हैं

तीसरे मोड़ की जानकारी को दो बार दिखाया गया है. यह जान-बूझकर किया गया है. अगर आपने तीसरे टर्न में कोई सवाल पूछा है और एजेंट ने चौथे टर्न में उसका जवाब दिया है, तो साफ़ तौर पर अलग-अलग करने की सुविधा, सवाल को एक खास जानकारी में और जवाब को दूसरी खास जानकारी में रख देती है. इससे दोनों खास जानकारी समझने में मुश्किल होती है. किसी बात को बीच में ही काट देने से बचने के लिए, अपनी बारी को दोहराना एक अच्छा तरीका है.

दोनों फ़ील्ड सेट करें. ADK में, दोनों के लिए कोई डिफ़ॉल्ट वैल्यू नहीं होती.

कंपैक्शन और मेमोरी सेवा कभी एक साथ नहीं होती

इन दोनों शब्दों के बारे में साफ़ तौर पर बताना ज़रूरी है, क्योंकि ये दोनों शब्द एक-दूसरे से मिलते-जुलते हैं, लेकिन सिस्टम अलग-अलग हैं.

EventsCompactionConfig को root_agent के बगल में, agent.py में App पर सेट किया गया है. यह ऐप्लिकेशन की प्रॉपर्टी है, न कि किसी सेवा की:

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

खास जानकारी में, बातचीत को फिर से लिखा जाता है. यह फ़ाइल को फिर से नहीं लिखता है.

remember() को कॉल करने की वजह के तौर पर इसलिए शामिल किया गया है, ताकि लोग जान-बूझकर इस विकल्प को चुन सकें. ट्रांसक्रिप्ट में मौजूद किसी भी कॉन्टेंट को, किसी और व्यक्ति के लिखे गए शब्दों में बदलकर पढ़ा जाता है. वहीं, फ़ाइल में लिखे गए किसी भी कॉन्टेंट को ठीक उसी तरह पढ़ा जाता है जिस तरह उसे सेव किया गया था. ऐसा कल और अगले हफ़्ते भी होगा. इस चरण के आखिर में, आपको अंतर दिखेगा.

खास जानकारी कहां सेव की जाती है

sessions.db में, events टेबल में, एजेंट किसी इवेंट लाइन में कंपैक्शन रिकॉर्ड लिखता है. इसमें टाइमस्टैंप, किसी अन्य टर्न इवेंट की तरह होता है.

सामान्य इवेंट में content फ़ील्ड होता है और खास जानकारी actions.compaction में मौजूद होती है.

👉💻 इसे अपने डेटाबेस में देखें:

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%';"

इवेंट का शेप कुछ ऐसा दिखता है:

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

इवेंट स्ट्रीम, हर इवेंट को उसके कॉन्टेंट से खींचती है. डेटाबेस में मौजूद खास जानकारी और हर बार मॉडल को भेजी जा रही जानकारी.

दूसरा एजेंट

budget_split एक अलग एजेंट है, न कि कोई टूल:

# 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)],
)

एजेंट का इस्तेमाल क्यों किया जाता है, किसी सामान्य फ़ंक्शन का क्यों नहीं? क्योंकि काम के लिए मॉडल की ज़रूरत होती है. "इनमें से कौनसे सेक्शन में चार लोगों के लिए 200 डॉलर का बजट है और कुल बजट कितना है" को जजमेंट के साथ अंकगणित के तौर पर पढ़ो. साथ ही, जवाब ऐसा होना चाहिए जिसे कोई व्यक्ति पढ़ सके. Python फ़ंक्शन को गुणा करने दें. साथ ही, उससे यह न पूछें कि किन विकल्पों के बारे में बताना ज़रूरी है.

तो फिर, मुख्य एजेंट को यह काम क्यों नहीं करने दिया जाता? अगली लाइन में देखें include_contents="none" का मतलब है कि इस एजेंट को बातचीत का कोई हिस्सा नहीं सौंपा गया है. कोई ग्रुप चैट नहीं, कोई प्राथमिकताएं नहीं, और यात्रा की कोई तारीख नहीं. इसे सिर्फ़ अनुरोध मिलता है. यह फ़्लैग सिर्फ़ Agent पर मौजूद होता है. यही वजह है कि यह फ़ंक्शन के बजाय एजेंट है.

ऐसा सब-एजेंट जो सिर्फ़ अनुरोध देख सकता है और कुछ नहीं

छोटे प्रॉम्प्ट को प्राथमिकता दें: ये सस्ते होते हैं, तेज़ी से काम करते हैं, और इन्हें गुमराह करना मुश्किल होता है. यह भरोसा करना कि अंकगणित के सवालों के जवाब पर, बीस टर्न पहले कही गई किसी बात का कोई असर नहीं पड़ेगा, क्योंकि उसे वह बात कभी नहीं दिखती.

क्यों

AgentTool

? एजेंट को इससे रैप करें, ताकि मुख्य एजेंट इसे किसी अन्य टूल की तरह कॉल कर सके. मॉडल के हिसाब से, budget_split और search_events एक जैसे हैं.

और

output_key="budget_plan", जवाब को बातचीत में शामिल करने के बजाय, उस नाम से सेशन की स्थिति में डालता है. जानें कि यह क्यों ज़रूरी है.

इसे चलाएं

👉💻 टर्मिनल 1 में, Ctrl-C दबाएं और उसी फ़्लैग के साथ फिर से शुरू करें:

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

👉 नया सेशन शुरू करें और खुलकर बातचीत करें. पांच या छह बार. यह काम करता है:

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?

👉 स्टेट टैब खोलें.

budget_plan में, अंकगणित के एजेंट से मिला पूरा जवाब देखें.

budget_plan after the first budget question

👉✨ अब इस पर मौजूद नंबर बदलें:

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

👉 budget_plan को फिर से देखें. इसे जोड़ा नहीं गया है, बल्कि बदला गया है. ऐसा इसलिए है, क्योंकि output_key में सबसे नया वर्शन मौजूद है.

बजट से जुड़े दूसरे सवाल के बाद बजट प्लान

आपको क्या नहीं दिखेगा: खास जानकारी

इन टर्न में से किसी एक में, ADK ने पिछली बातचीत की खास जानकारी दी है.

👉💻 इसे सीधे तौर पर पढ़ने के लिए, नए टर्मिनल में यह कमांड चलाएं:

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

देखें कि उसने क्या लिखा है

यहां एक छोटा किया गया उदाहरण दिया गया है:

── 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`

इसने कीमतों, सेक्शन, शहरों, और प्राथमिकताओं को बनाए रखा. साथ ही, यह भी बताया कि अब तक कुछ भी तय नहीं किया गया है.

इससे तीन बातें पता चलती हैं:

यह पैराफ़्रेज़ किया गया है. यहाँ मौजूद कोई भी जानकारी, किसी व्यक्ति के कहे गए शब्दों से नहीं ली गई है. इसमें यह जानकारी होती है कि क्या कहा गया था. इसे उसी प्रोसेस से जनरेट किया जाता है जिससे अन्य जानकारी जनरेट की जाती है. हालांकि, कभी-कभी मॉडल से कुछ गलतियां भी हो जाती हैं.

ध्यान दें: खास जानकारी देने वाली स्ट्रिंग, इंटरनल सेशन इवेंट के रिकॉर्ड में सेव की जाती हैं. इन्हें अलग-अलग यूज़र इंटरफ़ेस (यूआई) टैब के तौर पर सीधे तौर पर नहीं दिखाया जाता.

हर तीन टर्न के बाद, ऐसी बातचीत में जो अब पिछली खास जानकारी से बनी है.

एक ही बात को दोहराए बिना बुकिंग करने की कोशिश करें

नए सेशन में नया सेशन शुरू न करें — आपने अभी जो जवाब पढ़ा है वह इस सेशन के इवेंट में मौजूद है. यही वजह है कि एजेंट को अब भी ऐम्स्टर्डम के बारे में कुछ जानकारी है.

👉✨ इसे कोई नई जानकारी न दें:

Book us something.

देखें कि एजेंट कौनसे शो, कौनसे सेक्शन, और कितने लोगों के बारे में पूछता है. इसे सही के तौर पर मार्क करें: जवाब में बताया गया है कि उपयोगकर्ता ने कभी किसी चीज़ की पुष्टि नहीं की. इसलिए, एजेंट ने पूछा. कंपैक्शन को क्रेडिट दें.

कॉन्टेक्स्ट को छोटा करने के बाद भी, पिछले टर्न की जानकारी देने से कुछ समस्याएं हो सकती हैं. जैसे, जानकारी का सही तरीके से न मिलना. ऐसे में, एजेंट को उपयोगकर्ता से पुष्टि करनी पड़ सकती है कि वह अब भी उन शर्तों को पूरा कर रहा है या नहीं.

👉✨ इसका जवाब दें और बुकिंग को पूरा होने दें:

Amsterdam Saturday, section C, 8 people

यह कुछ भी खर्च करने से पहले, ऑर्डर को वापस पढ़ता है:

"You want to book 8 tickets in Section C for The Midnight Signal in Amsterdam on Saturday, November 14th. कुल लागत 76,000 रुपये होगी. क्या मैं आगे बढ़कर ये टिकट खरीद लूं?"

👉✨ इसकी पुष्टि करें:

yes

अब आपको पता चलेगा कि यह काम कर गया है:

"आपने 14 नवंबर, शनिवार को एम्स्टर्डम में होने वाले The Midnight Signal शो के लिए, सेक्शन C में आठ टिकट खरीदे हैं. इनका कुल किराया 76, 000 रुपये है. आपका ऑर्डर आईडी ord_227877fe है."

जाकर देखें कि क्या ऐसा हुआ है

👉 जगह की जानकारी वाले पैनल पर स्विच करें. सिर्फ़ एजेंट की बातों पर भरोसा न करें.

जगह की जानकारी वाले पैनल में क्रम

पैनल पर

आपको क्या दिखना चाहिए

खरीदे गए टिकट

1, और सही — एक बुकिंग, एक ऑर्डर

नीचे दिया गया ऑर्डर

ord_xxxxxxxx — 8× section C · 760

द टूर, एम्सटर्डम शनिवार

सेक्शन C में 8 की गिरावट आई है

गतिविधि

ord_xxxxxxxx — 8x section C @ 95 = 760

टिकट की बिक्री के बारे में जानने के लिए, पैनल देखें.

user:prefs को क्यों चुना गया

याद करें कि हमने user:prefs पिछले चरण में, सैम की शर्त को note_companion में लिखा था. साथ ही, हमने यह भी बताया था कि इस पर आपत्ति क्यों हो सकती है: जब एजेंट को यह जानकारी याद रह सकती है, तो टूल का इस्तेमाल क्यों किया जाए.

इसकी वजह यहां दी गई है. बातचीत में किसी भी तरह की स्थिति के बारे में जानकारी न दें. साथ ही, खास जानकारी में भी इसके बारे में न बताएं. देखें कि user:prefs और budget_plan में कोई बदलाव नहीं हुआ है. इसके बाद, ट्रांसक्रिप्ट देखें.

जवाब में, सामान्य जानकारी को शामिल किया जाता है और ज़्यादा जानकारी को हटा दिया जाता है. ऐसे कॉन्टेंट को बातचीत के अलावा किसी दूसरी जगह पर सेव करें जिसे आपको खोना नहीं है.

6. प्रोसेस को फिर से शुरू करना

40 मिनट तक ऑनलाइन न रहना

जब किसी एजेंट को टिकट की 40 मिनट की लंबी लाइन में इंतज़ार करना पड़ता है, तो Python प्रोसेस को चालू रखना और इंतज़ार करना मुश्किल और महंगा होता है.

लंबे समय तक इंतज़ार करने की समस्या को ठीक से मैनेज करने के लिए:

  1. डेटाबेस में स्थिति सेव करें: इससे यह जानकारी सेव की जाती है कि एजेंट ने प्रोसेस कहां रोकी थी, ताकि प्रोसेस को सुरक्षित तरीके से बंद किया जा सके.
  2. मांग पर चालू होना: इंतज़ार की अवधि खत्म होने पर, कोई बाहरी सिग्नल एजेंट को फिर से चालू करता है और वहीं से काम शुरू करता है जहां उसने छोड़ा था.

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 इस चरण के कोड पर जाएं:

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

इसे चलाएं

पिछले चरण के बाद क्या बदलाव हुआ:

फ़ाइल

क्या बदलाव हुए

tools.py

ने join_queue और check_queue को जोड़ा

agent.py

join_queue को LongRunningFunctionTool में रैप किया गया है. साथ ही, App, ResumabilityConfig को चालू करता है

join_queue से, रन को ब्लॉक करने के बजाय सीधे टिकट मिल जाता है. वहीं, ResumabilityConfig से रन को रोका जा सकता है और बाद में फिर से शुरू किया जा सकता है.

फिर से शुरू करने की सुविधा से क्या फ़ायदा मिलता है

👉💻 टर्मिनल 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"

👉✨ एजेंट से खरीदारी करने के लिए कहें:

Get us two tickets to the Amsterdam show.

यह recall को कॉल करता है. इसके बाद, search_events को कॉल करता है. फिर, join_queue को कॉल करता है. इसके बाद, यह इस तरह का जवाब देता है:

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

ध्यान दें कि इसने आपसे पूछे बिना शनिवार को चुन लिया है. एम्सटर्डम में दो शो हैं. 17 तारीख को मंगलवार है. पिछले चरण की मेमोरी फ़ाइल में बताया गया है कि सैम, वीकनाइट में शो नहीं करता. यह पिछली बुकिंग से जुड़ा एक तथ्य है, जिसके आधार पर इस बुकिंग से जुड़े सवाल का जवाब दिया जा रहा है.

यह भी ध्यान दें कि इसने एक सीट या कीमत देखने से पहले ही कतार में शामिल होने का अनुरोध किया था. साथ ही, इसने सीट या कीमत के बारे में कुछ नहीं बताया था. यह जगह का नियम है. आपको टिकट तब तक खरीदने होते हैं, जब तक वे सामने न आ जाएं. इसलिए, लाइन में जगह पाने के लिए ही आपको जल्दी आना चाहिए. आपको कौनसी सीटें लेनी हैं, यह तय करने के लिए आपके पास 40 मिनट हैं.

👉 कंट्रोल पैनल देखें. बैनर पर लिखा है:

जब आपका कोई वीडियो नहीं चल रहा होता है, तब भी कतार में वीडियो चलते रहते हैं

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

लाइन में लगने में 40 मिनट लगते हैं और काम करते समय यह लाइन कहीं नहीं जाती.

👉💻 एजेंट को बंद करें.:

Ctrl-C

👉💻 इसे टर्मिनल 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"

👉✨ उसी सेशन में, एजेंट से पूछें कि वह लाइन में कहां है:

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

रीस्टार्ट करने के बाद, वही टिकट

टिकट की तुलना करें और देखें कि जब कोई विज्ञापन नहीं चल रहा था, तब भी उसकी रैंक लगातार गिर रही थी. ध्यान दें कि एजेंट को इस बात की जानकारी नहीं थी कि उसे मार दिया गया है. इसने रीस्टार्ट होने से पहले मिले टिकट के साथ check_queue को कॉल किया, क्योंकि वह टिकट डिस्क पर था.

👉💻 इसकी वजह जानें:

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

ध्यान दें कि हर बातचीत का हर टर्न एक लाइन में होता है. साथ ही, टर्न में यह जानकारी होती है कि बातचीत किसने शुरू की, किस टूल का इस्तेमाल किया गया, क्या जवाब मिला, और नोट event_data में JSON के तौर पर होता है.

👉💻 इसमें अपना क्यू टिकट ढूंढें:

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

जब join_queue वापस आता है, तो उसका टिकट सीधे डेटाबेस में सेव हो जाता है. रीस्टार्ट करने पर, ADK डिस्क से पिछले टर्न और टूल के नतीजों को फिर से लोड करता है. इससे एजेंट को वहीं से शुरू करने में मदद मिलती है जहां उसने छोड़ा था.

साथ ही, ध्यान दें कि यह किस चीज़ से शुरू नहीं हुआ. एजेंट, वर्कफ़्लो को शुरू से चलाने के बजाय सीधे तौर पर फिर से शुरू करता है. LongRunningFunctionTool और ResumabilityConfig, डिस्क पर एक्ज़ीक्यूशन की स्थिति को सेव करते हैं. इसलिए, ADK को पता होता है कि एजेंट पहले से ही लाइन में इंतज़ार कर रहा है. इससे, वह टूर की तारीखों को फिर से खोजने या डुप्लीकेट टिकट का अनुरोध करने से बचता है.

एक ही टिकट, उस प्रोसेस के लिए जो बंद हो गई है

join_queue को क्यों चुना गया, check_queue को क्यों नहीं

दोनों फ़ंक्शन, मिलीसेकंड में वैल्यू दिखाते हैं. इन दोनों फ़ंक्शन के बीच अंतर यह है कि जब फ़ंक्शन वैल्यू दिखाता है, तब अंडरलाइंग जॉब पूरा हो चुका होता है या नहीं.

यह क्या दिखाता है

क्या काम पूरा हो गया है?

join_queue

लाइन में लगने का टिकट और शुरुआती पोज़िशन

नहीं. बाहरी सिस्टम में प्रोसेसिंग जारी रहती है.

check_queue

मौजूदा सूची की स्थिति

वाह। इससे तुरंत जवाब मिलता है.

LongRunningFunctionTool को ऐसे कामों के लिए डिज़ाइन किया गया है जो पूरा होने में समय लेते हैं. join_queue रैपिंग की मदद से, एजेंट को बाहरी कतार आगे बढ़ने के दौरान प्रोसेस को निलंबित करने की अनुमति मिलती है.

इसके उलट, check_queue सिंक्रोनस और पूरी तरह से काम करता है. इसे रैप करने से, एजेंट हर बार अपनी स्थिति की जांच करते समय, गलत तरीके से निष्पादन को निलंबित कर देगा. भले ही, वह लाइन के सबसे आगे पहुंच गया हो और उसे तुरंत खरीदारी पूरी करनी हो.

वापस आने पर, कौनसा कॉल अब भी बकाया है

उस वीडियो में क्या खास था

तीन कॉम्पोनेंट की मदद से, एक्ज़ीक्यूशन को निलंबित किया जा सकता है और उसे वापस चालू किया जा सकता है:

# 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: यह टूल को नॉन-ब्लॉकिंग के तौर पर मार्क करता है. साथ ही, एक ऐक्टिव हैंडल दिखाता है, ताकि एजेंट थ्रेड को बंद किए बिना इंतज़ार कर सके.
  • ResumabilityConfig: यह रनटाइम को निर्देश देता है कि वह अधूरे टास्क पर, एक्ज़ीक्यूशन लाइफ़साइकल को रोक दे. साथ ही, पिछली रिकॉर्ड की गई स्थिति से फिर से शुरू करे.
  • Session Storage URI (--session_service_uri): यह प्रोसेस की मेमोरी के बजाय, बाहरी स्टोरेज (जैसे कि SQLite, Cloud SQL) में स्टेट को सेव करता है. इससे रीस्टार्ट करने पर डेटा नहीं मिटता.

7. पुरानी जानकारी के आधार पर कार्रवाई करना

इंतज़ार करने के दौरान जानकारी पुरानी हो जाती है

सेव किया गया बातचीत का इतिहास, रीयल-टाइम में अपडेट नहीं होता. ज़्यादा इंतज़ार के समय में, एजेंट को पता चले बिना टिकट की उपलब्धता या कीमत जैसे बाहरी डेटा में बदलाव हो सकता है.

जिस एजेंट के पास ज़रूरी जानकारी नहीं होती है वह रुककर, जानकारी मांगता है. हालांकि, जिस एजेंट के पास पुराना डेटा होता है वह पुरानी मान्यताओं के आधार पर तुरंत कार्रवाई करता है. अमान्य कार्रवाइयों को रोकने के लिए, समय के हिसाब से संवेदनशील डेटा की पुष्टि तुरंत की जानी चाहिए. ऐसा ज़रूरी कार्रवाइयां करने से पहले किया जाना चाहिए.

अभी नया कोड लोड न करें. पिछले चरण के एजेंट पर बने रहें. यह टिकट खरीद सकता है, लेकिन फ़िलहाल इसमें पुरानी जानकारी या एक ही अनुरोध को कई बार पूरा करने से रोकने के लिए कोई सुरक्षा नहीं है.

👉🔴 जगह की जानकारी रीसेट करें पर क्लिक करें. इसके बाद, adk web में नया सेशन शुरू करें. इसके लिए, चैट के ऊपर मौजूद + नया सेशन बटन पर क्लिक करें.

पिछला टिकट और अधूरी खरीदारी की लंबित स्थिति को हटाने के लिए, नया सेशन शुरू करें. इस चरण के लिए, एक्ज़ीक्यूशन कॉन्टेक्स्ट को साफ़ करना ज़रूरी है, ताकि पुराने डेटा को सही तरीके से मैनेज किया जा सके.

स्नैपशॉट में मौजूद जानकारी

👉✨ उसी शो को फिर से चलाने के लिए कहें:

Get us two tickets to the Amsterdam show.

👉✨ अब, लाइन में इंतज़ार करते समय सीटों के बारे में पूछें:

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.

👉🔴 अच्छी सीटें बेचें को दबाएं. सेक्शन A की वैल्यू शून्य हो जाती है.

👉🔴 एजेंट को लाइन में सबसे आगे भेजने के लिए, इंतज़ार न करें पर क्लिक करें.

कतार में सबसे आगे

👉✨ इसे खरीदने के लिए कहें:

Buy the two section A seats.

यह सीट की उपलब्धता की दोबारा पुष्टि किए बिना purchase(section="A") को लागू करता है. इससे, जगह की जानकारी देने वाला एपीआई गड़बड़ी का रिस्पॉन्स दिखाता है:

{"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.

जगह के हिसाब से एपीआई में गड़बड़ी: सेक्शन A की सभी टिकटें बिक चुकी हैं

ध्यान दें कि एजेंट ने एपीआई से गड़बड़ी का जवाब मिलने के बाद ही सीट मैप के लिए फिर से क्वेरी की. इसकी मुख्य वजह, पुराना कॉन्टेक्स्ट है: सेशन की शुरुआत में, एजेंट ने "सेक्शन A: 400 उपलब्ध हैं" रिकॉर्ड किया था. यह जानकारी, बातचीत के इतिहास में बनी रही और मॉडल को पास कर दी गई.

इस बारे में साफ़ तौर पर बताएं कि पुरानी जानकारी कहां सेव की जाती है: यह बातचीत के इतिहास में सेव की जाती है. इसे temp: जैसे स्टेट प्रीफ़िक्स नहीं बदल सकते.

सीट का वह मैप जिस पर एजेंट अब भी काम कर रहा है

समस्या ठीक करने का तरीका

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 अब इस चरण का कोड लोड करें:

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

👉💻 टर्मिनल 1 में, Ctrl-C दबाकर रीस्टार्ट करें:

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"

क्या बदलाव हुए हैं:

फ़ाइल

क्या बदलाव हुए

fence.py

नया. यह कुकी, खरीदारी से ठीक पहले काम करती है और इसे ब्लॉक कर सकती है

agent.py

before_tool_callback=refresh_before_purchase

tools.py

purchase अब Idempotency-Key हेडर भेजता है

fence.py एक कॉलबैक है, जो टूल कॉल से ठीक पहले ADK चलाता है:

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

इसे साबित करें

👉🔴 जगह की जानकारी रीसेट करें. कतार में शामिल हों. इसके बाद, इंतज़ार के दौरान अच्छी सीटें बेचो को दबाएं. फिर, उसे सेक्शन A की सीटें खरीदने के लिए कहें.

इस बार यह अपने-आप बंद हो जाता है. एजेंट को कुछ इस तरह बोलते हुए देखें:

"ऐसा लगता है कि सेक्शन A की सभी टिकटें बिक गई हैं. मुझे सीट का मैप फिर से देखना है."

इसके बाद, बिना किसी प्रॉम्प्ट के get_seatmap को फिर से कॉल करें और सेक्शन C के बारे में जानकारी दें.

👉🔴 जगह के गतिविधि फ़ीड को ध्यान से पढ़ें, क्योंकि इसमें दोनों तरह की जानकारी दिखती है:

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

जगह की जानकारी वाला फ़ीड: कोशिश और अस्वीकार करना

डैशबोर्ड पर, आपको कॉल करने के लिए खरीदे गए टिकट दिखते हैं, लेकिन टिकट काउंटर शून्य पर रहता है. ऐसा इसलिए होता है, क्योंकि टूल के चलने से पहले fence.py एक गार्ड के तौर पर काम करता है:

  1. एजेंट, खरीदारी के टूल को कॉल करने की कोशिश करता है.
  2. fence.py सबसे पहले अनुरोध को पकड़ता है और सीट की उपलब्धता की जांच करता है.
  3. सीटें बुक हो जाने की वजह से, fence.py सीधे तौर पर गड़बड़ी का मैसेज दिखाता है. इससे एपीआई को कॉल करने से पहले ही खरीदारी रद्द हो जाती है.

प्रॉम्प्ट में दिए गए निर्देशों से यह पक्का नहीं किया जा सकता कि एलएलएम, बिक चुकी सीटों को खरीदने की कोशिश नहीं करेगा. कोड गार्ड (before_tool_callback) यह पक्का करते हैं कि बुरे मकसद से की गई खरीदारी कभी भी असल दुनिया तक न पहुंचे.

👉🔴 RESET THE VENUE और फिर BREAK THE NEXT PURCHASE दबाएं

👉✨ इसे खरीदने के लिए कहें. साथ ही, इसे जितनी बार चाहें उतनी बार कोशिश करने दें.

बार-बार कोशिश करने पर भी, एक ही ऑर्डर माना जाएगा

ऑर्डर की संख्या 1 ही रहती है, क्योंकि जगह के हिसाब से, फिर से कोशिश करने पर उसे वही आइडेंपोटेंसी कुंजी मिली और उसने डुप्लीकेट ऑर्डर बनाने के बजाय, मूल पुष्टि की जानकारी दी.

8. ऑटोनॉमस वर्कफ़्लो

एलएलएम, हर बार तय किए गए नियमों का सही तरीके से पालन नहीं कर पाते

एलएलएम, फ़ैसले लेने में बहुत अच्छे होते हैं. जैसे, उपयोगकर्ता की प्राथमिकताओं के आधार पर कोई शो चुनना. हालाँकि, एलएलएम से चरण-दर-चरण वाले नियमों का सख्ती से पालन कराने पर (जैसे, "सीटें देखने से पहले हमेशा लाइन में लगें"), वह कभी-कभी चरणों को छोड़ सकता है या क्रम को गड़बड़ कर सकता है.

इसे ठीक करने के लिए, वर्कफ़्लो को दो तरह के चरणों में बांटें:

  • तय किए गए नियम (कोड फ़ंक्शन): ऐसे चरण जो हर बार एक ही तरीके से होने चाहिए (जैसे, कतार का टिकट लेना, पोज़िशन की जांच करना) को सामान्य कोड नोड के तौर पर लिखा जाता है.
  • फ़ैसले (एजेंट नोड): जिन चरणों में फ़ैसले लेने की ज़रूरत होती है (जैसे, सीट सेक्शन चुनना, उपयोगकर्ता की प्राथमिकताओं की जांच करना) उन्हें एआई एजेंट को दिया जाता है.

देखें कि इस फ़्लो में असल में क्या है. इसमें से कुछ में कोई फ़ैसला नहीं दिया गया है:

चरण

फ़ैसला?

सिर्फ़ एक टिकट लें

नहीं. हमेशा एक, हमेशा खरीदारी से पहले

देखें कि आपकी बारी है या नहीं

नहीं. सवाल पूछें और जवाब पढ़ें

खरीदार के लिए संक्षिप्त जानकारी लिखो

नहीं. हर बार एक ही वाक्य और एक ही शेप

कौनसा शो, कौनसा सेक्शन

yes. कीमत, दिन, कौन आ रहा है, उन्हें क्या पसंद नहीं है

उपयोगकर्ता की प्राथमिकता और सीमा

yes.

सख्त प्रक्रियाओं के लिए एलएलएम पर भरोसा नहीं करना चाहिए. ऐसा इसलिए, क्योंकि मॉनिटर न किए जाने पर, संभावनाओं पर आधारित मॉडल, तय किए गए क्रम का पालन नहीं कर पाएंगे.

ADK का Workflow, एआई की मदद से फ़ैसले लेने की प्रोसेस को, तय किए गए तरीके से काम करने की प्रोसेस से अलग करता है:

  • फ़ंक्शन नोड, तय किए गए नियमों (जैसे, किसी कतार में शामिल होना या अपनी पोज़िशन देखना) को हर बार लागू करते हैं.
  • एजेंट नोड, कॉन्टेक्स्ट के हिसाब से फ़ैसले लेते हैं. जैसे, उपयोगकर्ता की प्राथमिकताओं के आधार पर सीटें चुनना.
  • Wait नोड, मेमोरी में प्रोसेस को चालू रखे बिना एक्ज़ीक्यूशन को निलंबित करते हैं.

ओरिजनल एजेंट को नहीं बदला जाता. यह वर्कफ़्लो ग्राफ़ में आखिरी नोड बन जाता है. इसमें अपने टूल, डेटा के पुराने होने की जांच, और एक ही अनुरोध को कई बार प्रोसेस करने से रोकने के लिए सुरक्षा से जुड़ी सुविधाएं मौजूद होती हैं. ग्राफ़, खरीदारी के फ़ैसले को लागू करने के क्रम को कंट्रोल करता है. वहीं, एजेंट खरीदारी के फ़ैसले को लागू करता है.

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं.

👉💻 इस चरण के कोड पर जाएं:

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

इसे चलाएं

👉💻 टर्मिनल 1 में, Ctrl-C दबाकर रीस्टार्ट करें. हमेशा की तरह ही निर्देश दें:

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"

पिछले चरण के बाद क्या बदलाव हुआ:

फ़ाइल

क्या बदलाव हुए

nightly.py

नया. ADK 2 Workflow ग्राफ़ के तौर पर, बिना किसी निगरानी के चलने वाला प्रोग्राम

agent.py

root_agent अब ग्राफ़ है. जिस एजेंट से आपकी बातचीत चल रही थी वह अब भी मौजूद है. उसका नाम बदलकर buyer_agent कर दिया गया है. यह एजेंट, के अंदर मौजूद एक नोड है

agent.py

इसमें join_queue और check_queue भी नहीं है — ग्राफ़ में अब क्यू है

ग्राफ़ को क्रम से तीन मैसेज के साथ चलाएं

nightly Python में यहां दी गई सभी कार्रवाइयां करें. (ग्राफ़ यहीं मौजूद है). हर बार एक जैसा इनपुट भेजें, ताकि वर्कफ़्लो ग्राफ़ की स्थिति में बदलाव देखा जा सके.

1. देखें कि अब आपको किससे बात करनी है

👉 ADK के वेब यूज़र इंटरफ़ेस (यूआई) को फिर से लोड करें.

# 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, root_agent को असाइन किए गए ऑब्जेक्ट को लागू करता है. यहां, root_agent, nightly वर्कफ़्लो ग्राफ़ की ओर इशारा करता है. साथ ही, buyer_agent को ग्राफ़ में फ़ाइनल नोड के तौर पर कॉन्फ़िगर किया गया है.

2. इसे शुरू करो

👉✨ इसे टाइप करें और Enter दबाएं:

go

ध्यान दें: अपने-आप काम करने वाले वर्कफ़्लो ग्राफ़ में, उपयोगकर्ता के इनपुट का इंतज़ार किए बिना ही शुरुआती प्रोसेस शुरू हो जाती है. adk web में मैसेज भेजने पर, पहली बार रन करने की सुविधा ट्रिगर होती है.

👉 अब वेब ब्राउज़र पर देखें. बाएं पैनल में, ग्राफ़ बनाया जाता है और नोड में रंग भरे जाते हैं. दाईं ओर मौजूद पैनल, इवेंट स्ट्रीम है. इसमें देखा जा सकता है कि फ़ैसले कैसे लिए जा रहे हैं:

check_front पर पार्क किया गया ग्राफ़

देखें कि सेशन की स्थिति में queue_up queue_ticket और queue_event_id कैसे लिखते हैं.

check_front पर पार्क किया गया ग्राफ़

आपको adk_request_input नाम का एक फ़ंक्शन कॉल दिखेगा, जो जवाब का इंतज़ार कर रहा है. इसके बाद, यह बंद हो जाता है और रन खत्म हो जाता है. ग्राफ़ check_front तक पहुंचा, जिसमें आपको 14,203वां स्थान मिला. इसके बाद, यह रुक गया.

👉 वेन्यू पैनल देखें: एक कतार टिकट और कुछ भी नहीं चल रहा है.

check_front पर पार्क किया गया ग्राफ़

3. इसे चालू करें और ध्यान रखें कि आपको किस बॉक्स में टाइप करना है

इवेंट स्ट्रीम को adk_request_input इवेंट तक स्क्रोल करें और उस इवेंट से जुड़ा इनपुट बॉक्स ढूंढें:

check_front पर पार्क किया गया ग्राफ़

अब आपके पास टाइप करने के लिए दो जगहें हैं और दोनों अलग-अलग काम करती हैं.

आपने क्या टाइप किया

इसमें क्या होता है

adk_request_input इवेंट पर मौजूद Enter your response... बॉक्स

Gemini को लाइव बातचीत के दौरान रोकने की सुविधा का इस्तेमाल किया जाता है. पार्क की गई रन check_front से जारी रहती है

सबसे नीचे मौजूद मुख्य चैट बॉक्स

इससे नया अनुरोध शुरू होता है. ग्राफ़ START से फिर से शुरू होता है, कोई शो फिर से चुनता है, और दूसरा कतार टिकट लेता है

👉✨ Enter your response... बॉक्स में कुछ भी टाइप करें और ऐरो बटन दबाएं.

👉 टर्मिनल 1:

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

एक लाइन. इसने दूसरा शो नहीं चुना और न ही दूसरा टिकट लिया. अगर ऐसा होता, तो आपको 14,203 लोगों की लाइन में सबसे आखिर में खड़ा होना पड़ता.

अगर आपको [open] और [pick_show] फिर से चलता हुआ दिखता है, तो इसका मतलब है कि आपने चैट बॉक्स में टाइप किया है. जगह की जानकारी रीसेट करें को दबाएं और फिर से शुरू करें.

👉 वेन्यू पैनल को फिर से देखें: अब भी एक टिकट है. साथ ही, इसकी पोज़िशन थोड़ी कम हो गई है, क्योंकि वेन्यू का टाइमर चलता रहा, जबकि आपका टाइमर नहीं चला.

एक बार जगाया गया है और अब भी एक टिकट है

4. सामने लाएं, फिर खत्म करें

👉🔴 वेन्यू पैनल पर, SKIP THE WAIT दबाएं.

तैयार किया गया ग्राफ़, जिसमें हर नोड हरे रंग का है

👉✨ adk_request_input इवेंट के सवाल का जवाब एक बार फिर से दें. जवाब, चैट बॉक्स में नहीं, बल्कि इसके बॉक्स में दें. पूरा हो चुका ग्राफ़, जिसमें हर नोड हरे रंग का है

इस बार, check_front आपको सबसे आगे रखेगा और आपको आगे बढ़ने का मौका मिलेगा. बाईं ओर मौजूद पैनल में, आखिरी दो नोड brief और concert को देखें.

पूरा हो चुका ग्राफ़, जिसमें हर नोड हरे रंग का है

👉 इसके बाद, ब्राउज़र देखें. यह रन पूरा हो गया है. इसमें चार चीज़ें हैं जिनके बारे में आपको पढ़ना चाहिए.

पूरा हो चुका ग्राफ़, जिसमें हर नोड हरे रंग का है

ग्राफ़ पूरी तरह से हरा है और किनारों को गिना गया है. brief और concert हाइलाइट किए गए हैं. साथ ही, pick_show, queue_up, और check_front के किनारों पर अब बैज दिख रहा है.

2x

, यह बताता है कि ग्राफ़ ने उस एज को कितनी बार पार किया. हर वेक-अप के लिए एक बार. सबसे ऊपर मौजूद दो वैल्यू, फिर से शुरू करने की कीमत हैं: शेड्यूलर हर बार शुरू से ग्राफ़ में फिर से शामिल होता है. इसका मतलब यह नहीं है कि काम तीन बार हुआ है. साथ ही, वेन्यू पैनल इसका सबूत है: एक कतार टिकट, दो नहीं. @node(rerun_on_resume=False) की वजह से ऐसा होता है.

ग्राफ़

ग्राफ़: नियमों के लिए फ़ंक्शन, फ़ैसले के लिए एजेंट

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

ध्यान दें कि दो नोड एजेंट हैं और दो फ़ंक्शन हैं. साथ ही, यह तय करें कि कौन-सा नोड क्या है. इसे डिज़ाइन से जुड़ा पूरा सवाल माना जाएगा.

नोड

वह आकार क्यों

pick_show

agent. किसी व्यक्ति के इतिहास से कोई शो चुनना, एक तरह का फ़ैसला है. यह recall को कॉल करता है, टूर को पढ़ता है, और पुष्टि किया गया Plan दिखाता है

queue_up

function. कतार में शामिल होना एक नियम है. कोई नियम, सुबह 3 बजे सिक्का उछालकर तय नहीं किया जाना चाहिए

check_front

function. "क्या मैं आगे की सीट पर हूं" सवाल का एक सही जवाब है और यह जगह के हिसाब से है

buy_it

agent. यह buyer_agent है. इसमें कोई बदलाव नहीं किया गया है. यह root_agent जैसा पहले था वैसा ही है. इंतज़ार के दौरान सेक्शन A की सभी सीटें बुक हो गई हैं. क्या आपको सेक्शन B या C की सीट चाहिए या कोई सीट नहीं चाहिए? यह वही फ़ैसला है जिसके बारे में आपने मॉड्यूल 4 में बताया था

आखिरी नोड देखें और बनाया गया एजेंट ढूंढें. कॉपी नहीं किया गया है, न ही फिर से लिखा गया है:

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

यह सब एक साथ आता है: इसमें मौजूद हर टूल, fence.py में सीट-मैप को फिर से पढ़ना, और purchase में आइडमपोटेंसी कुंजी. यह सिर्फ़ एक ही है.

इंतज़ार करने के बजाय, इसे क्यों बंद कर दिया गया

यहां ऐसा वर्शन दिया गया है जिसे किसी को भी नहीं लिखना चाहिए:

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

यह प्रोसेस को चालीस मिनट तक चालू रखता है, लेकिन इससे कोई फ़ायदा नहीं होता. यह टर्मिनल के साथ बंद हो जाता है और इसे फिर से शुरू नहीं किया जा सकता. इसके बजाय, check_front यह काम करता है:

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

RequestInput एक रुकावट है. ग्राफ़ वहीं रुक जाता है जहां वह है. इनवोकेशन, सेशन स्टोर पर जाता है. साथ ही, प्रोसेस को बंद किया जा सकता है.

जब तक रन शुरू नहीं होता, तब तक कोई शुल्क नहीं लगता. साथ ही, यह सिर्फ़ एक जगह पर खत्म होता है: खरीदे गए टिकट. ऐसा हो सकता है कि आपको पूरी रात जागना पड़े और कई बार उठना पड़े.

9. किसी व्यक्ति से बात करना

किसी व्यक्ति से दो बार पुष्टि किए बिना पैसे खर्च न करें

रात भर काम करने वाले ऑटोनॉमस एजेंट को, चैट के इतिहास में मौजूद अनुमानित या अस्पष्ट प्राथमिकताओं के आधार पर, फ़ाइनेंस से जुड़े महंगे फ़ैसले नहीं लेने चाहिए.

ज़्यादा जोखिम वाली कार्रवाइयां (जैसे, टिकट खरीदने के लिए पैसे खर्च करना) करने से पहले, एजेंट को बातचीत रोकनी होगी. साथ ही, उपयोगकर्ता से ज़ोर से बोलकर अपने बजट की पुष्टि करने के लिए कहना होगा. इसके बाद, खरीदारी करने से पहले, उसे इस बात की पुष्टि को सेव करना होगा.

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 इस चरण के कोड पर जाएं:

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

इसे चलाएं

👉💻 टर्मिनल 1 में, Ctrl-C दबाकर रीस्टार्ट करें. पहले जैसा ही निर्देश:

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"

पिछले चरण के बाद क्या बदलाव हुआ:

फ़ाइल

क्या बदलाव हुए

budget.py

इसमें उस व्यक्ति की कही गई बात को सेव किया जाता है. load() इसे राज्य से बाहर पढ़ता है और someone_is_there() जवाब देता है कि क्या आस-पास कोई है जिससे पूछा जा सकता है

tools.py

set_budget जोड़ा गया है, ताकि जब कोई व्यक्ति इसकी पुष्टि कर दे, तो एजेंट सेशन की स्थिति में तय की गई सीमा लिख सके

nightly.py

पहला नया नोड, agree_budget है. यह नोड, किसी भी अन्य नोड के चलने से पहले, उपयोगकर्ता से सवाल पूछता है और उसे जवाब पढ़कर सुनाता है. इसमें बजट और अनुरोध के बाकी हिस्से को कैप्चर किया जाता है. दोनों को फ़्री टेक्स्ट के तौर पर कैप्चर किया जाता है. साथ ही, SEATS को एक कॉन्स्टेंट के तौर पर हटा दिया जाता है

इसे मॉडल न करें. वाक्य को सेव करें.

agent/concert/budget.py खोलें और पूरी जानकारी पढ़ें:

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"

यह नोड दो चीज़ों की जाँच करता है... व्यक्ति ने यह कहा है और व्यक्ति ने इसकी पुष्टि की है.

इसके बाद, यह वाक्य को user: में रखने के बजाय, सेशन की मौजूदा स्थिति में रखता है. एक बुकिंग के लिए बजट तय किया गया हो. उस रात, उस बैंड के बारे में कहा गया था कि "अगर वे अच्छे हैं, तो 250 डॉलर". इसका टिकट बुकिंग से कोई लेना-देना नहीं है.

उन्होंने जो कुछ भी कहा है वह सब सच है

बजट पर न रुकें. लोग फ़ैसला लेते समय अपना मन बदल लेते हैं. इसलिए, एजेंट को जो कुछ भी याद है उस पर भरोसा करने के बजाय, यहां वह सब कुछ कैप्चर करें जो लोग पूछते हैं.

"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)

सिर्फ़ उन इवेंट को पढ़ सकता है जिन्हें उपयोगकर्ता ने बनाया है. यह सिर्फ़

text

हिस्सों को function_response से अलग कर दिया गया है, ताकि बजट से जुड़े सवाल और जवाब यहां न दिखें. डुप्लीकेट न हों.

ध्यान दें कि Note agree_budget, इसे हर बार कॉल करता है, न कि सिर्फ़ पहली बार. इसलिए, एजेंट के कतार में होने के दौरान टाइप की गई कोई भी जानकारी भी शामिल हो जाती है. इसके बाद, open_the_night पिकर को दोनों वाक्य, सवाल, और बजट दें. पिकर उन्हें पढ़ेगा.

ग्राफ़, एक नोड लंबा है

आइए, इस सवाल का जवाब बातचीत से शुरू करते हैं:

# 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)],
)

एक ही ग्राफ़, जिसमें agree_budget को सबसे आगे रखा गया है

दो नोड रुकते हैं और इंतज़ार करते हैं. साथ ही, वे एक ही तरीके से काम करते हैं. check_front किसी जगह के बारे में पूछता है और उसे घड़ी से जवाब मिलता है. agree_budget किसी व्यक्ति के लिए इंतज़ार करता है और इसका जवाब सिर्फ़ कोई व्यक्ति दे सकता है. यह अंतर, चरण 10 में बताया गया है.

इसे रन करें — चरण-दर-चरण

1. इसे शुरू करो

👉 ADK के वेब यूज़र इंटरफ़ेस (यूआई) को फिर से लोड करें और concert एजेंट चुनें.

👉✨ अपनी ज़रूरत के हिसाब से सवाल पूछें:

Get us two tickets to the Amsterdam show.

दौड़ रुक जाती है और आपसे पूछा जाता है कि आपको कितना खर्च करना है

आठवें चरण में ग्राफ़ में कोई उपयोगकर्ता नहीं था. इसलिए, आपने जो भी टाइप किया उसे हटा दिया गया और go ने भी ऐसा ही किया. यहां agree_budget, पहली लाइन में what_they_asked_for(ctx) को कॉल करता है. यह आपके टाइप किए गए सभी टेक्स्ट को इकट्ठा करके state["request"] में डाल देता है. इसके बाद, open_the_night बजट के साथ इसे पिकर को दे देता है.

आपने किसी दिन का नाम नहीं बताया है. साथ ही, एम्सटर्डम में दो शो हैं. इसलिए, पिकर को recall() पर वापस आते हुए देखें. इसके बाद, "सैम, हफ़्ते के कामकाजी दिनों में नहीं आता" को ढूंढें और शनिवार को चुनें.

साथ ही, ब्राउज़र में adk_request_input इवेंट पर रन बंद हो जाता है:

ग्राफ़ ने अभी-अभी आपसे एक सवाल पूछा है. यह इंटरप्ट, आठवें चरण में कॉल के इंतज़ार के दौरान सुनाई गई इंटरप्ट की तरह ही है. हालांकि, अब इसमें सवाल का जवाब देने के लिए किसी व्यक्ति को शामिल किया गया है.

2. लोगों के जवाब देने के तरीके से ही जवाब देना

👉✨ सबसे नीचे मौजूद चैट बॉक्स में नहीं, बल्कि Enter your response... बॉक्स में:

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

इसे बंद होते हुए देखें और पुष्टि करें:

[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?

यह नंबर को सेव करने से पहले उसे पढ़कर सुनाता है

3. इसकी पुष्टि करें

👉✨ एक ही बॉक्स:

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.

चार नोड, एक शब्द से बाहर निकल गए. 👉 स्टेट टैब खोलें और budget ढूंढें. यह आपके जवाब का पूरा वाक्य होता है.

राज्य टैब में बजट के बारे में अपने शब्दों में जानकारी दें

कोई प्रीफ़िक्स नहीं. यह सेशन की स्थिति है. अगर आपने नया सेशन शुरू किया है, तो बजट खत्म हो जाएगा. इसलिए, आपसे फिर से बजट सेट करने के लिए कहा जाएगा.

4. सबसे आगे पहुंचें और रेस पूरी करें

👉🔴 वेन्यू पैनल पर, SKIP THE WAIT दबाएं.

👉✨ adk_request_input के सबसे नए जवाब को इसके बॉक्स में फिर से लिखें:

go

फिर से चालू किया गया है और यह दो बार नहीं पूछता है

👉 वेन्यू पैनल देखें: एक ऑर्डर, उस कीमत पर जो आपने उस तरह की सीट के लिए तय की थी.

एक ऑर्डर, जिसमें आपने सहमति दी है

ग्राफ़ में किसी व्यक्ति से सवाल पूछने का तरीका

nightly.py खोलें और agree_budget पढ़ें. इन तीनों को एक साथ सही करें. इनमें से किसी एक के मौजूद न होने पर, आपको वही गड़बड़ी दिखेगी. यह हमेशा के लिए पूछता रहता है.

@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

इसके बिना, नोड को पूरा माना जाता है और उसे छोड़ दिया जाता है

स्थिर interrupt_id

डिफ़ॉल्ट रूप से, हर रन के लिए एक नया यूयूआईडी होता है. इसलिए, जवाब को खोजने के लिए कुछ भी नहीं है

ctx.resume_inputs[id]

जवाब कहां दिखेगा. नहीं, यह RequestInput की रिटर्न वैल्यू नहीं है — ADK, नोड को भेजने के बजाय उसे दोहराता है

ग्राफ़

यह आठवें चरण का ग्राफ़ है. इसमें सामने की ओर एक नोड ज़्यादा है. एक ही ग्राफ़, जिसमें agree_budget को सबसे आगे रखा गया है

उपयोगकर्ता की पुष्टि की गई पाबंदियां, सेशन के दायरे में आती हैं. बातचीत खत्म होने पर, ये पाबंदियां भी खत्म हो जाती हैं. सेशन के दौरान कुछ समय के लिए लागू की गई सीमाएं, सभी सेशन में लागू होने पर, एक बार लिए गए फ़ैसले अनजाने में स्थायी डिफ़ॉल्ट में बदल सकते हैं.

10. क्लाउड स्टैक

लैपटॉप के स्लीप मोड में जाने पर, डिवाइस में सेव की गई फ़ाइलें गायब हो जाती हैं

आपके लैपटॉप पर, एजेंट इन चीज़ों पर निर्भर करता है: आपका कंप्यूटर चालू हो, लोकल SQLite डेटाबेस, और लोकल फ़ाइलें. लैपटॉप का ढक्कन बंद करने या क्लाउड कंटेनर को फिर से शुरू करने पर, लोकल फ़ाइलें और मेमोरी मिट जाती हैं.

प्रोडक्शन में सही तरीके से काम करने के लिए:

  1. क्लाउड कंप्यूट: एजेंट को ऐसे कंटेनर (जैसे, Cloud Run) में चलाएं जो ऑनलाइन रहता है.
  2. Durable Cloud Databases: लोकल फ़ाइलों और SQLite को क्लाउड सेवाओं से बदलें. जैसे, सेशन डेटा के लिए Cloud SQL और फ़ाइलों के लिए Cloud Storage.
  3. क्लाउड टाइमर: मैनेज किए गए शेड्यूलर (जैसे कि Cloud Scheduler और Pub/Sub) का इस्तेमाल करें, ताकि लैपटॉप बंद होने पर भी एजेंट चालू हो जाए.

👉🔴 जगह की जानकारी वाले पैनल में, जगह की जानकारी रीसेट करें को दबाएं. हर चरण एक ही दुनिया से शुरू होता है: आठ शो, सभी सीटें उपलब्ध हैं, घड़ी 1× पर है.

👉💻 फ़ाइनल कोड पर जाएं:

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

फ़ाइल

क्या बदलाव हुए

main.py

नया. एक FastAPI, चैट पेज और एंडपॉइंट को दिखाता है, जिसे शेड्यूलर कॉल करता है

Dockerfile

नया. ऑर्डिनरी python-slim और uvicorn. इसमें ADK के बारे में कुछ भी नहीं बताया गया है

monstertix/

कॉपी किया गया है, इसलिए पेज को एजेंट के साथ एक ही कंटेनर में शिप किया जाता है

concert/

कोई बदलाव नहीं किया गया है. उस फ़ोल्डर की कॉपी बनाएं जिसका इस्तेमाल आपने नौवें चरण में किया था

वह पेज जिसे आपने पहले ही बना लिया है

** ADK वेब यूज़र इंटरफ़ेस** सिर्फ़ एक लोकल डीबगिंग इंटरफ़ेस है. इसका इस्तेमाल डेवलपमेंट के दौरान, स्थिति, इवेंट स्ट्रीम, और वर्कफ़्लो ग्राफ़ की जांच करने के लिए किया जाता है. इसे प्रोडक्शन डिप्लॉयमेंट के लिए पैकेज नहीं किया गया है.

प्रोडक्शन के लिए, तीसरे चरण में बनाया गया स्टैंडअलोन चैट फ़्रंटएंड इस्तेमाल करें. यह /wake एंडपॉइंट के ज़रिए, उपयोगकर्ता के मैसेज और इंटरैक्शन में आने वाली रुकावटों को मैनेज करता है. हालांकि, यह इंटरनल डीबगिंग कंट्रोल को ज़ाहिर नहीं करता.

लोकल स्टेट को क्लाउड सेवाओं पर माइग्रेट करना

सर्वर को बंद करें और देखें कि उसने क्या लिखा है:

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

डेवलपमेंट के लिए लोकल फ़ाइलें अच्छी तरह से काम करती हैं. हालांकि, प्रोडक्शन में वे काम नहीं करती हैं, क्योंकि Cloud Run कंटेनर, कुछ समय तक सेव रहने वाले इंस्टेंस-लोकल स्टोरेज का इस्तेमाल करते हैं.

Cloud Run में चल रहे ऐप्लिकेशन के लिए:

  • इंस्टेंस की संख्या शून्य हो जाती है: ट्रैफ़िक रुकने पर, कंटेनर बंद हो जाता है और उसका लोकल फ़ाइल सिस्टम मिट जाता है.
  • इंस्टेंस अलग-अलग स्केल आउट होते हैं: एक साथ चल रहे कई इंस्टेंस, डिस्क स्पेस शेयर नहीं करते. हर इंस्टेंस, अपने अलग फ़ाइल सिस्टम के साथ चलता है.

अगर कोई एजेंट, क्यू टिकट या सेशन की स्थिति को किसी लोकल SQLite फ़ाइल (sessions.db) में सेव करता है, तो नए या अलग कंटेनर इंस्टेंस पर भेजे गए बाद के अनुरोधों को खाली डेटाबेस मिलेगा. स्केलिंग और रीस्टार्ट करने के दौरान, स्थायी स्थिति बनाए रखने के लिए लोकल फ़ाइलों को मैनेज की गई बाहरी सेवाओं से बदलना होगा.

इसके बजाय, आपको ऐसे स्टेट की ज़रूरत होती है जो किसी भी कंटेनर से ज़्यादा समय तक बना रहे. तीन सेवाएं, हर फ़ाइल के लिए एक:

लैपटॉप पर

क्लाउड में

यह क्या है

sessions.db

Cloud SQL

मैनेज किया गया Postgres डेटाबेस. यह तब भी मौजूद होता है, जब कोई ऐप्लिकेशन नहीं चल रहा होता है

memory/userx.md

Cloud Storage

बकेट में मौजूद है. ऑब्जेक्ट के तौर पर फ़ाइलें, जिन्हें हर इंस्टेंस से एक साथ ऐक्सेस किया जा सकता है

artifacts/

Cloud Storage

एक ही बकेट में

clock.py

Cloud Scheduler

Google इसे क्रॉन करता है. आपका टर्मिनल, अलार्म क्लॉक नहीं है

उन्हें बनाना

PostgreSQL डेटाबेस, डेटाबेस उपयोगकर्ता, और Cloud Storage बकेट उपलब्ध कराएं. ध्यान दें कि Cloud SQL इंस्टेंस बनाने में कई मिनट लगते हैं. इसे setup.sh के दौरान बैकग्राउंड में शुरू किया गया था.

👉💻 इसे इकट्ठा करें और बाकी को बनाएं:

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

इस स्क्रिप्ट ने चार चीज़ें बनाई हैं. ऊपर दी गई टेबल की हर लाइन के लिए एक चीज़ बनाई गई है. साथ ही, इसने .env में दो यूआरआई लिखे हैं:

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

मैनेज किए गए शेड्यूल किए गए ट्रिगर की मदद से एजेंट को डिप्लॉय करना

सेवाएं तैयार होने के बाद, एजेंट को उसका स्थायी होम दें.

👉💻 तीन या चार मिनट. इसे शुरू करें और पढ़ें:

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

यह स्क्रिप्ट, agent/ से कंटेनर इमेज बनाती है. इसके बाद, इसे Cloud Run पर डिप्लॉय करती है. साथ ही, इवेंट-ड्रिवन इन्फ़्रास्ट्रक्चर को उपलब्ध कराती है.

पूरे डिप्लॉयमेंट में पांच अलग-अलग कॉम्पोनेंट होते हैं:

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

Cloud Scheduler, लोकल एक्ज़ीक्यूशन टाइमर (clock.py) को मैनेज की गई क्रॉन सेवा से बदलता है. यह सेवा, लोकल टर्मिनल सेशन से अलग होती है. यह अपने-आप फिर से कोशिश करने की सुविधा को मैनेज करती है और टाइमज़ोन के हिसाब से शेड्यूल करने की सुविधा देती है.

Pub/Sub को मैसेज डिसकपलिंग लेयर के तौर पर इस्तेमाल करना

Cloud Scheduler और Cloud Run के बीच Pub/Sub टॉपिक रखने से, आर्किटेक्चर से जुड़े तीन फ़ायदे मिलते हैं:

  • मैसेज को सुरक्षित रखने की सुविधा: अगर Cloud Run सेवा कोल्ड-स्टार्ट हो रही है, फिर से डिप्लॉय हो रही है या कुछ समय के लिए उपलब्ध नहीं है, तो यह सुविधा ट्रिगर करने वाले इवेंट को कतार में लगाती है.
  • रीट्राई करने की नीतियां: ये नीतियां, मैसेज डिलीवर करने में कुछ समय के लिए आने वाली समस्याओं को अपने-आप ठीक करती हैं. इसके लिए, ऐप्लिकेशन लेवल पर रीट्राई करने की कस्टम लॉजिक की ज़रूरत नहीं होती.
  • फ़ैन-आउट स्केलेबिलिटी: इसकी मदद से, कई डाउनस्ट्रीम सेवाएं एक ही शेड्यूल किए गए इवेंट के लिए सदस्यता ले सकती हैं. इसके लिए, उन्हें शेड्यूलर को फिर से कॉन्फ़िगर करने की ज़रूरत नहीं होती.

वैकल्पिक लेख

trigger_sources=["pubsub"] को कॉन्फ़िगर करने से, स्टैंडर्ड Pub/Sub एनवलप पेलोड के साथ काम करने वाला पुश एंडपॉइंट रजिस्टर होता है. इससे, आने वाले मैसेज सीधे तौर पर एजेंट को शुरू करने वाले इवेंट में बदल जाते हैं.

→ 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 * * *

इससे बात करना

👉 ब्राउज़र में एजेंट का यूआरएल खोलें. 👉✨ इसे बुकिंग करने के लिए कहें:

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

इसमें यह पूछा जाना चाहिए कि आपको कितना खर्च करना है

👉✨ जिस तरह से बोलकर जवाब दिया जाता है उसी तरह से जवाब दें. इसके बाद, इसकी पुष्टि करें:

up to 250 a seat for the good ones

फ़िलहाल, रन को सूची में रखा गया है.

👉🔴 जगह के पैनल पर, अभी शामिल हों दबाएं, ताकि मैसेज आने पर कतार तैयार हो.

इसे शेड्यूल के हिसाब से चलाने के लिए, नौकरी को चालू करना होगा. jobs run, Job.state must be ENABLED के साथ रोके गए काम को अस्वीकार करता है:

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

👉 कुछ ही सेकंड में, एजेंट पुष्टि की गई बजट सीमा के अंदर खरीदारी कर लेता है. साथ ही, ऑर्डर की पुष्टि करने वाला ईमेल अपने-आप भेज देता है.

👉🔴 इवेंट की जानकारी देने वाला पैनल देखें. एक ऑर्डर.

इसे हर पांच मिनट में चलाने का तरीका यहां बताया गया है:

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

व्यवस्थित करें

यहां दो चीज़ें डिप्लॉय की गई हैं: जगह और उसके बगल में मौजूद एजेंट. दोनों को स्केल-टू-ज़ीरो किया जा सकता है. इसलिए, इस्तेमाल न किए जा रहे इंस्टेंस के लिए कोई शुल्क नहीं लगता. फिर भी उन्हें मिटाएं.

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

ध्यान दें कि Cloud SQL और बकेट को जान-बूझकर अलग रखा गया है, क्योंकि इनमें आपका डेटा सेव होता है और दोनों के लिए बिलिंग जारी रहती है. जब आपको लगे कि काम पूरा हो गया है, तब:

./destroy-agent.sh --all

बाकी सभी चीज़ें स्थानीय होती हैं. काम पूरा होने के बाद, फ़ोल्डर को मिटा दें. इसके अलावा, इसे सेव करके रखें और अपने प्रोजेक्ट के साथ इसे अपनी पसंद के मुताबिक रन करें.

एजेंट, उसके टूल, उसके कॉलबैक, और बजट में कोई बदलाव नहीं हुआ. दो यूआरआई और एक एनवायरमेंट वैरिएबल ने ऐसा किया.

11. सिस्टम के बारे में खास जानकारी और समस्या हल करने की गाइड

एनवायरमेंट में अंतर: लोकल बनाम क्लाउड आर्किटेक्चर

लैपटॉप

क्लाउड में

क्या बदलाव हुए

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

Cloud SQL

कनेक्शन स्ट्रिंग

--artifact_service_uri=file://...

gs://bucket

यूआरआई

memory/userx.md

gs://bucket/memory/

पाथ

adk web

एक Cloud Run सेवा

Dockerfile और main.py

monstertix/clock.py

Cloud Scheduler → Pub/Sub

trigger_sources=["pubsub"]

कीबोर्ड पर आपकी गतिविधि

someone_is_there()

डिफ़ॉल्ट वैल्यू, जिसे हर अनुरोध के हिसाब से बदला जा सकता है

कोर एजेंट को लागू करने के तरीके में कोई बदलाव नहीं किया गया है. इसमें root_agent, टूल की परिभाषाएं, कॉल बैक गार्ड, और बजट लागू करने की सुविधा शामिल है. solutions/step10_deploy/concert/ की तुलना चरण 9 से करने पर, बाइट-फ़ॉर-बाइट मैच दिखता है. इससे यह पुष्टि होती है कि ADK की सेवा ऐब्स्ट्रैक्शन, बुनियादी ढांचे के कॉन्फ़िगरेशन को एजेंट लॉजिक से अलग करती है.

अगर आपको कोई समस्या आ रही है

किसी भी चरण में, काम करने वाले पूरे कोड को ऐक्सेस करें:

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

जब कोई समस्या आती है

समस्या का ब्यौरा

ठीक करें

✗ Cannot access project

गलत आईडी. rm ~/project_id.txt && ./setup.sh

मॉडल, 404 गड़बड़ी वाला मैसेज दिखाता है

-latest सिर्फ़ AI Studio में उपलब्ध हैं. ADK_MODEL=gemini-latest-flash का इस्तेमाल करें

address already in use

किसी ऐप्लिकेशन के लिए, पोर्ट 8000 या 8090 पहले से इस्तेमाल किया जा रहा है. lsof -ti:8000 | xargs kill -9

एजेंट इवेंट की जगह पर नहीं पहुंच सकता

curl $VENUE_URL/health या ./deploy-venue.sh फिर से

कतार कभी आगे नहीं बढ़ती

SKIP THE WAIT दबाएं. 1× पर, कतार अपने-आप आगे नहीं बढ़ेगी

पैनल पर दूसरा क्यू टिकट दिखता है

आपने adk_request_input इवेंट के Enter your response... बॉक्स में जवाब देने के बजाय, चैट बॉक्स में जवाब दिया है. चैट बॉक्स में नया रन शुरू होता है

ड्रॉपडाउन खाली है या ऐप्लिकेशन लोड नहीं होगा

adk web, agent/ में मौजूद हर फ़ोल्डर को एक ऐप्लिकेशन के तौर पर देखता है. अगर कोई फ़ोल्डर ऐप्लिकेशन नहीं है, तो पूरी सूची को अस्वीकार कर दिया जाता है. ls agent/ में सिर्फ़ concert/ दिखना चाहिए. agent/ से कोई भी चीज़ चलाने पर, memory/ पीछे छूट सकता है. ./use-solution.sh 8 --force उसे व्यवस्थित करता है

[EXPERIMENTAL] चेतावनियां

ऐसा होना चाहिए. EventsCompactionConfig और ResumabilityConfig, दोनों 2.6.2 वर्शन में प्री-जीए हैं

सब कुछ अजीब है

जगह की जानकारी रीसेट करें और फिर rm sessions.db दबाएं

मेमोरी फ़ाइल में डुप्लीकेट बुकिंग की जानकारी मौजूद है

remember() हर रन को जोड़ता है. ./use-solution.sh N --force से रीसेट हो जाता है या cp seed/memory/default.md memory/userx.md

adk: command not found

source .venv/bin/activate — हर नए टैब के लिए इसकी ज़रूरत होती है

Cloud Shell का समय खत्म हो गया है

adk web को फिर से शुरू करें. sessions.db से ज़्यादा सदस्य हैं. यह छठा चरण है, जो आपको बिना किसी शुल्क के मिल रहा है