Skip to content
Merged
147 changes: 147 additions & 0 deletions doc/multi-backend-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Multi-Backend Configuration Example

This example demonstrates how to configure and use the multi-backend retrieval architecture in NLWebNet.

## Single Backend (Default/Legacy)

```csharp
// Traditional single backend setup - still works
services.AddNLWebNet<MockDataBackend>(options =>
{
options.DefaultMode = QueryMode.List;
options.MaxResultsPerQuery = 20;
});
```

## Multi-Backend Configuration

```csharp
// New multi-backend setup
services.AddNLWebNetMultiBackend(
options =>
{
options.DefaultMode = QueryMode.List;
options.MaxResultsPerQuery = 50;
},
multiBackendOptions =>
{
multiBackendOptions.Enabled = true;
multiBackendOptions.EnableParallelQuerying = true;
multiBackendOptions.EnableResultDeduplication = true;
multiBackendOptions.MaxConcurrentQueries = 3;
multiBackendOptions.BackendTimeoutSeconds = 30;
multiBackendOptions.WriteEndpoint = "primary_backend";
});
```

## Configuration via appsettings.json

```json
{
"NLWebNet": {
"DefaultMode": "List",
"MaxResultsPerQuery": 50,
"MultiBackend": {
"Enabled": true,
"EnableParallelQuerying": true,
"EnableResultDeduplication": true,
"MaxConcurrentQueries": 3,
"BackendTimeoutSeconds": 30,
"WriteEndpoint": "primary_backend",
"Endpoints": {
"primary_backend": {
"Enabled": true,
"BackendType": "azure_ai_search",
"Priority": 10,
"Properties": {
"ConnectionString": "your-connection-string",
"IndexName": "your-index"
}
},
"secondary_backend": {
"Enabled": true,
"BackendType": "mock",
"Priority": 5,
"Properties": {}
}
}
}
}
}
```

## Usage Example

```csharp
public class ExampleController : ControllerBase
{
private readonly INLWebService _nlWebService;
private readonly IBackendManager _backendManager;

public ExampleController(INLWebService nlWebService, IBackendManager backendManager)
{
_nlWebService = nlWebService;
_backendManager = backendManager;
}

[HttpPost("search")]
public async Task<IActionResult> Search([FromBody] NLWebRequest request)
{
// Multi-backend search automatically handled
var response = await _nlWebService.ProcessRequestAsync(request);
return Ok(response);
}

[HttpGet("backend-info")]
public IActionResult GetBackendInfo()
{
// Get information about configured backends
var backendInfo = _backendManager.GetBackendInfo();
return Ok(backendInfo);
}

[HttpGet("write-backend-capabilities")]
public IActionResult GetWriteBackendCapabilities()
{
// Access the designated write backend
var writeBackend = _backendManager.GetWriteBackend();
if (writeBackend == null)
{
return NotFound("No write backend configured");
}

var capabilities = writeBackend.GetCapabilities();
return Ok(capabilities);
}
}
```

## Key Features

### Parallel Querying
- Queries execute simultaneously across all enabled backends
- Configurable concurrency limits and timeouts
- Graceful handling of backend failures

### Result Deduplication
- Automatic deduplication based on URL
- Higher scoring results from different backends take precedence
- Can be disabled for scenarios requiring all results

### Write Endpoint
- Designate one backend as the primary write endpoint
- Other backends remain read-only for queries
- Useful for hybrid architectures

### Backward Compatibility
- Existing single-backend configurations continue to work
- No breaking changes to existing APIs
- Gradual migration path available

## Migration from Single Backend

1. Replace `AddNLWebNet<T>()` with `AddNLWebNetMultiBackend()`
2. Set `MultiBackend.Enabled = false` initially to maintain existing behavior
3. Configure additional backends in the `Endpoints` section
4. Enable multi-backend mode by setting `MultiBackend.Enabled = true`
5. Test and adjust concurrency and timeout settings as needed
105 changes: 105 additions & 0 deletions src/NLWebNet/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NLWebNet.Models;
using NLWebNet.Services;
using NLWebNet.MCP;
Expand Down Expand Up @@ -94,4 +97,106 @@ public static IServiceCollection AddNLWebNet<TDataBackend>(this IServiceCollecti

return services;
}

/// <summary>
/// Adds NLWebNet services with multi-backend support
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="configureOptions">Optional configuration callback</param>
/// <param name="configureMultiBackend">Multi-backend configuration callback</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddNLWebNetMultiBackend(this IServiceCollection services,
Action<NLWebOptions>? configureOptions = null,
Action<MultiBackendOptions>? configureMultiBackend = null)
{
// Configure options
if (configureOptions != null)
{
services.Configure(configureOptions);
}

// Configure multi-backend options
if (configureMultiBackend != null)
{
services.Configure<MultiBackendOptions>(configureMultiBackend);
}

// Add logging (required for the services)
services.AddLogging();

// Register core NLWebNet services
services.AddScoped<INLWebService>(provider =>
{
var options = provider.GetRequiredService<IOptions<NLWebOptions>>();
var multiBackendOptions = provider.GetRequiredService<IOptions<MultiBackendOptions>>();
if (multiBackendOptions.Value.Enabled)
{
// Use multi-backend constructor
return new NLWebService(
provider.GetRequiredService<IQueryProcessor>(),
provider.GetRequiredService<IResultGenerator>(),
provider.GetRequiredService<IBackendManager>(),
provider.GetRequiredService<ILogger<NLWebService>>(),
options);
}
else
{
// Use single backend constructor for backward compatibility
return new NLWebService(
provider.GetRequiredService<IQueryProcessor>(),
provider.GetRequiredService<IResultGenerator>(),
provider.GetRequiredService<IDataBackend>(),
provider.GetRequiredService<ILogger<NLWebService>>(),
options);
}
});

services.AddScoped<IQueryProcessor, QueryProcessor>();
services.AddScoped<IResultGenerator>(provider =>
{
var options = provider.GetRequiredService<IOptions<NLWebOptions>>();
var multiBackendOptions = provider.GetRequiredService<IOptions<MultiBackendOptions>>();
if (multiBackendOptions.Value.Enabled)
{
// Use multi-backend constructor
return new ResultGenerator(
provider.GetRequiredService<IBackendManager>(),
provider.GetRequiredService<ILogger<ResultGenerator>>(),
options,
provider.GetService<IChatClient>());
}
else
{
// Use single backend constructor for backward compatibility
return new ResultGenerator(
provider.GetRequiredService<IDataBackend>(),
provider.GetRequiredService<ILogger<ResultGenerator>>(),
options,
provider.GetService<IChatClient>());
}
});

// Register MCP services
services.AddScoped<IMcpService, McpService>();

// Register multi-backend services
services.AddScoped<IBackendManager, BackendManager>();

// Register default data backend (can be overridden)
services.AddScoped<IDataBackend, MockDataBackend>();

// Add health checks
services.AddHealthChecks()
.AddCheck<NLWebHealthCheck>("nlweb")
.AddCheck<DataBackendHealthCheck>("data-backend")
.AddCheck<AIServiceHealthCheck>("ai-service");

// Add metrics
services.AddMetrics();

// Add rate limiting
services.AddSingleton<IRateLimitingService, InMemoryRateLimitingService>();

return services;
}
}
78 changes: 78 additions & 0 deletions src/NLWebNet/Models/MultiBackendOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.ComponentModel.DataAnnotations;

namespace NLWebNet.Models;

/// <summary>
/// Configuration options for multi-backend retrieval architecture.
/// </summary>
public class MultiBackendOptions
{
/// <summary>
/// The configuration section name for binding from appsettings.json.
/// </summary>
public const string SectionName = "NLWebNet:MultiBackend";

/// <summary>
/// Whether multi-backend mode is enabled. When false, uses single backend for backward compatibility.
/// </summary>
public bool Enabled { get; set; } = false;

/// <summary>
/// The identifier of the backend to use as the primary write endpoint.
/// </summary>
public string? WriteEndpoint { get; set; }

/// <summary>
/// Configuration for individual backend endpoints.
/// </summary>
public Dictionary<string, BackendEndpointOptions> Endpoints { get; set; } = new();

/// <summary>
/// Whether to enable parallel querying across backends.
/// </summary>
public bool EnableParallelQuerying { get; set; } = true;

/// <summary>
/// Whether to enable automatic result deduplication.
/// </summary>
public bool EnableResultDeduplication { get; set; } = true;

/// <summary>
/// Maximum number of concurrent backend queries.
/// </summary>
[Range(1, 10)]
public int MaxConcurrentQueries { get; set; } = 5;

/// <summary>
/// Timeout for individual backend queries in seconds.
/// </summary>
[Range(1, 120)]
public int BackendTimeoutSeconds { get; set; } = 30;
}

/// <summary>
/// Configuration for an individual backend endpoint.
/// </summary>
public class BackendEndpointOptions
{
/// <summary>
/// Whether this backend endpoint is enabled.
/// </summary>
public bool Enabled { get; set; } = true;

/// <summary>
/// The type of backend (e.g., "azure_ai_search", "mock", "custom").
/// </summary>
public string BackendType { get; set; } = string.Empty;

/// <summary>
/// Priority for this backend (higher values = higher priority).
/// Used for ordering results when deduplication is disabled.
/// </summary>
public int Priority { get; set; } = 0;

/// <summary>
/// Backend-specific configuration properties.
/// </summary>
public Dictionary<string, object> Properties { get; set; } = new();
}
5 changes: 5 additions & 0 deletions src/NLWebNet/Models/NLWebOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ public class NLWebOptions
/// Rate limiting configuration
/// </summary>
public RateLimitingOptions RateLimiting { get; set; } = new();

/// <summary>
/// Multi-backend configuration options. When enabled, overrides single backend behavior.
/// </summary>
public MultiBackendOptions MultiBackend { get; set; } = new();
}
Loading