Skip to content

Commit

Permalink
Aspire.AI.Azure.OpenAI integration (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros committed Feb 21, 2024
1 parent 9a020ad commit 857fc5d
Show file tree
Hide file tree
Showing 13 changed files with 44 additions and 58 deletions.
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
<ItemGroup>
<!-- Version together with Aspire -->
<PackageVersion Include="Aspire.Hosting" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.Azure" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Npgsql" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.RabbitMQ.Client" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.StackExchange.Redis" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="$(AspireVersion)" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="$(AspireVersion)" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery.Yarp" Version="$(AspireVersion)" />
<!-- Version together with ASP.NET -->
Expand Down Expand Up @@ -43,8 +45,7 @@
<!-- Xabaril packages -->
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="7.0.0" />
<!-- AI -->
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.12" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.0.1" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.4.0" />
<!-- Open Telemetry -->
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0-alpha.1" />
Expand Down
6 changes: 0 additions & 6 deletions src/Catalog.API/AIOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ public class AIOptions

public class OpenAIOptions
{
/// <summary>OpenAI API key for accessing embedding LLM.</summary>
public string ApiKey { get; set; }

/// <summary>Optional endpoint for which OpenAI API to access.</summary>
public string Endpoint { get; set; }

/// <summary>The name of the embedding model to use.</summary>
/// <remarks>When using Azure OpenAI, this should be the "Deployment name" of the embedding model.</remarks>
public string EmbeddingName { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Catalog.API/Catalog.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

<!-- AI -->
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Aspire.Azure.AI.OpenAI" />
<PackageReference Include="Pgvector" />
<PackageReference Include="Pgvector.EntityFrameworkCore" />
</ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/Catalog.API/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder)
builder.Services.AddOptions<AIOptions>()
.BindConfiguration("AI");

if (!string.IsNullOrWhiteSpace(builder.Configuration.GetConnectionString("openai")))
{
builder.AddAzureOpenAI("openai");
}

builder.Services.AddSingleton<ICatalogAI, CatalogAI>();
}
}
28 changes: 6 additions & 22 deletions src/Catalog.API/Services/CatalogAI.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
using Azure;
using Azure.AI.OpenAI;
using Azure.AI.OpenAI;
using Pgvector;

namespace eShop.Catalog.API.Services;

public sealed class CatalogAI : ICatalogAI
{
/// <summary>OpenAI API key for accessing embedding LLM.</summary>
private readonly string _aiKey;
/// <summary>Optional OpenAI API endpoint.</summary>
private readonly string _aiEndpoint;
/// <summary>The name of the embedding model to use.</summary>
private readonly string _aiEmbeddingModel;
private readonly OpenAIClient _openAIClient;

/// <summary>The web host environment.</summary>
private readonly IWebHostEnvironment _environment;
/// <summary>Logger for use in AI operations.</summary>
private readonly ILogger _logger;

public CatalogAI(IOptions<AIOptions> options, IWebHostEnvironment environment, ILogger<CatalogAI> logger)
public CatalogAI(IOptions<AIOptions> options, IWebHostEnvironment environment, ILogger<CatalogAI> logger, OpenAIClient openAIClient = null)
{
var aiOptions = options.Value;

_aiKey = aiOptions.OpenAI.ApiKey;
_aiEndpoint = aiOptions.OpenAI.Endpoint;
_openAIClient = openAIClient;
_aiEmbeddingModel = aiOptions.OpenAI.EmbeddingName ?? "text-embedding-ada-002";
IsEnabled = !string.IsNullOrWhiteSpace(_aiKey);

IsEnabled = _openAIClient != null;
_environment = environment;
_logger = logger;

if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation("API Key: {configured}", string.IsNullOrWhiteSpace(_aiKey) ? "Not configured" : "Configured");
_logger.LogInformation("Embedding model: \"{model}\"", _aiEmbeddingModel);
}
}
Expand All @@ -54,18 +45,11 @@ public async ValueTask<Vector> GetEmbeddingAsync(string text)
}

EmbeddingsOptions options = new(_aiEmbeddingModel, [text]);
return new Vector((await GetAIClient().GetEmbeddingsAsync(options)).Value.Data[0].Embedding);
return new Vector((await _openAIClient.GetEmbeddingsAsync(options)).Value.Data[0].Embedding);
}

/// <summary>Gets an embedding vector for the specified catalog item.</summary>
public ValueTask<Vector> GetEmbeddingAsync(CatalogItem item) => IsEnabled ?
GetEmbeddingAsync($"{item.Name} {item.Description}") :
ValueTask.FromResult<Vector>(null);

/// <summary>Gets the AI client used for creating embeddings.</summary>
private OpenAIClient GetAIClient() => !string.IsNullOrWhiteSpace(_aiKey) ?
!string.IsNullOrWhiteSpace(_aiEndpoint) ?
new OpenAIClient(new Uri(_aiEndpoint), new AzureKeyCredential(_aiKey)) :
new OpenAIClient(_aiKey) :
throw new InvalidOperationException("AI API key not configured");
}
2 changes: 1 addition & 1 deletion src/Catalog.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
}
//"AI": {
// "OpenAI": {
// "APIKey": "",
// "EmbeddingName": ""
// }
//}
}
6 changes: 0 additions & 6 deletions src/WebApp/AIOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ public class AIOptions

public class OpenAIOptions
{
/// <summary>OpenAI API key for accessing embedding LLM.</summary>
public string? ApiKey { get; set; }

/// <summary>Optional endpoint for which OpenAI API to access.</summary>
public string? Endpoint { get; set; }

/// <summary>The name of the chat model to use.</summary>
/// <remarks>When using Azure OpenAI, this should be the "Deployment name" of the chat model.</remarks>
public string ChatModel { get; set; } = "gpt-3.5-turbo-16k";
Expand Down
23 changes: 12 additions & 11 deletions src/WebApp/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
using eShop.WebApp;
using System;
using Azure.AI.OpenAI;
using eShop.WebApp;
using eShop.WebAppComponents.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.TextGeneration;

public static class Extensions
{
Expand Down Expand Up @@ -91,17 +97,12 @@ public static void AddAuthenticationServices(this IHostApplicationBuilder builde
private static void AddAIServices(this IHostApplicationBuilder builder)
{
var openAIOptions = builder.Configuration.GetSection("AI").Get<AIOptions>()?.OpenAI;
if (!string.IsNullOrWhiteSpace(openAIOptions?.ApiKey))
var deploymentName = openAIOptions?.ChatModel;

if (!string.IsNullOrWhiteSpace(builder.Configuration.GetConnectionString("openai")) && !string.IsNullOrWhiteSpace(deploymentName))
{
var kernelBuilder = builder.Services.AddKernel();
if (!string.IsNullOrWhiteSpace(openAIOptions.Endpoint))
{
kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatModel, openAIOptions.Endpoint, openAIOptions.ApiKey);
}
else
{
kernelBuilder.AddOpenAIChatCompletion(openAIOptions.ChatModel, openAIOptions.ApiKey);
}
builder.AddAzureOpenAI("openai");
builder.Services.AddAzureOpenAIChatCompletion(deploymentName);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/WebApp/WebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Aspire.Azure.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery.Yarp" />
<PackageReference Include="Microsoft.SemanticKernel" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
Expand Down
13 changes: 6 additions & 7 deletions src/WebApp/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
"EventBus": {
"SubscriptionClientName": "Ordering.webapp"
},
"SessionCookieLifetimeMinutes": 60
//"AI": {
// "OpenAI": {
// "APIKey": "",
// "ChatModel": "",
// }
//}
"SessionCookieLifetimeMinutes": 60,
"AI": {
"OpenAI": {
//"ChatModel": ""
}
}
}
6 changes: 5 additions & 1 deletion src/eShop.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
var orderDb = postgres.AddDatabase("orderingdb");
var webhooksDb = postgres.AddDatabase("webhooksdb");

var openAi = builder.AddAzureOpenAI("openai");

// Services
var identityApi = builder.AddProject<Projects.Identity_API>("identity-api")
.WithReference(identityDb)
Expand All @@ -32,7 +34,8 @@

var catalogApi = builder.AddProject<Projects.Catalog_API>("catalog-api")
.WithReference(rabbitMq)
.WithReference(catalogDb);
.WithReference(catalogDb)
.WithReference(openAi, optional: true);

var orderingApi = builder.AddProject<Projects.Ordering_API>("ordering-api")
.WithReference(rabbitMq)
Expand Down Expand Up @@ -66,6 +69,7 @@
.WithReference(catalogApi)
.WithReference(orderingApi)
.WithReference(rabbitMq)
.WithReference(openAi, optional: true)
.WithEnvironment("IdentityUrl", idpHttps)
.WithLaunchProfile("https");

Expand Down
3 changes: 3 additions & 0 deletions src/eShop.AppHost/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
},
"ConnectionStrings": {
//"OpenAi": "Endpoint=xxxx;Key=xxxx"
}
}
1 change: 1 addition & 0 deletions src/eShop.AppHost/eShop.AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
<PackageReference Include="Aspire.Hosting.Azure" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit 857fc5d

Please sign in to comment.