Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added OpenAI.Plugin files and configurations #30

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
DOCS_IMAGE_NAME: aihub-prepdocs
PLUGIN_IMAGE_NAME: aihub-plugin
AIHUB_ARTIFACT_NAME: aihub-tf-module

jobs:
Expand Down Expand Up @@ -105,6 +106,21 @@ jobs:
tags: ${{ env.REGISTRY }}/${{ env.GITHUB_REPOSITORY_LOWER_CASE }}/${{ env.DOCS_IMAGE_NAME }}:${{ env.MINVERVERSIONOVERRIDE }}
labels: ${{ steps.meta-docs.outputs.labels }}

- name: Extract metadata (tags, labels) for plugin Docker
id: meta-plugin
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.PLUGIN_IMAGE_NAME }}

- name: Build and push Docker image for plugin
uses: docker/build-push-action@v3
with:
context: ./src/OpenAI.Plugin/
file: ./src/OpenAI.Plugin/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ env.REGISTRY }}/${{ env.GITHUB_REPOSITORY_LOWER_CASE }}/${{ env.PLUGIN_IMAGE_NAME }}:${{ env.MINVERVERSIONOVERRIDE }}
labels: ${{ steps.meta-plugin.outputs.labels }}

- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,6 @@ src/AIHub/appsettings.Development.json

# .tfstate files
*.tfstate
*.tfstate.*
*.tfstate.*

local.settings.json
19 changes: 0 additions & 19 deletions infra/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions infra/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ locals {
ca_chat_name = "${var.ca_chat_name}${local.name_sufix}"
ca_prep_docs_name = "${var.ca_prep_docs_name}${local.name_sufix}"
ca_aihub_name = "${var.ca_aihub_name}${local.name_sufix}"
func_name = "plugin${local.sufix}"
}

resource "azurerm_resource_group" "rg" {
Expand Down Expand Up @@ -229,3 +230,18 @@ module "ca_aihub" {
enable_entra_id_authentication = var.enable_entra_id_authentication
image_name = var.ca_aihub_image
}

module "plugin" {
source = "./modules/ca-plugin"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
resource_group_id = azurerm_resource_group.rg.id
func_name = local.func_name
image_name = var.ca_plugin_image
cae_id = module.cae.cae_id
cae_default_domain = module.cae.default_domain
appi_instrumentation_key = module.appi.appi_key
openai_key = module.openai.openai_key
openai_model = module.openai.gpt_deployment_name
openai_endpoint = module.openai.openai_endpoint
}
74 changes: 74 additions & 0 deletions infra/modules/ca-plugin/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
resource "azurerm_storage_account" "sa" {
name = "stfunc${var.func_name}"
location = var.location
resource_group_name = var.resource_group_name
account_tier = "Standard"
account_replication_type = "LRS"
enable_https_traffic_only = true
}

resource "azapi_resource" "ca_function" {
schema_validation_enabled = false
name = "func-${var.func_name}"
location = var.location
parent_id = var.resource_group_id
type = "Microsoft.Web/sites@2023-01-01"
body = jsonencode({
kind = "functionapp,linux,container,azurecontainerapps"
properties : {
language = "dotnet-isolated"
managedEnvironmentId = "${var.cae_id}"
siteConfig = {
linuxFxVersion = "DOCKER|cmendibl3/aoai-plugin:0.8.0"
appSettings = [
{
name = "AzureWebJobsStorage"
value = azurerm_storage_account.sa.primary_connection_string
},
{
name = "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"
value = azurerm_storage_account.sa.primary_connection_string
},
{
name = "APPINSIGHTS_INSTRUMENTATIONKEY"
value = var.appi_instrumentation_key
},
{
name = "APPLICATIONINSIGHTS_CONNECTION_STRING"
value = "InstrumentationKey=${var.appi_instrumentation_key}"
},
{
name = "FUNCTIONS_WORKER_RUNTIME"
value = "dotnet-isolated"
},
{
name = "FUNCTIONS_EXTENSION_VERSION"
value = "~4"
},
{
name = "MODEL_ID"
value = var.openai_model
},
{
name = "API_KEY"
value = var.openai_key
},
{
name = "ENDPOINT"
value = var.openai_endpoint
},
{
name = "OpenApi__HostNames"
value = "https://func-${var.func_name}.${var.cae_default_domain}/api"
}
]
}
workloadProfileName = "Consumption"
resourceConfig = {
cpu = 1
memory = "2Gi"
}
httpsOnly = false
}
})
}
Empty file.
20 changes: 20 additions & 0 deletions infra/modules/ca-plugin/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
terraform {
required_version = ">= 1.1.8"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.87.0"
}
azapi = {
source = "Azure/azapi"
}
}
}

provider "azurerm" {
features {
cognitive_account {
purge_soft_delete_on_destroy = true
}
}
}
11 changes: 11 additions & 0 deletions infra/modules/ca-plugin/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "resource_group_name" {}
variable "resource_group_id" {}
variable "location" {}
variable "func_name" {}
variable "image_name" {}
variable "cae_id" {}
variable "cae_default_domain" {}
variable "appi_instrumentation_key" {}
variable "openai_key" {}
variable "openai_model" {}
variable "openai_endpoint" {}
2 changes: 1 addition & 1 deletion infra/modules/cae/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ output "cae_id" {
value = azapi_resource.cae.id
}

output "defaultDomain" {
output "default_domain" {
value = jsondecode(azapi_resource.cae.output).properties.defaultDomain
}

4 changes: 4 additions & 0 deletions infra/modules/openai/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ output "gpt4_deployment_model_name" {
output "embedding_deployment_name" {
value = azurerm_cognitive_deployment.embedding.name
}

output "openai_key" {
value = azurerm_cognitive_account.openai.primary_access_key
}
8 changes: 6 additions & 2 deletions infra/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,15 @@ variable "ca_chat_image" {
}

variable "ca_prep_docs_image" {
default = "ghcr.io/azure/aihub/aihub-prepdocs:1.0.6"
default = "ghcr.io/azure/aihub/aihub-prepdocs:1.0.8"
}

variable "ca_plugin_image" {
default = "ghcr.io/azure/aihub/aihub-plugin:1.0.8"
}

variable "ca_aihub_image" {
default = "ghcr.io/azure/aihub/aihub:1.0.6"
default = "ghcr.io/azure/aihub/aihub:1.0.8"
}

variable "use_random_suffix" {
Expand Down
1 change: 1 addition & 0 deletions src/OpenAI.Plugin/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
local.settings.json
23 changes: 23 additions & 0 deletions src/OpenAI.Plugin/AIPluginJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
public class AIPluginJson
{
[Function("GetAIPluginJson")]
public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = ".well-known/ai-plugin.json")] HttpRequestData req)
{
var currentDomain = $"{req.Url.Scheme}://{req.Url.Host}:{req.Url.Port}/api";

HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/json");

var settings = AIPluginSettings.FromFile();

// serialize app settings to json using System.Text.Json
var json = System.Text.Json.JsonSerializer.Serialize(settings);

// replace {url} with the current domain
json = json.Replace("{url}", currentDomain, StringComparison.OrdinalIgnoreCase);

response.WriteString(json);

return response;
}
}
73 changes: 73 additions & 0 deletions src/OpenAI.Plugin/CallTranscriptPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Net;
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Extensions.Logging;
using Models;

namespace OpenAI.Plugin
{
public class CallTranscriptPlugin
{
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
private readonly ILogger _logger;
private readonly Kernel _kernel;
private readonly string _endpoint = Environment.GetEnvironmentVariable("ENDPOINT")!;
private readonly string _deploymentName = Environment.GetEnvironmentVariable("MODEL_ID")!;
private readonly string _subscriptionKey = Environment.GetEnvironmentVariable("API_KEY")!;

public CallTranscriptPlugin(ILoggerFactory loggerFactory, Kernel kernel)
{
_logger = loggerFactory.CreateLogger<CallTranscriptPlugin>();
_kernel = kernel;
}

[Function("Call Transcript Plugin")]
[OpenApiOperation(operationId: "CallTranscriptPlugin", tags: new[] { "CallTranscriptPlugin" }, Description = "Used to analyze a call given the transcript and a prompt")]
[OpenApiRequestBody("application/json", typeof(ExecuteFunctionRequest), Description = "Variables to use when executing the specified function.", Required = true)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(ExecuteFunctionResponse), Description = "Returns the response from the AI.")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(ErrorResponse), Description = "Returned if the request body is invalid.")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.NotFound, contentType: "application/json", bodyType: typeof(ErrorResponse), Description = "Returned if the semantic function could not be found.")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "plugins/transcript")] HttpRequestData req,
FunctionContext executionContext)
{
_logger.LogInformation("C# HTTP trigger function processed a request.");

#pragma warning disable CA1062
var functionRequest = await JsonSerializer.DeserializeAsync<ExecuteFunctionRequest>(req.Body, _jsonOptions).ConfigureAwait(false);
#pragma warning disable CA1062
if (functionRequest == null)
{
return await CreateResponseAsync(req, HttpStatusCode.BadRequest, new ErrorResponse() { Message = $"Invalid request body {functionRequest}" }).ConfigureAwait(false);
}

try
{
var context = new KernelArguments
{
{ "transcript", functionRequest.Transcript }
};

var result = await _kernel.InvokeAsync("Prompts", "CallAnalyzer", context).ConfigureAwait(false);

return await CreateResponseAsync(
req,
HttpStatusCode.OK,
new ExecuteFunctionResponse() { Response = result.ToString() }).ConfigureAwait(false);
}
catch (Exception ex)
{
return await CreateResponseAsync(req, HttpStatusCode.BadRequest, new ErrorResponse() { Message = ex.Message }).ConfigureAwait(false);
}
}

private static async Task<HttpResponseData> CreateResponseAsync(HttpRequestData requestData, HttpStatusCode statusCode, object responseBody)
{
var responseData = requestData.CreateResponse(statusCode);
await responseData.WriteAsJsonAsync(responseBody).ConfigureAwait(false);
return responseData;
}
}
}
12 changes: 12 additions & 0 deletions src/OpenAI.Plugin/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS installer-env

COPY . /src/dotnet-function-app
RUN cd /src/dotnet-function-app && \
mkdir -p /home/site/wwwroot && \
dotnet publish *.csproj --output /home/site/wwwroot

FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4.0-dotnet-isolated8.0
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true

COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]
19 changes: 19 additions & 0 deletions src/OpenAI.Plugin/GlobalUsing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
global using Microsoft.SemanticKernel;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
global using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
global using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
global using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
global using Microsoft.Extensions.Hosting;
global using Microsoft.OpenApi.Models;
global using System.Text.Json.Serialization;
global using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
global using Microsoft.Extensions.Configuration;
global using System.Net;
global using System.Reflection;
global using Microsoft.Azure.Functions.Worker;
global using Microsoft.Azure.Functions.Worker.Http;
global using Azure.AI.OpenAI;
global using Azure;
global using Azure.Identity;
global using Models;
Loading
Loading