Google ADK ve Cloud Run'ı kullanarak Streamlit'te RAG yapay zeka ajanı dağıtma

1. Giriş

Bu codelab'de, bir kahve dükkanı için etkileşimli bir yapay zeka barista ajanı oluşturacaksınız. Google'ın açık kaynaklı Agent Development Kit (ADK) ve Gemini 3.5 Flash modelini kullanarak, ajanın önerilerini sahte bir menü veri kümesine dayandırmak için veriyle artırılmış üretim (RAG) uygulayacaksınız. Son olarak, ajanı Streamlit kullanıcı arayüzüne yerleştirip Cloud Run'a dağıtacaksınız.

Yapacaklarınız

  • Kahve ürünleri, etiketler ve alerjenler içeren bir RAG veri kaynağı (menu.json) oluşturun.
  • ADK'yı LlmAgent kullanarak bir yapay zeka aracısı oluşturun ve menü verilerini yüklemek için bir Python aracı bağlayın.
  • Temsilciyi, sohbet geçmişini yöneten bir Streamlit sohbet uygulamasına yerleştirin.
  • Kaynak tabanlı dağıtımı kullanarak Streamlit uygulamasını Cloud Run'a dağıtın.
  • RAG'nin temellendirilmesini ve alerjen farkındalığını test edin.

Mimari Şeması

İhtiyacınız olanlar

  • Chrome gibi bir web tarayıcısı
  • Faturalandırmanın etkin olduğu bir Google Cloud projesi.
  • Python'a dair temel bilgiler

Bu codelab, yeni başlayanlar da dahil olmak üzere her seviyeden geliştiriciye yöneliktir.

Tahmini Maliyet: 1,00 ABD dolarından az.

2. Başlamadan önce

Google Cloud projesi oluşturma

  1. Google Cloud Console'da bir Google Cloud projesi seçin veya oluşturun.
  2. Cloud projeniz için faturalandırmanın etkinleştirildiğinden emin olun.

Cloud Shell'i Başlatma

  1. Google Cloud Console'un üst kısmında Activate Cloud Shell'i (Cloud Shell'i Etkinleştir) tıklayın.

Cloud Shell'i etkinleştirme

  1. Kimlik doğrulamayı doğrulayın:

Cloud Shell'e yetki verin

  gcloud auth list
  1. Etkin projenizin ayarlandığını onaylayın:
  gcloud config get project

Gösterilen proje kimliği doğru değilse veya proje kimliği ayarlanmamışsa şu komutu çalıştırın:

  gcloud config set project <YOUR_PROJECT_ID>

API'leri etkinleştir

Gerekli tüm API'leri etkinleştirmek için bu komutu çalıştırın:

gcloud services enable \
 run.googleapis.com \
 aiplatform.googleapis.com \
 cloudbuild.googleapis.com

3. Projenizi oluşturma

Bu adımda, proje ortamı değişkenlerinizi başlatacak ve projeniz için bir çalışma dizini oluşturacaksınız.

  1. Etkin Cloud Shell oturumunuzda aşağıdaki proje ortam değişkenlerini başlatın:
  export PROJECT_ID=$(gcloud config get-value project)

Not: En yakın bölgeyi kullanın.

Size en yakın bölgeyi bulun ve aşağıdaki komutta insert-region-here yerine bu bölgeyi girin:

  export REGION=[insert-region-here]
  1. coffee-barista-agent adlı yeni bir proje dizini oluşturun ve bu dizine geçin:
  mkdir coffee-barista-agent && cd coffee-barista-agent

4. Sahte menü veri kaynağını oluşturma

Yapay Zeka Barista'yı temellendirmek ve var olmayan öğelerle ilgili halüsinasyonlar görmesini önlemek için yerel bir menü veri kümesi oluşturacaksınız. Aracı, bu dosyayı çalışma zamanında özel bir araç aracılığıyla okur.

  1. Cloud Shell Düzenleyici'de menu.json dosyasını oluşturup açın:
  cloudshell edit menu.json
  1. Aşağıdaki JSON içeriğini düzenleyiciye yapıştırın ve dosyayı kaydedin:
[
  {
    "name": "Espresso Solo",
    "description": "A single shot of rich, bold espresso.",
    "price": 2.50,
    "tags": ["strong", "hot", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Oat Milk Honey Latte",
    "description": "Creamy steamed oat milk with espresso and a touch of honey.",
    "price": 5.00,
    "tags": ["sweet", "hot", "dairy-free"],
    "allergens": []
  },
  {
    "name": "Cold Brew Coffee",
    "description": "Smooth, slow-steeped cold brew served over ice.",
    "price": 4.00,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Seasonal Pumpkin Latte",
    "description": "Spiced pumpkin sauce, espresso, and steamed milk, topped with whipped cream.",
    "price": 5.50,
    "tags": ["sweet", "hot", "seasonal"],
    "allergens": ["dairy"]
  },
  {
    "name": "Classic Croissant",
    "description": "Flaky, buttery traditional French pastry.",
    "price": 3.50,
    "tags": ["bakery", "savory"],
    "allergens": ["wheat", "dairy"]
  },
  {
    "name": "Vegan Blueberry Muffin",
    "description": "Soft, sweet muffin packed with real blueberries, entirely plant-based.",
    "price": 3.75,
    "tags": ["bakery", "sweet", "dairy-free", "vegan"],
    "allergens": ["wheat"]
  },
  {
    "name": "Nitro Cold Brew",
    "description": "Cold brew infused with nitrogen for a super smooth, creamy head.",
    "price": 4.50,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Iced Caramel Macchiato",
    "description": "Chilled milk and vanilla syrup marked with espresso and caramel drizzle.",
    "price": 5.25,
    "tags": ["sweet", "cold"],
    "allergens": ["dairy"]
  }
]
  1. JSON dosyasının doğru şekilde biçimlendirildiğini doğrulayın:
  cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"

💬 Tartışma: Yerel JSON ve Canlı Veritabanları

Neden canlı bir veritabanı yerine basit bir local menu.json dosyası kullanıyoruz?

Hızlı bir eğitim veya prototip için yerel bir JSON dosyası, ilk veritabanı kurulum süresini ve karmaşıklığını ortadan kaldırır. Ancak gerçek dünyadaki bir kurumsal üretim uygulamasında, aracı Cloud Firestore, AlloyDB veya Cloud SQL gibi yönetilen bir veritabanına bağlarsınız.

Canlı bir veritabanı kullanmak, kahve dükkanı yöneticilerinin kapsayıcı görüntüsünü yeniden oluşturmadan veya uygulama kodunu yeniden dağıtmadan mevsimlik ürünler eklemesine, fiyatları güncellemesine ya da alerjen etiketlerini dinamik olarak ayarlamasına olanak tanır. Bu codelab'in ilerleyen kısımlarında isteğe bağlı bir adım olarak canlı veritabanı kullanacağız.

5. ADK aracısını oluşturma

Şimdi gerekli paketleri yükleyip temel ADK temsilci mantığını oluşturacaksınız. Bir get_menu() aracı tanımlayıp LlmAgent'ye ileteceksiniz.

  1. Cloud Shell Düzenleyici'de requirements.txt dosyasını oluşturup açın:
  cloudshell edit requirements.txt
  1. Aşağıdaki bağımlılıkları düzenleyiciye yapıştırıp dosyayı kaydedin:
google-adk==2.2.0
streamlit==1.58.0
  1. Cloud Shell Düzenleyici'de agent.py dosyasını oluşturup açın:
  cloudshell edit agent.py
  1. Aşağıdaki kodu agent.py yapıştırın:
# agent.py
import json

from google.adk.agents import LlmAgent

# [START get_menu]
def get_menu() -> str:
    """Retrieves the coffee shop menu from menu.json.

    Returns:
        str: A JSON string representing the list of menu items.
    """
    try:
        with open("menu.json", "r") as f:
            menu_data = json.load(f)
            return json.dumps(menu_data)
    except Exception as e:
        return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]

# Create the barista agent
barista_agent = LlmAgent(
    name="barista_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly barista at ☕ Coffee Shop.
Your job is to recommend drinks and pastries to customers based on their preferences.

Rules you MUST follow:
1.  You must recommend items ONLY from the menu returned by get_menu().
2.  Do NOT recommend or suggest any item that is not present in the menu.
3.  If a user's preference is vague or unclear, ask exactly ONE friendly clarifying question to narrow down what they want (e.g., cold or hot, sweet or strong, coffee or pastry).
4.  Be warm and welcoming, but remain professional.
5.  Ground your recommendations in the actual tags, descriptions, and allergens listed in the menu (e.g., if a user is dairy-free, recommend ONLY items tagged 'dairy-free' or with no dairy allergens).
""",
    tools=[get_menu]
)

from google.adk.apps import App

# Define the App object
app = App(
    name="coffee_barista_app",
    root_agent=barista_agent
)
  1. Cloud Shell Düzenleyici'de app.py dosyasını oluşturup açın:
  cloudshell edit app.py
  1. Aşağıdaki kodu app.py yapıştırın:
# app.py
import streamlit as st
import json

# Set page config for a premium look
st.set_page_config(
    page_title="☕ Coffee Shop - Barista Bot",
    page_icon="☕",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Custom CSS to make the header sticky (adapts to light/dark themes)
st.markdown("""
<style>
    div[data-testid="element-container"]:has(.header-container),
    div.element-container:has(.header-container) {
        position: sticky;
        top: 2.875rem;
        z-index: 999;
        background-color: transparent;
        padding-bottom: 10px;
    }
</style>
""", unsafe_allow_html=True)

# App Header (using inline styles for the permanent coffee theme look)
st.markdown("""
<div class="header-container" style="text-align: center; padding: 20px; background: linear-gradient(135deg, #8B5E3C, #6F4E37); color: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
    <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; color: white;">☕ ☕ Coffee Shop</h1>
    <p style="margin: 5px 0 0 0; font-size: 1.1rem; opacity: 0.9; color: white;">Your friendly AI Barista is ready to help you find the perfect drink or pastry!</p>
</div>
""", unsafe_allow_html=True)

# Load Menu for the sidebar
# [START load_menu]
try:
    with open("menu.json", "r") as f:
        menu_items = json.load(f)
except Exception as e:
    st.error(f"Error loading menu: {e}")
    menu_items = []
# [END load_menu]

# Sidebar Menu & Configuration
with st.sidebar:
    st.markdown("## ☕ Coffee Shop Menu")
    st.markdown("Explore our offerings and ask the barista for recommendations.")
    st.markdown("---")

    for item in menu_items:
        with st.container(border=True):
            st.markdown(f"**{item['name']}**  •  **${item['price']:.2f}**")
            st.caption(item['description'])

            # Tags & Allergens as native badges
            tags = " ".join([f"`{t}`" for t in item.get("tags", [])])
            if tags:
                st.markdown(tags)

            allergens = ", ".join(item.get("allergens", []))
            if allergens:
                st.markdown(f"⚠️ *Allergens: {allergens}*")

# Chat Interface
if "session_id" not in st.session_state:
    import uuid
    st.session_state.session_id = str(uuid.uuid4())

if "runner" not in st.session_state:
    from google.adk.runners import InMemoryRunner
    from agent import app
    st.session_state.runner = InMemoryRunner(app=app)

if "messages" not in st.session_state:
    st.session_state.messages = [
        {"role": "assistant", "content": "Welcome to ☕ Coffee Shop! What can I get started for you today?"}
    ]

# Display existing messages
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# User Input
if prompt := st.chat_input("Ask for recommendations (e.g., 'What dairy-free pastries do you have?')"):
    # Display user message
    with st.chat_message("user"):
        st.markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})

    # Generate response
    with st.chat_message("assistant"):
        try:
            import asyncio

            # Run the ADK runner asynchronously using asyncio.run
            async def fetch_response():
                return await st.session_state.runner.run_debug(
                    prompt,
                    session_id=st.session_state.session_id
                )

            res_events = asyncio.run(fetch_response())

            response_text = "".join([
                part.text
                for event in res_events
                if event.content and event.content.parts
                for part in event.content.parts
                if part.text
            ])

            st.markdown(response_text)
            st.session_state.messages.append({"role": "assistant", "content": response_text})
        except Exception as e:
            st.error(f"Apologies, I ran into an error: {e}")

💬 Tartışma: Model Değiş Tokuşları ve Alma Jetonu Verimliliği

Neden menüyü almak için bir işlev aracı çağrılıyor? Bunun yerine, menü metninin tamamı doğrudan aracının sistem talimatlarına yapıştırılamaz mı?

Jeton ekonomisi! İstemde 8 öğe kullanmak ucuzdur ancak kahve dükkanı, özel malzemeler de dahil olmak üzere 500 öğeye çıkarsa ne olur? Büyük veri kümelerini doğrudan sistem istemine yapıştırmak, istem jetonu sayınızı artırır. Bu da her sorguda işlem maliyetlerini ve API yanıt gecikmesini yükseltir.

ADK aracı kullanıldığında temsilci, menüyü yalnızca gerektiğinde okumak için dinamik olarak izin ister. LLM, yalnızca alakalı menü verilerini bağlam olarak alır ve istem jetonu boyutunu en aza indirir.

💬 Tartışma: Bellek Durumu ve Üretim Depoları

Streamlit'in st.session_state içinde depolanan sohbet geçmişi, kullanıcı tarayıcı sekmesini kapattığında silinmeden kalır mı?

Hayır. st.session_state tamamen bellekte bulunur ve etkin tarayıcı bağlantısına özgüdür. Kullanıcı sayfayı yenilerse veya sekmeyi kapatırsa barista ile olan görüşme geçmişi kaybolur.

Üretim uygulaması için ADK çalıştırıcısını Cloud Firestore veya Redis gibi kalıcı depolama arka ucuna bağlarsınız. ADK, sayfa yeniden yüklemeleri ve cihazlar arasında sohbet geçmişini kaydetmeyi ve devam ettirmeyi kolaylaştıran yerleşik hizmet soyutlamaları (ör. SessionService) sağlar.

6. Aracıyı Cloud Run'a dağıtma

Cloud Run'ın yerleşik buildpack'lerini kullanarak Streamlit uygulamasını doğrudan kaynaktan dağıtacaksınız. En az ayrıcalık ilkesine uymak için varsayılan Compute Engine hizmet hesabını kullanmak yerine özel bir hizmet hesabı kullanarak oluşturma ve dağıtım yapacaksınız.

  1. Özel bir hizmet hesabı oluşturun:
  gcloud iam service-accounts create barista-agent-sa \
    --description="Service account for Coffee Barista ADK agent on Cloud Run" \
    --display-name="Barista Agent Service Account"
  1. Yeni hizmet hesabına Gemini Enterprise Ajan Platformu kullanıcı rolünü (roles/aiplatform.user) verin:
  gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/aiplatform.user"
  1. Yeni hizmet hesabı e-posta adresini --service-account işaretiyle ileterek hizmeti gcloud run deploy kullanarak dağıtın:
gcloud run deploy coffee-barista \
  --source . \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=codelab-streamlit-rag-adk \
  --command "/cnb/lifecycle/launcher" \
  --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
  --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
  1. Dağıtım tamamlandıktan sonra komut çıkışında hizmet URL'sini bulun.

💬 Tartışma: Kapsayıcıları ve Kaynakları Dağıtma ve IAM Güvenliği

Dockerfile veya Procfile oluşturmadan gcloud run deploy –source komutunu kullanarak Cloud Run'a dağıtım yaptık. Cloud Run, Python uygulamamızı nasıl derleyip yürütebildi?

Cloud Run, deponuzu analiz etmek için arka planda Buildpack'leri kullanır. Motor, requirements.txt ve Python kaynak dosyalarının varlığını algıladığında otomatik olarak bir Python çalışma zamanı kapsayıcısı derleyip paketler.

Özel bir Dockerfile yazarak kapsayıcınızın sistem paketleri ve temel katmanları üzerinde tam kontrol sahibi olursunuz. Procfile, bir kapsayıcıyı tam olarak yapılandırmadan başlangıç komutunu bildirmenin daha basit bir yoludur. Ancak hızlı dağıtımlar için kaynaktan dağıtım (--source) oldukça etkilidir.

Neden varsayılan Compute Engine hizmet hesabını kullanmak yerine barista-agent-sa adlı özel bir hizmet hesabı oluşturma gibi ek bir adım uyguladık?

Önce güvenlik! Varsayılan Compute Engine hizmet hesabında varsayılan olarak çok geniş kapsamlı Düzenleyici izinleri bulunur. Cloud Run kapsayıcımızı varsayılan hizmet hesabı altında çalıştırmak, uygulamamızda bir güvenlik hatası olması durumunda saldırganların Google Cloud projemizdeki diğer kaynakları okumasına, yazmasına veya silmesine olanak tanıyabilir.

Özel bir hizmet hesabı oluşturup yalnızca roles/aiplatform.user rolünü atayarak en az ayrıcalık ilkesini uygularız: Uygulama, Gemini'ı çağırmak için tam olarak ihtiyaç duyduğu erişime sahiptir ve daha fazlasına sahip değildir.

7. RAG davranışını test etme

Cloud Run hizmeti URL'sini bir web tarayıcısında açın ve yapay zeka Barista'ya sorular sorarak temellendirme ve güvenlik kısıtlamalarını test edin.

  1. Menü içi istek: İstem: "Güçlü ve sıcak bir şey öner."Beklenen: Ajan, espresso öneriyor.
  2. Menü dışı tuzak: Sor: "Matcha frappuccino var mı?"Beklenen: Temsilci kibarca reddedip menüde olmadığını açıklıyor.
  3. Alerjenlere duyarlı istek: "Laktoz intoleransım var. Ne yiyebilirim?" diye sorun. Beklenen: Temsilci yalnızca süt içermeyen menü öğelerini (ör. yulaf sütlü latte, espresso, soğuk demleme) önerir. Cappuccino veya Croissant'ı ÖNERMEZ.

RAG davranışını test etme

8. İsteğe bağlı: Vektör Arama'yı kullanarak aracınızı Firestore'da temellendirin

Üretim senaryosunda, menü öğelerinizi yerel bir menu.json dosyasına depolamak ideal değildir. Çünkü menüde yapılan herhangi bir değişiklik, kapsayıcı görüntüsünün yeniden oluşturulmasını ve Cloud Run hizmetinin yeniden dağıtılmasını gerektirir.

Uygulamayı dinamik ve ölçeklenebilir hale getirmek için menü verilerinizi Cloud Firestore'a taşıyabilir ve yalnızca anlamsal benzerliğe dayalı olarak en alakalı menü öğelerini almak için Vector Search'ü kullanabilirsiniz.

Vector Search'ü kullanarak Firestore'u entegre etme

1. Firestore API'yi etkinleştirme ve veritabanını başlatma

Firestore API'yi etkinleştirmek ve yerel modda coffee-menu adlı bir Firestore veritabanı oluşturmak için aşağıdaki komutları çalıştırın:

gcloud services enable firestore.googleapis.com

gcloud firestore databases create --database="coffee-menu" --location=$REGION

Not: API'nin etkinleştirilmesi 1-2 dakika sürebilir. Veritabanı oluşturma komutu sizden API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry? girmenizi isterse devam etmek için Y yazın veya bir dakika bekleyip komutu yeniden çalıştırın.

2. Menü verileriyle Firestore'u başlatma

Firestore veritabanınızı menu.json dosyanızdaki menü öğeleriyle hızlıca doldurmak için Cloud Shell'de yerel olarak bir Python komut dosyası çalıştırabilirsiniz.

  1. Doldurma komut dosyasını çalıştırmak için Firestore ve GenAI istemci kitaplıklarını Cloud Shell'e yerel olarak yükleyin:
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
  1. İlk yayın komut dosyası oluşturma seed.py:
cloudshell edit seed.py
  1. Aşağıdaki kodu seed.py yapıştırın:
# seed.py
import json
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database="coffee-menu")
client = genai.Client(
   vertexai=True,
   project=os.environ.get("PROJECT_ID"),
   location=os.environ.get("REGION", "us-central1")
)

with open("menu.json", "r") as f:
   menu_items = json.load(f)

for item in menu_items:
   # Use the name as the document ID
   doc_id = item["name"].lower().replace(" ", "-")

   # Generate text embedding using Vertex AI text-embedding-004 model
   text_to_embed = f"{item['name']}: {item['description']}"
   response = client.models.embed_content(
       model="text-embedding-004",
       contents=text_to_embed,
   )
   embedding = response.embeddings[0].values

   # Add embedding vector to the menu item data
   item["embedding"] = Vector(embedding)

   db.collection("menu").document(doc_id).set(item)

print("Firestore menu collection seeded with vector embeddings successfully!")
  1. Komut dosyasını çalıştırın:
python3 seed.py

3. Firestore vektör dizini oluşturma

Menü öğelerinizde vektör aramaları yapmak için Firestore veritabanınızdaki embedding alanında birleşik vektör dizini oluşturmanız gerekir.

Cloud Shell terminalinde aşağıdaki komutu çalıştırın:

gcloud firestore indexes composite create \
 --collection-group=menu \
 --query-scope=COLLECTION \
 --database="coffee-menu" \
 --field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'

Not: Firestore dizini oluşturma işlemi arka planda çalışır ve tamamlanması birkaç dakika sürebilir. Dizin oluşturulurken codelab'in sonraki adımlarına geçebilirsiniz.

4. Hizmet Hesabına Firestore Erişimi Verme

Cloud Run hizmetinizin Firestore'u sorgulayabilmesi için hizmet hesabına Cloud Datastore Kullanıcısı (roles/datastore.user) rolünü vermeniz gerekir:

gcloud projects add-iam-policy-binding $PROJECT_ID \
 --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --role="roles/datastore.user"

Not: Native modda Cloud Firestore'u kullanıyor olsak da Google Cloud, erişim denetimini yönetmek için birleştirilmiş Cloud Datastore IAM rollerini (roles/datastore.viewer veya roles/datastore.user) kullanır.

5. Kodu güncelleme

Şimdi kodunuzu, menüyü menu.json dosyasından okumak yerine Firestore'dan alacak şekilde güncelleyin.

  1. Cloud Shell Düzenleyici'de requirements.txt dosyasını açın:
cloudshell edit requirements.txt
  1. Firestore ve GenAI istemci kitaplıklarını dosyanın sonuna ekleyip kaydedin:
google-cloud-firestore==2.27.0
google-genai==2.11.0
  1. Cloud Shell Düzenleyici'de agent.py dosyasını açın:
cloudshell edit agent.py
  1. agent.py içinde # [START get_menu] bloğunu bulun ve tamamını (# [START get_menu] ile # [END get_menu] arasında) aşağıdaki Firestore uygulamasıyla değiştirin:
# [START get_menu]
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

def get_menu(query: str) -> str:
   """Retrieves coffee shop menu items matching the user's query.

   Args:
       query: The search query or preference to find matching menu items.

   Returns:
       str: A JSON string representing the list of top matching menu items.
   """
   try:
       # Initialize clients
       db = firestore.Client(database="coffee-menu")
       client = genai.Client()

       # Generate embedding for the search query
       response = client.models.embed_content(
           model="text-embedding-004",
           contents=query,
       )
       query_vector = response.embeddings[0].values

       # Search the Firestore database using Vector Search
       results = db.collection("menu").find_nearest(
           vector_field="embedding",
           query_vector=Vector(query_vector),
           distance_measure=DistanceMeasure.COSINE,
           limit=3,
       ).stream()

       menu_data = []
       for doc in results:
           item = doc.to_dict()
           # Remove embedding field to save tokens
           item.pop("embedding", None)
           menu_data.append(item)

       return json.dumps(menu_data)
   except Exception as e:
       return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]
  1. Cloud Shell Düzenleyici'de app.py dosyasını açın:
cloudshell edit app.py
  1. app.py içinde # [START load_menu] bloğunu bulun ve tamamını (# [START load_menu] ile # [END load_menu] arası) aşağıdaki Firestore yükleme mantığıyla değiştirin:
# [START load_menu]
from google.cloud import firestore

try:
   db = firestore.Client(database="coffee-menu")
   docs = db.collection("menu").stream()
   menu_items = []
   for doc in docs:
       item = doc.to_dict()
       item.pop("embedding", None)
       menu_items.append(item)
except Exception as e:
   st.error(f"Error loading menu from Firestore: {e}")
   menu_items = []
# [END load_menu]

6. Cloud Run'a yeniden dağıtma

Güncellenen uygulamayı dağıtın:

gcloud run deploy coffee-barista \
 --source . \
 --region $REGION \
 --allow-unauthenticated \
 --command "/cnb/lifecycle/launcher" \
 --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
 --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global

7. Firestore entegrasyonunu doğrulama

Temsilcinin Firestore'a bağlantısını test etmek için doğrudan Firestore'a yeni bir menü öğesi ekleyin ve temsilcinin bu öğeyi önerdiğini doğrulayın.

  1. Python kullanarak Firestore'daki menu koleksiyonuna yeni bir doküman yazmak için Cloud Shell'de aşağıdaki komutu çalıştırın:
python3 -c "
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database='coffee-menu')
client = genai.Client(
   vertexai=True,
   project=os.environ.get('PROJECT_ID'),
   location=os.environ.get('REGION', 'us-central1')
)

name = 'Matcha Green Tea Latte'
desc = 'Creamy steamed milk infused with premium Japanese matcha powder.'
res = client.models.embed_content(
   model='text-embedding-004',
   contents=f'{name}: {desc}'
)
embedding = res.embeddings[0].values

db.collection('menu').document('matcha-latte').set({
   'name': name,
   'description': desc,
   'price': 5.50,
   'tags': ['sweet', 'hot', 'dairy-free'],
   'allergens': [],
   'embedding': Vector(embedding)
})
print('Successfully added Matcha Latte with vector embeddings!')
"
  1. Sohbet oturumunu temizlemek ve yeni veritabanı durumunu yüklemek için tarayıcıda Streamlit uygulamanızı yenileyin.
  2. Unutmayın:
    • Matcha Green Tea Latte, kenar çubuğu menüsünde otomatik olarak görünür.
    • Chatbot'a "Maçalı içecekleriniz var mı?" diye sorun.
    • Temsilci, yeni Matcha Green Tea Latte'yi az önce eklediğiniz açıklama ve fiyatla birlikte başarıyla önermelidir. Bu, aracının doğrudan canlı Firestore veritabanınızda sorgu tabanlı olduğunu onaylar.

9. Temizleme

Google Cloud faturalandırma hesabınızın sürekli olarak ücretlendirilmesini önlemek için dağıtılan Cloud Run hizmetini ve özel hizmet hesabını silin.

Cloud Run hizmetini silin:

gcloud run services delete coffee-barista --region $REGION --quiet

Özel hizmet hesabını silin:

gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet

(İsteğe bağlı) Firestore veritabanını silin (oluşturulduysa):

gcloud firestore databases delete --database="coffee-menu" --quiet

İsteğe bağlı adım: Projenin tamamını silin. ⚠️BUNU YALNIZCA BU LABORATUVAR İÇİN ÖZEL BİR PROJE OLUŞTURDUYSANIZ YAPIN.

gcloud projects delete $PROJECT_ID

10. Tebrikler

Tebrikler! Google'ın ADK'sını ve Cloud Run'ı kullanarak bir Retrieval-Augmented Generation (RAG) yapay zeka barista ajanı oluşturup dağıtmış olmanız gerekir.

Öğrendikleriniz

  • Python'da basit RAG araçları oluşturma
  • ADK LlmAgent ve InMemoryRunner'yı kullanma
  • Streamlit'te durum bilgisi içeren sohbet deneyimleri oluşturma.
  • Kaynak tabanlı derlemeler kullanarak Streamlit'i Cloud Run'a dağıtma.

Referans belgeleri