Skip to content
Draft
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
61 changes: 61 additions & 0 deletions docs/Testing/semantic-cache-real-azure-openai-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Testing Semantic Cache with Real Azure OpenAI

This guide explains how to run the Semantic Cache end-to-end (E2E) tests using a real Azure OpenAI embedding deployment.

## Prerequisites

- An Azure OpenAI resource with an embedding model deployed.
- Docker Desktop (for local Redis + database containers).

## Environment variables

Set these before running the E2E tests:

```bash
# Enable semantic cache E2E tests
export ENABLE_SEMANTIC_CACHE_E2E_TESTS=true

# Required: Azure OpenAI
export AZURE_OPENAI_ENDPOINT="https://<your-resource-name>.openai.azure.com"
export AZURE_OPENAI_API_KEY="<your-api-key>"

# Optional: embedding model name (defaults to text-embedding-3-small)
export AZURE_OPENAI_EMBEDDING_MODEL="text-embedding-3-small"

# Optional: Redis connection string override
# export TEST_REDIS_CONNECTION_STRING="localhost:6379,password=TestRedisPassword123"
```

## Run the E2E tests

1. Ensure you have a reachable database for the provider you want to test (MSSQL/MySQL/PostgreSQL).

1. Run the tests from the test project:

```bash
cd src/Service.Tests
ENABLE_SEMANTIC_CACHE_E2E_TESTS=true dotnet test --filter "TestCategory=SemanticCacheE2E&TestCategory=MSSQL"
```

## Troubleshooting

### Tests are skipped

If you see `Assert.Inconclusive` messages, verify:

- `ENABLE_SEMANTIC_CACHE_E2E_TESTS=true`
- `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_API_KEY` are set

### Redis connection issues

- Ensure the `redis-test` container is running.
- Or set `TEST_REDIS_CONNECTION_STRING` to point at your Redis instance.

### Database prerequisite errors

The E2E tests apply the standard Service.Tests schema + seed scripts (DatabaseSchema-*.sql).
If initialization fails, ensure your database container/instance is reachable and the connection string env vars used by Service.Tests are set.

## Notes

- These tests call Azure OpenAI and may incur cost.
93 changes: 93 additions & 0 deletions src/Cli.Tests/EndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1271,4 +1271,97 @@ public void TestUpdateDatabaseType(string dbType, bool isSuccess)
// Assert
Assert.AreEqual(isSuccess, isError == 0);
}

/// <summary>
/// Test to verify configuring semantic cache settings via CLI.
/// Command: dab configure --runtime.semantic-cache.* {values}
/// </summary>
[TestMethod]
public void TestConfigureSemanticCache()
{
// Initialize the config file
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type",
"mssql", "--connection-string", TEST_ENV_CONN_STRING };
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);

Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
Assert.IsNotNull(runtimeConfig);
Assert.IsNotNull(runtimeConfig.Runtime);

// Act: Configure semantic cache with all options
string[] configureArgs = {
"configure", "-c", TEST_RUNTIME_CONFIG_FILE,
"--runtime.semantic-cache.enabled", "true",
"--runtime.semantic-cache.similarity-threshold", "0.85",
"--runtime.semantic-cache.max-results", "5",
"--runtime.semantic-cache.expire-seconds", "3600",
"--runtime.semantic-cache.azure-managed-redis.connection-string", "localhost:6379,ssl=True",
"--runtime.semantic-cache.azure-managed-redis.vector-index", "dab-semantic-index",
"--runtime.semantic-cache.azure-managed-redis.key-prefix", "dab:sc:",
"--runtime.semantic-cache.embedding-provider.type", "azure-openai",
"--runtime.semantic-cache.embedding-provider.endpoint", "https://test.openai.azure.com",
"--runtime.semantic-cache.embedding-provider.api-key", "test-key",
"--runtime.semantic-cache.embedding-provider.model", "text-embedding-ada-002"
};

int result = Program.Execute(configureArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);

// Assert: Verify command succeeded
Assert.AreEqual(0, result, "Configure command should succeed");

// Assert: Verify config was updated correctly
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? updatedConfig));
Assert.IsNotNull(updatedConfig);
Assert.IsNotNull(updatedConfig.Runtime);
Assert.IsNotNull(updatedConfig.Runtime.SemanticCache);

SemanticCacheOptions semanticCache = updatedConfig.Runtime.SemanticCache;
Assert.IsTrue(semanticCache.Enabled);
Assert.AreEqual(0.85, semanticCache.SimilarityThreshold);
Assert.AreEqual(5, semanticCache.MaxResults);
Assert.AreEqual(3600, semanticCache.ExpireSeconds);

Assert.IsNotNull(semanticCache.AzureManagedRedis);
Assert.AreEqual("localhost:6379,ssl=True", semanticCache.AzureManagedRedis.ConnectionString);
Assert.AreEqual("dab-semantic-index", semanticCache.AzureManagedRedis.VectorIndex);
Assert.AreEqual("dab:sc:", semanticCache.AzureManagedRedis.KeyPrefix);

Assert.IsNotNull(semanticCache.EmbeddingProvider);
Assert.AreEqual("azure-openai", semanticCache.EmbeddingProvider.Type);
Assert.AreEqual("https://test.openai.azure.com", semanticCache.EmbeddingProvider.Endpoint);
Assert.AreEqual("test-key", semanticCache.EmbeddingProvider.ApiKey);
Assert.AreEqual("text-embedding-ada-002", semanticCache.EmbeddingProvider.Model);
}

/// <summary>
/// Test to verify that semantic cache configuration validation works correctly.
/// Tests invalid values for similarity-threshold, max-results, and expire-seconds.
/// </summary>
[DataTestMethod]
[DataRow("--runtime.semantic-cache.similarity-threshold", "1.5", false, DisplayName = "Failure: similarity-threshold > 1.0")]
[DataRow("--runtime.semantic-cache.similarity-threshold", "-0.1", false, DisplayName = "Failure: similarity-threshold < 0.0")]
[DataRow("--runtime.semantic-cache.similarity-threshold", "0.85", true, DisplayName = "Success: valid similarity-threshold")]
[DataRow("--runtime.semantic-cache.max-results", "0", false, DisplayName = "Failure: max-results = 0")]
[DataRow("--runtime.semantic-cache.max-results", "-5", false, DisplayName = "Failure: max-results < 0")]
[DataRow("--runtime.semantic-cache.max-results", "10", true, DisplayName = "Success: valid max-results")]
[DataRow("--runtime.semantic-cache.expire-seconds", "0", false, DisplayName = "Failure: expire-seconds = 0")]
[DataRow("--runtime.semantic-cache.expire-seconds", "-100", false, DisplayName = "Failure: expire-seconds < 0")]
[DataRow("--runtime.semantic-cache.expire-seconds", "3600", true, DisplayName = "Success: valid expire-seconds")]
public void TestSemanticCacheValidation(string option, string value, bool isSuccess)
{
// Initialize the config file
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type",
"mssql", "--connection-string", TEST_ENV_CONN_STRING };
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);

Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
Assert.IsNotNull(runtimeConfig);

// Act: Update the semantic cache option
string[] configureArgs = { "configure", "-c", TEST_RUNTIME_CONFIG_FILE, option, value };
int result = Program.Execute(configureArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);

// Assert: Check if the operation succeeded as expected
Assert.AreEqual(isSuccess, result == 0);
}
}
56 changes: 56 additions & 0 deletions src/Cli/Commands/ConfigureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ public ConfigureOptions(
bool? runtimeMcpDmlToolsExecuteEntityEnabled = null,
bool? runtimeCacheEnabled = null,
int? runtimeCacheTtl = null,
bool? runtimeSemanticCacheEnabled = null,
double? runtimeSemanticCacheSimilarityThreshold = null,
int? runtimeSemanticCacheMaxResults = null,
int? runtimeSemanticCacheExpireSeconds = null,
string? runtimeSemanticCacheRedisConnectionString = null,
string? runtimeSemanticCacheRedisVectorIndex = null,
string? runtimeSemanticCacheRedisKeyPrefix = null,
string? runtimeSemanticCacheEmbeddingProviderType = null,
string? runtimeSemanticCacheEmbeddingEndpoint = null,
string? runtimeSemanticCacheEmbeddingApiKey = null,
string? runtimeSemanticCacheEmbeddingModel = null,
HostMode? runtimeHostMode = null,
IEnumerable<string>? runtimeHostCorsOrigins = null,
bool? runtimeHostCorsAllowCredentials = null,
Expand Down Expand Up @@ -103,6 +114,18 @@ public ConfigureOptions(
// Cache
RuntimeCacheEnabled = runtimeCacheEnabled;
RuntimeCacheTTL = runtimeCacheTtl;
// Semantic Cache
RuntimeSemanticCacheEnabled = runtimeSemanticCacheEnabled;
RuntimeSemanticCacheSimilarityThreshold = runtimeSemanticCacheSimilarityThreshold;
RuntimeSemanticCacheMaxResults = runtimeSemanticCacheMaxResults;
RuntimeSemanticCacheExpireSeconds = runtimeSemanticCacheExpireSeconds;
RuntimeSemanticCacheRedisConnectionString = runtimeSemanticCacheRedisConnectionString;
RuntimeSemanticCacheRedisVectorIndex = runtimeSemanticCacheRedisVectorIndex;
RuntimeSemanticCacheRedisKeyPrefix = runtimeSemanticCacheRedisKeyPrefix;
RuntimeSemanticCacheEmbeddingProviderType = runtimeSemanticCacheEmbeddingProviderType;
RuntimeSemanticCacheEmbeddingEndpoint = runtimeSemanticCacheEmbeddingEndpoint;
RuntimeSemanticCacheEmbeddingApiKey = runtimeSemanticCacheEmbeddingApiKey;
RuntimeSemanticCacheEmbeddingModel = runtimeSemanticCacheEmbeddingModel;
// Host
RuntimeHostMode = runtimeHostMode;
RuntimeHostCorsOrigins = runtimeHostCorsOrigins;
Expand Down Expand Up @@ -207,6 +230,39 @@ public ConfigureOptions(
[Option("runtime.cache.ttl-seconds", Required = false, HelpText = "Customize the DAB cache's global default time to live in seconds. Default: 5 seconds (Integer).")]
public int? RuntimeCacheTTL { get; }

[Option("runtime.semantic-cache.enabled", Required = false, HelpText = "Enable DAB's semantic cache globally. Default: false (boolean).")]
public bool? RuntimeSemanticCacheEnabled { get; }

[Option("runtime.semantic-cache.similarity-threshold", Required = false, HelpText = "Minimum similarity score for semantic cache hits. Default: 0.85 (double 0.0-1.0).")]
public double? RuntimeSemanticCacheSimilarityThreshold { get; }

[Option("runtime.semantic-cache.max-results", Required = false, HelpText = "Maximum number of KNN results to retrieve. Default: 5 (Integer).")]
public int? RuntimeSemanticCacheMaxResults { get; }

[Option("runtime.semantic-cache.expire-seconds", Required = false, HelpText = "TTL for semantic cache entries in seconds. Default: 86400 (1 day) (Integer).")]
public int? RuntimeSemanticCacheExpireSeconds { get; }

[Option("runtime.semantic-cache.azure-managed-redis.connection-string", Required = false, HelpText = "Redis connection string for semantic cache.")]
public string? RuntimeSemanticCacheRedisConnectionString { get; }

[Option("runtime.semantic-cache.azure-managed-redis.vector-index", Required = false, HelpText = "Redis vector index name. Default: 'dab-semantic-index'.")]
public string? RuntimeSemanticCacheRedisVectorIndex { get; }

[Option("runtime.semantic-cache.azure-managed-redis.key-prefix", Required = false, HelpText = "Redis key prefix for semantic cache entries. Default: 'dab:sc:'.")]
public string? RuntimeSemanticCacheRedisKeyPrefix { get; }

[Option("runtime.semantic-cache.embedding-provider.type", Required = false, HelpText = "Embedding provider type. Currently only 'azure-openai' is supported.")]
public string? RuntimeSemanticCacheEmbeddingProviderType { get; }

[Option("runtime.semantic-cache.embedding-provider.endpoint", Required = false, HelpText = "Azure OpenAI endpoint URL for embedding generation.")]
public string? RuntimeSemanticCacheEmbeddingEndpoint { get; }

[Option("runtime.semantic-cache.embedding-provider.api-key", Required = false, HelpText = "Azure OpenAI API key for embedding generation.")]
public string? RuntimeSemanticCacheEmbeddingApiKey { get; }

[Option("runtime.semantic-cache.embedding-provider.model", Required = false, HelpText = "Azure OpenAI embedding model deployment name (e.g., 'text-embedding-ada-002').")]
public string? RuntimeSemanticCacheEmbeddingModel { get; }

[Option("runtime.host.mode", Required = false, HelpText = "Set the host running mode of DAB in Development or Production. Default: Development.")]
public HostMode? RuntimeHostMode { get; }

Expand Down
Loading