Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0dc589e
Initial plan
Copilot Feb 3, 2026
d3187bd
Add EmbeddingsOptions and EmbeddingProviderType configuration models
Copilot Feb 3, 2026
6064826
Add CLI configure options for embeddings and register embedding service
Copilot Feb 3, 2026
0653f15
Add unit tests for embeddings and update JSON schema with embeddings …
Copilot Feb 3, 2026
21e81b9
Simplify HttpClient registration for embedding service
Copilot Feb 3, 2026
0cd8e53
Plan for embedding service enhancements
Copilot Feb 3, 2026
7fa1c49
Refactor embedding code into dedicated namespaces
Copilot Feb 3, 2026
c3f6937
Fix property renames and update tests
Copilot Feb 3, 2026
1e18c25
Add EmbeddingsOptionsConverter and fix all tests
Copilot Feb 3, 2026
857203a
Address code review feedback
Copilot Feb 3, 2026
89cb2d9
Address PR feedback: Add Azure OpenAI validation, cache key security,…
Copilot Feb 3, 2026
64b592e
Optimize ProviderName to avoid repeated string allocations
Copilot Feb 3, 2026
e8d7238
Fix schema mismatch, remove unused field, add enabled handling, valid…
Copilot Feb 3, 2026
d9c8a29
Add embedding health check execution and update JSON schema with endp…
Copilot Feb 4, 2026
3e02c0f
Add EmbeddingController for /embed REST endpoint with role-based auth…
Copilot Feb 4, 2026
5c05464
Address code review feedback for EmbeddingController
Copilot Feb 4, 2026
c9eba20
fix: manually deserialize EmbeddingsEndpointOptions and EmbeddingsHea…
robertopc1 Feb 6, 2026
d3a5209
feat: add embeddings config validation and unit tests
robertopc1 Feb 6, 2026
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
139 changes: 139 additions & 0 deletions schemas/dab.draft.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,145 @@
"default": 4
}
}
},
"embeddings": {
"type": "object",
"description": "Configuration for text embedding/vectorization service. Supports OpenAI and Azure OpenAI providers.",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the embedding service is enabled. Defaults to true.",
"default": true
},
"provider": {
"type": "string",
"description": "The embedding provider type.",
"enum": ["azure-openai", "openai"]
},
"base-url": {
"type": "string",
"description": "The provider base URL. For Azure OpenAI, use the Azure resource endpoint. For OpenAI, use https://api.openai.com."
},
"api-key": {
"type": "string",
"description": "The API key for authentication. Supports environment variable substitution with @env('VAR_NAME')."
},
"model": {
"type": "string",
"description": "The model or deployment name. Required for Azure OpenAI (deployment name). For OpenAI, defaults to 'text-embedding-3-small' if not specified."
},
"api-version": {
"type": "string",
"description": "Azure API version. Only used for Azure OpenAI provider.",
"default": "2024-02-01"
},
"dimensions": {
"type": "integer",
"description": "Output vector dimensions. Optional, uses model default if not specified. Useful for Redis schema alignment.",
"minimum": 1
},
"timeout-ms": {
"type": "integer",
"description": "Request timeout in milliseconds.",
"default": 30000,
"minimum": 1,
"maximum": 300000
},
"endpoint": {
"type": "object",
"description": "REST endpoint configuration for the embedding service.",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the /embed REST endpoint is enabled. Defaults to false.",
"default": false
},
"path": {
"type": "string",
"description": "The endpoint path. Defaults to '/embed'.",
"default": "/embed"
},
"roles": {
"type": "array",
"description": "The roles allowed to access the embedding endpoint. In development mode, defaults to ['anonymous'].",
"items": {
"type": "string"
}
}
}
},
"health": {
"type": "object",
"description": "Health check configuration for the embedding service.",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether health checks are enabled for embeddings. Defaults to true.",
"default": true
},
"threshold-ms": {
"type": "integer",
"description": "The maximum response time in milliseconds to be considered healthy.",
"default": 5000,
"minimum": 1,
"maximum": 300000
},
"test-text": {
"type": "string",
"description": "The text to use for health check validation.",
"default": "health check"
},
"expected-dimensions": {
"type": "integer",
"description": "The expected number of dimensions in the embedding result. If specified, dimension validation is performed.",
"minimum": 1
}
}
}
},
"required": ["provider", "base-url", "api-key"],
"allOf": [
{
"$comment": "Azure OpenAI requires the model (deployment name) to be specified.",
"if": {
"properties": {
"provider": {
"const": "azure-openai"
}
},
"required": ["provider"]
},
"then": {
"required": ["model"],
"properties": {
"api-version": {
"type": "string",
"description": "Azure API version. Required for Azure OpenAI provider.",
"default": "2024-02-01"
}
}
}
},
{
"$comment": "OpenAI does not require model (defaults to text-embedding-3-small) and does not use api-version.",
"if": {
"properties": {
"provider": {
"const": "openai"
}
},
"required": ["provider"]
},
"then": {
"properties": {
"api-version": false
}
}
}
]
}
}
},
Expand Down
79 changes: 79 additions & 0 deletions src/Cli/Commands/ConfigureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO.Abstractions;
using Azure.DataApiBuilder.Config;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Config.ObjectModel.Embeddings;
using Azure.DataApiBuilder.Product;
using Cli.Constants;
using CommandLine;
Expand Down Expand Up @@ -71,6 +72,21 @@ public ConfigureOptions(
RollingInterval? fileSinkRollingInterval = null,
int? fileSinkRetainedFileCountLimit = null,
long? fileSinkFileSizeLimitBytes = null,
CliBool? runtimeEmbeddingsEnabled = null,
EmbeddingProviderType? runtimeEmbeddingsProvider = null,
string? runtimeEmbeddingsBaseUrl = null,
string? runtimeEmbeddingsApiKey = null,
string? runtimeEmbeddingsModel = null,
string? runtimeEmbeddingsApiVersion = null,
int? runtimeEmbeddingsDimensions = null,
int? runtimeEmbeddingsTimeoutMs = null,
CliBool? runtimeEmbeddingsEndpointEnabled = null,
string? runtimeEmbeddingsEndpointPath = null,
IEnumerable<string>? runtimeEmbeddingsEndpointRoles = null,
CliBool? runtimeEmbeddingsHealthEnabled = null,
int? runtimeEmbeddingsHealthThresholdMs = null,
string? runtimeEmbeddingsHealthTestText = null,
int? runtimeEmbeddingsHealthExpectedDimensions = null,
string? config = null)
: base(config)
{
Expand Down Expand Up @@ -132,6 +148,24 @@ public ConfigureOptions(
FileSinkRollingInterval = fileSinkRollingInterval;
FileSinkRetainedFileCountLimit = fileSinkRetainedFileCountLimit;
FileSinkFileSizeLimitBytes = fileSinkFileSizeLimitBytes;
// Embeddings
RuntimeEmbeddingsEnabled = runtimeEmbeddingsEnabled;
RuntimeEmbeddingsProvider = runtimeEmbeddingsProvider;
RuntimeEmbeddingsBaseUrl = runtimeEmbeddingsBaseUrl;
RuntimeEmbeddingsApiKey = runtimeEmbeddingsApiKey;
RuntimeEmbeddingsModel = runtimeEmbeddingsModel;
RuntimeEmbeddingsApiVersion = runtimeEmbeddingsApiVersion;
RuntimeEmbeddingsDimensions = runtimeEmbeddingsDimensions;
RuntimeEmbeddingsTimeoutMs = runtimeEmbeddingsTimeoutMs;
// Embeddings Endpoint
RuntimeEmbeddingsEndpointEnabled = runtimeEmbeddingsEndpointEnabled;
RuntimeEmbeddingsEndpointPath = runtimeEmbeddingsEndpointPath;
RuntimeEmbeddingsEndpointRoles = runtimeEmbeddingsEndpointRoles;
// Embeddings Health
RuntimeEmbeddingsHealthEnabled = runtimeEmbeddingsHealthEnabled;
RuntimeEmbeddingsHealthThresholdMs = runtimeEmbeddingsHealthThresholdMs;
RuntimeEmbeddingsHealthTestText = runtimeEmbeddingsHealthTestText;
RuntimeEmbeddingsHealthExpectedDimensions = runtimeEmbeddingsHealthExpectedDimensions;
}

[Option("data-source.database-type", Required = false, HelpText = "Database type. Allowed values: MSSQL, PostgreSQL, CosmosDB_NoSQL, MySQL.")]
Expand Down Expand Up @@ -281,6 +315,51 @@ public ConfigureOptions(
[Option("runtime.telemetry.file.file-size-limit-bytes", Required = false, HelpText = "Configure maximum file size limit in bytes. Default: 1048576")]
public long? FileSinkFileSizeLimitBytes { get; }

[Option("runtime.embeddings.enabled", Required = false, HelpText = "Enable/disable the embedding service. Default: true")]
public CliBool? RuntimeEmbeddingsEnabled { get; }

[Option("runtime.embeddings.provider", Required = false, HelpText = "Configure embedding provider type. Allowed values: azure-openai, openai.")]
public EmbeddingProviderType? RuntimeEmbeddingsProvider { get; }

[Option("runtime.embeddings.base-url", Required = false, HelpText = "Configure the embedding provider base URL.")]
public string? RuntimeEmbeddingsBaseUrl { get; }

[Option("runtime.embeddings.api-key", Required = false, HelpText = "Configure the embedding API key for authentication.")]
public string? RuntimeEmbeddingsApiKey { get; }

[Option("runtime.embeddings.model", Required = false, HelpText = "Configure the model/deployment name. Required for Azure OpenAI, defaults to text-embedding-3-small for OpenAI.")]
public string? RuntimeEmbeddingsModel { get; }

[Option("runtime.embeddings.api-version", Required = false, HelpText = "Configure the Azure API version. Only used for Azure OpenAI provider. Default: 2024-02-01")]
public string? RuntimeEmbeddingsApiVersion { get; }

[Option("runtime.embeddings.dimensions", Required = false, HelpText = "Configure the output vector dimensions. Optional, uses model default if not specified.")]
public int? RuntimeEmbeddingsDimensions { get; }

[Option("runtime.embeddings.timeout-ms", Required = false, HelpText = "Configure the request timeout in milliseconds. Default: 30000")]
public int? RuntimeEmbeddingsTimeoutMs { get; }

[Option("runtime.embeddings.endpoint.enabled", Required = false, HelpText = "Enable/disable the endpoint for embeddings. Default: false")]
public CliBool? RuntimeEmbeddingsEndpointEnabled { get; }

[Option("runtime.embeddings.endpoint.path", Required = false, HelpText = "Configure the endpoint path for embeddings. Default: /embed")]
public string? RuntimeEmbeddingsEndpointPath { get; }

[Option("runtime.embeddings.endpoint.roles", Required = false, Separator = ',', HelpText = "Configure the roles allowed to access the embedding endpoint. Comma-separated list. In development mode defaults to 'anonymous'.")]
public IEnumerable<string>? RuntimeEmbeddingsEndpointRoles { get; }

[Option("runtime.embeddings.health.enabled", Required = false, HelpText = "Enable/disable health checks for the embedding service. Default: true")]
public CliBool? RuntimeEmbeddingsHealthEnabled { get; }

[Option("runtime.embeddings.health.threshold-ms", Required = false, HelpText = "Configure the health check threshold in milliseconds. Default: 5000")]
public int? RuntimeEmbeddingsHealthThresholdMs { get; }

[Option("runtime.embeddings.health.test-text", Required = false, HelpText = "Configure the test text for health check validation. Default: 'health check'")]
public string? RuntimeEmbeddingsHealthTestText { get; }

[Option("runtime.embeddings.health.expected-dimensions", Required = false, HelpText = "Configure the expected dimensions for health check validation. Optional.")]
public int? RuntimeEmbeddingsHealthExpectedDimensions { get; }

public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem)
{
logger.LogInformation("{productName} {version}", PRODUCT_NAME, ProductInfo.GetProductVersion());
Expand Down
113 changes: 113 additions & 0 deletions src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Azure.DataApiBuilder.Config.Converters;
using Azure.DataApiBuilder.Config.NamingPolicies;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Config.ObjectModel.Embeddings;
using Azure.DataApiBuilder.Core;
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Service;
Expand Down Expand Up @@ -908,6 +909,27 @@ options.FileSinkRetainedFileCountLimit is not null ||
}
}

// Embeddings: Provider, Endpoint, ApiKey, Model, ApiVersion, Dimensions, TimeoutMs, Enabled
if (options.RuntimeEmbeddingsProvider is not null ||
options.RuntimeEmbeddingsBaseUrl is not null ||
options.RuntimeEmbeddingsApiKey is not null ||
options.RuntimeEmbeddingsModel is not null ||
Comment on lines +912 to +916
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConfigGenerator only triggers embeddings updates for provider/base-url/api-key/model/api-version/dimensions/timeout/enabled. The CLI also defines runtime.embeddings.endpoint.* and runtime.embeddings.health.*, but those flags are ignored here.

Include the endpoint/health CLI flags in this condition so dab configure actually updates them.

Copilot uses AI. Check for mistakes.
options.RuntimeEmbeddingsApiVersion is not null ||
options.RuntimeEmbeddingsDimensions is not null ||
options.RuntimeEmbeddingsTimeoutMs is not null ||
options.RuntimeEmbeddingsEnabled is not null)
{
bool status = TryUpdateConfiguredEmbeddingsValues(options, runtimeConfig?.Runtime?.Embeddings, out EmbeddingsOptions? updatedEmbeddingsOptions);
if (status && updatedEmbeddingsOptions is not null)
{
runtimeConfig = runtimeConfig! with { Runtime = runtimeConfig.Runtime! with { Embeddings = updatedEmbeddingsOptions } };
}
else
{
return false;
}
}

return runtimeConfig != null;
}

Expand Down Expand Up @@ -1522,6 +1544,97 @@ private static bool TryUpdateConfiguredFileOptions(
}
}

/// <summary>
/// Attempts to update the embeddings configuration based on the provided options.
/// Creates a new EmbeddingsOptions object if the configuration is valid.
/// Provider, endpoint, and API key are required when configuring embeddings.
/// </summary>
/// <param name="options">The configuration options provided by the user.</param>
/// <param name="existingEmbeddingsOptions">The existing embeddings options from the runtime configuration.</param>
/// <param name="updatedEmbeddingsOptions">The resulting embeddings options if successful.</param>
/// <returns>True if the embeddings options were successfully configured; otherwise, false.</returns>
private static bool TryUpdateConfiguredEmbeddingsValues(
ConfigureOptions options,
EmbeddingsOptions? existingEmbeddingsOptions,
out EmbeddingsOptions? updatedEmbeddingsOptions)
{
updatedEmbeddingsOptions = null;

try
{
// Get values from options or fall back to existing configuration
EmbeddingProviderType? provider = options.RuntimeEmbeddingsProvider ?? existingEmbeddingsOptions?.Provider;
string? baseUrl = options.RuntimeEmbeddingsBaseUrl ?? existingEmbeddingsOptions?.BaseUrl;
string? apiKey = options.RuntimeEmbeddingsApiKey ?? existingEmbeddingsOptions?.ApiKey;
string? model = options.RuntimeEmbeddingsModel ?? existingEmbeddingsOptions?.Model;
string? apiVersion = options.RuntimeEmbeddingsApiVersion ?? existingEmbeddingsOptions?.ApiVersion;
int? dimensions = options.RuntimeEmbeddingsDimensions ?? existingEmbeddingsOptions?.Dimensions;
int? timeoutMs = options.RuntimeEmbeddingsTimeoutMs ?? existingEmbeddingsOptions?.TimeoutMs;
bool? enabled = options.RuntimeEmbeddingsEnabled.HasValue
? options.RuntimeEmbeddingsEnabled.Value == CliBool.True
: existingEmbeddingsOptions?.Enabled;

// Validate required fields
if (provider is null)
{
_logger.LogError("Failed to configure embeddings: provider is required. Use --runtime.embeddings.provider to specify the provider (azure-openai or openai).");
return false;
}

if (string.IsNullOrEmpty(baseUrl))
{
_logger.LogError("Failed to configure embeddings: base-url is required. Use --runtime.embeddings.base-url to specify the provider base URL.");
return false;
}

if (string.IsNullOrEmpty(apiKey))
{
_logger.LogError("Failed to configure embeddings: api-key is required. Use --runtime.embeddings.api-key to specify the authentication key.");
return false;
}

// Validate Azure OpenAI requires model/deployment name
if (provider == EmbeddingProviderType.AzureOpenAI && string.IsNullOrEmpty(model))
{
_logger.LogError("Failed to configure embeddings: model/deployment name is required for Azure OpenAI provider. Use --runtime.embeddings.model to specify the deployment name.");
return false;
}

// Validate dimensions if provided
if (dimensions is not null && dimensions <= 0)
{
_logger.LogError("Failed to configure embeddings: dimensions must be a positive integer.");
return false;
}

// Validate timeout if provided
if (timeoutMs is not null && timeoutMs <= 0)
{
_logger.LogError("Failed to configure embeddings: timeout-ms must be a positive integer.");
return false;
}

// Create the embeddings options
updatedEmbeddingsOptions = new EmbeddingsOptions(
Provider: (EmbeddingProviderType)provider,
BaseUrl: baseUrl,
ApiKey: apiKey,
Comment on lines +1617 to +1621
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryUpdateConfiguredEmbeddingsValues constructs a new EmbeddingsOptions without applying endpoint/health settings, even though the CLI exposes runtime.embeddings.endpoint.* and runtime.embeddings.health.* options.

Create/update EmbeddingsEndpointOptions and EmbeddingsHealthCheckConfig from the CLI flags and pass them into the EmbeddingsOptions constructor.

Copilot uses AI. Check for mistakes.
Enabled: enabled,
Model: model,
ApiVersion: apiVersion,
Dimensions: dimensions,
TimeoutMs: timeoutMs);

_logger.LogInformation("Updated RuntimeConfig with Runtime.Embeddings configuration.");
return true;
}
catch (Exception ex)
{
_logger.LogError("Failed to update RuntimeConfig.Embeddings with exception message: {exceptionMessage}.", ex.Message);
return false;
}
}

/// <summary>
/// Parse permission string to create PermissionSetting array.
/// </summary>
Expand Down
Loading
Loading