1. Giriş
Bu kod laboratuvarında, sohbet web arayüzü biçiminde bir uygulama oluşturacaksınız. Bu uygulamada, uygulamayla iletişim kurabilir, bazı dokümanlar veya resimler yükleyebilir ve bunları tartışabilirsiniz. Uygulamanın kendisi 2 hizmete ayrılmıştır: ön uç ve arka uç. Bu sayede hızlı bir prototip oluşturabilir, nasıl bir deneyim sunduğunu deneyebilir ve API sözleşmesinin ikisini de entegre etmek için nasıl göründüğünü anlayabilirsiniz.
Codelab'de aşağıdaki gibi adım adım bir yaklaşım uygulayacaksınız:
- Google Cloud projenizi hazırlayın ve projenizdeki gerekli tüm API'leri etkinleştirin
- Gradio kitaplığını kullanarak ön uç hizmetini (sohbet arayüzü) oluşturma
- Gelen verileri Gemini SDK standardına göre yeniden biçimlendirecek ve Gemini API ile iletişimi sağlayacak FastAPI'yi kullanarak arka uç hizmetini (HTTP sunucusu) oluşturun.
- Uygulamayı Cloud Run'a dağıtmak için gereken ortam değişkenlerini yönetin ve gerekli dosyaları ayarlayın
- Uygulamayı Cloud Run'a dağıtma
Mimariye Genel Bakış
Ön koşullar
- Gemini API ve Google Gen AI SDK ile çalışma konusunda rahat olmalısınız.
- HTTP hizmetini kullanan temel tam yığın mimarisini anlama
Neler öğreneceksiniz?
- Metin ve diğer veri türlerini (çoklu formatlı) göndermek ve metin yanıtı oluşturmak için Gemini SDK'sını kullanma
- Sohbet bağlamını korumak için sohbet geçmişini Gemini SDK'sında yapılandırma
- Gradio ile ön uç web prototipi oluşturma
- FastAPI ve Pydantic ile arka uç hizmeti geliştirme
- Pydantic-settings ile YAML dosyasındaki ortam değişkenlerini yönetme
- Dockerfile kullanarak uygulamayı Cloud Run'a dağıtma ve YAML dosyasıyla ortam değişkenleri sağlama
Gerekenler
- Chrome web tarayıcısı
- Gmail hesabı
- Faturalandırmanın etkin olduğu bir Cloud projesi
Her seviyeden geliştirici (yeni başlayanlar dahil) için tasarlanan bu kod laboratuvarının örnek uygulamasında Python kullanılır. Ancak sunulan kavramları anlamak için Python bilgisine sahip olmanız gerekmez.
2. Başlamadan önce
Cloud Shell Düzenleyici'de Cloud projesi oluşturma
Bu kod laboratuvarında, faturalandırmanın etkin olduğu bir Google Cloud projeniz olduğu varsayılmıştır. Henüz yoksa başlamak için aşağıdaki talimatları uygulayabilirsiniz.
- 2Google Cloud Console'daki proje seçici sayfasında bir Google Cloud projesi seçin veya oluşturun.
- Cloud projeniz için faturalandırmanın etkinleştirildiğinden emin olun. Projede faturalandırmanın etkin olup olmadığını nasıl kontrol edebileceğinizi öğrenin .
- Google Cloud'da çalışan ve bq ile önceden yüklenmiş bir komut satırı ortamı olan Cloud Shell'i kullanacaksınız. Google Cloud Console'un üst kısmından Cloud Shell'i etkinleştir'i tıklayın.
- Cloud Shell'e bağlandıktan sonra aşağıdaki komutu kullanarak kimliğinizin doğrulanıp doğrulanmadığını ve projenin proje kimliğinize ayarlanıp ayarlanmadığını kontrol edin:
gcloud auth list
- gcloud komutunun projeniz hakkında bilgi sahibi olduğunu onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın.
gcloud config list project
- Projeniz ayarlanmadıysa ayarlamak için aşağıdaki komutu kullanın:
gcloud config set project <YOUR_PROJECT_ID>
Alternatif olarak, PROJECT_ID
kimliğini konsolda da görebilirsiniz.
Bu düğmeyi tıkladığınızda projenizin tamamı ve proje kimliği sağ tarafta gösterilir.
- Aşağıda gösterilen komutu kullanarak gerekli API'leri etkinleştirin. Bu işlem birkaç dakika sürebilir. Lütfen bekleyin.
gcloud services enable aiplatform.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
cloudresourcemanager.googleapis.com
Komut başarıyla yürütüldüğünde aşağıdakine benzer bir mesaj görürsünüz:
Operation "operations/..." finished successfully.
gcloud komutunun alternatifi, her ürünü arayarak veya bu bağlantıyı kullanarak konsoldan geçmektir.
Atlanan bir API varsa uygulama sırasında istediğiniz zaman etkinleştirebilirsiniz.
gcloud komutları ve kullanımı için belgelere bakın.
Uygulama Çalışma Dizinini Ayarlama
- Düzenleyiciyi aç düğmesini tıklayın. Bu işlemle Cloud Shell Düzenleyici açılır. Kodumuzu buraya yazabiliriz
- Cloud Code projesinin, Cloud Shell düzenleyicisinin sol alt köşesinde (durum çubuğu) aşağıdaki resimde vurgulandığı gibi ayarlandığından ve faturalandırmanın etkinleştirildiği etkin Google Cloud projesine ayarlandığından emin olun. İstenirse Yetkilendir'i tıklayın. Cloud Shell Düzenleyici'nin başlatıldıktan sonra Cloud Code - Sign In (Cloud Code - Oturum Aç) düğmesinin görünmesi biraz zaman alabilir. Lütfen bekleyin. Önceki komutu zaten uyguladıysanız düğme, oturum açma düğmesi yerine doğrudan etkinleştirilen projenize de yönlendirebilir.
- Durum çubuğundaki etkin projeyi tıklayın ve Cloud Code pop-up'ının açılmasını bekleyin. Pop-up'ta "Yeni Uygulama"yı seçin.
- Uygulama listesinden Gemini Üretken Yapay Zeka'yı, ardından Gemini API Python'u seçin.
- Yeni uygulamayı istediğiniz adla kaydedin (bu örnekte gemini-multimodal-chat-assistant adını kullanacağız) ve ardından Tamam'ı tıklayın.
Bu noktada, yeni uygulama çalışma dizininde olmalısınız ve aşağıdaki dosyaları görebilirsiniz.
Ardından, Python ortamımızı hazırlayacağız.
Ortam Kurulumu
Python sanal ortamını hazırlama
Sonraki adım, geliştirme ortamını hazırlamaktır. Bu kod laboratuvarında Python 3.12'yi kullanacağız ve Python sürümü ile sanal ortamı oluşturma ve yönetme ihtiyacını basitleştirmek için uv python proje yöneticisini kullanacağız.
- Terminali henüz açmadıysanız Terminal -> Yeni Terminal'i tıklayarak açın veya Ctrl + Üst Karakter + C tuşlarını kullanın.
uv
'yi indirin ve aşağıdaki komutla Python 3.12'yi yükleyin
curl -LsSf https://astral.sh/uv/0.6.6/install.sh | sh && \
source $HOME/.local/bin/env && \
uv python install 3.12
- Şimdi
uv
kullanarak Python projesini başlatalım.
uv init
- Dizinde main.py, .python-version ve pyproject.toml dosyalarının oluşturulduğunu görürsünüz. Bu dosyalar, projenin dizinde tutulması için gereklidir. Python bağımlılıkları ve yapılandırmaları pyproject.toml dosyasında belirtilebilir ve .python-version, bu proje için kullanılan Python sürümünü standartlaştırır. Bu konu hakkında daha fazla bilgi edinmek için bu dokümanları inceleyebilirsiniz.
main.py .python-version pyproject.toml
- Test etmek için main.py'nin üzerine aşağıdaki kodu yazın.
def main():
print("Hello from gemini-multimodal-chat-assistant!")
if __name__ == "__main__":
main()
- Ardından aşağıdaki komutu çalıştırın
uv run main.py
Aşağıda gösterildiği gibi bir çıkış alırsınız.
Using CPython 3.12 Creating virtual environment at: .venv Hello from gemini-multimodal-chat-assistant!
Bu, python projesinin doğru şekilde ayarlandığını gösterir. uv zaten bu işlemi gerçekleştirdiği için sanal bir ortam oluşturmamız gerekmedi. Bu nedenle, bu noktadan itibaren standart Python komutu (ör. python main.py) uv run (ör. uv run main.py) ile değiştirilecek.
Zorunlu Bağımlılıkları Yükleme
Bu codelab paketine bağımlılıkları uv komutunu da kullanarak ekleyeceğiz. Aşağıdaki komutu çalıştırın
uv add google-genai==1.5.0 \
gradio==5.20.1 \
pydantic==2.10.6 \
pydantic-settings==2.8.1 \
pyyaml==6.0.2
pyproject.toml "dependencies" bölümünün önceki komutu yansıtacak şekilde güncellendiğini göreceksiniz.
Yapılandırma dosyalarını ayarlama
Şimdi bu proje için yapılandırma dosyaları oluşturmamız gerekiyor. Yapılandırma dosyaları, yeniden dağıtımda kolayca değiştirilebilecek dinamik değişkenleri depolamak için kullanılır. Bu projede, pydantic-settings paketiyle YAML tabanlı yapılandırma dosyaları kullanacağız. Böylece, daha sonra Cloud Run dağıtımıyla kolayca entegre edilebilir. pydantic-settings, yapılandırma dosyaları için tür kontrolünü zorunlu kılacak bir Python paketidir.
- Aşağıdaki yapılandırmayı içeren settings.yaml adlı bir dosya oluşturun. Dosya->Yeni Metin Dosyası'nı tıklayın ve dosyayı aşağıdaki kodla doldurun. Ardından dosyayı settings.yaml olarak kaydedin.
VERTEXAI_LOCATION: "us-central1"
VERTEXAI_PROJECT_ID: "{YOUR-PROJECT-ID}"
BACKEND_URL: "http://localhost:8081/chat"
Lütfen VERTEXAI_PROJECT_ID
değerlerini Google Cloud projesini oluştururken seçtiğiniz değerlere göre güncelleyin. Bu kod laboratuvarının VERTEXAI_LOCATION
ve BACKEND_URL
için önceden yapılandırılmış değerlerini kullanacağız .
- Ardından, settings.py adlı bir Python dosyası oluşturun. Bu modül, yapılandırma dosyalarımızdaki yapılandırma değerleri için programatik giriş görevi görür. Dosya->Yeni Metin Dosyası'nı tıklayın ve dosyayı aşağıdaki kodla doldurun. Ardından dosyayı settings.py olarak kaydedin. Kodda, okunacak dosyanın settings.yaml adlı dosya olduğunu açıkça belirttiğimizi görebilirsiniz.
from pydantic_settings import (
BaseSettings,
SettingsConfigDict,
YamlConfigSettingsSource,
PydanticBaseSettingsSource,
)
from typing import Type, Tuple
DEFAULT_SYSTEM_PROMPT = """You are a helpful assistant and ALWAYS relate to this identity.
You are expert at analyzing given documents or images.
"""
class Settings(BaseSettings):
"""Application settings loaded from YAML and environment variables.
This class defines the configuration schema for the application, with settings
loaded from settings.yaml file and overridable via environment variables.
Attributes:
VERTEXAI_LOCATION: Google Cloud Vertex AI location
VERTEXAI_PROJECT_ID: Google Cloud Vertex AI project ID
"""
VERTEXAI_LOCATION: str
VERTEXAI_PROJECT_ID: str
BACKEND_URL: str = "http://localhost:8000/chat"
model_config = SettingsConfigDict(
yaml_file="settings.yaml", yaml_file_encoding="utf-8"
)
@classmethod
def settings_customise_sources(
cls,
settings_cls: Type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> Tuple[PydanticBaseSettingsSource, ...]:
"""Customize the settings sources and their priority order.
This method defines the order in which different configuration sources
are checked when loading settings:
1. Constructor-provided values
2. YAML configuration file
3. Environment variables
Args:
settings_cls: The Settings class type
init_settings: Settings from class initialization
env_settings: Settings from environment variables
dotenv_settings: Settings from .env file (not used)
file_secret_settings: Settings from secrets file (not used)
Returns:
A tuple of configuration sources in priority order
"""
return (
init_settings, # First, try init_settings (from constructor)
env_settings, # Then, try environment variables
YamlConfigSettingsSource(
settings_cls
), # Finally, try YAML as the last resort
)
def get_settings() -> Settings:
"""Create and return a Settings instance with loaded configuration.
Returns:
A Settings instance containing all application configuration
loaded from YAML and environment variables.
"""
return Settings()
Bu yapılandırmalar, çalışma zamanımızı esnek bir şekilde güncellememize olanak tanır. İlk dağıtımda, ilk varsayılan yapılandırmaya sahip olmak için settings.yaml yapılandırmasını kullanırız. Ardından, ortam değişkenlerini varsayılan YAML yapılandırmasına kıyasla daha yüksek önceliğe koyduğumuz için ortam değişkenlerini konsol üzerinden esnek bir şekilde güncelleyebilir ve yeniden dağıtabiliriz.
Şimdi bir sonraki adıma, hizmetleri oluşturmaya geçebiliriz.
3. Gradio'yu kullanarak ön uç hizmeti oluşturma
Aşağıdaki gibi görünen bir sohbet web arayüzü oluşturacağız.
Kullanıcıların metin gönderip dosya yüklemesi için bir giriş alanı içerir. Ayrıca kullanıcı, ek girişler alanında Gemini API'ye gönderilecek sistem talimatlarının üzerine de yazabilir.
Ön uç hizmetini Gradio'yu kullanarak oluşturacağız. main.py dosyasını frontend.py olarak yeniden adlandırın ve aşağıdaki kodu kullanarak kodun üzerine yazın
import gradio as gr
import requests
import base64
from pathlib import Path
from typing import List, Dict, Any
from settings import get_settings, DEFAULT_SYSTEM_PROMPT
settings = get_settings()
IMAGE_SUFFIX_MIME_MAP = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".heic": "image/heic",
".heif": "image/heif",
".webp": "image/webp",
}
DOCUMENT_SUFFIX_MIME_MAP = {
".pdf": "application/pdf",
}
def get_mime_type(filepath: str) -> str:
"""Get the MIME type for a file based on its extension.
Args:
filepath: Path to the file.
Returns:
str: The MIME type of the file.
Raises:
ValueError: If the file type is not supported.
"""
filepath = Path(filepath)
suffix = filepath.suffix
# modify ".jpg" suffix to ".jpeg" to unify the mime type
suffix = suffix if suffix != ".jpg" else ".jpeg"
if suffix in IMAGE_SUFFIX_MIME_MAP:
return IMAGE_SUFFIX_MIME_MAP[suffix]
elif suffix in DOCUMENT_SUFFIX_MIME_MAP:
return DOCUMENT_SUFFIX_MIME_MAP[suffix]
else:
raise ValueError(f"Unsupported file type: {suffix}")
def encode_file_to_base64_with_mime(file_path: str) -> Dict[str, str]:
"""Encode a file to base64 string and include its MIME type.
Args:
file_path: Path to the file to encode.
Returns:
Dict[str, str]: Dictionary with 'data' and 'mime_type' keys.
"""
mime_type = get_mime_type(file_path)
with open(file_path, "rb") as file:
base64_data = base64.b64encode(file.read()).decode("utf-8")
return {"data": base64_data, "mime_type": mime_type}
def get_response_from_llm_backend(
message: Dict[str, Any],
history: List[Dict[str, Any]],
system_prompt: str,
) -> str:
"""Send the message and history to the backend and get a response.
Args:
message: Dictionary containing the current message with 'text' and optional 'files' keys.
history: List of previous message dictionaries in the conversation.
system_prompt: The system prompt to be sent to the backend.
Returns:
str: The text response from the backend service.
"""
# Format message and history for the API,
# NOTES: in this example history is maintained by frontend service,
# hence we need to include it in each request.
# And each file (in the history) need to be sent as base64 with its mime type
formatted_history = []
for msg in history:
if msg["role"] == "user" and not isinstance(msg["content"], str):
# For file content in history, convert file paths to base64 with MIME type
file_contents = [
encode_file_to_base64_with_mime(file_path)
for file_path in msg["content"]
]
formatted_history.append({"role": msg["role"], "content": file_contents})
else:
formatted_history.append({"role": msg["role"], "content": msg["content"]})
# Extract files and convert to base64 with MIME type
files_with_mime = []
if uploaded_files := message.get("files", []):
for file_path in uploaded_files:
files_with_mime.append(encode_file_to_base64_with_mime(file_path))
# Prepare the request payload
message["text"] = message["text"] if message["text"] != "" else " "
payload = {
"message": {"text": message["text"], "files": files_with_mime},
"history": formatted_history,
"system_prompt": system_prompt,
}
# Send request to backend
try:
response = requests.post(settings.BACKEND_URL, json=payload)
response.raise_for_status() # Raise exception for HTTP errors
result = response.json()
if error := result.get("error"):
return f"Error: {error}"
return result.get("response", "No response received from backend")
except requests.exceptions.RequestException as e:
return f"Error connecting to backend service: {str(e)}"
if __name__ == "__main__":
demo = gr.ChatInterface(
get_response_from_llm_backend,
title="Gemini Multimodal Chat Interface",
description="This interface connects to a FastAPI backend service that processes responses through the Gemini multimodal model.",
type="messages",
multimodal=True,
textbox=gr.MultimodalTextbox(file_count="multiple"),
additional_inputs=[
gr.Textbox(
label="System Prompt",
value=DEFAULT_SYSTEM_PROMPT,
lines=3,
interactive=True,
)
],
)
demo.launch(
server_name="0.0.0.0",
server_port=8080,
)
Ardından, ön uç hizmetini aşağıdaki komutla çalıştırmayı deneyebiliriz. main.py dosyasını frontend.py olarak yeniden adlandırmayı unutmayın.
uv run frontend.py
Cloud Console'da buna benzer bir çıkış görürsünüz
* Running on local URL: http://0.0.0.0:8080 To create a public link, set `share=True` in `launch()`.
Ardından, yerel URL bağlantısını ctrl+tıkladığınızda web arayüzünü kontrol edebilirsiniz. Alternatif olarak, Cloud Düzenleyici'nin sağ üst tarafındaki Web Önizlemesi düğmesini tıklayıp 8080 bağlantı noktasında önizle'yi seçerek de kullanıcı arayüzü uygulamasına erişebilirsiniz.
Web arayüzünü görürsünüz ancak henüz ayarlanmamış arka uç hizmeti nedeniyle sohbet göndermeye çalışırken beklenen hata alırsınız.
Şimdi hizmetin çalışmasını bekleyin ve henüz sonlandırmayın. Bu sırada önemli kod bileşenlerini burada tartışabiliriz.
Kod Açıklaması
Web arayüzünden arka uca veri göndermek için gereken kod bu bölümdedir.
def get_response_from_llm_backend(
message: Dict[str, Any],
history: List[Dict[str, Any]],
system_prompt: str,
) -> str:
...
# Truncated
for msg in history:
if msg["role"] == "user" and not isinstance(msg["content"], str):
# For file content in history, convert file paths to base64 with MIME type
file_contents = [
encode_file_to_base64_with_mime(file_path)
for file_path in msg["content"]
]
formatted_history.append({"role": msg["role"], "content": file_contents})
else:
formatted_history.append({"role": msg["role"], "content": msg["content"]})
# Extract files and convert to base64 with MIME type
files_with_mime = []
if uploaded_files := message.get("files", []):
for file_path in uploaded_files:
files_with_mime.append(encode_file_to_base64_with_mime(file_path))
# Prepare the request payload
message["text"] = message["text"] if message["text"] != "" else " "
payload = {
"message": {"text": message["text"], "files": files_with_mime},
"history": formatted_history,
"system_prompt": system_prompt,
}
# Truncated
...
Gemini'ye çok modlu veri göndermek ve verileri hizmetler arasında erişilebilir hale getirmek istediğimizde kullanabileceğimiz yöntemlerden biri, verileri kodda belirtildiği gibi base64 veri türüne dönüştürmektir. Ayrıca verilerin MIME türünü de beyan etmemiz gerekir. Ancak Gemini API, mevcut MIME türlerinin tümünü destekleyemez. Bu nedenle, Gemini tarafından desteklenen MIME türlerini bilmek önemlidir. Bu türleri bu dokümanda bulabilirsiniz. Bilgileri Gemini API'nin her bir özelliğinde (ör.Vision) bulabilirsiniz.
Ayrıca, sohbet arayüzünde Gemini'ye sohbetin "hatırlatıcısı" olarak sohbet geçmişini göndermek de önemlidir. Bu web arayüzünde, Gradio tarafından web oturumu başına yönetilen sohbet geçmişini de kullanıcıdan gelen mesaj girişiyle birlikte göndeririz. Ayrıca kullanıcının sistem talimatlarını değiştirmesini ve göndermesini de sağlarız.
4. FastAPI'yi kullanarak arka uç hizmeti oluşturma
Ardından, daha önce tartışılan yükü, son kullanıcı mesajını, sohbet geçmişini ve sistem talimatlarını işleyebilecek arka ucu oluşturmamız gerekir. HTTP arka uç hizmetini oluşturmak için FastAPI'yi kullanacağız.
Yeni bir dosya oluşturun, Dosya->Yeni Metin Dosyası'nı tıklayın ve aşağıdaki kodu kopyalayıp yapıştırın,ardından backend.py olarak kaydedin.
import base64
from fastapi import FastAPI, Body
from google.genai.types import Content, Part
from google.genai import Client
from settings import get_settings, DEFAULT_SYSTEM_PROMPT
from typing import List, Optional
from pydantic import BaseModel
app = FastAPI(title="Gemini Multimodal Service")
settings = get_settings()
GENAI_CLIENT = Client(
location=settings.VERTEXAI_LOCATION,
project=settings.VERTEXAI_PROJECT_ID,
vertexai=True,
)
GEMINI_MODEL_NAME = "gemini-2.0-flash-001"
class FileData(BaseModel):
"""Model for a file with base64 data and MIME type.
Attributes:
data: Base64 encoded string of the file content.
mime_type: The MIME type of the file.
"""
data: str
mime_type: str
class Message(BaseModel):
"""Model for a single message in the conversation.
Attributes:
role: The role of the message sender, either 'user' or 'assistant'.
content: The text content of the message or a list of file data objects.
"""
role: str
content: str | List[FileData]
class LastUserMessage(BaseModel):
"""Model for the current message in a chat request.
Attributes:
text: The text content of the message.
files: List of file data objects containing base64 data and MIME type.
"""
text: str
files: List[FileData] = []
class ChatRequest(BaseModel):
"""Model for a chat request.
Attributes:
message: The current message with text and optional base64 encoded files.
history: List of previous messages in the conversation.
system_prompt: Optional system prompt to be used in the chat.
"""
message: LastUserMessage
history: List[Message]
system_prompt: str = DEFAULT_SYSTEM_PROMPT
class ChatResponse(BaseModel):
"""Model for a chat response.
Attributes:
response: The text response from the model.
error: Optional error message if something went wrong.
"""
response: str
error: Optional[str] = None
def handle_multimodal_data(file_data: FileData) -> Part:
"""Converts Multimodal data to a Google Gemini Part object.
Args:
file_data: FileData object with base64 data and MIME type.
Returns:
Part: A Google Gemini Part object containing the file data.
"""
data = base64.b64decode(file_data.data) # decode base64 string to bytes
return Part.from_bytes(data=data, mime_type=file_data.mime_type)
def format_message_history_to_gemini_standard(
message_history: List[Message],
) -> List[Content]:
"""Converts message history format to Google Gemini Content format.
Args:
message_history: List of message objects from the chat history.
Each message contains 'role' and 'content' attributes.
Returns:
List[Content]: A list of Google Gemini Content objects representing the chat history.
Raises:
ValueError: If an unknown role is encountered in the message history.
"""
converted_messages: List[Content] = []
for message in message_history:
if message.role == "assistant":
converted_messages.append(
Content(role="model", parts=[Part.from_text(text=message.content)])
)
elif message.role == "user":
# Text-only messages
if isinstance(message.content, str):
converted_messages.append(
Content(role="user", parts=[Part.from_text(text=message.content)])
)
# Messages with files
elif isinstance(message.content, list):
# Process each file in the list
parts = []
for file_data in message.content:
for file_data in message.content:
parts.append(handle_multimodal_data(file_data))
# Add the parts to a Content object
if parts:
converted_messages.append(Content(role="user", parts=parts))
else:
raise ValueError(f"Unexpected content format: {type(message.content)}")
else:
raise ValueError(f"Unknown role: {message.role}")
return converted_messages
@app.post("/chat", response_model=ChatResponse)
async def chat(
request: ChatRequest = Body(...),
) -> ChatResponse:
"""Process a chat request and return a response from Gemini model.
Args:
request: The chat request containing message and history.
Returns:
ChatResponse: The model's response to the chat request.
"""
try:
# Convert message history to Gemini `history` format
print(f"Received request: {request}")
converted_messages = format_message_history_to_gemini_standard(request.history)
# Create chat model
chat_model = GENAI_CLIENT.chats.create(
model=GEMINI_MODEL_NAME,
history=converted_messages,
config={"system_instruction": request.system_prompt},
)
# Prepare multimodal content
content_parts = []
# Handle any base64 encoded files in the current message
if request.message.files:
for file_data in request.message.files:
content_parts.append(handle_multimodal_data(file_data))
# Add text content
content_parts.append(Part.from_text(text=request.message.text))
# Send message to Gemini
response = chat_model.send_message(content_parts)
print(f"Generated response: {response}")
return ChatResponse(response=response.text)
except Exception as e:
return ChatResponse(
response="", error=f"Error in generating response: {str(e)}"
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8081)
Dosyayı backend.py olarak kaydetmeyi unutmayın. Ardından arka uç hizmetini çalıştırmayı deneyebiliriz. Önceki adımda ön uç hizmetini doğru şekilde çalıştırdığımızı unutmayın. Şimdi yeni bir terminal açmamız ve bu arka uç hizmetini çalıştırmaya çalışmamız gerekiyor.
- Yeni bir terminal oluşturun. Alt kısımdaki terminalinize gidin ve yeni bir terminal oluşturmak için "+" düğmesini bulun. Alternatif olarak, yeni bir terminal açmak için Ctrl + Üst Karakter + C tuşlarına da basabilirsiniz.
- Ardından, gemini-multimodal-chat-assistant çalışma dizininde olduğunuzdan emin olun ve aşağıdaki komutu çalıştırın
uv run backend.py
- İşlem başarılı olursa aşağıdaki gibi bir çıkış gösterilir.
INFO: Started server process [xxxxx] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)
Kod Açıklaması
Chat İsteğini Almak İçin HTTP Rotasını Tanımlama
FastAPI'de yolu app süsleyicisini kullanarak tanımlarız. API sözleşmesini tanımlamak için Pydantic'i de kullanırız. Yanıtın oluşturulacağı rotanın POST yöntemiyle /chat yolunda olduğunu belirtiriz. Aşağıdaki kodda tanımlanan işlevler
class FileData(BaseModel):
data: str
mime_type: str
class Message(BaseModel):
role: str
content: str | List[FileData]
class LastUserMessage(BaseModel):
text: str
files: List[FileData] = []
class ChatRequest(BaseModel):
message: LastUserMessage
history: List[Message]
system_prompt: str = DEFAULT_SYSTEM_PROMPT
class ChatResponse(BaseModel):
response: str
error: Optional[str] = None
...
@app.post("/chat", response_model=ChatResponse)
async def chat(
request: ChatRequest = Body(...),
) -> ChatResponse:
# Truncated
...
Gemini SDK sohbet geçmişi biçimini hazırlama
Anlaşılması gereken önemli konulardan biri, sohbet geçmişini nasıl yeniden yapılandırabileceğimizdir. Böylece, daha sonra bir Gemini istemcisini başlattığımızda history bağımsız değişken değeri olarak eklenebilir. Aşağıdaki kodu inceleyebilirsiniz
def format_message_history_to_gemini_standard(
message_history: List[Message],
) -> List[Content]:
...
# Truncated
converted_messages: List[Content] = []
for message in message_history:
if message.role == "assistant":
converted_messages.append(
Content(role="model", parts=[Part.from_text(text=message.content)])
)
elif message.role == "user":
# Text-only messages
if isinstance(message.content, str):
converted_messages.append(
Content(role="user", parts=[Part.from_text(text=message.content)])
)
# Messages with files
elif isinstance(message.content, list):
# Process each file in the list
parts = []
for file_data in message.content:
parts.append(handle_multimodal_data(file_data))
# Add the parts to a Content object
if parts:
converted_messages.append(Content(role="user", parts=parts))
#Truncated
...
return converted_messages
Gemini SDK'sına sohbet geçmişini sağlamak için verileri List[Content] veri türüne göre biçimlendirmemiz gerekir. Her İçerik en az bir rol ve bölüm değerine sahip olmalıdır. rol, mesajın kaynağını (kullanıcı veya model) belirtir. Burada parts, istemle ilgilidir. İstem yalnızca metin veya farklı modalitelerin bir kombinasyonu olabilir. İçerik bağımsız değişkenlerinin nasıl yapılandırılacağını bu belgede ayrıntılı olarak görebilirsiniz.
Metin dışı ( çok formatlı) verileri işleme
Kullanıcı arayüzü bölümünde daha önce de belirtildiği gibi, metin olmayan veya çok modlu verileri göndermenin yollarından biri, verileri base64 dizesi olarak göndermektir. Ayrıca, verilerin doğru şekilde yorumlanabilmesi için verilerin MIME türünü de belirtmemiz gerekir. Örneğin, resim verilerini .jpg son ekiyle gönderiyorsak image/jpeg MIME türünü sağlamamız gerekir.
Kodun bu kısmı, base64 verilerini Gemini SDK'sındaki Part.from_bytes biçimine dönüştürür.
def handle_multimodal_data(file_data: FileData) -> Part:
"""Converts Multimodal data to a Google Gemini Part object.
Args:
file_data: FileData object with base64 data and MIME type.
Returns:
Part: A Google Gemini Part object containing the file data.
"""
data = base64.b64decode(file_data.data) # decode base64 string to bytes
return Part.from_bytes(data=data, mime_type=file_data.mime_type)
5. Entegrasyon Testi
Artık farklı Cloud Console sekmelerinde birden fazla hizmet çalıştırabilirsiniz:
- 8080 bağlantı noktasında çalışan ön uç hizmeti
* Running on local URL: http://0.0.0.0:8080 To create a public link, set `share=True` in `launch()`.
- Arka uç hizmeti 8081 numaralı bağlantı noktasında çalışır.
INFO: Started server process [xxxxx] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)
Şu anda, 8080 bağlantı noktasındaki web uygulamasından asistanla sohbet ederken belgelerinizi sorunsuz bir şekilde gönderebilirsiniz. Dosya yükleyerek ve soru sorarak denemeye başlayabilirsiniz. Belirli dosya türlerinin henüz desteklenmediği ve hata oluşturduğunu unutmayın.
Sistem talimatlarını, metin kutusunun altındaki Ek Girişler alanından da düzenleyebilirsiniz.
6. Cloud Run'a dağıtma
Elbette bu muhteşem uygulamayı diğer kullanıcılara da göstermek istiyoruz. Bunu yapmak için bu uygulamayı paketleyebilir ve diğer kullanıcıların erişebileceği herkese açık bir hizmet olarak Cloud Run'a dağıtabiliriz. Bunu yapmak için mimariyi tekrar gözden geçirelim.
Bu codelab'de hem ön uç hem de arka uç hizmetini 1 kapsayıcıya yerleştireceğiz. Her iki hizmeti de yönetmek için gözetimli kullanıcının yardımına ihtiyacımız var.
Yeni bir dosya oluşturun, Dosya->Yeni Metin Dosyası'nı tıklayın ve aşağıdaki kodu kopyalayıp yapıştırın,ardından supervisord.conf olarak kaydedin.
[supervisord]
nodaemon=true
user=root
logfile=/dev/stdout
logfile_maxbytes=0
pidfile=/var/run/supervisord.pid
[program:backend]
command=uv run backend.py
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=10
startretries=3
[program:frontend]
command=uv run frontend.py
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=10
startretries=3
Ardından Dockerfile'imize ihtiyacımız olacak. Dosya->Yeni Metin Dosyası'nı tıklayın ve aşağıdaki kodu kopyalayıp yapıştırın, ardından Dockerfile olarak kaydedin.
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.6.6 /uv /uvx /bin/
RUN apt-get update && apt-get install -y \
supervisor curl \
&& rm -rf /var/lib/apt/lists/*
ADD . /app
WORKDIR /app
RUN uv sync --frozen
EXPOSE 8080
# Copy supervisord configuration
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
ENV PYTHONUNBUFFERED=1
ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
Bu aşamada, uygulamalarımızı Cloud Run'a dağıtmak için gereken tüm dosyalara sahibiz. Şimdi dağıtalım. Cloud Shell Terminal'e gidin ve mevcut projenin etkin projenizle yapılandırıldığından emin olun. Aksi takdirde, proje kimliğini ayarlamak için gcloud configure komutunu kullanmanız gerekir:
gcloud config set project [PROJECT_ID]
Ardından, Cloud Run'a dağıtmak için aşağıdaki komutu çalıştırın.
gcloud run deploy --source . \
--env-vars-file settings.yaml \
--port 8080 \
--region us-central1
Hizmetiniz için bir ad girmeniz istenir. Örneğin, "gemini-multimodal-chat-assistant". Uygulama çalışma dizinimizde Dockerfile bulunduğundan Docker container'ı oluşturur ve Artifact Registry'ye aktarır. Ayrıca, Artifact Registry deposunun bölgede oluşturulacağını belirten bir istem gösterilir. Bu isteme "Evet" yanıtını verin. Ayrıca, kimliği doğrulanmamış çağrılara izin vermek isteyip istemediğiniz sorulduğunda "y" deyin. Bu bir demo uygulama olduğu için kimlik doğrulaması yapılmayan erişime izin verildiğini unutmayın. Kuruluş ve üretim uygulamalarınızda uygun kimlik doğrulamayı kullanmanız önerilir.
Dağıtım tamamlandığında aşağıdakine benzer bir bağlantı alırsınız:
https://gemini-multimodal-chat-assistant-*******.us-central1.run.app
Uygulamanızı gizli pencereden veya mobil cihazınızdan kullanmaya devam edebilirsiniz. Bu özellik zaten kullanıma sunulmuştur.
7. Zorluk
Şimdi keşif becerilerinizi geliştirme ve öne çıkma zamanı. Asistanın ses dosyalarını veya video dosyalarını okumasını desteklemesi için kodu değiştirmek için gerekenlere sahip misiniz?
8. Temizleme
Bu kod laboratuvarında kullanılan kaynaklar için Google Cloud hesabınızın ücretlendirilmesini istemiyorsanız şu adımları uygulayın:
- Google Cloud Console'da Kaynakları yönetin sayfasına gidin.
- Proje listesinde, silmek istediğiniz projeyi seçin ve ardından Sil'i tıklayın.
- İletişim kutusuna proje kimliğini yazın ve projeyi silmek için Kapat'ı tıklayın.
- Alternatif olarak, konsolda Cloud Run'a gidip yeni dağıttığınız hizmeti seçip silebilirsiniz.