لمحة عن هذا الدرس التطبيقي حول الترميز
1. مقدمة
نظرة عامة
في هذا الدليل التعليمي حول الرموز البرمجية، ستتعرّف على كيفية نشر تطبيق FastAPI على Cloud Run. التطبيق هو تطبيق محادثة آلية يطلب من نموذج Gemini تنفيذ إجراءات معيّنة.
ما ستتعرّف عليه
- كيفية نشر FastAPI على Cloud Run
- طلب Gemini من Cloud Run في Python باستخدام مكتبة عملاء Google
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"
امنح حساب الخدمة إذن الوصول إلى Gemini من خلال دور "مستخدم Vertex AI".
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
أولاً، عليك إنشاء نماذج 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. اختبار الخدمة
افتح عنوان URL للخدمة في متصفّح الويب واطرح سؤالاً على Gemini، مثلاً "لماذا لون السماء أزرق؟".
6. تهانينا!
تهانينا على إكمال دورة codelab.
المواضيع التي تناولناها
- كيفية نشر FastAPI على Cloud Run
- طلب Gemini من Cloud Run في Python باستخدام مكتبة عملاء Google
7. تَنظيم
لحذف خدمة Cloud Run، انتقِل إلى وحدة تحكّم Cloud Run على الرابط https://console.cloud.google.com/run واحذِف الخدمة.
إذا اخترت حذف المشروع بأكمله، يمكنك الانتقال إلى https://console.cloud.google.com/cloud-resource-manager واختيار المشروع الذي أنشأته في الخطوة 2 ثم اختيار "حذف". في حال حذف المشروع، عليك تغيير المشاريع في حزمة Cloud SDK. يمكنك عرض قائمة بجميع المشاريع المتاحة من خلال تشغيل gcloud projects list
.