From a33efa52ba5159018642de3962f6b9f78b633599 Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 15 Oct 2024 15:58:50 -0700 Subject: [PATCH 1/2] Updated code and readme with correct BlobSource Signed-off-by: Paul Yuknewicz --- README.md | 23 ++------------ text_summarize/function_app.py | 56 ++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 6a0a376..f970ec4 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,6 @@ Alternatively you can [create a Language resource](https://portal.azure.com/#cre } ``` - -### Using Visual Studio -1) Open `text_summarization.sln` using Visual Studio 2022 or later. -2) Press Run (`F5`) to run in the debugger -3) Open Storage Explorer, Storage Accounts -> Emulator -> Blob Containers -> and create a container `unprocessed-text` if it does not already exists -4) Copy any .txt document file with text into the `unprocessed-text` container - -You will see AI analysis happen in the Terminal standard out. The analysis will be saved in a .txt file in the `processed-text` blob container. - ### Using VS Code 1) Open the root folder in VS Code: @@ -78,21 +69,11 @@ code . 3) Run and Debug by pressing `F5` 4) Open Storage Explorer, Storage Accounts -> Emulator -> Blob Containers -> and create a container `unprocessed-text` if it does not already exists 5) Copy any .txt document file with text into the `unprocessed-text` container +6) In the Azure extension of VS Code, open Azure:Workspace -> Local Project -> Functions -> `summarize_function`. Right-click and Execute Function now. At the command palette prompt, enter the path to the storage blob you just uploaded: `unprocessed-text/`. This will simulate an EventGrid trigger locally and your function will trigger and show output in the terminal. You will see AI analysis happen in the Terminal standard out. The analysis will be saved in a .txt file in the `processed-text` blob container. -### Using Functions Core Tools CLI -0) Ensure `local.settings.json` exists already using steps above -1) Open a new terminal and do the following: - -```bash -cd text_summarization -func start -``` -2) Open Storage Explorer, Storage Accounts -> Emulator -> Blob Containers -> and create a container `test-samples-trigger` if it does not already exists -3) Copy any .txt document file with text into the `test-samples-trigger` container - -You will see AI analysis happen in the Terminal standard out. The analysis will be saved in a .txt file in the `test-samples-output` blob container. +Note, this newer mechanism for BlobTrigger with EventGrid source is documented in more detail here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-event-grid-blob-trigger?pivots=programming-language-python#run-the-function-locally. ## Deploy to Azure diff --git a/text_summarize/function_app.py b/text_summarize/function_app.py index 6ec7d7c..79b3644 100644 --- a/text_summarize/function_app.py +++ b/text_summarize/function_app.py @@ -2,45 +2,54 @@ import logging from azure.ai.textanalytics import TextAnalyticsClient from azure.identity import DefaultAzureCredential +from azure.functions import BlobSource import os app = func.FunctionApp() -# Load AI url and secrets from Env Variables in Terminal before running, -# e.g. `export TEXT_ANALYTICS_ENDPOINT=https://.cognitiveservices.azure.com/` -endpoint = os.getenv('TEXT_ANALYTICS_ENDPOINT', 'SETENVVAR!') +# Load AI url and secrets from Env Variables in Terminal before running +# e.g. `export TEXT_ANALYTICS_ENDPOINT= +# https://.cognitiveservices.azure.com/` +endpoint = os.getenv('TEXT_ANALYTICS_ENDPOINT', 'SETENVVAR!') -# Create client using Entra User or Managed Identity (no longer AzureKeyCredential) -# This requires a sub domain name to be set in endpoint URL for Managed Identity support -# See https://learn.microsoft.com/en-us/azure/ai-services/authentication#authenticate-with-microsoft-entra-id +# Create client using Entra User or Managed Identity +# This requires a sub domain name to be set in endpoint URL +# See https://learn.microsoft.com/en-us/azure/ai-services/authentication +# #authenticate-with-microsoft-entra-id text_analytics_client = TextAnalyticsClient( endpoint=endpoint, credential=DefaultAzureCredential(), ) + @app.function_name(name="summarize_function") -@app.blob_trigger(arg_name="myblob", path="unprocessed-text/{name}", - connection="AzureWebJobsStorage", source="EventGrid") -@app.blob_output(arg_name="outputblob", path="processed-text/{name}-output.txt", connection="AzureWebJobsStorage") +@app.blob_trigger( + arg_name="myblob", path="unprocessed-text/{name}", + connection="AzureWebJobsStorage", + source=BlobSource.EVENT_GRID + ) +@app.blob_output( + arg_name="outputblob", path="processed-text/{name}-output.txt", + connection="AzureWebJobsStorage") def test_function(myblob: func.InputStream, outputblob: func.Out[str]): - logging.info(f"Triggered item: {myblob.name}\n") + logging.info(f"Triggered item: {myblob.name}\n") + + document = [myblob.read().decode('utf-8')] + summarized_text = ai_summarize_txt(document) + logging.info(f"\n *****Summary***** \n{summarized_text}") + outputblob.set(summarized_text) - document = [myblob.read().decode('utf-8')] - summarized_text = ai_summarize_txt(document) - logging.info(f"\n *****Summary***** \n{summarized_text}"); - outputblob.set(summarized_text) # Example method for summarizing text def ai_summarize_txt(document): poller = text_analytics_client.begin_extract_summary(document) extract_summary_results = poller.result() - summarized_text = "" - document_results = poller.result() + for result in extract_summary_results: if result.kind == "ExtractiveSummarization": - summarized_text= "Summary extracted: \n{}".format( + summarized_text = "Summary extracted: \n{}".format( " ".join([sentence.text for sentence in result.sentences])) print(summarized_text) logging.info(f"Returning summarized text: \n{summarized_text}") @@ -48,15 +57,22 @@ def ai_summarize_txt(document): print("...Is an error with code '{}' and message '{}'".format( result.error.code, result.error.message )) - logging.error(f"Error with code '{result.error.code}' and message '{result.error.message}'") + logging.error( + f"Error with code '{result.error.code}' and message " + + "'{result.error.message}'" + ) # Perform sentiment analysis on document summary - sentiment_result = text_analytics_client.analyze_sentiment([summarized_text])[0] + sentiment_result = text_analytics_client.analyze_sentiment( + [summarized_text] + )[0] print(f"\nSentiment: {sentiment_result.sentiment}") print(f"Positive Score: {sentiment_result.confidence_scores.positive}") print(f"Negative Score: {sentiment_result.confidence_scores.negative}") print(f"Neutral Score: {sentiment_result.confidence_scores.neutral}") - summary_with_sentiment = summarized_text + f"\nSentiment: {sentiment_result.sentiment}\n" + summary_with_sentiment = ( + summarized_text + "\nSentiment: " + f"{sentiment_result.sentiment}\n" + ) return summary_with_sentiment From 799b2355450edaf6d29184aaaeb1d43233749b5d Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Mon, 21 Oct 2024 16:24:08 +0200 Subject: [PATCH 2/2] Reverting to source="EventGrid" for now Signed-off-by: Paul Yuknewicz --- README.md | 42 ++++++++++++++++++++++----------- text_summarize/function_app.py | 2 +- text_summarize/requirements.txt | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f970ec4..81a1d63 100644 --- a/README.md +++ b/README.md @@ -91,31 +91,38 @@ The main operation of the code starts with the `summarize_function` function in ```python @app.function_name(name="summarize_function") -@app.blob_trigger(arg_name="myblob", path="unprocessed-text/{name}", - connection="AzureWebJobsStorage", source="EventGrid") -@app.blob_output(arg_name="outputblob", path="processed-text/{name}-output.txt", connection="AzureWebJobsStorage") +@app.blob_trigger( + arg_name="myblob", path="unprocessed-text/{name}", + connection="AzureWebJobsStorage", + source="EventGrid" + ) +@app.blob_output( + arg_name="outputblob", path="processed-text/{name}-output.txt", + connection="AzureWebJobsStorage") def test_function(myblob: func.InputStream, outputblob: func.Out[str]): - logging.info(f"Triggered item: {myblob.name}\n") + logging.info(f"Triggered item: {myblob.name}\n") - document = [myblob.read().decode('utf-8')] - summarized_text = ai_summarize_txt(document) - logging.info(f"\n *****Summary***** \n{summarized_text}"); - outputblob.set(summarized_text) + document = [myblob.read().decode('utf-8')] + summarized_text = ai_summarize_txt(document) + logging.info(f"\n *****Summary***** \n{summarized_text}") + outputblob.set(summarized_text) ``` The `ai_summarize_txt` helper function does the heavy lifting for summary extraction and sentiment analysis using the `TextAnalyticsClient` SDK from the [AI Language Services](https://learn.microsoft.com/en-us/azure/ai-services/language-service/): ```python + + +# Example method for summarizing text def ai_summarize_txt(document): poller = text_analytics_client.begin_extract_summary(document) extract_summary_results = poller.result() - summarized_text = "" - document_results = poller.result() + for result in extract_summary_results: if result.kind == "ExtractiveSummarization": - summarized_text= "Summary extracted: \n{}".format( + summarized_text = "Summary extracted: \n{}".format( " ".join([sentence.text for sentence in result.sentences])) print(summarized_text) logging.info(f"Returning summarized text: \n{summarized_text}") @@ -123,16 +130,23 @@ def ai_summarize_txt(document): print("...Is an error with code '{}' and message '{}'".format( result.error.code, result.error.message )) - logging.error(f"Error with code '{result.error.code}' and message '{result.error.message}'") + logging.error( + f"Error with code '{result.error.code}' and message " + + "'{result.error.message}'" + ) # Perform sentiment analysis on document summary - sentiment_result = text_analytics_client.analyze_sentiment([summarized_text])[0] + sentiment_result = text_analytics_client.analyze_sentiment( + [summarized_text] + )[0] print(f"\nSentiment: {sentiment_result.sentiment}") print(f"Positive Score: {sentiment_result.confidence_scores.positive}") print(f"Negative Score: {sentiment_result.confidence_scores.negative}") print(f"Neutral Score: {sentiment_result.confidence_scores.neutral}") - summary_with_sentiment = summarized_text + f"\nSentiment: {sentiment_result.sentiment}\n" + summary_with_sentiment = ( + summarized_text + "\nSentiment: " + f"{sentiment_result.sentiment}\n" + ) return summary_with_sentiment ``` diff --git a/text_summarize/function_app.py b/text_summarize/function_app.py index 79b3644..1a3e336 100644 --- a/text_summarize/function_app.py +++ b/text_summarize/function_app.py @@ -26,7 +26,7 @@ @app.blob_trigger( arg_name="myblob", path="unprocessed-text/{name}", connection="AzureWebJobsStorage", - source=BlobSource.EVENT_GRID + source="EventGrid" ) @app.blob_output( arg_name="outputblob", path="processed-text/{name}-output.txt", diff --git a/text_summarize/requirements.txt b/text_summarize/requirements.txt index a9bdcfd..47ad0f5 100644 --- a/text_summarize/requirements.txt +++ b/text_summarize/requirements.txt @@ -2,6 +2,6 @@ # The Python Worker is managed by the Azure Functions platform # Manually managing azure-functions-worker may cause unexpected issues -azure-functions +azure-functions>=1.21.3 azure-ai-textanalytics azure-identity