From d0f50886f75cd6286e6113d58960db00add75c2e Mon Sep 17 00:00:00 2001 From: "sachin.patil@databricks.com" Date: Wed, 17 Jan 2024 14:37:30 +0000 Subject: [PATCH] Updated version --- ...onfigure External models for Automation.py | 121 +++++++++++++ ...figure Foundation models for Automation.py | 110 ++++++++++++ ...a: Serve External models for Automation.py | 161 +++++++++++++++++ ... Serve Foundation models for Automation.py | 166 ++++++++++++++++++ EA 0: Introduction.py | 89 ++++++++++ ES 0b: Ingest Emails into Lakehouse.py | 42 +++++ _resources/00-setup.py | 50 ++++++ images/EmailAutomation.png | Bin 0 -> 48148 bytes 8 files changed, 739 insertions(+) create mode 100644 (Clone) ES 1a: Configure External models for Automation.py create mode 100644 (Clone) ES 1b: Configure Foundation models for Automation.py create mode 100644 (Clone) ES 2a: Serve External models for Automation.py create mode 100644 (Clone) ES 2b: Serve Foundation models for Automation.py create mode 100644 EA 0: Introduction.py create mode 100644 ES 0b: Ingest Emails into Lakehouse.py create mode 100644 _resources/00-setup.py create mode 100644 images/EmailAutomation.png diff --git a/(Clone) ES 1a: Configure External models for Automation.py b/(Clone) ES 1a: Configure External models for Automation.py new file mode 100644 index 0000000..01eab00 --- /dev/null +++ b/(Clone) ES 1a: Configure External models for Automation.py @@ -0,0 +1,121 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC # External Models for Email Response Automation + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Install MLflow with external models support + +# COMMAND ---------- + +# MAGIC %pip install mlflow[genai]>=2.9.0 +# MAGIC %pip install --upgrade mlflow +# MAGIC %pip install --upgrade langchain +# MAGIC dbutils.library.restartPython() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC In this notebook, we will use External Models for automation of the email response. External models are third-party models hosted outside of Databricks. Supported by Model Serving, external models allow you to streamline the usage and management of various large language model (LLM) providers, such as OpenAI and Anthropic, within an organization. For this specific problem, we have picked OpenAI. +# MAGIC +# MAGIC https://docs.databricks.com/en/generative-ai/external-models/index.html + +# COMMAND ---------- + +# MAGIC %run "./ES 0a: Intro & Config" + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ### Read the emails from the bronze layer + +# COMMAND ---------- + +# MAGIC %md +# MAGIC Lets read the raw emails persisted in the bronze layer in a Dataframe. +# MAGIC + +# COMMAND ---------- + +emails_silver=spark \ + .read \ + .table(config['table_emails_bronze']) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Create OpenAI Completion endpoint for our solution + +# COMMAND ---------- + +import mlflow.deployments + +client = mlflow.deployments.get_deploy_client("databricks") +client.create_endpoint( + name="Email-OpenAI-Completion-Endpoint", + config={ + "served_entities": [{ + "external_model": { + "name": "gpt-3.5-turbo-instruct", + "provider": "openai", + "task": "llm/v1/completions", + "openai_config": { + "openai_api_key": "{{secrets/email_openai_secret_scope/openai_api_key}}", + } + } + }] + } +) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Setup Langchain for the external model +# MAGIC +# MAGIC We will setup Langchain to define the prompt template that will retrieve email Catagory, Sentiment, Synopsis and possible reply. +# MAGIC +# MAGIC The possible reply can be based on the templated and embedding can be used for it. However in this solution, we can not defined it. + +# COMMAND ---------- + +import mlflow +from langchain.chains import LLMChain +from langchain.prompts import PromptTemplate +from langchain.llms import Databricks + +gateway = Databricks( + host="https://" + spark.conf.get("spark.databricks.workspaceUrl"), + endpoint_name="Email-OpenAI-Completion-Endpoint", + temperature=0.1, +) + +# Build Prompt Template +template = """ +Given the following email text, categorise whether the email is a job request, customer query or generic email where no action required. It should capture sentiment of the email as positive, negative or neutral. Also it should create a short summary of the email. In addition, it should draft possible reply to email. + +The output should be structured as a JSON dictionary of dictionaries. First attribute name is "Category" which categorises of the email as three possible values - Job, Query or No Action. Second json attribute name is Sentiment with possible values - positive, negative or neutral. Third json attribute name is "Synopsis" which should capture short email summary. Forth JSON attribute name "Reply" should be possibly email reply to the original email. + +Email summary begin here: {email_body}""" + +prompt = PromptTemplate(template=template, input_variables=["email_body"]) + +# Build LLM Chain +llm_chain = LLMChain(prompt=prompt, llm=gateway) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Test one of the emails with the OpenAI API + +# COMMAND ---------- + +# Retrieve a single review for testing +test_single_review = emails_silver.limit(1).select("email_body_clean").collect()[0][0] + +# print(test_single_review) +# Predict on the review +response_string = llm_chain.invoke(test_single_review) + +# Print string +print(response_string) diff --git a/(Clone) ES 1b: Configure Foundation models for Automation.py b/(Clone) ES 1b: Configure Foundation models for Automation.py new file mode 100644 index 0000000..e19a252 --- /dev/null +++ b/(Clone) ES 1b: Configure Foundation models for Automation.py @@ -0,0 +1,110 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC # Foundation Models for Email Response Automation + +# COMMAND ---------- + +# MAGIC %md +# MAGIC Databricks Model Serving supports any Foundation Model, be it a fully custom model, a Databricks-managed model, or a third-party Foundation Model. This flexibility allows you to choose the right model for the right job, keeping you ahead of future advances in the range of available models. +# MAGIC +# MAGIC You can get started with Foundation Model APIs on a pay-per-token basis, which significantly reduces operational costs. Alternatively, for workloads requiring fine-tuned models or performance guarantees, you can switch to Provisioned Throughput. +# MAGIC +# MAGIC Please refer Foundation Model API documentation for more details: +# MAGIC https://docs.databricks.com/en/machine-learning/foundation-models/index.html?_gl=1*licvcu*_gcl_au*ODA1MDc0NDEzLjE3MDMzMjEzNDk + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Install the SDK on a Databricks Notebook + +# COMMAND ---------- + +# MAGIC %pip install mlflow==2.9.0 langchain==0.0.344 databricks-sdk==0.12.0 + +# COMMAND ---------- + +!pip install databricks-genai-inference + +dbutils.library.restartPython() + +# COMMAND ---------- + +# MAGIC %run "./ES 0a: Intro & Config" + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ### Read the emails from the bronze layer + +# COMMAND ---------- + +# MAGIC %md +# MAGIC Lets read the raw emails persisted in the bronze layer in a Dataframe. +# MAGIC + +# COMMAND ---------- + +emails_silver=spark \ + .read \ + .table(config['table_emails_bronze']) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Create Mistral-8X7b-instruct Completion endpoint for our solution +# MAGIC +# MAGIC We analysed multiple foundation models and Mistral is providing better results for the email automation in our case. However any foundation model can be used based on your preference and uniqueness of your data + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Setup Langchain for the Foundation model +# MAGIC +# MAGIC We will setup Langchain to define the prompt template that will retrieve email Catagory, Sentiment, Synopsis and possible reply. +# MAGIC +# MAGIC The possible reply can be based on the templated and embedding can be used for it. However in this solution, we can not defined it. + +# COMMAND ---------- + +import mlflow +from langchain.chains import LLMChain +from langchain.prompts import PromptTemplate +from langchain.llms import Databricks +from langchain.chat_models import ChatDatabricks +from langchain.schema.output_parser import StrOutputParser + +prompt = PromptTemplate( + input_variables = ["question"], + template = "You are an assistant. Give a short answer to this question: {question}" +) +chat_model = ChatDatabricks(endpoint="databricks-mixtral-8x7b-instruct", max_tokens = 500) + +# Build Prompt Template +template = """ +[INST] <> +Given the following email text, categorise whether the email is a job request, customer query or generic email where no action required. It should capture sentiment of the email as positive, negative or neutral. Also it should create a short summary of the email. In addition, it should draft possible reply to email. the output of the questions should only be a JSON dictionary of dictionaries + +The output should be structured as a JSON dictionary of dictionaries. First attribute name is "Category" which categorises of the email as three possible values - Job, Query or No Action. Second json attribute name is Sentiment with possible values - positive, negative or neutral. Third json attribute name is "Synopsis" which should capture short email summary in 2-3 lines. Forth JSON attribute name "Reply" should be possibly email reply to the original email. +<> +Email summary begin here DO NOT give answer except a JSON and No other text : {email_body} [/INST] """ + +prompt = PromptTemplate(template=template, input_variables=["email_body"]) + +# Build LLM Chain +llm_chain = LLMChain(prompt=prompt, llm=chat_model) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Test one of the emails with the Mistral model API + +# COMMAND ---------- + +# Retrieve a single review for testing +test_single_review = emails_silver.limit(2).select("email_body_clean").collect()[0][0] + +# print(test_single_review) +# Predict on the review +response = llm_chain.run(test_single_review) + +print(f"response.text:{response}") diff --git a/(Clone) ES 2a: Serve External models for Automation.py b/(Clone) ES 2a: Serve External models for Automation.py new file mode 100644 index 0000000..b61439c --- /dev/null +++ b/(Clone) ES 2a: Serve External models for Automation.py @@ -0,0 +1,161 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC # External Models for Email Response Automation + +# COMMAND ---------- + +# MAGIC %pip install mlflow==2.9.0 langchain==0.0.344 databricks-sdk==0.12.0 + +# COMMAND ---------- + +# MAGIC %pip install mlflow[genai]>=2.9.0 +# MAGIC %pip install --upgrade mlflow +# MAGIC %pip install --upgrade langchain +# MAGIC dbutils.library.restartPython() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC In this notebook, we will use External Models for automation of the email response. External models are third-party models hosted outside of Databricks. Supported by Model Serving, external models allow you to streamline the usage and management of various large language model (LLM) providers, such as OpenAI and Anthropic, within an organization. For this specific problem, we have picked OpenAI. +# MAGIC +# MAGIC https://docs.databricks.com/en/generative-ai/external-models/index.html + +# COMMAND ---------- + +# MAGIC %run "./ES 0a: Intro & Config" + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ### Read the emails from the bronze layer + +# COMMAND ---------- + +# MAGIC %md +# MAGIC Lets read the raw emails persisted in the bronze layer in a Dataframe. +# MAGIC + +# COMMAND ---------- + +emails_silver=spark \ + .read \ + .table(config['table_emails_bronze']) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Setup Langchain for the external model +# MAGIC +# MAGIC We will setup Langchain to define the prompt template that will retrieve email Catagory, Sentiment, Synopsis and possible reply. +# MAGIC +# MAGIC The possible reply can be based on the templated and embedding can be used for it. However in this solution, we can not defined it. + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ###Create Email Summarisation UDF + +# COMMAND ---------- + +import mlflow +from langchain.chains import LLMChain +from langchain.prompts import PromptTemplate +from langchain.llms import Databricks +from langchain import OpenAI, PromptTemplate, LLMChain +from pyspark.sql import functions as SF +from pyspark.sql.types import StringType +from tenacity import retry, stop_after_attempt, wait_exponential +from langchain.chat_models import ChatDatabricks +from langchain.chat_models import ChatOpenAI +from langchain.schema.output_parser import StrOutputParser +from tenacity import retry, stop_after_attempt, wait_exponential +import os + +spark.conf.set("spark.sql.execution.arrow.maxRecordsPerBatch",2) +spark.conf.set("spark.sql.execution.arrow.pyspark.enabled", "true") +token = dbutils.notebook.entry_point.getDbutils().notebook().getContext().apiToken().get() +url = spark.conf.get("spark.databricks.workspaceUrl") + + + +# Create Summary function +def run_summarisation_pipeline(email_body): + + os.environ['DATABRICKS_TOKEN'] = token + + # Chat GPT LLM Model + gateway = Databricks( + host="https://" + url, + endpoint_name="Email-OpenAI-Completion-Endpoint" + ) + + # Build Prompt Template + prompt_template_string = """ + Given the following email text, categorise whether the email is a job request, customer query or generic email where no action required. It should capture sentiment of the email as positive, negative or neutral. Also it should create a short summary of the email. In addition, it should draft possible reply to email. + + The output should be structured as a JSON dictionary of dictionaries. First attribute name is "Category" which categorises of the email as three possible values - Job, Query or No Action. Second json attribute name is Sentiment with possible values - positive, negative or neutral. Third json attribute name is "Synopsis" which should capture short email summary. Forth JSON attribute name "Reply" should be possibly email reply to the original email. + + Email summary begin here: {email_body}""" + + prompt_template = PromptTemplate(template=prompt_template_string, input_variables=["email_body"]) + + # Build LLM Chain + chain = LLMChain(prompt=prompt_template, llm=gateway) + + @retry(wait=wait_exponential(multiplier=10, min=10, max=1800), stop=stop_after_attempt(7)) + def _call_with_retry(email_body): + return chain.invoke(email_body) + + try: + summary_string = _call_with_retry(email_body) + except Exception as e: + summary_string = f"FAILED: {e.last_attempt.exception()}" + + return summary_string + + +# Create UDF +summarisation_udf = SF.udf( + lambda x: run_summarisation_pipeline( + email_body=x + ), + StringType(), +) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ###Process incoming data + +# COMMAND ---------- + +# Repartition for concurrency +emails_silver = emails_silver.repartition(10) + +# Make the requests +emails_silver_with_summary = ( + emails_silver + .withColumn("summary_string", summarisation_udf(SF.col("email_body_clean"))) +) + + +# Save table +# ( +# emails_silver_with_summary +# .write +# .mode("overwrite") +# .option("overwriteSchema", "true") +# .saveAsTable(config['table_emails_silver_externalm']) +# ) + +# COMMAND ---------- + +display(emails_silver_with_summary) + +# COMMAND ---------- + +display(emails_silver_with_summary) + +# COMMAND ---------- + + diff --git a/(Clone) ES 2b: Serve Foundation models for Automation.py b/(Clone) ES 2b: Serve Foundation models for Automation.py new file mode 100644 index 0000000..466bf07 --- /dev/null +++ b/(Clone) ES 2b: Serve Foundation models for Automation.py @@ -0,0 +1,166 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC # External Models for Email Response Automation + +# COMMAND ---------- + +# MAGIC %pip install mlflow==2.9.0 langchain==0.0.344 databricks-sdk==0.12.0 + +# COMMAND ---------- + +!pip install databricks-genai-inference + +dbutils.library.restartPython() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC In this notebook, we will use Foundation Models for automation of the email response. + +# COMMAND ---------- + +# MAGIC %run "./ES 0a: Intro & Config" + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ### Read the emails from the bronze layer + +# COMMAND ---------- + +# MAGIC %md +# MAGIC Lets read the raw emails persisted in the bronze layer in a Dataframe. +# MAGIC + +# COMMAND ---------- + +emails_silver=spark \ + .read \ + .table(config['table_emails_bronze']) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Setup Langchain for the Foundation model +# MAGIC +# MAGIC We will setup Langchain to define the prompt template that will retrieve email Catagory, Sentiment, Synopsis and possible reply. +# MAGIC +# MAGIC The possible reply can be based on the templated and embedding can be used for it. However in this solution, we can not defined it. + +# COMMAND ---------- + +import mlflow +from langchain.chains import LLMChain +from langchain.prompts import PromptTemplate +from langchain.llms import Databricks +from langchain.chat_models import ChatDatabricks +from langchain.schema.output_parser import StrOutputParser + +prompt = PromptTemplate( + input_variables = ["question"], + template = "You are an assistant. Give a short answer to this question: {question}" +) +chat_model = ChatDatabricks(endpoint="databricks-mixtral-8x7b-instruct", max_tokens = 500) + +# Build Prompt Template +template = """ +[INST] <> +Given the following email text, categorise whether the email is a job request, customer query or generic email where no action required. It should capture sentiment of the email as positive, negative or neutral. Also it should create a short summary of the email. In addition, it should draft possible reply to email. the output of the questions should only be a JSON dictionary of dictionaries + +The output should be structured as a JSON dictionary of dictionaries. First attribute name is "Category" which categorises of the email as three possible values - Job, Query or No Action. Second json attribute name is Sentiment with possible values - positive, negative or neutral. Third json attribute name is "Synopsis" which should capture short email summary in 2-3 lines. Forth JSON attribute name "Reply" should be possibly email reply to the original email. +<> +Email summary begin here DO NOT give answer except a JSON and No other text : {email_body} [/INST] """ + +prompt = PromptTemplate(template=template, input_variables=["email_body"]) + +# Build LLM Chain +llm_chain = LLMChain(prompt=prompt, llm=chat_model) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ###Create Email Summarisation UDF + +# COMMAND ---------- + +import mlflow +from langchain.chains import LLMChain +from langchain.prompts import PromptTemplate +from langchain.llms import Databricks +from langchain import OpenAI, PromptTemplate, LLMChain +from pyspark.sql import functions as SF +from pyspark.sql.types import StringType +from tenacity import retry, stop_after_attempt, wait_exponential +from langchain.chat_models import ChatDatabricks +from langchain.schema.output_parser import StrOutputParser +import os + + +spark.conf.set("spark.sql.execution.arrow.maxRecordsPerBatch",2) +spark.conf.set("spark.sql.execution.arrow.pyspark.enabled", "true") +token = dbutils.notebook.entry_point.getDbutils().notebook().getContext().apiToken().get() + +# Create Summary function +def run_summarisation_pipeline(email_body): + + os.environ['DATABRICKS_TOKEN'] = token + + # Chat GPT LLM Model + chat_model = ChatDatabricks(endpoint="databricks-mixtral-8x7b-instruct", max_tokens = 500) + + # Build Prompt Template + template = """ + [INST] <> + Given the following email text, categorise whether the email is a job request, customer query or generic email where no action required. It should capture sentiment of the email as positive, negative or neutral. Also it should create a short summary of the email. In addition, it should draft possible reply to email. the output of the questions should only be a JSON dictionary of dictionaries + + The output should be structured as a JSON dictionary of dictionaries. First attribute name is "Category" which categorises of the email as three possible values - Job, Query or No Action. Second json attribute name is Sentiment with possible values - positive, negative or neutral. Third json attribute name is "Synopsis" which should capture short email summary in 2-3 lines. Forth JSON attribute name "Reply" should be possibly email reply to the original email. + <> + Email summary begin here DO NOT give answer except a JSON and No other text : {email_body} [/INST] """ + + prompt = PromptTemplate(template=template, input_variables=["email_body"]) + + # Build LLM Chain + chain = LLMChain(prompt=prompt, llm=chat_model) + + @retry(wait=wait_exponential(multiplier=10, min=10, max=1800), stop=stop_after_attempt(7)) + def _call_with_retry(email_body): + return chain.run(email_body) + + try: + summary_string = _call_with_retry(email_body) + except Exception as e: + summary_string = f"FAILED: {e.last_attempt.exception()}" + + return summary_string + + +# Create UDF +summarisation_udf = SF.udf( + lambda x: run_summarisation_pipeline( + email_body=x + ), + StringType(), +) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ###Process incoming data + +# COMMAND ---------- + +# Make the requests +emails_silver_with_summary = ( + emails_silver + .withColumn("summary_string", summarisation_udf(SF.col("email_body_clean"))) +) + + +# Save table +( + emails_silver_with_summary + .write + .mode("overwrite") + .option("overwriteSchema", "true") + .saveAsTable(config['table_emails_silver_foundationalm']) +) diff --git a/EA 0: Introduction.py b/EA 0: Introduction.py new file mode 100644 index 0000000..8a68c49 --- /dev/null +++ b/EA 0: Introduction.py @@ -0,0 +1,89 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC #Reducing time-to-resolution for email customer support using LLMs + +# COMMAND ---------- + +# MAGIC %md +# MAGIC The purpose of this notebook is to introduce the automation of customer support thorugh email. We use Large Langauge models (LLM) to automation the the email response process. You may find this notebook at https://github.com/sachinpatilb/PersonalRepo/tree/master/EmailSummary + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ### Introduction +# MAGIC Organizations often receive customer support inquiries through email channels. These emails need to adhere to service level agreements (SLAs) set by the organization or regulators, with penalties for failing to meet SLAs and rewards for exceeding them. Providing faster and more effective responses to customer inquiries enhances customer experience. However, many organizations struggle to meet SLAs due to the high volume of emails received and limited staff resources to respond to them. +# MAGIC +# MAGIC This solution accelerator proposes to use Large Language Models (LLMs) to automate the email response process. It involves the following key activities: +# MAGIC +# MAGIC +# MAGIC +# MAGIC 1. Categorization: The first step is to categorize the emails to understand the customer requests and urgency, including associated SLAs, and determine the appropriate approach for responding. Emails can be categorized as queries about the product, specific job requests, or generic emails that don't require a response. +# MAGIC 2. Sentiment Analysis: The sentiment of the email - positive, neutral, or negative - is analyzed. +# MAGIC 3. Synopsis: A summary of the email is created to help customer support professionals quickly understand its contents without reading the entire email. +# MAGIC 4. Automated Email Response: Based on the email's category, sentiment, and analysis, an automated customer email response is generated. +# MAGIC +# MAGIC +# MAGIC As part of the solution, we present two approaches to deploy the solution on the Databricks Data Intelligence Platform: +# MAGIC 1. Proprietary SaaS LLMs: One approach is to call proprietary SaaS LLMs APIs from the Databricks Data Intelligence platform. This is convenient as it eliminates the need to train the model from scratch. Instead, pre-trained models can be used to classify and categorize emails. +# MAGIC 2. Open LLMs: The second approach involves deploying the solution within the organization's infrastructure as emails can contain sensitive information that cannot be shared outside the organization. This can be achieved using existing open LLM models such as LLAMA, Mosaic, etc. Fine-tuning of these models can be done using Databricks Mosaic models. This option provides more control over the infrastructure and can also reduce costs. +# MAGIC +# MAGIC + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Solution Design/architecture +# MAGIC +# MAGIC In this section, let's describe the solution design and its + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Configuration Settings +# MAGIC +# MAGIC The following represent configuration settings used across various notebooks in this solution accelerator. You should read through all the notebooks to understand how the configuration settings are used before making any changes to the values below. + +# COMMAND ---------- + +# DBTITLE 1,Instantiate Config Variable +if 'config' not in locals().keys(): + config = {} + +# COMMAND ---------- + +# DBTITLE 1,Database and Volume +config['catalog'] = 'email_summary_llm_solution' +config['schema'] = 'email_llm' +config['volume'] = 'source_data' +config['vol_data_landing'] = f"/Volumes/{config['catalog']}/{config['schema']}/{config['volume']}" +config['table_emails_bronze'] = 'emails_bronze' +config['table_emails_silver_foundationalm'] = 'emails_foundational_silver' +config['table_emails_silver_externalm'] = 'emails_externalm_silver' +config['table_emails_silver'] = 'emails_silver' + +# COMMAND ---------- + +# create catalog if not exists +spark.sql('create catalog if not exists {0}'.format(config['catalog'])) + +# set current catalog context +spark.sql('USE CATALOG {0}'.format(config['catalog'])) + +# create database if not exists +spark.sql('create database if not exists {0}'.format(config['schema'])) + +# set current datebase context +spark.sql('USE {0}'.format(config['schema'])) + +# COMMAND ---------- + +# DBTITLE 1,Storage (see notebook 0b for more info) +config['mount_point'] ='/tmp/emails_summary' + +# file paths +config['checkpoint_path'] = config['mount_point'] + '/checkpoints' +config['schema_path'] = config['mount_point'] + '/schema' + +# COMMAND ---------- + +config['openai_api_key']=dbutils.secrets.get(scope = "email_openai_secret_scope", key = "openai_api_key") diff --git a/ES 0b: Ingest Emails into Lakehouse.py b/ES 0b: Ingest Emails into Lakehouse.py new file mode 100644 index 0000000..60bfcab --- /dev/null +++ b/ES 0b: Ingest Emails into Lakehouse.py @@ -0,0 +1,42 @@ +# Databricks notebook source +# MAGIC %run ./_resources/00-setup + +# COMMAND ---------- + +# MAGIC %md ## Ingest customer emails into the Bronze Emails table +# MAGIC +# MAGIC Along with our customer, we have created sample emails based on the real world emails received by our electricity supplier for their business customers + +# COMMAND ---------- + +bronzeDF = (spark.readStream \ + .format("cloudFiles") + .option("cloudFiles.format", "csv") + .option("cloudFiles.schemaLocation", config['schema_path']) \ + .option("multiLine", "true") \ + .option("escape", "\"") \ + .option("quote","\"") \ + .option("header", "true") \ + .option("inferSchema","true") \ + .option("rescuedDataColumn", "_rescued_data") \ + .load(config['vol_data_landing'])) + +# COMMAND ---------- + +_ = ( + bronzeDF + .writeStream + .format('delta') + .outputMode('append') + .option('checkpointLocation', f"{config['checkpoint_path']}/emails_bronze") + .toTable(config['table_emails_bronze']) + ) + +# COMMAND ---------- + +# MAGIC %sql +# MAGIC select * from email_summary_llm_solution.email_llm.emails_bronze + +# COMMAND ---------- + + diff --git a/_resources/00-setup.py b/_resources/00-setup.py new file mode 100644 index 0000000..9acdcae --- /dev/null +++ b/_resources/00-setup.py @@ -0,0 +1,50 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC ## Configuration Settings +# MAGIC +# MAGIC The following represent configuration settings used across various notebooks in this solution accelerator. You should read through all the notebooks to understand how the configuration settings are used before making any changes to the values below. + +# COMMAND ---------- + +# DBTITLE 1,Instantiate Config Variable +if 'config' not in locals().keys(): + config = {} + +# COMMAND ---------- + +# DBTITLE 1,Database and Volume +config['catalog'] = 'email_summary_llm_solution' +config['schema'] = 'email_llm' +config['volume'] = 'source_data' +config['vol_data_landing'] = f"/Volumes/{config['catalog']}/{config['schema']}/{config['volume']}" +config['table_emails_bronze'] = 'emails_bronze' +config['table_emails_silver_foundationalm'] = 'emails_foundational_silver' +config['table_emails_silver_externalm'] = 'emails_externalm_silver' +config['table_emails_silver'] = 'emails_silver' + +# COMMAND ---------- + +# create catalog if not exists +spark.sql('create catalog if not exists {0}'.format(config['catalog'])) + +# set current catalog context +spark.sql('USE CATALOG {0}'.format(config['catalog'])) + +# create database if not exists +spark.sql('create database if not exists {0}'.format(config['schema'])) + +# set current datebase context +spark.sql('USE {0}'.format(config['schema'])) + +# COMMAND ---------- + +config['mount_point'] ='/tmp/emails_summary' + +# file paths +config['checkpoint_path'] = config['mount_point'] + '/checkpoints' +config['schema_path'] = config['mount_point'] + '/schema' + +# COMMAND ---------- + +# DBTITLE 1,Add OpenAI Key through the secrets +config['openai_api_key']=dbutils.secrets.get(scope = "email_openai_secret_scope", key = "openai_api_key") diff --git a/images/EmailAutomation.png b/images/EmailAutomation.png new file mode 100644 index 0000000000000000000000000000000000000000..bac290fe102ccc9109618b4e72cb069fdf079ca3 GIT binary patch literal 48148 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�k#~Y&D9cD-3 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 literal 0 HcmV?d00001