diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/AIChat.AOAI.Api.csproj b/samples/AIChat.AOAI/AIChat.AOAI.Api/AIChat.AOAI.Api.csproj new file mode 100644 index 00000000..cdd199ab --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/AIChat.AOAI.Api.csproj @@ -0,0 +1,21 @@ + + + net8.0 + enable + true + preview + $(NoWarn);CA2007;IDE1006;SKEXP0001;SKEXP0110;SKEXP0050;OPENAI001;SKEXP0010 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/Controllers/Generated/ChatController.cs b/samples/AIChat.AOAI/AIChat.AOAI.Api/Controllers/Generated/ChatController.cs new file mode 100644 index 00000000..e85cf09e --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/Controllers/Generated/ChatController.cs @@ -0,0 +1,47 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Api.Controllers; + +/// +/// Provides the Web API functionality. +/// +[Consumes(System.Net.Mime.MediaTypeNames.Application.Json)] +[Produces(System.Net.Mime.MediaTypeNames.Application.Json)] +public partial class ChatController : ControllerBase +{ + private readonly WebApi _webApi; + private readonly IChatManager _manager; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + public ChatController(WebApi webApi, IChatManager manager) + { _webApi = webApi.ThrowIfNull(); _manager = manager.ThrowIfNull(); ChatControllerCtor(); } + + partial void ChatControllerCtor(); // Enables additional functionality to be added to the constructor. + + /// + /// Creates a new Chat. + /// + /// The created Chat. + [HttpPost("chats", Name="Chat_Create")] + [AcceptsBody(typeof(Common.Entities.Chat))] + [ProducesResponseType(typeof(Common.Entities.Chat), (int)HttpStatusCode.Created)] + public Task Create() + => _webApi.PostWithResultAsync(Request, p => _manager.CreateAsync(p.Value!), statusCode: HttpStatusCode.Created); + + /// + /// Chat Completion. + /// + /// The Message. + /// A resultant ChatCompletionResponse. + [HttpPost("chats/completion", Name="Chat_ChatCompletion")] + [ProducesResponseType(typeof(Common.Entities.ChatCompletionResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public Task ChatCompletion(string? message) + => _webApi.PostWithResultAsync(Request, p => _manager.ChatCompletionAsync(message), alternateStatusCode: HttpStatusCode.NoContent, operationType: CoreEx.OperationType.Unspecified); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/Controllers/Generated/ReferenceDataController.cs b/samples/AIChat.AOAI/AIChat.AOAI.Api/Controllers/Generated/ReferenceDataController.cs new file mode 100644 index 00000000..f0a83ce7 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/Controllers/Generated/ReferenceDataController.cs @@ -0,0 +1,45 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +using CommonRefDataNamespace = AIChat.AOAI.Common.Entities; + +namespace AIChat.AOAI.Api.Controllers; + +/// +/// Provides the ReferenceData Web API functionality. +/// +[Consumes(System.Net.Mime.MediaTypeNames.Application.Json)] +[Produces(System.Net.Mime.MediaTypeNames.Application.Json)] +public partial class ReferenceDataController : ControllerBase +{ + private readonly ReferenceDataContentWebApi _webApi; + private readonly ReferenceDataOrchestrator _orchestrator; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + public ReferenceDataController(ReferenceDataContentWebApi webApi, ReferenceDataOrchestrator orchestrator) + { _webApi = webApi.ThrowIfNull(); _orchestrator = orchestrator.ThrowIfNull(); } + + /// + /// Gets all of the 'Role' reference data items that match the specified criteria. + /// + /// The reference data code list. + /// The reference data text (including wildcards). + /// The 'Role' array. + [HttpGet("ref/roles", Name="ReferenceData_RoleGetAll")] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] + public Task RoleGetAll([FromQuery] IEnumerable? codes = default, string? text = default) + => _webApi.GetAsync(Request, p => _orchestrator.GetWithFilterAsync(codes, text, p.RequestOptions.IncludeInactive)); + + /// + /// Gets the reference data entries for the specified entities and codes from the query string; e.g: ref?entity=codeX,codeY&entity2=codeZ&entity3 + /// + /// The 'ReferenceDataMultiDictionary'. + [HttpGet("ref", Name="ReferenceData_GetNamed")] + [ProducesResponseType(typeof(CoreEx.RefData.ReferenceDataMultiDictionary), (int)HttpStatusCode.OK)] + public Task GetNamed() => _webApi.GetAsync(Request, p => _orchestrator.GetNamedAsync(p.RequestOptions)); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/GlobalUsings.cs b/samples/AIChat.AOAI/AIChat.AOAI.Api/GlobalUsings.cs new file mode 100644 index 00000000..473a1b50 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/GlobalUsings.cs @@ -0,0 +1,23 @@ +global using Azure.Monitor.OpenTelemetry.AspNetCore; +global using CoreEx; +global using CoreEx.AspNetCore.WebApis; +global using CoreEx.AspNetCore.HealthChecks; +global using CoreEx.Entities; +global using CoreEx.Events; +global using CoreEx.Http; +global using CoreEx.RefData; +global using CoreEx.Validation; +global using Microsoft.AspNetCore.Diagnostics.HealthChecks; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.Extensions.Caching.Memory; +global using Microsoft.OpenApi.Models; +global using OpenTelemetry.Trace; +global using System.Net; +global using System.Reflection; +global using AIChat.AOAI.Api; +global using AIChat.AOAI.Business; +global using AIChat.AOAI.Business.Data; +global using AIChat.AOAI.Business.Entities; +//global using AIChat.AOAI.Business.Validation; +global using RefDataNamespace = AIChat.AOAI.Business.Entities; +global using AzCosmos = Microsoft.Azure.Cosmos; diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/Program.cs b/samples/AIChat.AOAI/AIChat.AOAI.Api/Program.cs new file mode 100644 index 00000000..88e824d4 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/Program.cs @@ -0,0 +1,27 @@ +using Microsoft.SemanticKernel; + +var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddEnvironmentVariables("AOAI_"); + +var startup = new Startup(); +startup.ConfigureServices(builder.Services, builder.Environment); + +// Add the semantic kernel +builder.Services.AddKernel() + .AddAzureOpenAIChatCompletion( + builder.Configuration["AzureOpenAI::ModelName"] ?? string.Empty, + builder.Configuration["AzureOpenAI::Endpoint"] ?? string.Empty, + builder.Configuration["AzureOpenAI::APIKey"] ?? string.Empty + ); + +builder.Services.AddTransient((serviceProvider) => { + return new Kernel(serviceProvider); +}); + +if (!builder.Environment.IsDevelopment()) + builder.Services.AddOpenTelemetry().UseAzureMonitor().WithTracing(b => b.AddSource("CoreEx.*", "AIChat.AOAI.*")); + +var app = builder.Build(); +startup.Configure(app); + +app.Run(); \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/Startup.cs b/samples/AIChat.AOAI/AIChat.AOAI.Api/Startup.cs new file mode 100644 index 00000000..06e02455 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/Startup.cs @@ -0,0 +1,110 @@ +using Microsoft.SemanticKernel; + +namespace AIChat.AOAI.Api; + +/// +/// Represents the startup class. +/// +public class Startup +{ + /// + /// The configure services method called by the runtime; use this method to add services to the container. + /// + /// The . + public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env) + { + // Add the core services. + services.AddSettings() + .AddExecutionContext() + .AddJsonSerializer() + .AddReferenceDataOrchestrator() + .AddReferenceDataContentWebApi() + .AddWebApi() + .AddJsonMergePatch() + .AddRequestCache() + .AddValidationTextProvider() + .AddValidators() + .AddMappers() + .AddSingleton(); + + // Add the cosmos database. + services.AddSingleton(sp => + { + var settings = sp.GetRequiredService(); + var cco = new AzCosmos.CosmosClientOptions { SerializerOptions = new AzCosmos.CosmosSerializationOptions { PropertyNamingPolicy = AzCosmos.CosmosPropertyNamingPolicy.CamelCase, IgnoreNullValues = true } }; + return new AzCosmos.CosmosClient(settings.CosmosConnectionString, cco); + }).AddCosmosDb(sp => + { + var settings = sp.GetRequiredService(); + return new AOAICosmosDb(sp.GetRequiredService().GetDatabase(settings.CosmosDatabaseId), sp.GetRequiredService()); + }); + + // Add the generated reference data services. + services.AddGeneratedReferenceDataManagerServices() + .AddGeneratedReferenceDataDataSvcServices() + .AddGeneratedReferenceDataDataServices(); + + // Add the generated entity services. + services.AddGeneratedManagerServices() + .AddGeneratedDataSvcServices() + .AddGeneratedDataServices(); + + // Add the event publishing; this will need to be updated from the null publisher to the actual as appropriate. + services.AddEventDataFormatter() + .AddCloudEventSerializer() + .AddNullEventPublisher(); + + // Add controllers. + services.AddControllers(); + + // Add health checks. + services.AddHealthChecks(); + + // Add Azure monitor open telemetry. + if (!env.IsDevelopment()) + services.AddOpenTelemetry().UseAzureMonitor().WithTracing(b => b.AddSource("CoreEx.*", "AIChat.AOAI.*", "Microsoft.EntityFrameworkCore.*", "EntityFrameworkCore.*")); + + // Add the swagger capabilities. + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "AIChat.AOAI API", Version = "v1" }); + options.OperationFilter(); // Needed to support AcceptsBodyAttribute where body parameter not explicitly defined. + options.OperationFilter(); // Needed to support PagingAttribute where PagingArgs parameter not explicitly defined. + options.OperationFilter(); // Needed to support QueryAttribute where QueryArgs parameter not explicitly defined. + }); + } + + /// + /// The configure method called by the runtime; use this method to configure the HTTP request pipeline. + /// + /// The . + public void Configure(IApplicationBuilder app) + { + // Handle any unhandled exceptions. + app.UseWebApiExceptionHandler(); + + // Authenticate the user. + //app.UseAuthentication(); + + // Add Swagger as an endpoint and to serve the swagger-ui to the pipeline. + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.RoutePrefix = ""; // Default as the root/home page. + c.SwaggerEndpoint("/swagger/v1/swagger.json", "AIChat.AOAI"); + }); + + // Add execution context set up to the pipeline. + app.UseExecutionContext(); + app.UseReferenceDataOrchestrator(); + + // Add health checks. + app.UseHealthChecks("/health"); + app.UseHealthChecks("/health/detailed", new HealthCheckOptions { ResponseWriter = HealthReportStatusWriter.WriteJsonResults }); // Secure with permissions / or remove given data returned. + + // Use controllers. + app.UseRouting(); + //app.UseAuthorization(); + app.UseEndpoints(endpoints => endpoints.MapControllers()); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Api/appsettings.json b/samples/AIChat.AOAI/AIChat.AOAI.Api/appsettings.json new file mode 100644 index 00000000..db156771 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Api/appsettings.json @@ -0,0 +1,27 @@ +{ + "AllowedHosts": "*", + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + // Set using environment variables: 'AOAI_CosmosConnectionString' and 'AOAI_CosmosDatabaseId'. + "CosmosConnectionString": "AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==;AccountEndpoint=https://localhost:8081", + "CosmosDatabaseId": "Chats", + "Invokers": { + "Default": { + "TracingEnabled": true, + "LoggingEnabled": true + }, + "CoreEx.Validation.ValidationInvoker": { + "TracingEnabled": false, + "LoggingEnabled": false + } + }, + "AzureOpenAI:": { + "ModelName": "", + "Endpoint": "https://.openai.azure.com/", + "APIKey": "" + }, + "IncludeExceptionInResult": true +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/AIChat.AOAI.Business.csproj b/samples/AIChat.AOAI/AIChat.AOAI.Business/AIChat.AOAI.Business.csproj new file mode 100644 index 00000000..4128a037 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/AIChat.AOAI.Business.csproj @@ -0,0 +1,19 @@ + + + net8.0 + enable + enable + preview + $(NoWarn);CA2007;IDE1006;SKEXP0001;SKEXP0110;SKEXP0050;OPENAI001;SKEXP0010 + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/AOAISettings.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/AOAISettings.cs new file mode 100644 index 00000000..16c891e0 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/AOAISettings.cs @@ -0,0 +1,19 @@ +namespace AIChat.AOAI.Business; + +/// +/// Provides the settings. +/// +/// The . +public class AOAISettings(IConfiguration configuration) : SettingsBase(configuration, ["AOAI/", "Common/"]) +{ + + /// + /// Gets the CosmosDB connection string. + /// + public string CosmosConnectionString => GetRequiredValue(); + + /// + /// Gtes the CosmosDB database identifier. + /// + public string CosmosDatabaseId => GetRequiredValue(); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/AOAICosmosDb.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/AOAICosmosDb.cs new file mode 100644 index 00000000..833c2fc4 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/AOAICosmosDb.cs @@ -0,0 +1,33 @@ +using Microsoft.Azure.Cosmos; + +namespace AIChat.AOAI.Business.Data; + +/// +/// Provides the AIChat.AOAI CosmosDb client. +/// +/// The . +/// The . +public class AOAICosmosDb : CosmosDb +{ + /// + /// Gets the Person container identifier. + /// + public const string ChatContainerId = "Chats"; + + private readonly Lazy> _chats; + + /// + /// Initializes a new instance of the class. + /// + /// The CosmosDb . + /// The . + public AOAICosmosDb(Database database, IMapper mapper) : base(database, mapper) + { + _chats = new(() => Container(ChatContainerId)); + } + + /// + /// Exposes entity from the container. + /// + public CosmosDbContainer Chats => _chats.Value; +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/ChatData.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/ChatData.cs new file mode 100644 index 00000000..2dbe1448 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/ChatData.cs @@ -0,0 +1,47 @@ +using Microsoft.Azure.Cosmos; + +namespace AIChat.AOAI.Business.Data +{ + public partial class ChatData + { + partial void ChatDataCtor() + { + } + + /// + /// Performs the query filtering + /// + /// + /// + private async Task> GetChatCompletionsOnImplementationAsync(int completions) + { + try + { + string query = """ + SELECT TOP @completions * + FROM c + ORDER BY c._ts DESC + """; + + var queryDef = new QueryDefinition(query) + .WithParameter("@completions", completions); + using FeedIterator resultSet = _cosmos.Chats.Container.GetItemQueryIterator(queryDef); + List chats = []; + while (resultSet.HasMoreResults) + { + FeedResponse response = await resultSet.ReadNextAsync(); + chats.AddRange(response); + } + + var result = new ChatCollectionResult(chats); + + return Result.Ok(result); + } + catch (Exception ex) + { + return Result.Fail(ex.Message); + } + + } + } +} diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ChatData.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ChatData.cs new file mode 100644 index 00000000..958042a7 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ChatData.cs @@ -0,0 +1,71 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Data; + +/// +/// Provides the data access. +/// +public partial class ChatData : IChatData +{ + private readonly AOAICosmosDb _cosmos; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public ChatData(AOAICosmosDb cosmos) + { _cosmos = cosmos.ThrowIfNull(); ChatDataCtor(); } + + partial void ChatDataCtor(); // Enables additional functionality to be added to the constructor. + + /// + public Task> CreateAsync(Chat value) + => _cosmos.Chats.CreateWithResultAsync(value); + + /// + public Task> GetChatCompletionsAsync(int completions) => GetChatCompletionsOnImplementationAsync(completions); + + /// + /// Provides the to Cosmos mapping. + /// + public partial class EntityToModelCosmosMapper : Mapper + { + /// + /// Initializes a new instance of the class. + /// + public EntityToModelCosmosMapper() + { + Map((s, d) => d.Id = TypeToStringConverter.Default.ConvertToDestination(s.Id), OperationTypes.Any, s => s.Id == default, d => d.Id = default); + Map((s, d) => d.Role = s.RoleSid, OperationTypes.Any, s => s.RoleSid == default, d => d.Role = default); + Map((s, d) => d.Prompt = s.Prompt, OperationTypes.Any, s => s.Prompt == default, d => d.Prompt = default); + Map((s, d) => d.ETag = s.ETag, OperationTypes.Any, s => s.ETag == default, d => d.ETag = default); + Map((o, s, d) => d.ChangeLog = o.Map(s.ChangeLog, d.ChangeLog), OperationTypes.Any, s => s.ChangeLog == default, d => d.ChangeLog = default); + EntityToModelCosmosMapperCtor(); + } + + partial void EntityToModelCosmosMapperCtor(); // Enables the constructor to be extended. + } + + /// + /// Provides the Cosmos to mapping. + /// + public partial class ModelToEntityCosmosMapper : Mapper + { + /// + /// Initializes a new instance of the class. + /// + public ModelToEntityCosmosMapper() + { + Map((s, d) => d.Id = (Guid)TypeToStringConverter.Default.ConvertToSource(s.Id!), OperationTypes.Any, s => s.Id == default, d => d.Id = default); + Map((s, d) => d.RoleSid = (string?)s.Role!, OperationTypes.Any, s => s.Role == default, d => d.RoleSid = default); + Map((s, d) => d.Prompt = (string?)s.Prompt!, OperationTypes.Any, s => s.Prompt == default, d => d.Prompt = default); + Map((s, d) => d.ETag = (string?)s.ETag!, OperationTypes.Any, s => s.ETag == default, d => d.ETag = default); + Map((o, s, d) => d.ChangeLog = o.Map(s.ChangeLog, d.ChangeLog), OperationTypes.Any, s => s.ChangeLog == default, d => d.ChangeLog = default); + ModelToEntityCosmosMapperCtor(); + } + + partial void ModelToEntityCosmosMapperCtor(); // Enables the constructor to be extended. + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/IChatData.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/IChatData.cs new file mode 100644 index 00000000..c5d14208 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/IChatData.cs @@ -0,0 +1,25 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Data; + +/// +/// Defines the data access. +/// +public partial interface IChatData +{ + /// + /// Creates a new . + /// + /// The . + /// The created . + Task> CreateAsync(Chat value); + + /// + /// Get Chat Completions. + /// + /// The Completions. + /// A resultant . + Task> GetChatCompletionsAsync(int completions); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/IReferenceDataData.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/IReferenceDataData.cs new file mode 100644 index 00000000..bc5b01b2 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/IReferenceDataData.cs @@ -0,0 +1,17 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Data; + +/// +/// Provides the ReferenceData data access. +/// +public partial interface IReferenceDataData +{ + /// + /// Gets all the items. + /// + /// The . + Task> RoleGetAllAsync(); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ReferenceDataData.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ReferenceDataData.cs new file mode 100644 index 00000000..f4e796cc --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ReferenceDataData.cs @@ -0,0 +1,70 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Data; + +/// +/// Provides the ReferenceData data access. +/// +public partial class ReferenceDataData : IReferenceDataData +{ + private readonly AOAICosmosDb _cosmos; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public ReferenceDataData(AOAICosmosDb cosmos) + { _cosmos = cosmos.ThrowIfNull(); ReferenceDataDataCtor(); } + + partial void ReferenceDataDataCtor(); // Enables additional functionality to be added to the constructor. + + /// + public Task> RoleGetAllAsync() + => DataInvoker.Current.InvokeAsync(this, (_, ct) => _cosmos.ValueContainer("RefData").Query().SelectQueryWithResultAsync(ct)); + + /// + /// Provides the to Entity Framework mapping. + /// + public partial class RoleToModelCosmosMapper : Mapper + { + /// + /// Initializes a new instance of the class. + /// + public RoleToModelCosmosMapper() + { + Map((s, d) => d.Id = s.Id, OperationTypes.Any, s => s.Id == default, d => d.Id = default); + Map((s, d) => d.Code = s.Code, OperationTypes.Any, s => s.Code == default, d => d.Code = default); + Map((s, d) => d.Text = s.Text, OperationTypes.Any, s => s.Text == default, d => d.Text = default); + Map((s, d) => d.IsActive = s.IsActive, OperationTypes.Any, s => s.IsActive == default, d => d.IsActive = default); + Map((s, d) => d.SortOrder = s.SortOrder, OperationTypes.Any, s => s.SortOrder == default, d => d.SortOrder = default); + Map((s, d) => d.ETag = s.ETag, OperationTypes.Any, s => s.ETag == default, d => d.ETag = default); + RoleToModelCosmosMapperCtor(); + } + + partial void RoleToModelCosmosMapperCtor(); // Enables the constructor to be extended. + } + + /// + /// Provides the Entity Framework to mapping. + /// + public partial class ModelToRoleCosmosMapper : Mapper + { + /// + /// Initializes a new instance of the class. + /// + public ModelToRoleCosmosMapper() + { + Map((s, d) => d.Id = (Guid)s.Id!, OperationTypes.Any, s => s.Id == default, d => d.Id = default); + Map((s, d) => d.Code = (string?)s.Code!, OperationTypes.Any, s => s.Code == default, d => d.Code = default); + Map((s, d) => d.Text = (string?)s.Text!, OperationTypes.Any, s => s.Text == default, d => d.Text = default); + Map((s, d) => d.IsActive = (bool)s.IsActive!, OperationTypes.Any, s => s.IsActive == default, d => d.IsActive = default); + Map((s, d) => d.SortOrder = (int)s.SortOrder!, OperationTypes.Any, s => s.SortOrder == default, d => d.SortOrder = default); + Map((s, d) => d.ETag = (string?)s.ETag!, OperationTypes.Any, s => s.ETag == default, d => d.ETag = default); + ModelToRoleCosmosMapperCtor(); + } + + partial void ModelToRoleCosmosMapperCtor(); // Enables the constructor to be extended. + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ReferenceDataServiceCollectionExtensions.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ReferenceDataServiceCollectionExtensions.cs new file mode 100644 index 00000000..8ba2a130 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ReferenceDataServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides the generated Data-layer services. +/// +public static partial class ReferenceDataServiceCollectionsExtension +{ + /// + /// Adds the generated Data-layer services. + /// + /// The . + /// The . + public static IServiceCollection AddGeneratedReferenceDataDataServices(this IServiceCollection services) + { + return services.AddScoped(); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ServiceCollectionExtensions.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..b6f971b1 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Generated/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides the generated Data-layer services. +/// +public static partial class ServiceCollectionsExtension +{ + /// + /// Adds the generated Data-layer services. + /// + /// The . + /// The . + public static IServiceCollection AddGeneratedDataServices(this IServiceCollection services) + { + return services.AddScoped(); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Model/Generated/Chat.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Model/Generated/Chat.cs new file mode 100644 index 00000000..3f0e5e8e --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Model/Generated/Chat.cs @@ -0,0 +1,63 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Data.Model; + +/// +/// Represents the Chat model for data persistence model. +/// +public partial class Chat : IIdentifier, IETag, IChangeLog +{ + /// + /// Gets or sets the identifier. + /// + public string? Id { get; set; } + + /// + /// Gets or sets the Role. + /// + public string? Role { get; set; } + + /// + /// Gets or sets the Prompt. + /// + public string? Prompt { get; set; } + + /// + /// Gets or sets the Completion. + /// + public string? Completion { get; set; } + + /// + /// Gets or sets the Completion Tokens. + /// + public int CompletionTokens { get; set; } + + /// + /// Gets or sets the Prompt Tokens. + /// + public int PromptTokens { get; set; } + + /// + /// Gets or sets the Total Tokens. + /// + public int TotalTokens { get; set; } + + /// + /// Gets or sets the Model. + /// + public string? Model { get; set; } + + /// + /// Gets or sets the ETag. + /// + [Newtonsoft.Json.JsonProperty("_etag")] + [JsonPropertyName("_etag")] + public string? ETag { get; set; } + + /// + /// Gets or sets the Change Log. + /// + public ChangeLog? ChangeLog { get; set; } +} diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Model/Generated/Role.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Model/Generated/Role.cs new file mode 100644 index 00000000..25dafb46 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Data/Model/Generated/Role.cs @@ -0,0 +1,15 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Data.Model; + +/// +/// Represents the Role model. +/// +public partial class Role : ReferenceDataBase { } + +/// +/// Represents the collection. +/// +public partial class RoleCollection : List { } diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ChatDataSvc.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ChatDataSvc.cs new file mode 100644 index 00000000..8f72f4d8 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ChatDataSvc.cs @@ -0,0 +1,37 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.DataSvc; + +/// +/// Provides the data repository services. +/// +public partial class ChatDataSvc : IChatDataSvc +{ + private readonly IChatData _data; + private readonly IEventPublisher _events; + private readonly IRequestCache _cache; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + public ChatDataSvc(IChatData data, IEventPublisher events, IRequestCache cache) + { _data = data.ThrowIfNull(); _events = events.ThrowIfNull(); _cache = cache.ThrowIfNull(); ChatDataSvcCtor(); } + + partial void ChatDataSvcCtor(); // Enables additional functionality to be added to the constructor. + + /// + public Task> CreateAsync(Chat value) => DataSvcInvoker.Current.InvokeAsync(this, (_, __) => + { + return Result.GoAsync(_data.CreateAsync(value)) + .Then(r => _events.PublishValueEvent(r, new Uri($"aichat/aoai/chat/{r.Id}", UriKind.Relative), $"AIChat.AOAI.Chat", "Created")) + .CacheSet(_cache); + }, new InvokerArgs { EventPublisher = _events }); + + /// + public Task> GetChatCompletionsAsync(int completions) => _data.GetChatCompletionsAsync(completions); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/IChatDataSvc.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/IChatDataSvc.cs new file mode 100644 index 00000000..27bf27f6 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/IChatDataSvc.cs @@ -0,0 +1,25 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.DataSvc; + +/// +/// Defines the data repository services. +/// +public partial interface IChatDataSvc +{ + /// + /// Creates a new . + /// + /// The . + /// The created . + Task> CreateAsync(Chat value); + + /// + /// Get Chat Completions. + /// + /// The Completions. + /// A resultant . + Task> GetChatCompletionsAsync(int completions); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/IReferenceDataDataSvc.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/IReferenceDataDataSvc.cs new file mode 100644 index 00000000..0a0949a3 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/IReferenceDataDataSvc.cs @@ -0,0 +1,18 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.DataSvc; + +/// +/// Provides the ReferenceData data services. +/// +public partial interface IReferenceDataDataSvc +{ + /// + /// Gets the for the specified . + /// + /// The . + /// The corresponding . + Task> GetAsync(Type type); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ReferenceDataDataSvc.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ReferenceDataDataSvc.cs new file mode 100644 index 00000000..b8c15fed --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ReferenceDataDataSvc.cs @@ -0,0 +1,28 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.DataSvc; + +/// +/// Provides the ReferenceData data services. +/// +public partial class ReferenceDataDataSvc : IReferenceDataDataSvc +{ + private readonly IReferenceDataData _data; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public ReferenceDataDataSvc(IReferenceDataData data) { _data = data ?? throw new ArgumentNullException(nameof(data)); ReferenceDataDataSvcCtor(); } + + partial void ReferenceDataDataSvcCtor(); // Enables the ReferenceDataDataSvc constructor to be extended. + + /// + public Task> GetAsync(Type type) => type switch + { + Type _ when type == typeof(RefDataNamespace.Role) => Result.GoAsync(_data.RoleGetAllAsync()).ThenAs(v => (IReferenceDataCollection)v), + _ => throw new InvalidOperationException($"Type {type.FullName} is not a known {nameof(IReferenceData)}.") + }; +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ReferenceDataServiceCollectionExtensions.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ReferenceDataServiceCollectionExtensions.cs new file mode 100644 index 00000000..73d0a7c1 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ReferenceDataServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides the generated DataSvc-layer services. +/// +public static partial class ReferenceDataServiceCollectionsExtension +{ + /// + /// Adds the generated DataSvc-layer services. + /// + /// The . + /// The . + public static IServiceCollection AddGeneratedReferenceDataDataSvcServices(this IServiceCollection services) + { + return services.AddScoped(); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ServiceCollectionExtensions.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..c0ff5919 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/DataSvc/Generated/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides the generated DataSvc-layer services. +/// +public static partial class ServiceCollectionsExtension +{ + /// + /// Adds the generated DataSvc-layer services. + /// + /// The . + /// The . + public static IServiceCollection AddGeneratedDataSvcServices(this IServiceCollection services) + { + return services.AddScoped(); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/Chat.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/Chat.cs new file mode 100644 index 00000000..7532089b --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/Chat.cs @@ -0,0 +1,107 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Entities; + +/// +/// Represents the Chat entity. +/// +public partial class Chat : EntityBase, IIdentifier, IETag, IChangeLogEx +{ + private Guid _id; + private string? _roleSid; + private string? _prompt; + private string? _etag; + private ChangeLogEx? _changeLog; + + /// + /// Gets or sets the identifier. + /// + public Guid Id { get => _id; set => SetValue(ref _id, value); } + + /// + /// Gets or sets the using the underlying Serialization Identifier (SID). + /// + [JsonPropertyName("role")] + public string? RoleSid { get => _roleSid; set => SetValue(ref _roleSid, value, propertyName: nameof(Role)); } + + /// + /// Gets the corresponding text (read-only where selected). + /// + public string? RoleText => RefDataNamespace.Role.GetRefDataText(_roleSid); + + /// + /// Gets or sets the Role (see ). + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [JsonIgnore] + public RefDataNamespace.Role? Role { get => _roleSid; set => SetValue(ref _roleSid, value); } + + /// + /// Gets or sets the Prompt. + /// + public string? Prompt { get => _prompt; set => SetValue(ref _prompt, value); } + + /// + /// Gets or sets the ETag. + /// + [JsonPropertyName("etag")] + public string? ETag { get => _etag; set => SetValue(ref _etag, value); } + + /// + /// Gets or sets the Change Log (see ). + /// + public ChangeLogEx? ChangeLog { get => _changeLog; set => SetValue(ref _changeLog, value); } + + /// + protected override IEnumerable GetPropertyValues() + { + yield return CreateProperty(nameof(Id), Id, v => Id = v); + yield return CreateProperty(nameof(Role), RoleSid, v => RoleSid = v); + yield return CreateProperty(nameof(Prompt), Prompt, v => Prompt = v); + yield return CreateProperty(nameof(ETag), ETag, v => ETag = v); + yield return CreateProperty(nameof(ChangeLog), ChangeLog, v => ChangeLog = v); + } +} + +/// +/// Represents the collection. +/// +public partial class ChatCollection : EntityBaseCollection +{ + /// + /// Initializes a new instance of the class. + /// + public ChatCollection() { } + + /// + /// Initializes a new instance of the class with to add. + /// + /// The items to add. + public ChatCollection(IEnumerable items) => AddRange(items); +} + +/// +/// Represents the collection result. +/// +public class ChatCollectionResult : EntityCollectionResult +{ + /// + /// Initializes a new instance of the class. + /// + public ChatCollectionResult() { } + + /// + /// Initializes a new instance of the class with . + /// + /// The . + public ChatCollectionResult(PagingArgs? paging) : base(paging) { } + + /// + /// Initializes a new instance of the class with to add. + /// + /// The items to add. + /// The optional . + public ChatCollectionResult(IEnumerable items, PagingArgs? paging = null) : base(paging) => Items.AddRange(items); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/ChatCompletionResponse.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/ChatCompletionResponse.cs new file mode 100644 index 00000000..9b395738 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/ChatCompletionResponse.cs @@ -0,0 +1,24 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Entities; + +/// +/// Represents the Completion Response entity. +/// +public partial class ChatCompletionResponse : EntityBase +{ + private string? _message; + + /// + /// Gets or sets the Message. + /// + public string? Message { get => _message; set => SetValue(ref _message, value); } + + /// + protected override IEnumerable GetPropertyValues() + { + yield return CreateProperty(nameof(Message), Message, v => Message = v); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/Role.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/Role.cs new file mode 100644 index 00000000..0bdb164c --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Entities/Generated/Role.cs @@ -0,0 +1,43 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business.Entities; + +/// +/// Represents the Role entity. +/// +public partial class Role : ReferenceDataBaseEx +{ + /// + /// An implicit cast from an to . + /// + /// The . + /// The corresponding . + public static implicit operator Role?(Guid id) => ConvertFromId(id); + + /// + /// An implicit cast from a to . + /// + /// The . + /// The corresponding . + [return: NotNullIfNotNull(nameof(code))] + public static implicit operator Role?(string? code) => ConvertFromCode(code); +} + +/// +/// Represents the collection. +/// +public partial class RoleCollection : ReferenceDataCollectionBase +{ + /// + /// Initializes a new instance of the class. + /// + public RoleCollection() { } + + /// + /// Initializes a new instance of the class with to add. + /// + /// The items to add. + public RoleCollection(IEnumerable items) => AddRange(items); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ChatManager.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ChatManager.cs new file mode 100644 index 00000000..003fe50f --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ChatManager.cs @@ -0,0 +1,41 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business; + +/// +/// Provides the business functionality. +/// +public partial class ChatManager : IChatManager +{ + private readonly IChatDataSvc _dataService; + private readonly Kernel _kernel; + private readonly IIdentifierGenerator _identifierGenerator; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + public ChatManager(IChatDataSvc dataService, Kernel kernel, IIdentifierGenerator identifierGenerator) + { _dataService = dataService.ThrowIfNull(); _kernel = kernel.ThrowIfNull(); _identifierGenerator = identifierGenerator.ThrowIfNull(); ChatManagerCtor(); } + + partial void ChatManagerCtor(); // Enables additional functionality to be added to the constructor. + + /// + public Task> CreateAsync(Chat value) => ManagerInvoker.Current.InvokeAsync(this, (_, ct) => + { + return Result.Go(value).Required() + .AdjustsAsync(async v => v.Id = await _identifierGenerator.GenerateIdentifierAsync().ConfigureAwait(false)) + .ThenAsAsync(v => _dataService.CreateAsync(v)); + }, InvokerArgs.Create); + + /// + public Task> ChatCompletionAsync(string? message) => ManagerInvoker.Current.InvokeAsync(this, (_, ct) => + { + return Result.Go() + .ThenAsAsync(() => ChatCompletionOnImplementationAsync(message)); + }, InvokerArgs.Unspecified); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/IChatManager.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/IChatManager.cs new file mode 100644 index 00000000..e4a610a7 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/IChatManager.cs @@ -0,0 +1,25 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business; + +/// +/// Defines the business functionality. +/// +public partial interface IChatManager +{ + /// + /// Creates a new . + /// + /// The . + /// The created . + Task> CreateAsync(Chat value); + + /// + /// Chat Completion. + /// + /// The Message. + /// A resultant . + Task> ChatCompletionAsync(string? message); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ReferenceDataProvider.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ReferenceDataProvider.cs new file mode 100644 index 00000000..e4baf5fe --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ReferenceDataProvider.cs @@ -0,0 +1,30 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Business; + +/// +/// Provides the implementation using the corresponding data services. +/// +public partial class ReferenceDataProvider : IReferenceDataProvider +{ + private readonly IReferenceDataDataSvc _dataService; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public ReferenceDataProvider(IReferenceDataDataSvc dataService) { _dataService = dataService.ThrowIfNull(); ReferenceDataProviderCtor(); } + + partial void ReferenceDataProviderCtor(); // Enables the ReferenceDataProvider constructor to be extended. + + /// + public Type[] Types => + [ + typeof(RefDataNamespace.Role) + ]; + + /// + public Task> GetAsync(Type type, CancellationToken cancellationToken = default) => _dataService.GetAsync(type); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ReferenceDataServiceCollectionExtensions.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ReferenceDataServiceCollectionExtensions.cs new file mode 100644 index 00000000..2fc5acb4 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ReferenceDataServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides the generated Manager-layer services. +/// +public static partial class ReferenceDataServiceCollectionsExtension +{ + /// + /// Adds the generated Manager-layer services. + /// + /// The . + /// The . + public static IServiceCollection AddGeneratedReferenceDataManagerServices(this IServiceCollection services) + => services.AddScoped(); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ServiceCollectionExtensions.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..3eefe5d9 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Generated/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides the generated Manager-layer services. +/// +public static partial class ServiceCollectionsExtension +{ + /// + /// Adds the generated Manager-layer services. + /// + /// The . + /// The . + public static IServiceCollection AddGeneratedManagerServices(this IServiceCollection services) + { + return services.AddScoped(); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/GlobalUsings.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/GlobalUsings.cs new file mode 100644 index 00000000..e11dfacb --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/GlobalUsings.cs @@ -0,0 +1,40 @@ +global using CoreEx; +global using CoreEx.Caching; +global using CoreEx.Configuration; +global using CoreEx.Cosmos; +global using CoreEx.Data.Querying; +global using CoreEx.Entities; +global using CoreEx.Entities.Extended; +global using CoreEx.Events; +global using CoreEx.Http; +global using CoreEx.Http.Extended; +global using CoreEx.Invokers; +global using CoreEx.Json; +global using CoreEx.Mapping; +global using CoreEx.Mapping.Converters; +global using CoreEx.RefData; +global using CoreEx.RefData.Extended; +global using CoreEx.Results; +global using CoreEx.Validation; +global using CoreEx.Validation.Rules; +global using AzCosmos = Microsoft.Azure.Cosmos; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.Logging; +global using System; +global using System.Collections.Generic; +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; +global using System.Linq; +global using System.Text.Json.Serialization; +global using System.Text.RegularExpressions; +global using System.Net.Http; +global using System.Threading; +global using System.Threading.Tasks; +global using AIChat.AOAI.Business; +global using AIChat.AOAI.Business.Entities; +global using AIChat.AOAI.Business.Data; +global using AIChat.AOAI.Business.DataSvc; +//global using AIChat.AOAI.Business.Validation; +global using RefDataNamespace = AIChat.AOAI.Business.Entities; +global using ExecutionContext = CoreEx.ExecutionContext; +global using Microsoft.SemanticKernel; \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Business/Manager/ChatManager.cs b/samples/AIChat.AOAI/AIChat.AOAI.Business/Manager/ChatManager.cs new file mode 100644 index 00000000..4b570783 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Business/Manager/ChatManager.cs @@ -0,0 +1,112 @@ +using Microsoft.SemanticKernel.ChatCompletion; + +namespace AIChat.AOAI.Business +{ + public partial class ChatManager + { + /// + /// Chat completion + /// + /// + /// + private async Task> ChatCompletionOnImplementationAsync(string? message) + { + ChatCompletionResponse result = new(); + try + { + var histories = await _dataService.GetChatCompletionsAsync(3); + if (histories.IsFailure) + { + histories = new(); + } + var generateMessage = await GenerateCompletion(message ?? string.Empty, histories); + + await _dataService.CreateAsync( + new() + { + RoleSid = "User", + Prompt = message, + } + ); + + await _dataService.CreateAsync( + new() + { + RoleSid = "Assistant", + Prompt = generateMessage, + } + ); + + result.Message = generateMessage; + return Result.Ok(result); + } + catch (Exception ex) + { + return Result.Fail(ex.Message); + } + } + + /// + /// Generate completion + /// + /// + /// + private async Task GenerateCompletion(string message, ChatCollectionResult? histories) + { + string systemPrompt = """ + You are an intelligent assistant.You are designed to provide helpful answers to user questions. + You are friendly, and informative and can be lighthearted. Be concise in your response, but still friendly. + """; + + // Create the completion + var chatCompletionService = _kernel.GetRequiredService(); + ChatHistory history = []; + history.Add( + new() + { + Role = AuthorRole.System, + Content = systemPrompt, + } + ); + + if (histories != null && histories.Items.Count > 0) + { + foreach (var item in histories.Items) + { + if (item == null) continue; + + history.Add( + new() + { + Role = item.Role?.Code switch + { + "User" => AuthorRole.User, + "System" => AuthorRole.System, + "Assistant" => AuthorRole.Assistant, + _ => AuthorRole.System, + }, + Items = [ + new TextContent { Text = item.Prompt } + ] + } + ); + } + } + + history.Add( + new() + { + Role = AuthorRole.User, + AuthorName = CoreEx.ExecutionContext.Current.UserName, + Items = [ + new TextContent { Text = message }, + ] + } + ); + + var response = await chatCompletionService.GetChatMessageContentAsync(chatHistory: history, kernel: _kernel); + + return response.Content ?? string.Empty; + } + } +} diff --git a/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/AIChat.AOAI.CodeGen.csproj b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/AIChat.AOAI.CodeGen.csproj new file mode 100644 index 00000000..990cef75 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/AIChat.AOAI.CodeGen.csproj @@ -0,0 +1,12 @@ + + + Exe + net8.0 + enable + enable + preview + + + + + \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/Program.cs b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/Program.cs new file mode 100644 index 00000000..f6b8aad0 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/Program.cs @@ -0,0 +1,15 @@ +namespace AIChat.AOAI.CodeGen; + +/// +/// Represents the code generation program (capability). +/// +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +public static class Program +{ + /// + /// Main startup. + /// + /// The startup arguments. + /// The status code whereby zero indicates success. + public static Task Main(string[] args) => Beef.CodeGen.CodeGenConsole.Create("AIChat", "AOAI").Supports(entity: true, refData: true, dataModel: true).RunAsync(args); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/datamodel.beef-5.yaml b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/datamodel.beef-5.yaml new file mode 100644 index 00000000..2dadc1ac --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/datamodel.beef-5.yaml @@ -0,0 +1,13 @@ +# Defines the internal model (persisted in Cosmos). +jsonSerializer: Newtonsoft +etagJsonName: _etag +entities: +- { name: Chat, text: Chat model for data persistence, + properties: [ + { name: Id, text: '{{Chat}} identifier', type: string, primaryKey: true }, + { name: Role }, + { name: Prompt }, + { name: ETag, }, + { name: ChangeLog, type: ChangeLog } + ] + } \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/entity.beef-5.yaml b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/entity.beef-5.yaml new file mode 100644 index 00000000..e9c3aa59 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/entity.beef-5.yaml @@ -0,0 +1,70 @@ +cosmosType: AOAICosmosDb +eventSubjectRoot: AIChat +eventActionFormat: PastTense +eventSourceRoot: AIChat/AOAI +eventSourceKind: Relative +webApiAutoLocation: true +autoImplement: Cosmos +refDataText: true +entities: + # The following is an example Entity with CRUD and Query operations defined accessing a Cosmos DB. + +- { + name: Chat, + collection: true, + collectionResult: true, + identifierGenerator: true, + webApiRoutePrefix: chats, + behavior: c, + cosmosContainerId: Chats, + cosmosModel: Model.Chat, + managerCtorParams: [ + "Kernel", + ], + properties: [ + { name: Id, type: Guid, primaryKey: true, dataConverter: 'TypeToStringConverter' }, + { name: Role, type: ^Role }, + { name: Prompt, type: string }, + { name: ETag, type: string, jsonDataModelName: _etag }, + { name: ChangeLog, type: ChangeLog } + ], + operations: [ + { + name: GetChatCompletions, + type: Custom, + returnType: ChatCollectionResult, + paging: true, + eventPublish: None, + excludeWebApi: true, + excludeWebApiAgent: true, + excludeIManager: true, + excludeManager: true, + parameters: [ + { name: Completions, type: int } + ] + }, + { + name: ChatCompletion, + type: Custom, + webApiRoute: completion, + returnType: ChatCompletionResponse, + eventPublish: None, + excludeIDataSvc: true, + excludeDataSvc: true, + excludeIData: true, + excludeData: true, + managerCustom: true, + parameters: [ + { name: Message, type: string } + ] + } + ] + } + +- { + name: ChatCompletionResponse, + excludeAll: true, + properties: [ + { name: Message, type: string } + ] + } diff --git a/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/refdata.beef-5.yaml b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/refdata.beef-5.yaml new file mode 100644 index 00000000..c7452b47 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.CodeGen/refdata.beef-5.yaml @@ -0,0 +1,7 @@ +webApiRoutePrefix: ref +cosmosName: AOAICosmosDb +refDataType: Guid +autoImplement: Cosmos +entities: + # The following is an example read-only reference data Entity accessing Cosmos DB. + - { name: Role, cosmosContainerId: RefData, cosmosValueContainer: true, cosmosModel: Model.Role, dataModel: true } diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/AIChat.AOAI.Common.csproj b/samples/AIChat.AOAI/AIChat.AOAI.Common/AIChat.AOAI.Common.csproj new file mode 100644 index 00000000..57b3fe3d --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/AIChat.AOAI.Common.csproj @@ -0,0 +1,11 @@ + + + netstandard2.1 + enable + Preview + true + + + + + \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/ChatAgent.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/ChatAgent.cs new file mode 100644 index 00000000..cbfa5606 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/ChatAgent.cs @@ -0,0 +1,22 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Agents; + +/// +/// Provides the HTTP agent. +/// +/// The underlying . +/// The optional . +/// The optional . +public partial class ChatAgent(HttpClient client, IJsonSerializer? jsonSerializer = null, CoreEx.ExecutionContext? executionContext = null) : TypedHttpClientBase(client, jsonSerializer, executionContext), IChatAgent +{ + /// + public Task> CreateAsync(Chat value, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + => PostAsync("chats", value, requestOptions, cancellationToken: cancellationToken); + + /// + public Task> ChatCompletionAsync(string? message, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + => PostAsync("chats/completion", requestOptions, [new HttpArg("message", message)], cancellationToken); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/IChatAgent.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/IChatAgent.cs new file mode 100644 index 00000000..081ad7b3 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/IChatAgent.cs @@ -0,0 +1,29 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Agents; + +/// +/// Defines the HTTP agent. +/// +public partial interface IChatAgent +{ + /// + /// Creates a new . + /// + /// The . + /// The optional . + /// The . + /// A . + Task> CreateAsync(Chat value, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); + + /// + /// Chat Completion. + /// + /// The Message. + /// The optional . + /// The . + /// A . + Task> ChatCompletionAsync(string? message, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/IReferenceDataAgent.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/IReferenceDataAgent.cs new file mode 100644 index 00000000..a2079266 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/IReferenceDataAgent.cs @@ -0,0 +1,30 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Agents; + +/// +/// Defines the ReferenceData HTTP agent. +/// +public partial interface IReferenceDataAgent +{ + /// + /// Gets all of the items that match the filter arguments. + /// + /// The optional arguments. + /// The optional . + /// The . + /// A . + Task> RoleGetAllAsync(ReferenceDataFilter? args = null, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); + + /// + /// Gets the reference data entries for the specified entities and codes from the query string; e.g: ref?entity=codeX,codeY&entity2=codeZ&entity3 + /// + /// The optional list of reference data names. + /// The optional . + /// The . + /// A . + /// The reference data objects will need to be manually extracted from the corresponding response content. + Task GetNamedAsync(string[] names, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/ReferenceDataAgent.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/ReferenceDataAgent.cs new file mode 100644 index 00000000..8b39e425 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Agents/Generated/ReferenceDataAgent.cs @@ -0,0 +1,28 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Agents; + +/// +/// Provides the ReferenceData HTTP agent. +/// +/// The underlying . +/// The optional . +/// The optional . +public partial class ReferenceDataAgent(HttpClient client, IJsonSerializer? jsonSerializer = null, CoreEx.ExecutionContext? executionContext = null) : TypedHttpClientBase(client, jsonSerializer, executionContext), IReferenceDataAgent +{ + /// + public Task> RoleGetAllAsync(ReferenceDataFilter? args = null, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + => GetAsync("ref/roles", requestOptions, [new HttpArg("args", args, HttpArgType.FromUriUseProperties)], cancellationToken); + + /// + public Task GetNamedAsync(string[] names, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + { + var ro = requestOptions ?? new HttpRequestOptions(); + if (names != null) + ro.UrlQueryString += string.Join("&", names); + + return GetAsync("ref", ro, null, cancellationToken); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/Chat.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/Chat.cs new file mode 100644 index 00000000..3fa91fd8 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/Chat.cs @@ -0,0 +1,71 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Entities; + +/// +/// Represents the Chat entity. +/// +public partial class Chat : IIdentifier, IETag, IChangeLog +{ + /// + /// Gets or sets the identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets the corresponding Role text (read-only where selected). + /// + public string? RoleText { get; set; } + + /// + /// Gets or sets the Role code. + /// + public string? Role { get; set; } + + /// + /// Gets or sets the Prompt. + /// + public string? Prompt { get; set; } + + /// + /// Gets or sets the ETag. + /// + [JsonPropertyName("etag")] + public string? ETag { get; set; } + + /// + /// Gets or sets the Change Log. + /// + public ChangeLog? ChangeLog { get; set; } +} + +/// +/// Represents the Chat collection. +/// +public partial class ChatCollection : List { } + +/// +/// Represents the Chat collection result. +/// +public class ChatCollectionResult : CollectionResult +{ + /// + /// Initializes a new instance of the class. + /// + public ChatCollectionResult() { } + + /// + /// Initializes a new instance of the class with . + /// + /// The . + public ChatCollectionResult(PagingArgs? paging) : base(paging) { } + + /// + /// Initializes a new instance of the class with to add. + /// + /// The items to add. + /// The . + public ChatCollectionResult(IEnumerable items, PagingArgs? paging = null) : base(paging) => Items.AddRange(items); +} diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/ChatCompletionResponse.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/ChatCompletionResponse.cs new file mode 100644 index 00000000..b7b12998 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/ChatCompletionResponse.cs @@ -0,0 +1,16 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Entities; + +/// +/// Represents the Chat Completion Response entity. +/// +public partial class ChatCompletionResponse +{ + /// + /// Gets or sets the Message. + /// + public string? Message { get; set; } +} diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/Role.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/Role.cs new file mode 100644 index 00000000..5d437836 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/Entities/Generated/Role.cs @@ -0,0 +1,15 @@ +/* + * This file is automatically generated; any changes will be lost. + */ + +namespace AIChat.AOAI.Common.Entities; + +/// +/// Represents the Role entity. +/// +public partial class Role : ReferenceDataBase { } + +/// +/// Represents the Role collection. +/// +public partial class RoleCollection : List { } diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Common/GlobalUsings.cs b/samples/AIChat.AOAI/AIChat.AOAI.Common/GlobalUsings.cs new file mode 100644 index 00000000..ee6bad63 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Common/GlobalUsings.cs @@ -0,0 +1,13 @@ +global using CoreEx.Entities; +global using CoreEx.Http; +global using CoreEx.Json; +global using CoreEx.RefData; +global using System; +global using System.Collections.Generic; +global using System.Diagnostics.CodeAnalysis; +global using System.Text.Json.Serialization; +global using System.Net.Http; +global using System.Threading; +global using System.Threading.Tasks; +global using AIChat.AOAI.Common.Entities; +global using RefDataNamespace = AIChat.AOAI.Common.Entities; \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Test/AIChat.AOAI.Test.csproj b/samples/AIChat.AOAI/AIChat.AOAI.Test/AIChat.AOAI.Test.csproj new file mode 100644 index 00000000..15838c0d --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Test/AIChat.AOAI.Test.csproj @@ -0,0 +1,48 @@ + + + + net8.0 + false + enable + enable + + + + 1701;1702;CA1707 + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Test/Apis/FixtureSetup.cs b/samples/AIChat.AOAI/AIChat.AOAI.Test/Apis/FixtureSetup.cs new file mode 100644 index 00000000..984d70c1 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Test/Apis/FixtureSetup.cs @@ -0,0 +1,57 @@ +namespace AIChat.AOAI.Test.Apis; + +[SetUpFixture] +public class FixtureSetUp +{ + [OneTimeSetUp] + public void OneTimeSetUp() + { + TestSetUp.Default.EnableExpectedEvents(); + TestSetUp.Default.ExpectNoEvents(); + + TestSetUp.Default.RegisterSetUp(async (count, _, ct) => + { + // Setup and load cosmos once only. + if (count == 0) + { + using var test = ApiTester.Create(); + using var scope = test.Services.CreateScope(); + var cosmosDb = scope.ServiceProvider.GetRequiredService(); + + // Create the Cosmos Db (where not exists). + await cosmosDb.Database.Client.CreateDatabaseIfNotExistsAsync(cosmosDb.Database.Id, cancellationToken: ct).ConfigureAwait(false); + + // Create 'Person' container. + var cdp = cosmosDb.Database.DefineContainer(cosmosDb.Chats.Container.Id, "/_partitionKey") + .WithIndexingPolicy() + .WithCompositeIndex() + .Path("/lastName", AzCosmos.CompositePathSortOrder.Ascending) + .Path("/firstName", AzCosmos.CompositePathSortOrder.Ascending) + .Attach() + .Attach() + .Build(); + + var ac = await cosmosDb.Database.ReplaceOrCreateContainerAsync(cdp, cancellationToken: ct).ConfigureAwait(false); + + // Create 'RefData' container. + var cdr = cosmosDb.Database.DefineContainer("RefData", "/_partitionKey") + .WithUniqueKey() + .Path("/type") + .Path("/value/code") + .Attach() + .Build(); + + var rdc = await cosmosDb.Database.ReplaceOrCreateContainerAsync(cdr, cancellationToken: ct).ConfigureAwait(false); + + // Import the data. + //var jdr = JsonDataReader.ParseYaml("Person.yaml"); + //await cosmosDb.Chats.ImportBatchAsync(jdr, cancellationToken: ct).ConfigureAwait(false); + + var jdr = JsonDataReader.ParseYaml("RefData.yaml", new JsonDataReaderArgs(new CoreEx.Text.Json.ReferenceDataContentJsonSerializer())); + await cosmosDb.ImportValueBatchAsync("RefData", jdr, ReferenceDataOrchestrator.GetAllTypesInNamespace(), cancellationToken: ct).ConfigureAwait(false); + } + + return true; + }); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Test/Apis/ReferenceDataTest.cs b/samples/AIChat.AOAI/AIChat.AOAI.Test/Apis/ReferenceDataTest.cs new file mode 100644 index 00000000..f3c1948c --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Test/Apis/ReferenceDataTest.cs @@ -0,0 +1,28 @@ +using AIChat.AOAI.Common.Entities; + +namespace AIChat.AOAI.Test.Apis; + +[TestFixture] +public class ReferenceDataTest : UsingApiTester +{ + [OneTimeSetUp] + public void OneTimeSetUp() => Assert.That(TestSetUp.Default.SetUp(), Is.True); + + [Test] + public void A110_Roles() + { + Agent() + .ExpectStatusCode(HttpStatusCode.OK) + .Run(a => a.RoleGetAllAsync()) + .AssertJsonFromResource("ReferenceData_A110_Genders_Response.json", "id", "etag"); + } + + [Test] + public void B110_GetNamed() + { + Agent() + .ExpectStatusCode(HttpStatusCode.OK) + .Run(a => a.GetNamedAsync(["gender"])) + .AssertJsonFromResource("ReferenceData_B110_GetNamed_Response.json", "gender.id", "gender.etag"); + } +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Test/Cosmos/RefData.yaml b/samples/AIChat.AOAI/AIChat.AOAI.Test/Cosmos/RefData.yaml new file mode 100644 index 00000000..e77608b2 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Test/Cosmos/RefData.yaml @@ -0,0 +1,5 @@ +Ref: + - Role: + - USER: User + - SYSTEM: System + - ASSITANT: Assistant \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Test/GlobalUsings.cs b/samples/AIChat.AOAI/AIChat.AOAI.Test/GlobalUsings.cs new file mode 100644 index 00000000..740ea703 --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Test/GlobalUsings.cs @@ -0,0 +1,26 @@ +global using CoreEx; +global using CoreEx.Cosmos; +global using CoreEx.Cosmos.Batch; +global using CoreEx.Entities; +global using CoreEx.Json.Data; +global using CoreEx.Http; +global using CoreEx.RefData; +global using CoreEx.Validation; +global using AzCosmos = Microsoft.Azure.Cosmos; +global using Microsoft.Extensions.DependencyInjection; +global using Moq; +global using NUnit.Framework; +global using System; +global using System.Net; +global using System.Reflection; +global using System.Threading; +global using System.Threading.Tasks; +global using UnitTestEx; +global using UnitTestEx.Expectations; +global using AIChat.AOAI.Api; +global using AIChat.AOAI.Business; +global using AIChat.AOAI.Business.Data; +global using AIChat.AOAI.Business.DataSvc; +global using AIChat.AOAI.Business.Validation; +global using AIChat.AOAI.Common.Agents; +global using HttpRequestOptions = CoreEx.Http.HttpRequestOptions; \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.Test/appsettings.unittest.json b/samples/AIChat.AOAI/AIChat.AOAI.Test/appsettings.unittest.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.Test/appsettings.unittest.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/samples/AIChat.AOAI/AIChat.AOAI.sln b/samples/AIChat.AOAI/AIChat.AOAI.sln new file mode 100644 index 00000000..7df135da --- /dev/null +++ b/samples/AIChat.AOAI/AIChat.AOAI.sln @@ -0,0 +1,106 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35825.156 d17.13 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIChat.AOAI.Api", "AIChat.AOAI.Api\AIChat.AOAI.Api.csproj", "{FBC7842F-B35C-4813-B3F3-EB4E34583EEC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIChat.AOAI.Business", "AIChat.AOAI.Business\AIChat.AOAI.Business.csproj", "{F98FFBA3-32CD-4127-B464-A9D18A545D2E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIChat.AOAI.Common", "AIChat.AOAI.Common\AIChat.AOAI.Common.csproj", "{81860842-8A59-4A45-8C15-84FA45606BD3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{53EEA045-4195-4A3F-A7C1-13A517B88080}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIChat.AOAI.Test", "AIChat.AOAI.Test\AIChat.AOAI.Test.csproj", "{27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{A4E363CD-D0BE-4B43-9C80-18EA6CD2FAA6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIChat.AOAI.CodeGen", "AIChat.AOAI.CodeGen\AIChat.AOAI.CodeGen.csproj", "{A65032E5-F3FC-4963-B135-E462190D0ABC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ソリューション項目", "ソリューション項目", "{2022CB0F-8BAF-F49E-0C48-8EF4C96EC109}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Debug|x64.ActiveCfg = Debug|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Debug|x64.Build.0 = Debug|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Debug|x86.ActiveCfg = Debug|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Debug|x86.Build.0 = Debug|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Release|Any CPU.Build.0 = Release|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Release|x64.ActiveCfg = Release|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Release|x64.Build.0 = Release|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Release|x86.ActiveCfg = Release|Any CPU + {FBC7842F-B35C-4813-B3F3-EB4E34583EEC}.Release|x86.Build.0 = Release|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Debug|x64.ActiveCfg = Debug|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Debug|x64.Build.0 = Debug|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Debug|x86.ActiveCfg = Debug|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Debug|x86.Build.0 = Debug|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Release|Any CPU.Build.0 = Release|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Release|x64.ActiveCfg = Release|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Release|x64.Build.0 = Release|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Release|x86.ActiveCfg = Release|Any CPU + {F98FFBA3-32CD-4127-B464-A9D18A545D2E}.Release|x86.Build.0 = Release|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Debug|x64.ActiveCfg = Debug|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Debug|x64.Build.0 = Debug|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Debug|x86.ActiveCfg = Debug|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Debug|x86.Build.0 = Debug|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Release|Any CPU.Build.0 = Release|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Release|x64.ActiveCfg = Release|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Release|x64.Build.0 = Release|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Release|x86.ActiveCfg = Release|Any CPU + {81860842-8A59-4A45-8C15-84FA45606BD3}.Release|x86.Build.0 = Release|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Debug|x64.ActiveCfg = Debug|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Debug|x64.Build.0 = Debug|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Debug|x86.ActiveCfg = Debug|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Debug|x86.Build.0 = Debug|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Release|Any CPU.Build.0 = Release|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Release|x64.ActiveCfg = Release|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Release|x64.Build.0 = Release|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Release|x86.ActiveCfg = Release|Any CPU + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06}.Release|x86.Build.0 = Release|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Debug|x64.ActiveCfg = Debug|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Debug|x64.Build.0 = Debug|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Debug|x86.ActiveCfg = Debug|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Debug|x86.Build.0 = Debug|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Release|Any CPU.Build.0 = Release|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Release|x64.ActiveCfg = Release|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Release|x64.Build.0 = Release|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Release|x86.ActiveCfg = Release|Any CPU + {A65032E5-F3FC-4963-B135-E462190D0ABC}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {27C4E8CA-5C89-4B72-9D21-55DEB38BCC06} = {53EEA045-4195-4A3F-A7C1-13A517B88080} + {A65032E5-F3FC-4963-B135-E462190D0ABC} = {A4E363CD-D0BE-4B43-9C80-18EA6CD2FAA6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {44AE4366-B3C4-4390-A283-8C2F5D600AC5} + EndGlobalSection +EndGlobal diff --git a/samples/AIChat.AOAI/README.md b/samples/AIChat.AOAI/README.md new file mode 100644 index 00000000..f088c9d9 --- /dev/null +++ b/samples/AIChat.AOAI/README.md @@ -0,0 +1,114 @@ +Beef x AI +--- +The purpose of the sample is demonstrate the usage of _Beef_ in a Gen AI scenario. + +Use Azure OpenAI to have conversations. + +> [!WARNING] +> The tools you use include a preview. +> Use of this code should be considered carefully. + +Prerequisites +--- +You need to deploy one of the models from Azure OpenAI Service. + +Scope +--- +| Endpoint | Description | +| -- | -- | +| POST /chats | Create a new chat history | +| POST /chats/completion | generative AI message | + +Cosmos DB usage +--- +- `RefData` - manage role. +- `Chat` - manage chat history. + +Solution skeleton +--- +``` +dotnet new beef --company AIChat --appname AOAI --datasource Cosmos +``` + +Code Generation +--- +Please copy the .yaml of this project. + +Then run the following command. +``` +dotnet run all +``` + + +Install AI tools +--- +You need to install the following AI tools. +``` +dotnet add package Microsoft.SemanticKernel --version 1.41.0 +dotnet add package Aspire.Azure.AI.OpenAI --version 9.1.0-preview.1.25121.10 +dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI --version 1.41.0 +``` + +Add `global using Microsoft.SemanticKernel;` to `GlobalUsings.cs` + + +Design +--- +The flow of this sample is as follows. +```mermaid +sequenceDiagram + participant Cosmos + participant AOAI + participant Beef + participant Client + + Client ->> Beef: Call API + Beef ->> Cosmos: Get the top 3 histories + Beef ->> Beef: Set system prompt + Beef ->> Beef: Set history messages + Beef ->> Beef: Set client message + Beef ->> AOAI: Call AOAI to generate message + Beef ->> Cosmos: Store client message and generative message + Beef ->> Client: Reply with the generated message +``` + +Startup +--- +Add the semantic kernel using Azure OpenAI Client. +```csharp +// Add the semantic kernel +builder.Services.AddKernel() + .AddAzureOpenAIChatCompletion( + builder.Configuration["AzureOpenAI::ModelName"] ?? string.Empty, + builder.Configuration["AzureOpenAI::Endpoint"] ?? string.Empty, + builder.Configuration["AzureOpenAI::APIKey"] ?? string.Empty + ); + +builder.Services.AddTransient((serviceProvider) => { + return new Kernel(serviceProvider); +}); +``` + +To use Azure OpenAI Service, add the following to appsettins.json +```json +"AzureOpenAI:": { + "ModelName": "", + "Endpoint": "https://.openai.azure.com/", + "APIKey": "" +}, +``` + +Validation +--- +ToDo + +Test +--- +ToDo + + +REFERENCES +--- +1. [MSLearn - RAG Chatbot](https://learn.microsoft.com/en-us/azure/cosmos-db/gen-ai/rag-chatbot) +2. [MSLearn - SemanticKernel](https://learn.microsoft.com/ja-jp/semantic-kernel/overview/) +3. [MSLearn - .NET Aspire](https://learn.microsoft.com/ja-jp/dotnet/aspire/) \ No newline at end of file