Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using CrestApps.Core.AI.Models;
using Microsoft.Extensions.AI;

namespace CrestApps.Core.AI.Capabilities;

/// <summary>
/// Carries the information required by an <see cref="IAIModelParameterBinder"/> to apply a selected
/// model parameter value to the outgoing request.
/// </summary>
public sealed class AIModelParameterBindingContext
{
/// <summary>
/// Initializes a new instance of the <see cref="AIModelParameterBindingContext"/> class.
/// </summary>
/// <param name="descriptor">The effective descriptor of the parameter being applied.</param>
/// <param name="value">The value selected by the operator.</param>
/// <param name="chatOptions">The chat options to mutate.</param>
/// <param name="completionContext">The completion context of the current request.</param>
public AIModelParameterBindingContext(
AIModelParameterDescriptor descriptor,
string value,
ChatOptions chatOptions,
AICompletionContext completionContext)
{
ArgumentNullException.ThrowIfNull(descriptor);
ArgumentNullException.ThrowIfNull(chatOptions);
ArgumentNullException.ThrowIfNull(completionContext);

Descriptor = descriptor;
Value = value;
ChatOptions = chatOptions;
CompletionContext = completionContext;
}

/// <summary>
/// Gets the effective descriptor of the parameter being applied.
/// </summary>
public AIModelParameterDescriptor Descriptor { get; }

/// <summary>
/// Gets the value selected by the operator.
/// </summary>
public string Value { get; }

/// <summary>
/// Gets the chat options to mutate.
/// </summary>
public ChatOptions ChatOptions { get; }

/// <summary>
/// Gets the completion context of the current request.
/// </summary>
public AICompletionContext CompletionContext { get; }

/// <summary>
/// Gets or sets the deployment resolved for the current request.
/// </summary>
public AIDeployment Deployment { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using CrestApps.Core.AI.Models;

namespace CrestApps.Core.AI.Capabilities;

/// <summary>
/// Resolves the metadata-driven capabilities of an <see cref="AIDeployment"/> by merging the globally
/// registered model features and parameters with the metadata stored on the deployment.
/// </summary>
public interface IAIModelCapabilityService
{
/// <summary>
/// Gets every model feature registered by the application.
/// </summary>
IReadOnlyList<AIModelFeatureDescriptor> GetRegisteredFeatures();

/// <summary>
/// Gets every model parameter registered by the application.
/// </summary>
IReadOnlyList<AIModelParameterDescriptor> GetRegisteredParameters();

/// <summary>
/// Gets the effective capabilities exposed by the given deployment.
/// </summary>
/// <param name="deployment">The deployment to inspect.</param>
AIDeploymentCapabilities GetCapabilities(AIDeployment deployment);

/// <summary>
/// Gets the effective capabilities exposed by the deployment with the given technical name.
/// </summary>
/// <param name="deploymentName">The technical name of the deployment.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask<AIDeploymentCapabilities> GetCapabilitiesAsync(string deploymentName, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace CrestApps.Core.AI.Capabilities;

/// <summary>
/// Applies the value selected for a registered model parameter to the outgoing chat request.
/// Modules register a binder for every parameter they contribute so runtime behavior stays
/// provider-agnostic and free of model name detection.
/// </summary>
public interface IAIModelParameterBinder
{
/// <summary>
/// Gets the technical name of the parameter this binder applies.
/// </summary>
string ParameterName { get; }

/// <summary>
/// Applies the selected value to the request represented by the given context.
/// </summary>
/// <param name="context">The binding context.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
Task BindAsync(AIModelParameterBindingContext context, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using CrestApps.Core.AI.Models;

namespace CrestApps.Core.AI.Completions;

/// <summary>
/// Provides extension methods for <see cref="AICompletionContext"/>.
/// </summary>
public static class AICompletionContextExtensions
{
/// <summary>
/// Copies the model parameter values held by the given metadata onto the completion context.
/// Empty values are ignored so a stored blank never overrides a deployment default.
/// </summary>
/// <param name="context">The completion context to populate.</param>
/// <param name="metadata">The metadata holding the selected model parameter values.</param>
public static void ApplyModelParameters(this AICompletionContext context, AIModelParametersMetadata metadata)
{
ArgumentNullException.ThrowIfNull(context);

if (metadata?.Values is not { Count: > 0 })
{
return;
}

foreach (var (name, value) in metadata.Values)
{
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(value))
{
continue;
}

context.ModelParameters[name] = value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ private string _utilityDeploymentIdBackingField
set => UtilityDeploymentName = value;
}

/// <summary>
/// Gets the model parameter values selected for this request, keyed by the registered
/// parameter technical name. Values for parameters that the resolved deployment does not
/// expose are discarded before the request is sent to the provider.
/// </summary>
public Dictionary<string, string> ModelParameters { get; } = new(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Gets the additional provider-specific properties applied to the completion request.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
namespace CrestApps.Core.AI.Models;

/// <summary>
/// Represents the effective capabilities of an <see cref="AIDeployment"/> after the registered
/// definitions have been merged with the deployment specific metadata.
/// </summary>
public sealed class AIDeploymentCapabilities
{
/// <summary>
/// Gets an instance that exposes no features and no parameters.
/// </summary>
public static AIDeploymentCapabilities Empty { get; } = new AIDeploymentCapabilities([], []);

private readonly Dictionary<string, AIModelParameterDescriptor> _parameters;
private readonly HashSet<string> _features;

/// <summary>
/// Initializes a new instance of the <see cref="AIDeploymentCapabilities"/> class.
/// </summary>
/// <param name="features">The features exposed by the deployment.</param>
/// <param name="parameters">The effective parameters exposed by the deployment.</param>
public AIDeploymentCapabilities(
IEnumerable<AIModelFeatureDescriptor> features,
IEnumerable<AIModelParameterDescriptor> parameters)
{
ArgumentNullException.ThrowIfNull(features);
ArgumentNullException.ThrowIfNull(parameters);

Features = [.. features.OrderBy(feature => feature.Order).ThenBy(feature => feature.Name, StringComparer.OrdinalIgnoreCase)];
Parameters = [.. parameters.OrderBy(parameter => parameter.Order).ThenBy(parameter => parameter.Name, StringComparer.OrdinalIgnoreCase)];
_features = new HashSet<string>(Features.Select(feature => feature.Name), StringComparer.OrdinalIgnoreCase);
_parameters = Parameters.ToDictionary(parameter => parameter.Name, StringComparer.OrdinalIgnoreCase);
}

/// <summary>
/// Gets the features exposed by the deployment.
/// </summary>
public IReadOnlyList<AIModelFeatureDescriptor> Features { get; }

/// <summary>
/// Gets the effective parameters exposed by the deployment.
/// </summary>
public IReadOnlyList<AIModelParameterDescriptor> Parameters { get; }

/// <summary>
/// Determines whether the deployment exposes the given feature.
/// </summary>
/// <param name="featureName">The technical name of the feature.</param>
public bool SupportsFeature(string featureName)
{
return !string.IsNullOrWhiteSpace(featureName) && _features.Contains(featureName);
}

/// <summary>
/// Gets the effective descriptor of the given parameter, or <see langword="null"/> when the
/// deployment does not expose it.
/// </summary>
/// <param name="parameterName">The technical name of the parameter.</param>
public AIModelParameterDescriptor GetParameter(string parameterName)
{
if (string.IsNullOrWhiteSpace(parameterName))
{
return null;
}

return _parameters.TryGetValue(parameterName, out var descriptor)
? descriptor
: null;
}

/// <summary>
/// Determines whether the deployment exposes the given parameter.
/// </summary>
/// <param name="parameterName">The technical name of the parameter.</param>
public bool SupportsParameter(string parameterName)
{
return GetParameter(parameterName) is not null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace CrestApps.Core.AI.Models;

/// <summary>
/// Metadata stored on <see cref="AIDeployment"/> describing the features and configurable parameters
/// exposed by the underlying model. Editors, validation, and runtime request generation are driven from
/// this metadata instead of provider or model name detection.
/// </summary>
public sealed class AIDeploymentModelMetadata
{
/// <summary>
/// Gets or sets the technical names of the registered model features supported by this deployment.
/// </summary>
public string[] Features { get; set; } = [];

/// <summary>
/// Gets or sets the supported model parameters keyed by their registered technical name.
/// A parameter that is not present in this dictionary is not supported by the deployment and is
/// never rendered by editors or sent to the provider.
/// </summary>
public Dictionary<string, AIDeploymentModelParameter> Parameters { get; set; } = new(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Determines whether the deployment supports the given registered feature.
/// </summary>
/// <param name="featureName">The technical name of the feature.</param>
public bool SupportsFeature(string featureName)
{
if (string.IsNullOrWhiteSpace(featureName) || Features is not { Length: > 0 })
{
return false;
}

return Features.Any(feature => string.Equals(feature, featureName, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
/// Determines whether the deployment supports the given registered parameter.
/// </summary>
/// <param name="parameterName">The technical name of the parameter.</param>
public bool SupportsParameter(string parameterName)
{
return !string.IsNullOrWhiteSpace(parameterName) &&
Parameters is not null &&
Parameters.ContainsKey(parameterName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
namespace CrestApps.Core.AI.Models;

/// <summary>
/// Describes the per-deployment metadata of a supported model parameter. Every member is optional and,
/// when supplied, narrows the globally registered <see cref="AIModelParameterDescriptor"/> so a deployment
/// can describe the exact behavior of its underlying model.
/// </summary>
public sealed class AIDeploymentModelParameter
{
/// <summary>
/// Gets or sets the subset of allowed values supported by this deployment.
/// When empty, the registered allowed values are used.
/// </summary>
public string[] AllowedValues { get; set; }

/// <summary>
/// Gets or sets the value applied when an operator does not select one.
/// </summary>
public string DefaultValue { get; set; }

/// <summary>
/// Gets or sets the inclusive minimum accepted value for numeric parameters.
/// </summary>
public double? Minimum { get; set; }

/// <summary>
/// Gets or sets the inclusive maximum accepted value for numeric parameters.
/// </summary>
public double? Maximum { get; set; }

/// <summary>
/// Gets or sets the increment applied by numeric editors.
/// </summary>
public double? Step { get; set; }

/// <summary>
/// Creates a copy of this instance.
/// </summary>
public AIDeploymentModelParameter Clone()
{
return new AIDeploymentModelParameter
{
AllowedValues = AllowedValues is null
? null
: [.. AllowedValues],
DefaultValue = DefaultValue,
Minimum = Minimum,
Maximum = Maximum,
Step = Step,
};
}
}
Loading
Loading