Skip to content

Commit 70c69a2

Browse files
committed
Added automated infrastructure and code deployment with bicep and azure CLI
Some code clean up Additional documentation and examples
1 parent 9d728df commit 70c69a2

17 files changed

+555
-47
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,5 @@ paket-files/
261261

262262
# Python Tools for Visual Studio (PTVS)
263263
__pycache__/
264-
*.pyc
264+
*.pyc
265+
.azure

trblob_ProcessFile.cs BlobTriggerProcessFile.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
namespace Company.Function
1717
{
18-
public class trblob_ProcessFile
18+
public class BlobTriggerProcessFile
1919
{
2020
[FunctionName("BlobTriggerProcessFile")]
2121
public async Task RunAsync([BlobTrigger("raw/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log)
@@ -31,13 +31,13 @@ public async Task RunAsync([BlobTrigger("raw/{name}", Connection = "StorageConne
3131

3232
AzureKeyCredential credential = new AzureKeyCredential(subscriptionKey);
3333
DocumentAnalysisClient client = new DocumentAnalysisClient(new Uri(endpoint), credential);
34-
35-
string imgUrl = $"https://{Environment.GetEnvironmentVariable("StorageAccount")}.blob.core.windows.net/raw/{name}";
34+
35+
string imgUrl = $"https://{Environment.GetEnvironmentVariable("StorageAccount")}.blob.core.windows.net/raw/{name}"; //?sp=racwdli&st=2023-10-26T17:24:43Z&se=2024-01-02T02:24:43Z&spr=https&sv=2022-11-02&sr=c&sig=2zJpV7Hy47fcYZCZW7pZhxMlp%2FGxY0U1pVE5DEsXL98%3D";
3636

3737
log.LogInformation(imgUrl);
3838

3939
Uri fileUri = new Uri(imgUrl);
40-
40+
4141
AnalyzeDocumentOperation operation = await client.AnalyzeDocumentFromUriAsync(WaitUntil.Completed, "prebuilt-read", fileUri);
4242

4343
AnalyzeResult result = operation.Value;
@@ -91,8 +91,8 @@ public async Task RunAsync([BlobTrigger("raw/{name}", Connection = "StorageConne
9191

9292
}
9393

94-
log.LogInformation($"JSON file {newName} saved to Azure Blob Storage.");
95-
94+
log.LogInformation($"JSON file {newName} saved to Azure Blob Storage.");
95+
content = "";
9696
}
9797

9898
}

chr_olderFunction.csproj DocumentQuestionsFunction.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<PackageReference Include="Azure.AI.OpenAI" Version="1.0.0-beta.8" />
99
<PackageReference Include="Azure.Identity" Version="1.10.3" />
1010
<PackageReference Include="Azure.Storage.Blobs" Version="12.18.0" />
11-
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.1" />
11+
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.2.1" />
1212
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.2.0" />
1313
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta2" />
1414
</ItemGroup>

chr_olderFunction.sln DocumentQuestionsFunction.sln

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.5.002.0
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "chr_olderFunction", "chr_olderFunction.csproj", "{C4D5A230-367E-4C0A-A62A-C84997225B4E}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentQuestionsFunction", "DocumentQuestionsFunction.csproj", "{C4D5A230-367E-4C0A-A62A-C84997225B4E}"
77
EndProject
88
Global
99
GlobalSection(SolutionConfigurationPlatforms) = preSolution

httpTriggerAskAboutADoc.cs HttpTriggerOpenAISdkAskQuestion.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
namespace Company.Function
2020
{
21-
public static class httpTriggerAskAboutADoc
21+
public static class HttpTriggerAskAboutADoc
2222
{
2323
//function you can call to ask a question about a document.
24-
[FunctionName("httpTriggerAskAboutADoc")]
24+
[FunctionName("HttpTriggerOpenAiSdkAskQuestion")]
2525
public static async Task<IActionResult> Run(
2626
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
2727
ILogger log)

httpTriggerSemanticConfigAskQuestion.cs HttpTriggerSemanticKernelAskQuestion.cs

+11-12
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323

2424
namespace Company.Function
2525
{
26-
public static class httpTriggerSemanticConfigAskQuestion
26+
public static class HttpTriggerSemanticKernelAskQuestion
2727
{
2828
//function you can call to ask a question about a document.
29-
[FunctionName("httpTriggerSemanticConfigAskQuestion")]
29+
[FunctionName("HttpTriggerSemanticKernelAskQuestion")]
3030
public static async Task<IActionResult> Run(
3131
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
3232
ILogger log)
@@ -56,19 +56,18 @@ public static async Task<IActionResult> Run(
5656
var embeddingModel = Environment.GetEnvironmentVariable("OpenAIEmbeddingModel");
5757
var apiKey = Environment.GetEnvironmentVariable("OpenAIKey");
5858

59-
var kernel = Kernel.Builder
60-
.WithAzureChatCompletionService(chatModel, openAIEndpoint, apiKey)
61-
.WithAzureTextEmbeddingGenerationService(embeddingModel, openAIEndpoint, apiKey)
62-
.Build();
59+
// var kernel = Kernel.Builder
60+
// .WithAzureChatCompletionService(chatModel, openAIEndpoint, apiKey)
61+
// .WithAzureTextEmbeddingGenerationService(embeddingModel, openAIEndpoint, apiKey)
62+
// .Build();
6363

6464
var memoryWithCustomDb = new MemoryBuilder()
65-
.WithAzureTextEmbeddingGenerationService(embeddingModel, openAIEndpoint, apiKey)
66-
.WithAzureTextEmbeddingGenerationService(embeddingModel, openAIEndpoint, apiKey)
67-
.WithMemoryStore(store)
68-
.Build();
65+
.WithAzureTextEmbeddingGenerationService(embeddingModel, openAIEndpoint, apiKey)
66+
.WithMemoryStore(store)
67+
.Build();
6968

70-
string nameWithoutExtension = Path.GetFileNameWithoutExtension(filename);
71-
var docFile = await GetBlobContentAsync(nameWithoutExtension, log);
69+
string nameWithoutExtension = Path.GetFileNameWithoutExtension(filename);
70+
var docFile = await GetBlobContentAsync(nameWithoutExtension, log);
7271
await MMSemanticMemory.StoreMemoryAsync(memoryWithCustomDb, docFile, log);
7372
var memories = await MMSemanticMemory.SearchMemoryAsync(memoryWithCustomDb, question, log);
7473

trUploadFile.cs HttpTriggerUploadFile.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace Company.Function
1515
{
16-
public static class trUploadFile
16+
public static class HttpTriggerUploadFile
1717
{
1818
[FunctionName("HttpTriggerUploadFile")]
1919
public static async Task<IActionResult> Run(

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) [year] [fullname]
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Properties/launchSettings.json

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
{
22
"profiles": {
3-
"chr_olderFunction": {
3+
"docquestions": {
44
"commandName": "Project",
55
"commandLineArgs": "--port 7018",
6-
"launchBrowser": false
6+
"environmentVariables": {
7+
"OpenAIEndpoint": "",
8+
"OpenAIKey": "",
9+
"OpenAIChatModel": "gpt-4-32k",
10+
"OpenAIEmbeddingModel": "text-embedding-ada-002",
11+
"StorageAccount": "",
12+
"StorageConnectionString": "",
13+
"ExtractedContainerName": "extracted",
14+
"ContainerName": "raw",
15+
"RawStorageConnectionString": "",
16+
"DocumentIntelligenceSubscriptionKey": "",
17+
"DocumentIntelligenceEndpoint": ""
18+
},
19+
"nativeDebugging": false
720
}
821
}
922
}

README.md

+80-21
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,89 @@
1-
# semantic_kernel_askq
2-
c# function app - upload docs with rest api, and another functino to process data, and another 2 to ask qs
1+
# Semantic Kernel and Azure OpenAI: Ask Questions on your document
32

4-
Create an Azure Function: c#, 6 Isolated LTS
3+
## Overview
4+
5+
This solution provides an example of how to process your own documents and then use [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) and [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) to ask question specific to that document.
6+
7+
## What's Included
8+
9+
This solution consists of C# function app which has 4 functions:
10+
11+
1. `HttpTriggerUploadFile` - upload documents to an Azure Storage account via a REST Api
12+
2. `BlobTriggerProcessFile` - detects the uploaded document and processes it through [Azure Cognitive Services Document Intelligence](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/overview?view=doc-intel-3.1.0) into one or more JSON files (depending on the size of the document)
13+
3. `HttpTriggerOpenAiSdkAskQuestion` - REST Api to ask questions about the document using the OpenAI SDK
14+
4. `HttpTriggerSemanticKernelAskQuestion` - REST Api to ask questions about the document using Semantic Kernel SDK
15+
16+
## Getting Started
17+
18+
### Prerequisites
19+
20+
Before deploying your solution, you will need access to an Azure OpenAI instance in the same subscription where you are going to deploy your solution and retrieve its `Endpoint` and a `Key`
21+
22+
### Deploying
23+
24+
Deployment is automated using PowerShell, the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/) and [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/).\
25+
To run the script, you will need to select an Azure location for deployment, the Azure Open AI endpoint and key and pick a name for the function (this must be a globally unique name and less than 10 characters).
26+
27+
*NOTE:* The template assumes you have both `gpt-4-32k` and `text-embedding-ada-002` models deployed to your Azure OpenAI instance. If you want to use a different model, change the `openAIChatModel` and `openAIEmbeddingModel` default parameter values in `./infra/functions.bicep` file. Be aware, that using a different GPT model may result in max token violations with the example below.
28+
29+
``` powershell
30+
# obtain an Azure access token
31+
az login
32+
33+
# deploy the solutin
34+
.\deploy.ps1 -functionAppName <function name> -openAiEndpoint <http endpoint value> -openAiKey <openai key> -location <azure location>
35+
```
36+
37+
If successful, this process will create:
38+
39+
- Storage account with two blob containers (`raw` for uploaded documents and `extracted` for processed output)
40+
- Application Insights instance
41+
- Function app with 4 functions with system assigned managed identity
42+
- Role assigment for the function identity to access blob storage and call Azure OpenAI
43+
- Azure Cognitive Services account with system assigned managed identity
44+
- Role assigment for Cognitive Services identity for read access to `raw` container and write access to `extracted` container
45+
46+
### Running Samples
547

648
We will have the following functions in our Function App:
749

8-
1. BlobTriggerProcessFile
9-
- Takes data from raw container, processes it, into a json file.
10-
- ```
11-
{"FileName":"Test2_output.pdf","blobName":"Test2_output/Test2_output_1.json","Content":"This document has been updated. Here is new content for the document.Here are the reasons that Megan is cool:"}
12-
```
50+
1. Upload a document using the `HttpTriggerUploadFile` REST API. \
51+
For this example, download and use [US Declaration of Independence as a PDF file](https://uscode.house.gov/download/annualhistoricalarchives/pdf/OrganicLaws2006/decind.pdf) \
52+
Once the file us uploaded, the `BlobTriggerProcessFile` will automatically trigger, process it with Document Intelligence and create a new folder called `decind` in the `extracted` blob container and save 3 JSON files.
1353

14-
2. httpTriggerAskAboutADoc
15-
-Creates a post
16-
```
17-
{
18-
"filename": "Test2_output.pdf",
19-
"question": "who is cool?"
20-
}
21-
```
54+
2. Ask questions using the `HttpTriggerSemanticKernelAskQuestion` and/or `HttpTriggerOpenAiSdkAskQuestion` function - this uses semantic config to only load max of 2 pages to reduce tokens provided to Azure OpenAI.
55+
56+
Question:
57+
58+
``` json
59+
{
60+
"filename": "decind.pdf",
61+
"question": "How many people signed this document"
62+
}
63+
```
2264

23-
4. httpTriggerSemanticConfigAskQuestion - uses semantic config to only load max of 2 pages to reduce tokens provided to Azure OpenAI.
65+
Return:
66+
67+
``` text
68+
The document was signed by fifty-six signers.
69+
```
70+
71+
Question:
72+
73+
``` json
74+
{
75+
"filename": "decind.pdf",
76+
"question": "summarize this document in two bulleted sentences"
77+
}
2478
```
25-
{"filename": "Test2_output.pdf", "question": "who is cool?" }
79+
80+
Return:
81+
82+
``` text
83+
- The Declaration of Independence was unanimously agreed upon by thirteen united states of America on July 4, 1776, to express their decision to dissolve their political connection with Great Britain and become independent due to numerous abuses and usurpations by the king.
84+
- The fundamental principles of their new government would be based on the belief that all men are created equal with certain unalienable rights including life, liberty, and the pursuit of happiness, and if any government becomes destructive of these ends, it is the right of the people to alter or abolish it, and to institute a new government.
2685
```
2786

28-
6. HttpTriggerUploadFile
29-
Post API to upload information
30-
87+
### What's next?
88+
89+
Try uploading your own documents and start asking questions!

deploy.ps1

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
param(
2+
[string] $functionAppName,
3+
[string] $location,
4+
[string] $openAiEndpoint,
5+
[string] $openAiKey
6+
)
7+
8+
$error.Clear()
9+
$ErrorActionPreference = 'Stop'
10+
11+
$resourceGroupName = $functionAppName + "-rg"
12+
$storageAccountName = ($functionAppName + "storage").ToLower().Replace("-","").Replace("_","")
13+
$cognitiveServicesAccountName = $functionAppName + "-cogsvcs"
14+
15+
Write-Host "Creating Resource Group $resourceGroupName in $location" -ForegroundColor Green
16+
#write out the values for each variable
17+
Write-Host "Function App Name: $functionAppName" -ForegroundColor Green
18+
Write-Host "Location: $location" -ForegroundColor Green
19+
Write-Host "Storage Account Name: $storageAccountName" -ForegroundColor Green
20+
Write-Host "Cognitive Services Account Name: $cognitiveServicesAccountName" -ForegroundColor Green
21+
Write-Host "Open AI Endpoint: $openAiEndpoint" -ForegroundColor Green
22+
23+
az deployment sub create --location $location --template-file ./infra/main.bicep --parameters resourceGroupName=$resourceGroupName location=$location functionAppName=$functionAppName storageAccountName=$storageAccountName cognitiveServicesAccountName=$cognitiveServicesAccountName openAiEndpoint=$openAiEndpoint openAiKey=$openAiKey -o table
24+
25+
if(!$?){ exit }
26+
27+
28+
Write-Host "Publishing $functionAppName to $resourceGroupName" -ForegroundColor Green
29+
func azure functionapp publish $functionAppName --dotnet-version "7.0" --csharp
30+
31+

infra/cognitiveservices.bicep

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
param cognitiveServicesAccountName string
2+
param location string = resourceGroup().location
3+
4+
resource cognitiveServicesAccount 'Microsoft.CognitiveServices/accounts@2021-04-30' = {
5+
name: cognitiveServicesAccountName
6+
location: location
7+
kind: 'CognitiveServices'
8+
identity: {
9+
type: 'SystemAssigned'
10+
}
11+
sku: {
12+
name: 'S0'
13+
}
14+
properties:{
15+
16+
}
17+
}
18+
19+
output cogsvcsPrincipalId string = cognitiveServicesAccount.identity.principalId

0 commit comments

Comments
 (0)