This is an old revision of the document!
✅ 1. Azure Resources Checklist
| Resource Type | Name (per your setup) | Notes |
| Function App | dehamerfuncapp | Your C# Azure Function app |
| Storage Account | Linked to Function App | Required for deployment—should auto-create if –functions-version specified |
| Azure Cognitive Services (Text Analytics) | dehamersentimentai | For sentiment classification |
| Azure OpenAI | don-openai-useast | For GPT-4.1-based reply generation |
Install These
brew install dotnet-sdk #This will be version 9 which will not work but gives you the rest of the stuff needed. brew install azure-cli
Assuming you already have created a Resource Group named don-test-rg, an OpenAI deployment named and deployed a gpt model named gpt-4.1.
Install this from the webpage and follow directions. dotnet 8.x
Then these
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.OpenApi --version 1.4.0 dotnet --list-sdks dotnet add package Azure.AI.TextAnalytics --version 5.3.0 dotnet add package Azure.AI.OpenAI --version 2.0.0
Create Storage Account
az storage account create \ --name dehamerfuncstorage \ --location eastus \ --resource-group don-test-rg \ --sku Standard_LRS
Create Function Plan for Windows
az functionapp plan create \ --resource-group don-test-rg \ --name donfuncplan \ --location eastus \ --sku B1 \ --is-linux false
Create Function App
az functionapp create \ --resource-group don-test-rg \ --name dehamerfuncapp \ --plan donfuncplan \ --storage-account dehamerfunctstorage \ --runtime dotnet-isolated \ --functions-version 4 \ --os-type Windows
Create TexAnalytics
az cognitiveservices account create \ --name dehamersentimentai \ --resource-group don-test-rg \ --location eastus \ --kind TextAnalytics \ --sku S \ --custom-domain dehamersentimentai az cognitiveservices account update \ --name donsentimentai \ --resource-group don-test-rg \ --set properties.publicNetworkAccess=Enabled
Get keys and endpoint for export and app settings
export TEXT_ANALYTICS_ENDPOINT=`az cognitiveservices account show \ --name dehamersentimentai \ --resource-group don-test-rg \ --query "properties.endpoint"|sed -e s:\"::g` export TEXT_ANALYTICS_KEY=`az cognitiveservices account keys list \ --name dehamersentimentai \ --resource-group don-test-rg \ --query "key1" -o tsv` az functionapp config appsettings set \ --name dehamerfuncapp \ --resource-group don-test-rg \ --settings \ TEXT_ANALYTICS_ENDPOINT=$TEXT_ANALYTICS_ENDPOINT \ TEXT_ANALYTICS_KEY=$TEXT_ANALYTICS_KEY export OPENAI_KEY=`az cognitiveservices account keys list \ --name don-openai-useast \ --resource-group don-test-rg \ --query "key1" -o tsv` export OPENAI_ENDPOINT=https://don-openai-useast.openai.azure.com/ az functionapp config appsettings set \ --name donfuncapp \ --resource-group don-test-rg \ --settings \ OPENAI_KEY=$OPENAI_KEY \ OPENAI_ENDPOINT=$OPENAI_ENDPOINT #Confirm they were set az functionapp config appsettings list \ --name dehamerfuncapp \ --resource-group don-test-rg \ --query "[?starts_with(name, 'TEXT_') || starts_with(name, 'OPENAI_')]" -o table
Create a directory to store the functionapp files
mkdir ~/FeedbackFunctionDotNet cd ~/FeedbackFunctionDotNet
Save the FeedbackFunction.cs, FeedbackFunction.csproj, global.json, host.json, and local.settings.json into the FeedBackFunctionDotNet directory.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Azure;
using Azure.AI.TextAnalytics;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
public class DeHamerFeedbackFunction
{
private readonly ILogger _logger;
public DeHamerFeedbackFunction(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<DeHamerFeedbackFunction>();
}
[Function("DeHamerFeedbackFunction")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]
HttpRequestData req)
{
_logger.LogInformation("DeHamerFeedbackFunction triggered.");
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(requestBody);
string feedback = data?["feedback"] ?? string.Empty;
// Get Text Analytics config
var textEndpoint = Environment.GetEnvironmentVariable("TEXT_ANALYTICS_ENDPOINT");
var textKey = Environment.GetEnvironmentVariable("TEXT_ANALYTICS_KEY");
_logger.LogInformation("Using Text Analytics endpoint: {endpoint}", textEndpoint);
var credentials = new AzureKeyCredential(textKey);
var client = new TextAnalyticsClient(new Uri(textEndpoint), credentials);
var documentSentiment = await client.AnalyzeSentimentAsync(feedback);
var sentiment = documentSentiment.Value.Sentiment.ToString().ToLower();
_logger.LogInformation("Sentiment detected: {sentiment}", sentiment);
// Compose prompt
string prompt = sentiment switch
{
"positive" => $"Respond in a cheerful and thankful tone to the following positive customer feedback:\n\"{feedback}\"",
"negative" => $"Respond in a harsh, sarcastic, and annoyed tone to the following negative customer feedback:\n\"{feedback}\"",
_ => $"Respond neutrally and professionally to the following feedback:\n\"{feedback}\""
};
// OpenAI Setup
var openaiEndpoint = Environment.GetEnvironmentVariable("OPENAI_API_ENDPOINT")?.TrimEnd('/');
var openaiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
var deployment = Environment.GetEnvironmentVariable("OPENAI_DEPLOYMENT");
_logger.LogInformation("Calling OpenAI at: {url}", $"{openaiEndpoint}/openai/deployments/{deployment}/chat/completions");
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", openaiKey);
var payload = JsonSerializer.Serialize(new
{
messages = new[]
{
new { role = "system", content = "You are a customer support agent who mirrors the customer's sentiment tone." },
new { role = "user", content = prompt }
}
});
_logger.LogInformation("Payload sent to OpenAI: {payload}", payload);
var response = await httpClient.PostAsync(
$"{openaiEndpoint}/openai/deployments/{deployment}/chat/completions?api-version=2024-02-15-preview",
new StringContent(payload, Encoding.UTF8, "application/json"));
if (!response.IsSuccessStatusCode)
{
var errorDetails = await response.Content.ReadAsStringAsync();
_logger.LogError("OpenAI API call failed: {status} - {details}", response.StatusCode, errorDetails);
var errorResponse = req.CreateResponse(System.Net.HttpStatusCode.InternalServerError);
await errorResponse.WriteAsJsonAsync(new { error = "Failed to get OpenAI response", status = response.StatusCode, details = errorDetails });
return errorResponse;
}
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
_logger.LogInformation("Raw OpenAI response: {json}", json);
var message = json.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString();
var result = new
{
sentiment = sentiment,
message = message
};
var responseData = req.CreateResponse(System.Net.HttpStatusCode.OK);
await responseData.WriteAsJsonAsync(result);
return responseData;
}
}
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.17.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.OpenApi" Version="1.5.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.0" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
Make sure this matches the version of dotnet 8.x you install.
{
"sdk": {
"version": "8.0.411"
}
}
{
"version": "2.0"
}
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}