Skip to main content

Microsoft Foundry

Rag.NET ships no Foundry package and needs none. The pipeline consumes two Microsoft.Extensions.AI abstractions — IChatClient and IEmbeddingGenerator<string, Embedding<float>> — and resolves them from the container when the pipeline is built, so any client that produces those two is wired the same way. This page is the Foundry-shaped version of Getting Started's step 2; every step after it is unchanged.

Three shapes, differing only in how the client is constructed:

ShapeEndpointClient
Foundry in the cloudhttps://<resource>.openai.azure.com/openai/v1/OpenAIClient, package OpenAI
Foundry Localthe URL the local web service reports, plus /v1the same OpenAIClient, different base URI
Existing Azure OpenAI codethe same resourceAzureOpenAIClient, package Azure.AI.OpenAI

The third is listed because it still works, not because it is preferred: if your application already builds an AzureOpenAIClient, client.GetChatClient(deployment).AsIChatClient() and client.GetEmbeddingClient(deployment).AsIEmbeddingGenerator() register exactly as below and nothing on this page changes. Microsoft's current guidance for new code is the OpenAI package against the /openai/v1/ route, which is what the examples use.

Foundry in the cloud​

dotnet add package Rag.NET
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI # supplies AsIChatClient / AsIEmbeddingGenerator
dotnet add package OpenAI
dotnet add package Azure.Identity # keyless authentication only

With an API key​

using System.ClientModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI;

var foundry = new OpenAIClient(
new ApiKeyCredential(Environment.GetEnvironmentVariable("FOUNDRY_KEY")!),
new OpenAIClientOptions
{
Endpoint = new Uri("https://<resource>.openai.azure.com/openai/v1/"),
});

var services = new ServiceCollection();

services.AddChatClient(foundry.GetChatClient("gpt-5-mini").AsIChatClient());
services.AddEmbeddingGenerator(
foundry.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator());

The strings are deployment names, not model names. They happen to match when a deployment is named after its model, and they stop matching the moment someone names one chat-prod — see what to pin below, because one of those two names ends up in your index.

Keyless, with Microsoft Entra ID​

An API key grants full access to the resource and has to be rotated by hand. For anything long-lived prefer a token credential; only the construction changes.

using System.ClientModel.Primitives;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI;

// The authentication-policy constructor is marked for evaluation only; without the suppression
// this is a compile error, not a warning.
#pragma warning disable OPENAI001

var foundry = new OpenAIClient(
authenticationPolicy: new BearerTokenPolicy(
new DefaultAzureCredential(), "https://ai.azure.com/.default"),
options: new OpenAIClientOptions
{
Endpoint = new Uri("https://<resource>.openai.azure.com/openai/v1/"),
});

#pragma warning restore OPENAI001

var services = new ServiceCollection();

services.AddChatClient(foundry.GetChatClient("gpt-5-mini").AsIChatClient());
services.AddEmbeddingGenerator(
foundry.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator());

The token scope is https://ai.azure.com/.default, and the identity needs the Entra ID role assignments for inference on the resource.

Then the pipeline, unchanged​

using Rag.NET.Abstractions;
using Rag.NET.DependencyInjection;
using Rag.NET.PgVector;

services.AddRagNet(rag => rag
.UsePgVector("Host=localhost;Database=ragdb;Username=postgres;Password=secret",
vectorDimensions: 1536));

var pipeline = services.BuildServiceProvider().GetRequiredService<IRagPipeline>();

Models that are not OpenAI's​

Foundry serves DeepSeek, Llama, Grok and the rest of the catalogue through that same endpoint and the same credentials: deploy the model, put the deployment name where gpt-5-mini is, change nothing else. Rag.NET's chat path goes through GetChatClient(...).AsIChatClient(), which calls chat completions — the API every Foundry deployment supports, rather than the Responses API, which some deployments reject with 400 Model not supported.

What does change is the token accounting; see the third item in what to pin.

Foundry Local​

Foundry Local runs models on the machine and exposes an OpenAI-compatible REST server, so the wiring is the cloud wiring with a different base URI and a credential nobody checks.

Start the service first. It is hosted in-process by the Microsoft.AI.Foundry.Local SDK, which downloads and loads a model and then starts the web service on a URL it reports back — take the URL from the SDK rather than hard-coding a port, which is not stable. Microsoft's inference SDK walkthrough carries the full bootstrap.

using System.ClientModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI;

// foundryLocalUrl is what the Foundry Local SDK reported, e.g. "http://127.0.0.1:52495"
var local = new OpenAIClient(
new ApiKeyCredential("not-needed"),
new OpenAIClientOptions { Endpoint = new Uri($"{foundryLocalUrl}/v1") });

var services = new ServiceCollection();

services.AddChatClient(local.GetChatClient(modelId).AsIChatClient());

It answers; it does not embed​

That snippet registers a chat client and stops there, and the omission is the point. The Foundry Local REST surface is chat completions, audio transcription, token counting and model management — its reference lists no embeddings route. A RAG pipeline cannot run on a chat client alone: ingestion embeds every chunk and retrieval embeds every query.

So pair it with an embedder. For a pipeline that stays entirely on the machine, that is Rag.NET.Embeddings.Onnx — a local ONNX model, in-process, no external API:

using Rag.NET.DependencyInjection;
using Rag.NET.Embeddings.Onnx;

services.AddRagNet(rag => rag
.UseOnnxEmbeddings(o =>
{
o.ModelPath = "models/all-MiniLM-L6-v2.onnx";
o.TokenizerVocabPath = "models/vocab.txt";
}));

UseOnnxEmbeddings registers the IEmbeddingGenerator<string, Embedding<float>> itself, so there is no AddEmbeddingGenerator call in this shape. The other option is the ordinary one — a cloud embedder from the section above alongside the local chat client. Rag.NET never assumes the two come from the same provider.

What to pin​

The embedding identity Rag.NET stores is the deployment name. After each successful store, embedding versioning stamps the document with the identity read from the generator's EmbeddingGeneratorMetadata, and Microsoft.Extensions.AI.OpenAI builds that metadata from the string you passed to GetEmbeddingClient — so the stamp is openai/<deployment-name>, whatever model sits behind the deployment. Repoint a deployment at a different model and every vector in the index is stale while still stamped current: ReindexStaleAsync sees one unchanged name and finds nothing to do. Where a deployment's model can change under you, pin the identity yourself:

using Rag.NET.DependencyInjection;

// UseEmbeddingVersioning ships in Rag.NET.Storage.Sqlite
services.AddRagNet(rag => rag
.UseEmbeddingVersioning(o => o.ModelId = "text-embedding-3-small@2024-05"));

Vector dimensions are the deployment's, not the default's. vectorDimensions on the store call has to match what the embedding deployment returns — 1536 for text-embedding-3-small, 3072 for text-embedding-3-large — and on a store that bakes the dimension into its schema it cannot be changed afterwards. Vector stores has the per-store detail.

Cost tracking estimates when the provider is quiet. UseCostBudgeting (Resilience) records real token counts when the response reports both input and output usage, and otherwise estimates both sides with the tiktoken cl100k tokenizer. Azure OpenAI deployments report usage. For catalogue models that do not, the ledger still moves — on an estimate produced by an OpenAI tokenizer, which is approximate for a model that does not tokenize like one. Budgets enforced against those numbers are approximate in the same measure.

  • Getting Started — the same six steps with OpenAI in this page's place
  • Choosing packages — what else a given pipeline needs
  • Resilience — retries, fallback chains and cost budgets around the model boundary
  • Security — what sits between your prompt and the model