نماینده GenAI برای اتوماسیون سفارش فروش

1. بررسی اجمالی

آخرین به روز رسانی: 2024-10-18

نوشته شده توسط Sanggyu Lee (sanggyulee@google.com)

چیزی که خواهی ساخت

در این کد لبه، شما یک عامل GenAI برای مشتریان خرده‌فروش می‌سازید

برنامه شما:

  • روی موبایل یا دسکتاپ کار می کند.
  • می توانید برای یک مورد عکس بگیرید و با چت صوتی آن را سفارش دهید.
  • نحوه کار این برنامه به این صورت است: شما به سادگی از آیتم عکس می گیرید و به عنوان مثال می گویید: " من می خواهم این را سفارش دهم، 3 جعبه. من مدیر Walmart Honolulu هستم. " برنامه عکس را در Cloud Storage آپلود می کند و رونویسی می کند. ضبط صدای شما سپس این اطلاعات به یک مدل Gemini در Vertex AI ارسال می‌شود که کالا و فروشگاه شما (Walmart Honolulu) را شناسایی می‌کند. اگر درخواست با معیارهای سفارش فروش مطابقت داشته باشد، سیستم یک سفارش فروش با یک شناسه منحصر به فرد ایجاد می کند.

c8333d8139d8764c.png

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

چیزی که یاد خواهید گرفت

  • نحوه ایجاد یک عامل هوش مصنوعی با Vertex AI
  • نحوه ارسال صدا و دریافت رونویسی متن از سرویس Speech-to-Text API
  • نحوه استقرار عامل هوش مصنوعی در Cloud Run

این آزمایشگاه کد روی برنامه‌های عامل GenAI با Gemini متمرکز شده است. مفاهیم غیر مرتبط و بلوک‌های کد محو شده‌اند و برای شما ارائه می‌شوند تا به سادگی کپی و جای‌گذاری کنید.

آنچه شما نیاز دارید

  • حساب Google Cloud
  • آشنایی با Python، Javascript و Google Cloud

معماری

b21e2a3deedb60ec.png

این نماینده برای سفارش ساده با استفاده از ویژگی های چند وجهی Gemini با اعلان های تصویر و متن است. اگر دستور گفته شود، مدل Chirp 2 Google Speech آن را به متن رونویسی می‌کند، که سپس همراه با یک تصویر ارائه‌شده، برای جستجو در مدل Gemini Vertex AI استفاده می‌شود.

خواهیم ساخت:

  1. یک محیط توسعه ایجاد کنید
  2. برنامه Flask برای تماس توسط کاربران از طریق تلفن همراه یا رایانه شخصی. برنامه در Cloud Run اجرا خواهد شد.

3. راه اندازی و الزامات

تنظیم محیط خود به خود

  1. به Google Cloud Console وارد شوید و یک پروژه جدید ایجاد کنید یا از یک موجود استفاده مجدد کنید. اگر قبلاً یک حساب Gmail یا Google Workspace ندارید، باید یک حساب ایجاد کنید .

fbef9caa1602edd0.png

97bdebccea2ba4be.png

3e14a8a504bb53ce.png

  • نام پروژه نام نمایشی برای شرکت کنندگان این پروژه است. این یک رشته کاراکتری است که توسط API های Google استفاده نمی شود. همیشه می توانید آن را به روز کنید.
  • شناسه پروژه در تمام پروژه‌های Google Cloud منحصربه‌فرد است و تغییرناپذیر است (پس از تنظیم نمی‌توان آن را تغییر داد). Cloud Console به طور خودکار یک رشته منحصر به فرد تولید می کند. معمولاً برای شما مهم نیست که چیست. در اکثر کدها، باید شناسه پروژه خود را ارجاع دهید (معمولاً با نام PROJECT_ID شناخته می شود). اگر شناسه تولید شده را دوست ندارید، ممکن است یک شناسه تصادفی دیگر ایجاد کنید. از طرف دیگر، می‌توانید خودتان را امتحان کنید، و ببینید آیا در دسترس است یا خیر. پس از این مرحله نمی توان آن را تغییر داد و در طول مدت پروژه باقی می ماند.
  • برای اطلاع شما، مقدار سومی وجود دارد، Project Number که برخی از API ها از آن استفاده می کنند. در مورد هر سه این مقادیر در مستندات بیشتر بیاموزید.
  1. در مرحله بعد، برای استفاده از منابع Cloud/APIها باید صورتحساب را در کنسول Cloud فعال کنید . اجرا کردن از طریق این کد لبه هزینه زیادی نخواهد داشت. برای خاموش کردن منابع برای جلوگیری از تحمیل صورت‌حساب فراتر از این آموزش، می‌توانید منابعی را که ایجاد کرده‌اید حذف کنید یا پروژه را حذف کنید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان 300 دلاری هستند.

Cloud Shell را راه اندازی کنید

در حالی که Google Cloud را می توان از راه دور از لپ تاپ شما کار کرد، در این کد لبه از Google Cloud Shell استفاده خواهید کرد، یک محیط خط فرمان که در Cloud اجرا می شود.

از Google Cloud Console ، روی نماد Cloud Shell در نوار ابزار بالا سمت راست کلیک کنید:

55efc1aaa7a4d3ad.png

تهیه و اتصال به محیط فقط چند لحظه طول می کشد. وقتی تمام شد، باید چیزی شبیه به این را ببینید:

7ffe5cbb04455448.png

این ماشین مجازی با تمام ابزارهای توسعه که شما نیاز دارید بارگذاری شده است. این یک فهرست اصلی 5 گیگابایتی دائمی را ارائه می دهد و در Google Cloud اجرا می شود و عملکرد و احراز هویت شبکه را تا حد زیادی افزایش می دهد. تمام کارهای شما در این کد لبه را می توان در یک مرورگر انجام داد. شما نیازی به نصب چیزی ندارید.

4. قبل از شروع

API ها را فعال کنید

API های مورد نیاز برای آزمایشگاه را فعال کنید. این چند دقیقه طول می کشد.

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

خروجی کنسول مورد انتظار:

Operation "operations/acf.p2-639929424533-ffa3a09b-7663-4b31-8f78-5872bf4ad778" finished successfully.

محیط ها را تنظیم کنید

قبل از دستور CLI، پارامترهای Google Cloud Environments را تنظیم کنید.

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}

به عنوان مثال:

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. زیرساخت های خود را بسازید

شبکه را برای برنامه خود ایجاد کنید

یک VPC برای برنامه خود ایجاد کنید. برای ایجاد VPC با نام "demonetwork"، اجرا کنید:

gcloud compute networks create demonetwork \
    --subnet-mode custom

برای ایجاد زیرشبکه "genai-subnet" با محدوده آدرس 10.10.0.0/24 در شبکه "demonetwork"، اجرا کنید:

gcloud compute networks subnets create genai-subnet \
    --network demonetwork \
    --region us-central1 \
    --range 10.10.0.0/24

یک Cloud SQL برای PostgreSQL ایجاد کنید

محدوده آدرس IP اختصاص داده شده برای دسترسی به سرویس خصوصی.

gcloud compute addresses create google-managed-services-my-network \
    --global \
    --purpose=VPC_PEERING \
    --prefix-length=16 \
    --description="peering range for Google" \
    --network=demonetwork

یک اتصال خصوصی ایجاد کنید.

gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=google-managed-services-my-network \
    --network=demonetwork

دستور create gcloud sql instances را برای ایجاد یک نمونه Cloud SQL اجرا کنید.

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

تکمیل این دستور ممکن است چند دقیقه طول بکشد.

خروجی کنسول مورد انتظار:

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

یک پایگاه داده برای برنامه خود و کاربر ایجاد کنید

دستور ایجاد پایگاه داده های gcloud sql را برای ایجاد یک پایگاه داده Cloud SQL در sql-retail-genai اجرا کنید.

gcloud sql databases create retail-orders \
  --instance sql-retail-genai

یک کاربر پایگاه داده PostgreSQL ایجاد کنید، بهتر است رمز عبور را تغییر دهید.

gcloud sql users create aiagent --instance sql-retail-genai --password "genaiaigent2@"

یک سطل برای ذخیره تصاویر ایجاد کنید

یک سطل خصوصی برای نماینده خود ایجاد کنید

gsutil mb -l $REGION gs://$GENAI_BUCKET

مجوزهای Bucket را به روز کنید

gsutil iam ch serviceAccount:<your service account>: roles/storage.objectUser gs://$GENAI_BUCKET

اگر فرض کنید از حساب سرویس محاسباتی پیش‌فرض خود استفاده کنید:

gsutil iam ch serviceAccount:$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")-compute@developer.gserviceaccount.com:roles/storage.objectUser gs://$GENAI_BUCKET

6. کدهایی را برای اپلیکیشن خود آماده کنید

کدها را آماده کنید

برنامه وب برای ثبت سفارش با استفاده از Flask ساخته شده است و می تواند در یک مرورگر وب روی موبایل یا رایانه شخصی اجرا شود. به میکروفون و دوربین دستگاه متصل دسترسی پیدا می کند و از مدل Chirp 2 Google Speech و مدل Gemini Pro 1.5 Vertex AI استفاده می کند. نتایج سفارش در یک پایگاه داده Cloud SQL ذخیره می شود.

اگر از نام متغیرهای محیطی مثال ارائه شده در صفحه قبل استفاده کرده اید، می توانید بدون تغییر از کد زیر استفاده کنید. اگر نام متغیرهای محیطی را سفارشی کرده‌اید، باید مقداری از متغیرها را در کد تغییر دهید.

دو دایرکتوری به صورت زیر ایجاد کنید.

mkdir -p genai-agent/templates

یک requirement.txt ایجاد کنید

vi ~/genai-agent/requirements.txt

لیستی از بسته ها را در فایل متنی وارد کنید.

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 ایجاد کنید

vi ~/genai-agent/main.py

کد پایتون را در فایل main.py وارد کنید.

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 را ایجاد کنید

vi ~/genai-agent/templates/index.html

کد HTML را در فایل index.html وارد کنید.

<!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 ایجاد کنید

vi ~/genai-agent/templates/orderlist.html

کد HTML را در فایل orderlist.html وارد کنید.

<!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 را در Cloud Run اجرا کنید

از دایرکتوری genai-agent، از دستور زیر برای استقرار برنامه در Cloud Run استفاده کنید:

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

خروجی مورد انتظار:

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

این کار چند دقیقه طول می کشد و در صورت تکمیل موفقیت آمیز URL سرویس را مشاهده خواهید کرد.

خروجی مورد انتظار:

..........
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

همچنین می‌توانید url سرویس را در کنسول Cloud Run خود بررسی کنید.

8. تست کنید

  1. URL سرویس ایجاد شده در مرحله قبلی Cloud Run Deployment را در تلفن همراه یا لپ تاپ خود تایپ کنید.
  2. از یک کالا برای سفارش خود عکس بگیرید و مقدار سفارش (جعبه ها) و نام فروشگاه خرده فروشی را با تایپ یا صدا وارد کنید. <ex> "من می خواهم این سه جعبه را سفارش دهم. اوه نه ببخشید اوه هفت جعبه. این Walmart Mountain Vew است"
  3. روی "ارسال" کلیک کنید و بررسی کنید که آیا سفارش شما تکمیل شده است یا خیر.
  4. می‌توانید سابقه سفارش را در {Service URL}/orderlist بررسی کنید

de0db1a08082c634.png

9. تبریک می گویم

تبریک می گویم! شما یک نماینده GenAI ساخته‌اید که می‌تواند فرآیندهای تجاری را با استفاده از Gemini در چندوجهی Vertex AI خودکار کند.

خوشحالم که درخواست‌ها را اصلاح کنید و نماینده را مطابق با نیازهای خاص خود تنظیم کنید.