Gemini'yi kullanarak FastAPI chatbot uygulamasını Cloud Run'a dağıtma

Gemini'yi kullanarak FastAPI chatbot uygulamasını Cloud Run'a dağıtma

Bu codelab hakkında

subjectSon güncelleme Nis 2, 2025
account_circleBir Google çalışanı tarafından yazılmıştır

1. Giriş

Bu codelab'de, FastAPI uygulamasını Cloud Run'a nasıl dağıtacağınızı öğreneceksiniz. Uygulama, bir Gemini modeline istem gönderen bir chatbot uygulamasıdır.

  • FastAPI'yi Cloud Run'a dağıtma
  • Google istemci kitaplığı kullanarak Python'da Cloud Run'dan Gemini'yi isteme

2. Kurulum ve Gereksinimler

Bu codelab boyunca kullanılacak ortam değişkenlerini ayarlayın.

PROJECT_ID=<YOUR_PROJECT_ID>
REGION
=<YOUR_REGION>
GEMINI_MODEL
=gemini-2.0-flash-001

SERVICE_NAME
=fastapi-gemini
SERVICE_ACCOUNT
=fastapi-gemini-sa
SERVICE_ACCOUNT_ADDRESS
=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com

Aşağıdaki komutu çalıştırarak hizmet hesabını oluşturun:

gcloud iam service-accounts create $SERVICE_ACCOUNT \
 
--display-name="Service Account for FastAPI Gemini CR service"

Hizmet hesabınıza Vertex AI Kullanıcı rolü ile Gemini'ye erişim izni verin.

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
 
--role="roles/aiplatform.user"

3. Uygulamayı oluşturma

Kodunuz için bir dizin oluşturun.

mkdir codelab-cr-fastapi-gemini
cd codelab
-cr-fastapi-gemini

Öncelikle bir şablon dizini oluşturarak html şablonlarını oluşturursunuz.

mkdir templates
cd templates

Aşağıdaki içeriği içeren ai_message.html adlı yeni bir dosya oluşturun:

<div class="message-container ai-message-container">
    {{ ai_response_text }}
</div>

Aşağıdaki içeriği içeren message.html adlı yeni bir dosya oluşturun:

<div class="message-container user-message">
    {{ message }}
</div>

Aşağıdaki içeriği içeren index.html adlı yeni bir dosya oluşturun:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPI HTMX Gemini Chat</title>
    <style>
        body { font-family: sans-serif; max-width: 700px; margin: auto; padding: 20px; background-color: #f4f4f4; }
        #chat-messages { border: 1px solid #ccc; background-color: #fff; padding: 15px; height: 400px; overflow-y: scroll; margin-bottom: 15px; border-radius: 5px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); }
        .message-container { margin-bottom: 10px; padding: 8px 12px; border-radius: 15px; max-width: 80%; word-wrap: break-word; }
        .user-message { background-color: #dcf8c6; align-self: flex-end; margin-left: auto; text-align: right; border-bottom-right-radius: 0;}
        .ai-message-container { background-color: #eee; align-self: flex-start; margin-right: auto; border-bottom-left-radius: 0;}
        .ai-message-container p { margin: 0.2em 0; } /* Spacing for streamed paragraphs */
        .ai-message-container p:first-child { margin-top: 0; }
        .ai-message-container p:last-child { margin-bottom: 0; }
        form { display: flex; margin-top: 10px; }
        input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 20px; margin-right: 10px; }
        button { padding: 10px 20px; background-color: #0b93f6; color: white; border: none; border-radius: 20px; cursor: pointer; font-weight: bold; }
        button:hover { background-color: #0a84dd; }
    </style>
    <script src="https://unpkg.com/htmx.org@2.0.4"
    integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/htmx-ext-sse@2.2.2" crossorigin="anonymous"></script>
</head>
<body>

    <h1>Chat with Gemini</h1>

    <div id="chat-messages">
        {% for msg in messages %}
             {# Render initial messages if needed #}
        {% endfor %}
    </div>

    <form
        hx-post="/ask"             {# Post to the /ask endpoint #}
        hx-target="#chat-messages" {# Target the main chat area #}
        hx-swap="beforeend"        {# Append the response (user msg + AI placeholder) #}
        hx-on::after-request="this.reset(); document.getElementById('chat-messages').scrollTop = document.getElementById('chat-messages').scrollHeight;" {# Clear form & scroll down #}
        >
        <input type="text" name="message" placeholder="Ask Gemini..." autofocus autocomplete="off">
        <button type="submit">Send</button>
    </form>

    <script>
        // Initial scroll to bottom on page load (if needed)
        window.onload = () => {
            const chatBox = document.getElementById('chat-messages');
            chatBox.scrollTop = chatBox.scrollHeight;
        }
    </script>

</body>
</html>

Şimdi kök dizininde Python kodunuzu ve diğer dosyalarınızı oluşturun.

cd ..

Aşağıdaki içeriği içeren bir .gcloudignore dosyası oluşturun:

__pycache__

Aşağıdaki içeriğe sahip main.py adlı bir dosya oluşturun:

from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from typing import List, Annotated
from google import genai
import os

# in case the env var isn't set, use YOUR_<VARIABLE> as the default
# to help with debugging
project_id = os.getenv("PROJECT_ID", "YOUR_PROJECT_ID")
region = os.getenv("REGION", "YOUR_REGION")
gemini_model = os.getenv("GEMINI_MODEL", "gemini-2.0-flash-001")

app = FastAPI(title="FastAPI HTMX Chat")

templates = Jinja2Templates(directory="templates")

genai_client = genai.Client(
   
vertexai=True, project=project_id, location=region
)

system_prompt = f"""
You're a chatbot that helps pass the time with small talk, that is
polite conversation about unimportant or uncontroversial matters
that allows people to pass the time. Please keep your answers short.
"""

chat_messages: List[str] = []

# --- Routes ---
@app.get("/", response_class=HTMLResponse)
async def get_chat_ui(request: Request):
    """Serves the main chat page."""
   
print("Serving index.html")
   
return templates.TemplateResponse(
       
"index.html",
       
{"request": request, "messages": chat_messages} # Pass existing messages
   
)

@app.post("/ask", response_class=HTMLResponse)
async def ask_gemini_and_respond(
   
request: Request,
   
# Use Annotated for dependency injection with Form data
   
message: Annotated[str, Form()]
):
   
   
user_msg_html = templates.get_template('message.html').render({'message': message})
   
   
print("asking gemini...")
   
response = genai_client.models.generate_content(
       
model=gemini_model,
       
contents=[message],
       
config=genai.types.GenerateContentConfig(
           
system_instruction=system_prompt,
           
temperature=0.7,
       
),
   
)
   
   
print("Gemini responded with: " + response.text)
   
   
ai_response_html = templates.get_template('ai_message.html').render({'ai_response_text': response.text})

   
combined_html = user_msg_html + ai_response_html

   
return HTMLResponse(content=combined_html)

Aşağıdaki içeriği içeren bir Dockerfile oluşturun:

# Build stage
FROM python:3.12-slim AS builder

WORKDIR /app

# Install poetry
RUN pip install poetry
RUN poetry self add poetry-plugin-export

# Copy poetry files
COPY pyproject.toml poetry.lock* ./

# Copy application code
COPY . .

# Export dependencies to requirements.txt
RUN poetry export -f requirements.txt --output requirements.txt

# Final stage
FROM python:3.12-slim

RUN apt-get update && apt-get install -y libcairo2 python3-dev libffi-dev

WORKDIR /app

# Copy files from builder
COPY --from=builder /app/ .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Compile bytecode to improve startup latency
# -q: Quiet mode
# -b: Write legacy bytecode files (.pyc) alongside source
# -f: Force rebuild even if timestamps are up-to-date
RUN python -m compileall -q -b -f .

# Expose port
EXPOSE 8080

# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

pyproject.toml dosyası oluşturma

[tool.poetry]
name = "codelab"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.115.12"
uvicorn = {extras = ["standard"], version = "^0.34.0"}
jinja2 = "^3.1.6"
python-multipart = "^0.0.20"
google-genai = "^1.8.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

4. Cloud Run&#39;a dağıt

gcloud run deploy $SERVICE_NAME \
 --source . \
 --allow-unauthenticated \
 --service-account=$SERVICE_ACCOUNT_ADDRESS \
 --set-env-vars=PROJECT_ID=$PROJECT_ID \
 --set-env-vars=REGION=$REGION \
 --set-env-vars=GEMINI_MODEL=$GEMINI_MODEL

5. Hizmetinizi test etme

Web tarayıcınızda Hizmet URL'sini açın ve Gemini'ye bir soru sorun (ör.Gökyüzü neden mavi?).

6. Tebrikler!

Codelab'i tamamladığınız için tebrikler.

İşlediğimiz konular

  • FastAPI'yi Cloud Run'a dağıtma
  • Google istemci kitaplığı kullanarak Python'da Cloud Run'dan Gemini'yi isteme

7. Temizleme

Cloud Run hizmetini silmek için https://console.cloud.google.com/run adresindeki Cloud Run Cloud Console'a gidip hizmeti silin.

Projenin tamamını silmeyi seçerseniz https://console.cloud.google.com/cloud-resource-manager adresine gidip 2. adımda oluşturduğunuz projeyi seçin ve Sil'i tıklayın. Projeyi silerseniz Cloud SDK'nızdaki projeleri değiştirmeniz gerekir. gcloud projects list komutunu çalıştırarak mevcut tüm projelerin listesini görüntüleyebilirsiniz.