Cara men-deploy aplikasi chatbot FastAPI ke Cloud Run menggunakan Gemini

Cara men-deploy aplikasi chatbot FastAPI ke Cloud Run menggunakan Gemini

Tentang codelab ini

subjectTerakhir diperbarui Apr 2, 2025
account_circleDitulis oleh Googler

1. Pengantar

Ringkasan

Dalam codelab ini, Anda akan mempelajari cara men-deploy aplikasi FastAPI ke Cloud Run. Aplikasi ini adalah aplikasi chatbot yang meminta model Gemini.

Yang akan Anda pelajari

  • Cara men-deploy FastAPI ke Cloud Run
  • Meminta Gemini dari Cloud Run dalam python menggunakan library klien Google

2. Penyiapan dan Persyaratan

Tetapkan variabel lingkungan yang akan digunakan di seluruh codelab ini.

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

Buat akun layanan dengan menjalankan perintah ini:

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

Berikan akses ke Gemini ke akun layanan Anda dengan peran Vertex AI User.

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

3. Membuat aplikasi

Buat direktori untuk kode Anda.

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

Pertama, Anda akan membuat template html dengan membuat direktori template.

mkdir templates
cd templates

Buat file baru bernama ai_message.html dengan konten berikut:

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

Buat file baru bernama message.html dengan konten berikut:

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

Buat file baru bernama index.html dengan konten berikut:

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

Sekarang buat kode python dan file lainnya di direktori root

cd ..

Buat file .gcloudignore dengan konten berikut:

__pycache__

Buat file bernama main.py dengan konten berikut:

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)

Buat Dockerfile dengan konten berikut:

# 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"]

Membuat file pyproject.toml

[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. Men-deploy ke Cloud Run

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

Buka URL Layanan di browser web Anda dan ajukan pertanyaan kepada Gemini, misalnya Mengapa langit berwarna biru?

6. Selamat!

Selamat, Anda telah menyelesaikan codelab.

Yang telah kita bahas

  • Cara men-deploy FastAPI ke Cloud Run
  • Meminta Gemini dari Cloud Run dalam python menggunakan library klien Google

7. Pembersihan

Untuk menghapus layanan Cloud Run, buka Konsol Cloud Cloud Run di https://console.cloud.google.com/run, lalu hapus layanan tersebut.

Jika memilih untuk menghapus seluruh project, Anda dapat membuka https://console.cloud.google.com/cloud-resource-manager, memilih project yang Anda buat di Langkah 2, lalu memilih Hapus. Jika menghapus project, Anda harus mengubah project di Cloud SDK. Anda dapat melihat daftar semua project yang tersedia dengan menjalankan gcloud projects list.