1. Übersicht
Zuletzt aktualisiert:18. Oktober 2024
Von Sanggyu Lee (sanggyulee@google.com)
Umfang
In diesem Codelab erstellen Sie einen GenAI-Kundenservicemitarbeiter für Einzelhändler
Mit der Anwendung können Sie Folgendes tun:
- Funktioniert auf Mobilgeräten und Computern.
- Sie können ein Foto von einem Artikel aufnehmen und ihn per Sprachchat bestellen.
- So funktioniert die App: Sie machen einfach ein Foto des Artikels und sagen beispielsweise: Ich möchte drei Schachteln davon bestellen. Ich bin die Managerin von Walmart Honolulu.“ Die App lädt das Foto in Cloud Storage hoch und transkribiert Ihre Sprachaufnahme. Diese Informationen werden dann an ein Gemini-Modell in Vertex AI gesendet, das den Artikel und Ihren Shop (Walmart Honolulu) identifiziert. Wenn die Anfrage die Kriterien für einen Auftrag erfüllt, generiert das System einen Auftrag mit einer eindeutigen ID.
2. Lerninhalte
Aufgaben in diesem Lab
- KI-Chatbot mit Vertex AI erstellen
- Audio senden und eine Texttranskription vom Speech-to-Text API-Dienst empfangen
- KI-Agent in Cloud Run bereitstellen
In diesem Codelab geht es um GenAI-Agent-Apps mit Gemini. Auf irrelevante Konzepte wird nicht genauer eingegangen und entsprechende Codeblöcke können Sie einfach kopieren und einfügen.
Voraussetzungen
- Google Cloud-Konto
- Kenntnisse in Python, JavaScript und Google Cloud
Architektur
Dieser Agent dient der vereinfachten Bestellung mithilfe der multimodalen Funktionen von Gemini mit Bild- und Textprompts. Wenn die Bestellung gesprochen wird, wird sie vom Chirp 2-Modell von Google Speech in Text umgewandelt. Dieser Text wird dann zusammen mit einem bereitgestellten Bild verwendet, um das Gemini-Modell von Vertex AI abzufragen.
Wir erstellen Folgendes :
- Entwicklungsumgebung erstellen
- Flask-App, die von Nutzern über Mobilgeräte oder Computer aufgerufen werden kann. Die App wird in Cloud Run ausgeführt.
3. Einrichtung und Anforderungen
Einrichten der Umgebung im eigenen Tempo
- Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie ein Konto erstellen.
- Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es ist ein Zeichenstring, der von Google APIs nicht verwendet wird. Sie können ihn jederzeit aktualisieren.
- Die Projekt-ID ist für alle Google Cloud-Projekte eindeutig und kann nach der Festlegung nicht mehr geändert werden. In der Cloud Console wird automatisch ein eindeutiger String generiert. In der Regel spielt es keine Rolle, wie er lautet. In den meisten Codelabs müssen Sie auf Ihre Projekt-ID verweisen (in der Regel als PROJECT_ID bezeichnet). Wenn Ihnen die generierte ID nicht gefällt, können Sie eine andere zufällige ID generieren. Alternativ können Sie Ihr eigenes Konto ausprobieren und prüfen, ob es verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts bestehen.
- Zur Information: Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten finden Sie in der Dokumentation.
- Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Cloud-Ressourcen/-APIs verwenden zu können. Die Durchführung dieses Codelabs ist kostenlos oder kostet nur sehr wenig. Wenn Sie die Ressourcen deaktivieren möchten, um Kosten nach Abschluss dieser Anleitung zu vermeiden, können Sie die von Ihnen erstellten Ressourcen oder das Projekt löschen. Neuen Google Cloud-Nutzern steht das kostenlose Testprogramm mit einem Guthaben von 300$ zur Verfügung.
Cloud Shell starten
Sie können Google Cloud zwar per Fernzugriff von Ihrem Laptop aus nutzen, in diesem Codelab verwenden Sie jedoch Google Cloud Shell, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.
Klicken Sie in der Google Cloud Console rechts oben in der Symbolleiste auf das Cloud Shell-Symbol:
Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Minuten dauern. Wenn der Vorgang abgeschlossen ist, sollte in etwa Folgendes angezeigt werden:
Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft auf Google Cloud. Dadurch werden Netzwerkleistung und Authentifizierung erheblich verbessert. Alle Aufgaben in diesem Codelab können in einem Browser ausgeführt werden. Sie müssen nichts installieren.
4. Hinweis
APIs aktivieren
Aktivieren Sie die für das Lab erforderlichen APIs. Dies dauert ein paar Minuten.
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
Erwartete Console-Ausgabe :
Operation "operations/acf.p2-639929424533-ffa3a09b-7663-4b31-8f78-5872bf4ad778" finished successfully.
Umgebungen einrichten
Bevor Sie den Befehl für die Befehlszeile ausführen, müssen Sie die Parameter für Google Cloud-Umgebungen einrichten.
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. Infrastruktur aufbauen
Netzwerk für Ihre App erstellen
Erstellen Sie eine VPC für Ihre App. Führen Sie zum Erstellen der VPC mit dem Namen „demonetwork“ Folgendes aus :
gcloud compute networks create demonetwork \ --subnet-mode custom
Führen Sie zum Erstellen des Subnetzes „genai-subnet“ mit dem Adressbereich 10.10.0.0/24 im Netzwerk „demonetwork“ Folgendes aus:
gcloud compute networks subnets create genai-subnet \ --network demonetwork \ --region us-central1 \ --range 10.10.0.0/24
Cloud SQL for PostgreSQL erstellen
Zugewiesene IP-Adressbereiche für den Zugriff auf private Dienste.
gcloud compute addresses create google-managed-services-my-network \ --global \ --purpose=VPC_PEERING \ --prefix-length=16 \ --description="peering range for Google" \ --network=demonetwork
Erstellen Sie eine private Verbindung.
gcloud services vpc-peerings connect \ --service=servicenetworking.googleapis.com \ --ranges=google-managed-services-my-network \ --network=demonetwork
Führen Sie den Befehl gcloud sql instances create aus, um eine Cloud SQL-Instanz zu erstellen.
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
Die Verarbeitung dieses Befehls kann einige Minuten dauern.
Erwartete Console-Ausgabe :
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
Datenbank für Ihre App und den Nutzer erstellen
Führen Sie den Befehl gcloud sql databases create aus, um eine Cloud SQL-Datenbank in „sql-retail-genai“ zu erstellen.
gcloud sql databases create retail-orders \ --instance sql-retail-genai
Erstellen Sie einen PostgreSQL-Datenbanknutzer. Ändern Sie das Passwort.
gcloud sql users create aiagent --instance sql-retail-genai --password "genaiaigent2@"
Bucket zum Speichern von Bildern erstellen
Privaten Bucket für Ihren Kundenservicemitarbeiter erstellen
gsutil mb -l $REGION gs://$GENAI_BUCKET
Bucket-Berechtigungen aktualisieren
gsutil iam ch serviceAccount:<your service account>: roles/storage.objectUser gs://$GENAI_BUCKET
Wenn Sie davon ausgehen, dass Sie Ihr Standard-Compute-Dienstkonto verwenden :
gsutil iam ch serviceAccount:$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")-compute@developer.gserviceaccount.com:roles/storage.objectUser gs://$GENAI_BUCKET
6. Codes für Ihre App vorbereiten
Codes vorbereiten
Die Webanwendung zum Aufgeben von Bestellungen wurde mit Flask erstellt und kann in einem Webbrowser auf einem Mobilgerät oder PC ausgeführt werden. Dabei wird auf das Mikrofon und die Kamera des verbundenen Geräts zugegriffen und das Chirp 2-Modell von Google Speech und das Gemini Pro 1.5-Modell von Vertex AI verwendet. Bestellergebnisse werden in einer Cloud SQL-Datenbank gespeichert.
Wenn Sie die auf der vorherigen Seite genannten Beispielnamen für Umgebungsvariablen verwendet haben, können Sie den folgenden Code unverändert verwenden. Wenn Sie die Namen von Umgebungsvariablen angepasst haben, müssen Sie einige Variablenwerte im Code entsprechend ändern.
Erstellen Sie zwei Verzeichnisse.
mkdir -p genai-agent/templates
requirements.txt-Datei erstellen
vi ~/genai-agent/requirements.txt
Geben Sie eine Liste der Pakete in die Textdatei ein.
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 erstellen
vi ~/genai-agent/main.py
Geben Sie Python-Code in die Datei „main.py“ ein.
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 erstellen
vi ~/genai-agent/templates/index.html
Geben Sie den HTML-Code in die Datei „index.html“ ein.
<!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 erstellen
vi ~/genai-agent/templates/orderlist.html
Geben Sie den HTML-Code in die Datei „orderlist.html“ ein.
<!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-Anwendung in Cloud Run bereitstellen
Verwenden Sie im Verzeichnis „genai-agent“ den folgenden Befehl, um die App in Cloud Run bereitzustellen:
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
Erwartete Ausgabe :
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
Das kann einige Minuten dauern. Wenn der Vorgang erfolgreich abgeschlossen wurde, wird die Dienst-URL angezeigt.
Erwartete Ausgabe :
.......... 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
Sie können die Dienst-URL auch in der Cloud Run Console prüfen.
8. Test
- Geben Sie die Dienst-URL ein, die im vorherigen Schritt der Cloud Run-Bereitstellung generiert wurde.
- Nehmen Sie ein Foto für einen Artikel für Ihre Bestellung auf und geben Sie die Bestellmenge(Kartons) und den Namen des Einzelhändlers per Spracheingabe oder Tastatur ein. <ex> „Ich möchte diese drei Boxen bestellen. Oh nein, es sind sieben Boxen. Ich bin bei Walmart Mountain View.“
- Klicken Sie auf „Senden“ und prüfen Sie, ob Ihre Bestellung abgeschlossen wurde.
- Sie können den Bestellverlauf unter {Service URL}/orderlist einsehen.
9. Glückwunsch
Glückwunsch! Sie haben einen GenAI-Agenten erstellt, der Geschäftsprozesse mithilfe von Gemini und der Multimodalität von Vertex AI automatisieren kann.
Ich freue mich, dass Sie die Prompts ändern und den Bot an Ihre spezifischen Anforderungen anpassen können.