Compare commits

..
Author SHA1 Message Date
1ab800435b Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-06 13:01:49 -07:00
288e04c0c7 Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-02 10:22:28 -07:00
81a65dd886 Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-02 10:22:19 -07:00
21e7e6c034 Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-02 10:22:09 -07:00
b197e97d8e Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-02 10:21:53 -07:00
3d149433b7 Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-02 10:21:41 -07:00
a2faa62725 Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-02 10:21:25 -07:00
0f13a19fef Update README.md
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-04-02 10:21:14 -07:00
Shawn HenryandGitHub c4c54cfc67 Revise agent examples in README.md
Updated examples for creating agents using OpenAI and Azure AI, and updated Important notice
2026-04-02 10:03:25 -07:00
426 changed files with 2314 additions and 16579 deletions
-137
View File
@@ -1,137 +0,0 @@
#
# Runs the .NET sample verification tool, which builds and executes sample projects
# and verifies their output using deterministic checks and AI-powered verification.
#
# Results are displayed as a GitHub Job Summary and the CSV report is uploaded as an artifact.
#
name: dotnet-verify-samples
on:
workflow_dispatch:
inputs:
category:
description: "Sample category to run (blank for all)"
required: false
type: choice
options:
- ""
- "01-get-started"
- "02-agents"
- "03-workflows"
parallelism:
description: "Max parallel sample runs"
required: false
default: "8"
type: string
schedule:
- cron: "0 6 * * 1-5" # Weekdays at 6:00 UTC
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
jobs:
verify-samples:
runs-on: ubuntu-latest
environment: 'integration'
timeout-minutes: 90
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
sparse-checkout: |
.
.github
dotnet
python
declarative-agents
- name: Setup dotnet
uses: actions/setup-dotnet@v5.2.0
with:
global-json-file: ${{ github.workspace }}/dotnet/global.json
- name: Azure CLI Login
if: github.event_name != 'pull_request'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Generate filtered solution
shell: pwsh
run: |
./dotnet/eng/scripts/New-FilteredSolution.ps1 `
-Solution dotnet/agent-framework-dotnet.slnx `
-TargetFramework net10.0 `
-Configuration Debug `
-OutputPath dotnet/filtered.slnx `
-Verbose
- name: Build solution
shell: bash
run: dotnet build dotnet/filtered.slnx -f net10.0 --warnaserror
- name: Run verify-samples
id: verify
working-directory: dotnet
shell: bash
run: |
CATEGORY_ARG=""
if [ -n "$CATEGORY_INPUT" ]; then
CATEGORY_ARG="--category $CATEGORY_INPUT"
fi
dotnet run --project eng/verify-samples -- \
$CATEGORY_ARG \
--parallel "$PARALLELISM" \
--md results.md \
--csv results.csv \
--log results.log
env:
CATEGORY_INPUT: ${{ github.event.inputs.category || '' }}
PARALLELISM: ${{ github.event.inputs.parallelism || '8' }}
# OpenAI Models
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_CHAT_MODEL_NAME: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_REASONING_MODEL_NAME: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
# Azure OpenAI Models
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }}
# Azure AI Foundry
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }}
AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }}
- name: Write Job Summary
if: always()
working-directory: dotnet
shell: bash
run: |
if [ -f results.md ]; then
cat results.md >> "$GITHUB_STEP_SUMMARY"
else
echo "⚠️ No results.md generated — verify-samples may have failed to start." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload results
if: always()
uses: actions/upload-artifact@v7
with:
name: verify-samples-results
path: |
dotnet/results.csv
dotnet/results.log
if-no-files-found: warn
- name: Fail if samples failed
if: always() && steps.verify.outcome == 'failure'
shell: bash
run: exit 1
+8 -14
View File
@@ -115,13 +115,12 @@ jobs:
-m "not integration"
--timeout=120 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Surface failing tests
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
@@ -164,7 +163,6 @@ jobs:
-n logical --dist worksteal
--timeout=120 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Test OpenAI samples
timeout-minutes: 10
@@ -175,7 +173,7 @@ jobs:
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
@@ -227,7 +225,6 @@ jobs:
-n logical --dist worksteal
--timeout=120 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Test Azure samples
timeout-minutes: 10
@@ -238,7 +235,7 @@ jobs:
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
@@ -288,7 +285,6 @@ jobs:
-n logical --dist worksteal
--timeout=120 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Stop local MCP server
if: always()
@@ -314,7 +310,7 @@ jobs:
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
@@ -379,13 +375,12 @@ jobs:
-x
--timeout=360 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Surface failing tests
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
@@ -435,13 +430,12 @@ jobs:
-n logical --dist worksteal
--timeout=120 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Surface failing tests
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
@@ -495,13 +489,13 @@ jobs:
echo "Cosmos DB emulator did not become ready in time." >&2
exit 1
- name: Test with pytest (Cosmos integration)
run: uv run --directory packages/azure-cosmos poe integration-tests -n logical --dist worksteal --timeout=120 --session-timeout=900 --timeout_method thread --retries 2 --retry-delay 5 --junitxml=pytest.xml
run: uv run --directory packages/azure-cosmos poe integration-tests -n logical --dist worksteal --timeout=120 --session-timeout=900 --timeout_method thread --retries 2 --retry-delay 5
working-directory: ./python
- name: Surface failing tests
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
+2 -2
View File
@@ -40,7 +40,7 @@ jobs:
UV_CACHE_DIR: /tmp/.uv-cache
# Unit tests
- name: Run all tests
run: uv run poe test -A --junitxml=pytest.xml
run: uv run poe test -A
working-directory: ./python
# Surface failing tests
@@ -48,7 +48,7 @@ jobs:
if: always()
uses: pmeier/pytest-results-action@v0.7.2
with:
path: ./python/pytest.xml
path: ./python/**.xml
summary: true
display-options: fEX
fail-on-empty: false
-5
View File
@@ -47,8 +47,6 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
pytest.xml
python-coverage.xml
*.cover
*.py,cover
.hypothesis/
@@ -232,6 +230,3 @@ local.settings.json
# Database files
*.db
python/dotnet-ref
# Generated filtered solution files (created by eng/scripts/New-FilteredSolution.ps1)
dotnet/filtered-*.slnx
+2 -2
View File
@@ -28,7 +28,7 @@ Welcome to Microsoft's comprehensive multi-language framework for building, orch
Python
```bash
pip install agent-framework
pip install agent-framework --pre
# This will install all sub-packages, see `python/packages` for individual packages.
# It may take a minute on first install on Windows.
```
@@ -90,7 +90,7 @@ Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-commu
Create a simple Azure Responses Agent that writes a haiku about the Microsoft Agent Framework
```python
# pip install agent-framework
# pip install agent-framework --pre
# Use `az login` to authenticate with Azure CLI
import os
import asyncio
+1 -13
View File
@@ -9,16 +9,9 @@ The `verify-samples` project (`dotnet/eng/verify-samples/`) is an automated tool
## Running verify-samples
**Important:** By default, samples must be pre-built before running verify-samples. Build the solution first, or pass `--build` to build samples during the run:
```bash
cd dotnet
dotnet build agent-framework-dotnet.slnx -f net10.0
```
Then run verify-samples:
```bash
# Run all samples across all categories
dotnet run --project eng/verify-samples -- --log results.log --csv results.csv
@@ -31,12 +24,8 @@ dotnet run --project eng/verify-samples -- Agent_Step02_StructuredOutput Agent_S
# Control parallelism (default 8)
dotnet run --project eng/verify-samples -- --parallel 8 --log results.log
# Build samples during run (skips the need for a prior build step)
# This may cause build conflicts as multiple samples are built in parallel, so use with caution
dotnet run --project eng/verify-samples -- --build --log results.log
# Combine options
dotnet run --project eng/verify-samples -- --category 03-workflows --parallel 4 --log results.log --csv results.csv --md results.md
dotnet run --project eng/verify-samples -- --category 03-workflows --parallel 4 --log results.log --csv results.csv
```
### Required Environment Variables
@@ -51,7 +40,6 @@ Individual samples require their own env vars (e.g., `AZURE_AI_PROJECT_ENDPOINT`
- `--log results.log` — detailed per-sample log with stdout/stderr, AI reasoning, and a summary
- `--csv results.csv` — tabular summary with Sample, ProjectPath, Status, FailedChecks, and Failures columns
- `--md results.md` — Markdown summary with results table and collapsible failure details (suitable for GitHub PR comments)
## Sample Categories
+1 -1
View File
@@ -36,7 +36,7 @@ using types like `IChatClient`, `FunctionInvokingChatClient`, `AITool`, `AIFunct
- **Async**: Use `Async` suffix for methods returning `Task`/`ValueTask`
- **Private classes**: Should be `sealed` unless subclassed
- **Config**: Read from environment variables with `UPPER_SNAKE_CASE` naming
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking; test methods returning `Task`/`ValueTask` must use the `Async` suffix.
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking
## Key Design Principles
+1 -1
View File
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<!-- Aspire.* -->
<PackageVersion Include="Anthropic" Version="12.11.0" />
<PackageVersion Include="Anthropic" Version="12.8.0" />
<PackageVersion Include="Anthropic.Foundry" Version="0.4.2" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
-3
View File
@@ -106,9 +106,6 @@
<File Path="samples/02-agents/AgentSkills/README.md" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Agent_Step01_FileBasedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Agent_Step02_CodeDefinedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
+2 -2
View File
@@ -246,7 +246,7 @@ internal static class AgentsSamples
ExpectedOutputDescription =
[
"The output should contain information about both the current time and the weather in Seattle.",
"The weather information should be similar to: cloudy with a high of 15°C. Exact phrasing may vary.",
"The weather information should reference the plugin result: cloudy with a high of 15°C.",
"The output should not contain error messages or stack traces.",
],
},
@@ -521,7 +521,7 @@ internal static class AgentsSamples
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
[
"The output should contain multiple joke responses showing a multi-turn conversation.",
"The output should demonstrate server-side conversation sessions with non-streaming and streaming turns.",
"The output should not contain error messages or stack traces.",
],
},
@@ -1,98 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
namespace VerifySamples;
/// <summary>
/// Writes a Markdown summary of sample verification results.
/// </summary>
internal static class MarkdownResultWriter
{
/// <summary>
/// Writes the results to a Markdown file at the specified path.
/// </summary>
public static async Task WriteAsync(
string path,
IReadOnlyList<VerificationResult> orderedResults,
IReadOnlyList<(string Name, string Reason)> skipped,
TimeSpan elapsed)
{
var passCount = orderedResults.Count(r => r.Passed);
var failCount = orderedResults.Count(r => !r.Passed);
var sb = new StringBuilder();
sb.AppendLine("# Sample Verification Results");
sb.AppendLine();
sb.AppendLine($"**{passCount} passed, {failCount} failed, {skipped.Count} skipped** | Elapsed: {elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}");
sb.AppendLine();
// Results table
sb.AppendLine("| Sample | Status | Failed Checks | Failures |");
sb.AppendLine("|--------|--------|---------------|----------|");
foreach (var result in orderedResults)
{
var status = result.Passed ? "✅ PASSED" : "❌ FAILED";
var failedChecks = result.Failures.Count;
var failures = MdEscape(string.Join("; ", result.Failures));
sb.AppendLine($"| {MdEscape(result.SampleName)} | {status} | {failedChecks} | {failures} |");
}
foreach (var (name, reason) in skipped)
{
sb.AppendLine($"| {MdEscape(name)} | ⏭️ SKIPPED | 0 | {MdEscape(reason)} |");
}
// Collapsible AI reasoning details for failures
var failures2 = orderedResults.Where(r => !r.Passed && !string.IsNullOrEmpty(r.AIReasoning)).ToList();
if (failures2.Count > 0)
{
sb.AppendLine();
sb.AppendLine("## Failure Details");
sb.AppendLine();
foreach (var result in failures2)
{
sb.AppendLine($"<details><summary><strong>{HtmlEscape(result.SampleName)}</strong></summary>");
sb.AppendLine();
if (result.Failures.Count > 0)
{
foreach (var failure in result.Failures)
{
sb.AppendLine($"- {MdEscape(failure)}");
}
sb.AppendLine();
}
sb.AppendLine("**AI Reasoning:**");
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(result.AIReasoning);
sb.AppendLine("```");
sb.AppendLine();
sb.AppendLine("</details>");
sb.AppendLine();
}
}
await File.WriteAllTextAsync(path, sb.ToString());
}
/// <summary>
/// Escapes pipe characters and newlines for use inside Markdown table cells.
/// </summary>
private static string MdEscape(string value)
{
return value.Replace("|", "\\|").Replace("\n", " ").Replace("\r", "");
}
/// <summary>
/// Escapes HTML special characters for use inside HTML tags.
/// </summary>
private static string HtmlEscape(string value)
{
return value.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;");
}
}
+1 -12
View File
@@ -13,10 +13,6 @@
// dotnet run -- --parallel 16 # Run up to 16 samples concurrently
// dotnet run -- --log results.log # Write sequential log to file
// dotnet run -- --csv results.csv # Write CSV summary to file
// dotnet run -- --md results.md # Write Markdown summary to file
// dotnet run -- --build # Build samples during run (default: --no-build)
// Note: By default, this tool expects sample build outputs to already exist.
// Pre-build the solution before running, or pass --build to avoid missing build output failures.
//
// Required environment variables (for AI-powered samples):
// AZURE_OPENAI_ENDPOINT
@@ -66,7 +62,7 @@ try
// Run all samples
var reporter = new ConsoleReporter();
var verifier = new SampleVerifier(chatClient);
var orchestrator = new VerificationOrchestrator(verifier, reporter, dotnetRoot, TimeSpan.FromMinutes(3), logWriter, buildSamples: options.BuildSamples);
var orchestrator = new VerificationOrchestrator(verifier, reporter, dotnetRoot, TimeSpan.FromMinutes(3), logWriter);
var run = await orchestrator.RunAllAsync(options.Samples, options.MaxParallelism);
@@ -94,13 +90,6 @@ try
Console.WriteLine($"CSV written to: {options.CsvFilePath}");
}
// Write Markdown summary
if (options.MarkdownFilePath is not null)
{
await MarkdownResultWriter.WriteAsync(options.MarkdownFilePath, orderedResults, run.Skipped, stopwatch.Elapsed);
Console.WriteLine($"Markdown written to: {options.MarkdownFilePath}");
}
return orderedResults.Any(r => !r.Passed) ? 1 : 0;
}
finally
+2 -11
View File
@@ -20,32 +20,23 @@ internal static class SampleRunner
{
/// <summary>
/// Runs <c>dotnet run --framework net10.0</c> in the given project directory.
/// When <paramref name="build"/> is false (the default), <c>--no-build</c> is passed
/// to skip building, assuming the project was pre-built.
/// </summary>
public static Task<SampleRunResult> RunAsync(
string projectPath,
TimeSpan timeout,
bool build = false,
CancellationToken cancellationToken = default)
=> RunAsync(projectPath, DotnetRunArgs(build), timeout, inputs: null, inputDelayMs: 0, cancellationToken: cancellationToken);
=> RunAsync(projectPath, "run --framework net10.0", timeout, inputs: null, inputDelayMs: 0, cancellationToken: cancellationToken);
/// <summary>
/// Runs <c>dotnet run --framework net10.0</c> with stdin inputs.
/// When <paramref name="build"/> is false (the default), <c>--no-build</c> is passed
/// to skip building, assuming the project was pre-built.
/// </summary>
public static Task<SampleRunResult> RunAsync(
string projectPath,
TimeSpan timeout,
string?[]? inputs,
int inputDelayMs = 2000,
bool build = false,
CancellationToken cancellationToken = default)
=> RunAsync(projectPath, DotnetRunArgs(build), timeout, inputs, inputDelayMs, cancellationToken);
private static string DotnetRunArgs(bool build) =>
$"run {(build ? "" : "--no-build")} --framework net10.0";
=> RunAsync(projectPath, "run --framework net10.0", timeout, inputs, inputDelayMs, cancellationToken);
/// <summary>
/// Runs an arbitrary <c>dotnet</c> command in the given working directory.
+11 -38
View File
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
@@ -28,19 +27,11 @@ internal sealed class SampleVerifier
instructions: """
You are a test output verifier. You will be given:
1. The actual stdout output of a program
2. The stderr output (if any)
3. A list of expectations about what the output should contain or demonstrate
2. A list of expectations about what the output should contain or demonstrate
Your job is to determine whether the actual output satisfies each expectation.
Be reasonable the output comes from an LLM so exact wording won't match, but the
semantic intent should be clearly satisfied.
In your response, you MUST:
- Always provide ai_reasoning with a brief overall assessment.
- Always provide exactly one entry in expectation_results for each expectation,
in the same order as the input list.
- For each expectation_results entry, echo the expectation text in the expectation
field and explain your assessment in the detail field, citing evidence from the output.
""",
name: "OutputVerifier");
}
@@ -87,7 +78,7 @@ internal sealed class SampleVerifier
}
else
{
var aiResult = await this.VerifyWithAIAsync(run.Stdout, run.Stderr, sample.ExpectedOutputDescription);
var aiResult = await this.VerifyWithAIAsync(run.Stdout, sample.ExpectedOutputDescription);
aiReasoning = aiResult.Reasoning;
foreach (var unmet in aiResult.UnmetExpectations)
@@ -109,28 +100,16 @@ internal sealed class SampleVerifier
}
private async Task<(string Reasoning, List<string> UnmetExpectations)> VerifyWithAIAsync(
string stdout,
string stderr,
string actualOutput,
string[] expectations)
{
var expectationList = string.Join("\n", expectations.Select((e, i) => $" {i + 1}. {e}"));
var stderrSection = string.IsNullOrWhiteSpace(stderr)
? ""
: $"""
Stderr output:
---
{Truncate(stderr, 2000)}
---
""";
var prompt = $"""
Actual program output:
---
{Truncate(stdout, 4000)}
{Truncate(actualOutput, 4000)}
---
{stderrSection}
Expectations to verify:
{expectationList}
@@ -147,9 +126,7 @@ internal sealed class SampleVerifier
return ($"AI verification returned null result. Raw: {response.Text}", ["AI verification returned null result."]);
}
var reasoning = string.IsNullOrWhiteSpace(result.AIReasoning)
? "(no reasoning provided)"
: result.AIReasoning;
var reasoning = result.Reasoning ?? "(no reasoning provided)";
// Collect unmet expectations as individual failures
var unmet = new List<string>();
@@ -197,14 +174,12 @@ internal sealed class AIVerificationResponse
public bool Pass { get; set; }
/// <summary>Brief explanation of the overall assessment.</summary>
[JsonPropertyName("ai_reasoning")]
[Description("Always required. Brief explanation of the overall assessment, covering all expectations.")]
public string AIReasoning { get; set; } = string.Empty;
[JsonPropertyName("reasoning")]
public string? Reasoning { get; set; }
/// <summary>Per-expectation results.</summary>
[JsonPropertyName("expectation_results")]
[Description("Always required. One entry per expectation, in the same order as the input list.")]
public List<ExpectationResult> ExpectationResults { get; set; } = [];
public List<ExpectationResult>? ExpectationResults { get; set; }
}
/// <summary>
@@ -215,8 +190,7 @@ internal sealed class ExpectationResult
{
/// <summary>The expectation text that was evaluated.</summary>
[JsonPropertyName("expectation")]
[Description("Echo back the expectation text being evaluated.")]
public string Expectation { get; set; } = string.Empty;
public string? Expectation { get; set; }
/// <summary>Whether this expectation was met.</summary>
[JsonPropertyName("met")]
@@ -224,6 +198,5 @@ internal sealed class ExpectationResult
/// <summary>Detail about how the expectation was or was not met.</summary>
[JsonPropertyName("detail")]
[Description("Explain how the expectation was or was not met, citing specific evidence from the output.")]
public string Detail { get; set; } = string.Empty;
public string? Detail { get; set; }
}
@@ -14,22 +14,19 @@ internal sealed class VerificationOrchestrator
private readonly LogFileWriter? _logWriter;
private readonly string _dotnetRoot;
private readonly TimeSpan _timeout;
private readonly bool _buildSamples;
public VerificationOrchestrator(
SampleVerifier verifier,
ConsoleReporter reporter,
string dotnetRoot,
TimeSpan timeout,
LogFileWriter? logWriter = null,
bool buildSamples = false)
LogFileWriter? logWriter = null)
{
this._verifier = verifier;
this._reporter = reporter;
this._logWriter = logWriter;
this._dotnetRoot = dotnetRoot;
this._timeout = timeout;
this._buildSamples = buildSamples;
}
/// <summary>
@@ -139,8 +136,8 @@ internal sealed class VerificationOrchestrator
var projectPath = Path.Combine(this._dotnetRoot, sample.ProjectPath);
var run = sample.Inputs.Length > 0
? await SampleRunner.RunAsync(projectPath, this._timeout, sample.Inputs, sample.InputDelayMs, build: this._buildSamples)
: await SampleRunner.RunAsync(projectPath, this._timeout, build: this._buildSamples);
? await SampleRunner.RunAsync(projectPath, this._timeout, sample.Inputs, sample.InputDelayMs)
: await SampleRunner.RunAsync(projectPath, this._timeout);
log.Add($"[{sample.Name}] Completed ({run.Elapsed.TotalSeconds:F1}s, exit={run.ExitCode})");
this._reporter.WriteLineWithPrefix(
@@ -17,22 +17,11 @@ internal sealed class VerifyOptions
/// </summary>
public string? CsvFilePath { get; init; }
/// <summary>
/// Path to write a Markdown summary file, or <c>null</c> to skip.
/// </summary>
public string? MarkdownFilePath { get; init; }
/// <summary>
/// Path to write a sequential log file, or <c>null</c> to skip.
/// </summary>
public string? LogFilePath { get; init; }
/// <summary>
/// When true, samples are built as part of <c>dotnet run</c>.
/// When false (the default), <c>--no-build</c> is passed, assuming a prior build step.
/// </summary>
public bool BuildSamples { get; init; }
/// <summary>
/// The filtered list of samples to process.
/// </summary>
@@ -60,8 +49,6 @@ internal sealed class VerifyOptions
var categoryFilter = ExtractArg(argList, "--category");
var logFilePath = ExtractArg(argList, "--log");
var csvFilePath = ExtractArg(argList, "--csv");
var markdownFilePath = ExtractArg(argList, "--md");
var buildSamples = ExtractFlag(argList, "--build");
int maxParallelism = 8;
var parallelArg = ExtractArg(argList, "--parallel");
@@ -111,8 +98,6 @@ internal sealed class VerifyOptions
MaxParallelism = maxParallelism,
LogFilePath = logFilePath,
CsvFilePath = csvFilePath,
MarkdownFilePath = markdownFilePath,
BuildSamples = buildSamples,
Samples = samples,
};
}
@@ -136,16 +121,4 @@ internal sealed class VerifyOptions
list.RemoveRange(idx, 2);
return value;
}
private static bool ExtractFlag(List<string> list, string flag)
{
var idx = list.IndexOf(flag);
if (idx < 0)
{
return false;
}
list.RemoveAt(idx);
return true;
}
}
+8 -8
View File
@@ -1,21 +1,21 @@
<Project>
<PropertyGroup>
<!-- Central version prefix - applies to all nuget packages. -->
<VersionPrefix>1.1.0</VersionPrefix>
<RCNumber>1</RCNumber>
<VersionPrefix>1.0.0</VersionPrefix>
<RCNumber>6</RCNumber>
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260410.1</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260410.1</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260402.1</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260402.1</PackageVersion>
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
<GitTag>1.1.0</GitTag>
<GitTag>1.0.0</GitTag>
<Configurations>Debug;Release;Publish</Configurations>
<IsPackable>true</IsPackable>
<!-- Package validation. Baseline Version should be the latest version available on NuGet. -->
<PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
<!-- Enable validation for GA packages -->
<EnablePackageValidation Condition="'$(IsReleased)' == 'true'">true</EnablePackageValidation>
<PackageValidationBaselineVersion>1.0.0-rc5</PackageValidationBaselineVersion>
<!-- Enable validation for RC packages and GA packages -->
<EnablePackageValidation Condition="'$(IsReleaseCandidate)' == 'true' OR '$(IsReleased)' == 'true'">true</EnablePackageValidation>
<!-- Validate assembly attributes only for Publish builds -->
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003</NoWarn>
<!-- Do not validate reference assemblies -->
@@ -8,7 +8,7 @@ using Microsoft.Agents.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -11,7 +11,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
@@ -8,7 +8,7 @@ using Microsoft.Agents.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -16,7 +16,7 @@ using OpenAI.Chat;
using SampleApp;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -8,7 +8,7 @@
//
// Environment variables:
// AZURE_OPENAI_ENDPOINT
// AZURE_OPENAI_DEPLOYMENT_NAME (defaults to "gpt-5.4-mini")
// AZURE_OPENAI_DEPLOYMENT_NAME (defaults to "gpt-4o-mini")
//
// Run with: func start
// Then call: POST http://localhost:7071/api/agents/HostedAgent/run
@@ -23,7 +23,7 @@ using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Set up an AI agent following the standard Microsoft Agent Framework pattern.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+1 -1
View File
@@ -15,7 +15,7 @@ All samples require the following environment variables:
```bash
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
```
For the client samples, you can optionally set:
@@ -97,7 +97,7 @@ Console.WriteLine("""
""");
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT environment variable is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Log application startup
appLogger.LogInformation("OpenTelemetry Aspire Demo application started");
@@ -34,7 +34,7 @@ graph TD
Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource.
@@ -9,7 +9,7 @@ using Azure.Identity;
using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerName = "Joker";
const string JokerInstructions = "You are good at telling jokes.";
@@ -22,5 +22,5 @@ Set the following environment variables:
```powershell
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -9,7 +9,7 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerName = "JokerAgent";
@@ -22,5 +22,5 @@ Set the following environment variables:
```powershell
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -8,7 +8,7 @@ using Microsoft.Agents.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -12,5 +12,5 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -9,7 +9,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Responses;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -12,5 +12,5 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -8,7 +8,7 @@ using OpenAI;
using OpenAI.Chat;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
AIAgent agent = new OpenAIClient(
apiKey)
@@ -9,5 +9,5 @@ Set the following environment variables:
```powershell
$env:OPENAI_API_KEY="*****" # Replace with your OpenAI api key
$env:OPENAI_CHAT_MODEL_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:OPENAI_CHAT_MODEL_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -7,7 +7,7 @@ using OpenAI;
using OpenAI.Responses;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
AIAgent agent = new OpenAIClient(
apiKey)
@@ -9,5 +9,5 @@ Set the following environment variables:
```powershell
$env:OPENAI_API_KEY="*****" # Replace with your OpenAI api key
$env:OPENAI_CHAT_MODEL_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:OPENAI_CHAT_MODEL_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -16,7 +16,7 @@ using OpenAI.Responses;
// --- Configuration ---
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// --- Skills Provider ---
// Discovers skills from the 'skills' directory containing SKILL.md files.
@@ -6,7 +6,7 @@ This sample demonstrates how to use **file-based Agent Skills** with a `ChatClie
- Discovering skills from `SKILL.md` files on disk via `AgentFileSkillsSource`
- The progressive disclosure pattern: advertise → load → read resources → run scripts
- Using the `AgentSkillsProvider` constructor with a skill directory path and script runner
- Using the `AgentSkillsProvider` constructor with a skill directory path and script executor
- Running file-based scripts (Python) via a subprocess-based executor
## Skills Included
@@ -30,7 +30,7 @@ Converts between common units (miles↔km, pounds↔kg) using a multiplication f
```bash
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
```
### Run
@@ -16,7 +16,7 @@ using OpenAI.Responses;
// --- Configuration ---
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// --- Build the code-defined skill ---
var unitConverterSkill = new AgentInlineSkill(
@@ -31,7 +31,7 @@ Converts between common units using multiplication factors. Defined entirely in
```bash
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
```
### Run
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);MAAI001;IDE0051</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,111 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to define Agent Skills as C# classes using AgentClassSkill
// with attributes for automatic script and resource discovery.
using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
// --- Configuration ---
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// --- Class-Based Skill ---
// Instantiate the skill class.
var unitConverter = new UnitConverterSkill();
// --- Skills Provider ---
var skillsProvider = new AgentSkillsProvider(unitConverter);
// --- Agent Setup ---
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "UnitConverterAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant that can convert units.",
},
AIContextProviders = [skillsProvider],
},
model: deploymentName);
// --- Example: Unit conversion ---
Console.WriteLine("Converting units with class-based skills");
Console.WriteLine(new string('-', 60));
AgentResponse response = await agent.RunAsync(
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?");
Console.WriteLine($"Agent: {response.Text}");
/// <summary>
/// A unit-converter skill defined as a C# class using attributes for discovery.
/// </summary>
/// <remarks>
/// Properties annotated with <see cref="AgentSkillResourceAttribute"/> are automatically
/// discovered as skill resources, and methods annotated with <see cref="AgentSkillScriptAttribute"/>
/// are automatically discovered as skill scripts. Alternatively,
/// <see cref="AgentSkill.Resources"/> and <see cref="AgentSkill.Scripts"/> can be overridden.
/// </remarks>
internal sealed class UnitConverterSkill : AgentClassSkill<UnitConverterSkill>
{
/// <inheritdoc/>
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"unit-converter",
"Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.");
/// <inheritdoc/>
protected override string Instructions => """
Use this skill when the user asks to convert between units.
1. Review the conversion-table resource to find the factor for the requested conversion.
2. Use the convert script, passing the value and factor from the table.
3. Present the result clearly with both units.
""";
/// <summary>
/// Gets the <see cref="JsonSerializerOptions"/> used to marshal parameters and return values
/// for scripts and resources.
/// </summary>
/// <remarks>
/// This override is not necessary for this sample, but can be used to provide custom
/// serialization options, for example a source-generated <c>JsonTypeInfoResolver</c>
/// for Native AOT compatibility.
/// </remarks>
protected override JsonSerializerOptions? SerializerOptions => null;
/// <summary>
/// A conversion table resource providing multiplication factors.
/// </summary>
[AgentSkillResource("conversion-table")]
[Description("Lookup table of multiplication factors for common unit conversions.")]
public string ConversionTable => """
# Conversion Tables
Formula: **result = value × factor**
| From | To | Factor |
|-------------|-------------|----------|
| miles | kilometers | 1.60934 |
| kilometers | miles | 0.621371 |
| pounds | kilograms | 0.453592 |
| kilograms | pounds | 2.20462 |
""";
/// <summary>
/// Converts a value by the given factor.
/// </summary>
[AgentSkillScript("convert")]
[Description("Multiplies a value by a conversion factor and returns the result as JSON.")]
private static string ConvertUnits(double value, double factor)
{
double result = Math.Round(value * factor, 4);
return JsonSerializer.Serialize(new { value, factor, result });
}
}
@@ -1,53 +0,0 @@
# Class-Based Agent Skills Sample
This sample demonstrates how to define **Agent Skills as C# classes** using `AgentClassSkill`
with **attributes** for automatic script and resource discovery.
## What it demonstrates
- Creating skills as classes that extend `AgentClassSkill`
- Using `[AgentSkillResource]` on properties to define resources
- Using `[AgentSkillScript]` on methods to define scripts
- Automatic discovery (no need to override `Resources`/`Scripts`)
- Using the `AgentSkillsProvider` constructor with class-based skills
- Overriding `SerializerOptions` for Native AOT compatibility
## Skills Included
### unit-converter (class-based)
A `UnitConverterSkill` class that converts between common units. Defined in `Program.cs`:
- `conversion-table` — Static resource with factor table
- `convert` — Script that performs `value × factor` conversion
## Running the Sample
### Prerequisites
- .NET 10.0 SDK
- Azure OpenAI endpoint with a deployed model
### Setup
```bash
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
```
### Run
```bash
dotnet run
```
### Expected Output
```
Converting units with class-based skills
------------------------------------------------------------
Agent: Here are your conversions:
1. **26.2 miles → 42.16 km** (a marathon distance)
2. **75 kg → 165.35 lbs**
```
@@ -1,32 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);MAAI001;IDE0051</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SubprocessScriptRunner.cs" Link="SubprocessScriptRunner.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
<!-- Copy skills directory to output -->
<ItemGroup>
<None Include="skills\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -1,150 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates an advanced scenario: combining multiple skill types in a single agent
// using AgentSkillsProviderBuilder. The builder is designed for cases where the simple
// AgentSkillsProvider constructors are insufficient — for example, when you need to mix skill
// sources, apply filtering, or configure cross-cutting options in one place.
//
// Three different skill sources are registered here:
// 1. File-based: unit-converter (miles↔km, pounds↔kg) from SKILL.md on disk
// 2. Code-defined: volume-converter (gallons↔liters) using AgentInlineSkill
// 3. Class-based: temperature-converter (°F↔°C↔K) using AgentClassSkill with attributes
//
// For simpler, single-source scenarios, see the earlier steps in this sample series
// (e.g., Step01 for file-based, Step02 for code-defined, Step03 for class-based).
using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
// --- Configuration ---
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// --- 1. Code-Defined Skill: volume-converter ---
var volumeConverterSkill = new AgentInlineSkill(
name: "volume-converter",
description: "Convert between gallons and liters using a multiplication factor.",
instructions: """
Use this skill when the user asks to convert between gallons and liters.
1. Review the volume-conversion-table resource to find the correct factor.
2. Use the convert-volume script, passing the value and factor.
""")
.AddResource("volume-conversion-table",
"""
# Volume Conversion Table
Formula: **result = value × factor**
| From | To | Factor |
|---------|---------|---------|
| gallons | liters | 3.78541 |
| liters | gallons | 0.264172|
""")
.AddScript("convert-volume", (double value, double factor) =>
{
double result = Math.Round(value * factor, 4);
return JsonSerializer.Serialize(new { value, factor, result });
});
// --- 2. Class-Based Skill: temperature-converter ---
var temperatureConverter = new TemperatureConverterSkill();
// --- 3. Build provider combining all three source types ---
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills")) // File-based: unit-converter
.UseSkill(volumeConverterSkill) // Code-defined: volume-converter
.UseSkill(temperatureConverter) // Class-based: temperature-converter
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync)
.Build();
// --- Agent Setup ---
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "MultiConverterAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant that can convert units, volumes, and temperatures.",
},
AIContextProviders = [skillsProvider],
},
model: deploymentName);
// --- Example: Use all three skills ---
Console.WriteLine("Converting with mixed skills (file + code + class)");
Console.WriteLine(new string('-', 60));
AgentResponse response = await agent.RunAsync(
"I need three conversions: " +
"1) How many kilometers is a marathon (26.2 miles)? " +
"2) How many liters is a 5-gallon bucket? " +
"3) What is 98.6°F in Celsius?");
Console.WriteLine($"Agent: {response.Text}");
/// <summary>
/// A temperature-converter skill defined as a C# class using attributes for discovery.
/// </summary>
/// <remarks>
/// Properties annotated with <see cref="AgentSkillResourceAttribute"/> are automatically
/// discovered as skill resources, and methods annotated with <see cref="AgentSkillScriptAttribute"/>
/// are automatically discovered as skill scripts.
/// </remarks>
internal sealed class TemperatureConverterSkill : AgentClassSkill<TemperatureConverterSkill>
{
/// <inheritdoc/>
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"temperature-converter",
"Convert between temperature scales (Fahrenheit, Celsius, Kelvin).");
/// <inheritdoc/>
protected override string Instructions => """
Use this skill when the user asks to convert temperatures.
1. Review the temperature-conversion-formulas resource for the correct formula.
2. Use the convert-temperature script, passing the value, source scale, and target scale.
3. Present the result clearly with both temperature scales.
""";
/// <summary>
/// A reference table of temperature conversion formulas.
/// </summary>
[AgentSkillResource("temperature-conversion-formulas")]
[Description("Formulas for converting between Fahrenheit, Celsius, and Kelvin.")]
public string ConversionFormulas => """
# Temperature Conversion Formulas
| From | To | Formula |
|-------------|-------------|---------------------------|
| Fahrenheit | Celsius | °C = (°F 32) × 5/9 |
| Celsius | Fahrenheit | °F = (°C × 9/5) + 32 |
| Celsius | Kelvin | K = °C + 273.15 |
| Kelvin | Celsius | °C = K 273.15 |
""";
/// <summary>
/// Converts a temperature value between scales.
/// </summary>
[AgentSkillScript("convert-temperature")]
[Description("Converts a temperature value from one scale to another.")]
private static string ConvertTemperature(double value, string from, string to)
{
double result = (from.ToUpperInvariant(), to.ToUpperInvariant()) switch
{
("FAHRENHEIT", "CELSIUS") => Math.Round((value - 32) * 5.0 / 9.0, 2),
("CELSIUS", "FAHRENHEIT") => Math.Round(value * 9.0 / 5.0 + 32, 2),
("CELSIUS", "KELVIN") => Math.Round(value + 273.15, 2),
("KELVIN", "CELSIUS") => Math.Round(value - 273.15, 2),
_ => throw new ArgumentException($"Unsupported conversion: {from} → {to}")
};
return JsonSerializer.Serialize(new { value, from, to, result });
}
}
@@ -1,67 +0,0 @@
# Mixed Agent Skills Sample (Advanced)
This sample demonstrates an **advanced scenario**: combining multiple skill types in a single agent using `AgentSkillsProviderBuilder`.
> **Tip:** For simpler, single-source scenarios, use the `AgentSkillsProvider` constructors directly — see [Step01](../Agent_Step01_FileBasedSkills/) (file-based), [Step02](../Agent_Step02_CodeDefinedSkills/) (code-defined), or [Step03](../Agent_Step03_ClassBasedSkills/) (class-based).
## What it demonstrates
- Combining file-based, code-defined, and class-based skills in one provider
- Using `UseFileSkill` and `UseSkill` on the builder to register different skill types
- Aggregating skills from all sources into a single provider with automatic deduplication
## When to use `AgentSkillsProviderBuilder`
The builder is intended for advanced scenarios where the simple `AgentSkillsProvider` constructors are insufficient:
| Scenario | Builder method |
|----------|---------------|
| **Mixed skill types** — combine file-based, code-defined, and class-based skills | `UseFileSkill` + `UseSkill` / `UseSkills` |
| **Multiple file script runners** — use different script runners for different file skill directories | `UseFileSkill` / `UseFileSkills` with per-source `scriptRunner` |
| **Skill filtering** — include/exclude skills using a predicate | `UseFilter(predicate)` |
## Skills Included
### unit-converter (file-based)
Discovered from `skills/unit-converter/SKILL.md` on disk. Converts miles↔km, pounds↔kg.
### volume-converter (code-defined)
Defined as `AgentInlineSkill` in `Program.cs`. Converts gallons↔liters.
### temperature-converter (class-based)
Defined as `TemperatureConverterSkill` class in `Program.cs`. Converts °F↔°C↔K.
## Running the Sample
### Prerequisites
- .NET 10.0 SDK
- Azure OpenAI endpoint with a deployed model
### Setup
```bash
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
```
### Run
```bash
dotnet run
```
### Expected Output
```
Converting with mixed skills (file + code + class)
------------------------------------------------------------
Agent: Here are your conversions:
1. **26.2 miles → 42.16 km** (a marathon distance)
2. **5 gallons → 18.93 liters**
3. **98.6°F → 37.0°C**
```
@@ -1,11 +0,0 @@
---
name: unit-converter
description: Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.
---
## Usage
When the user requests a unit conversion:
1. First, review `references/unit-conversion-table.md` to find the correct factor
2. Run the `scripts/convert-units.py` script with `--value <number> --factor <factor>` (e.g. `--value 26.2 --factor 1.60934`)
3. Present the converted value clearly with both units
@@ -1,10 +0,0 @@
# Conversion Tables
Formula: **result = value × factor**
| From | To | Factor |
|-------------|-------------|----------|
| miles | kilometers | 1.60934 |
| kilometers | miles | 0.621371 |
| pounds | kilograms | 0.453592 |
| kilograms | pounds | 2.20462 |
@@ -1,29 +0,0 @@
# Unit conversion script
# Converts a value using a multiplication factor: result = value × factor
#
# Usage:
# python scripts/convert-units.py --value 26.2 --factor 1.60934
# python scripts/convert-units.py --value 75 --factor 2.20462
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert a value using a multiplication factor.",
epilog="Examples:\n"
" python scripts/convert-units.py --value 26.2 --factor 1.60934\n"
" python scripts/convert-units.py --value 75 --factor 2.20462",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--value", type=float, required=True, help="The numeric value to convert.")
parser.add_argument("--factor", type=float, required=True, help="The conversion factor from the table.")
args = parser.parse_args()
result = round(args.value * args.factor, 4)
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
if __name__ == "__main__":
main()
@@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);MAAI001;CA1812;IDE0051</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,210 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use Dependency Injection (DI) with Agent Skills.
// It shows two approaches side-by-side, each handling a different conversion domain:
//
// 1. Code-defined skill (AgentInlineSkill) — converts distances (miles ↔ kilometers).
// Resources and scripts are inline delegates that resolve services from IServiceProvider.
//
// 2. Class-based skill (AgentClassSkill) — converts weights (pounds ↔ kilograms).
// Resources and scripts are encapsulated in a class, also resolving services from IServiceProvider.
//
// Both skills share the same ConversionService registered in the DI container,
// showing that DI works identically regardless of how the skill is defined.
// When prompted with a question spanning both domains, the agent uses both skills.
using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI.Responses;
// --- Configuration ---
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// --- DI Container ---
// Register application services that skill resources and scripts can resolve at execution time.
ServiceCollection services = new();
services.AddSingleton<ConversionService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
// =====================================================================
// Approach 1: Code-Defined Skill with DI (AgentInlineSkill)
// =====================================================================
// Handles distance conversions (miles ↔ kilometers).
// Resources and scripts are inline delegates. Each delegate can declare
// an IServiceProvider parameter that the framework injects automatically.
var distanceSkill = new AgentInlineSkill(
name: "distance-converter",
description: "Convert between distance units. Use when asked to convert miles to kilometers or kilometers to miles.",
instructions: """
Use this skill when the user asks to convert between distance units (miles and kilometers).
1. Review the distance-table resource to find the factor for the requested conversion.
2. Use the convert script, passing the value and factor from the table.
""")
.AddResource("distance-table", (IServiceProvider serviceProvider) =>
{
var service = serviceProvider.GetRequiredService<ConversionService>();
return service.GetDistanceTable();
})
.AddScript("convert", (double value, double factor, IServiceProvider serviceProvider) =>
{
var service = serviceProvider.GetRequiredService<ConversionService>();
return service.Convert(value, factor);
});
// =====================================================================
// Approach 2: Class-Based Skill with DI (AgentClassSkill)
// =====================================================================
// Handles weight conversions (pounds ↔ kilograms).
// Resources and scripts are discovered via reflection using attributes.
// Methods with an IServiceProvider parameter receive DI automatically.
//
// Alternatively, class-based skills can accept dependencies through their
// constructor. Register the skill class itself in the ServiceCollection and
// resolve it from the container:
//
// services.AddSingleton<WeightConverterSkill>();
// var weightSkill = serviceProvider.GetRequiredService<WeightConverterSkill>();
var weightSkill = new WeightConverterSkill();
// --- Skills Provider ---
// Both skills are registered with the same provider so the agent can use either one.
var skillsProvider = new AgentSkillsProvider(distanceSkill, weightSkill);
// --- Agent Setup ---
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(
options: new ChatClientAgentOptions
{
Name = "UnitConverterAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant that can convert units.",
},
AIContextProviders = [skillsProvider],
},
model: deploymentName,
services: serviceProvider);
// --- Example: Unit conversion ---
// This prompt spans both domains, so the agent will use both skills.
Console.WriteLine("Converting units with DI-powered skills");
Console.WriteLine(new string('-', 60));
AgentResponse response = await agent.RunAsync(
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?");
Console.WriteLine($"Agent: {response.Text}");
// ---------------------------------------------------------------------------
// Class-Based Skill
// ---------------------------------------------------------------------------
/// <summary>
/// A weight-converter skill defined as a C# class that uses Dependency Injection.
/// </summary>
/// <remarks>
/// This skill resolves <see cref="ConversionService"/> from the DI container
/// in both its resource and script methods. Methods with an <see cref="IServiceProvider"/>
/// parameter are automatically injected by the framework. Properties and methods annotated
/// with <see cref="AgentSkillResourceAttribute"/> and <see cref="AgentSkillScriptAttribute"/>
/// are automatically discovered via reflection.
/// </remarks>
internal sealed class WeightConverterSkill : AgentClassSkill<WeightConverterSkill>
{
/// <inheritdoc/>
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"weight-converter",
"Convert between weight units. Use when asked to convert pounds to kilograms or kilograms to pounds.");
/// <inheritdoc/>
protected override string Instructions => """
Use this skill when the user asks to convert between weight units (pounds and kilograms).
1. Review the weight-table resource to find the factor for the requested conversion.
2. Use the convert script, passing the value and factor from the table.
3. Present the result clearly with both units.
""";
/// <summary>
/// Returns the weight conversion table from the DI-registered <see cref="ConversionService"/>.
/// </summary>
[AgentSkillResource("weight-table")]
[Description("Lookup table of multiplication factors for weight conversions.")]
private static string GetWeightTable(IServiceProvider serviceProvider)
{
var service = serviceProvider.GetRequiredService<ConversionService>();
return service.GetWeightTable();
}
/// <summary>
/// Converts a value by the given factor using the DI-registered <see cref="ConversionService"/>.
/// </summary>
[AgentSkillScript("convert")]
[Description("Multiplies a value by a conversion factor and returns the result as JSON.")]
private static string Convert(double value, double factor, IServiceProvider serviceProvider)
{
var service = serviceProvider.GetRequiredService<ConversionService>();
return service.Convert(value, factor);
}
}
// ---------------------------------------------------------------------------
// Services
// ---------------------------------------------------------------------------
/// <summary>
/// Provides conversion rates between units.
/// In a real application this could call an external API, read from a database,
/// or apply time-varying exchange rates.
/// </summary>
internal sealed class ConversionService
{
/// <summary>
/// Returns a markdown table of supported distance conversions.
/// </summary>
public string GetDistanceTable() =>
"""
# Distance Conversions
Formula: **result = value × factor**
| From | To | Factor |
|-------------|-------------|----------|
| miles | kilometers | 1.60934 |
| kilometers | miles | 0.621371 |
""";
/// <summary>
/// Returns a markdown table of supported weight conversions.
/// </summary>
public string GetWeightTable() =>
"""
# Weight Conversions
Formula: **result = value × factor**
| From | To | Factor |
|-------------|-------------|----------|
| pounds | kilograms | 0.453592 |
| kilograms | pounds | 2.20462 |
""";
/// <summary>
/// Converts a value by the given factor and returns a JSON result.
/// </summary>
public string Convert(double value, double factor)
{
double result = Math.Round(value * factor, 4);
return JsonSerializer.Serialize(new { value, factor, result });
}
}
@@ -1,65 +0,0 @@
# Agent Skills with Dependency Injection
This sample demonstrates how to use **Dependency Injection (DI)** with Agent Skills. It shows two approaches side-by-side, each handling a different conversion domain:
1. **Code-defined skill** (`AgentInlineSkill`) — converts **distances** (miles ↔ kilometers)
2. **Class-based skill** (`AgentClassSkill`) — converts **weights** (pounds ↔ kilograms)
Both skills resolve the same `ConversionService` from the DI container. When prompted with a question spanning both domains, the agent uses both skills.
## What It Shows
- Registering application services in a `ServiceCollection`
- Defining a **code-defined** skill (distance converter) with resources and scripts that resolve services from `IServiceProvider`
- Defining a **class-based** skill (weight converter) with resources and scripts that resolve services from `IServiceProvider`
- Passing the built `IServiceProvider` to the agent so skills can access DI services at execution time
- Running a single prompt that exercises both skills to show they work together
## How It Works
1. A `ConversionService` is registered as a singleton in the DI container
2. **Code-defined skill**: An `AgentInlineSkill` for distance conversions declares `IServiceProvider` as a parameter in its `AddResource` and `AddScript` delegates — the framework injects it automatically
3. **Class-based skill**: A `WeightConverterSkill` class extends `AgentClassSkill` for weight conversions and uses `CreateResource`/`CreateScript` factory methods with `IServiceProvider` parameters
4. Both skills resolve `ConversionService` from the provider — one for distance tables, the other for weight tables
5. A single agent is created with both skills registered, and the service provider flows through to skill execution
> **Tip:** Class-based skills can also accept dependencies through their **constructor**. Register the skill class in the `ServiceCollection` and resolve it from the container instead of calling `new` directly. This is useful when the skill itself needs injected services beyond what the resource/script delegates use.
## How It Differs from Other Samples
| Sample | Skill Type | DI Support |
|--------|------------|------------|
| [Step02](../Agent_Step02_CodeDefinedSkills/) | Code-defined (`AgentInlineSkill`) | No — static resources |
| [Step03](../Agent_Step03_ClassBasedSkills/) | Class-based (`AgentClassSkill`) | No — static resources |
| **Step05 (this)** | **Both code-defined and class-based** | **Yes — DI via `IServiceProvider`** |
## Prerequisites
- .NET 10
- An Azure OpenAI deployment
## Configuration
Set the following environment variables:
| Variable | Description |
|---|---|
| `AZURE_OPENAI_ENDPOINT` | Your Azure OpenAI endpoint URL |
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Model deployment name (defaults to `gpt-5.4-mini`) |
## Running the Sample
```bash
dotnet run
```
### Expected Output
```
Converting units with DI-powered skills
------------------------------------------------------------
Agent: Here are your conversions:
1. **26.2 miles → 42.16 km** (a marathon distance)
2. **75 kg → 165.35 lbs**
```
+10 -23
View File
@@ -6,32 +6,19 @@ Samples demonstrating Agent Skills capabilities. Each sample shows a different w
|--------|-------------|
| [Agent_Step01_FileBasedSkills](Agent_Step01_FileBasedSkills/) | Define skills as `SKILL.md` files on disk with reference documents. Uses a unit-converter skill. |
| [Agent_Step02_CodeDefinedSkills](Agent_Step02_CodeDefinedSkills/) | Define skills entirely in C# code using `AgentInlineSkill`, with static/dynamic resources and scripts. |
| [Agent_Step03_ClassBasedSkills](Agent_Step03_ClassBasedSkills/) | Define skills as C# classes using `AgentClassSkill`. |
| [Agent_Step04_MixedSkills](Agent_Step04_MixedSkills/) | **(Advanced)** Combine file-based, code-defined, and class-based skills using `AgentSkillsProviderBuilder`. |
| [Agent_Step05_SkillsWithDI](Agent_Step05_SkillsWithDI/) | Use Dependency Injection with both code-defined (`AgentInlineSkill`) and class-based (`AgentClassSkill`) skills. |
## Key Concepts
### Skill Types
### File-Based vs Code-Defined Skills
| Aspect | File-Based | Code-Defined | Class-Based |
|--------|-----------|--------------|-------------|
| Definition | `SKILL.md` files on disk | `AgentInlineSkill` instances in C# | Classes extending `AgentClassSkill` |
| Resources | All files in skill directory (filtered by extension) | `AddResource` (static value or delegate-backed) | `CreateResource` factory methods |
| Scripts | Supported via script runner delegate | `AddScript` delegates | `CreateScript` factory methods |
| Discovery | Automatic from directory path | Explicit via constructor | Explicit via constructor |
| Dynamic content | No (static files only) | Yes (factory delegates) | Yes (factory delegates) |
| Sharing pattern | Copy skill directory | Inline or shared instances | Package in shared assemblies/NuGet |
| DI support | No | Yes (via `IServiceProvider` parameter) | Yes (via `IServiceProvider` parameter) |
| Aspect | File-Based | Code-Defined |
|--------|-----------|--------------|
| Definition | `SKILL.md` files on disk | `AgentInlineSkill` instances in C# |
| Resources | All files in skill directory (filtered by extension) | `AddResource` (static value or delegate-backed) |
| Scripts | Supported via script executor delegate | `AddScript` delegates |
| Discovery | Automatic from directory path | Explicit via constructor |
| Dynamic content | No (static files only) | Yes (factory delegates) |
| Reusability | Copy skill directory | Inline or shared instances |
### `AgentSkillsProvider` vs `AgentSkillsProviderBuilder`
For single-source scenarios, use the `AgentSkillsProvider` constructors directly. To combine multiple skill types, use the `AgentSkillsProviderBuilder`.
For single-source scenarios, use the `AgentSkillsProvider` constructors directly — they accept a skill directory path, a set of skills, or a custom source.
Use `AgentSkillsProviderBuilder` for advanced scenarios where simple constructors are insufficient:
- **Mixed skill types** — combine file-based, code-defined, and class-based skills in one provider
- **Multiple file script runners** — use different script runners for different file skill directories
- **Skill filtering** — include or exclude skills using a predicate
See [Agent_Step04_MixedSkills](Agent_Step04_MixedSkills/) for a working example.
@@ -12,7 +12,7 @@ using Microsoft.SemanticKernel.Connectors.InMemory;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
// Create a vector store to store the chat messages in.
@@ -14,7 +14,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var mem0ServiceUri = Environment.GetEnvironmentVariable("MEM0_ENDPOINT") ?? throw new InvalidOperationException("MEM0_ENDPOINT is not set.");
var mem0ApiKey = Environment.GetEnvironmentVariable("MEM0_API_KEY") ?? throw new InvalidOperationException("MEM0_API_KEY is not set.");
@@ -15,7 +15,7 @@ using Microsoft.Agents.AI.Foundry;
string foundryEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? "memory-store-sample";
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
// Create an AIProjectClient for Foundry with Azure Identity authentication.
@@ -14,7 +14,7 @@ This sample demonstrates how to create and run an agent that uses Microsoft Foun
## Prerequisites
1. Azure subscription with Microsoft Foundry project
2. Azure OpenAI resource with a chat model deployment (e.g., gpt-5.4-mini) and an embedding model deployment (e.g., text-embedding-ada-002)
2. Azure OpenAI resource with a chat model deployment (e.g., gpt-4o-mini) and an embedding model deployment (e.g., text-embedding-ada-002)
3. .NET 10.0 SDK
4. Azure CLI logged in (`az login`)
@@ -26,7 +26,7 @@ export AZURE_AI_PROJECT_ENDPOINT="https://your-account.services.ai.azure.com/api
export AZURE_AI_MEMORY_STORE_ID="my_memory_store"
# Model deployment names (models deployed in your Foundry project)
export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
export AZURE_AI_EMBEDDING_DEPLOYMENT_NAME="text-embedding-ada-002"
```
@@ -15,7 +15,7 @@ using OpenAI.Chat;
using SampleApp;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -13,7 +13,7 @@ This sample demonstrates how to create a custom `ChatHistoryProvider` that keeps
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- An Azure OpenAI resource with:
- A chat deployment (e.g., `gpt-5.4-mini`)
- A chat deployment (e.g., `gpt-4o-mini`)
- An embedding deployment (e.g., `text-embedding-3-large`)
## Configuration
@@ -23,7 +23,7 @@ Set the following environment variables:
| Variable | Description | Default |
|---|---|---|
| `AZURE_OPENAI_ENDPOINT` | Your Azure OpenAI endpoint URL | *(required)* |
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Chat model deployment name | `gpt-5.4-mini` |
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Chat model deployment name | `gpt-4o-mini` |
| `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME` | Embedding model deployment name | `text-embedding-3-large` |
## Running the Sample
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using OpenAI.Responses;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
AIAgent agent =
new ResponsesClient(new ApiKeyCredential(apiKey))
@@ -7,7 +7,7 @@ using Microsoft.Extensions.AI;
using OpenAI;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5";
var client = new OpenAIClient(apiKey)
.GetResponsesClient()
@@ -7,7 +7,7 @@ using OpenAI.Chat;
using OpenAIChatClientSample;
string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
string model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
string model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
// Create a ChatClient directly from OpenAIClient
ChatClient chatClient = new OpenAIClient(apiKey).GetChatClient(model);
@@ -13,7 +13,7 @@ This sample demonstrates how to create an AI agent directly from an `OpenAI.Chat
1. Set the required environment variables:
```bash
set OPENAI_API_KEY=your_api_key_here
set OPENAI_CHAT_MODEL_NAME=gpt-5.4-mini
set OPENAI_CHAT_MODEL_NAME=gpt-4o-mini
```
2. Run the sample:
@@ -7,7 +7,7 @@ using OpenAI.Responses;
using OpenAIResponseClientSample;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
// Create a ResponsesClient directly from OpenAIClient
ResponsesClient responseClient = new OpenAIClient(apiKey).GetResponsesClient();
@@ -13,7 +13,7 @@ This sample demonstrates how to create an AI agent directly from an `OpenAI.Resp
1. Set the required environment variables:
```bash
set OPENAI_API_KEY=your_api_key_here
set OPENAI_CHAT_MODEL_NAME=gpt-5.4-mini
set OPENAI_CHAT_MODEL_NAME=gpt-4o-mini
```
2. Run the sample:
@@ -15,7 +15,7 @@ using OpenAI.Chat;
using OpenAI.Conversations;
string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
string model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5.4-mini";
string model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
// Create a ConversationClient directly from OpenAIClient
OpenAIClient openAIClient = new(apiKey);
@@ -69,7 +69,7 @@ foreach (ClientResult result in getConversationItemsResults.GetRawPages())
1. Set the required environment variables:
```powershell
$env:OPENAI_API_KEY = "your_api_key_here"
$env:OPENAI_CHAT_MODEL_NAME = "gpt-5.4-mini"
$env:OPENAI_CHAT_MODEL_NAME = "gpt-4o-mini"
```
2. Run the sample:
@@ -15,7 +15,7 @@ using Microsoft.SemanticKernel.Connectors.InMemory;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -14,7 +14,7 @@ using OpenAI.Chat;
using Qdrant.Client;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
var afOverviewUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/overview/index.md";
var afMigrationUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/migration-guide/from-semantic-kernel/index.md";
@@ -23,7 +23,7 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
$env:AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME="text-embedding-3-large" # Optional, defaults to text-embedding-3-large
```
@@ -13,7 +13,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
TextSearchProviderOptions textSearchOptions = new()
{
@@ -14,7 +14,7 @@ using OpenAI.Responses;
using OpenAI.VectorStores;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create an AI Project client and get an OpenAI client that works with the foundry service.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -8,7 +8,7 @@ using Neo4j.AgentFramework.GraphRAG;
using Neo4j.Driver;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var neo4jUri = Environment.GetEnvironmentVariable("NEO4J_URI") ?? throw new InvalidOperationException("NEO4J_URI is not set.");
var neo4jUsername = Environment.GetEnvironmentVariable("NEO4J_USERNAME") ?? "neo4j";
var neo4jPassword = Environment.GetEnvironmentVariable("NEO4J_PASSWORD") ?? throw new InvalidOperationException("NEO4J_PASSWORD is not set.");
@@ -15,7 +15,7 @@ The sample uses a Neo4j fulltext index for retrieval and a Cypher `RetrievalQuer
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
$env:NEO4J_URI="neo4j+s://your-instance.databases.neo4j.io"
$env:NEO4J_USERNAME="neo4j"
$env:NEO4J_PASSWORD="your-password"
@@ -14,7 +14,7 @@ using OpenAI.Chat;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create a sample function tool that the agent can use.
[Description("Get the weather for a given location.")]
@@ -14,7 +14,7 @@ using SampleApp;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create chat client to be used by chat client agents.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -28,7 +28,7 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
## Run the sample
@@ -11,7 +11,7 @@ using Microsoft.Agents.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create the agent
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -18,7 +18,7 @@ using SampleApp;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create a vector store to store the chat messages in.
// Replace this with a vector store implementation of your choice if you want to persist the chat history to disk.
@@ -11,7 +11,7 @@ using OpenTelemetry;
using OpenTelemetry.Trace;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
// Create TracerProvider with console exporter
@@ -12,7 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create a host builder that we will register services with and then run.
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
@@ -11,7 +11,7 @@ using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -22,7 +22,7 @@ To use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector)
1. Open a web browser and navigate to the URL displayed in the terminal. If not opened automatically, this will open the MCP Inspector interface.
1. In the MCP Inspector interface, add the following environment variables to allow your MCP server to access Microsoft Foundry Project to create and run the agent:
- AZURE_AI_PROJECT_ENDPOINT = https://your-resource.openai.azure.com/ # Replace with your Microsoft Foundry Project endpoint
- AZURE_AI_MODEL_DEPLOYMENT_NAME = gpt-5.4-mini # Replace with your model deployment name
- AZURE_AI_MODEL_DEPLOYMENT_NAME = gpt-4o-mini # Replace with your model deployment name
1. Find and click the `Connect` button in the MCP Inspector interface to connect to the MCP server.
1. As soon as the connection is established, open the `Tools` tab in the MCP Inspector interface and select the `Joker` tool from the list.
1. Specify your prompt as a value for the `query` argument, for example: `Tell me a joke about a pirate` and click the `Run Tool` button to run the tool.
@@ -9,7 +9,7 @@ using OpenAI.Chat;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -20,7 +20,7 @@ This sample demonstrates how to use image multi-modality with an AI agent. It sh
Before running this sample, ensure you have:
1. An Azure OpenAI project set up
2. A compatible model deployment (e.g., gpt-5.4-mini)
2. A compatible model deployment (e.g., gpt-4o)
3. Azure CLI installed and authenticated
## Environment Variables
@@ -29,7 +29,7 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Replace with your model deployment name (optional, defaults to gpt-5.4-mini)
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o" # Replace with your model deployment name (optional, defaults to gpt-4o)
```
## Run the sample
@@ -10,7 +10,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
@@ -15,7 +15,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Responses;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5";
var stateStore = new Dictionary<string, JsonElement?>();
@@ -24,5 +24,5 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5" # Optional, defaults to gpt-5
```
@@ -15,7 +15,7 @@ using Microsoft.Extensions.AI;
// Get Microsoft Foundry configuration from environment variables
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
// Get a client to create/retrieve server side agents with
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -27,7 +27,7 @@ Attempting to use function middleware on agents that do not wrap a ChatClientAge
1. Environment variables:
- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: Chat deployment name (optional; defaults to `gpt-5.4-mini`)
- `AZURE_OPENAI_DEPLOYMENT_NAME`: Chat deployment name (optional; defaults to `gpt-4o`)
2. Sign in with Azure CLI (PowerShell):
```powershell
az login
@@ -17,7 +17,7 @@ using Microsoft.Extensions.DependencyInjection;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create a service collection to hold the agent plugin and its dependencies.
ServiceCollection services = new();
@@ -12,7 +12,7 @@ using Microsoft.Extensions.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Construct the agent, and provide a factory to create an in-memory chat message store with a reducer that keeps only the last 2 non-system messages.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -8,7 +8,7 @@ using Microsoft.Agents.AI;
using OpenAI.Responses;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
@@ -23,5 +23,5 @@ Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -10,7 +10,7 @@ using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deepResearchDeploymentName = Environment.GetEnvironmentVariable("AZURE_AI_REASONING_DEPLOYMENT_NAME") ?? "o3-deep-research";
var modelDeploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var modelDeploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
var bingConnectionId = Environment.GetEnvironmentVariable("AZURE_AI_BING_CONNECTION_ID") ?? throw new InvalidOperationException("AZURE_AI_BING_CONNECTION_ID is not set.");
// Configure extended network timeout for long-running Deep Research tasks.
@@ -13,7 +13,7 @@ Before running this sample, ensure you have:
1. A Microsoft Foundry project set up
2. A deep research model deployment (e.g., o3-deep-research)
3. A model deployment (e.g., gpt-5.4-mini)
3. A model deployment (e.g., gpt-4o)
4. A Bing Connection configured in your Microsoft Foundry project
5. Azure CLI installed and authenticated
@@ -45,5 +45,5 @@ $env:AZURE_AI_BING_CONNECTION_ID="/subscriptions/<sub-id>/resourceGroups/<rg>/pr
# Optional, defaults to o3-deep-research
$env:AZURE_AI_REASONING_DEPLOYMENT_NAME="o3-deep-research"
# Optional, defaults to gpt-5.4-mini
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
# Optional, defaults to gpt-4o
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o"
@@ -8,7 +8,7 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create the chat client
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
@@ -18,7 +18,7 @@ using SampleApp;
using MEAI = Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5-mini";
// A sample function to load the next three calendar events for the user.
Func<Task<string[]>> loadNextThreeCalendarEvents = async () =>
@@ -16,7 +16,7 @@ using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid

Some files were not shown because too many files have changed in this diff Show More