From 263c6fcd35b367bacca84f62046b95544e7afa84 Mon Sep 17 00:00:00 2001 From: Ani Date: Mon, 12 Aug 2024 10:50:16 -0500 Subject: [PATCH] feat: Add finance advisor spanner demo (#901) Co-authored-by: Owl Bot Co-authored-by: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Co-authored-by: Holt Skinner --- .github/CODEOWNERS | 1 + .github/actions/spelling/allow.txt | 29 +++ .github/workflows/linter.yaml | 1 + .../finance-advisor-spanner/Dockerfile | 10 + .../finance-advisor-spanner/README.md | 128 +++++++++++ .../Schema-Operations.sql | 136 ++++++++++++ .../finance-advisor-spanner/database.py | 210 ++++++++++++++++++ .../finance-advisor-spanner/graph_viz.py | 50 +++++ .../finance-advisor-spanner/home.py | 35 +++ .../pages/1_Asset_Search.py | 71 ++++++ .../pages/2_Semantic_Search.py | 45 ++++ .../pages/3_Graph_Visualization.py | 24 ++ .../pages/4_Exposure_Check.py | 48 ++++ .../finance-advisor-spanner/requirements.txt | 10 + 14 files changed, 798 insertions(+) create mode 100644 gemini/sample-apps/finance-advisor-spanner/Dockerfile create mode 100644 gemini/sample-apps/finance-advisor-spanner/README.md create mode 100644 gemini/sample-apps/finance-advisor-spanner/Schema-Operations.sql create mode 100644 gemini/sample-apps/finance-advisor-spanner/database.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/graph_viz.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/home.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/pages/1_Asset_Search.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/pages/2_Semantic_Search.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/pages/3_Graph_Visualization.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/pages/4_Exposure_Check.py create mode 100644 gemini/sample-apps/finance-advisor-spanner/requirements.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d4e328b424..ba4cd8610c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,6 +46,7 @@ /generative-ai/vision/getting-started @iamthuya @GoogleCloudPlatform/generative-ai-devrel /generative-ai/gemini/use-cases/intro_multimodal_use_cases.ipynb @saeedaghabozorgi @GoogleCloudPlatform/generative-ai-devrel /generative-ai/gemini/sample-apps/genwealth/ @paulramsey @GoogleCloudPlatform/generative-ai-devrel +/generative-ai/gemini/sample-apps/finance-advisor-spanner/ @anirbanbagchi1979 @GoogleCloudPlatform/generative-ai-devrel /generative-ai/gemini/use-cases/applying-llms-to-data/analyze-poster-images-in-bigquery/poster_image_analysis.ipynb @aliciawilliams @GoogleCloudPlatform/generative-ai-devrel /generative-ai/gemini/use-cases/retail/product_attributes_extraction.ipynb @tianli @GoogleCloudPlatform/generative-ai-devrel /generative-ai/gemini/use-cases/retrieval-augmented-generation/RAG_Based_on_Sensitive_Data_Protection_using_Faker.ipynb @ainaomotayo @GoogleCloudPlatform/generative-ai-devrel diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 189d72f163..61a5016c75 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -1,4 +1,5 @@ AFX +ANZ APIENTRY APSTUDIO Adidas @@ -7,6 +8,7 @@ Arborio Arepa Arsan Ashish +Aspeed Autechre Autorater BITCODE @@ -54,6 +56,7 @@ FLX FMWK FPDF FTPS +Finvest Firestore Fishburne Flatform @@ -69,6 +72,7 @@ Genkit Gisting Glickman Googlers +HDFC HIDPI HMO HREDRAW @@ -78,11 +82,13 @@ Hickson Hmmm Hogwarts Hubmann +ICICI INFOPLIST IVF Jang Jedi Joji +KNN Kaelen Kaggle Kamradt @@ -104,12 +110,17 @@ Lottry MSCHF MSGSEND MTL +Maarten +Mahindra Mamah +Mandiri Memegen Mewgler Mosher +NARI NCCREATE NDEBUG +NGRAMS NMT NOMINMAX NOZORDER @@ -123,7 +134,9 @@ Onone PDFs PLOTLYENV Parmar +Persero Phaidon +Pharma QPM Qwiklabs RAGAS @@ -132,7 +145,9 @@ ROOTSPAN RRF RTN RYDE +Resona Rizzoli +Robeco SDKROOT SEK SEO @@ -149,6 +164,7 @@ Schwimmer Selam Sestero Shazeer +Shenzhou Simpsons Siri Sketchfab @@ -157,11 +173,14 @@ Storrer Strappy Surampudi TARG +TOKENLIST TPU TPUs Tadao Tafel +Tbk Tbl +Tencent Testables Tianli Topolino @@ -170,6 +189,7 @@ Tribbiani Tricyle UDFs USERDATA +Unimicron Urs Uszkoreit VFT @@ -187,6 +207,7 @@ Wehn Wnd Womens XXE +Zijin Zscaler Zuercher aadd @@ -204,6 +225,7 @@ astype autoptr autosxs backticks +bagchi barmode barpolar baxis @@ -224,6 +246,7 @@ colab coloraxis colorbar colorway +colvis colwidth constexpr corpuses @@ -261,6 +284,7 @@ forno freedraw freopen fromarray +fts fulltext funtion gboolean @@ -312,6 +336,7 @@ iphoneos ipykernel ipynb isa +itables iterrows jegadesh jetbrains @@ -344,6 +369,7 @@ mec meme memes metadatas +mgrs miranda mpn nbconvert @@ -351,6 +377,8 @@ nbfmt nbformat ncols ndarray +ngram +ngrams nlp nmade nmilitary @@ -398,6 +426,7 @@ pymupdf pypdf pyplot pysftp +pyvis qubit qubits ragas diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 0b58037035..ae48fa4e2f 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -52,6 +52,7 @@ jobs: env: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FILTER_REGEX_EXCLUDE: 'gemini/sample-apps/finance-advisor-spanner/.*\.sql' JAVASCRIPT_DEFAULT_STYLE: prettier LOG_LEVEL: WARN SHELLCHECK_OPTS: -e SC1091 -e 2086 diff --git a/gemini/sample-apps/finance-advisor-spanner/Dockerfile b/gemini/sample-apps/finance-advisor-spanner/Dockerfile new file mode 100644 index 0000000000..73700b2da5 --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.12 + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["streamlit", "run", "home.py", "--server.enableCORS", "false", "--browser.serverAddress", "0.0.0.0", "--browser.gatherUsageStats", "false", "--server.port", "8080"] diff --git a/gemini/sample-apps/finance-advisor-spanner/README.md b/gemini/sample-apps/finance-advisor-spanner/README.md new file mode 100644 index 0000000000..a5fdd15f2b --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/README.md @@ -0,0 +1,128 @@ +# Finvest Spanner Demo App + +**Authors:** [Anirban Bagchi](https://github.com/anirbanbagchi1979) and [Derek Downey](https://github.com/dtest) + +Finvest Logo + +Consider a modern financial services company where I am a financial advisor. Finding the right financial investments can be challenging because of the complex nature of investments from structured data such as expense ratios, fund returns, to complex data such as asset holdings, their industry sectors, and more unstructured data, such as investment philosophy and client's investment goals. Let me show you how Spanner makes this process easy by combining these diverse data structures into a single multi-model platform. + +The client wants me to find assets for funds in North America and Europe that invest in derivatives. I select North America and Europe and put in derivatives as my search term. Spanner runs a relational and text search to return a list of funds. + +Next, the client wants to narrow this list to specific fund managers. I don't know the exact name, so I put in Liz Peters, and Spanner performs a fuzzy match(Full Text Search - Substring Match) of the name Liz Peters to find funds managed by Elizabeth Peterson. + +Among these funds, the client wants to choose from socially responsible funds. Next, I check the box for vector search, and now I can see ESG funds because Spanner performed a KNN vector search to match the search term "socially responsible" with "environmental, social and governance". + +Finally, before I recommend a fund, I also want to check the exposure to a particular sector. This can be complex because funds can invest in other funds, called fund of funds which makes it hard to compute this. Spanner performs a graph search using this asset knowledge graph. By traversing the funds and its holdings which could also be funds and their holdings, Spanner can compute the client's exposure to a particular sector. I can see the funds that have exposure of 20% or more in the technology sector. + +With the power of Spanner's multimodel support, I can run complex workloads on a single database for relational, analytical, text and vector use cases with virtually unlimited scale, five nines of availability—including enterprise security and governance for mission critical workloads. + +This demo highlights [Spanner](https://cloud.google.com/spanner), integration with [Vertex AI LLMs](https://cloud.google.com/model-garden?hl=en) for both embeddings and text completion models. You will learn how Spanner can help with use cases where you run Full Text Search, Approximate Nearest Neighbor search and vector similarity search. + +## Tech Stack + +The Finvest Spanner demo application was built using: + +- [Spanner](https://cloud.google.com/spanner) +- [Vertex AI](https://cloud.google.com/vertex-ai?hl=en) LLMs ([textembeddings-gecko@004](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings) ) +- [Cloud Run](https://cloud.google.com/run) +- [Dataflow](https://cloud.google.com/dataflow?) +- [Streamlit](https://streamlit.io/) + +## Deploying the Finvest Spanner Demo Application + +1. Login to the [Google Cloud Console](https://console.cloud.google.com/). + +2. [Create a new project](https://developers.google.com/maps/documentation/places/web-service/cloud-setup) to host the demo and isolate it from other resources in your account. + +3. [Switch](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects) to your new project. + +4. [Activate Cloud Shell](https://cloud.google.com/shell/docs/using-cloud-shell) and confirm your project by running the follow4ng commands. Click **Authorize** if prompted. + + ```bash + gcloud auth list + gcloud config list project + ``` + +5. Clone this repository and navigate to the project root: + + ```bash + cd + git clone https://github.com/GoogleCloudPlatform/generative-ai.git + cd generative-ai/gemini/sample-apps/finance-advisor-spanner/ + ``` + +6. Create a Spanner instance + + + > Note the instance Name + +7. Import the data into the Spanner instance + + The bucket which has the Spanner export is in this public GCS Bucket + + `https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/spanner-fts-mf-data-export/` + + > Note the Database Name + + The import process will run and import the database into a new Spanner database. + +8. Run Additional DDL statements for the database to have all the necessary components. + The DDL statements are in [Schema-Operations.sql](./Schema-Operations.sql) file in this directory. + + Change the endpoint as per your project and the spanner instance location + + ```sql + ALTER MODEL EmbeddingsModel SET OPTIONS ( + endpoint = '//aiplatform.googleapis.com/projects/'YOUR PROJECT ID HERE'/locations/'YOUR SPANNER INSTANCE LOCATION HERE'/publishers/google/models/text-embedding-003' + ) + ; + ``` + + Next run the rest of DDL statements without any change + +9. In Cloud Shell: + + Open `.env` file in the same directory using vi or other Editor + + Edit the following fields with the instance name from Step 6 and database name from Step 7 + + ```bash + instance_id='YOUR INSTANCE ID' + database_id='YOUR DATABASE ID' + ``` + +10. Now Build & Deploy the application: + + Build: + + ```bash + gcloud builds submit --tag gcr.io/'YOUR PROJECT ID HERE'/finance-advisor-app + ``` + + Deploy: + + ```bash + gcloud run deploy finance-advisor-app --image gcr.io/'YOUR PROJECT ID HERE'/finance-advisor-app --platform managed --region 'YOUR SPANNER REGION' --allow-unauthenticated + ``` + +### Troubleshooting + +### Frontend + +The frontend application is Streamlit running on CloudRun + +## Purpose and Extensibility + +The purpose of this repository is to help you provision an isolated demo environment that highlights the Full Text Search, Semantic Search and Graph capabilities of Spanner. While the ideas in this repository can be extended for many real-world use cases, the demo code itself is overly permissive and has not been hardened for security or reliability. The sample code in this repository is provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and it should NOT be used for production use cases without doing your own testing and security hardening. + +## Clean Up + +Be sure to delete the resources you no longer need when you're done with the demo. If you created a new project for the lab as recommended, you can delete the whole project using the command below in your Cloud Shell session (NOT the pgadmin VM). + +**DANGER: Be sure to set PROJECT_ID to the correct project, and run this command ONLY if you are SURE there is nothing in the project that you might still need. This command will permanently destroy everything in the project.** + +```bash +# Set your project id +PROJECT_ID='YOUR PROJECT ID HERE' +gcloud projects delete ${PROJECT_ID} +``` diff --git a/gemini/sample-apps/finance-advisor-spanner/Schema-Operations.sql b/gemini/sample-apps/finance-advisor-spanner/Schema-Operations.sql new file mode 100644 index 0000000000..4c51aec249 --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/Schema-Operations.sql @@ -0,0 +1,136 @@ + +ALTER MODEL EmbeddingsModel SET OPTIONS ( +endpoint = '//aiplatform.googleapis.com/projects//locations//publishers/google/models/text-embedding-003' +) +; +ALTER TABLE EU_MutualFunds ADD COLUMN fund_name_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(fund_name)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN category_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(category)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN investment_strategy_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(investment_strategy)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN investment_managers_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(investment_managers)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN fund_benchmark_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(fund_benchmark)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN morningstar_benchmark_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(morningstar_benchmark)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN top5_regions_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(top5_regions)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN top5_holdings_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(top5_holdings)) HIDDEN; +ALTER TABLE EU_MutualFunds ADD COLUMN investment_managers_Substring_Tokens TOKENLIST AS (TOKENIZE_SUBSTRING(investment_managers)) HIDDEN; +ALTER TABLE + EU_MutualFunds ADD COLUMN investment_managers_Substring_Tokens_NGRAM TOKENLIST AS ( TOKENIZE_SUBSTRING(investment_managers, + ngram_size_min=>2, + ngram_size_max=>3, + relative_search_types=>["word_prefix", + "word_suffix"])) HIDDEN; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1958 ; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2008; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2004; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1989; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2002; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2014; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2019; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2005; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1988; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2006; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1987; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2007; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1992; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1974; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2011; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1996; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2018; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1941; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1972; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1993; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2013; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1991; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2010; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1997; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2001; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2015; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1934; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1985; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1990; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2017; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1998; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1999; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2012; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1984; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1995; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2009; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2003; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1994; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1973; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1981; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2016; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2020; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 2000; +UPDATE EU_MutualFunds SET investment_strategy_Embedding_vector = investment_strategy_Embedding WHERE investment_strategy_Embedding is not NULL and EXTRACT(YEAR from inception_date) = 1992; +CREATE SEARCH INDEX + category_Tokens_IDX +ON + EU_MutualFunds(category_Tokens); +CREATE SEARCH INDEX + fund_benchmark_Tokens_IDX +ON + EU_MutualFunds(fund_benchmark_Tokens); +CREATE SEARCH INDEX + fund_name_Tokens_IDX +ON + EU_MutualFunds(fund_name_Tokens); +CREATE SEARCH INDEX + investment_managers_Tokens_IDX +ON + EU_MutualFunds(investment_managers_Tokens); +CREATE SEARCH INDEX + investment_strategy_Tokens_IDX +ON + EU_MutualFunds(investment_strategy_Tokens); +CREATE SEARCH INDEX + morningstar_benchmark_Tokens_IDX +ON + EU_MutualFunds(morningstar_benchmark_Tokens); +CREATE SEARCH INDEX + top5_holdings_Tokens_IDX +ON + EU_MutualFunds(top5_holdings_Tokens); +CREATE SEARCH INDEX + top5_regions_Tokens_IDX +ON + EU_MutualFunds(top5_regions_Tokens); +CREATE SEARCH INDEX + investment_managers_Substring_Tokens_IDX +ON + EU_MutualFunds(investment_managers_Substring_Tokens); +CREATE SEARCH INDEX + investment_managers_Substring_investment_Strategy_Tokens_Combo_IDX +ON + EU_MutualFunds(investment_managers_Substring_Tokens, + investment_strategy_Tokens); +CREATE SEARCH INDEX + investment_managers_Substring_NgRAM_investment_Strategy_Tokens_Combo_IDX +ON + EU_MutualFunds(investment_strategy_Tokens, + investment_managers_Substring_Tokens_NGRAM); +CREATE VECTOR INDEX + InvestmentStrategyEmbeddingIndex +ON + EU_MutualFunds(investment_strategy_Embedding_vector) +WHERE + investment_strategy_Embedding_vector IS NOT NULL OPTIONS ( tree_depth = 2, + num_leaves = 40, + distance_type = 'EUCLIDEAN' ); +CREATE SEARCH INDEX + investment_managers_Substring_Tokens_with_vectors_NGRAM_IDX +ON + EU_MutualFunds(investment_managers_Substring_Tokens_NGRAM) STORING (investment_strategy_Embedding_vector); +CREATE OR REPLACE PROPERTY GRAPH FundGraph NODE TABLES( Companies AS Company DEFAULT LABEL PROPERTIES ALL COLUMNS, + EU_MutualFunds AS Fund DEFAULT LABEL PROPERTIES ALL COLUMNS EXCEPT (_Injected_SearchUid, + _Injected_VectorIndex_InvestmentStrategyEmbeddingIndex_FP8, + _Injected_VectorIndex_InvestmentStrategyEmbeddingIndex_LeafId), + Sectors AS Sector DEFAULT LABEL PROPERTIES ALL COLUMNS ) EDGE TABLES( FundHoldsCompany SOURCE KEY(NewMFSequence) + REFERENCES + Fund(NewMFSequence) DESTINATION KEY(CompanySeq) + REFERENCES + Company(CompanySeq) LABEL Holds PROPERTIES ALL COLUMNS, + CompanyBelongsSector SOURCE KEY(CompanySeq) + REFERENCES + Company(CompanySeq) DESTINATION KEY(SectorSeq) + REFERENCES + Sector(SectorSeq) LABEL Belongs_To PROPERTIES ALL COLUMNS ); diff --git a/gemini/sample-apps/finance-advisor-spanner/database.py b/gemini/sample-apps/finance-advisor-spanner/database.py new file mode 100644 index 0000000000..da7a3f24aa --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/database.py @@ -0,0 +1,210 @@ +"""This file is for database operations done by the application """ + +# pylint: disable=line-too-long +import os + +from dotenv import load_dotenv +from google.api_core.client_options import ClientOptions +from google.cloud import spanner +import pandas as pd +import streamlit as st +from streamlit_extras.stylable_container import stylable_container + +load_dotenv() + +instance_id = os.getenv("instance_id") +database_id = os.getenv("database_id") +api_endpoint = os.getenv("api_endpoint") + +options = ClientOptions(api_endpoint=api_endpoint) +spanner_client = spanner.Client(client_options=options) + +instance = spanner_client.instance(instance_id) +database = instance.database(database_id) + + +def spanner_read_data(query: str, *vector_input: list) -> pd.DataFrame: + """This function helps read data from Spanner""" + with database.snapshot() as snapshot: + if len(vector_input) != 0: + results = snapshot.execute_sql( + query, + params={"vector": vector_input[0]}, + ) + else: + results = snapshot.execute_sql(query) + rows = list(results) + cols = [x.name for x in results.fields] + return pd.DataFrame(rows, columns=cols) + + +def fts_query(query_params: list) -> dict: + """This function runs Full Text Search Query""" + if query_params[1] == "": + fts_query_str = ( + "SELECT DISTINCT fund_name,investment_strategy,investment_managers,fund_trailing_return_ytd,top5_holdings FROM EU_MutualFunds WHERE SEARCH(investment_strategy_Tokens, '" + + query_params[0] + + "') order by fund_name;" + ) + else: + fts_query_str = ( + "SELECT DISTINCT fund_name, manager, strategy, score FROM (SELECT fund_name , investment_managers AS manager, investment_strategy as strategy, SCORE_NGRAMS(investment_managers_Substring_Tokens_NGRAM, '" + + query_params[1] + + "') AS score FROM EU_MutualFunds WHERE SEARCH_NGRAMS(investment_managers_Substring_Tokens_NGRAM, '" + + query_params[1] + + "', min_ngrams=>1) AND SEARCH(investment_strategy_Tokens, '" + + query_params[0] + + "') ) ORDER BY score DESC;" + ) + + return_vals = {} + return_vals["query"] = fts_query_str + df = spanner_read_data(fts_query_str) + + return_vals["data"] = df + return return_vals + + +def semantic_query(query_params: list) -> dict: + """This function runs Semantic Text Search Query""" + if query_params[1].strip() != "": + semantic_query_string = ( + "SELECT fund_name, investment_strategy,investment_managers, COSINE_DISTANCE( investment_strategy_Embedding, (SELECT embeddings. VALUES FROM ML.PREDICT( MODEL EmbeddingsModel, (SELECT '" + + query_params[0] + + "' AS content) ) ) ) AS distance FROM EU_MutualFunds WHERE investment_strategy_Embedding is not NULL AND search_substring(investment_managers_substring_tokens, '" + + query_params[1] + + "')ORDER BY distance LIMIT 10;" + ) + else: + semantic_query_string = ( + "SELECT fund_name, investment_strategy,investment_managers, COSINE_DISTANCE( investment_strategy_Embedding, (SELECT embeddings. VALUES FROM ML.PREDICT( MODEL EmbeddingsModel, (SELECT '" + + query_params[0] + + "' AS content) ) ) ) AS distance FROM EU_MutualFunds WHERE investment_strategy_Embedding is not NULL ORDER BY distance LIMIT 10;" + ) + return_vals = {} + return_vals["query"] = semantic_query_string + df = spanner_read_data(semantic_query_string) + + return_vals["data"] = df + return return_vals + + +def semantic_query_ann(query_params: list) -> dict: + """This function runs Semantic Text Search ANN Query""" + + embedding_query = ( + 'SELECT embeddings. VALUES as vector FROM ML.PREDICT( MODEL EmbeddingsModel, (SELECT "' + + query_params[0] + + '" AS content) ) ;' + ) + vector_input = spanner_read_data(embedding_query).values.tolist() + + if query_params[1].strip() != "": + ann_query = ( + "SELECT funds.fund_name, funds.investment_strategy, funds.investment_managers FROM (SELECT NewMFSequence, APPROX_EUCLIDEAN_DISTANCE(investment_strategy_Embedding_vector, @vector, options => JSON '{\"num_leaves_to_search\": 10}') AS distance FROM EU_MutualFunds @{force_index = InvestmentStrategyEmbeddingIndex} WHERE investment_strategy_Embedding_vector IS NOT NULL ORDER BY distance LIMIT 500 ) AS ann JOIN EU_MutualFunds AS funds ON ann.NewMFSequence = funds.NewMFSequence WHERE SEARCH_NGRAMS(funds.investment_managers_Substring_Tokens_NGRAM, '" + + query_params[1] + + "',min_ngrams=>1) ORDER BY SCORE_NGRAMS(funds.investment_managers_Substring_Tokens_NGRAM, '" + + query_params[1] + + "') desc;" + ) + else: + ann_query = "SELECT fund_name, investment_strategy, investment_managers, APPROX_EUCLIDEAN_DISTANCE(investment_strategy_Embedding_vector, @vector, options => JSON '{\"num_leaves_to_search\": 10}') AS distance FROM EU_MutualFunds @{force_index = InvestmentStrategyEmbeddingIndex} WHERE investment_strategy_Embedding_vector IS NOT NULL ORDER BY distance LIMIT 100;" + results_df = spanner_read_data(ann_query, vector_input[0][0]) + results_df = spanner_read_data(ann_query, vector_input[0][0]) + results_df = spanner_read_data(ann_query, vector_input[0][0]) + results_df = spanner_read_data(ann_query, vector_input[0][0]) + + return_vals = {} + return_vals["query"] = ann_query + return_vals["data"] = results_df + return return_vals + + +def like_query(query_params: list) -> dict: + """This function runs Precise Text Search Query""" + + if query_params[1] == "EXCLUDE": + query_params[1] = "AND" + precise_query = ( + " SELECT DISTINCT fund_name, investment_managers, investment_strategy FROM EU_MutualFunds WHERE investment_managers LIKE ('%" + + query_params[3] + + "%') AND ( investment_strategy LIKE ('%" + + query_params[0] + + "%') " + + query_params[1] + + " investment_strategy LIKE ('%" + + query_params[2] + + "%') ) ORDER BY fund_name;" + ) + return_vals = {} + return_vals["query"] = precise_query + df = spanner_read_data(precise_query) + + return_vals["data"] = df + return return_vals + + +def compliance_query(query_params: list) -> dict: + """This function runs Compliance Graph Search Query""" + graph_compliance_query = ( + "GRAPH FundGraph MATCH (sector:Sector {sector_name: '" + + query_params[0] + + "'})<-[:BELONGS_TO]-(company:Company)<-[h:HOLDS]-(fund:Fund) RETURN fund.fund_name, SUM(h.percentage) AS totalHoldings GROUP BY fund.fund_name NEXT FILTER totalHoldings > " + + query_params[1] + + " RETURN fund_name, totalHoldings" + ) + + return_vals = {} + return_vals["query"] = graph_compliance_query + df = spanner_read_data(graph_compliance_query) + return_vals["data"] = df + return return_vals + + +def graph_dtls_query() -> dict: + """This function runs Graph Details Query""" + company_query = "select CompanySeq,name from Companies;" + + return_vals = {} + df_companies = spanner_read_data(company_query) + return_vals["Companies"] = df_companies + + sector_query = "select * from Sectors;" + df_sectors = spanner_read_data(sector_query) + return_vals["Sectors"] = df_sectors + + managers_query = "select * from Managers LIMIT 100;" + df_managers = spanner_read_data(managers_query) + return_vals["Managers"] = df_managers + + company_belong_sector_query = "SELECT * from CompanyBelongsSector;" + df_comp_sec_edge = spanner_read_data(company_belong_sector_query) + return_vals["CompanySectorRelation"] = df_comp_sec_edge + + mgr_fund_edge_query = " SELECT mgrs.NewMFSequence,fund_name,ManagerSeq from ManagerManagesFund mgrs JOIN EU_MutualFunds funds ON mgrs.NewMFSequence = funds.NewMFSequence where ManagerSeq in (select ManagerSeq from Managers LIMIT 100);" + mgr_fund_edge = spanner_read_data(mgr_fund_edge_query) + return_vals["ManagerFundRelation"] = mgr_fund_edge + + funds_node_query = "select fund_name, NewMFSequence from EU_MutualFunds where NewMFSequence in (SELECT NewMFSequence FROM FundHoldsCompany);" + funds_node = spanner_read_data(funds_node_query) + return_vals["Funds"] = funds_node + + funds_hold_company_edge_query = "SELECT * FROM FundHoldsCompany;" + funds_hold_company_edge = spanner_read_data(funds_hold_company_edge_query) + return_vals["FundsHoldsCompaniesRelation"] = funds_hold_company_edge + + return return_vals + + +def display_spanner_query(spanner_query: str) -> None: + """This function runs Graph Details Query""" + with st.expander("Spanner Query"): + with stylable_container( + "codeblock", + """ + code { + white-space: pre-wrap !important; + } + """, + ): + st.code(spanner_query, language="sql", line_numbers=False) diff --git a/gemini/sample-apps/finance-advisor-spanner/graph_viz.py b/gemini/sample-apps/finance-advisor-spanner/graph_viz.py new file mode 100644 index 0000000000..153e99ac63 --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/graph_viz.py @@ -0,0 +1,50 @@ +"""This module is the page for Graph Viz Data Search feature""" + +# pylint: disable=import-error, line-too-long, unused-variable + +from database import graph_dtls_query +from pyvis.network import Network + + +def generate_graph() -> None: + """This function is for generating the Graph Visualization""" + + graph = Network("900px", "900px", notebook=True, heading="") + return_vals = graph_dtls_query() + companies = return_vals.get("Companies") + for index, row in companies.iterrows(): # type: ignore[union-attr] # might ignore other potential errors + graph.add_node( + str(row["CompanySeq"]), + label=row["name"], + title=row["name"], + shape="triangle", + ) + + sectors = return_vals.get("Sectors") + for index, row in sectors.iterrows(): # type: ignore[union-attr] # might ignore other potential errors + graph.add_node( + str(row["SectorSeq"]), + label=row["sector_name"], + shape="square", + color="red", + title=row["sector_name"], + ) + + funds = return_vals.get("Funds") + for index, row in funds.iterrows(): # type: ignore[union-attr] # might ignore other potential errors + graph.add_node( + str(row["NewMFSequence"]), + label=row["fund_name"], + color="green", + title=row["fund_name"], + ) + + comp_sector_relation = return_vals.get("CompanySectorRelation") + for index, row in comp_sector_relation.iterrows(): # type: ignore[union-attr] # might ignore other potential errors + graph.add_edge(str(row["CompanySeq"]), str(row["SectorSeq"]), title="BELONGS") + + fund_hold_company_relation = return_vals.get("FundsHoldsCompaniesRelation") + for index, row in fund_hold_company_relation.iterrows(): # type: ignore[union-attr] # might ignore other potential errors + graph.add_edge(str(row["NewMFSequence"]), str(row["CompanySeq"]), title="HOLDS") + + graph.show("graph_viz.html") diff --git a/gemini/sample-apps/finance-advisor-spanner/home.py b/gemini/sample-apps/finance-advisor-spanner/home.py new file mode 100644 index 0000000000..ece994341a --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/home.py @@ -0,0 +1,35 @@ +"""This file is the home Page of the Python Streamlit app""" + +# pylint: disable= import-error,line-too-long + +import streamlit as st + +st.set_page_config( + layout="wide", + page_title="FinVest Advisor", + page_icon="https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/small-logo.png", + initial_sidebar_state="expanded", +) + + +st.logo( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/investments.png" +) + +st.header("Welcome") +st.image( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/Finvest-white-removebg-preview.png" +) + + +def table_columns_layout_setup() -> dict: + """This function implements common layouts across the pages""" + st.columns([0.25, 0.25, 0.20, 0.10]) + classes = ["display", "compact", "cell-border", "stripe"] + buttons = ["pageLength", "csvHtml5", "excelHtml5", "colvis"] + style = "table-layout:auto;width:auto;margin:auto;caption-side:bottom" + it_args = {"classes": classes, "style": style} + + if buttons: + it_args["buttons"] = buttons + return it_args diff --git a/gemini/sample-apps/finance-advisor-spanner/pages/1_Asset_Search.py b/gemini/sample-apps/finance-advisor-spanner/pages/1_Asset_Search.py new file mode 100644 index 0000000000..4f9d940ffe --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/pages/1_Asset_Search.py @@ -0,0 +1,71 @@ +"""This module is the page for Asset Search feature""" + +# pylint: disable=line-too-long,import-error,invalid-name + +from database import display_spanner_query, fts_query, like_query +from home import table_columns_layout_setup +from itables.streamlit import interactive_table +import streamlit as st + +st.logo( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/investments.png" +) + + +def asset_search_common(query_parameters: list, query_type: str) -> None: + """This function implements Asset search common functions""" + + st.header("FinVest Fund Advisor") + st.subheader("Asset Search") + + with st.spinner("Querying Spanner..."): + if query_type == "PRECISE": + return_vals = like_query(query_parameters) + else: + return_vals = fts_query(query_parameters) + spanner_query = return_vals.get("query") + data = return_vals.get("data") + display_spanner_query(str(spanner_query)) + + interactive_table(data, caption="", **table_columns_layout_setup()) + + +with st.sidebar: + with st.form("Asset Search"): + st.subheader("Search Criteria") + precise_vs_text = st.radio("", ["Full-Text", "Precise"], horizontal=True) + precise_search = False + with st.expander("Asset Strategy", expanded=True): + investment_strategy_pt1 = st.text_input("", value="Europe") + and_or_exclude = st.radio("", ["AND", "OR", "EXCLUDE"], horizontal=True) + investment_strategy_pt2 = st.text_input("", value="Asia") + investment_manager = st.text_input("Investment Manager", value="James") + investment_strategy = "" + if precise_vs_text == "Full-Text": + if and_or_exclude == "EXCLUDE": + investment_strategy = ( + investment_strategy_pt1 + " -" + investment_strategy_pt2 + ) + else: + investment_strategy = ( + investment_strategy_pt1 + + " " + + and_or_exclude + + " " + + investment_strategy_pt2 + ) + else: + precise_search = True + asset_search_submitted = st.form_submit_button("Submit") +if asset_search_submitted: + if precise_search: + query_params = [ + investment_strategy_pt1.strip(), + and_or_exclude, + investment_strategy_pt2.strip(), + investment_manager.strip(), + ] + asset_search_common(query_params, "PRECISE") + else: + query_params = [investment_strategy, investment_manager] + asset_search_common(query_params, "FTS") diff --git a/gemini/sample-apps/finance-advisor-spanner/pages/2_Semantic_Search.py b/gemini/sample-apps/finance-advisor-spanner/pages/2_Semantic_Search.py new file mode 100644 index 0000000000..7e09e945e1 --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/pages/2_Semantic_Search.py @@ -0,0 +1,45 @@ +"""This module is the page for Semantic Search feature""" + +# pylint: disable=line-too-long,import-error,invalid-name + +from database import display_spanner_query, semantic_query, semantic_query_ann +from home import table_columns_layout_setup +from itables.streamlit import interactive_table +import streamlit as st + +st.logo( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/investments.png" +) + + +def asset_semantic_search() -> None: + """This function implements Semantic Search feature""" + + st.header("FinVest Fund Advisor") + st.subheader("Semantic Search") + query_params = [investment_strategy.strip(), investment_manager.strip()] + + with st.spinner("Querying Spanner..."): + if annVsKNN == "KNN": + semantic_return_vals = semantic_query(query_params) + else: + semantic_return_vals = semantic_query_ann(query_params) + semantic_queries = semantic_return_vals.get("query") + data = semantic_return_vals.get("data") + display_spanner_query(str(semantic_queries)) + + interactive_table(data, caption="", **table_columns_layout_setup()) + + +with st.sidebar: + with st.form("Asset Semantic Search"): + st.subheader("Search Criteria") + annVsKNN = st.radio("", ["ANN", "KNN"], horizontal=True) + investment_strategy = st.text_area( + "Search for me", + value="Invest in companies which also subscribe to my ideas around climate change, doing good for the planet", + ) + investment_manager = st.text_input("Investment Manager", value="Maarten") + asset_semantic_search_submitted = st.form_submit_button("Submit") +if asset_semantic_search_submitted: + asset_semantic_search() diff --git a/gemini/sample-apps/finance-advisor-spanner/pages/3_Graph_Visualization.py b/gemini/sample-apps/finance-advisor-spanner/pages/3_Graph_Visualization.py new file mode 100644 index 0000000000..a9b48f724b --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/pages/3_Graph_Visualization.py @@ -0,0 +1,24 @@ +"""This module is the page for Graph Visualization feature""" + +# pylint: disable=line-too-long,import-error,invalid-name + +import graph_viz +import streamlit as st +import streamlit.components.v1 as components + +st.subheader("Show me the Relationships between Funds ,Companies and Sectors") + +st.logo( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/investments.png" +) +graph_viz.generate_graph() + +with open("graph_viz.html", "r", encoding="utf-8") as html_file: + source_code = html_file.read() +components.html(source_code, height=950, width=900) + +with st.sidebar: + st.subheader("Legend") + st.image( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/Graph-legend.png" + ) diff --git a/gemini/sample-apps/finance-advisor-spanner/pages/4_Exposure_Check.py b/gemini/sample-apps/finance-advisor-spanner/pages/4_Exposure_Check.py new file mode 100644 index 0000000000..6479a4382f --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/pages/4_Exposure_Check.py @@ -0,0 +1,48 @@ +"""This module is the page for Exposure Check Search feature""" + +# pylint: disable=line-too-long,import-error,invalid-name + +from database import compliance_query, display_spanner_query +from home import table_columns_layout_setup +from itables.streamlit import interactive_table +import streamlit as st + +st.logo( + "https://storage.googleapis.com/github-repo/generative-ai/sample-apps/finance-advisor-spanner/images/investments.png" +) + + +def compliance_search() -> None: + """This function implements Compliance Check Graph feature""" + st.header("FinVest Fund Advisor") + st.subheader("Exposure Check") + + query_params = [] + query_params.append(sectorOption) + query_params.append(exposurePercentage) + with st.spinner("Querying Spanner..."): + compliance_vals = compliance_query(query_params) + compliance_queries = compliance_vals.get("query") + data = compliance_vals.get("data") + display_spanner_query(str(compliance_queries)) + + interactive_table(data, caption="", **table_columns_layout_setup()) + + +with st.sidebar: + with st.form("Compliance Search"): + st.subheader("Search Criteria") + sectorOption = st.selectbox( + "Which sector would you want to focus on?", + ("Technology", "Pharma", "Semiconductors"), + index=None, + placeholder="Select sector ...", + ) + exposurePercentage = st.select_slider( + "How much exposure to this sector would you prefer", + options=["10%", "20%", "30%", "40%", "50%", "60%", "70%"], + ) + exposurePercentage = exposurePercentage[:2] + compliance_search_submitted = st.form_submit_button("Submit") +if compliance_search_submitted: + compliance_search() diff --git a/gemini/sample-apps/finance-advisor-spanner/requirements.txt b/gemini/sample-apps/finance-advisor-spanner/requirements.txt new file mode 100644 index 0000000000..4e0ee04f3e --- /dev/null +++ b/gemini/sample-apps/finance-advisor-spanner/requirements.txt @@ -0,0 +1,10 @@ +streamlit +google-cloud-spanner +itables +streamlit-navigation-bar +streamlit-extras +streamlit-agraph +SPARQLWrapper +pyvis +python-dotenv +