Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions samples/cs/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<CentralPackageFloatingVersionsEnabled>true</CentralPackageFloatingVersionsEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="*-*" />
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="1.0.0" />
<PackageVersion Include="Betalgo.Ranul.OpenAI" Version="9.1.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.15" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.15" />
<PackageVersion Include="NAudio" Version="2.2.1" />
<PackageVersion Include="OpenAI" Version="2.5.0" />
</ItemGroup>
</Project>
</Project>
3 changes: 3 additions & 0 deletions samples/cs/embeddings/Embeddings.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="Embeddings.csproj" />
</Solution>
3 changes: 3 additions & 0 deletions samples/cs/rag/rag.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="rag/rag.csproj" />
</Solution>
206 changes: 206 additions & 0 deletions samples/cs/rag/rag/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
using Betalgo.Ranul.OpenAI.ObjectModels.ResponseModels;
using Microsoft.AI.Foundry.Local;
using Microsoft.ML.OnnxRuntimeGenAI;
using static Betalgo.Ranul.OpenAI.ObjectModels.StaticValues.AssistantsStatics.MessageStatics;
using static System.Runtime.InteropServices.JavaScript.JSType;

internal class Program
{
private static async Task Main(string[] args)
{
CancellationToken ct = new CancellationToken();

var config = new Configuration
{
AppName = "foundry_local_rag",
LogLevel = LogLevel.Information
};

// Initialize the singleton instance.
await FoundryLocalManager.CreateAsync(config, Utils.GetAppLogger());
var mgr = FoundryLocalManager.Instance;

// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "") Console.WriteLine();
currentEp = epName;
}
Console.Write($"\r {epName.PadRight(30)} {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();


// Get the model catalog
var catalog = await mgr.GetCatalogAsync();

// Get an embedding model
var embeddingModel = await catalog.GetModelAsync("qwen3-embedding-0.6b") ?? throw new Exception("Embedding model not found");

// Download the model (the method skips download if already cached)
await embeddingModel.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f)
{
Console.WriteLine();
}
});

// Load the model
Console.Write($"Loading embedding model {embeddingModel.Id}...");
await embeddingModel.LoadAsync();


// Get an embedding client
var embeddingClient = await embeddingModel.GetEmbeddingClientAsync();

// Generate embeddings for multiple inputs

// Knowledge base — each string represents a document
var documents = new List<string>
{
"Foundry Local runs AI models directly on your device without cloud connectivity.",
"The Foundry Local SDK supports Python, C#, JavaScript, and Rust.",
"Embedding models convert text into numerical vectors for similarity search.",
"Foundry Local uses ONNX Runtime for efficient model inference on CPUs and GPUs.",
"The model catalog provides pre-optimized models that you can download and run locally.",
"Retrieval-augmented generation grounds model responses in your own data.",
"Vector similarity search finds documents that are semantically close to a query.",
"Chat completions generate natural language responses from a prompt and context.",
};

Console.WriteLine("\n--- Batch Embeddings ---");
var response = await embeddingClient.GenerateEmbeddingsAsync(documents);

Console.WriteLine($"Number of embeddings: {response.Data.Count}");
for (var i = 0; i < response.Data.Count; i++)
{
Console.WriteLine($" [{i}] Dimensions: {response.Data[i].Embedding.Count}");
}

Console.WriteLine($"Indexed {response.Data.Count} documents.");


// Get a model using an alias.
var chatModel = await catalog.GetModelAsync("qwen2.5-0.5b") ?? throw new Exception("Model not found");

// Download the model (the method skips download if already cached)
await chatModel.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f)
{
Console.WriteLine();
}
});

// Load the model
Console.Write($"Loading model {chatModel.Id}...");
await chatModel.LoadAsync();

// <chat_completion>
// Get a chat client
var chatClient = await chatModel.GetChatClientAsync();

Console.WriteLine("\nModels loaded. Ready for questions.");
Console.WriteLine("\nThe knowledge base contains information about:");
Console.WriteLine(" - Foundry Local features and architecture");
Console.WriteLine(" - Supported programming languages");
Console.WriteLine(" - Embedding models and vector search");
Console.WriteLine(" - ONNX Runtime inference");
Console.WriteLine(" - The model catalog");
Console.WriteLine(" - RAG and chat completions");
Console.WriteLine("\nExample questions:");
Console.WriteLine(" \"What programming languages does the SDK support?\"");
Console.WriteLine(" \"How does Foundry Local run models?\"");
Console.WriteLine(" \"What is retrieval-augmented generation?\"");
Console.WriteLine("\nType \"quit\" to exit.\n");

// Interactive query loop
while (true)
{
Console.WriteLine("Question:");
var query = Console.ReadLine()?.Trim();
if (string.IsNullOrEmpty(query) || query.ToLower() == "quit")
{
break;
}

// Embed the query
var queryResponse = await embeddingClient.GenerateEmbeddingAsync(query);
var queryEmbedding = queryResponse.Data[0].Embedding;

// Retrieve the most relevant documents
var results = FindRelevant(queryEmbedding, response.Data, topK: 2);
string context = string.Join("\n", results.Select(r => $"- {documents[r.Item1]}"));

// Build the prompt with retrieved context
string content =
$$"""
Answer the user's question using only the provided context.
If the context doesn't contain enough information, say so.

Context:
{{context}}
""";

// Create chat messages
List<ChatMessage> messages = new()
{
new ChatMessage { Role = "system", Content = content },
new ChatMessage { Role = "user", Content = query }
};

// Get a streaming chat completion response
Console.WriteLine("Answer:");
var streamingResponse = chatClient.CompleteChatStreamingAsync(messages, ct);
await foreach (var chunk in streamingResponse)
{
Console.Write(chunk.Choices[0].Message.Content);
Console.Out.Flush();
}
Console.WriteLine();
}

// Tidy up - unload the models
await chatModel.UnloadAsync();
await embeddingModel.UnloadAsync();
}

private static double CosineSimilarity(List<double> a, List<double> b)
{
double dot = 0;
double norm_a = 0;
double norm_b = 0;

for (int i = 0; i < a.Count; i++)
{
dot += a[i] * b[i];
norm_a += a[i] * a[i];
norm_b += b[i] * b[i];
}

norm_a = (float)Math.Sqrt(norm_a);
norm_b = (float)Math.Sqrt(norm_b);

return norm_a * norm_b != 0 ? dot / (norm_a * norm_b) : 0.0f;
}


private static (int, double)[] FindRelevant(List<double> queryEmbedding, List<EmbeddingResponse> docEmbeddings, int topK = 2)
{
var scores = new List<(int, double)>();
for (int i = 0; i < docEmbeddings.Count; i++)
{
double score = CosineSimilarity(queryEmbedding, docEmbeddings[i].Embedding);
scores.Add((i, score));
}
scores.Sort((x, y) => y.Item2.CompareTo(x.Item2));
return scores.Take(topK).ToArray();
}
}
18 changes: 18 additions & 0 deletions samples/cs/rag/rag/rag.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\Shared\Utils.cs" Link="Utils.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AI.Foundry.Local" />
</ItemGroup>

</Project>
16 changes: 12 additions & 4 deletions samples/cs/tutorial-document-summarizer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,30 @@ await model.DownloadAsync(progress =>
// </init>

// <summarization>
var systemPrompt =
var systemPrompt1 =
"Summarize the following document into concise bullet points. " +
"Focus on the key points and main ideas.";

var systemPrompt2 =
"Summarize the following document in a single, concise paragraph. " +
"Capture the main argument and supporting points.";
Comment on lines +63 to +65

var systemPrompt3 =
"Extract the three most important takeaways from the following document. " +
"Number each takeaway and keep each to one or two sentences.";

// <file_reading>
var target = args.Length > 0 ? args[0] : "document.txt";
var target = args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "document.txt");
// </file_reading>

if (Directory.Exists(target))
{
await SummarizeDirectoryAsync(chatClient, target, systemPrompt, ct);
await SummarizeDirectoryAsync(chatClient, target, systemPrompt1, ct);
}
else
{
Console.WriteLine($"--- {Path.GetFileName(target)} ---");
await SummarizeFileAsync(chatClient, target, systemPrompt, ct);
await SummarizeFileAsync(chatClient, target, systemPrompt1, ct);
}
// </summarization>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
<ItemGroup>
<None Update="document.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions samples/cs/tutorial-document-summarizer/document.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Automated testing is a practice in software development where tests are written and executed
by specialized tools rather than performed manually. There are several categories of automated
tests, including unit tests, integration tests, and end-to-end tests. Unit tests verify that
individual functions or methods behave correctly in isolation. Integration tests check that
multiple components work together as expected. End-to-end tests simulate real user workflows
across the entire application.

Adopting automated testing brings measurable benefits to a development team. It catches
regressions early, before they reach production. It reduces the time spent on repetitive
manual verification after each code change. It serves as living documentation of expected
behavior, which helps new team members understand the codebase. Continuous integration
pipelines rely on automated tests to gate deployments and maintain release quality.

Effective test suites follow a few guiding principles. Tests should be deterministic, meaning
they produce the same result every time they run. Tests should be independent, so that one
failing test does not cascade into false failures elsewhere. Tests should run fast, because
slow tests discourage developers from running them frequently. Finally, tests should be
maintained alongside production code so they stay accurate as the application evolves.
2 changes: 1 addition & 1 deletion samples/cs/verify-winml/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ await candidate.DownloadAsync(progress =>
{
var chatClient = await chosen.GetChatClientAsync();
chatClient.Settings.Temperature = 0;
chatClient.Settings.MaxTokens = 16;
chatClient.Settings.MaxTokens = 500;
var messages = new List<ChatMessage>
{
new() { Role = "system", Content = "You are a helpful assistant." },
Expand Down
3 changes: 3 additions & 0 deletions samples/cs/verify-winml/VerifyWinML.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="VerifyWinML.csproj" />
</Solution>