FastAPI-Chatbot-Anwendung mit Gemini in Cloud Run bereitstellen

FastAPI-Chatbot-Anwendung mit Gemini in Cloud Run bereitstellen

Informationen zu diesem Codelab

subjectZuletzt aktualisiert: Apr. 2, 2025
account_circleVerfasst von einem Google-Mitarbeiter

1. Einführung

In diesem Codelab erfahren Sie, wie Sie eine FastAPI-Anwendung in Cloud Run bereitstellen. Die App ist eine Chatbot-App, die ein Gemini-Modell anfordert.

  • FastAPI in Cloud Run bereitstellen
  • Gemini über Cloud Run in Python mit einer Google-Clientbibliothek auffordern

2. Einrichtung und Anforderungen

Legen Sie Umgebungsvariablen fest, die in diesem Codelab verwendet werden.

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

Erstellen Sie das Dienstkonto mit dem folgenden Befehl:

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

Gewähren Sie Ihrem Dienstkonto mit der Rolle „Vertex AI-Nutzer“ Zugriff auf Gemini.

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

3. Anwendung erstellen

Erstellen Sie ein Verzeichnis für Ihren Code.

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

Erstellen Sie zuerst die HTML-Vorlagen, indem Sie ein Verzeichnis „Vorlagen“ anlegen.

mkdir templates
cd templates

Erstellen Sie eine neue Datei mit dem Namen ai_message.html und folgendem Inhalt:

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

Erstellen Sie eine neue Datei mit dem Namen message.html und folgendem Inhalt:

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

Erstellen Sie eine neue Datei mit dem Namen index.html und folgendem Inhalt:

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

Erstellen Sie nun Ihren Python-Code und andere Dateien im Stammverzeichnis.

cd ..

Erstellen Sie eine .gcloudignore-Datei mit folgendem Inhalt:

__pycache__

Erstellen Sie eine Datei mit dem Namen main.py und mit folgendem Inhalt:

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)

Erstellen Sie eine Dockerfile-Datei mit folgendem Inhalt:

# 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-Datei erstellen

[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. In Cloud Run bereitstellen

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

Öffnen Sie die Dienst-URL in Ihrem Webbrowser und stellen Sie Gemini eine Frage, z.B. „Warum ist der Himmel blau?“

6. Das wars! Sie haben das Lab erfolgreich abgeschlossen.

Herzlichen Glückwunsch zum Abschluss des Codelabs.

Behandelte Themen

  • FastAPI in Cloud Run bereitstellen
  • Gemini über Cloud Run in Python mit einer Google-Clientbibliothek auffordern

7. Bereinigen

Wenn Sie den Cloud Run-Dienst löschen möchten, rufen Sie die Cloud Console unter https://console.cloud.google.com/run auf und löschen Sie den Dienst.

Wenn Sie das gesamte Projekt löschen möchten, rufen Sie https://console.cloud.google.com/cloud-resource-manager auf, wählen Sie das in Schritt 2 erstellte Projekt aus und klicken Sie auf „Löschen“. Wenn Sie das Projekt löschen, müssen Sie die Projekte in Ihrem Cloud SDK ändern. Sie können eine Liste aller verfügbaren Projekte aufrufen, indem Sie gcloud projects list ausführen.