Skip to content
Open
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
11 changes: 8 additions & 3 deletions 03-AIPatternsAndApplications/01-embeddings-semantic-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ _Click the image to watch the video_
Traditional search works by matching keywords. If you search for "running shoes," it finds documents containing those exact words.

But what if someone searches for:

- "sneakers for jogging"
- "athletic footwear"
- "shoes for my morning run"

These all mean the same thing, but keyword search would miss them.

**Semantic search** solves this by understanding *meaning*, not just words.
**Semantic search** solves this by understanding _meaning_, not just words.

---

Expand All @@ -33,7 +34,7 @@ An **embedding** is a way to represent text (or images, or other data) as a list
"I love pizza" → [0.12, -0.45, 0.89, 0.23, -0.67, ...]
```

These numbers capture the *meaning* of the text. Similar meanings produce similar numbers.
These numbers capture the _meaning_ of the text. Similar meanings produce similar numbers.

> **Learn more:** [Understand embeddings in .NET](https://learn.microsoft.com/dotnet/ai/conceptual/embeddings) explains how embedding models work and when to use them.

Expand Down Expand Up @@ -129,6 +130,7 @@ static float CosineSimilarity(ReadOnlyMemory<float> a, ReadOnlyMemory<float> b)
```

Cosine similarity returns a value between -1 and 1:

- **1.0** = Identical meaning
- **0.0** = Unrelated
- **-1.0** = Opposite meaning
Expand Down Expand Up @@ -185,10 +187,12 @@ Notice: "sneakers for jogging" ranks high even though it shares no words with th
## Part 4: Vector Stores

For real applications, you don't want to:

1. Generate embeddings for your entire dataset on every search
2. Loop through all embeddings to find matches

**Vector stores** (or vector databases) solve this by:

- Storing embeddings persistently
- Using optimized algorithms for fast similarity search
- Scaling to millions of documents
Expand Down Expand Up @@ -218,6 +222,7 @@ public class Product
```

Key attributes:

- `[VectorStoreKey]` - Unique identifier
- `[VectorStoreData]` - Searchable/filterable data
- `[VectorStoreVector(dimensions, distanceFunction)]` - The embedding vector
Expand Down Expand Up @@ -337,7 +342,7 @@ The code stays the same - just swap the vector store implementation!

| Sample | Description |
|--------|-------------|
| [RAGSimple-02MEAIVectorsMemory](../samples/CoreSamples/RAGSimple-02MEAIVectorsMemory/) | In-memory vector store with Azure OpenAI |
| [RAGSimple-02MEAIVectorsMemory](../samples/CoreSamples/RAGSimple-02MEAIVectorsMemory/) | Local sqlite-vec vector store with Azure OpenAI |
| [RAGSimple-03MEAIVectorsAISearch](../samples/CoreSamples/RAGSimple-03MEAIVectorsAISearch/) | Azure AI Search vector store |
| [RAGSimple-04MEAIVectorsQdrant](../samples/CoreSamples/RAGSimple-04MEAIVectorsQdrant/) | Qdrant vector store |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ Language models are trained on data from a specific point in time. They don't kn
RAG has two phases:

### Phase 1: Retrieval

Find relevant documents from your knowledge base using semantic search.

### Phase 2: Generation

Pass those documents to the AI along with the user's question.

![How RAG Works](./images/how-rag-works.png)
Expand Down Expand Up @@ -366,7 +368,8 @@ IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator =

| Sample | Description |
|--------|-------------|
| [RAGSimple-02MEAIVectorsMemory](../samples/CoreSamples/RAGSimple-02MEAIVectorsMemory/) | RAG with in-memory vectors |
| [RAGSimple-02MEAIVectorsMemory](../samples/CoreSamples/RAGSimple-02MEAIVectorsMemory/) | RAG with a local sqlite-vec store |
| [DataIngestion-01-Simple](../samples/CoreSamples/DataIngestion-01-Simple/) | Document ingestion pipeline (read → chunk → embed → store) with sqlite-vec |
| [RAGSimple-03MEAIVectorsAISearch](../samples/CoreSamples/RAGSimple-03MEAIVectorsAISearch/) | RAG with Azure AI Search |
| [RAGSimple-04MEAIVectorsQdrant](../samples/CoreSamples/RAGSimple-04MEAIVectorsQdrant/) | RAG with Qdrant |
| [RAGSimple-15Ollama-DeepSeekR1](../samples/CoreSamples/RAGSimple-15Ollama-DeepSeekR1/) | RAG with DeepSeek reasoning |
Expand Down
7 changes: 6 additions & 1 deletion 03-AIPatternsAndApplications/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,23 @@ _Click the image to watch the video_
This lesson is divided into five parts:

### [Part 1: Embeddings and Semantic Search](./01-embeddings-semantic-search.md)

Understand how AI represents meaning as vectors and build search that finds by intent.

### [Part 2: Retrieval-Augmented Generation (RAG)](./02-retrieval-augmented-generation.md)

Ground AI responses in your own documents and data.

### [Part 3: Vision and Document Understanding](./03-vision-document-understanding.md)

Process images, PDFs, and visual content with multimodal AI.

### [Part 4: Combining Patterns](./04-combining-patterns.md)

Build sophisticated applications that combine multiple patterns.

### [Part 5: Local Model Runners](./05-LocalModelRunners.md)

Run AI models locally using AI Toolkit, Docker Model Runner, and Foundry Local.

---
Expand Down Expand Up @@ -65,7 +70,7 @@ All code samples for this lesson are located in the **[`samples/CoreSamples/`](.

| Category | Samples | Description |
|----------|---------|-------------|
| **Embeddings & RAG** | [RAGSimple-02MEAIVectorsMemory](../samples/CoreSamples/RAGSimple-02MEAIVectorsMemory/) | In-memory vector store |
| **Embeddings & RAG** | [RAGSimple-02MEAIVectorsMemory](../samples/CoreSamples/RAGSimple-02MEAIVectorsMemory/) | Local sqlite-vec vector store |
| | [RAGSimple-03MEAIVectorsAISearch](../samples/CoreSamples/RAGSimple-03MEAIVectorsAISearch/) | Azure AI Search |
| | [RAGSimple-04MEAIVectorsQdrant](../samples/CoreSamples/RAGSimple-04MEAIVectorsQdrant/) | Qdrant vector store |
| **Vision** | [Vision-01MEAI-AzureOpenAI](../samples/CoreSamples/Vision-01MEAI-AzureOpenAI/) | Vision with Azure OpenAI |
Expand Down
1 change: 1 addition & 0 deletions 04-AgentsWithMAF/02-agents-with-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ static string GetOrderStatus(string orderId) { ... }
|--------|-------------|
| [MAF-BackgroundResponses-02-Tools](../samples/MAF/MAF-BackgroundResponses-02-Tools/) | Agent with function tools |
| [MAF-BackgroundResponses-03-Complex](../samples/MAF/MAF-BackgroundResponses-03-Complex/) | Complex tool scenarios |
| [MAF-ImageGen-03-Foundry](../samples/MAF/MAF-ImageGen-03-Foundry/) | Agent with an image-generation tool (GPT-Image-2 on Microsoft Foundry) |

---

Expand Down
1 change: 1 addition & 0 deletions 04-AgentsWithMAF/03-multi-agent-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ catch (AgentException ex)
| [MAF02](../samples/MAF/MAF02/) | Simple sequential workflow |
| [MAF-MultiAgents](../samples/MAF/MAF-MultiAgents/) | Multi-model orchestration |
| [MAF-MultiAgents-Factory-01](../samples/MAF/MAF-MultiAgents-Factory-01/) | Advanced workflow patterns |
| [A2A-01](../samples/MAF/A2A-01/) | Agent-to-Agent (A2A) protocol: host a MAF agent and call it as a remote agent |

---

Expand Down
2 changes: 2 additions & 0 deletions 04-AgentsWithMAF/04-model-context-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ Need to integrate with external service?
|--------|-------------|
| [MCP-01-HuggingFace](../samples/CoreSamples/MCP-01-HuggingFace/) | MCP with Azure OpenAI |
| [MCP-02-HuggingFace-Ollama](../samples/CoreSamples/MCP-02-HuggingFace-Ollama/) | MCP with local Ollama models |
| [MCP-03-MicrosoftLearn](../samples/CoreSamples/MCP-03-MicrosoftLearn/) | Before/after demo grounding answers with the Microsoft Learn MCP server |
| [MAF-MCP-01](../samples/MAF/MAF-MCP-01/) | A MAF agent that owns Microsoft Learn MCP tools |
| Aspire MCP Sample | See [04-PracticalSamples](../04-PracticalSamples/) for Aspire + MCP integration |

---
Expand Down
66 changes: 66 additions & 0 deletions samples/CoreSamples/BasicChat-05AIFoundryModels/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# BasicChat-05AIFoundryModels — Foundry model swap + Integrated Security

A Foundations / MEAI sample. One `IChatClient`, one
**Microsoft Foundry** endpoint, many models — swap the model by changing a single string,
and authenticate the way you should in Foundry: **Integrated Security (Microsoft Entra ID)**.

## What it shows

1. **Chat with a deployment name + endpoint + apikey** — the familiar key-based path.
2. **Foundry model swap** — change only `AzureOpenAI:Deployment` to switch
**`gpt-5.5` → `grok-4`** (same code, same endpoint, same `IChatClient`).
3. **Integrated Security (recommended)** — drop the key entirely and use
`AzureCliCredential` / Microsoft Entra ID. This is the way to work in Foundry.
4. **Simple one-shot prompt** — the sample asks a hardcoded question so the demo
stays focused on `IChatClient`, model swapping, and auth.

> Then the demo switches to **`BasicChat-03Ollama`** to run the same `IChatClient`
> against a local model — zero changes to app logic.

## Config (User Secrets)

```bash
cd samples/CoreSamples/BasicChat-05AIFoundryModels

# Foundry endpoint (one endpoint hosts many models)
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://<your-foundry-resource>.openai.azure.com/"

# Model = deployment name. Swap this to change models (gpt-5.5 -> grok-4 -> ...)
dotnet user-secrets set "AzureOpenAI:Deployment" "gpt-5.5"

# Auth mode: "integrated" (recommended) or "apikey"
dotnet user-secrets set "AzureOpenAI:AuthMode" "integrated"

# Only needed when AuthMode=apikey
dotnet user-secrets set "AzureOpenAI:ApiKey" "<your-key>"
```

## Run the demo (suggested order)

```bash
# 1. Key auth with gpt-5.5
dotnet user-secrets set "AzureOpenAI:AuthMode" "apikey"
dotnet user-secrets set "AzureOpenAI:Deployment" "gpt-5.5"
dotnet run app.cs

# 2. Foundry model swap: gpt-5.5 -> grok-4 (same endpoint, same code)
dotnet user-secrets set "AzureOpenAI:Deployment" "grok-4"
dotnet run app.cs

# 3. Integrated Security (the recommended way) — no key in config
dotnet user-secrets set "AzureOpenAI:AuthMode" "integrated"
az login
dotnet run app.cs
```

The console prints the active **model** and **auth** mode, runs one hardcoded question,
and labels the answer with `AI [<deployment>]:` so the model swap is visible at runtime.

## Suggested prompt

Use this question to make the model identity visible after the deployment swap:

`what is your model name?`

> Integrated Security requires the signed-in account to have the **Azure AI Developer**
> role on the Foundry resource.
77 changes: 37 additions & 40 deletions samples/CoreSamples/BasicChat-05AIFoundryModels/app.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,49 @@
#:package Microsoft.Extensions.Configuration.UserSecrets@10.0.3
#:property UserSecretsId=genai-beginners-dotnet

using Azure.AI.OpenAI;
using Azure;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using System.Text;

var config = new ConfigurationBuilder().AddUserSecrets<Program>().Build();

// One Microsoft Foundry endpoint can host MANY models behind it.
var endpoint = config["AzureOpenAI:Endpoint"]
?? throw new InvalidOperationException("Set AzureOpenAI:Endpoint in User Secrets. See: https://github.com/microsoft/Generative-AI-for-beginners-dotnet/blob/main/01-IntroductionToGenerativeAI/setup-azure-openai.md");
var deploymentName = config["AzureOpenAI:Deployment"] ?? "gpt-5-mini";

IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
// Swap the model by changing ONLY the deployment name.
// In Microsoft Foundry you deploy several models behind the same endpoint, e.g.:
// gpt-5.5 -> grok-4.3 -> phi-4 -> ... (same code, same endpoint, just this string).
//var deploymentName = config["AzureOpenAI:Deployment"] ?? "gpt-5-mini";
var deploymentName = "Kimi-K2.6";

Comment on lines +20 to +25
// Auth mode. "integrated" = Microsoft Entra ID (recommended for Foundry: no keys to leak).
// "apikey" = key from the Foundry portal (endpoint + deployment + apikey).
var authMode = config["AzureOpenAI:AuthMode"] ?? "integrated";

AzureOpenAIClient azureClient = authMode.Equals("apikey", StringComparison.OrdinalIgnoreCase)
// Key auth: deployment name + endpoint + apikey.
? new AzureOpenAIClient(new Uri(endpoint),
new AzureKeyCredential(config["AzureOpenAI:ApiKey"]
?? throw new InvalidOperationException("Set AzureOpenAI:ApiKey in User Secrets when AuthMode=apikey.")))
// Integrated Security (recommended): Microsoft Entra ID via `az login`, no secrets.
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());

IChatClient client = azureClient
.GetChatClient(deploymentName)
.AsIChatClient()
.AsBuilder()
.Build();

var history = new List<ChatMessage>
{
new(ChatRole.Assistant, "You are a useful chatbot. If you don't know an answer, say 'I don't know!'. Always reply in a funny way. Use emojis if possible.")
};

while (true)
{
Console.Write("Q: ");
var userQ = Console.ReadLine();
if (string.IsNullOrEmpty(userQ))
{
break;
}
history.Add(new ChatMessage(ChatRole.User, userQ));

var sb = new StringBuilder();
var result = client.GetStreamingResponseAsync(history);
Console.Write("AI: ");
await foreach (var item in result)
{
// validate if the item is null or has no contents
if (item == null || item.Contents.Count == 0)
{
continue; // skip to the next item if it's null or empty
}
sb.Append(item);
Console.Write(item.Contents[0].ToString());
}
Console.WriteLine();

history.Add(new ChatMessage(ChatRole.Assistant, sb.ToString()));
}
.AsIChatClient();

Console.WriteLine($"Microsoft Foundry chat · model: {deploymentName} · auth: {authMode}");
Console.WriteLine("(swap the model with AzureOpenAI:Deployment, e.g. gpt-5.5 -> grok-4)");
Console.WriteLine("(switch auth with AzureOpenAI:AuthMode = apikey or integrated)");
Console.WriteLine();

var question = "what is your model name?";
Console.WriteLine($"Q: {question}");

var response = await client.GetResponseAsync(question);

Console.WriteLine();
Console.WriteLine($"AI [{deploymentName}]: {response.Text}");
28 changes: 28 additions & 0 deletions samples/CoreSamples/CoreGenerativeAITechniques.sln
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCP-02-HuggingFace-Ollama",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VideoGeneration-AzureSora2-01", "VideoGeneration-AzureSora2-01\VideoGeneration-AzureSora2-01.csproj", "{8E3D5B50-B346-43C5-B71C-43C4427B39C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCP-03-MicrosoftLearn", "MCP-03-MicrosoftLearn\MCP-03-MicrosoftLearn.csproj", "{06279A2D-4582-4854-ADDB-FE81DEEC5443}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataIngestion-01-Simple", "DataIngestion-01-Simple\DataIngestion-01-Simple.csproj", "{E5E21F6D-230A-4862-9B38-AAE41242B25C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -327,6 +331,30 @@ Global
{8E3D5B50-B346-43C5-B71C-43C4427B39C9}.Release|x64.Build.0 = Release|Any CPU
{8E3D5B50-B346-43C5-B71C-43C4427B39C9}.Release|x86.ActiveCfg = Release|Any CPU
{8E3D5B50-B346-43C5-B71C-43C4427B39C9}.Release|x86.Build.0 = Release|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Debug|x64.ActiveCfg = Debug|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Debug|x64.Build.0 = Debug|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Debug|x86.ActiveCfg = Debug|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Debug|x86.Build.0 = Debug|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Release|Any CPU.Build.0 = Release|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Release|x64.ActiveCfg = Release|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Release|x64.Build.0 = Release|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Release|x86.ActiveCfg = Release|Any CPU
{06279A2D-4582-4854-ADDB-FE81DEEC5443}.Release|x86.Build.0 = Release|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Debug|x64.ActiveCfg = Debug|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Debug|x64.Build.0 = Debug|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Debug|x86.ActiveCfg = Debug|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Debug|x86.Build.0 = Debug|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Release|Any CPU.Build.0 = Release|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Release|x64.ActiveCfg = Release|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Release|x64.Build.0 = Release|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Release|x86.ActiveCfg = Release|Any CPU
{E5E21F6D-230A-4862-9B38-AAE41242B25C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>DataIngestion_01_Simple</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>genai-beginners-dotnet</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<None Include="data\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.18.0" />
<PackageReference Include="ElBruno.Connectors.SqliteVec" Version="0.5.2" />
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.AI" Version="10.6.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.6.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.DataIngestion" Version="10.6.0-preview.1.26261.4" />
<PackageReference Include="Microsoft.Extensions.DataIngestion.Markdig" Version="10.6.0-preview.1.26261.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
<PackageReference Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="3.0.0-preview.26160.2" />
</ItemGroup>

</Project>
Loading
Loading