Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Prompt token replacement alternative solution #2073

Merged
merged 2 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/release-notes/breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
> [!NOTE]
> This section is for changes that are not yet released but will affect future releases.

## Starting with 0.9.1

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.

The following variables are supported:

| Name | Value | Example
| --- | --- | --- |
| `current_datetime_utc` | The current UTC date and time. | `The current date is {{foundationallm:current_datetime_utc:dddd, MMMM dd, yyyy}}. This looks great.` -> `The current date is Sunday, December 15, 2024. This looks great.`

## Starting with 0.9.0

### Configuration changes
Expand Down
13 changes: 13 additions & 0 deletions src/dotnet/Common/Constants/Templates/TemplateVariables.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace FoundationaLLM.Common.Constants.Templates
{
/// <summary>
/// Provides template variables that can be used in templates.
/// </summary>
public static class TemplateVariables
{
/// <summary>
/// Token for current date in UTC format.
/// </summary>
public const string CurrentDateTimeUTC = "current_datetime_utc";
}
}
15 changes: 15 additions & 0 deletions src/dotnet/Common/Interfaces/ITemplatingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace FoundationaLLM.Common.Interfaces
{
/// <summary>
/// Defines the interface for a templating engine.
/// </summary>
public interface ITemplatingService
{
/// <summary>
/// Transforms the input string by replacing tokens with the corresponding values.
/// </summary>
/// <param name="s">The input string to be transformed.</param>
/// <returns>The transformed string where all the valid tokens have been replaced.</returns>
string Transform(string s);
}
}
15 changes: 15 additions & 0 deletions src/dotnet/Common/Services/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;
Expand Down Expand Up @@ -151,7 +152,7 @@
policyBuilder.RequireAuthenticatedUser();
if (requireScopes)
{
var requiredScopes = builder.Configuration[entraScopesConfigurationKey]?.Split(' ');

Check warning on line 155 in src/dotnet/Common/Services/DependencyInjection.cs

View workflow job for this annotation

GitHub Actions / build (CoreWorker)

Possible null reference argument for parameter 'key' in 'string? IConfiguration.this[string key]'.

Check warning on line 155 in src/dotnet/Common/Services/DependencyInjection.cs

View workflow job for this annotation

GitHub Actions / build (SemanticKernelAPI)

Possible null reference argument for parameter 'key' in 'string? IConfiguration.this[string key]'.

Check warning on line 155 in src/dotnet/Common/Services/DependencyInjection.cs

View workflow job for this annotation

GitHub Actions / build (CoreAPI)

Possible null reference argument for parameter 'key' in 'string? IConfiguration.this[string key]'.

Check warning on line 155 in src/dotnet/Common/Services/DependencyInjection.cs

View workflow job for this annotation

GitHub Actions / build (GatekeeperAPI)

Possible null reference argument for parameter 'key' in 'string? IConfiguration.this[string key]'.

Check warning on line 155 in src/dotnet/Common/Services/DependencyInjection.cs

View workflow job for this annotation

GitHub Actions / build (OrchestrationAPI)

Possible null reference argument for parameter 'key' in 'string? IConfiguration.this[string key]'.
if (requiredScopes != null && requiredScopes.Length > 0)
{
policyBuilder.RequireClaim(ClaimConstants.Scope, requiredScopes);
Expand Down Expand Up @@ -297,5 +298,19 @@

services.AddSingleton<IAzureCosmosDBService, AzureCosmosDBService>();
}

/// <summary>
/// Registers the <see cref="ITemplatingService"/> implementation with the dependency injection container.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> application builder managing the dependency injection container.</param>
public static void AddRegexTemplatingEngine(this IHostApplicationBuilder builder) =>
builder.Services.AddSingleton<ITemplatingService, RegexTemplatingService>();

/// <summary>
/// Registers the <see cref="ITemplatingService"/> implementation with the dependency injection container.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> dependency injection container service collection.</param>
public static void AddRegexTemplatingEngine(this IServiceCollection services) =>
services.AddSingleton<ITemplatingService, RegexTemplatingService>();
}
}
78 changes: 78 additions & 0 deletions src/dotnet/Common/Services/Templates/RegexTemplatingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using FoundationaLLM.Common.Constants.Templates;
using FoundationaLLM.Common.Interfaces;
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;

namespace FoundationaLLM.Common.Services.Templates
{
/// <summary>
/// Templating engine that uses regular expressions to replace tokens in strings.
/// </summary>
/// <param name="logger">The logger used for logging.</param>
public partial class RegexTemplatingService(
ILogger<RegexTemplatingService> logger) : ITemplatingService
{
/// <summary>
/// Regular expression pattern for template variables.
/// </summary>
private const string REGEX_VARIABLE_PATTERN = "\\{\\{foundationallm:(.*?)\\}\\}";

private readonly ILogger<RegexTemplatingService> _logger = logger;

[GeneratedRegex(REGEX_VARIABLE_PATTERN, RegexOptions.Compiled)]
private static partial Regex VariableRegex();

/// <inheritdoc/>
public string Transform(string s)
{
if (string.IsNullOrWhiteSpace(s))
{
return string.Empty;
}

try
{
// Expects the format {{foundationallm:variable_name[:format]}}

var matches = VariableRegex().Matches(s);
Dictionary<string, string> replacements = [];

foreach (Match match in matches)
{
var matchedVariable = match.Value;

var variableTokens = match.Groups[1].Value.Split(":", 2);
var variableName = variableTokens[0];
var variableFormat = variableTokens.Length > 1 ? variableTokens[1] : null;

switch (variableName)
{
case TemplateVariables.CurrentDateTimeUTC:
replacements.Add(
matchedVariable,
string.IsNullOrWhiteSpace(variableFormat)
? DateTime.UtcNow.ToString()
: DateTime.UtcNow.ToString(variableFormat));
break;
default:
break;
}
}

var transformedString = s;
foreach (var replacement in replacements)
{
transformedString = transformedString.Replace(replacement.Key, replacement.Value);
}

return transformedString;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while transforming the string.");
}

return s;
}
}
}
15 changes: 15 additions & 0 deletions src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class OrchestrationBuilder
/// <param name="resourceProviderServices">A dictionary of <see cref="IResourceProviderService"/> resource providers hashed by resource provider name.</param>
/// <param name="llmOrchestrationServiceManager">The <see cref="ILLMOrchestrationServiceManager"/> that manages internal and external orchestration services.</param>
/// <param name="cosmosDBService">The <see cref="IAzureCosmosDBService"/> used to interact with the Cosmos DB database.</param>
/// <param name="templatingService">The <see cref="ITemplatingService"/> used to render templates.</param>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> provding dependency injection services for the current scope.</param>
/// <param name="loggerFactory">The logger factory used to create new loggers.</param>
/// <returns></returns>
Expand All @@ -53,6 +54,7 @@ public class OrchestrationBuilder
Dictionary<string, IResourceProviderService> resourceProviderServices,
ILLMOrchestrationServiceManager llmOrchestrationServiceManager,
IAzureCosmosDBService cosmosDBService,
ITemplatingService templatingService,
IServiceProvider serviceProvider,
ILoggerFactory loggerFactory)
{
Expand All @@ -64,6 +66,7 @@ public class OrchestrationBuilder
originalRequest.SessionId,
originalRequest.Settings?.ModelParameters,
resourceProviderServices,
templatingService,
callContext.CurrentUserIdentity!,
logger);

Expand Down Expand Up @@ -176,6 +179,7 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync<LongRunningOperationCon
string? sessionId,
Dictionary<string, object>? modelParameterOverrides,
Dictionary<string, IResourceProviderService> resourceProviderServices,
ITemplatingService templatingService,
UnifiedUserIdentity currentUserIdentity,
ILogger<OrchestrationBuilder> logger)
{
Expand Down Expand Up @@ -256,6 +260,17 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync<LongRunningOperationCon
var retrievedPrompt = await promptResourceProvider.GetResourceAsync<PromptBase>(
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.Prefix = templatingService.Transform(multipartPrompt.Prefix!);
multipartPrompt.Suffix = templatingService.Transform(multipartPrompt.Suffix!);
}
}
explodedObjectsManager.TryAdd(
retrievedPrompt.ObjectId!,
retrievedPrompt);
Expand Down
7 changes: 7 additions & 0 deletions src/dotnet/Orchestration/Services/OrchestrationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class OrchestrationService : IOrchestrationService
{
private readonly ILLMOrchestrationServiceManager _llmOrchestrationServiceManager;
private readonly IAzureCosmosDBService _cosmosDBService;
private readonly ITemplatingService _templatingService;
private readonly ICallContext _callContext;
private readonly IConfiguration _configuration;
private readonly ILogger<OrchestrationService> _logger;
Expand All @@ -39,6 +40,7 @@ public class OrchestrationService : IOrchestrationService
/// <param name="resourceProviderServices">A list of <see cref="IResourceProviderService"/> resource providers hashed by resource provider name.</param>
/// <param name="llmOrchestrationServiceManager">The <see cref="ILLMOrchestrationServiceManager"/> managing the internal and external LLM orchestration services.</param>
/// <param name="cosmosDBService">The <see cref="IAzureCosmosDBService"/> used to interact with the Cosmos DB database.</param>
/// <param name="templatingService">The <see cref="ITemplatingService"/> used to render templates.</param>
/// <param name="callContext">The call context of the request being handled.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> used to retrieve app settings from configuration.</param>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> provding dependency injection services for the current scope.</param>
Expand All @@ -47,6 +49,7 @@ public OrchestrationService(
IEnumerable<IResourceProviderService> resourceProviderServices,
ILLMOrchestrationServiceManager llmOrchestrationServiceManager,
IAzureCosmosDBService cosmosDBService,
ITemplatingService templatingService,
ICallContext callContext,
IConfiguration configuration,
IServiceProvider serviceProvider,
Expand All @@ -56,6 +59,7 @@ public OrchestrationService(
rps => rps.Name);
_llmOrchestrationServiceManager = llmOrchestrationServiceManager;
_cosmosDBService = cosmosDBService;
_templatingService = templatingService;

_callContext = callContext;
_configuration = configuration;
Expand Down Expand Up @@ -100,6 +104,7 @@ public async Task<CompletionResponse> GetCompletion(string instanceId, Completio
_resourceProviderServices,
_llmOrchestrationServiceManager,
_cosmosDBService,
_templatingService,
_serviceProvider,
_loggerFactory)
?? throw new OrchestrationException($"The orchestration builder was not able to create an orchestration for agent [{completionRequest.AgentName ?? string.Empty}].");
Expand Down Expand Up @@ -139,6 +144,7 @@ public async Task<LongRunningOperation> StartCompletionOperation(string instance
_resourceProviderServices,
_llmOrchestrationServiceManager,
_cosmosDBService,
_templatingService,
_serviceProvider,
_loggerFactory)
?? throw new OrchestrationException($"The orchestration builder was not able to create an orchestration for agent [{completionRequest.AgentName ?? string.Empty}].");
Expand Down Expand Up @@ -225,6 +231,7 @@ private async Task<CompletionResponse> GetCompletionForAgentConversation(
_resourceProviderServices,
_llmOrchestrationServiceManager,
_cosmosDBService,
_templatingService,
_serviceProvider,
_loggerFactory);

Expand Down
3 changes: 3 additions & 0 deletions src/dotnet/OrchestrationAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ public static void Main(string[] args)
builder.AddGroupMembership();
builder.AddAuthorizationServiceClient();

// Add the templating engine.
builder.AddRegexTemplatingEngine();

//----------------------------
// Resource providers
//----------------------------
Expand Down
Loading
Loading