search/web-app/main.py (295 lines of code) (raw):

# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Flask Web Server""" import base64 import os import re from urllib.parse import urlparse from consts import ( CUSTOM_UI_ENGINE_IDS, LOCATION, PROJECT_ID, SUMMARY_MODELS, VALID_LANGUAGES, WIDGET_CONFIGS, IMAGE_SEARCH_ENGINE_IDs, RECOMMENDATIONS_DATASTORE_IDs, ) from ekg_utils import search_public_kg from flask import Flask, render_template, request from google.api_core.exceptions import ResourceExhausted import requests from vais_utils import list_documents, recommend_personalize, search_enterprise_search from werkzeug.exceptions import HTTPException app = Flask(__name__) app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 # Set maximum upload size to 16MB FORM_OPTIONS = { "language_list": VALID_LANGUAGES, "default_language": VALID_LANGUAGES[0], } CUSTOM_UI_SEARCH_ENGINES = [d["name"] for d in CUSTOM_UI_ENGINE_IDS] NAV_LINKS = [ {"link": "/", "name": "Widgets", "icon": "widgets"}, { "link": "/search", "name": "Custom UI", "icon": "build", }, { "link": "/image-search", "name": "Image Search", "icon": "image", }, { "link": "/recommend", "name": "Recommendations", "icon": "recommend", }, {"link": "/ekg", "name": "Enterprise Knowledge Graph", "icon": "scatter_plot"}, { "link": "https://github.com/GoogleCloudPlatform/generative-ai/blob/main/search/retrieval-augmented-generation/examples/question_answering.ipynb", # noqa: E501 "name": "🦜️🔗 Retrieval Augmented Generation (RAG)", }, { "link": "https://github.com/GoogleCloudPlatform/generative-ai/tree/main/search/web-app", "name": "Source Code", "icon": "code", }, ] RECOMMENDATIONS_DOCUMENTS = list_documents( project_id=PROJECT_ID, location=LOCATION, datastore_id=RECOMMENDATIONS_DATASTORE_IDs[0]["datastore_id"], ) VALID_IMAGE_MIMETYPES = {"image/jpeg", "image/png", "image/bmp"} @app.route("/", methods=["GET"]) @app.route("/finance", methods=["GET"]) def index() -> str: """ Web Server, Homepage for Widgets """ return render_template( "index.html", title=NAV_LINKS[0]["name"], nav_links=NAV_LINKS, search_engine_options=WIDGET_CONFIGS, ) @app.route("/search", methods=["GET"]) def search() -> str: """ Web Server, Homepage for Search - Custom UI """ return render_template( "search.html", title=NAV_LINKS[1]["name"], nav_links=NAV_LINKS, search_engines=CUSTOM_UI_SEARCH_ENGINES, summary_models=SUMMARY_MODELS, ) @app.route("/search_vais", methods=["POST"]) def search_vais() -> str: """ Handle Search Vertex AI Search Request """ search_query = request.form.get("search_query", "") # Check if POST Request includes search query if not search_query: return render_template( "search.html", title=NAV_LINKS[1]["name"], nav_links=NAV_LINKS, search_engines=CUSTOM_UI_SEARCH_ENGINES, summary_models=SUMMARY_MODELS, message_error="No query provided", ) search_engine = request.form.get("search_engine", "") if not search_engine: return render_template( "search.html", title=NAV_LINKS[1]["name"], nav_links=NAV_LINKS, search_engines=CUSTOM_UI_SEARCH_ENGINES, summary_models=SUMMARY_MODELS, message_error="No search engine selected", ) summary_model = request.form.get("summary_model") summary_preamble = request.form.get("summary_preamble") results, summary, request_url, raw_request, raw_response = search_enterprise_search( project_id=PROJECT_ID, location=LOCATION, engine_id=CUSTOM_UI_ENGINE_IDS[int(search_engine)]["engine_id"], search_query=search_query, summary_model=summary_model, summary_preamble=summary_preamble, ) return render_template( "search.html", title=NAV_LINKS[1]["name"], nav_links=NAV_LINKS, search_engines=CUSTOM_UI_SEARCH_ENGINES, summary_models=SUMMARY_MODELS, message_success=search_query, results=results, summary=summary, request_url=request_url, raw_request=raw_request, raw_response=raw_response, ) @app.route("/image-search", methods=["GET"]) def image_search() -> str: """ Web Server, Homepage for Image Search - Custom UI """ return render_template( "image-search.html", title=NAV_LINKS[2]["name"], nav_links=NAV_LINKS, ) @app.route("/imagesearch_vais", methods=["POST"]) def imagesearch_vais() -> str: """ Handle Image Search Vertex AI Search Request """ search_query = request.form.get("search_query", "") image_file = request.files["image"] image_content = None image_bytes = None # Check if POST Request includes search query if not search_query and not image_file: return render_template( "image-search.html", nav_links=NAV_LINKS, message_error="No query provided", ) if image_file: image_content = image_file.read() elif search_query: # Check if text is a url image_url = urlparse(search_query) if all([image_url.scheme, image_url.netloc, image_url.path]): image_response = requests.get( image_url.geturl(), allow_redirects=True, timeout=5 ) mime_type = image_response.headers["Content-Type"] if mime_type not in VALID_IMAGE_MIMETYPES: return render_template( "image-search.html", nav_links=NAV_LINKS, message_error=f"Invalid image format - {mime_type}. Valid types {VALID_IMAGE_MIMETYPES}", ) image_content = image_response.content if image_content: search_query = None image_bytes = base64.b64encode(image_content) try: results, _, request_url, raw_request, raw_response = search_enterprise_search( project_id=PROJECT_ID, location=LOCATION, engine_id=IMAGE_SEARCH_ENGINE_IDs[0]["engine_id"], search_query=search_query, image_bytes=image_bytes, params={"search_type": 1}, ) except Exception as e: return render_template( "image-search.html", nav_links=NAV_LINKS, message_error=e.args[0], ) return render_template( "image-search.html", title=NAV_LINKS[2]["name"], nav_links=NAV_LINKS, message_success="Success", results=results, request_url=request_url, raw_request=raw_request, raw_response=raw_response, ) @app.route("/recommend", methods=["GET"]) def recommend() -> str: """ Web Server, Homepage for Recommendations - Custom UI """ return render_template( "recommend.html", nav_links=NAV_LINKS, title=NAV_LINKS[3]["name"], documents=RECOMMENDATIONS_DOCUMENTS, attribution_token="", ) @app.route("/recommend_vais", methods=["POST"]) def recommend_vais() -> str: """ Handle Recommend Vertex AI Search Request """ document_id = request.form.get("document_id", "") attribution_token = request.form.get("attribution_token", "") # Check if POST Request includes document id if not document_id: return render_template( "recommend.html", title=NAV_LINKS[3]["name"], nav_links=NAV_LINKS, documents=RECOMMENDATIONS_DOCUMENTS, attribution_token=attribution_token, message_error="No document provided", ) ( results, attribution_token, request_url, raw_request, raw_response, ) = recommend_personalize( project_id=PROJECT_ID, location=LOCATION, datastore_id=RECOMMENDATIONS_DATASTORE_IDs[0]["datastore_id"], serving_config_id=RECOMMENDATIONS_DATASTORE_IDs[0]["engine_id"], document_id=document_id, attribution_token=attribution_token, ) return render_template( "recommend.html", title=NAV_LINKS[3]["name"], nav_links=NAV_LINKS, documents=RECOMMENDATIONS_DOCUMENTS, message_success=document_id, results=results, attribution_token=attribution_token, request_url=request_url, raw_request=raw_request, raw_response=raw_response, ) @app.route("/ekg", methods=["GET"]) def ekg() -> str: """ Web Server, Homepage for EKG """ return render_template( "ekg.html", title=NAV_LINKS[4]["name"], nav_links=NAV_LINKS, form_options=FORM_OPTIONS, ) @app.route("/search_ekg", methods=["POST"]) def search_ekg() -> str: """ Handle Search EKG Request """ search_query = request.form.get("search_query", "") # Check if POST Request includes search query if not search_query: return render_template( "ekg.html", title=NAV_LINKS[4]["name"], nav_links=NAV_LINKS, form_options=FORM_OPTIONS, message_error="No query provided", ) languages = request.form.getlist("languages") form_types = request.form.get("types", "") types = re.split(r"[\s,]", form_types) if form_types else [] entities, request_url, raw_request, raw_response = search_public_kg( project_id=PROJECT_ID, location=LOCATION, search_query=search_query, languages=languages, types=types, ) return render_template( "ekg.html", title=NAV_LINKS[4]["name"], nav_links=NAV_LINKS, form_options=FORM_OPTIONS, message_success=search_query, entities=entities, request_url=request_url, raw_request=raw_request, raw_response=raw_response, ) @app.errorhandler(Exception) def handle_exception(ex: Exception): """ Handle Application Exceptions """ message_error = "An Unknown Error Occurred" # Pass through HTTP errors if isinstance(ex, HTTPException): message_error = ex.get_description() elif isinstance(ex, ResourceExhausted): message_error = ex.message else: message_error = str(ex) return render_template( "search.html", title=NAV_LINKS[1]["name"], form_options=FORM_OPTIONS, nav_links=NAV_LINKS, search_engines=CUSTOM_UI_SEARCH_ENGINES, summary_models=SUMMARY_MODELS, message_error=message_error, ) if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))