diff --git a/docs/release-notes/breaking-changes.md b/docs/release-notes/breaking-changes.md index 82a2146ec5..9dc26c0208 100644 --- a/docs/release-notes/breaking-changes.md +++ b/docs/release-notes/breaking-changes.md @@ -5,6 +5,20 @@ ## Starting with 0.9.1 +### Configuration changes + +The following new App Configuration settings are required: + +|Name | Default value | Description | +|--- | --- | --- | +|`FoundationaLLM:Code:CodeExecution:AzureContainerAppsDynamicSessions` | `{"DynamicSessionsEndpoints": []}` | Provides the configuration for the Azure Container Apps Dynamic Sessions code execution service. `DynamicSessionsEnpoints` is a list of Dynamic Sessions endpoints that are used to run code execution sessions. Must contain at least one value. | + +### Agent tool configuration changes + +Each agent tool should have an entry in the `properties` dictionary named `foundationallm_aca_code_execution_enabled` (`true` or `false`) to indicate whether the tool requires code execution sessions based on the the Azure Container Apps Dynamic Sessions service. + +### Prompt definition changes + Prompt prefixes and suffixes support FoundationaLLM variables for dynamic replacement at runtime. The variable format is `{{foundationallm:variable_name[:format]}}` where - `variable_name` is the name of the well-known variable. - `format` is the optional formatting applied to the value of the variable. diff --git a/src/dotnet/Common/Constants/Agents/AgentToolPropertyNames.cs b/src/dotnet/Common/Constants/Agents/AgentToolPropertyNames.cs new file mode 100644 index 0000000000..1f69f05e0d --- /dev/null +++ b/src/dotnet/Common/Constants/Agents/AgentToolPropertyNames.cs @@ -0,0 +1,23 @@ +namespace FoundationaLLM.Common.Constants.Agents +{ + /// + /// Provides well-known parameter names for agent tools. + /// + public static class AgentToolPropertyNames + { + /// + /// Indicates whether code execution is enabled or not. + /// + public const string FoundationaLLM_AzureContainerApps_CodeExecution_Enabled = "foundationallm_aca_code_execution_enabled"; + + /// + /// The session identifier required to execute code in Azure Container Apps Dynamic Sessions. + /// + public const string FoundationaLLM_AzureContainerApps_CodeExecution_SessionId = "foundationallm_aca_code_execution_session_id"; + + /// + /// The endpoint required to execute code in Azure Container Apps Dynamic Sessions. + /// + public const string FoundationaLLM_AzureContainerApps_CodeExecution_Endpoint = "foundationallm_aca_code_execution_endpoint"; + } +} diff --git a/src/dotnet/Common/Constants/Data/AppConfiguration.json b/src/dotnet/Common/Constants/Data/AppConfiguration.json index 73fa415b62..ae81b92812 100644 --- a/src/dotnet/Common/Constants/Data/AppConfiguration.json +++ b/src/dotnet/Common/Constants/Data/AppConfiguration.json @@ -90,6 +90,23 @@ } ] }, + { + "namespace": "Code:CodeExecution", + "dependency_injection_key": null, + "configuration_section": { + "description": "Configuration section used to identify settings for code execution services." + }, + "configuration_keys": [ + { + "name": "AzureContainerAppsDynamicSessions", + "description": "The settings for the Azure Container Apps Dynamic Sessions code execution service.", + "secret": "", + "value": "{\"DynamicSessionsEndpoints\": []}", + "content_type": "application/json", + "first_version": "0.9.1" + } + ] + }, { "namespace": "ResourceProviders:AIModel", "dependency_injection_key": { diff --git a/src/dotnet/Common/Interfaces/ICodeExecutionService.cs b/src/dotnet/Common/Interfaces/ICodeExecutionService.cs new file mode 100644 index 0000000000..0f2d2c7e0c --- /dev/null +++ b/src/dotnet/Common/Interfaces/ICodeExecutionService.cs @@ -0,0 +1,25 @@ +using FoundationaLLM.Common.Models.Authentication; +using FoundationaLLM.Common.Models.CodeExecution; + +namespace FoundationaLLM.Common.Interfaces +{ + /// + /// Defines the capabilities for code execution services. + /// + public interface ICodeExecutionService + { + /// + /// Creates a new code execution session. + /// + /// The unique identifier of the FoundationaLLM instance. + /// The context in which the code execution session is created. This is usually the name of the agent tool, but it is not limited to that. + /// The unique identifier of the conversation. + /// The providing the user identity information. + /// A object with the properties of the code execution session. + Task CreateCodeExecutionSession( + string instanceId, + string context, + string conversationId, + UnifiedUserIdentity userIdentity); + } +} diff --git a/src/dotnet/Common/Models/CodeExecution/CodeExecutionSession.cs b/src/dotnet/Common/Models/CodeExecution/CodeExecutionSession.cs new file mode 100644 index 0000000000..271479c501 --- /dev/null +++ b/src/dotnet/Common/Models/CodeExecution/CodeExecutionSession.cs @@ -0,0 +1,18 @@ +namespace FoundationaLLM.Common.Models.CodeExecution +{ + /// + /// Provides details about a code execution session. + /// + public class CodeExecutionSession + { + /// + /// The unique identifier for the code execution session. + /// + public required string SessionId { get; set; } + + /// + /// The endpoint used to execute the code. + /// + public required string Endpoint { get; set; } + } +} diff --git a/src/dotnet/Common/Models/Configuration/CodeExecution/AzureContainerAppsCodeExecutionServiceSettings.cs b/src/dotnet/Common/Models/Configuration/CodeExecution/AzureContainerAppsCodeExecutionServiceSettings.cs new file mode 100644 index 0000000000..d01c145bc2 --- /dev/null +++ b/src/dotnet/Common/Models/Configuration/CodeExecution/AzureContainerAppsCodeExecutionServiceSettings.cs @@ -0,0 +1,13 @@ +namespace FoundationaLLM.Common.Models.Configuration.CodeExecution +{ + /// + /// Provides settings for the Azure Container Apps code execution service. + /// + public class AzureContainerAppsCodeExecutionServiceSettings + { + /// + /// Get or sets the list of Azure Container Apps Dynamic Sessions endpoints. + /// + public List DynamicSessionsEndpoints { get; set; } = []; + } +} diff --git a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs index 55d486d859..1e8227ae8f 100644 --- a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs +++ b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; namespace FoundationaLLM.Common.Models.ResourceProviders.Agent { @@ -38,5 +39,25 @@ public class AgentTool /// [JsonPropertyName("properties")] public Dictionary Properties { get; set; } = []; + + /// + /// Tries to get the value of a property. + /// + /// The type of the property being retrieved. + /// The name of the property being retrieved. + /// The resultig property value. + /// if the property value was successfull retrieved, otherwise. + public bool TryGetPropertyValue(string propertyName, out T? propertyValue) + { + propertyValue = default; + + if (Properties.TryGetValue(propertyName, out var value)) + { + propertyValue = ((JsonElement)value).Deserialize(); + return true; + } + + return false; + } } } diff --git a/src/dotnet/Common/Services/CodeExecution/AzureContainerAppsCodeExecutionService.cs b/src/dotnet/Common/Services/CodeExecution/AzureContainerAppsCodeExecutionService.cs new file mode 100644 index 0000000000..7568b06b98 --- /dev/null +++ b/src/dotnet/Common/Services/CodeExecution/AzureContainerAppsCodeExecutionService.cs @@ -0,0 +1,64 @@ +using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Models.Authentication; +using FoundationaLLM.Common.Models.CodeExecution; +using FoundationaLLM.Common.Models.Configuration.CodeExecution; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace FoundationaLLM.Common.Services.CodeExecution +{ + /// + /// Provides a code execution service that uses Azure Container Apps Dynamic Sessions to execute code. + /// + /// The options for the Azure Container Apps code execution service. + /// The logger used for logging. + public class AzureContainerAppsCodeExecutionService( + IOptions options, + ILogger logger) : ICodeExecutionService + { + private readonly AzureContainerAppsCodeExecutionServiceSettings _settings = options.Value; + private readonly ILogger _logger = logger; + + /// + public Task CreateCodeExecutionSession( + string instanceId, + string context, + string conversationId, + UnifiedUserIdentity userIdentity) + { + string newSessionId; + + if (string.IsNullOrWhiteSpace(context)) + { + // Since the context is invalid, the session identifier will be a random GUID. + + _logger.LogWarning("An empty context was provided for creating a code execution session identifier."); + newSessionId = Guid.NewGuid().ToString().ToLower(); + } + else if (string.IsNullOrWhiteSpace(conversationId)) + { + // Since the conversation identifier is invalid, the session identifier will use a random GUID instead. + _logger.LogWarning("An empty conversation identifier was provided for creating a code execution session identifier."); + newSessionId = $"{context}-{Guid.NewGuid().ToString().ToLower()}"; + } + else + { + // The session identifier will be a combination of the context and conversation identifier. + newSessionId = $"{context}-{conversationId}"; + } + + // Ensure the session identifier is no longer than 128 characters. + if (newSessionId.Length > 128) + { + _logger.LogWarning("The generated code execution session identifier is longer than 128 characters. It will be truncated."); + newSessionId = newSessionId[..128]; + } + + return Task.FromResult(new CodeExecutionSession + { + SessionId = newSessionId, + Endpoint = _settings.DynamicSessionsEndpoints.First() + }); + } + } +} diff --git a/src/dotnet/Common/Services/CodeExecution/DependencyInjection.cs b/src/dotnet/Common/Services/CodeExecution/DependencyInjection.cs new file mode 100644 index 0000000000..dd4e2bc98e --- /dev/null +++ b/src/dotnet/Common/Services/CodeExecution/DependencyInjection.cs @@ -0,0 +1,36 @@ +using FoundationaLLM.Common.Constants.Configuration; +using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Models.Configuration.CodeExecution; +using FoundationaLLM.Common.Services.CodeExecution; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace FoundationaLLM +{ + /// + /// General purpose dependency injection extensions. + /// + public static partial class DependencyInjection + { + /// + /// Registers the implementation with the dependency injection container. + /// + /// The application builder managing the dependency injection container. + public static void AddAzureContainerAppsCodeExecutionService(this IHostApplicationBuilder builder) => + builder.Services.AddAzureContainerAppsCodeExecutionService(builder.Configuration); + + /// + /// Registers the implementation with the dependency injection container. + /// + /// The dependency injection container service collection. + /// The application configuration manager. + public static void AddAzureContainerAppsCodeExecutionService(this IServiceCollection services, IConfigurationManager configuration) + { + services.AddOptions() + .Bind(configuration.GetSection(AppConfigurationKeys.FoundationaLLM_Code_CodeExecution_AzureContainerAppsDynamicSessions)); + + services.AddSingleton(); + } + } +} diff --git a/src/dotnet/Common/Services/DependencyInjection.cs b/src/dotnet/Common/Services/DependencyInjection.cs index a85a404270..bd8b473786 100644 --- a/src/dotnet/Common/Services/DependencyInjection.cs +++ b/src/dotnet/Common/Services/DependencyInjection.cs @@ -1,5 +1,4 @@ -using Azure.Monitor.OpenTelemetry.AspNetCore; -using Azure.Monitor.OpenTelemetry.Exporter; +using Azure.Monitor.OpenTelemetry.Exporter; using FoundationaLLM.Common.Authentication; using FoundationaLLM.Common.Constants; using FoundationaLLM.Common.Constants.Authorization; @@ -10,7 +9,6 @@ using FoundationaLLM.Common.Services.API; using FoundationaLLM.Common.Services.Azure; using FoundationaLLM.Common.Services.Security; -using FoundationaLLM.Common.Services.Templates; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Fluent; @@ -298,19 +296,5 @@ public static void AddAzureCosmosDBService(this IServiceCollection services, ICo services.AddSingleton(); } - - /// - /// Registers the implementation with the dependency injection container. - /// - /// The application builder managing the dependency injection container. - public static void AddRegexTemplatingEngine(this IHostApplicationBuilder builder) => - builder.Services.AddSingleton(); - - /// - /// Registers the implementation with the dependency injection container. - /// - /// The dependency injection container service collection. - public static void AddRegexTemplatingEngine(this IServiceCollection services) => - services.AddSingleton(); } } diff --git a/src/dotnet/Common/Services/Templates/DependencyInjection.cs b/src/dotnet/Common/Services/Templates/DependencyInjection.cs new file mode 100644 index 0000000000..1722321507 --- /dev/null +++ b/src/dotnet/Common/Services/Templates/DependencyInjection.cs @@ -0,0 +1,27 @@ +using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Services.Templates; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace FoundationaLLM +{ + /// + /// General purpose dependency injection extensions. + /// + public static partial class DependencyInjection + { + /// + /// Registers the implementation with the dependency injection container. + /// + /// The application builder managing the dependency injection container. + public static void AddRegexTemplatingService(this IHostApplicationBuilder builder) => + builder.Services.AddSingleton(); + + /// + /// Registers the implementation with the dependency injection container. + /// + /// The dependency injection container service collection. + public static void AddRegexTemplatingService(this IServiceCollection services) => + services.AddSingleton(); + } +} diff --git a/src/dotnet/Common/Templates/AppConfigurationKeyFilters.cs b/src/dotnet/Common/Templates/AppConfigurationKeyFilters.cs index b4743e4578..9b0c2cc5ca 100644 --- a/src/dotnet/Common/Templates/AppConfigurationKeyFilters.cs +++ b/src/dotnet/Common/Templates/AppConfigurationKeyFilters.cs @@ -30,6 +30,12 @@ public static partial class AppConfigurationKeyFilters public const string FoundationaLLM_PythonSDK = "FoundationaLLM:PythonSDK:*"; + /// + /// Filter for the configuration section used to identify settings for code execution services. + /// + public const string FoundationaLLM_Code_CodeExecution = + "FoundationaLLM:Code:CodeExecution:*"; + /// /// Filter for the configuration section used to identify the storage settings for the FoundationaLLM.AIModel resource provider. /// diff --git a/src/dotnet/Common/Templates/AppConfigurationKeySections.cs b/src/dotnet/Common/Templates/AppConfigurationKeySections.cs index db788f57ba..4ee0cbf167 100644 --- a/src/dotnet/Common/Templates/AppConfigurationKeySections.cs +++ b/src/dotnet/Common/Templates/AppConfigurationKeySections.cs @@ -30,6 +30,12 @@ public static partial class AppConfigurationKeySections public const string FoundationaLLM_PythonSDK = "FoundationaLLM:PythonSDK"; + /// + /// Configuration section used to identify settings for code execution services. + /// + public const string FoundationaLLM_Code_CodeExecution = + "FoundationaLLM:Code:CodeExecution"; + /// /// Configuration section used to identify the storage settings for the FoundationaLLM.AIModel resource provider. /// diff --git a/src/dotnet/Common/Templates/AppConfigurationKeys.cs b/src/dotnet/Common/Templates/AppConfigurationKeys.cs index 73b2937df1..799d105f16 100644 --- a/src/dotnet/Common/Templates/AppConfigurationKeys.cs +++ b/src/dotnet/Common/Templates/AppConfigurationKeys.cs @@ -80,6 +80,17 @@ public static class AppConfigurationKeys #endregion + #region FoundationaLLM:Code:CodeExecution + + /// + /// The app configuration key for the FoundationaLLM:Code:CodeExecution:AzureContainerAppsDynamicSessions setting. + /// Value description:
The settings for the Azure Container Apps Dynamic Sessions code execution service.
+ ///
+ public const string FoundationaLLM_Code_CodeExecution_AzureContainerAppsDynamicSessions = + "FoundationaLLM:Code:CodeExecution:AzureContainerAppsDynamicSessions"; + + #endregion + #region FoundationaLLM:ResourceProviders:AIModel #endregion diff --git a/src/dotnet/Common/Templates/appconfig.template.json b/src/dotnet/Common/Templates/appconfig.template.json index 1b5da9776a..9dc8cf4e1e 100644 --- a/src/dotnet/Common/Templates/appconfig.template.json +++ b/src/dotnet/Common/Templates/appconfig.template.json @@ -63,6 +63,13 @@ "content_type": "", "tags": {} }, + { + "key": "FoundationaLLM:Code:CodeExecution:AzureContainerAppsDynamicSessions", + "value": "{"DynamicSessionsEndpoints": []}", + "label": null, + "content_type": "application/json", + "tags": {} + }, { "key": "FoundationaLLM:ResourceProviders:AIModel:Storage:AuthenticationType", "value": "AzureIdentity", diff --git a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs index f10bfb6c7a..1b1b4321fd 100644 --- a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs +++ b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs @@ -41,6 +41,7 @@ public class OrchestrationBuilder /// The that manages internal and external orchestration services. /// The used to interact with the Cosmos DB database. /// The used to render templates. + /// The used to execute code. /// The provding dependency injection services for the current scope. /// The logger factory used to create new loggers. /// @@ -55,6 +56,7 @@ public class OrchestrationBuilder ILLMOrchestrationServiceManager llmOrchestrationServiceManager, IAzureCosmosDBService cosmosDBService, ITemplatingService templatingService, + ICodeExecutionService codeExecutionService, IServiceProvider serviceProvider, ILoggerFactory loggerFactory) { @@ -67,6 +69,7 @@ public class OrchestrationBuilder originalRequest.Settings?.ModelParameters, resourceProviderServices, templatingService, + codeExecutionService, callContext.CurrentUserIdentity!, logger); @@ -176,10 +179,11 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync LoadAgent( string instanceId, string agentName, - string? sessionId, + string? conversationId, Dictionary? modelParameterOverrides, Dictionary resourceProviderServices, ITemplatingService templatingService, + ICodeExecutionService codeExecutionService, UnifiedUserIdentity currentUserIdentity, ILogger logger) { @@ -360,10 +364,33 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync toolParameters = []; + + if (tool.TryGetPropertyValue( + AgentToolPropertyNames.FoundationaLLM_AzureContainerApps_CodeExecution_Enabled, out bool requiresCodeExecution) + && requiresCodeExecution) + { + var codeExecutionSession = await codeExecutionService.CreateCodeExecutionSession( + instanceId, + tool.Name, + conversationId!, + currentUserIdentity); + + toolParameters.Add( + AgentToolPropertyNames.FoundationaLLM_AzureContainerApps_CodeExecution_Endpoint, + codeExecutionSession.Endpoint); + toolParameters.Add( + AgentToolPropertyNames.FoundationaLLM_AzureContainerApps_CodeExecution_SessionId, + codeExecutionSession.SessionId); + } + explodedObjectsManager.TryAdd( tool.Name, - tool); + toolParameters); + // Ensure all resource object identifiers are exploded. foreach (var resourceObjectId in tool.ResourceObjectIds.Values) { var resourcePath = ResourcePath.GetResourcePath(resourceObjectId.ObjectId); diff --git a/src/dotnet/Orchestration/Services/OrchestrationService.cs b/src/dotnet/Orchestration/Services/OrchestrationService.cs index a24d84adf7..c4cdf7df1a 100644 --- a/src/dotnet/Orchestration/Services/OrchestrationService.cs +++ b/src/dotnet/Orchestration/Services/OrchestrationService.cs @@ -26,6 +26,7 @@ public class OrchestrationService : IOrchestrationService private readonly ILLMOrchestrationServiceManager _llmOrchestrationServiceManager; private readonly IAzureCosmosDBService _cosmosDBService; private readonly ITemplatingService _templatingService; + private readonly ICodeExecutionService _codeExecutionService; private readonly ICallContext _callContext; private readonly IConfiguration _configuration; private readonly ILogger _logger; @@ -41,6 +42,7 @@ public class OrchestrationService : IOrchestrationService /// The managing the internal and external LLM orchestration services. /// The used to interact with the Cosmos DB database. /// The used to render templates. + /// The used to execute code. /// The call context of the request being handled. /// The used to retrieve app settings from configuration. /// The provding dependency injection services for the current scope. @@ -50,6 +52,7 @@ public OrchestrationService( ILLMOrchestrationServiceManager llmOrchestrationServiceManager, IAzureCosmosDBService cosmosDBService, ITemplatingService templatingService, + ICodeExecutionService codeExecutionService, ICallContext callContext, IConfiguration configuration, IServiceProvider serviceProvider, @@ -60,6 +63,7 @@ public OrchestrationService( _llmOrchestrationServiceManager = llmOrchestrationServiceManager; _cosmosDBService = cosmosDBService; _templatingService = templatingService; + _codeExecutionService = codeExecutionService; _callContext = callContext; _configuration = configuration; @@ -105,6 +109,7 @@ public async Task GetCompletion(string instanceId, Completio _llmOrchestrationServiceManager, _cosmosDBService, _templatingService, + _codeExecutionService, _serviceProvider, _loggerFactory) ?? throw new OrchestrationException($"The orchestration builder was not able to create an orchestration for agent [{completionRequest.AgentName ?? string.Empty}]."); @@ -145,6 +150,7 @@ public async Task StartCompletionOperation(string instance _llmOrchestrationServiceManager, _cosmosDBService, _templatingService, + _codeExecutionService, _serviceProvider, _loggerFactory) ?? throw new OrchestrationException($"The orchestration builder was not able to create an orchestration for agent [{completionRequest.AgentName ?? string.Empty}]."); @@ -232,6 +238,7 @@ private async Task GetCompletionForAgentConversation( _llmOrchestrationServiceManager, _cosmosDBService, _templatingService, + _codeExecutionService, _serviceProvider, _loggerFactory); diff --git a/src/dotnet/OrchestrationAPI/Program.cs b/src/dotnet/OrchestrationAPI/Program.cs index 47d8d015db..028c002a74 100644 --- a/src/dotnet/OrchestrationAPI/Program.cs +++ b/src/dotnet/OrchestrationAPI/Program.cs @@ -63,6 +63,8 @@ public static void Main(string[] args) options.Select(AppConfigurationKeyFilters.FoundationaLLM_APIEndpoints_CoreAPI_Configuration_CosmosDB); options.Select(AppConfigurationKeyFilters.FoundationaLLM_APIEndpoints_AzureEventGrid_Essentials); options.Select(AppConfigurationKeyFilters.FoundationaLLM_APIEndpoints_AzureEventGrid_Configuration); + options.Select(AppConfigurationKeyFilters.FoundationaLLM_Code_CodeExecution); + options.Select(AppConfigurationKeys.FoundationaLLM_Events_Profiles_OrchestrationAPI); })); if (builder.Environment.IsDevelopment()) @@ -118,7 +120,10 @@ public static void Main(string[] args) builder.AddAuthorizationServiceClient(); // Add the templating engine. - builder.AddRegexTemplatingEngine(); + builder.AddRegexTemplatingService(); + + // Add the code execution service. + builder.AddAzureContainerAppsCodeExecutionService(); //---------------------------- // Resource providers diff --git a/tests/dotnet/Orchestration.Tests/Services/OrchestrationServiceTests.cs b/tests/dotnet/Orchestration.Tests/Services/OrchestrationServiceTests.cs index c0e9a56157..351bb906a8 100644 --- a/tests/dotnet/Orchestration.Tests/Services/OrchestrationServiceTests.cs +++ b/tests/dotnet/Orchestration.Tests/Services/OrchestrationServiceTests.cs @@ -36,6 +36,7 @@ public OrchestrationServiceTests() null, _cosmosDBService, null, + null, _callContext, _configuration, null,