diff --git a/src/dotnet/Common/Models/ResourceProviders/Prompt/MultipartPrompt.cs b/src/dotnet/Common/Models/ResourceProviders/Prompt/MultipartPrompt.cs index 054b73a5cc..077b430fb1 100644 --- a/src/dotnet/Common/Models/ResourceProviders/Prompt/MultipartPrompt.cs +++ b/src/dotnet/Common/Models/ResourceProviders/Prompt/MultipartPrompt.cs @@ -20,6 +20,12 @@ public class MultipartPrompt : PromptBase [JsonPropertyName("suffix")] public string? Suffix { get; set; } + /// + /// Optional string token replacements for the prompt. + /// + [JsonPropertyName("token_replacements")] + public TokenReplacementDefinition[] TokenReplacements { get; set; } = []; + /// /// Set default property values. /// diff --git a/src/dotnet/Common/Models/ResourceProviders/Prompt/TokenReplacementDefinition.cs b/src/dotnet/Common/Models/ResourceProviders/Prompt/TokenReplacementDefinition.cs new file mode 100644 index 0000000000..41a3d4bc8c --- /dev/null +++ b/src/dotnet/Common/Models/ResourceProviders/Prompt/TokenReplacementDefinition.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace FoundationaLLM.Common.Models.ResourceProviders.Prompt +{ + /// + /// String text token replacement definition. + /// + public class TokenReplacementDefinition + { + /// + /// The token to be replaced. + /// + [JsonPropertyName("token")] + public required string Token { get; set; } + + /// + /// The code to compute the replacement value. + /// + [JsonPropertyName("compute_code")] + public required string ComputeCode { get; set; } + } +} diff --git a/src/dotnet/Orchestration/Orchestration.csproj b/src/dotnet/Orchestration/Orchestration.csproj index 97863d6cb9..f304b20efc 100644 --- a/src/dotnet/Orchestration/Orchestration.csproj +++ b/src/dotnet/Orchestration/Orchestration.csproj @@ -16,6 +16,7 @@ + diff --git a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs index 26e2333a0a..543fe08d31 100644 --- a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs +++ b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs @@ -16,6 +16,7 @@ using FoundationaLLM.Common.Models.ResourceProviders.Prompt; using FoundationaLLM.Common.Models.ResourceProviders.Vectorization; using FoundationaLLM.Orchestration.Core.Interfaces; +using FoundationaLLM.Orchestration.Core.Services.TokenReplacement; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -204,7 +205,7 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync( resourceObjectId.ObjectId, currentUserIdentity); + + if(retrievedPrompt is MultipartPrompt multipartPrompt) + { + //check for token replacements, multipartPrompt variable has the same reference as retrievedPrompt therefore this edits the prefix/suffix in place + if (multipartPrompt is not null && multipartPrompt.TokenReplacements != null && multipartPrompt.TokenReplacements.Length > 0) + { + var tokenReplacementEngine = new TokenReplacementEngine(multipartPrompt.TokenReplacements.ToList()); + multipartPrompt.Prefix = await tokenReplacementEngine.ReplaceTokensAsync(multipartPrompt.Prefix!); + multipartPrompt.Suffix = await tokenReplacementEngine.ReplaceTokensAsync(multipartPrompt.Suffix!); + } + } + explodedObjectsManager.TryAdd( retrievedPrompt.ObjectId!, retrievedPrompt); diff --git a/src/dotnet/Orchestration/Services/TokenReplacement/TokenReplacementEngine.cs b/src/dotnet/Orchestration/Services/TokenReplacement/TokenReplacementEngine.cs new file mode 100644 index 0000000000..b1c82a71ad --- /dev/null +++ b/src/dotnet/Orchestration/Services/TokenReplacement/TokenReplacementEngine.cs @@ -0,0 +1,68 @@ +using System.Text.RegularExpressions; +using FoundationaLLM.Common.Models.ResourceProviders.Prompt; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; + +namespace FoundationaLLM.Orchestration.Core.Services.TokenReplacement +{ + /// + /// Token replacement engine, responsible for replacing tokens in a string with their computed values. + /// + public class TokenReplacementEngine + { + private readonly List _tokenReplacements; + private readonly ScriptOptions _scriptOptions; + + /// + /// Creates an instance of the class. + /// + /// + public TokenReplacementEngine(List tokenReplacements) + { + _tokenReplacements = tokenReplacements; + + // Define script options, such as referencing necessary assemblies and namespaces + _scriptOptions = ScriptOptions.Default + .AddImports("System"); + } + + /// + /// Replaces tokens in the input string with their corresponding computed values. + /// + /// The input string containing tokens to be replaced. + /// A task that represents the asynchronous operation. The task result contains the string with tokens replaced. + public async Task ReplaceTokensAsync(string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + return input; + } + string pattern = @"{{\s*(\w+)\s*}}"; + return await Task.Run(() => Regex.Replace(input, pattern, match => + { + string tokenName = match.Groups[1].Value; + var tokenReplacement = _tokenReplacements.Find(tr => tr.Token == $"{{{{{tokenName}}}}}"); + + if (tokenReplacement != null) + { + try + { + // Evaluate the compute code + var result = CSharpScript.EvaluateAsync( + tokenReplacement.ComputeCode, + _scriptOptions).Result; + return result; + } + catch (Exception ex) + { + // Handle errors in compute code + return $"[Error: {ex.Message}]"; + } + } + + // If token not found, return it unchanged or handle as needed + return match.Value; + }, RegexOptions.IgnoreCase)); + } + } +}