1. 簡介
總覽
在本程式碼研究室中,您將瞭解如何將 FastAPI 應用程式部署至 Cloud Run。這個應用程式是聊天機器人應用程式,會提示 Gemini 模型。
課程內容
- 如何將 FastAPI 部署至 Cloud Run
- 使用 Google 用戶端程式庫,透過 Python 從 Cloud Run 提示 Gemini
2. 設定和需求
設定本程式碼實驗室全程會用到的環境變數。
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
執行下列指令來建立服務帳戶:
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--display-name="Service Account for FastAPI Gemini CR service"
將 Vertex AI 使用者角色授予服務帳戶,允許存取 Gemini。
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
--role="roles/aiplatform.user"
3. 建立應用程式
為程式碼建立目錄。
mkdir codelab-cr-fastapi-gemini
cd codelab-cr-fastapi-gemini
首先,請建立 templates 目錄,藉此建立 HTML 範本。
mkdir templates
cd templates
建立名為 ai_message.html 的新檔案,並加入下列內容:
<div class="message-container ai-message-container">
{{ ai_response_text }}
</div>
建立名為 message.html 的新檔案,並加入下列內容:
<div class="message-container user-message">
{{ message }}
</div>
建立名為 index.html 的新檔案,並加入下列內容:
<!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>
現在請在根目錄中建立 Python 程式碼和其他檔案
cd ..
建立 .gcloudignore 檔案,並加入以下內容:
__pycache__
建立名為 main.py 的檔案,並加入以下內容:
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)
建立 Dockerfile,並加入以下內容:
# 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 檔案
[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
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. 測試服務
在網路瀏覽器中開啟服務網址,然後向 Gemini 提問,例如「天空為什麼是藍色的?」。
6. 恭喜!
恭喜您完成本程式碼研究室!
涵蓋內容
- 如何將 FastAPI 部署至 Cloud Run
- 使用 Google 用戶端程式庫,透過 Python 從 Cloud Run 提示 Gemini
7. 清理
如要刪除 Cloud Run 服務,請前往 Cloud Run Cloud 控制台 (https://console.cloud.google.com/run) 並刪除服務。
如要刪除整個專案,請前往 https://console.cloud.google.com/cloud-resource-manager,選取您在步驟 2 中建立的專案,然後選擇「刪除」。刪除專案後,您必須在 Cloud SDK 中變更專案。如要查看所有可用專案的清單,請執行 gcloud projects list。