Microsoft Agent Framework
Microsoft.Extensions.AI
Created: 27 Feb 2026
Updated: 27 Feb 2026
IEmbeddingGenerator: Custom Middleware
DelegatingEmbeddingGenerator<TInput, TEmbedding> is the embedding counterpart of DelegatingChatClient. Derive from it to add custom behaviour — logging, metrics, rate limiting, retries — without changing the rest of the pipeline. The base class delegates all calls to the inner generator by default; you only override what you need.
Key Concepts
1. Deriving from DelegatingEmbeddingGenerator
Override GenerateAsync to intercept embedding requests. Call base.GenerateAsync to pass through to the inner generator:
public sealed class LoggingEmbeddingGenerator(
IEmbeddingGenerator<string, Embedding<float>> inner)
: DelegatingEmbeddingGenerator<string, Embedding<float>>(inner)
{
public override async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
IEnumerable<string> values,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
{
Console.WriteLine($"Embedding {values.Count()} input(s)...");
var result = await base.GenerateAsync(values, options, cancellationToken);
Console.WriteLine($"Got {result.Count} vector(s), dims={result[0].Vector.Length}");
return result;
}
}
2. Wrapping the Generator
Pass the inner generator to the constructor — no builder required:
IEmbeddingGenerator<string, Embedding<float>> raw =
new OpenAIClient(apiKey)
.GetEmbeddingClient("text-embedding-3-small")
.AsIEmbeddingGenerator();
IEmbeddingGenerator<string, Embedding<float>> generator =
new LoggingEmbeddingGenerator(raw);
3. GenerateVectorAsync Goes Through Middleware
The convenience method GenerateVectorAsync internally calls GenerateAsync, so your middleware intercepts it automatically — no extra work needed.
Full Example
using Microsoft.Extensions.AI;
using OpenAI;
namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Embeddings;
/// <summary>
/// Custom middleware built on DelegatingEmbeddingGenerator.
/// Logs every generation request: input count, vector dimensions, and elapsed time.
/// </summary>
public sealed class LoggingEmbeddingGenerator(
IEmbeddingGenerator<string, Embedding<float>> inner)
: DelegatingEmbeddingGenerator<string, Embedding<float>>(inner)
{
private int _batchNumber;
public override async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
IEnumerable<string> values,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
{
var list = values.ToList();
int id = Interlocked.Increment(ref _batchNumber);
Console.WriteLine($" [EMB #{id}] Generating {list.Count} embedding(s)...");
foreach (var v in list)
Console.WriteLine($" [EMB #{id}] \"{v}\"");
var started = DateTime.UtcNow;
GeneratedEmbeddings<Embedding<float>> result =
await base.GenerateAsync(list, options, cancellationToken);
var ms = (DateTime.UtcNow - started).TotalMilliseconds;
Console.WriteLine($" [EMB #{id}] Done — {result.Count} vector(s), " +
$"dims={result[0].Vector.Length}, elapsed={ms:F0} ms");
return result;
}
}
/// <summary>
/// Demonstrates DelegatingEmbeddingGenerator custom middleware.
/// Scenario: E-commerce product tagging with audit logging.
/// </summary>
public static class EmbeddingMiddlewareDemo
{
public static async Task RunAsync()
{
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY")
?? throw new InvalidOperationException("Set OPEN_AI_KEY environment variable.");
IEmbeddingGenerator<string, Embedding<float>> rawGenerator =
new OpenAIClient(apiKey)
.GetEmbeddingClient("text-embedding-3-small")
.AsIEmbeddingGenerator();
IEmbeddingGenerator<string, Embedding<float>> generator =
new LoggingEmbeddingGenerator(rawGenerator);
Console.WriteLine("====== IEmbeddingGenerator — Custom Middleware (DelegatingEmbeddingGenerator) ======\n");
Console.WriteLine("--- Batch 1: Product Names ---");
await generator.GenerateAsync(["Wireless Headphones", "Mechanical Keyboard"]);
Console.WriteLine();
Console.WriteLine("--- Batch 2: Single Item ---");
await generator.GenerateVectorAsync("USB-C Docking Station");
Console.WriteLine();
Console.WriteLine("--- Batch 3: Categories ---");
await generator.GenerateAsync(["Audio & Video", "Computing", "Accessories"]);
Console.WriteLine();
}
}