نحوه استقرار یک برنامه چت بات FastAPI در Cloud Run با استفاده از Gemini، نحوه استقرار یک برنامه چت بات FastAPI در Cloud Run با استفاده از Gemini، نحوه استقرار یک برنامه چت بات FastAPI در Cloud Run با استفاده از Gemini، نحوه استقرار یک برنامه چت بات FastAPI در Cloud Run با استفاده از Gemini

۱. مقدمه

نمای کلی

در این آزمایشگاه کد، شما یاد خواهید گرفت که چگونه یک برنامه FastAPI را در Cloud Run مستقر کنید. این برنامه یک برنامه چت‌بات است که یک مدل Gemini را فراخوانی می‌کند.

آنچه یاد خواهید گرفت

  • نحوه استقرار FastAPI در Cloud Run
  • اجرای سریع Gemini از Cloud Run در پایتون با استفاده از یک کتابخانه کلاینت گوگل

۲. تنظیمات و الزامات

متغیرهای محیطی که در سراسر این آزمایشگاه کد استفاده خواهند شد را تنظیم کنید.

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"

۳. اپلیکیشن را ایجاد کنید

یک دایرکتوری برای کد خود ایجاد کنید.

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>

اکنون کد پایتون و سایر فایل‌ها را در دایرکتوری ریشه ایجاد کنید.

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"

۴. استقرار در 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

۵. سرویس خود را آزمایش کنید

آدرس اینترنتی سرویس را در مرورگر وب خود باز کنید و از Gemini یک سوال بپرسید، مثلاً چرا آسمان آبی است؟

۶. تبریک می‌گویم!

تبریک می‌گویم که آزمایشگاه کد را تمام کردید!

آنچه ما پوشش داده‌ایم

  • نحوه استقرار FastAPI در Cloud Run
  • اجرای سریع Gemini از Cloud Run در پایتون با استفاده از یک کتابخانه کلاینت گوگل

۷. تمیز کردن

برای حذف سرویس Cloud Run، به کنسول ابری Cloud Run در آدرس https://console.cloud.google.com/run بروید و سرویس را حذف کنید.

اگر تصمیم به حذف کل پروژه دارید، می‌توانید به آدرس https://console.cloud.google.com/cloud-resource-manager بروید، پروژه‌ای را که در مرحله ۲ ایجاد کرده‌اید انتخاب کنید و گزینه Delete را انتخاب کنید. اگر پروژه را حذف کنید، باید پروژه‌ها را در Cloud SDK خود تغییر دهید. می‌توانید با اجرای gcloud projects list لیست تمام پروژه‌های موجود را مشاهده کنید.