1. Genel Bakış
Son Güncelleme: 18.10.2024
Yazan: Sanggyu Lee (sanggyulee@google.com)
Oluşturacağınız uygulama
Bu codelab'de, perakende müşterileri için bir üretken yapay zeka aracısı oluşturacaksınız
Uygulamanız şunları yapabilecek:
- Mobil cihazlarda veya masaüstünde çalışır.
- Bir ürünün fotoğrafını çekerek sesli sohbet üzerinden sipariş verebilirsiniz.
- Uygulamanın işleyiş şekli şu şekildedir: Ürünün fotoğrafını çeker ve örneğin "Bunu 3 kutu sipariş etmek istiyorum. Honolulu'daki Walmart'ın yöneticisiyim." Uygulama, fotoğrafı Cloud Storage'a yükler ve ses kaydınızı metne dönüştürür. Bu bilgiler daha sonra Vertex AI'daki bir Gemini modeline gönderilir. Bu model, öğeyi ve mağazanızı (Walmart Honolulu) tanımlar. İstek, satış siparişi ölçütlerini karşılıyorsa sistem benzersiz bir kimliğe sahip bir satış siparişi oluşturur.
2. Neler öğreneceksiniz?
Neler öğreneceksiniz?
- Vertex AI ile yapay zeka aracısı oluşturma
- Speech-to-Text API hizmetine ses gönderip transkript alma
- Yapay zeka aracınızı Cloud Run'a dağıtma
Bu codelab, Gemini ile GenAI temsilci uygulamalarına odaklanmaktadır. Alakalı olmayan kavramlar ve kod blokları işaretlenmiştir ve yalnızca kopyalayıp yapıştırmanız için kullanımınıza sunulmuştur.
Gerekenler
- Google Cloud Hesabı
- Python, JavaScript ve Google Cloud hakkında bilgi
Mimari
Bu temsilci, resim ve metin istemleriyle Gemini'nin çoklu mod özelliklerini kullanarak sipariş vermeyi kolaylaştırır. Sipariş sözlü olarak verilirse Google Speech'in Chirp 2 modeli siparişi metne dönüştürür. Bu metin, sağlanan bir görselle birlikte Vertex AI'ın Gemini modelini sorgulamak için kullanılır.
Aşağıdakileri oluşturacağız :
- Geliştirme ortamı oluşturma
- Kullanıcıların mobil cihaz veya bilgisayar üzerinden çağıracağı Flask uygulaması. Uygulama, Cloud Run'da çalışır.
3. Kurulum ve Gereksinimler
Kendi hızınızda ortam kurulumu
- Google Cloud Console'da oturum açın ve yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.
- Proje adı, bu projenin katılımcılarının görünen adıdır. Google API'leri tarafından kullanılmayan bir karakter dizesidir. Dilediğiniz zaman güncelleyebilirsiniz.
- Proje kimliği, tüm Google Cloud projelerinde benzersizdir ve değiştirilemez (ayarlandıktan sonra değiştirilemez). Cloud Console, benzersiz bir dize otomatik olarak oluşturur. Bu dizenin ne olduğu genellikle önemli değildir. Çoğu kod laboratuvarında proje kimliğinize (genellikle PROJECT_ID olarak tanımlanır) referans vermeniz gerekir. Oluşturulan kimliği beğenmezseniz rastgele başka bir kimlik oluşturabilirsiniz. Alternatif olarak, kendi anahtarınızı deneyerek kullanılabilir olup olmadığını görebilirsiniz. Bu adımdan sonra değiştirilemez ve proje boyunca geçerli kalır.
- Bazı API'lerin kullandığı üçüncü bir değer (proje numarası) olduğunu belirtmek isteriz. Bu üç değer hakkında daha fazla bilgiyi dokümanlar bölümünde bulabilirsiniz.
- Ardından, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmak çok pahalı değildir. Bu eğitimden sonra faturalandırılmamak için kaynakları kapatmak istiyorsanız oluşturduğunuz kaynakları veya projeyi silebilirsiniz. Yeni Google Cloud kullanıcıları 300 ABD doları değerindeki ücretsiz deneme programına uygundur.
Cloud Shell'i Başlatma
Google Cloud, dizüstü bilgisayarınızdan uzaktan çalıştırılabilir olsa da bu kod laboratuvarında bulutta çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacaksınız.
Google Cloud Console'da, sağ üstteki araç çubuğunda Cloud Shell simgesini tıklayın:
Ortam sağlanıp bağlantı kurulabilmesi için birkaç saniye beklemeniz gerekir. İşlem tamamlandığında aşağıdakine benzer bir ekran görürsünüz:
Bu sanal makinede ihtiyaç duyacağınız tüm geliştirme araçları yüklüdür. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışır. Bu sayede ağ performansını ve kimlik doğrulamayı büyük ölçüde iyileştirir. Bu codelab'deki tüm çalışmalarınızı tarayıcıda yapabilirsiniz. Hiçbir şey yüklemeniz gerekmez.
4. Başlamadan önce
API'leri etkinleştir
Laboratuvar için gerekli API'leri etkinleştirin. Bu işlem birkaç dakika sürer.
gcloud services enable \ run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com \ speech.googleapis.com \ sqladmin.googleapis.com \ logging.googleapis.com \ compute.googleapis.com \ servicenetworking.googleapis.com \ monitoring.googleapis.com
Beklenen konsol çıkışı :
Operation "operations/acf.p2-639929424533-ffa3a09b-7663-4b31-8f78-5872bf4ad778" finished successfully.
Ortamları ayarlama
CLI komutundan önce Google Cloud ortamları için parametreleri ayarlayın.
export PROJECT_ID="<YOUR_PROJECT_ID>" export VPC_NAME="<YOUR_VPC_NAME>" e.g : demonetwork export SUBNET_NAME="<YOUR_SUBNET_NAME>" e.g : genai-subnet export REGION="<YOUR_REGION>" e.g : us-central1 export GENAI_BUCKET="<YOUR BUCKET FOR AGENT>" # eg> genai-${PROJECT_ID}
For example :
export PROJECT_ID=$(gcloud config get-value project) export VPC_NAME="demonetwork" export SUBNET_NAME="genai-subnet" export REGION="us-central1" export GENAI_BUCKET="genai-${PROJECT_ID}"
5. Altyapınızı oluşturun
Uygulamanız için ağı oluşturma
Uygulamanız için bir VPC oluşturun. "demonetwork" adlı VPC'yi oluşturmak için şunları çalıştırın :
gcloud compute networks create demonetwork \ --subnet-mode custom
"demonetwork" ağında 10.10.0.0/24 adres aralığına sahip "genai-subnet" alt ağını oluşturmak için:
gcloud compute networks subnets create genai-subnet \ --network demonetwork \ --region us-central1 \ --range 10.10.0.0/24
PostgreSQL için Cloud SQL oluşturma
Özel hizmet erişimi için ayrılan IP adresi aralıkları.
gcloud compute addresses create google-managed-services-my-network \ --global \ --purpose=VPC_PEERING \ --prefix-length=16 \ --description="peering range for Google" \ --network=demonetwork
Özel bağlantı oluşturun.
gcloud services vpc-peerings connect \ --service=servicenetworking.googleapis.com \ --ranges=google-managed-services-my-network \ --network=demonetwork
Cloud SQL örneği oluşturmak için gcloud sql instances create komutunu çalıştırın.
gcloud sql instances create sql-retail-genai \ --database-version POSTGRES_14 \ --tier db-f1-micro \ --region=$REGION \ --project=$PROJECT_ID \ --network=projects/${PROJECT_ID}/global/networks/${VPC_NAME} \ --no-assign-ip \ --enable-google-private-path
Bu komutun çalıştırılması birkaç dakika sürebilir.
Beklenen konsol çıkışı :
Created [https://sqladmin.googleapis.com/sql/v1beta4/projects/evident-trees-438609-q3/instances/sql-retail-genai]. NAME: sql-retail-genai DATABASE_VERSION: POSTGRES_14 LOCATION: us-central1-c TIER: db-f1-micro PRIMARY_ADDRESS: - PRIVATE_ADDRESS: 10.66.0.3 STATUS: RUNNABLE
Uygulamanız ve kullanıcı için veritabanı oluşturma
sql-retail-genai içinde bir Cloud SQL veritabanı oluşturmak için gcloud sql databases create komutunu çalıştırın.
gcloud sql databases create retail-orders \ --instance sql-retail-genai
PostgreSQL veritabanı kullanıcısı oluşturun. Şifrenizi değiştirmeniz daha iyi olur.
gcloud sql users create aiagent --instance sql-retail-genai --password "genaiaigent2@"
Resim depolamak için paket oluşturma
Temsilciniz için özel bir paket oluşturma
gsutil mb -l $REGION gs://$GENAI_BUCKET
Paket izinlerini güncelleme
gsutil iam ch serviceAccount:<your service account>: roles/storage.objectUser gs://$GENAI_BUCKET
Varsayılan Compute hizmet hesabınızı kullanacağınızı varsayarsak :
gsutil iam ch serviceAccount:$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")-compute@developer.gserviceaccount.com:roles/storage.objectUser gs://$GENAI_BUCKET
6. Uygulamanız için kodlar hazırlama
Kodları hazırlama
Sipariş verme web uygulaması Flask kullanılarak oluşturulur ve mobil cihazlarda veya bilgisayarlarda bir web tarayıcısında çalıştırılabilir. Bağlı cihazın mikrofonuna ve kamerasına erişir ve Google Speech'in Chirp 2 modelini ve Vertex AI'ın Gemini Pro 1.5 modelini kullanır. Sıralama sonuçları bir Cloud SQL veritabanında depolanır.
Önceki sayfada verilen örnek ortam değişkeni adlarını kullandıysanız aşağıdaki kodu değişiklik yapmadan kullanabilirsiniz. Ortam değişkeni adlarını özelleştirdiyseniz koddaki bazı değişken değerlerini buna göre değiştirmeniz gerekir.
Aşağıdaki gibi iki dizin oluşturun.
mkdir -p genai-agent/templates
requirements.txt dosyası oluşturma
vi ~/genai-agent/requirements.txt
Metin dosyasına paketlerin listesini girin.
aiofiles==24.1.0
aiohappyeyeballs==2.4.3
aiohttp==3.10.9
aiosignal==1.3.1
annotated-types==0.7.0
asn1crypto==1.5.1
attrs==24.2.0
blinker==1.8.2
cachetools==5.5.0
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.3.2
click==8.1.7
cloud-sql-python-connector==1.12.1
cryptography==43.0.1
docstring_parser==0.16
Flask==3.0.3
frozenlist==1.4.1
google-api-core==2.20.0
google-auth==2.35.0
google-cloud-aiplatform==1.69.0
google-cloud-bigquery==3.26.0
google-cloud-core==2.4.1
google-cloud-resource-manager==1.12.5
google-cloud-speech==2.27.0
google-cloud-storage==2.18.2
google-crc32c==1.6.0
google-resumable-media==2.7.2
googleapis-common-protos==1.65.0
greenlet==3.1.1
grpc-google-iam-v1==0.13.1
grpcio==1.66.2
grpcio-status==1.66.2
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==3.0.0
multidict==6.1.0
numpy==2.1.2
packaging==24.1
pg8000==1.31.2
pgvector==0.3.5
proto-plus==1.24.0
protobuf==5.28.2
pyasn1==0.6.1
pyasn1_modules==0.4.1
pycparser==2.22
pydantic==2.9.2
pydantic_core==2.23.4
python-dateutil==2.9.0.post0
requests==2.32.3
rsa==4.9
scramp==1.4.5
shapely==2.0.6
six==1.16.0
SQLAlchemy==2.0.35
typing_extensions==4.12.2
urllib3==2.2.3
Werkzeug==3.0.4
yarl==1.13.1
main.py dosyası oluşturun
vi ~/genai-agent/main.py
main.py dosyasına Python kodu girin.
from flask import Flask, render_template, request, jsonify, Response
import os
import base64
from google.api_core.client_options import ClientOptions
from google.cloud.speech_v2 import SpeechClient
from google.cloud.speech_v2.types import cloud_speech
import vertexai
from vertexai.generative_models import GenerativeModel, Part, SafetySetting
from google.cloud import storage
import uuid # Import the uuid module
from typing import Dict # Add this import
import datetime
import json
import re
import os
from google.cloud.sql.connector import Connector
import pg8000
import sqlalchemy
from sqlalchemy import create_engine, text
app = Flask(__name__)
# Replace with your actual project ID
project_id = os.environ.get("PROJECT_ID")
# Use a connection pool to reuse connections and improve performance
# This also handles connection lifecycle management automatically
engine = None
# Configure Google Cloud Storage
storage_client = storage.Client()
bucket_name = os.environ.get("GENAI_BUCKET")
client = SpeechClient(
client_options=ClientOptions(
api_endpoint="us-central1-speech.googleapis.com",
),
)
def get_engine():
global engine # Use global to access/modify the global engine variable
if engine is None: # Create the engine only once
connector = Connector()
def getconn() -> pg8000.dbapi.Connection:
conn: pg8000.dbapi.Connection = connector.connect(
os.environ["INSTANCE_CONNECTION_NAME"], # Cloud SQL instance connection name
"pg8000",
user=os.environ["DB_USER"],
password=os.environ["DB_PASS"],
db=os.environ["DB_NAME"],
ip_type="PRIVATE",
)
return conn
engine = create_engine(
"postgresql+pg8000://",
creator=getconn,
pool_pre_ping=True, # Check connection validity before use
pool_size=5, # Adjust pool size as needed
max_overflow=2, # Allow some overflow for bursts
pool_recycle=300, # Recycle connections after 5 minutes
)
return engine
def migrate_db() -> None:
engine = get_engine() # Get the engine (creates it if necessary)
with engine.begin() as conn:
sql = """
CREATE TABLE IF NOT EXISTS image_sales_orders (
order_id SERIAL PRIMARY KEY,
vendor_name VARCHAR(80) NOT NULL,
order_item VARCHAR(100) NOT NULL,
order_boxes INT NOT NULL,
time_cast TIMESTAMP NOT NULL
);
"""
conn.execute(text(sql))
@app.before_request
def init_db():
migrate_db()
#print("Migration complete.")
@app.route('/')
def index():
return render_template('index.html')
@app.route('/orderlist')
def orderlist():
engine = get_engine()
with engine.connect() as conn:
sql = text("""
SELECT order_id, vendor_name, order_item, order_boxes, time_cast
FROM image_sales_orders
ORDER BY time_cast DESC
""")
result = conn.execute(sql).mappings() # Use .mappings() for dict-like access
orders = []
for row in result:
order = {
'OrderId': row['order_id'],
'VendorName': row['vendor_name'],
'OrderItem': row['order_item'],
'OrderBoxes': row['order_boxes'],
'OrderDate': row['time_cast'].strftime('%Y-%m-%d'),
'OrderTime': row['time_cast'].strftime('%H:%M:%S'),
}
orders.append(order)
return render_template('orderlist.html', orders=orders)
@app.route("/upload_photo", methods=["POST"])
def upload_photo():
# Get the uploaded file
file = request.files["photo"]
# Generate a unique filename
filename = f"{uuid.uuid4()}--{file.filename}"
# Upload the file to Google Cloud Storage
bucket = storage_client.get_bucket(bucket_name)
blob = bucket.blob(filename)
generation_match_precondition = 0
blob.upload_from_file(file, if_generation_match=generation_match_precondition)
# Return the destination filename
image_url = f"gs://{bucket_name}/{filename}"
# Return the destination filename
return image_url
@app.route('/upload', methods=['POST'])
def upload():
audio_data = request.form['audio_data']
audio_data = base64.b64decode(audio_data.split(',')[1])
audio_path = f"{uuid.uuid4()}--audio.wav"
with open(audio_path, 'wb') as f:
f.write(audio_data)
transcript = transcribe_speech(audio_path)
os.remove(audio_path)
return jsonify({'transcript': transcript})
@app.route("/orders", methods=["POST"])
def cast_order() -> Response:
prompt = request.form['transcript']
image_url = request.form['image_url']
print(f"Prompt: {prompt}")
print(f"Image URL: {image_url}")
model_response = generate(image_url=image_url, prompt=prompt)
# Extract the text content from the model response
response_text = model_response.text if hasattr(model_response, 'text') else str(model_response)
#print(f"Response from Model !!!!!!: {response_text}")
try:
response_json = json.loads(response_text)
function_name = response_json.get("function")
parameters = response_json.get("parameters")
except json.JSONDecodeError as e:
logging.error(f"JSON decoding error: {e}")
return Response(
"I cannot fulfill your request because I cannot find the [Product Name], [Quantity (Box)], and [Retail Store Name] in the provided image and prompt.",
status=500
)
if function_name == 'Z_SALES_ORDER_SRV/orderlistSet':
engine = get_engine()
with engine.connect() as conn:
try:
# Explicitly convert order_boxes to integer
order_boxes = int(parameters["order_boxes"])
vendor_name = parameters["vendor_name"]
order_item = parameters["order_item"]
# Prepare the SQL statement
sql = text("""
INSERT INTO image_sales_orders (vendor_name, order_item, order_boxes, time_cast)
VALUES (:vendor_name, :order_item, :order_boxes, NOW())
""")
# Prepare parameters
params = {
"vendor_name": vendor_name,
"order_item": order_item,
"order_boxes": order_boxes,
}
# Execute the SQL statement with parameters
conn.execute(sql, params)
conn.commit()
response_message = f"Dear [{vendor_name}],\n\nYour order has been completed as follows. \n\nItem Name : {order_item}\nQTY(Boxes) : {order_boxes}\n\nThanks."
return Response(response_message, status=200)
except (KeyError, ValueError) as e:
logging.error(f"Error inserting into database: {e}")
response_message = "Error processing your order. Please check the input data."
return Response(response_message, status=500)
else:
# Handle other function names if necessary
return Response("Unknown function.", status=400)
def transcribe_speech(audio_file):
with open(audio_file, "rb") as f:
content = f.read()
config = cloud_speech.RecognitionConfig(
auto_decoding_config=cloud_speech.AutoDetectDecodingConfig(),
language_codes=["auto"],
#language_codes=["ko-KR"], -- In case that needs to choose specific language
model="chirp_2",
)
request = cloud_speech.RecognizeRequest(
recognizer=f"projects/{project_id}/locations/us-central1/recognizers/_",
config=config,
content=content,
)
response = client.recognize(request=request)
transcript = ""
for result in response.results:
transcript += result.alternatives[0].transcript
return transcript
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
#app.run(debug=True)
def generate(image_url,prompt):
vertexai.init(project=project_id, location="us-central1")
model = GenerativeModel("gemini-1.5-pro-002")
image1 = Part.from_uri(uri=image_url, mime_type="image/jpeg")
prompt_default = """A retail store will give you an image with order details as an Input. You will identify the order details and provide an output as the following json format. You should not add any comment on it. The Box quantity should be arabic number. You can extract the item name from a given image or prompt. However, you should extract the retail store name or the quantity from only the text prompt but not the given image. All parameter values are strings. Don't assume any parameters. Do not wrap the json codes in JSON markers.
{\"function\":\"Z_SALES_ORDER_SRV/orderlistSet\",\"parameters\":{\"vendor_name\":Retail store name,\"order_item\":Item name,\"order_boxes\":Box quantity}}
If you are not clear on any parameter, provide the output as follows.
{\"function\":\"None\"}
You should not use the json markdown for the result.
Input :"""
generation_config = {
"max_output_tokens": 8192,
"temperature": 0,
"top_p": 0.95,
}
safety_settings = [
SafetySetting(
category=SafetySetting.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold=SafetySetting.HarmBlockThreshold.OFF
),
SafetySetting(
category=SafetySetting.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=SafetySetting.HarmBlockThreshold.OFF
),
SafetySetting(
category=SafetySetting.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold=SafetySetting.HarmBlockThreshold.OFF
),
SafetySetting(
category=SafetySetting.HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold=SafetySetting.HarmBlockThreshold.OFF
),
]
responses = model.generate_content(
[prompt_default, image1, prompt],
generation_config=generation_config,
safety_settings=safety_settings,
stream=True,
)
response = ""
for content in responses:
response += content.text
print(f"Content: {content}")
print(f"Content type: {type(content)}")
print(f"Content attributes: {dir(content)}")
print(f"response_texts={response}")
if response.startswith('json'):
return clean_json_string(response)
else:
return response
def clean_json_string(json_string):
pattern = r'^```json\s*(.*?)\s*```$'
cleaned_string = re.sub(pattern, r'\1', json_string, flags=re.DOTALL)
return cleaned_string.strip()
index.html dosyası oluşturma
vi ~/genai-agent/templates/index.html
HTML kodunu index.html dosyasına girin.
<!DOCTYPE html>
<html>
<head>
<title>GenAI Agent for Retail</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* Styles adjusted for chatbot interface */
body {
font-family: Arial, sans-serif;
background-color: #343541;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 10px;
background-color: #343541;
}
.message {
max-width: 80%;
margin-bottom: 15px;
padding: 10px;
border-radius: 10px;
color: #dcdcdc;
word-wrap: break-word;
}
.user-message {
background-color: #3e3f4b;
align-self: flex-end;
}
.assistant-message {
background-color: #444654;
align-self: flex-start;
}
.message-input {
padding: 10px;
background-color: #40414f;
display: flex;
align-items: center;
}
.message-input textarea {
flex: 1;
padding: 10px;
border: none;
border-radius: 5px;
resize: none;
background-color: #40414f;
color: #dcdcdc;
height: 40px;
max-height: 100px;
overflow-y: auto;
}
.message-input button {
padding: 15px;
margin-left: 5px;
background-color: #19c37d;
border: none;
border-radius: 5px;
color: white;
font-weight: bold;
cursor: pointer;
flex-shrink: 0;
}
.image-preview {
max-width: 100%;
border-radius: 10px;
margin-bottom: 10px;
}
.hidden {
display: none;
}
/* Media queries for responsive design */
@media screen and (max-width: 600px) {
.message {
max-width: 100%;
}
.message-input {
flex-direction: column;
}
.message-input textarea {
width: 100%;
margin-bottom: 10px;
}
.message-input button {
width: 100%;
margin: 5px 0;
}
}
</style>
</head>
<body>
<div class="chat-container" id="chat-container">
<!-- Messages will be appended here -->
</div>
<div class="message-input">
<input type="file" name="photo" id="photo" accept="image/*" capture="camera" class="hidden">
<button id="uploadImageButton">📷</button>
<button id="recordButton">🎤</button>
<textarea id="transcript" rows="1" placeholder="Enter a message here by voice or typing..."></textarea>
<button id="sendButton">Send</button>
</div>
<script>
const chatContainer = document.getElementById('chat-container');
const transcriptInput = document.getElementById('transcript');
const sendButton = document.getElementById('sendButton');
const recordButton = document.getElementById('recordButton');
const uploadImageButton = document.getElementById('uploadImageButton');
const photoInput = document.getElementById('photo');
let mediaRecorder;
let audioChunks = [];
let imageUrl = '';
function appendMessage(content, sender) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', sender === 'user' ? 'user-message' : 'assistant-message');
if (typeof content === 'string') {
const messageContent = document.createElement('p');
messageContent.innerText = content;
messageDiv.appendChild(messageContent);
} else {
messageDiv.appendChild(content);
}
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
sendButton.addEventListener('click', () => {
const message = transcriptInput.value.trim();
if (message !== '') {
appendMessage(message, 'user');
// Prepare form data
const formData = new FormData();
formData.append('transcript', message);
formData.append('image_url', imageUrl);
// Send the message to the server
fetch('/orders', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
appendMessage(data, 'assistant');
// Reset imageUrl after sending
imageUrl = '';
})
.catch(error => {
console.error('Error:', error);
});
transcriptInput.value = '';
}
});
transcriptInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendButton.click();
}
});
recordButton.addEventListener('click', async () => {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
recordButton.innerText = '🎤';
return;
}
let stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
recordButton.innerText = '⏹️';
mediaRecorder.ondataavailable = event => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = async () => {
let audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
audioChunks = [];
let reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = () => {
let base64String = reader.result;
// Send the audio data to the server
fetch('/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'audio_data=' + encodeURIComponent(base64String)
})
.then(response => response.json())
.then(data => {
transcriptInput.value = data.transcript;
})
.catch(error => {
console.error('Error:', error);
});
};
};
});
uploadImageButton.addEventListener('click', () => {
photoInput.click();
});
photoInput.addEventListener('change', function() {
if (photoInput.files && photoInput.files[0]) {
const file = photoInput.files[0];
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.src = e.target.result;
img.classList.add('image-preview');
appendMessage(img, 'user');
};
reader.readAsDataURL(file);
const formData = new FormData();
formData.append('photo', photoInput.files[0]);
// Upload the image to the server
fetch('/upload_photo', {
method: 'POST',
body: formData,
})
.then(response => response.text())
.then(url => {
imageUrl = url;
})
.catch(error => {
console.error('Error uploading photo:', error);
});
}
});
</script>
</body>
</html>
orderlist.html dosyası oluşturun
vi ~/genai-agent/templates/orderlist.html
orderlist.html dosyasına HTML kodu girin.
<!DOCTYPE html>
<html>
<head>
<title>Order List</title>
<style>
body {
font-family: sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
h1 {
text-align: center;
color: #28a745; /* Green header */
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Add a subtle shadow */
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #28a745; /* Green header background */
color: white;
}
tr:nth-child(even) {
background-color: #f8f9fa; /* Alternating row color */
}
tr:hover {
background-color: #e9ecef; /* Hover effect */
}
</style>
</head>
<body>
<h1>Order List</h1>
<table>
<thead>
<tr>
<th>Order ID</th>
<th>Retail Store Name</th>
<th>Order Item</th>
<th>Order Boxes</th>
<th>Order Date</th>
<th>Order Time</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>{{ order.OrderId }}</td>
<td>{{ order.VendorName }}</td>
<td>{{ order.OrderItem }}</td>
<td>{{ order.OrderBoxes }}</td>
<td>{{ order.OrderDate }}</td>
<td>{{ order.OrderTime }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
7. Flask uygulamasını Cloud Run'a dağıtma
Uygulamayı Cloud Run'a dağıtmak için genai-agent dizininden aşağıdaki komutu kullanın:
cd ~/genai-agent
gcloud run deploy --source . genai-agent-sales-order \ --set-env-vars=PROJECT_ID=$PROJECT_ID \ --set-env-vars=REGION=$REGION \ --set-env-vars=INSTANCE_CONNECTION_NAME="${PROJECT_ID}:${REGION}:sql-retail-genai" \ --set-env-vars=DB_USER=aiagent \ --set-env-vars=DB_PASS=genaiaigent2@ \ --set-env-vars=DB_NAME=retail-orders \ --set-env-vars=GENAI_BUCKET=$GENAI_BUCKET \ --network=$PROJECT_ID \ --subnet=$SUBNET_NAME \ --vpc-egress=private-ranges-only \ --region=$REGION \ --allow-unauthenticated
Beklenen çıkış :
Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named [cloud-run-source-deploy] in region [us-central1] will be created. Do you want to continue (Y/n)? Y
Bu işlem birkaç dakika sürer ve başarılı olursa hizmet URL'sini görürsünüz.
Beklenen çıkış :
.......... Building using Buildpacks and deploying container to Cloud Run service [genai-agent-sales-order] in project [xxxx] region [us-central1] ✓ Building and deploying... Done. ✓ Uploading sources... ✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds/395d141c-2dcf-465d-acfb-f97831c448c3?project=xxxx]. ✓ Creating Revision... ✓ Routing traffic... ✓ Setting IAM Policy... Done. Service [genai-agent-sales-order] revision [genai-agent-sales-order-00013-ckp] has been deployed and is serving 100 percent of traffic. Service URL: https://genai-agent-sales-order-xxxx.us-central1.run.app
Hizmet URL'sini Cloud Run konsolunuzdan da kontrol edebilirsiniz.
8. Test
- Cloud Run dağıtımının önceki adımında oluşturulan hizmet URL'sini mobil cihazınıza veya dizüstü bilgisayarınıza yazın.
- Siparişiniz için bir öğenin fotoğrafını çekin ve sipariş miktarını(kutu) ve perakende mağaza adını yazarak veya sesli olarak girin. <ex> "Bu üç kutuyu sipariş etmek istiyorum. Hayır, yedi kutu. Bu Walmart Mountain Vew"
- "Gönder"i tıklayın ve siparişinizin tamamlanıp tamamlanmadığını kontrol edin.
- Sipariş geçmişini {Service URL}/orderlist adresinden kontrol edebilirsiniz.
9. Tebrikler
Tebrikler! Vertex AI'ın çoklu modalite özelliğini kullanarak Gemini'yi kullanarak iş süreçlerini otomatikleştirebilen bir üretken yapay zeka temsilcisi oluşturdunuz.
İstemleri değiştirmenizi ve temsilciyi ihtiyaçlarınıza göre özelleştirmenizi bekliyorum.