mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175e8e15a4 | ||
|
|
090b88a956 | ||
|
|
746c7da216 | ||
|
|
f804646239 | ||
|
|
d30103fee6 | ||
|
|
073114d2c1 | ||
|
|
00adc5bf9e | ||
|
|
55ae57c0ed | ||
|
|
d284d96a9e | ||
|
|
e94cfc6aef | ||
|
|
d1a81159de | ||
|
|
9f0dbe5f8d | ||
|
|
3fc1d00026 | ||
|
|
e4defadc79 | ||
|
|
c798cb7a2e | ||
|
|
3446eb8d5d | ||
|
|
5f06b68535 | ||
|
|
524c0216e4 | ||
|
|
281661e409 | ||
|
|
b0613a8ceb | ||
|
|
79b38040e8 | ||
|
|
a356a16568 |
@@ -0,0 +1,122 @@
|
||||
#
|
||||
# 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
|
||||
workflow-samples
|
||||
|
||||
- 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: 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
|
||||
@@ -230,3 +230,6 @@ local.settings.json
|
||||
# Database files
|
||||
*.db
|
||||
python/dotnet-ref
|
||||
|
||||
# Generated filtered solution files (created by eng/scripts/New-FilteredSolution.ps1)
|
||||
dotnet/filtered-*.slnx
|
||||
|
||||
@@ -28,7 +28,7 @@ Welcome to Microsoft's comprehensive multi-language framework for building, orch
|
||||
Python
|
||||
|
||||
```bash
|
||||
pip install agent-framework --pre
|
||||
pip install agent-framework
|
||||
# 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 --pre
|
||||
# pip install agent-framework
|
||||
# Use `az login` to authenticate with Azure CLI
|
||||
import os
|
||||
import asyncio
|
||||
@@ -120,38 +120,38 @@ if __name__ == "__main__":
|
||||
```
|
||||
|
||||
### Basic Agent - .NET
|
||||
Create a simple Agent, using Microsoft Foundry with token-based auth, that writes a haiku about the Microsoft Agent Framework
|
||||
|
||||
```c#
|
||||
// dotnet add package Microsoft.Agents.AI.Foundry
|
||||
// Use `az login` to authenticate with Azure CLI
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using System;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
|
||||
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 agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: deploymentName, name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
```
|
||||
|
||||
Create a simple Agent, using OpenAI Responses, that writes a haiku about the Microsoft Agent Framework
|
||||
|
||||
```c#
|
||||
// dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
|
||||
using Microsoft.Agents.AI;
|
||||
// dotnet add package Microsoft.Agents.AI.OpenAI
|
||||
using System;
|
||||
using OpenAI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// Replace the <apikey> with your OpenAI API key.
|
||||
var agent = new OpenAIClient("<apikey>")
|
||||
.GetResponsesClient("gpt-4o-mini")
|
||||
.AsAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
```
|
||||
|
||||
Create a simple Agent, using Microsoft Foundry with token-based auth, that writes a haiku about the Microsoft Agent Framework
|
||||
|
||||
```c#
|
||||
// dotnet add package Microsoft.Agents.AI.AzureAI --prerelease
|
||||
// dotnet add package Azure.Identity
|
||||
// Use `az login` to authenticate with Azure CLI
|
||||
using Azure.AI.Projects;
|
||||
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-4o-mini";
|
||||
|
||||
var agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: deploymentName, name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
.GetResponsesClient()
|
||||
.AsAIAgent(model: "gpt-5.4-mini", name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
```
|
||||
@@ -207,4 +207,9 @@ The samples typically read configuration from environment variables. Common requ
|
||||
|
||||
## Important Notes
|
||||
|
||||
If you use the Microsoft Agent Framework to build applications that operate with third-party servers or agents, you do so at your own risk. We recommend reviewing all data being shared with third-party servers or agents and being cognizant of third-party practices for retention and location of data. It is your responsibility to manage whether your data will flow outside of your organization's Azure compliance and geographic boundaries and any related implications.
|
||||
> [!IMPORTANT]
|
||||
> If you use Microsoft Agent Framework to build applications that operate with any third-party servers, agents, code, or non-Azure Direct models (“Third-Party Systems”), you do so at your own risk. Third-Party Systems are Non-Microsoft Products under the Microsoft Product Terms and are governed by their own third-party license terms. You are responsible for any usage and associated costs.
|
||||
>
|
||||
>We recommend reviewing all data being shared with and received from Third-Party Systems and being cognizant of third-party practices for handling, sharing, retention and location of data. It is your responsibility to manage whether your data will flow outside of your organization’s Azure compliance and geographic boundaries and any related implications, and that appropriate permissions, boundaries and approvals are provisioned.
|
||||
>
|
||||
>You are responsible for carefully reviewing and testing applications you build using Microsoft Agent Framework in the context of your specific use cases, and making all appropriate decisions and customizations. This includes implementing your own responsible AI mitigations such as metaprompt, content filters, or other safety systems, and ensuring your applications meet appropriate quality, reliability, security, and trustworthiness standards. See also: [Transparency FAQ](./TRANSPARENCY_FAQ.md)
|
||||
|
||||
+2
-1
@@ -25,7 +25,7 @@ dotnet run --project eng/verify-samples -- Agent_Step02_StructuredOutput Agent_S
|
||||
dotnet run --project eng/verify-samples -- --parallel 8 --log results.log
|
||||
|
||||
# Combine options
|
||||
dotnet run --project eng/verify-samples -- --category 03-workflows --parallel 4 --log results.log --csv results.csv
|
||||
dotnet run --project eng/verify-samples -- --category 03-workflows --parallel 4 --log results.log --csv results.csv --md results.md
|
||||
```
|
||||
|
||||
### Required Environment Variables
|
||||
@@ -40,6 +40,7 @@ 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
|
||||
|
||||
|
||||
+3
-2
@@ -29,13 +29,14 @@ using types like `IChatClient`, `FunctionInvokingChatClient`, `AITool`, `AIFunct
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- **Encoding**: All new files must be saved with UTF-8 encoding with BOM (Byte Order Mark). This is required for `dotnet format` to work correctly.
|
||||
- **Command output capture**: When running `dotnet build`, `dotnet test`, `dotnet format`, or similar commands, redirect output to a temp file first (e.g., `dotnet build --tl:off 2>&1 | Out-File $env:TEMP\build.log`), then analyze the file as needed. This avoids re-running expensive commands when the initial analysis misses something.
|
||||
- **Encoding**: All new files must be saved with UTF-8 encoding with BOM (Byte Order Mark). This is required for `dotnet format` to work correctly. When using PowerShell `Set-Content`, always pass `-Encoding UTF8BOM` to preserve the BOM (e.g., `Set-Content $file $content -NoNewline -Encoding UTF8BOM`).
|
||||
- **Copyright header**: `// Copyright (c) Microsoft. All rights reserved.` at top of all `.cs` files
|
||||
- **XML docs**: Required for all public methods and classes
|
||||
- **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
|
||||
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking; test methods returning `Task`/`ValueTask` must use the `Async` suffix.
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>false</IsReleaseCandidate>
|
||||
<IsGenerallyAvailable>false</IsGenerallyAvailable>
|
||||
<IsReleased>false</IsReleased>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Aspire.* -->
|
||||
<PackageVersion Include="Anthropic" Version="12.8.0" />
|
||||
<PackageVersion Include="Anthropic" Version="12.11.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)" />
|
||||
@@ -19,13 +19,13 @@
|
||||
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0" />
|
||||
<!-- Azure.* -->
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="2.0.0-beta.2" />
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="2.0.0" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.10" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.19.0" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.20.0" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
|
||||
<!-- Google Gemini -->
|
||||
<PackageVersion Include="Google.GenAI" Version="0.11.0" />
|
||||
<PackageVersion Include="Google.GenAI" Version="1.6.0" />
|
||||
<PackageVersion Include="Mscc.GenerativeAI.Microsoft" Version="2.9.3" />
|
||||
<!-- Microsoft.Azure.* -->
|
||||
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
|
||||
@@ -35,7 +35,7 @@
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.4" />
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.Memory" Version="10.0.4" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.9.0" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.10.0" />
|
||||
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
<Project Path="samples/02-agents/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
|
||||
</Folder>
|
||||
@@ -107,6 +106,9 @@
|
||||
<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" />
|
||||
|
||||
@@ -781,19 +781,6 @@ internal static class AgentsSamples
|
||||
SkipReason = "Requires local Ollama server.",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Agent_With_OpenAIAssistants",
|
||||
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants",
|
||||
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
|
||||
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should contain a joke about a pirate from the OpenAI Assistants API.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Agent_With_OpenAIChatCompletion",
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
// 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
|
||||
//
|
||||
// Required environment variables (for AI-powered samples):
|
||||
// AZURE_OPENAI_ENDPOINT
|
||||
@@ -90,6 +91,13 @@ 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
|
||||
|
||||
@@ -17,6 +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>
|
||||
@@ -49,6 +54,7 @@ internal sealed class VerifyOptions
|
||||
var categoryFilter = ExtractArg(argList, "--category");
|
||||
var logFilePath = ExtractArg(argList, "--log");
|
||||
var csvFilePath = ExtractArg(argList, "--csv");
|
||||
var markdownFilePath = ExtractArg(argList, "--md");
|
||||
|
||||
int maxParallelism = 8;
|
||||
var parallelArg = ExtractArg(argList, "--parallel");
|
||||
@@ -98,6 +104,7 @@ internal sealed class VerifyOptions
|
||||
MaxParallelism = maxParallelism,
|
||||
LogFilePath = logFilePath,
|
||||
CsvFilePath = csvFilePath,
|
||||
MarkdownFilePath = markdownFilePath,
|
||||
Samples = samples,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<RCNumber>5</RCNumber>
|
||||
<RCNumber>6</RCNumber>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260330.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260330.1</PackageVersion>
|
||||
<GitTag>1.0.0-rc5</GitTag>
|
||||
<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.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-rc4</PackageValidationBaselineVersion>
|
||||
<PackageValidationBaselineVersion>1.0.0-rc5</PackageValidationBaselineVersion>
|
||||
<!-- Enable validation for RC packages and GA packages -->
|
||||
<EnablePackageValidation Condition="'$(IsReleaseCandidate)' == 'true' OR '$(IsGenerallyAvailable)' == 'true'">true</EnablePackageValidation>
|
||||
<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 -->
|
||||
|
||||
@@ -20,10 +20,10 @@ const string JokerName = "JokerAgent";
|
||||
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Define the agent you want to create. (Prompt Agent in this case)
|
||||
var agentVersionCreationOptions = new AgentVersionCreationOptions(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are good at telling jokes." });
|
||||
var agentVersionCreationOptions = new ProjectsAgentVersionCreationOptions(new DeclarativeAgentDefinition(model: deploymentName) { Instructions = "You are good at telling jokes." });
|
||||
// Azure.AI.Agents SDK creates and manages agent by name and versions.
|
||||
// You can create a server side agent version with the Azure.AI.Agents SDK client below.
|
||||
var createdAgentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: JokerName, options: agentVersionCreationOptions);
|
||||
var createdAgentVersion = aiProjectClient.AgentAdministrationClient.CreateAgentVersion(agentName: JokerName, options: agentVersionCreationOptions);
|
||||
|
||||
// Note:
|
||||
// agentVersion.Id = "<agentName>:<versionNumber>",
|
||||
@@ -34,15 +34,15 @@ var createdAgentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: J
|
||||
FoundryAgent existingJokerAgent = aiProjectClient.AsAIAgent(createdAgentVersion);
|
||||
|
||||
// You can also create another AIAgent version by providing the same name with a different definition.
|
||||
AgentVersion newJokerAgentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion newJokerAgentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
JokerName,
|
||||
new AgentVersionCreationOptions(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are extremely hilarious at telling jokes." }));
|
||||
new ProjectsAgentVersionCreationOptions(new DeclarativeAgentDefinition(model: deploymentName) { Instructions = "You are extremely hilarious at telling jokes." }));
|
||||
FoundryAgent newJokerAgent = aiProjectClient.AsAIAgent(newJokerAgentVersion);
|
||||
|
||||
// You can also get the AIAgent latest version just providing its name.
|
||||
AgentRecord jokerAgentRecord = await aiProjectClient.Agents.GetAgentAsync(JokerName);
|
||||
ProjectsAgentRecord jokerAgentRecord = await aiProjectClient.AgentAdministrationClient.GetAgentAsync(JokerName);
|
||||
FoundryAgent jokerAgentLatest = aiProjectClient.AsAIAgent(jokerAgentRecord);
|
||||
AgentVersion latestAgentVersion = jokerAgentRecord.GetLatestVersion();
|
||||
ProjectsAgentVersion latestAgentVersion = jokerAgentRecord.GetLatestVersion();
|
||||
|
||||
// The AIAgent version can be accessed via the GetService method.
|
||||
Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
|
||||
@@ -55,4 +55,4 @@ Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate
|
||||
Console.WriteLine(await jokerAgentLatest.RunAsync("Now tell me a joke about a cat and a dog using last joke as the anchor.", session));
|
||||
|
||||
// Cleanup by agent name removes both agent versions created.
|
||||
aiProjectClient.Agents.DeleteAgent(existingJokerAgent.Name);
|
||||
aiProjectClient.AgentAdministrationClient.DeleteAgent(existingJokerAgent.Name);
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with OpenAI Assistants as the backend.
|
||||
|
||||
// WARNING: The Assistants API is deprecated and will be shut down.
|
||||
// For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete - OpenAI Assistants API is deprecated but still used in this sample
|
||||
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI;
|
||||
using OpenAI.Assistants;
|
||||
|
||||
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-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Get a client to create/retrieve server side agents with.
|
||||
var assistantClient = new OpenAIClient(apiKey).GetAssistantClient();
|
||||
|
||||
// You can create a server side assistant with the OpenAI SDK.
|
||||
var createResult = await assistantClient.CreateAssistantAsync(model, new() { Name = JokerName, Instructions = JokerInstructions });
|
||||
|
||||
// You can retrieve an already created server side assistant as an AIAgent.
|
||||
AIAgent agent1 = await assistantClient.GetAIAgentAsync(createResult.Value.Id);
|
||||
|
||||
// You can also create a server side assistant and return it as an AIAgent directly.
|
||||
AIAgent agent2 = await assistantClient.CreateAIAgentAsync(
|
||||
model: model,
|
||||
name: JokerName,
|
||||
instructions: JokerInstructions);
|
||||
|
||||
// You can invoke the agent like any other AIAgent.
|
||||
AgentSession session = await agent1.CreateSessionAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", session));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
await assistantClient.DeleteAssistantAsync(agent1.Id);
|
||||
await assistantClient.DeleteAssistantAsync(agent2.Id);
|
||||
@@ -1,16 +0,0 @@
|
||||
# Prerequisites
|
||||
|
||||
WARNING: The Assistants API is deprecated and will be shut down.
|
||||
For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10 SDK or later
|
||||
- OpenAI API key
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```powershell
|
||||
$env:OPENAI_API_KEY="*****" # Replace with your OpenAI API key
|
||||
$env:OPENAI_CHAT_MODEL_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
|
||||
```
|
||||
@@ -25,7 +25,6 @@ See the README.md for each sample for the prerequisites for that sample.
|
||||
|[Creating an AIAgent with GitHub Copilot](./Agent_With_GitHubCopilot/)|This sample demonstrates how to create an AIAgent using GitHub Copilot SDK as the underlying inference service|
|
||||
|[Creating an AIAgent with Ollama](./Agent_With_Ollama/)|This sample demonstrates how to create an AIAgent using Ollama as the underlying inference service|
|
||||
|[Creating an AIAgent with ONNX](./Agent_With_ONNX/)|This sample demonstrates how to create an AIAgent using ONNX as the underlying inference service|
|
||||
|[Creating an AIAgent with OpenAI Assistants](./Agent_With_OpenAIAssistants/)|This sample demonstrates how to create an AIAgent using OpenAI Assistants as the underlying inference service.</br>WARNING: The Assistants API is deprecated and will be shut down. For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration|
|
||||
|[Creating an AIAgent with OpenAI ChatCompletion](./Agent_With_OpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using OpenAI ChatCompletion as the underlying inference service|
|
||||
|[Creating an AIAgent with OpenAI Responses](./Agent_With_OpenAIResponses/)|This sample demonstrates how to create an AIAgent using OpenAI Responses as the underlying inference service|
|
||||
|
||||
|
||||
@@ -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 executor
|
||||
- Using the `AgentSkillsProvider` constructor with a skill directory path and script runner
|
||||
- Running file-based scripts (Python) via a subprocess-based executor
|
||||
|
||||
## Skills Included
|
||||
|
||||
+6
@@ -6,8 +6,14 @@
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);MAAI001</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>
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to define Agent Skills as C# classes using AgentClassSkill.
|
||||
// Class-based skills bundle all components into a single class implementation.
|
||||
|
||||
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-4o-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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Class-based skills bundle all components (name, description, body, resources, scripts)
|
||||
/// into a single class.
|
||||
/// </remarks>
|
||||
internal sealed class UnitConverterSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
/// <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.
|
||||
""";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
[
|
||||
CreateResource(
|
||||
"conversion-table",
|
||||
"""
|
||||
# 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 |
|
||||
"""),
|
||||
];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
[
|
||||
CreateScript("convert", ConvertUnits),
|
||||
];
|
||||
|
||||
private static string ConvertUnits(double value, double factor)
|
||||
{
|
||||
double result = Math.Round(value * factor, 4);
|
||||
return JsonSerializer.Serialize(new { value, factor, result });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Class-Based Agent Skills Sample
|
||||
|
||||
This sample demonstrates how to define **Agent Skills as C# classes** using `AgentClassSkill`.
|
||||
|
||||
## What it demonstrates
|
||||
|
||||
- Creating skills as classes that extend `AgentClassSkill`
|
||||
- Bundling name, description, body, resources, and scripts into a single class
|
||||
- Using the `AgentSkillsProvider` constructor with class-based skills
|
||||
|
||||
## 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-4o-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**
|
||||
```
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);MAAI001</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>
|
||||
@@ -0,0 +1,149 @@
|
||||
// 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
|
||||
//
|
||||
// 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.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-4o-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.
|
||||
/// </summary>
|
||||
internal sealed class TemperatureConverterSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
/// <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.
|
||||
""";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
[
|
||||
CreateResource(
|
||||
"temperature-conversion-formulas",
|
||||
"""
|
||||
# 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 |
|
||||
"""),
|
||||
];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
[
|
||||
CreateScript("convert-temperature", ConvertTemperature),
|
||||
];
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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-4o-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**
|
||||
```
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
---
|
||||
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
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# 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 |
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# 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()
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);MAAI001;CA1812</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>
|
||||
@@ -0,0 +1,208 @@
|
||||
// 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.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-4o-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 encapsulated in a class. Factory methods
|
||||
// CreateResource and CreateScript accept delegates with IServiceProvider.
|
||||
//
|
||||
// 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 functions. This enables clean separation of
|
||||
/// concerns and testability while retaining the class-based skill pattern.
|
||||
/// </remarks>
|
||||
internal sealed class WeightConverterSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
/// <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.
|
||||
""";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
[
|
||||
CreateResource("weight-table", (IServiceProvider serviceProvider) =>
|
||||
{
|
||||
var service = serviceProvider.GetRequiredService<ConversionService>();
|
||||
return service.GetWeightTable();
|
||||
}),
|
||||
];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
[
|
||||
CreateScript("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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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-4o-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**
|
||||
```
|
||||
@@ -6,19 +6,32 @@ 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
|
||||
|
||||
### File-Based vs Code-Defined Skills
|
||||
### Skill Types
|
||||
|
||||
| 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 |
|
||||
| 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) |
|
||||
|
||||
For single-source scenarios, use the `AgentSkillsProvider` constructors directly. To combine multiple skill types, use the `AgentSkillsProviderBuilder`.
|
||||
### `AgentSkillsProvider` vs `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.
|
||||
|
||||
+4
-4
@@ -44,10 +44,10 @@ ClientResult<VectorStore> vectorStoreCreate = await vectorStoreClient.CreateVect
|
||||
FileSearchTool fileSearchTool = new([vectorStoreCreate.Value.Id]);
|
||||
#pragma warning restore OPENAI001
|
||||
|
||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
"AskContoso",
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(model: deploymentName)
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(model: deploymentName)
|
||||
{
|
||||
Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
|
||||
Tools = { fileSearchTool }
|
||||
@@ -68,4 +68,4 @@ Console.WriteLine(await agent.RunAsync("What is the best way to maintain the Tra
|
||||
// Cleanup
|
||||
await fileClient.DeleteFileAsync(uploadResult.Value.Id);
|
||||
await vectorStoreClient.DeleteVectorStoreAsync(vectorStoreCreate.Value.Id);
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
||||
await aiProjectClient.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
|
||||
@@ -19,10 +19,10 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYME
|
||||
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Create a server side agent and expose it as an AIAgent.
|
||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
"Joker",
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(model: deploymentName)
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(model: deploymentName)
|
||||
{
|
||||
Instructions = "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.",
|
||||
})
|
||||
|
||||
+4
-4
@@ -18,10 +18,10 @@ const string JokerName = "JokerAgent";
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential());
|
||||
|
||||
// Create a server-side agent version using the native SDK.
|
||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
JokerName,
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(model: deploymentName)
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(model: deploymentName)
|
||||
{
|
||||
Instructions = "You are good at telling jokes.",
|
||||
}));
|
||||
@@ -33,4 +33,4 @@ FoundryAgent agent = aiProjectClient.AsAIAgent(agentVersion);
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
// Cleanup: deletes the agent and all its versions.
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
||||
await aiProjectClient.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
using Azure.AI.Extensions.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.Agents;
|
||||
using Azure.AI.Projects.Memory;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Foundry;
|
||||
|
||||
@@ -31,10 +31,10 @@ var mcpTool = ResponseTool.CreateMcpTool(
|
||||
toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval));
|
||||
|
||||
// Create a server side agent with the mcp tool, and expose it as an AIAgent.
|
||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
"MicrosoftLearnAgent",
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(model: model)
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(model: model)
|
||||
{
|
||||
Instructions = "You answer questions by searching the Microsoft Learn content only.",
|
||||
Tools = { mcpTool }
|
||||
@@ -47,7 +47,7 @@ AgentSession session = await agent.CreateSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", session));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
aiProjectClient.Agents.DeleteAgent(agent.Name);
|
||||
aiProjectClient.AgentAdministrationClient.DeleteAgent(agent.Name);
|
||||
|
||||
// **** MCP Tool with Approval Required ****
|
||||
// *****************************************
|
||||
@@ -61,10 +61,10 @@ var mcpToolWithApproval = ResponseTool.CreateMcpTool(
|
||||
toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.AlwaysRequireApproval));
|
||||
|
||||
// Create an agent with the MCP tool that requires approval.
|
||||
AgentVersion agentVersionWithApproval = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion agentVersionWithApproval = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
"MicrosoftLearnAgentWithApproval",
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(model: model)
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(model: model)
|
||||
{
|
||||
Instructions = "You answer questions by searching the Microsoft Learn content only.",
|
||||
Tools = { mcpToolWithApproval }
|
||||
|
||||
@@ -58,9 +58,9 @@ public static class Program
|
||||
finally
|
||||
{
|
||||
// Cleanup the agents created for the sample.
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(frenchAgent.Name);
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(spanishAgent.Name);
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(englishAgent.Name);
|
||||
await aiProjectClient.AgentAdministrationClient.DeleteAgentAsync(frenchAgent.Name);
|
||||
await aiProjectClient.AgentAdministrationClient.DeleteAgentAsync(spanishAgent.Name);
|
||||
await aiProjectClient.AgentAdministrationClient.DeleteAgentAsync(englishAgent.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +76,10 @@ public static class Program
|
||||
AIProjectClient aiProjectClient,
|
||||
string model)
|
||||
{
|
||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
||||
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
$"{targetLanguage} Translator",
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(model: model)
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(model: model)
|
||||
{
|
||||
Instructions = $"You are a translation assistant that translates the provided text to {targetLanguage}.",
|
||||
}));
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using System.Text;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
@@ -31,22 +32,26 @@ public static class Program
|
||||
{
|
||||
private static async Task Main()
|
||||
{
|
||||
// Set up the Azure OpenAI 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-4o-mini";
|
||||
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
|
||||
// Set up the Azure AI Project client
|
||||
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-4o-mini";
|
||||
var chatClient = new AIProjectClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.ProjectOpenAIClient.GetChatClient(deploymentName).AsIChatClient();
|
||||
|
||||
// Create the executors
|
||||
ChatClientAgent physicist = new(
|
||||
var physicist = new ChatClientAgent(
|
||||
chatClient,
|
||||
name: "Physicist",
|
||||
instructions: "You are an expert in physics. You answer questions from a physics perspective."
|
||||
);
|
||||
ChatClientAgent chemist = new(
|
||||
).BindAsExecutor(new AIAgentHostOptions { ForwardIncomingMessages = false });
|
||||
|
||||
var chemist = new ChatClientAgent(
|
||||
chatClient,
|
||||
name: "Chemist",
|
||||
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective."
|
||||
);
|
||||
).BindAsExecutor(new AIAgentHostOptions { ForwardIncomingMessages = false });
|
||||
|
||||
var startExecutor = new ConcurrentStartExecutor();
|
||||
var aggregationExecutor = new ConcurrentAggregationExecutor();
|
||||
|
||||
@@ -61,11 +66,30 @@ public static class Program
|
||||
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input: "What is temperature?");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is WorkflowOutputEvent output)
|
||||
switch (evt)
|
||||
{
|
||||
Console.WriteLine($"Workflow completed with results:\n{output.Data}");
|
||||
case WorkflowOutputEvent workflowOutput:
|
||||
Console.WriteLine($"Workflow completed with results:\n{workflowOutput.Data}");
|
||||
break;
|
||||
|
||||
case WorkflowErrorEvent workflowError:
|
||||
WriteError(workflowError.Exception?.ToString() ?? "Unknown workflow error occurred");
|
||||
break;
|
||||
|
||||
case ExecutorFailedEvent executorFailed:
|
||||
WriteError($"Executor '{executorFailed.ExecutorId}' failed with {(
|
||||
executorFailed.Data == null ? "unknown error" : $"exception {executorFailed.Data}"
|
||||
)}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteError(string error)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Write(error);
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +116,7 @@ internal sealed partial class ConcurrentStartExecutor() :
|
||||
// the message but will not start processing until they receive a turn token.
|
||||
await context.SendMessageAsync(new ChatMessage(ChatRole.User, message), cancellationToken: cancellationToken);
|
||||
// Broadcast the turn token to kick off the agents.
|
||||
await context.SendMessageAsync(new TurnToken(emitEvents: true), cancellationToken: cancellationToken);
|
||||
await context.SendMessageAsync(new TurnToken(emitEvents: false), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +140,19 @@ internal sealed partial class ConcurrentAggregationExecutor() :
|
||||
public override async ValueTask HandleAsync(List<ChatMessage> message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._messages.AddRange(message);
|
||||
}
|
||||
|
||||
if (this._messages.Count == 2)
|
||||
protected override ValueTask OnMessageDeliveryFinishedAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
StringBuilder resultBuilder = new();
|
||||
foreach (ChatMessage m in this._messages)
|
||||
{
|
||||
var formattedMessages = string.Join(Environment.NewLine, this._messages.Select(m => $"{m.AuthorName}: {m.Text}"));
|
||||
await context.YieldOutputAsync(formattedMessages, cancellationToken);
|
||||
resultBuilder.AppendLine($"{m.AuthorName}: {m.Text}");
|
||||
resultBuilder.AppendLine();
|
||||
}
|
||||
|
||||
this._messages.Clear();
|
||||
|
||||
return context.YieldOutputAsync(resultBuilder.ToString(), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ internal sealed class Program
|
||||
agentDescription: "Escalate agent for human support");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineSelfServiceAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineSelfServiceAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -144,7 +144,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineTicketingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
private static DeclarativeAgentDefinition DefineTicketingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -208,7 +208,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineTicketRoutingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
private static DeclarativeAgentDefinition DefineTicketRoutingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -253,7 +253,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineWindowsSupportAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
private static DeclarativeAgentDefinition DefineWindowsSupportAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -323,7 +323,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineResolutionAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
private static DeclarativeAgentDefinition DefineResolutionAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -357,7 +357,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition TicketEscalationAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
private static DeclarativeAgentDefinition TicketEscalationAgent(IConfiguration configuration, TicketingPlugin plugin) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
@@ -88,7 +88,7 @@ internal sealed class Program
|
||||
agentDescription: "Weather agent for DeepResearch workflow");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineResearchAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineResearchAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -114,13 +114,13 @@ internal sealed class Program
|
||||
""",
|
||||
Tools =
|
||||
{
|
||||
//AgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
//ProjectsAgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
// new BingGroundingSearchToolParameters(
|
||||
// [new BingGroundingSearchConfiguration(this.GetSetting(Settings.FoundryGroundingTool))]))
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefinePlannerAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefinePlannerAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions = // TODO: Use Structured Inputs / Prompt Template
|
||||
@@ -139,7 +139,7 @@ internal sealed class Program
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineManagerAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineManagerAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions = // TODO: Use Structured Inputs / Prompt Template
|
||||
@@ -225,7 +225,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineSummaryAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineSummaryAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -240,18 +240,18 @@ internal sealed class Program
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineKnowledgeAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineKnowledgeAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Tools =
|
||||
{
|
||||
//AgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
//ProjectsAgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
// new BingGroundingSearchToolParameters(
|
||||
// [new BingGroundingSearchConfiguration(this.GetSetting(Settings.FoundryGroundingTool))]))
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineCoderAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineCoderAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -265,7 +265,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineWeatherAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineWeatherAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -274,7 +274,7 @@ internal sealed class Program
|
||||
""",
|
||||
Tools =
|
||||
{
|
||||
AgentTool.CreateOpenApiTool(
|
||||
ProjectsAgentTool.CreateOpenApiTool(
|
||||
new OpenApiFunctionDefinition(
|
||||
"weather-forecast",
|
||||
BinaryData.FromString(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "wttr.json"))),
|
||||
|
||||
@@ -67,9 +67,9 @@ internal sealed class Program
|
||||
agentDescription: "Provides information about the restaurant menu");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineMenuAgent(IConfiguration configuration, AIFunction[] functions)
|
||||
private static DeclarativeAgentDefinition DefineMenuAgent(IConfiguration configuration, AIFunction[] functions)
|
||||
{
|
||||
PromptAgentDefinition agentDefinition =
|
||||
DeclarativeAgentDefinition agentDefinition =
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
@@ -46,7 +46,7 @@ internal sealed class Program
|
||||
await CreateAgentsAsync(aiProjectClient, configuration);
|
||||
|
||||
// Ensure workflow agent exists in Foundry.
|
||||
AgentVersion agentVersion = await CreateWorkflowAsync(aiProjectClient, configuration);
|
||||
ProjectsAgentVersion agentVersion = await CreateWorkflowAsync(aiProjectClient, configuration);
|
||||
|
||||
string workflowInput = GetWorkflowInput(args);
|
||||
|
||||
@@ -86,7 +86,7 @@ internal sealed class Program
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<AgentVersion> CreateWorkflowAsync(AIProjectClient agentClient, IConfiguration configuration)
|
||||
private static async Task<ProjectsAgentVersion> CreateWorkflowAsync(AIProjectClient agentClient, IConfiguration configuration)
|
||||
{
|
||||
string workflowYaml = File.ReadAllText("MathChat.yaml");
|
||||
|
||||
@@ -114,7 +114,7 @@ internal sealed class Program
|
||||
agentDescription: "Teacher agent for MathChat workflow");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineStudentAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineStudentAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -127,7 +127,7 @@ internal sealed class Program
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineTeacherAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineTeacherAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
@@ -68,7 +68,7 @@ internal sealed class Program
|
||||
agentDescription: "Chats with the user with location awareness.");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineLocationTriageAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineLocationTriageAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -79,7 +79,7 @@ internal sealed class Program
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineLocationCaptureAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineLocationCaptureAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -128,7 +128,7 @@ internal sealed class Program
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineLocationAwareAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineLocationAwareAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
// Parameterized instructions reference the "location" input argument.
|
||||
|
||||
@@ -63,9 +63,9 @@ internal sealed class Program
|
||||
agentDescription: "Provides information about the restaurant menu");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineMenuAgent(IConfiguration configuration, AIFunction[] functions)
|
||||
private static DeclarativeAgentDefinition DefineMenuAgent(IConfiguration configuration, AIFunction[] functions)
|
||||
{
|
||||
PromptAgentDefinition agentDefinition =
|
||||
DeclarativeAgentDefinition agentDefinition =
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
@@ -125,9 +125,9 @@ internal sealed class Program
|
||||
agentDescription: "Provides information based on search results");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineSearchAgent(IConfiguration configuration)
|
||||
private static DeclarativeAgentDefinition DefineSearchAgent(IConfiguration configuration)
|
||||
{
|
||||
return new PromptAgentDefinition(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
return new DeclarativeAgentDefinition(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
"""
|
||||
|
||||
@@ -67,7 +67,7 @@ internal sealed class Program
|
||||
agentDescription: "Editor agent for Marketing workflow");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineAnalystAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineAnalystAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -79,13 +79,13 @@ internal sealed class Program
|
||||
""",
|
||||
Tools =
|
||||
{
|
||||
//AgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
//ProjectsAgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
// new BingGroundingSearchToolParameters(
|
||||
// [new BingGroundingSearchConfiguration(configuration[Application.Settings.FoundryGroundingTool])]))
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineWriterAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineWriterAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -96,7 +96,7 @@ internal sealed class Program
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineEditorAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineEditorAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
@@ -62,7 +62,7 @@ internal sealed class Program
|
||||
agentDescription: "Teacher agent for MathChat workflow");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineStudentAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineStudentAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
@@ -75,7 +75,7 @@ internal sealed class Program
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineTeacherAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineTeacherAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
@@ -58,7 +58,7 @@ internal sealed class Program
|
||||
agentDescription: "Searches documents on Microsoft Learn");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineSearchAgent(IConfiguration configuration) =>
|
||||
private static DeclarativeAgentDefinition DefineSearchAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
+1
@@ -6,6 +6,7 @@
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);MAAIW001</NoWarn> <!-- Handoff Orchestrations are Experimental -->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Anthropic" />
|
||||
<PackageReference Include="AWSSDK.Extensions.Bedrock.MEAI" />
|
||||
<PackageReference Include="Google.GenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Amazon.BedrockRuntime;
|
||||
using Google.GenAI;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -9,22 +9,20 @@ using Microsoft.Extensions.AI;
|
||||
const string Topic = "Goldendoodles make the best pets.";
|
||||
|
||||
// Create the IChatClients to talk to different services.
|
||||
IChatClient aws = new AmazonBedrockRuntimeClient(
|
||||
Environment.GetEnvironmentVariable("BEDROCK_ACCESS_KEY"!),
|
||||
Environment.GetEnvironmentVariable("BEDROCK_SECRET_KEY")!,
|
||||
Amazon.RegionEndpoint.USEast1)
|
||||
.AsIChatClient("amazon.nova-pro-v1:0");
|
||||
IChatClient google = new Client(vertexAI: false, apiKey: Environment.GetEnvironmentVariable("GOOGLE_GENAI_API_KEY"))
|
||||
.AsIChatClient("gemini-2.5-flash");
|
||||
|
||||
IChatClient anthropic = new Anthropic.AnthropicClient(
|
||||
new() { ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") })
|
||||
.AsIChatClient("claude-sonnet-4-20250514");
|
||||
|
||||
IChatClient openai = new OpenAI.OpenAIClient(
|
||||
Environment.GetEnvironmentVariable("OPENAI_API_KEY")!).GetChatClient("gpt-4o-mini")
|
||||
.AsIChatClient();
|
||||
Environment.GetEnvironmentVariable("OPENAI_API_KEY"))
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient("gpt-4o-mini");
|
||||
|
||||
// Define our agents.
|
||||
AIAgent researcher = new ChatClientAgent(aws,
|
||||
AIAgent researcher = new ChatClientAgent(google,
|
||||
instructions: """
|
||||
Write a short essay on topic specified by the user. The essay should be three to five paragraphs, written at a
|
||||
high school reading level, and include relevant background information, key claims, and notable perspectives.
|
||||
@@ -60,6 +58,12 @@ AIAgent workflowAgent = AgentWorkflowBuilder.BuildSequential(researcher, factChe
|
||||
string? lastAuthor = null;
|
||||
await foreach (var update in workflowAgent.RunStreamingAsync(Topic))
|
||||
{
|
||||
// Skip WorkflowEvent-only updates
|
||||
if ((update.Contents == null || update.Contents.Count == 0) && update.RawRepresentation is WorkflowEvent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastAuthor != update.AuthorName)
|
||||
{
|
||||
lastAuthor = update.AuthorName;
|
||||
|
||||
@@ -20,7 +20,7 @@ internal static class HostAgentFactory
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
AgentRecord agentRecord = await aiProjectClient.Agents.GetAgentAsync(agentName);
|
||||
ProjectsAgentRecord agentRecord = await aiProjectClient.AgentAdministrationClient.GetAgentAsync(agentName);
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(agentRecord, tools: tools);
|
||||
|
||||
AgentCard agentCard = agentType.ToUpperInvariant() switch
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.Agents.AI</RootNamespace>
|
||||
<NoWarn>$(NoWarn);MEAI001</NoWarn>
|
||||
<IsReleaseCandidate>true</IsReleaseCandidate>
|
||||
<IsReleased>true</IsReleased>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -25,8 +25,8 @@ internal sealed class AzureAIProjectChatClient : DelegatingChatClient
|
||||
{
|
||||
private readonly ChatClientMetadata? _metadata;
|
||||
private readonly AIProjectClient _agentClient;
|
||||
private readonly AgentVersion? _agentVersion;
|
||||
private readonly AgentRecord? _agentRecord;
|
||||
private readonly ProjectsAgentVersion? _agentVersion;
|
||||
private readonly ProjectsAgentRecord? _agentRecord;
|
||||
private readonly ChatOptions? _chatOptions;
|
||||
private readonly AgentReference _agentReference;
|
||||
|
||||
@@ -56,34 +56,34 @@ internal sealed class AzureAIProjectChatClient : DelegatingChatClient
|
||||
/// Initializes a new instance of the <see cref="AzureAIProjectChatClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="aiProjectClient">An instance of <see cref="AIProjectClient"/> to interact with Azure AI Agents services.</param>
|
||||
/// <param name="agentRecord">An instance of <see cref="AgentRecord"/> representing the specific agent to use.</param>
|
||||
/// <param name="agentRecord">An instance of <see cref="ProjectsAgentRecord"/> representing the specific agent to use.</param>
|
||||
/// <param name="chatOptions">An instance of <see cref="ChatOptions"/> representing the options on how the agent was predefined.</param>
|
||||
/// <remarks>
|
||||
/// The <see cref="IChatClient"/> provided should be decorated with a <see cref="AzureAIProjectChatClient"/> for proper functionality.
|
||||
/// </remarks>
|
||||
internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentRecord agentRecord, ChatOptions? chatOptions)
|
||||
internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, ProjectsAgentRecord agentRecord, ChatOptions? chatOptions)
|
||||
: this(aiProjectClient, Throw.IfNull(agentRecord).GetLatestVersion(), chatOptions)
|
||||
{
|
||||
this._agentRecord = agentRecord;
|
||||
}
|
||||
|
||||
internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentVersion agentVersion, ChatOptions? chatOptions)
|
||||
internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, ProjectsAgentVersion agentVersion, ChatOptions? chatOptions)
|
||||
: this(
|
||||
aiProjectClient,
|
||||
CreateAgentReference(Throw.IfNull(agentVersion)),
|
||||
(agentVersion.Definition as PromptAgentDefinition)?.Model,
|
||||
(agentVersion.Definition as DeclarativeAgentDefinition)?.Model,
|
||||
chatOptions)
|
||||
{
|
||||
this._agentVersion = agentVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AgentReference"/> from an <see cref="AgentVersion"/>.
|
||||
/// Creates an <see cref="AgentReference"/> from an <see cref="ProjectsAgentVersion"/>.
|
||||
/// Uses the agent version's version if available, otherwise defaults to "latest".
|
||||
/// </summary>
|
||||
/// <param name="agentVersion">The agent version to create a reference from.</param>
|
||||
/// <returns>An <see cref="AgentReference"/> for the specified agent version.</returns>
|
||||
private static AgentReference CreateAgentReference(AgentVersion agentVersion)
|
||||
private static AgentReference CreateAgentReference(ProjectsAgentVersion agentVersion)
|
||||
{
|
||||
// If the version is null, empty, or whitespace, use "latest" as the default.
|
||||
// This handles cases where hosted agents (like MCP agents) may not have a version assigned.
|
||||
@@ -98,9 +98,9 @@ internal sealed class AzureAIProjectChatClient : DelegatingChatClient
|
||||
? this._metadata
|
||||
: (serviceKey is null && serviceType == typeof(AIProjectClient))
|
||||
? this._agentClient
|
||||
: (serviceKey is null && serviceType == typeof(AgentVersion))
|
||||
: (serviceKey is null && serviceType == typeof(ProjectsAgentVersion))
|
||||
? this._agentVersion
|
||||
: (serviceKey is null && serviceType == typeof(AgentRecord))
|
||||
: (serviceKey is null && serviceType == typeof(ProjectsAgentRecord))
|
||||
? this._agentRecord
|
||||
: (serviceKey is null && serviceType == typeof(AgentReference))
|
||||
? this._agentReference
|
||||
|
||||
@@ -38,7 +38,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
||||
/// <remarks>
|
||||
/// When instantiating a <see cref="ChatClientAgent"/> by using an <see cref="AgentReference"/>, minimal information will be available about the agent in the instance level, and any logic that relies
|
||||
/// on <see cref="AIAgent.GetService{TService}(object?)"/> to retrieve information about the agent like <see cref="AgentVersion" /> will receive <see langword="null"/> as the result.
|
||||
/// on <see cref="AIAgent.GetService{TService}(object?)"/> to retrieve information about the agent like <see cref="ProjectsAgentVersion" /> will receive <see langword="null"/> as the result.
|
||||
/// </remarks>
|
||||
public static FoundryAgent AsAIAgent(
|
||||
this AIProjectClient aiProjectClient,
|
||||
@@ -67,7 +67,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="AgentRecord"/>.
|
||||
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="ProjectsAgentRecord"/>.
|
||||
/// </summary>
|
||||
/// <param name="aiProjectClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
||||
/// <param name="agentRecord">The agent record to be converted. The latest version will be used. Cannot be <see langword="null"/>.</param>
|
||||
@@ -78,7 +78,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentRecord"/> is <see langword="null"/>.</exception>
|
||||
public static FoundryAgent AsAIAgent(
|
||||
this AIProjectClient aiProjectClient,
|
||||
AgentRecord agentRecord,
|
||||
ProjectsAgentRecord agentRecord,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null)
|
||||
@@ -100,7 +100,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="AgentVersion"/>.
|
||||
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="ProjectsAgentVersion"/>.
|
||||
/// </summary>
|
||||
/// <param name="aiProjectClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
||||
/// <param name="agentVersion">The agent version to be converted. Cannot be <see langword="null"/>.</param>
|
||||
@@ -111,7 +111,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentVersion"/> is <see langword="null"/>.</exception>
|
||||
public static FoundryAgent AsAIAgent(
|
||||
this AIProjectClient aiProjectClient,
|
||||
AgentVersion agentVersion,
|
||||
ProjectsAgentVersion agentVersion,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null)
|
||||
@@ -206,7 +206,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// <summary>Creates a <see cref="ChatClientAgent"/> with the specified options.</summary>
|
||||
private static ChatClientAgent CreateChatClientAgent(
|
||||
AIProjectClient aiProjectClient,
|
||||
AgentVersion agentVersion,
|
||||
ProjectsAgentVersion agentVersion,
|
||||
ChatClientAgentOptions agentOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
IServiceProvider? services)
|
||||
@@ -249,7 +249,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
||||
private static ChatClientAgent AsChatClientAgent(
|
||||
AIProjectClient aiProjectClient,
|
||||
AgentVersion agentVersion,
|
||||
ProjectsAgentVersion agentVersion,
|
||||
ChatClientAgentOptions agentOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
IServiceProvider? services)
|
||||
@@ -258,7 +258,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
||||
private static ChatClientAgent AsChatClientAgent(
|
||||
AIProjectClient aiProjectClient,
|
||||
AgentRecord agentRecord,
|
||||
ProjectsAgentRecord agentRecord,
|
||||
ChatClientAgentOptions agentOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
IServiceProvider? services)
|
||||
@@ -293,14 +293,14 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
||||
private static ChatClientAgent AsChatClientAgent(
|
||||
AIProjectClient AIProjectClient,
|
||||
AgentVersion agentVersion,
|
||||
AIProjectClient aiProjectClient,
|
||||
ProjectsAgentVersion agentVersion,
|
||||
IList<AITool>? tools,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
bool requireInvocableTools,
|
||||
IServiceProvider? services)
|
||||
=> AsChatClientAgent(
|
||||
AIProjectClient,
|
||||
aiProjectClient,
|
||||
agentVersion,
|
||||
CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
|
||||
clientFactory,
|
||||
@@ -308,21 +308,21 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
||||
private static ChatClientAgent AsChatClientAgent(
|
||||
AIProjectClient AIProjectClient,
|
||||
AgentRecord agentRecord,
|
||||
AIProjectClient aiProjectClient,
|
||||
ProjectsAgentRecord agentRecord,
|
||||
IList<AITool>? tools,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
bool requireInvocableTools,
|
||||
IServiceProvider? services)
|
||||
=> AsChatClientAgent(
|
||||
AIProjectClient,
|
||||
aiProjectClient,
|
||||
agentRecord,
|
||||
CreateChatClientAgentOptions(agentRecord.GetLatestVersion(), new ChatOptions() { Tools = tools }, requireInvocableTools),
|
||||
clientFactory,
|
||||
services);
|
||||
|
||||
/// <summary>
|
||||
/// This method creates <see cref="ChatClientAgentOptions"/> for the specified <see cref="AgentVersion"/> and the provided tools.
|
||||
/// This method creates <see cref="ChatClientAgentOptions"/> for the specified <see cref="ProjectsAgentVersion"/> and the provided tools.
|
||||
/// </summary>
|
||||
/// <param name="agentVersion">The agent version.</param>
|
||||
/// <param name="chatOptions">The <see cref="ChatOptions"/> to use when interacting with the agent.</param>
|
||||
@@ -334,12 +334,12 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
/// This method rebuilds the agent options from the agent definition returned by the version and combine with the in-proc tools when provided
|
||||
/// this ensures that all required tools are provided and the definition of the agent options are consistent with the agent definition coming from the server.
|
||||
/// </remarks>
|
||||
private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
|
||||
private static ChatClientAgentOptions CreateChatClientAgentOptions(ProjectsAgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
|
||||
{
|
||||
var agentDefinition = agentVersion.Definition;
|
||||
|
||||
List<AITool>? agentTools = null;
|
||||
if (agentDefinition is PromptAgentDefinition { Tools: { Count: > 0 } definitionTools })
|
||||
if (agentDefinition is DeclarativeAgentDefinition { Tools: { Count: > 0 } definitionTools })
|
||||
{
|
||||
// Check if no tools were provided while the agent definition requires in-proc tools.
|
||||
if (requireInvocableTools && chatOptions?.Tools is not { Count: > 0 } && definitionTools.Any(t => t is FunctionTool))
|
||||
@@ -395,7 +395,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
Description = agentVersion.Description,
|
||||
};
|
||||
|
||||
if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
|
||||
if (agentDefinition is DeclarativeAgentDefinition promptAgentDefinition)
|
||||
{
|
||||
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
|
||||
agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
|
||||
|
||||
@@ -17,12 +17,12 @@ namespace Microsoft.Agents.AI.Foundry;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class wraps <see cref="AgentTool"/> (Azure.AI.Projects.OpenAI) and <see cref="ResponseTool"/> (OpenAI SDK) factory methods,
|
||||
/// This class wraps <see cref="ProjectsAgentTool"/> (Azure.AI.Projects.Agents) and <see cref="ResponseTool"/> (OpenAI SDK) factory methods,
|
||||
/// returning <see cref="AITool"/> directly — eliminating the need for manual casting and <c>.AsAITool()</c> calls.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Instead of writing:
|
||||
/// <c>((ResponseTool)AgentTool.CreateOpenApiTool(definition)).AsAITool()</c>
|
||||
/// <c>((ResponseTool)ProjectsAgentTool.CreateOpenApiTool(definition)).AsAITool()</c>
|
||||
/// You can write:
|
||||
/// <c>FoundryAITool.CreateOpenApiTool(definition)</c>
|
||||
/// </para>
|
||||
@@ -37,7 +37,7 @@ public static class FoundryAITool
|
||||
/// <returns>An <see cref="AITool"/> wrapping the provided response tool.</returns>
|
||||
public static AITool FromResponseTool(ResponseTool responseTool) => responseTool.AsAITool();
|
||||
|
||||
// --- Azure.AI.Projects.OpenAI AgentTool factories ---
|
||||
// --- Azure.AI.Projects.OpenAI ProjectsAgentTool factories ---
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for OpenAPI tool invocations.
|
||||
@@ -45,7 +45,7 @@ public static class FoundryAITool
|
||||
/// <param name="definition">The OpenAPI function definition specifying the API endpoint, schema, and authentication.</param>
|
||||
/// <returns>An <see cref="AITool"/> that calls the specified OpenAPI endpoint.</returns>
|
||||
public static AITool CreateOpenApiTool(OpenApiFunctionDefinition definition)
|
||||
=> ((ResponseTool)AgentTool.CreateOpenApiTool(definition)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateOpenApiTool(definition)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for Bing Grounding search.
|
||||
@@ -53,7 +53,7 @@ public static class FoundryAITool
|
||||
/// <param name="options">The Bing Grounding search configuration options.</param>
|
||||
/// <returns>An <see cref="AITool"/> for Bing Grounding search.</returns>
|
||||
public static AITool CreateBingGroundingTool(BingGroundingSearchToolOptions options)
|
||||
=> ((ResponseTool)AgentTool.CreateBingGroundingTool(options)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateBingGroundingTool(options)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for Bing Custom Search.
|
||||
@@ -61,7 +61,7 @@ public static class FoundryAITool
|
||||
/// <param name="parameters">The Bing Custom Search configuration parameters.</param>
|
||||
/// <returns>An <see cref="AITool"/> for Bing Custom Search.</returns>
|
||||
public static AITool CreateBingCustomSearchTool(BingCustomSearchToolOptions parameters)
|
||||
=> ((ResponseTool)AgentTool.CreateBingCustomSearchTool(parameters)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateBingCustomSearchTool(parameters)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for Microsoft Fabric data agent.
|
||||
@@ -69,7 +69,7 @@ public static class FoundryAITool
|
||||
/// <param name="options">The Fabric data agent configuration options.</param>
|
||||
/// <returns>An <see cref="AITool"/> for Microsoft Fabric.</returns>
|
||||
public static AITool CreateMicrosoftFabricTool(FabricDataAgentToolOptions options)
|
||||
=> ((ResponseTool)AgentTool.CreateMicrosoftFabricTool(options)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateMicrosoftFabricTool(options)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for SharePoint grounding.
|
||||
@@ -77,7 +77,7 @@ public static class FoundryAITool
|
||||
/// <param name="options">The SharePoint grounding configuration options.</param>
|
||||
/// <returns>An <see cref="AITool"/> for SharePoint grounding.</returns>
|
||||
public static AITool CreateSharepointTool(SharePointGroundingToolOptions options)
|
||||
=> ((ResponseTool)AgentTool.CreateSharepointTool(options)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateSharepointTool(options)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for Azure AI Search.
|
||||
@@ -85,7 +85,7 @@ public static class FoundryAITool
|
||||
/// <param name="options">Optional Azure AI Search configuration options.</param>
|
||||
/// <returns>An <see cref="AITool"/> for Azure AI Search.</returns>
|
||||
public static AITool CreateAzureAISearchTool(AzureAISearchToolOptions? options = null)
|
||||
=> ((ResponseTool)AgentTool.CreateAzureAISearchTool(options)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateAzureAISearchTool(options)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for browser automation.
|
||||
@@ -93,7 +93,7 @@ public static class FoundryAITool
|
||||
/// <param name="parameters">The browser automation configuration parameters.</param>
|
||||
/// <returns>An <see cref="AITool"/> for browser automation.</returns>
|
||||
public static AITool CreateBrowserAutomationTool(BrowserAutomationToolOptions parameters)
|
||||
=> ((ResponseTool)AgentTool.CreateBrowserAutomationTool(parameters)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateBrowserAutomationTool(parameters)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for structured output capture.
|
||||
@@ -101,7 +101,7 @@ public static class FoundryAITool
|
||||
/// <param name="outputs">The structured output definition.</param>
|
||||
/// <returns>An <see cref="AITool"/> for structured output capture.</returns>
|
||||
public static AITool CreateStructuredOutputsTool(StructuredOutputDefinition outputs)
|
||||
=> ((ResponseTool)AgentTool.CreateStructuredOutputsTool(outputs)).AsAITool();
|
||||
=> ((ResponseTool)ProjectsAgentTool.CreateStructuredOutputsTool(outputs)).AsAITool();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AITool"/> for Agent-to-Agent (A2A) communication.
|
||||
@@ -110,7 +110,7 @@ public static class FoundryAITool
|
||||
/// <param name="agentCardPath">Optional path to the agent card.</param>
|
||||
/// <returns>An <see cref="AITool"/> for A2A communication.</returns>
|
||||
public static AITool CreateA2ATool(Uri baseUri, string? agentCardPath = null)
|
||||
=> AgentTool.CreateA2ATool(baseUri, agentCardPath).AsAITool();
|
||||
=> ProjectsAgentTool.CreateA2ATool(baseUri, agentCardPath).AsAITool();
|
||||
|
||||
// --- OpenAI SDK ResponseTool factories ---
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.Memory;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Compliance.Redaction;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.ClientModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.Memory;
|
||||
|
||||
namespace Microsoft.Agents.AI.Foundry;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>true</IsReleaseCandidate>
|
||||
<IsReleased>true</IsReleased>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,106 +2,36 @@
|
||||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,Microsoft.Agents.AI.ChatClientAgentOptions,System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,System.String,System.String,System.String,System.Collections.Generic.IList{Microsoft.Extensions.AI.AITool},System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsIChatClientWithStoredOutputDisabled(OpenAI.Responses.ResponsesClient)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,Microsoft.Agents.AI.ChatClientAgentOptions,System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,System.String,System.String,System.String,System.Collections.Generic.IList{Microsoft.Extensions.AI.AITool},System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsIChatClientWithStoredOutputDisabled(OpenAI.Responses.ResponsesClient)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,Microsoft.Agents.AI.ChatClientAgentOptions,System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,System.String,System.String,System.String,System.Collections.Generic.IList{Microsoft.Extensions.AI.AITool},System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsIChatClientWithStoredOutputDisabled(OpenAI.Responses.ResponsesClient)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,Microsoft.Agents.AI.ChatClientAgentOptions,System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,System.String,System.String,System.String,System.Collections.Generic.IList{Microsoft.Extensions.AI.AITool},System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsIChatClientWithStoredOutputDisabled(OpenAI.Responses.ResponsesClient)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,Microsoft.Agents.AI.ChatClientAgentOptions,System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,System.String,System.String,System.String,System.Collections.Generic.IList{Microsoft.Extensions.AI.AITool},System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:OpenAI.Responses.OpenAIResponseClientExtensions.AsIChatClientWithStoredOutputDisabled(OpenAI.Responses.ResponsesClient)</Target>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
|
||||
@@ -1,433 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace OpenAI.Assistants;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for OpenAI <see cref="AssistantClient"/>
|
||||
/// to simplify the creation of AI agents that work with OpenAI services.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These extensions bridge the gap between OpenAI SDK client objects and the Microsoft Agent Framework,
|
||||
/// allowing developers to easily create AI agents that leverage OpenAI's chat completion and response services.
|
||||
/// The methods handle the conversion from OpenAI clients to <see cref="IChatClient"/> instances and then wrap them
|
||||
/// in <see cref="ChatClientAgent"/> objects that implement the <see cref="AIAgent"/> interface.
|
||||
/// </remarks>
|
||||
[Experimental(DiagnosticIds.Experiments.AIOpenAIAssistants)]
|
||||
public static class OpenAIAssistantClientExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ChatClientAgent"/> from a <see cref="ClientResult{Assistant}"/>.
|
||||
/// </summary>
|
||||
/// <param name="assistantClient">The assistant client.</param>
|
||||
/// <param name="assistantClientResult">The client result containing the assistant.</param>
|
||||
/// <param name="chatOptions">Optional chat options.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static ChatClientAgent AsAIAgent(
|
||||
this AssistantClient assistantClient,
|
||||
ClientResult<Assistant> assistantClientResult,
|
||||
ChatOptions? chatOptions = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
if (assistantClientResult is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantClientResult));
|
||||
}
|
||||
|
||||
return assistantClient.AsAIAgent(assistantClientResult.Value, chatOptions, clientFactory, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ChatClientAgent"/> from an <see cref="Assistant"/>.
|
||||
/// </summary>
|
||||
/// <param name="assistantClient">The assistant client.</param>
|
||||
/// <param name="assistantMetadata">The assistant metadata.</param>
|
||||
/// <param name="chatOptions">Optional chat options.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static ChatClientAgent AsAIAgent(
|
||||
this AssistantClient assistantClient,
|
||||
Assistant assistantMetadata,
|
||||
ChatOptions? chatOptions = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
if (assistantMetadata is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantMetadata));
|
||||
}
|
||||
if (assistantClient is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantClient));
|
||||
}
|
||||
|
||||
var chatClient = assistantClient.AsIChatClient(assistantMetadata.Id);
|
||||
|
||||
if (clientFactory is not null)
|
||||
{
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(assistantMetadata.Instructions) && chatOptions?.Instructions is null)
|
||||
{
|
||||
chatOptions ??= new ChatOptions();
|
||||
chatOptions.Instructions = assistantMetadata.Instructions;
|
||||
}
|
||||
|
||||
return new ChatClientAgent(chatClient, options: new()
|
||||
{
|
||||
Id = assistantMetadata.Id,
|
||||
Name = assistantMetadata.Name,
|
||||
Description = assistantMetadata.Description,
|
||||
ChatOptions = chatOptions
|
||||
}, services: services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AssistantClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="assistantClient">The <see cref="AssistantClient"/> to create the <see cref="ChatClientAgent"/> with.</param>
|
||||
/// <param name="agentId"> The ID of the server side agent to create a <see cref="ChatClientAgent"/> for.</param>
|
||||
/// <param name="chatOptions">Options that should apply to all runs of the agent.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant agent.</returns>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static async Task<ChatClientAgent> GetAIAgentAsync(
|
||||
this AssistantClient assistantClient,
|
||||
string agentId,
|
||||
ChatOptions? chatOptions = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (assistantClient is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantClient));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentId))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId));
|
||||
}
|
||||
|
||||
var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false);
|
||||
return assistantClient.AsAIAgent(assistantResponse, chatOptions, clientFactory, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ChatClientAgent"/> from a <see cref="ClientResult{Assistant}"/>.
|
||||
/// </summary>
|
||||
/// <param name="assistantClient">The assistant client.</param>
|
||||
/// <param name="assistantClientResult">The client result containing the assistant.</param>
|
||||
/// <param name="options">Full set of options to configure the agent.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assistantClientResult"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static ChatClientAgent AsAIAgent(
|
||||
this AssistantClient assistantClient,
|
||||
ClientResult<Assistant> assistantClientResult,
|
||||
ChatClientAgentOptions options,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
if (assistantClientResult is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantClientResult));
|
||||
}
|
||||
|
||||
return assistantClient.AsAIAgent(assistantClientResult.Value, options, clientFactory, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ChatClientAgent"/> from an <see cref="Assistant"/>.
|
||||
/// </summary>
|
||||
/// <param name="assistantClient">The assistant client.</param>
|
||||
/// <param name="assistantMetadata">The assistant metadata.</param>
|
||||
/// <param name="options">Full set of options to configure the agent.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assistantMetadata"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static ChatClientAgent AsAIAgent(
|
||||
this AssistantClient assistantClient,
|
||||
Assistant assistantMetadata,
|
||||
ChatClientAgentOptions options,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
if (assistantMetadata is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantMetadata));
|
||||
}
|
||||
|
||||
if (assistantClient is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantClient));
|
||||
}
|
||||
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var chatClient = assistantClient.AsIChatClient(assistantMetadata.Id);
|
||||
|
||||
if (clientFactory is not null)
|
||||
{
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.ChatOptions?.Instructions) && !string.IsNullOrWhiteSpace(assistantMetadata.Instructions))
|
||||
{
|
||||
options.ChatOptions ??= new ChatOptions();
|
||||
options.ChatOptions.Instructions = assistantMetadata.Instructions;
|
||||
}
|
||||
|
||||
var mergedOptions = new ChatClientAgentOptions()
|
||||
{
|
||||
Id = assistantMetadata.Id,
|
||||
Name = options.Name ?? assistantMetadata.Name,
|
||||
Description = options.Description ?? assistantMetadata.Description,
|
||||
ChatOptions = options.ChatOptions,
|
||||
AIContextProviders = options.AIContextProviders,
|
||||
ChatHistoryProvider = options.ChatHistoryProvider,
|
||||
UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs
|
||||
};
|
||||
|
||||
return new ChatClientAgent(chatClient, mergedOptions, services: services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AssistantClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="assistantClient">The <see cref="AssistantClient"/> to create the <see cref="ChatClientAgent"/> with.</param>
|
||||
/// <param name="agentId"> The ID of the server side agent to create a <see cref="ChatClientAgent"/> for.</param>
|
||||
/// <param name="options">Full set of options to configure the agent.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant agent.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assistantClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="agentId"/> is empty or whitespace.</exception>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static async Task<ChatClientAgent> GetAIAgentAsync(
|
||||
this AssistantClient assistantClient,
|
||||
string agentId,
|
||||
ChatClientAgentOptions options,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (assistantClient is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assistantClient));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentId))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId));
|
||||
}
|
||||
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false);
|
||||
return assistantClient.AsAIAgent(assistantResponse, options, clientFactory, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AI agent from an <see cref="AssistantClient"/> using the OpenAI Assistant API.
|
||||
/// </summary>
|
||||
/// <param name="client">The OpenAI <see cref="AssistantClient" /> to use for the agent.</param>
|
||||
/// <param name="model">The model identifier to use (e.g., "gpt-4").</param>
|
||||
/// <param name="instructions">Optional system instructions that define the agent's behavior and personality.</param>
|
||||
/// <param name="name">Optional name for the agent for identification purposes.</param>
|
||||
/// <param name="description">Optional description of the agent's capabilities and purpose.</param>
|
||||
/// <param name="tools">Optional collection of AI tools that the agent can use during conversations.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An <see cref="ChatClientAgent"/> instance backed by the OpenAI Assistant service.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> or <paramref name="model"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace.</exception>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static async Task<ChatClientAgent> CreateAIAgentAsync(
|
||||
this AssistantClient client,
|
||||
string model,
|
||||
string? instructions = null,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
ILoggerFactory? loggerFactory = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await client.CreateAIAgentAsync(model,
|
||||
new ChatClientAgentOptions()
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
|
||||
{
|
||||
Tools = tools,
|
||||
Instructions = instructions,
|
||||
}
|
||||
},
|
||||
clientFactory,
|
||||
loggerFactory,
|
||||
services,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AI agent from an <see cref="AssistantClient"/> using the OpenAI Assistant API.
|
||||
/// </summary>
|
||||
/// <param name="client">The OpenAI <see cref="AssistantClient" /> to use for the agent.</param>
|
||||
/// <param name="model">The model identifier to use (e.g., "gpt-4").</param>
|
||||
/// <param name="options">Full set of options to configure the agent.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An <see cref="ChatClientAgent"/> instance backed by the OpenAI Assistant service.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> or <paramref name="model"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace.</exception>
|
||||
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
|
||||
public static async Task<ChatClientAgent> CreateAIAgentAsync(
|
||||
this AssistantClient client,
|
||||
string model,
|
||||
ChatClientAgentOptions options,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
ILoggerFactory? loggerFactory = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(client);
|
||||
Throw.IfNull(model);
|
||||
Throw.IfNull(options);
|
||||
|
||||
var assistantOptions = new AssistantCreationOptions()
|
||||
{
|
||||
Name = options.Name,
|
||||
Description = options.Description,
|
||||
Instructions = options.ChatOptions?.Instructions,
|
||||
};
|
||||
|
||||
// Convert AITools to ToolDefinitions and ToolResources
|
||||
var toolDefinitionsAndResources = ConvertAIToolsToToolDefinitions(options.ChatOptions?.Tools);
|
||||
if (toolDefinitionsAndResources.ToolDefinitions is { Count: > 0 } toolDefinitions)
|
||||
{
|
||||
toolDefinitions.ForEach(x => assistantOptions.Tools.Add(x));
|
||||
}
|
||||
if (toolDefinitionsAndResources.ToolResources is not null)
|
||||
{
|
||||
assistantOptions.ToolResources = toolDefinitionsAndResources.ToolResources;
|
||||
}
|
||||
|
||||
// Create the assistant in the assistant service.
|
||||
var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions, cancellationToken).ConfigureAwait(false);
|
||||
var assistantId = assistantCreateResult.Value.Id;
|
||||
|
||||
// Build the local agent object.
|
||||
var chatClient = client.AsIChatClient(assistantId);
|
||||
if (clientFactory is not null)
|
||||
{
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
var agentOptions = options.Clone();
|
||||
agentOptions.Id = assistantId;
|
||||
options.ChatOptions ??= new ChatOptions();
|
||||
options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools;
|
||||
|
||||
return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
|
||||
}
|
||||
|
||||
private static (List<ToolDefinition>? ToolDefinitions, ToolResources? ToolResources, List<AITool>? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList<AITool>? tools)
|
||||
{
|
||||
List<ToolDefinition>? toolDefinitions = null;
|
||||
ToolResources? toolResources = null;
|
||||
List<AITool>? functionToolsAndOtherTools = null;
|
||||
|
||||
if (tools is not null)
|
||||
{
|
||||
foreach (AITool tool in tools)
|
||||
{
|
||||
switch (tool)
|
||||
{
|
||||
case HostedCodeInterpreterTool codeTool:
|
||||
|
||||
toolDefinitions ??= [];
|
||||
toolDefinitions.Add(new CodeInterpreterToolDefinition());
|
||||
|
||||
if (codeTool.Inputs is { Count: > 0 })
|
||||
{
|
||||
foreach (var input in codeTool.Inputs)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case HostedFileContent hostedFile:
|
||||
// If the input is a HostedFileContent, we can use its ID directly.
|
||||
toolResources ??= new();
|
||||
toolResources.CodeInterpreter ??= new();
|
||||
toolResources.CodeInterpreter.FileIds.Add(hostedFile.FileId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HostedFileSearchTool fileSearchTool:
|
||||
toolDefinitions ??= [];
|
||||
toolDefinitions.Add(new FileSearchToolDefinition
|
||||
{
|
||||
MaxResults = fileSearchTool.MaximumResultCount,
|
||||
});
|
||||
|
||||
if (fileSearchTool.Inputs is { Count: > 0 })
|
||||
{
|
||||
foreach (var input in fileSearchTool.Inputs)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case HostedVectorStoreContent hostedVectorStore:
|
||||
toolResources ??= new();
|
||||
toolResources.FileSearch ??= new();
|
||||
toolResources.FileSearch.VectorStoreIds.Add(hostedVectorStore.VectorStoreId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
functionToolsAndOtherTools ??= [];
|
||||
functionToolsAndOtherTools.Add(tool);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (toolDefinitions, toolResources, functionToolsAndOtherTools);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>true</IsReleaseCandidate>
|
||||
<IsReleased>true</IsReleased>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative;
|
||||
/// <param name="projectCredentials">The credentials used to authenticate with the Foundry project. This must be a valid instance of <see cref="TokenCredential"/>.</param>
|
||||
public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential projectCredentials) : ResponseAgentProvider
|
||||
{
|
||||
private readonly Dictionary<string, AgentVersion> _versionCache = [];
|
||||
private readonly Dictionary<string, ProjectsAgentVersion> _versionCache = [];
|
||||
private readonly Dictionary<string, AIAgent> _agentCache = [];
|
||||
|
||||
private AIProjectClient? _agentClient;
|
||||
@@ -99,7 +99,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
IDictionary<string, object?>? inputArguments,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
AgentVersion agentVersionResult = await this.QueryAgentAsync(agentId, agentVersion, cancellationToken).ConfigureAwait(false);
|
||||
ProjectsAgentVersion agentVersionResult = await this.QueryAgentAsync(agentId, agentVersion, cancellationToken).ConfigureAwait(false);
|
||||
AIAgent agent = await this.GetAgentAsync(agentVersionResult, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ChatOptions chatOptions =
|
||||
@@ -133,10 +133,10 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<AgentVersion> QueryAgentAsync(string agentName, string? agentVersion, CancellationToken cancellationToken = default)
|
||||
private async Task<ProjectsAgentVersion> QueryAgentAsync(string agentName, string? agentVersion, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string agentKey = $"{agentName}:{agentVersion}";
|
||||
if (this._versionCache.TryGetValue(agentKey, out AgentVersion? targetAgent))
|
||||
if (this._versionCache.TryGetValue(agentKey, out ProjectsAgentVersion? targetAgent))
|
||||
{
|
||||
return targetAgent;
|
||||
}
|
||||
@@ -145,8 +145,8 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
|
||||
if (string.IsNullOrEmpty(agentVersion))
|
||||
{
|
||||
AgentRecord agentRecord =
|
||||
await client.Agents.GetAgentAsync(
|
||||
ProjectsAgentRecord agentRecord =
|
||||
await client.AgentAdministrationClient.GetAgentAsync(
|
||||
agentName,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -155,7 +155,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
else
|
||||
{
|
||||
targetAgent =
|
||||
await client.Agents.GetAgentVersionAsync(
|
||||
await client.AgentAdministrationClient.GetAgentVersionAsync(
|
||||
agentName,
|
||||
agentVersion,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
@@ -166,7 +166,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
return targetAgent;
|
||||
}
|
||||
|
||||
private async Task<AIAgent> GetAgentAsync(AgentVersion agentVersion, CancellationToken cancellationToken = default)
|
||||
private async Task<AIAgent> GetAgentAsync(ProjectsAgentVersion agentVersion, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this._agentCache.TryGetValue(agentVersion.Id, out AIAgent? agent))
|
||||
{
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>true</IsReleaseCandidate>
|
||||
<IsReleased>true</IsReleased>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Specialized;
|
||||
@@ -154,6 +155,7 @@ public static partial class AgentWorkflowBuilder
|
||||
/// The <see cref="AIAgent"/> must be capable of understanding those <see cref="AgentRunOptions"/> provided. If the agent
|
||||
/// ignores the tools or is otherwise unable to advertize them to the underlying provider, handoffs will not occur.
|
||||
/// </remarks>
|
||||
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
|
||||
public static HandoffWorkflowBuilder CreateHandoffBuilderWith(AIAgent initialAgent)
|
||||
{
|
||||
Throw.IfNull(initialAgent);
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config`1</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.ConfigurationExtensions</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`1</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`2</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config`1</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.ConfigurationExtensions</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`1</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`2</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config`1</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.ConfigurationExtensions</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`1</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`2</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config`1</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.ConfigurationExtensions</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`1</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`2</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Config`1</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.ConfigurationExtensions</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`1</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.Workflows.Configured`2</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.AgentWorkflowBuilder.CreateHandoffBuilderWith(Microsoft.Agents.AI.AIAgent)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.BindExecutor``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.ConfigureFactory``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.AgentWorkflowBuilder.CreateHandoffBuilderWith(Microsoft.Agents.AI.AIAgent)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.BindExecutor``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.ConfigureFactory``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.AgentWorkflowBuilder.CreateHandoffBuilderWith(Microsoft.Agents.AI.AIAgent)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.BindExecutor``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.ConfigureFactory``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.AgentWorkflowBuilder.CreateHandoffBuilderWith(Microsoft.Agents.AI.AIAgent)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.BindExecutor``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.ConfigureFactory``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.AgentWorkflowBuilder.CreateHandoffBuilderWith(Microsoft.Agents.AI.AIAgent)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.BindExecutor``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.Workflows.ExecutorBindingExtensions.ConfigureFactory``2(System.Func{Microsoft.Agents.AI.Workflows.Config{``1},System.String,System.Threading.Tasks.ValueTask{``0}},System.String,``1)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.Workflows.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
</Suppressions>
|
||||
@@ -23,7 +23,8 @@ internal sealed class StreamingRunEventStream : IRunEventStream
|
||||
private readonly CancellationTokenSource _runLoopCancellation;
|
||||
private readonly bool _disableRunLoop;
|
||||
private Task? _runLoopTask;
|
||||
private RunStatus _runStatus = RunStatus.NotStarted;
|
||||
private volatile RunStatus _runStatus = RunStatus.NotStarted;
|
||||
|
||||
private int _completionEpoch; // Tracks which completion signal belongs to which consumer iteration
|
||||
|
||||
public StreamingRunEventStream(ISuperStepRunner stepRunner, bool disableRunLoop = false)
|
||||
@@ -127,7 +128,7 @@ internal sealed class StreamingRunEventStream : IRunEventStream
|
||||
|
||||
// Wait for next input from the consumer
|
||||
// Works for both Idle (no work) and PendingRequests (waiting for responses)
|
||||
await this._inputWaiter.WaitForInputAsync(TimeSpan.FromSeconds(1), linkedSource.Token).ConfigureAwait(false);
|
||||
await this._inputWaiter.WaitForInputAsync(linkedSource.Token).ConfigureAwait(false);
|
||||
|
||||
// When signaled, resume running
|
||||
this._runStatus = RunStatus.Running;
|
||||
@@ -209,7 +210,10 @@ internal sealed class StreamingRunEventStream : IRunEventStream
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Get the current epoch - we'll only respond to completion signals from this epoch or later
|
||||
int myEpoch = Volatile.Read(ref this._completionEpoch) + 1;
|
||||
int currentEpoch = Volatile.Read(ref this._completionEpoch);
|
||||
|
||||
bool expectingFreshWork = this._stepRunner.HasUnprocessedMessages || this._runStatus == RunStatus.Running;
|
||||
int myEpoch = expectingFreshWork ? currentEpoch + 1 : currentEpoch;
|
||||
|
||||
// Use custom async enumerable to avoid exceptions on cancellation.
|
||||
NonThrowingChannelReaderAsyncEnumerable<WorkflowEvent> eventStream = new(this._eventChannel.Reader);
|
||||
@@ -283,10 +287,6 @@ internal sealed class StreamingRunEventStream : IRunEventStream
|
||||
{
|
||||
// Discard each event (including InternalCompletionSignals)
|
||||
}
|
||||
|
||||
// After clearing, signal the run loop to continue if needed
|
||||
// The run loop will send a new completion signal when it finishes processing from the restored state
|
||||
this.SignalInput();
|
||||
}
|
||||
|
||||
public async ValueTask StopAsync()
|
||||
|
||||
@@ -310,22 +310,40 @@ public abstract class Executor : IIdentified
|
||||
return result.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once per superstep before any messages are delivered to the Executor.
|
||||
/// </summary>
|
||||
/// <param name="context">The workflow context.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A ValueTask representing the asynchronous operation.</returns>
|
||||
protected internal virtual ValueTask OnMessageDeliveryStartingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) => default;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once per superstep after all messages have been delivered to the Executor.
|
||||
/// </summary>
|
||||
/// <param name="context">The workflow context.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A ValueTask representing the asynchronous operation.</returns>
|
||||
protected internal virtual ValueTask OnMessageDeliveryFinishedAsync(IWorkflowContext context, CancellationToken cancellationToken = default) => default;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before a checkpoint is saved, allowing custom pre-save logic in derived classes.
|
||||
/// </summary>
|
||||
/// <param name="context">The workflow context.</param>
|
||||
/// <returns>A ValueTask representing the asynchronous operation.</returns>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A ValueTask representing the asynchronous operation.</returns>
|
||||
protected internal virtual ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) => default;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after a checkpoint is loaded, allowing custom post-load logic in derived classes.
|
||||
/// </summary>
|
||||
/// <param name="context">The workflow context.</param>
|
||||
/// <returns>A ValueTask representing the asynchronous operation.</returns>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A ValueTask representing the asynchronous operation.</returns>
|
||||
protected internal virtual ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) => default;
|
||||
|
||||
/// <summary>
|
||||
|
||||
+10
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.Agents.AI.Workflows.Specialized;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -9,13 +10,21 @@ using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows;
|
||||
|
||||
internal static class DiagnosticConstants
|
||||
{
|
||||
public const string ExperimentalFeatureDiagnostic = "MAAIW001";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Prefer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed in a future release before GA.")]
|
||||
#pragma warning disable MAAIW001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
public sealed class HandoffsWorkflowBuilder(AIAgent initialAgent) : HandoffWorkflowBuilderCore<HandoffsWorkflowBuilder>(initialAgent)
|
||||
#pragma warning restore MAAIW001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
|
||||
public sealed class HandoffWorkflowBuilder(AIAgent initialAgent) : HandoffWorkflowBuilderCore<HandoffWorkflowBuilder>(initialAgent)
|
||||
{
|
||||
}
|
||||
@@ -23,6 +32,7 @@ public sealed class HandoffWorkflowBuilder(AIAgent initialAgent) : HandoffWorkfl
|
||||
/// <summary>
|
||||
/// Provides a builder for specifying the handoff relationships between agents and building the resulting workflow.
|
||||
/// </summary>
|
||||
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
|
||||
public class HandoffWorkflowBuilderCore<TBuilder> where TBuilder : HandoffWorkflowBuilderCore<TBuilder>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -182,6 +182,9 @@ public sealed class InProcessExecutionEnvironment : IWorkflowExecutionEnvironmen
|
||||
AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new(runHandle);
|
||||
Run run = new(runHandle);
|
||||
await run.RunToNextHaltAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return run;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,17 +249,33 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
|
||||
Executor executor = await this.RunContext.EnsureExecutorAsync(receiverId, this.StepTracer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
this.StepTracer.TraceActivated(receiverId);
|
||||
while (envelopes.TryDequeue(out var envelope))
|
||||
{
|
||||
(object message, TypeId messageType) = await TranslateMessageAsync(envelope).ConfigureAwait(false);
|
||||
|
||||
await executor.ExecuteCoreAsync(
|
||||
message,
|
||||
messageType,
|
||||
this.RunContext.BindWorkflowContext(receiverId, envelope.TraceContext),
|
||||
this.TelemetryContext,
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
// TODO: #5084 - Add delivery-level activity (max one per step per executor) to capture non-message
|
||||
// specific invocations of executor logic.
|
||||
IWorkflowContext tracelessContext = this.RunContext.BindWorkflowContext(receiverId);
|
||||
|
||||
try
|
||||
{
|
||||
await executor.OnMessageDeliveryStartingAsync(tracelessContext, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
while (envelopes.TryDequeue(out var envelope))
|
||||
{
|
||||
(object message, TypeId messageType) = await TranslateMessageAsync(envelope).ConfigureAwait(false);
|
||||
|
||||
await executor.ExecuteCoreAsync(
|
||||
message,
|
||||
messageType,
|
||||
this.RunContext.BindWorkflowContext(receiverId, envelope.TraceContext),
|
||||
this.TelemetryContext,
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await executor.OnMessageDeliveryFinishedAsync(tracelessContext, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
async ValueTask<(object, TypeId)> TranslateMessageAsync(MessageEnvelope envelope)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>true</IsReleaseCandidate>
|
||||
<IsReleased>true</IsReleased>
|
||||
<NoWarn>$(NoWarn);MEAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
|
||||
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
|
||||
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
@@ -31,6 +32,7 @@ internal sealed class HandoffAgentExecutorOptions
|
||||
public HandoffToolCallFilteringBehavior ToolCallFilteringBehavior { get; set; } = HandoffToolCallFilteringBehavior.HandoffOnly;
|
||||
}
|
||||
|
||||
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
|
||||
internal sealed class HandoffMessagesFilter
|
||||
{
|
||||
private readonly HandoffToolCallFilteringBehavior _filteringBehavior;
|
||||
@@ -40,6 +42,7 @@ internal sealed class HandoffMessagesFilter
|
||||
this._filteringBehavior = filteringBehavior;
|
||||
}
|
||||
|
||||
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
|
||||
internal static bool IsHandoffFunctionName(string name)
|
||||
{
|
||||
return name.StartsWith(HandoffWorkflowBuilder.FunctionPrefix, StringComparison.Ordinal);
|
||||
@@ -164,6 +167,7 @@ internal sealed class HandoffMessagesFilter
|
||||
}
|
||||
|
||||
/// <summary>Executor used to represent an agent in a handoffs workflow, responding to <see cref="HandoffState"/> events.</summary>
|
||||
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
|
||||
internal sealed class HandoffAgentExecutor(
|
||||
AIAgent agent,
|
||||
HandoffAgentExecutorOptions options) : Executor<HandoffState, HandoffState>(agent.GetDescriptiveId(), declareCrossRunShareable: true), IResettableExecutor
|
||||
|
||||
@@ -2,71 +2,176 @@
|
||||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProvider</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProviderOptions</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(System.Collections.Generic.IEnumerable{Microsoft.Agents.AI.AgentInlineSkill},Microsoft.Agents.AI.AgentSkillsProviderOptions,Microsoft.Extensions.Logging.ILoggerFactory)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProvider</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.get_SimulateServiceStoredChatHistory</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.set_SimulateServiceStoredChatHistory(System.Boolean)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Extensions.AI.ChatClientBuilderExtensions.UseServiceStoredChatHistorySimulation(Microsoft.Extensions.AI.ChatClientBuilder)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProviderOptions</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(System.Collections.Generic.IEnumerable{Microsoft.Agents.AI.AgentInlineSkill},Microsoft.Agents.AI.AgentSkillsProviderOptions,Microsoft.Extensions.Logging.ILoggerFactory)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProvider</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.get_SimulateServiceStoredChatHistory</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.set_SimulateServiceStoredChatHistory(System.Boolean)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Extensions.AI.ChatClientBuilderExtensions.UseServiceStoredChatHistorySimulation(Microsoft.Extensions.AI.ChatClientBuilder)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProviderOptions</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(System.Collections.Generic.IEnumerable{Microsoft.Agents.AI.AgentInlineSkill},Microsoft.Agents.AI.AgentSkillsProviderOptions,Microsoft.Extensions.Logging.ILoggerFactory)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProvider</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.get_SimulateServiceStoredChatHistory</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.set_SimulateServiceStoredChatHistory(System.Boolean)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Extensions.AI.ChatClientBuilderExtensions.UseServiceStoredChatHistorySimulation(Microsoft.Extensions.AI.ChatClientBuilder)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProviderOptions</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(System.Collections.Generic.IEnumerable{Microsoft.Agents.AI.AgentInlineSkill},Microsoft.Agents.AI.AgentSkillsProviderOptions,Microsoft.Extensions.Logging.ILoggerFactory)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProvider</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.get_SimulateServiceStoredChatHistory</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.set_SimulateServiceStoredChatHistory(System.Boolean)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Extensions.AI.ChatClientBuilderExtensions.UseServiceStoredChatHistorySimulation(Microsoft.Extensions.AI.ChatClientBuilder)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Microsoft.Agents.AI.FileAgentSkillsProviderOptions</Target>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(System.Collections.Generic.IEnumerable{Microsoft.Agents.AI.AgentInlineSkill},Microsoft.Agents.AI.AgentSkillsProviderOptions,Microsoft.Extensions.Logging.ILoggerFactory)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.get_SimulateServiceStoredChatHistory</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.ChatClientAgentOptions.set_SimulateServiceStoredChatHistory(System.Boolean)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Extensions.AI.ChatClientBuilderExtensions.UseServiceStoredChatHistorySimulation(Microsoft.Extensions.AI.ChatClientBuilder)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>true</IsReleaseCandidate>
|
||||
<IsReleased>true</IsReleased>
|
||||
<NoWarn>$(NoWarn);MEAI001;MAAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -36,6 +37,11 @@ public abstract class AgentSkillScript
|
||||
/// </summary>
|
||||
public string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the JSON schema describing the parameters accepted by this script, or <see langword="null"/> if not available.
|
||||
/// </summary>
|
||||
public virtual JsonElement? ParametersSchema => null;
|
||||
|
||||
/// <summary>
|
||||
/// Runs the script with the given arguments.
|
||||
/// </summary>
|
||||
|
||||
@@ -117,26 +117,24 @@ public sealed partial class AgentSkillsProvider : AIContextProvider
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentSkillsProvider"/> class
|
||||
/// with one or more inline (code-defined) skills.
|
||||
/// Initializes a new instance of the <see cref="AgentSkillsProvider"/> class.
|
||||
/// Duplicate skill names are automatically deduplicated (first occurrence wins).
|
||||
/// </summary>
|
||||
/// <param name="skills">The inline skills to include.</param>
|
||||
public AgentSkillsProvider(params AgentInlineSkill[] skills)
|
||||
: this(skills as IEnumerable<AgentInlineSkill>)
|
||||
/// <param name="skills">The skills to include.</param>
|
||||
public AgentSkillsProvider(params AgentSkill[] skills)
|
||||
: this(skills as IEnumerable<AgentSkill>)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentSkillsProvider"/> class
|
||||
/// with inline (code-defined) skills.
|
||||
/// Initializes a new instance of the <see cref="AgentSkillsProvider"/> class.
|
||||
/// Duplicate skill names are automatically deduplicated (first occurrence wins).
|
||||
/// </summary>
|
||||
/// <param name="skills">The inline skills to include.</param>
|
||||
/// <param name="skills">The skills to include.</param>
|
||||
/// <param name="options">Optional provider configuration.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory.</param>
|
||||
public AgentSkillsProvider(
|
||||
IEnumerable<AgentInlineSkill> skills,
|
||||
IEnumerable<AgentSkill> skills,
|
||||
AgentSkillsProviderOptions? options = null,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
: this(
|
||||
|
||||
@@ -11,15 +11,31 @@ namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Fluent builder for constructing an <see cref="AgentSkillsProvider"/> backed by a composite source.
|
||||
/// Intended for advanced scenarios where the simple <see cref="AgentSkillsProvider"/> constructors are insufficient.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use this builder to combine multiple skill sources into a single provider:
|
||||
/// For simple, single-source scenarios, prefer the <see cref="AgentSkillsProvider"/> constructors directly
|
||||
/// (e.g., passing a skill directory path or a set of skills). Use this builder when you need one or more
|
||||
/// of the following advanced capabilities:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><strong>Mixed skill types</strong> — combine file-based, code-defined (<see cref="AgentInlineSkill"/>),
|
||||
/// and class-based (<see cref="AgentClassSkill"/>) skills in a single provider.</description></item>
|
||||
/// <item><description><strong>Multiple file script runners</strong> — use different script runners for different
|
||||
/// file skill directories via per-source <c>scriptRunner</c> parameters on
|
||||
/// <see cref="UseFileSkill"/> / <see cref="UseFileSkills(IEnumerable{string}, AgentFileSkillsSourceOptions?, AgentFileSkillScriptRunner?)"/>.</description></item>
|
||||
/// <item><description><strong>Skill filtering</strong> — include or exclude skills using a predicate
|
||||
/// via <see cref="UseFilter"/>.</description></item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Example — combining file-based and code-defined skills:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// var provider = new AgentSkillsProviderBuilder()
|
||||
/// .UseFileSkills("/path/to/skills")
|
||||
/// .UseSkills(myInlineSkill1, myInlineSkill2)
|
||||
/// .UseFileScriptRunner(SubprocessScriptRunner.RunAsync)
|
||||
/// .Build();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for defining skills as C# classes that bundle all components together.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Inherit from this class to create a self-contained skill definition. Override the abstract
|
||||
/// properties to provide name, description, and instructions. Use <see cref="CreateResource(string, object, string?)"/>,
|
||||
/// <see cref="CreateResource(string, Delegate, string?)"/>, and <see cref="CreateScript"/> to define
|
||||
/// inline resources and scripts.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class PdfFormatterSkill : AgentClassSkill
|
||||
/// {
|
||||
/// private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
/// private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
///
|
||||
/// public override AgentSkillFrontmatter Frontmatter { get; } = new("pdf-formatter", "Format documents as PDF.");
|
||||
/// protected override string Instructions => "Use this skill to format documents...";
|
||||
///
|
||||
/// public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
/// [
|
||||
/// CreateResource("template", "Use this template..."),
|
||||
/// ];
|
||||
///
|
||||
/// public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
/// [
|
||||
/// CreateScript("format-pdf", FormatPdf),
|
||||
/// ];
|
||||
///
|
||||
/// private static string FormatPdf(string content) => content;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
|
||||
public abstract class AgentClassSkill : AgentSkill
|
||||
{
|
||||
private string? _content;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw instructions text for this skill.
|
||||
/// </summary>
|
||||
protected abstract string Instructions { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// Returns a synthesized XML document containing name, description, instructions, resources, and scripts.
|
||||
/// The result is cached after the first access. Override to provide custom content.
|
||||
/// </remarks>
|
||||
public override string Content => this._content ??= AgentInlineSkillContentBuilder.Build(
|
||||
this.Frontmatter.Name,
|
||||
this.Frontmatter.Description,
|
||||
this.Instructions,
|
||||
this.Resources,
|
||||
this.Scripts);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skill resource backed by a static value.
|
||||
/// </summary>
|
||||
/// <param name="name">The resource name.</param>
|
||||
/// <param name="value">The static resource value.</param>
|
||||
/// <param name="description">An optional description of the resource.</param>
|
||||
/// <returns>A new <see cref="AgentSkillResource"/> instance.</returns>
|
||||
protected static AgentSkillResource CreateResource(string name, object value, string? description = null)
|
||||
=> new AgentInlineSkillResource(name, value, description);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skill resource backed by a delegate that produces a dynamic value.
|
||||
/// </summary>
|
||||
/// <param name="name">The resource name.</param>
|
||||
/// <param name="method">A method that produces the resource value when requested.</param>
|
||||
/// <param name="description">An optional description of the resource.</param>
|
||||
/// <returns>A new <see cref="AgentSkillResource"/> instance.</returns>
|
||||
protected static AgentSkillResource CreateResource(string name, Delegate method, string? description = null)
|
||||
=> new AgentInlineSkillResource(name, method, description);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skill script backed by a delegate.
|
||||
/// </summary>
|
||||
/// <param name="name">The script name.</param>
|
||||
/// <param name="method">A method to execute when the script is invoked.</param>
|
||||
/// <param name="description">An optional description of the script.</param>
|
||||
/// <returns>A new <see cref="AgentSkillScript"/> instance.</returns>
|
||||
protected static AgentSkillScript CreateScript(string name, Delegate method, string? description = null)
|
||||
=> new AgentInlineSkillScript(name, method, description);
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
@@ -27,8 +25,8 @@ namespace Microsoft.Agents.AI;
|
||||
public sealed class AgentInlineSkill : AgentSkill
|
||||
{
|
||||
private readonly string _instructions;
|
||||
private List<AgentSkillResource>? _resources;
|
||||
private List<AgentSkillScript>? _scripts;
|
||||
private List<AgentInlineSkillResource>? _resources;
|
||||
private List<AgentInlineSkillScript>? _scripts;
|
||||
private string? _cachedContent;
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +75,7 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
public override AgentSkillFrontmatter Frontmatter { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Content => this._cachedContent ??= this.BuildContent();
|
||||
public override string Content => this._cachedContent ??= AgentInlineSkillContentBuilder.Build(this.Frontmatter.Name, this.Frontmatter.Description, this._instructions, this._resources, this._scripts);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources;
|
||||
@@ -125,91 +123,4 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
(this._scripts ??= []).Add(new AgentInlineSkillScript(name, method, description));
|
||||
return this;
|
||||
}
|
||||
|
||||
private string BuildContent()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"<name>{EscapeXmlString(this.Frontmatter.Name)}</name>\n")
|
||||
.Append($"<description>{EscapeXmlString(this.Frontmatter.Description)}</description>\n\n")
|
||||
.Append("<instructions>\n")
|
||||
.Append(EscapeXmlString(this._instructions))
|
||||
.Append("\n</instructions>");
|
||||
|
||||
if (this.Resources is { Count: > 0 })
|
||||
{
|
||||
sb.Append("\n\n<resources>\n");
|
||||
foreach (var resource in this.Resources)
|
||||
{
|
||||
if (resource.Description is not null)
|
||||
{
|
||||
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\" description=\"{EscapeXmlString(resource.Description)}\"/>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\"/>\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append("</resources>");
|
||||
}
|
||||
|
||||
if (this.Scripts is { Count: > 0 })
|
||||
{
|
||||
sb.Append("\n\n<scripts>\n");
|
||||
foreach (var script in this.Scripts)
|
||||
{
|
||||
JsonElement? parametersSchema = ((AgentInlineSkillScript)script).ParametersSchema;
|
||||
|
||||
if (script.Description is null && parametersSchema is null)
|
||||
{
|
||||
sb.Append($" <script name=\"{EscapeXmlString(script.Name)}\"/>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(script.Description is not null
|
||||
? $" <script name=\"{EscapeXmlString(script.Name)}\" description=\"{EscapeXmlString(script.Description)}\">\n"
|
||||
: $" <script name=\"{EscapeXmlString(script.Name)}\">\n");
|
||||
|
||||
if (parametersSchema is not null)
|
||||
{
|
||||
sb.Append($" <parameters_schema>{EscapeXmlString(parametersSchema.Value.GetRawText(), preserveQuotes: true)}</parameters_schema>\n");
|
||||
}
|
||||
|
||||
sb.Append(" </script>\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append("</scripts>");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes XML special characters: always escapes <c>&</c>, <c><</c>, <c>></c>,
|
||||
/// <c>"</c>, and <c>'</c>. When <paramref name="preserveQuotes"/> is <see langword="true"/>,
|
||||
/// quotes are left unescaped to preserve readability of embedded content such as JSON.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to escape.</param>
|
||||
/// <param name="preserveQuotes">
|
||||
/// When <see langword="true"/>, leaves <c>"</c> and <c>'</c> unescaped for use in XML element content (e.g., JSON).
|
||||
/// When <see langword="false"/> (default), escapes all XML special characters including quotes.
|
||||
/// </param>
|
||||
private static string EscapeXmlString(string value, bool preserveQuotes = false)
|
||||
{
|
||||
var result = value
|
||||
.Replace("&", "&")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">");
|
||||
|
||||
if (!preserveQuotes)
|
||||
{
|
||||
result = result
|
||||
.Replace("\"", """)
|
||||
.Replace("'", "'");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Internal helper that builds XML-structured content strings for code-defined and class-based skills.
|
||||
/// </summary>
|
||||
internal static class AgentInlineSkillContentBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the complete skill content containing name, description, instructions, resources, and scripts.
|
||||
/// </summary>
|
||||
/// <param name="name">The skill name.</param>
|
||||
/// <param name="description">The skill description.</param>
|
||||
/// <param name="instructions">The raw instructions text.</param>
|
||||
/// <param name="resources">Optional resources associated with the skill.</param>
|
||||
/// <param name="scripts">Optional scripts associated with the skill.</param>
|
||||
/// <returns>An XML-structured content string.</returns>
|
||||
public static string Build(
|
||||
string name,
|
||||
string description,
|
||||
string instructions,
|
||||
IReadOnlyList<AgentSkillResource>? resources,
|
||||
IReadOnlyList<AgentSkillScript>? scripts)
|
||||
{
|
||||
_ = Throw.IfNullOrWhitespace(name);
|
||||
_ = Throw.IfNullOrWhitespace(description);
|
||||
_ = Throw.IfNullOrWhitespace(instructions);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"<name>{EscapeXmlString(name)}</name>\n")
|
||||
.Append($"<description>{EscapeXmlString(description)}</description>\n\n")
|
||||
.Append("<instructions>\n")
|
||||
.Append(EscapeXmlString(instructions))
|
||||
.Append("\n</instructions>");
|
||||
|
||||
if (resources is { Count: > 0 })
|
||||
{
|
||||
sb.Append("\n\n<resources>\n");
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Description is not null)
|
||||
{
|
||||
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\" description=\"{EscapeXmlString(resource.Description)}\"/>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\"/>\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append("</resources>");
|
||||
}
|
||||
|
||||
if (scripts is { Count: > 0 })
|
||||
{
|
||||
sb.Append("\n\n<scripts>\n");
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
var parametersSchema = script.ParametersSchema;
|
||||
|
||||
if (script.Description is null && parametersSchema is null)
|
||||
{
|
||||
sb.Append($" <script name=\"{EscapeXmlString(script.Name)}\"/>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(script.Description is not null
|
||||
? $" <script name=\"{EscapeXmlString(script.Name)}\" description=\"{EscapeXmlString(script.Description)}\">\n"
|
||||
: $" <script name=\"{EscapeXmlString(script.Name)}\">\n");
|
||||
|
||||
if (parametersSchema is not null)
|
||||
{
|
||||
sb.Append($" <parameters_schema>{EscapeXmlString(parametersSchema.Value.GetRawText(), preserveQuotes: true)}</parameters_schema>\n");
|
||||
}
|
||||
|
||||
sb.Append(" </script>\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append("</scripts>");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes XML special characters: always escapes <c>&</c>, <c><</c>, <c>></c>,
|
||||
/// <c>"</c>, and <c>'</c>. When <paramref name="preserveQuotes"/> is <see langword="true"/>,
|
||||
/// quotes are left unescaped to preserve readability of embedded content such as JSON.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to escape.</param>
|
||||
/// <param name="preserveQuotes">
|
||||
/// When <see langword="true"/>, leaves <c>"</c> and <c>'</c> unescaped for use in XML element content (e.g., JSON).
|
||||
/// When <see langword="false"/> (default), escapes all XML special characters including quotes.
|
||||
/// </param>
|
||||
private static string EscapeXmlString(string value, bool preserveQuotes = false)
|
||||
{
|
||||
var result = value
|
||||
.Replace("&", "&")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">");
|
||||
|
||||
if (!preserveQuotes)
|
||||
{
|
||||
result = result
|
||||
.Replace("\"", """)
|
||||
.Replace("'", "'");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal sealed class AgentInlineSkillScript : AgentSkillScript
|
||||
/// <summary>
|
||||
/// Gets the JSON schema describing the parameters accepted by this script, or <see langword="null"/> if not available.
|
||||
/// </summary>
|
||||
public JsonElement? ParametersSchema => this._function.JsonSchema;
|
||||
public override JsonElement? ParametersSchema => this._function.JsonSchema;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<object?> RunAsync(AgentSkill skill, AIFunctionArguments arguments, CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -26,7 +26,6 @@ internal static class DiagnosticIds
|
||||
// We use the same IDs so consumers do not need to suppress additional diagnostics
|
||||
// when using the experimental OpenAI APIs.
|
||||
internal const string AIOpenAIResponses = "OPENAI001";
|
||||
internal const string AIOpenAIAssistants = "OPENAI001";
|
||||
|
||||
private const string MEAIExperiments = "MEAI001";
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ namespace Shared.Foundry;
|
||||
|
||||
internal static class AgentFactory
|
||||
{
|
||||
public static async ValueTask<AgentVersion> CreateAgentAsync(
|
||||
public static async ValueTask<ProjectsAgentVersion> CreateAgentAsync(
|
||||
this AIProjectClient aiProjectClient,
|
||||
string agentName,
|
||||
AgentDefinition agentDefinition,
|
||||
ProjectsAgentDefinition agentDefinition,
|
||||
string agentDescription)
|
||||
{
|
||||
AgentVersionCreationOptions options =
|
||||
ProjectsAgentVersionCreationOptions options =
|
||||
new(agentDefinition)
|
||||
{
|
||||
Description = agentDescription,
|
||||
@@ -29,7 +29,7 @@ internal static class AgentFactory
|
||||
},
|
||||
};
|
||||
|
||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, options).ConfigureAwait(false);
|
||||
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(agentName, options).ConfigureAwait(false);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
try
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Foundry.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for versioned <see cref="FoundryAgent"/> creation via
|
||||
/// <c>AIProjectClient.Agents.CreateAgentVersionAsync</c> and <c>AIProjectClient.AsAIAgent(AgentVersion)</c>.
|
||||
/// <c>AIProjectClient.AgentAdministrationClient.CreateAgentVersionAsync</c> and <c>AIProjectClient.AsAIAgent(ProjectsAgentVersion)</c>.
|
||||
/// </summary>
|
||||
public class FoundryVersionedAgentCreateTests
|
||||
{
|
||||
@@ -32,10 +32,10 @@ public class FoundryVersionedAgentCreateTests
|
||||
const string AgentInstructions = "You are an integration test agent";
|
||||
|
||||
// Act.
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
AgentName,
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = AgentInstructions
|
||||
})
|
||||
@@ -53,17 +53,17 @@ public class FoundryVersionedAgentCreateTests
|
||||
Assert.Equal(AgentDescription, agent.Description);
|
||||
Assert.Equal(AgentInstructions, agent.GetService<ChatClientAgent>()!.Instructions);
|
||||
|
||||
var agentRecord = await this._client.Agents.GetAgentAsync(agent.Name);
|
||||
var agentRecord = await this._client.AgentAdministrationClient.GetAgentAsync(agent.Name);
|
||||
Assert.NotNull(agentRecord);
|
||||
Assert.Equal(AgentName, agentRecord.Value.Name);
|
||||
var definition = Assert.IsType<PromptAgentDefinition>(agentRecord.Value.GetLatestVersion().Definition);
|
||||
var definition = Assert.IsType<DeclarativeAgentDefinition>(agentRecord.Value.GetLatestVersion().Definition);
|
||||
Assert.Equal(AgentDescription, agentRecord.Value.GetLatestVersion().Description);
|
||||
Assert.Equal(AgentInstructions, definition.Instructions);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
await this._client.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,15 +95,15 @@ public class FoundryVersionedAgentCreateTests
|
||||
var vectorStoreMetadata = await projectOpenAIClient.GetProjectVectorStoresClient().CreateVectorStoreAsync(options: new() { FileIds = { uploadedAgentFile.Id }, Name = "WordCodeLookup_VectorStore" });
|
||||
|
||||
// Act — create agent version with FileSearch tool via native SDK, then wrap with AsAIAgent.
|
||||
var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
var definition = new DeclarativeAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = { ResponseTool.CreateFileSearchTool(vectorStoreIds: [vectorStoreMetadata.Value.Id]) }
|
||||
};
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
AgentName,
|
||||
new AgentVersionCreationOptions(definition));
|
||||
new ProjectsAgentVersionCreationOptions(definition));
|
||||
|
||||
var agent = this._client.AsAIAgent(agentVersion);
|
||||
|
||||
@@ -117,7 +117,7 @@ public class FoundryVersionedAgentCreateTests
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
await this._client.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
await projectOpenAIClient.GetProjectVectorStoresClient().DeleteVectorStoreAsync(vectorStoreMetadata.Value.Id);
|
||||
await projectOpenAIClient.GetProjectFilesClient().DeleteFileAsync(uploadedAgentFile.Id);
|
||||
File.Delete(searchFilePath);
|
||||
@@ -149,15 +149,15 @@ public class FoundryVersionedAgentCreateTests
|
||||
);
|
||||
|
||||
// Act — create agent version with CodeInterpreter tool via native SDK, then wrap with AsAIAgent.
|
||||
var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
var definition = new DeclarativeAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = { ResponseTool.CreateCodeInterpreterTool(new CodeInterpreterToolContainer(CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration([uploadedCodeFile.Id]))) }
|
||||
};
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
AgentName,
|
||||
new AgentVersionCreationOptions(definition));
|
||||
new ProjectsAgentVersionCreationOptions(definition));
|
||||
|
||||
var agent = this._client.AsAIAgent(agentVersion);
|
||||
|
||||
@@ -171,7 +171,7 @@ public class FoundryVersionedAgentCreateTests
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
await this._client.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
await projectOpenAIClient.GetProjectFilesClient().DeleteFileAsync(uploadedCodeFile.Id);
|
||||
File.Delete(codeFilePath);
|
||||
}
|
||||
@@ -252,14 +252,14 @@ public class FoundryVersionedAgentCreateTests
|
||||
Description = "Retrieve information about countries by currency code"
|
||||
};
|
||||
|
||||
var definition = new PromptAgentDefinition(model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
var definition = new DeclarativeAgentDefinition(model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = { (ResponseTool)AgentTool.CreateOpenApiTool(openApiFunction) }
|
||||
Tools = { (ResponseTool)ProjectsAgentTool.CreateOpenApiTool(openApiFunction) }
|
||||
};
|
||||
|
||||
AgentVersionCreationOptions creationOptions = new(definition);
|
||||
AgentVersion agentVersion = await this._client.Agents.CreateAgentVersionAsync(AgentName, creationOptions);
|
||||
ProjectsAgentVersionCreationOptions creationOptions = new(definition);
|
||||
ProjectsAgentVersion agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(AgentName, creationOptions);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -269,7 +269,7 @@ public class FoundryVersionedAgentCreateTests
|
||||
// Assert the agent was created correctly and retains version metadata.
|
||||
Assert.NotNull(agent);
|
||||
Assert.Equal(AgentName, agent.Name);
|
||||
var retrievedVersion = agent.GetService<AgentVersion>();
|
||||
var retrievedVersion = agent.GetService<ProjectsAgentVersion>();
|
||||
Assert.NotNull(retrievedVersion);
|
||||
|
||||
// Step 3: Call RunAsync to trigger the server-side OpenAPI function.
|
||||
@@ -301,7 +301,7 @@ public class FoundryVersionedAgentCreateTests
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(AgentName);
|
||||
await this._client.AgentAdministrationClient.DeleteAgentAsync(AgentName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,15 +317,15 @@ public class FoundryVersionedAgentCreateTests
|
||||
|
||||
// Create agent version with the function tool registered in the server-side definition,
|
||||
// then wrap with AsAIAgent passing the local AIFunction implementation.
|
||||
var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
var definition = new DeclarativeAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
};
|
||||
definition.Tools.Add(weatherFunction.AsOpenAIResponseTool());
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
AgentName,
|
||||
new AgentVersionCreationOptions(definition));
|
||||
new ProjectsAgentVersionCreationOptions(definition));
|
||||
|
||||
FoundryAgent agent = this._client.AsAIAgent(agentVersion, tools: [weatherFunction]);
|
||||
|
||||
@@ -342,7 +342,7 @@ public class FoundryVersionedAgentCreateTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
await this._client.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Foundry.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration test fixture that creates versioned Foundry agents via
|
||||
/// <c>AIProjectClient.Agents.CreateAgentVersionAsync</c> and wraps them
|
||||
/// with <c>AIProjectClient.AsAIAgent(AgentVersion)</c>.
|
||||
/// <c>AIProjectClient.AgentAdministrationClient.CreateAgentVersionAsync</c> and wraps them
|
||||
/// with <c>AIProjectClient.AsAIAgent(ProjectsAgentVersion)</c>.
|
||||
/// </summary>
|
||||
public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
{
|
||||
@@ -121,7 +121,7 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
string instructions = "You are a helpful assistant.",
|
||||
IList<AITool>? aiTools = null)
|
||||
{
|
||||
var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
var definition = new DeclarativeAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = instructions
|
||||
};
|
||||
@@ -139,9 +139,9 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
}
|
||||
}
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
GenerateUniqueAgentName(name),
|
||||
new AgentVersionCreationOptions(definition));
|
||||
new ProjectsAgentVersionCreationOptions(definition));
|
||||
|
||||
return this._client.AsAIAgent(agentVersion, tools: aiTools).GetService<ChatClientAgent>()!;
|
||||
}
|
||||
@@ -150,15 +150,15 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
{
|
||||
options.Name ??= GenerateUniqueAgentName("HelpfulAssistant");
|
||||
|
||||
var definition = new PromptAgentDefinition(
|
||||
var definition = new DeclarativeAgentDefinition(
|
||||
options.ChatOptions?.ModelId ?? TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = options.ChatOptions?.Instructions
|
||||
};
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
options.Name,
|
||||
new AgentVersionCreationOptions(definition) { Description = options.Description });
|
||||
new ProjectsAgentVersionCreationOptions(definition) { Description = options.Description });
|
||||
|
||||
var agent = this._client.AsAIAgent(agentVersion, tools: options.ChatOptions?.Tools);
|
||||
|
||||
@@ -169,7 +169,7 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
$"{baseName}-{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent) =>
|
||||
this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
this._client.AgentAdministrationClient.DeleteAgentAsync(agent.Name);
|
||||
|
||||
public async Task DeleteSessionAsync(AgentSession session)
|
||||
{
|
||||
@@ -201,7 +201,7 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
|
||||
if (this._client is not null && this._agent is not null)
|
||||
{
|
||||
return new ValueTask(this._client.Agents.DeleteAgentAsync(this._agent.Name));
|
||||
return new ValueTask(this._client.AgentAdministrationClient.DeleteAgentAsync(this._agent.Name));
|
||||
}
|
||||
|
||||
return default;
|
||||
@@ -211,10 +211,10 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
{
|
||||
this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
GenerateUniqueAgentName("HelpfulAssistant"),
|
||||
new AgentVersionCreationOptions(
|
||||
new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
new ProjectsAgentVersionCreationOptions(
|
||||
new DeclarativeAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = "You are a helpful assistant."
|
||||
}));
|
||||
@@ -227,15 +227,15 @@ public class FoundryVersionedAgentFixture : IChatClientAgentFixture
|
||||
this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
options.Name ??= GenerateUniqueAgentName("HelpfulAssistant");
|
||||
|
||||
var definition = new PromptAgentDefinition(
|
||||
var definition = new DeclarativeAgentDefinition(
|
||||
options.ChatOptions?.ModelId ?? TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions = options.ChatOptions?.Instructions
|
||||
};
|
||||
|
||||
var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
|
||||
var agentVersion = await this._client.AgentAdministrationClient.CreateAgentVersionAsync(
|
||||
options.Name,
|
||||
new AgentVersionCreationOptions(definition) { Description = options.Description });
|
||||
new ProjectsAgentVersionCreationOptions(definition) { Description = options.Description });
|
||||
|
||||
this._agent = this._client.AsAIAgent(agentVersion, tools: options.ChatOptions?.Tools);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.Memory;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Foundry;
|
||||
|
||||
+129
-129
@@ -187,7 +187,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region AsAIAgent(AIProjectClient, AgentRecord) Tests
|
||||
#region AsAIAgent(AIProjectClient, ProjectsAgentRecord) Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
||||
@@ -197,7 +197,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient? client = null;
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
@@ -217,20 +217,20 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
mockClient.Object.AsAIAgent((AgentRecord)null!));
|
||||
mockClient.Object.AsAIAgent((ProjectsAgentRecord)null!));
|
||||
|
||||
Assert.Equal("agentRecord", exception.ParamName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentRecord creates a valid agent.
|
||||
/// Verify that AsAIAgent with ProjectsAgentRecord creates a valid agent.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentRecord_CreatesValidAgent()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
@@ -243,14 +243,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentRecord and clientFactory applies the factory.
|
||||
/// Verify that AsAIAgent with ProjectsAgentRecord and clientFactory applies the factory.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentRecord_WithClientFactory_AppliesFactoryCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
TestChatClient? testChatClient = null;
|
||||
|
||||
// Act
|
||||
@@ -267,7 +267,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region AsAIAgent(AIProjectClient, AgentVersion) Tests
|
||||
#region AsAIAgent(AIProjectClient, ProjectsAgentVersion) Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
||||
@@ -277,7 +277,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient? client = null;
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
@@ -297,20 +297,20 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
mockClient.Object.AsAIAgent((AgentVersion)null!));
|
||||
mockClient.Object.AsAIAgent((ProjectsAgentVersion)null!));
|
||||
|
||||
Assert.Equal("agentVersion", exception.ParamName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentVersion creates a valid agent.
|
||||
/// Verify that AsAIAgent with ProjectsAgentVersion creates a valid agent.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentVersion_CreatesValidAgent()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
@@ -323,14 +323,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentVersion and clientFactory applies the factory.
|
||||
/// Verify that AsAIAgent with ProjectsAgentVersion and clientFactory applies the factory.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentVersion_WithClientFactory_AppliesFactoryCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
TestChatClient? testChatClient = null;
|
||||
|
||||
// Act
|
||||
@@ -353,7 +353,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
var tools = new List<AITool>
|
||||
{
|
||||
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
||||
@@ -375,7 +375,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act - should not throw even without tools when requireInvocableTools is false
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
@@ -439,7 +439,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region AsAIAgent(AIProjectClient, AgentRecord) with tools Tests
|
||||
#region AsAIAgent(AIProjectClient, ProjectsAgentRecord) with tools Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with additional tools when the definition has no tools does not throw and results in an agent with no tools.
|
||||
@@ -449,7 +449,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
var tools = new List<AITool>
|
||||
{
|
||||
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
||||
@@ -463,9 +463,9 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
Assert.IsType<FoundryAgent>(agent);
|
||||
var chatClient = agent.GetService<IChatClient>();
|
||||
Assert.NotNull(chatClient);
|
||||
var agentVersion = chatClient.GetService<AgentVersion>();
|
||||
var agentVersion = chatClient.GetService<ProjectsAgentVersion>();
|
||||
Assert.NotNull(agentVersion);
|
||||
var definition = Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
||||
var definition = Assert.IsType<DeclarativeAgentDefinition>(agentVersion.Definition);
|
||||
Assert.Empty(definition.Tools);
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord, tools: null);
|
||||
@@ -502,7 +502,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Manually add tools to the definition to simulate inline tools
|
||||
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
||||
if (agentVersion.Definition is DeclarativeAgentDefinition promptDef)
|
||||
{
|
||||
promptDef.Tools.Add(ResponseTool.CreateFunctionTool("inline_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
||||
}
|
||||
@@ -513,9 +513,9 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
// Act & Assert
|
||||
var agent = client.AsAIAgent(agentVersion, tools: [invocableInlineAITool, shouldBeIgnoredTool]);
|
||||
Assert.NotNull(agent);
|
||||
var version = agent.GetService<AgentVersion>();
|
||||
var version = agent.GetService<ProjectsAgentVersion>();
|
||||
Assert.NotNull(version);
|
||||
var definition = Assert.IsType<PromptAgentDefinition>(version.Definition);
|
||||
var definition = Assert.IsType<DeclarativeAgentDefinition>(version.Definition);
|
||||
Assert.NotEmpty(definition.Tools);
|
||||
Assert.NotNull(GetAgentChatOptions(agent));
|
||||
Assert.NotNull(GetAgentChatOptions(agent)!.Tools);
|
||||
@@ -535,7 +535,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
var tools = new List<AITool>
|
||||
{
|
||||
AIFunctionFactory.Create(() => "tool1", "param_tool_1", "First parameter tool"),
|
||||
@@ -550,7 +550,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
Assert.IsType<FoundryAgent>(agent);
|
||||
var chatClient = agent.GetService<IChatClient>();
|
||||
Assert.NotNull(chatClient);
|
||||
var agentVersion = chatClient.GetService<AgentVersion>();
|
||||
var agentVersion = chatClient.GetService<ProjectsAgentVersion>();
|
||||
Assert.NotNull(agentVersion);
|
||||
}
|
||||
|
||||
@@ -565,7 +565,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
public async Task CreateAIAgentAsync_WithResponseToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
||||
{
|
||||
// Arrange
|
||||
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
||||
var definition = new DeclarativeAgentDefinition("test-model") { Instructions = "Test instructions" };
|
||||
|
||||
var fabricToolOptions = new FabricDataAgentToolOptions();
|
||||
fabricToolOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
|
||||
@@ -577,33 +577,33 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
// Add tools to the definition
|
||||
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateBingCustomSearchTool(new BingCustomSearchToolOptions([new BingCustomSearchConfiguration("connection-id", "instance-name")])));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateBrowserAutomationTool(new BrowserAutomationToolOptions(new BrowserAutomationToolConnectionParameters("id"))));
|
||||
definition.Tools.Add(AgentTool.CreateA2ATool(new Uri("https://test-uri.microsoft.com")));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateBingGroundingTool(new BingGroundingSearchToolOptions([new BingGroundingSearchConfiguration("connection-id")])));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateMicrosoftFabricTool(fabricToolOptions));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateOpenApiTool(new OpenApiFunctionDefinition("name", BinaryData.FromString(OpenAPISpec), new OpenAPIAnonymousAuthenticationDetails())));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateSharepointTool(sharepointOptions));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateStructuredOutputsTool(structuredOutputs));
|
||||
definition.Tools.Add((ResponseTool)AgentTool.CreateAzureAISearchTool(new AzureAISearchToolOptions([new AzureAISearchToolIndex() { IndexName = "name" }])));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateBingCustomSearchTool(new BingCustomSearchToolOptions([new BingCustomSearchConfiguration("connection-id", "instance-name")])));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateBrowserAutomationTool(new BrowserAutomationToolOptions(new BrowserAutomationToolConnectionParameters("id"))));
|
||||
definition.Tools.Add(ProjectsAgentTool.CreateA2ATool(new Uri("https://test-uri.microsoft.com")));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateBingGroundingTool(new BingGroundingSearchToolOptions([new BingGroundingSearchConfiguration("connection-id")])));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateMicrosoftFabricTool(fabricToolOptions));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateOpenApiTool(new OpenApiFunctionDefinition("name", BinaryData.FromString(OpenAPISpec), new OpenAPIAnonymousAuthenticationDetails())));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateSharepointTool(sharepointOptions));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateStructuredOutputsTool(structuredOutputs));
|
||||
definition.Tools.Add((ResponseTool)ProjectsAgentTool.CreateAzureAISearchTool(new AzureAISearchToolOptions([new AzureAISearchToolIndex() { IndexName = "name" }])));
|
||||
|
||||
// Generate agent definition response with the tools
|
||||
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
|
||||
|
||||
using var testClient = CreateTestAgentClientWithHandler(agentDefinitionResponse: definitionResponse);
|
||||
|
||||
var options = new AgentVersionCreationOptions(definition);
|
||||
var options = new ProjectsAgentVersionCreationOptions(definition);
|
||||
|
||||
// Act
|
||||
var agentVersion = (await testClient.Client.Agents.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agentVersion = (await testClient.Client.AgentAdministrationClient.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agent = testClient.Client.AsAIAgent(agentVersion);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.IsType<FoundryAgent>(agent);
|
||||
var agentVersion2 = agent.GetService<AgentVersion>()!;
|
||||
var agentVersion2 = agent.GetService<ProjectsAgentVersion>()!;
|
||||
Assert.NotNull(agentVersion);
|
||||
if (agentVersion2.Definition is PromptAgentDefinition promptDef)
|
||||
if (agentVersion2.Definition is DeclarativeAgentDefinition promptDef)
|
||||
{
|
||||
Assert.NotEmpty(promptDef.Tools);
|
||||
Assert.Equal(10, promptDef.Tools.Count);
|
||||
@@ -624,19 +624,19 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
functionDescription: "Gets the user's name, as used for friendly address."
|
||||
);
|
||||
|
||||
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
||||
var definition = new DeclarativeAgentDefinition("test-model") { Instructions = "Test" };
|
||||
definition.Tools.Add(functionTool);
|
||||
|
||||
// Generate response with the declarative function
|
||||
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
||||
var definitionResponse = new DeclarativeAgentDefinition("test-model") { Instructions = "Test" };
|
||||
definitionResponse.Tools.Add(functionTool);
|
||||
|
||||
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
||||
|
||||
var options = new AgentVersionCreationOptions(definition);
|
||||
var options = new ProjectsAgentVersionCreationOptions(definition);
|
||||
|
||||
// Act
|
||||
var agentVersion = (await testClient.Client.Agents.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agentVersion = (await testClient.Client.AgentAdministrationClient.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agent = testClient.Client.AsAIAgent(agentVersion);
|
||||
|
||||
// Assert
|
||||
@@ -652,7 +652,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
using var testClient = CreateTestAgentClientWithHandler();
|
||||
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
||||
var definition = new DeclarativeAgentDefinition("test-model") { Instructions = "Test" };
|
||||
|
||||
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
|
||||
using var doc = JsonDocument.Parse("{}");
|
||||
@@ -661,10 +661,10 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
// Add to definition
|
||||
definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
||||
|
||||
var options = new AgentVersionCreationOptions(definition);
|
||||
var options = new ProjectsAgentVersionCreationOptions(definition);
|
||||
|
||||
// Act
|
||||
var agentVersion = (await testClient.Client.Agents.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agentVersion = (await testClient.Client.AgentAdministrationClient.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agent = testClient.Client.AsAIAgent(agentVersion);
|
||||
|
||||
// Assert
|
||||
@@ -679,7 +679,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
public async Task AsAIAgent_WithDeclarativeFunctionInDefinition_AcceptsDeclarativeFunctionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
||||
var definition = new DeclarativeAgentDefinition("test-model") { Instructions = "Test" };
|
||||
|
||||
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
|
||||
using var doc = JsonDocument.Parse("{}");
|
||||
@@ -689,15 +689,15 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
||||
|
||||
// Generate response with the declarative function
|
||||
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
||||
var definitionResponse = new DeclarativeAgentDefinition("test-model") { Instructions = "Test" };
|
||||
definitionResponse.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
||||
|
||||
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
||||
|
||||
var options = new AgentVersionCreationOptions(definition);
|
||||
var options = new ProjectsAgentVersionCreationOptions(definition);
|
||||
|
||||
// Act
|
||||
var agentVersion = (await testClient.Client.Agents.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agentVersion = (await testClient.Client.AgentAdministrationClient.CreateAgentVersionAsync("test-agent", options)).Value;
|
||||
var agent = testClient.Client.AsAIAgent(agentVersion);
|
||||
|
||||
// Assert
|
||||
@@ -758,7 +758,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
int factoryCallCount = 0;
|
||||
|
||||
// Act
|
||||
@@ -785,7 +785,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent1 = client.AsAIAgent(
|
||||
@@ -904,7 +904,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
// Arrange
|
||||
var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
||||
|
||||
var agentVersion = (await aiProjectClient.Agents.CreateAgentVersionAsync("test-agent", new AgentVersionCreationOptions(new PromptAgentDefinition("test-model") { Instructions = "Test instructions" }))).Value;
|
||||
var agentVersion = (await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync("test-agent", new ProjectsAgentVersionCreationOptions(new DeclarativeAgentDefinition("test-model") { Instructions = "Test instructions" }))).Value;
|
||||
|
||||
// Act
|
||||
var agent = aiProjectClient.AsAIAgent(agentVersion);
|
||||
@@ -1043,21 +1043,21 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetService<AgentRecord> Tests
|
||||
#region GetService<ProjectsAgentRecord> Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns AgentRecord for agents created from AgentRecord.
|
||||
/// Verify that GetService returns ProjectsAgentRecord for agents created from ProjectsAgentRecord.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentRecord_ReturnsAgentRecord()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
var retrievedRecord = agent.GetService<AgentRecord>();
|
||||
var retrievedRecord = agent.GetService<ProjectsAgentRecord>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedRecord);
|
||||
@@ -1065,7 +1065,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentRecord when agent is created from AgentReference.
|
||||
/// Verify that GetService returns null for ProjectsAgentRecord when agent is created from AgentReference.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentReference_ReturnsNullForAgentRecord()
|
||||
@@ -1076,7 +1076,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentReference);
|
||||
var retrievedRecord = agent.GetService<AgentRecord>();
|
||||
var retrievedRecord = agent.GetService<ProjectsAgentRecord>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(retrievedRecord);
|
||||
@@ -1084,21 +1084,21 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetService<AgentVersion> Tests
|
||||
#region GetService<ProjectsAgentVersion> Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns AgentVersion for agents created from AgentVersion.
|
||||
/// Verify that GetService returns ProjectsAgentVersion for agents created from ProjectsAgentVersion.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentVersion_ReturnsAgentVersion()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
var retrievedVersion = agent.GetService<AgentVersion>();
|
||||
var retrievedVersion = agent.GetService<ProjectsAgentVersion>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedVersion);
|
||||
@@ -1106,7 +1106,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentVersion when agent is created from AgentReference.
|
||||
/// Verify that GetService returns null for ProjectsAgentVersion when agent is created from AgentReference.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentReference_ReturnsNullForAgentVersion()
|
||||
@@ -1117,7 +1117,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentReference);
|
||||
var retrievedVersion = agent.GetService<AgentVersion>();
|
||||
var retrievedVersion = agent.GetService<ProjectsAgentVersion>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(retrievedVersion);
|
||||
@@ -1128,14 +1128,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
#region ChatClientMetadata Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatClientMetadata is properly populated for agents created from AgentRecord.
|
||||
/// Verify that ChatClientMetadata is properly populated for agents created from ProjectsAgentRecord.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatClientMetadata_WithAgentRecord_IsPopulatedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
@@ -1147,18 +1147,18 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatClientMetadata.DefaultModelId is set from PromptAgentDefinition model property.
|
||||
/// Verify that ChatClientMetadata.DefaultModelId is set from DeclarativeAgentDefinition model property.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatClientMetadata_WithPromptAgentDefinition_SetsDefaultModelIdFromModel()
|
||||
public void ChatClientMetadata_WithDeclarativeAgentDefinition_SetsDefaultModelIdFromModel()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
var definition = new PromptAgentDefinition("gpt-4-turbo")
|
||||
var definition = new DeclarativeAgentDefinition("gpt-4-turbo")
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
};
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord(definition);
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord(definition);
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
@@ -1172,14 +1172,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatClientMetadata is properly populated for agents created from AgentVersion.
|
||||
/// Verify that ChatClientMetadata is properly populated for agents created from ProjectsAgentVersion.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatClientMetadata_WithAgentVersion_IsPopulatedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
@@ -1188,7 +1188,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
// Assert
|
||||
Assert.NotNull(metadata);
|
||||
Assert.NotNull(metadata.DefaultModelId);
|
||||
Assert.Equal((agentVersion.Definition as PromptAgentDefinition)!.Model, metadata.DefaultModelId);
|
||||
Assert.Equal((agentVersion.Definition as DeclarativeAgentDefinition)!.Model, metadata.DefaultModelId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1216,14 +1216,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentReference when agent is created from AgentRecord.
|
||||
/// Verify that GetService returns null for AgentReference when agent is created from ProjectsAgentRecord.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentRecord_ReturnsAlsoAgentReference()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
@@ -1235,14 +1235,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentReference when agent is created from AgentVersion.
|
||||
/// Verify that GetService returns null for AgentReference when agent is created from ProjectsAgentVersion.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentVersion_ReturnsAlsoAgentReference()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
@@ -1278,14 +1278,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
#region Empty Version and ID Handling Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentRecord handles empty version by using "latest" as fallback.
|
||||
/// Verify that AsAIAgent with ProjectsAgentRecord handles empty version by using "latest" as fallback.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentRecordEmptyVersion_CreatesAgentWithGeneratedId()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecordWithEmptyVersion();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecordWithEmptyVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
@@ -1297,14 +1297,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentVersion handles empty version by using "latest" as fallback.
|
||||
/// Verify that AsAIAgent with ProjectsAgentVersion handles empty version by using "latest" as fallback.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentVersionEmptyVersion_CreatesAgentWithGeneratedId()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersionWithEmptyVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersionWithEmptyVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
@@ -1316,14 +1316,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentRecord handles whitespace-only version by using "latest" as fallback.
|
||||
/// Verify that AsAIAgent with ProjectsAgentRecord handles whitespace-only version by using "latest" as fallback.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentRecordWhitespaceVersion_CreatesAgentWithGeneratedId()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecordWithWhitespaceVersion();
|
||||
ProjectsAgentRecord agentRecord = this.CreateTestAgentRecordWithWhitespaceVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentRecord);
|
||||
@@ -1335,14 +1335,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent with AgentVersion handles whitespace-only version by using "latest" as fallback.
|
||||
/// Verify that AsAIAgent with ProjectsAgentVersion handles whitespace-only version by using "latest" as fallback.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentVersionWhitespaceVersion_CreatesAgentWithGeneratedId()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersionWithWhitespaceVersion();
|
||||
ProjectsAgentVersion agentVersion = this.CreateTestAgentVersionWithWhitespaceVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(agentVersion);
|
||||
@@ -1364,11 +1364,11 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
public void AsAIAgent_WithServerHostedTools_AddsToolsToAgentOptions()
|
||||
{
|
||||
// Arrange
|
||||
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
||||
DeclarativeAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
||||
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
||||
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson(agentDefinition: definition)))!;
|
||||
ProjectsAgentVersion agentVersion = ModelReaderWriter.Read<ProjectsAgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson(agentDefinition: definition)))!;
|
||||
|
||||
// Act - no tools provided, but requireInvocableTools is false when no tools param is passed
|
||||
FoundryAgent agent = client.AsAIAgent(agentVersion);
|
||||
@@ -1385,7 +1385,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
/// <summary>
|
||||
/// Creates a test AIProjectClient with fake behavior.
|
||||
/// </summary>
|
||||
private FakeAgentClient CreateTestAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
||||
private FakeAgentClient CreateTestAgentClient(string? agentName = null, string? instructions = null, string? description = null, ProjectsAgentDefinition? agentDefinitionResponse = null)
|
||||
{
|
||||
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse);
|
||||
}
|
||||
@@ -1395,7 +1395,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
/// Used for tests that exercise the protocol-method code path (CreateAgentVersion).
|
||||
/// The returned client must be disposed to clean up the underlying HttpClient/handler.
|
||||
/// </summary>
|
||||
private static DisposableTestClient CreateTestAgentClientWithHandler(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
||||
private static DisposableTestClient CreateTestAgentClientWithHandler(string? agentName = null, string? instructions = null, string? description = null, ProjectsAgentDefinition? agentDefinitionResponse = null)
|
||||
{
|
||||
var responseJson = TestDataUtil.GetAgentVersionResponseJson(agentName, agentDefinitionResponse, instructions, description);
|
||||
|
||||
@@ -1439,59 +1439,59 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AgentRecord for testing.
|
||||
/// Creates a test ProjectsAgentRecord for testing.
|
||||
/// </summary>
|
||||
private AgentRecord CreateTestAgentRecord(AgentDefinition? agentDefinition = null)
|
||||
private ProjectsAgentRecord CreateTestAgentRecord(ProjectsAgentDefinition? agentDefinition = null)
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson(agentDefinition: agentDefinition)))!;
|
||||
return ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson(agentDefinition: agentDefinition)))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AIProjectClient with empty version fields for testing hosted MCP agents.
|
||||
/// </summary>
|
||||
private FakeAgentClient CreateTestAgentClientWithEmptyVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
||||
private FakeAgentClient CreateTestAgentClientWithEmptyVersion(string? agentName = null, string? instructions = null, string? description = null, ProjectsAgentDefinition? agentDefinitionResponse = null)
|
||||
{
|
||||
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, useEmptyVersion: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AgentRecord with empty version for testing hosted MCP agents.
|
||||
/// Creates a test ProjectsAgentRecord with empty version for testing hosted MCP agents.
|
||||
/// </summary>
|
||||
private AgentRecord CreateTestAgentRecordWithEmptyVersion(AgentDefinition? agentDefinition = null)
|
||||
private ProjectsAgentRecord CreateTestAgentRecordWithEmptyVersion(ProjectsAgentDefinition? agentDefinition = null)
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithEmptyVersion(agentDefinition: agentDefinition)))!;
|
||||
return ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithEmptyVersion(agentDefinition: agentDefinition)))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AgentVersion with empty version for testing hosted MCP agents.
|
||||
/// Creates a test ProjectsAgentVersion with empty version for testing hosted MCP agents.
|
||||
/// </summary>
|
||||
private AgentVersion CreateTestAgentVersionWithEmptyVersion()
|
||||
private ProjectsAgentVersion CreateTestAgentVersionWithEmptyVersion()
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion()))!;
|
||||
return ModelReaderWriter.Read<ProjectsAgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion()))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AIProjectClient with whitespace-only version fields for testing hosted MCP agents.
|
||||
/// </summary>
|
||||
private FakeAgentClient CreateTestAgentClientWithWhitespaceVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
||||
private FakeAgentClient CreateTestAgentClientWithWhitespaceVersion(string? agentName = null, string? instructions = null, string? description = null, ProjectsAgentDefinition? agentDefinitionResponse = null)
|
||||
{
|
||||
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, versionMode: VersionMode.Whitespace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AgentRecord with whitespace-only version for testing hosted MCP agents.
|
||||
/// Creates a test ProjectsAgentRecord with whitespace-only version for testing hosted MCP agents.
|
||||
/// </summary>
|
||||
private AgentRecord CreateTestAgentRecordWithWhitespaceVersion(AgentDefinition? agentDefinition = null)
|
||||
private ProjectsAgentRecord CreateTestAgentRecordWithWhitespaceVersion(ProjectsAgentDefinition? agentDefinition = null)
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithWhitespaceVersion(agentDefinition: agentDefinition)))!;
|
||||
return ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithWhitespaceVersion(agentDefinition: agentDefinition)))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AgentVersion with whitespace-only version for testing hosted MCP agents.
|
||||
/// Creates a test ProjectsAgentVersion with whitespace-only version for testing hosted MCP agents.
|
||||
/// </summary>
|
||||
private AgentVersion CreateTestAgentVersionWithWhitespaceVersion()
|
||||
private ProjectsAgentVersion CreateTestAgentVersionWithWhitespaceVersion()
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithWhitespaceVersion()))!;
|
||||
return ModelReaderWriter.Read<ProjectsAgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithWhitespaceVersion()))!;
|
||||
}
|
||||
|
||||
private const string OpenAPISpec = """
|
||||
@@ -1525,11 +1525,11 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test AgentVersion for testing.
|
||||
/// Creates a test ProjectsAgentVersion for testing.
|
||||
/// </summary>
|
||||
private AgentVersion CreateTestAgentVersion()
|
||||
private ProjectsAgentVersion CreateTestAgentVersion()
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson()))!;
|
||||
return ModelReaderWriter.Read<ProjectsAgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson()))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1547,11 +1547,11 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
/// </summary>
|
||||
private sealed class FakeAgentClient : AIProjectClient
|
||||
{
|
||||
public FakeAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, bool useEmptyVersion = false, VersionMode versionMode = VersionMode.Normal)
|
||||
public FakeAgentClient(string? agentName = null, string? instructions = null, string? description = null, ProjectsAgentDefinition? agentDefinitionResponse = null, bool useEmptyVersion = false, VersionMode versionMode = VersionMode.Normal)
|
||||
{
|
||||
// Handle backward compatibility with bool parameter
|
||||
var effectiveVersionMode = useEmptyVersion ? VersionMode.Empty : versionMode;
|
||||
this.Agents = new FakeAgentsClient(agentName, instructions, description, agentDefinitionResponse, effectiveVersionMode);
|
||||
this.AgentAdministrationClient = new FakeAgentsClient(agentName, instructions, description, agentDefinitionResponse, effectiveVersionMode);
|
||||
}
|
||||
|
||||
public override ClientConnection GetConnection(string connectionId)
|
||||
@@ -1559,17 +1559,17 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
return new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None);
|
||||
}
|
||||
|
||||
public override AgentsClient Agents { get; }
|
||||
public override AgentAdministrationClient AgentAdministrationClient { get; }
|
||||
|
||||
private sealed class FakeAgentsClient : AgentsClient
|
||||
private sealed class FakeAgentsClient : AgentAdministrationClient
|
||||
{
|
||||
private readonly string? _agentName;
|
||||
private readonly string? _instructions;
|
||||
private readonly string? _description;
|
||||
private readonly AgentDefinition? _agentDefinition;
|
||||
private readonly ProjectsAgentDefinition? _agentDefinition;
|
||||
private readonly VersionMode _versionMode;
|
||||
|
||||
public FakeAgentsClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, VersionMode versionMode = VersionMode.Normal)
|
||||
public FakeAgentsClient(string? agentName = null, string? instructions = null, string? description = null, ProjectsAgentDefinition? agentDefinitionResponse = null, VersionMode versionMode = VersionMode.Normal)
|
||||
{
|
||||
this._agentName = agentName;
|
||||
this._instructions = instructions;
|
||||
@@ -1601,44 +1601,44 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
public override ClientResult GetAgent(string agentName, RequestOptions options)
|
||||
{
|
||||
var responseJson = this.GetAgentResponseJson();
|
||||
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
|
||||
return ClientResult.FromValue(ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
|
||||
}
|
||||
|
||||
public override ClientResult<AgentRecord> GetAgent(string agentName, CancellationToken cancellationToken = default)
|
||||
public override ClientResult<ProjectsAgentRecord> GetAgent(string agentName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var responseJson = this.GetAgentResponseJson();
|
||||
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
||||
return ClientResult.FromValue(ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
||||
}
|
||||
|
||||
public override Task<ClientResult> GetAgentAsync(string agentName, RequestOptions options)
|
||||
{
|
||||
var responseJson = this.GetAgentResponseJson();
|
||||
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
|
||||
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
|
||||
}
|
||||
|
||||
public override Task<ClientResult<AgentRecord>> GetAgentAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
public override Task<ClientResult<ProjectsAgentRecord>> GetAgentAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var responseJson = this.GetAgentResponseJson();
|
||||
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
||||
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<ProjectsAgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
||||
}
|
||||
|
||||
public override ClientResult<AgentVersion> CreateAgentVersion(string agentName, AgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
|
||||
public override ClientResult<ProjectsAgentVersion> CreateAgentVersion(string agentName, ProjectsAgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var responseJson = this.GetAgentVersionResponseJson();
|
||||
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
||||
return ClientResult.FromValue(ModelReaderWriter.Read<ProjectsAgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
||||
}
|
||||
|
||||
public override Task<ClientResult<AgentVersion>> CreateAgentVersionAsync(string agentName, AgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
|
||||
public override Task<ClientResult<ProjectsAgentVersion>> CreateAgentVersionAsync(string agentName, ProjectsAgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var responseJson = this.GetAgentVersionResponseJson();
|
||||
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
||||
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<ProjectsAgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition GeneratePromptDefinitionResponse(PromptAgentDefinition inputDefinition, List<AITool>? tools)
|
||||
private static DeclarativeAgentDefinition GeneratePromptDefinitionResponse(DeclarativeAgentDefinition inputDefinition, List<AITool>? tools)
|
||||
{
|
||||
var definitionResponse = new PromptAgentDefinition(inputDefinition.Model) { Instructions = inputDefinition.Instructions };
|
||||
var definitionResponse = new DeclarativeAgentDefinition(inputDefinition.Model) { Instructions = inputDefinition.Instructions };
|
||||
if (tools is not null)
|
||||
{
|
||||
foreach (var tool in tools)
|
||||
|
||||
@@ -29,7 +29,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the agent response JSON with optional placeholder replacements applied.
|
||||
/// </summary>
|
||||
public static string GetAgentResponseJson(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetAgentResponseJson(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_agentResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -42,7 +42,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the agent version response JSON with optional placeholder replacements applied.
|
||||
/// </summary>
|
||||
public static string GetAgentVersionResponseJson(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetAgentVersionResponseJson(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_agentVersionResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -55,7 +55,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the agent version response JSON with empty version and ID fields for testing hosted agents like MCP agents.
|
||||
/// </summary>
|
||||
public static string GetAgentVersionResponseJsonWithEmptyVersion(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetAgentVersionResponseJsonWithEmptyVersion(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_agentVersionResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -71,7 +71,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the agent response JSON with empty version and ID fields in the latest version for testing hosted agents like MCP agents.
|
||||
/// </summary>
|
||||
public static string GetAgentResponseJsonWithEmptyVersion(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetAgentResponseJsonWithEmptyVersion(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_agentResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -87,7 +87,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the agent version response JSON with whitespace-only version and ID fields for testing hosted agents like MCP agents.
|
||||
/// </summary>
|
||||
public static string GetAgentVersionResponseJsonWithWhitespaceVersion(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetAgentVersionResponseJsonWithWhitespaceVersion(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_agentVersionResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -103,7 +103,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the agent response JSON with whitespace-only version and ID fields in the latest version for testing hosted agents like MCP agents.
|
||||
/// </summary>
|
||||
public static string GetAgentResponseJsonWithWhitespaceVersion(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetAgentResponseJsonWithWhitespaceVersion(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_agentResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -119,7 +119,7 @@ internal static class TestDataUtil
|
||||
/// <summary>
|
||||
/// Gets the OpenAI default response JSON with optional placeholder replacements applied.
|
||||
/// </summary>
|
||||
public static string GetOpenAIDefaultResponseJson(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
public static string GetOpenAIDefaultResponseJson(string? agentName = null, ProjectsAgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
|
||||
{
|
||||
var json = s_openAIDefaultResponseJson;
|
||||
json = ApplyAgentName(json, agentName);
|
||||
@@ -138,7 +138,7 @@ internal static class TestDataUtil
|
||||
return json;
|
||||
}
|
||||
|
||||
private static string ApplyAgentDefinition(string json, AgentDefinition? definition)
|
||||
private static string ApplyAgentDefinition(string json, ProjectsAgentDefinition? definition)
|
||||
{
|
||||
return (definition is not null)
|
||||
? json.Replace(AgentDefinitionPlaceholder, ModelReaderWriter.Write(definition).ToString())
|
||||
|
||||
-1013
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,382 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="AgentClassSkill"/> and <see cref="AgentInMemorySkillsSource"/>.
|
||||
/// </summary>
|
||||
public sealed class AgentClassSkillTests
|
||||
{
|
||||
[Fact]
|
||||
public void Resources_DefaultsToNull_WhenNotOverridden()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new MinimalClassSkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Null(skill.Resources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scripts_DefaultsToNull_WhenNotOverridden()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new MinimalClassSkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Null(skill.Scripts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resources_ReturnsOverriddenList_WhenOverridden()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new FullClassSkill();
|
||||
|
||||
// Act
|
||||
var resources = skill.Resources;
|
||||
|
||||
// Assert
|
||||
Assert.Single(resources!);
|
||||
Assert.Equal("test-resource", resources![0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scripts_ReturnsOverriddenList_WhenOverridden()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new FullClassSkill();
|
||||
|
||||
// Act
|
||||
var scripts = skill.Scripts;
|
||||
|
||||
// Assert
|
||||
Assert.Single(scripts!);
|
||||
Assert.Equal("TestScript", scripts![0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResourcesAndScripts_CanBeLazyLoaded_AndCached()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new LazyLoadedSkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(0, skill.ResourceCreationCount);
|
||||
Assert.Equal(0, skill.ScriptCreationCount);
|
||||
|
||||
var firstResources = skill.Resources;
|
||||
var firstScripts = skill.Scripts;
|
||||
var secondResources = skill.Resources;
|
||||
var secondScripts = skill.Scripts;
|
||||
|
||||
Assert.Single(firstResources!);
|
||||
Assert.Single(firstScripts!);
|
||||
Assert.Same(firstResources, secondResources);
|
||||
Assert.Same(firstScripts, secondScripts);
|
||||
Assert.Equal(1, skill.ResourceCreationCount);
|
||||
Assert.Equal(1, skill.ScriptCreationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Name_Content_ReturnClassDefinedValues()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new MinimalClassSkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("minimal", skill.Frontmatter.Name);
|
||||
Assert.Contains("<instructions>", skill.Content);
|
||||
Assert.Contains("Minimal skill body.", skill.Content);
|
||||
Assert.Contains("</instructions>", skill.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_ReturnsSynthesizedXmlDocument()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new MinimalClassSkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Contains("<name>minimal</name>", skill.Content);
|
||||
Assert.Contains("<description>A minimal skill.</description>", skill.Content);
|
||||
Assert.Contains("<instructions>", skill.Content);
|
||||
Assert.Contains("Minimal skill body.", skill.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AgentInMemorySkillsSource_ReturnsAllSkillsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var skills = new AgentClassSkill[] { new MinimalClassSkill(), new FullClassSkill() };
|
||||
var source = new AgentInMemorySkillsSource(skills);
|
||||
|
||||
// Act
|
||||
var result = await source.GetSkillsAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("minimal", result[0].Frontmatter.Name);
|
||||
Assert.Equal("full", result[1].Frontmatter.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentClassSkill_InvalidFrontmatter_ThrowsArgumentException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => new AgentSkillFrontmatter("INVALID-NAME", "An invalid skill."));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkillWithOnlyResources_HasNullScripts()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new ResourceOnlySkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Single(skill.Resources!);
|
||||
Assert.Null(skill.Scripts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkillWithOnlyScripts_HasNullResources()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new ScriptOnlySkill();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Null(skill.Resources);
|
||||
Assert.Single(skill.Scripts!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_ReturnsCachedInstance_OnRepeatedAccess()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new FullClassSkill();
|
||||
|
||||
// Act
|
||||
var first = skill.Content;
|
||||
var second = skill.Content;
|
||||
|
||||
// Assert
|
||||
Assert.Same(first, second);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_IncludesParametersSchema_WhenScriptsHaveParameters()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new FullClassSkill();
|
||||
|
||||
// Act
|
||||
var content = skill.Content;
|
||||
|
||||
// Assert — scripts with typed parameters should have their schema included
|
||||
Assert.Contains("parameters_schema", content);
|
||||
Assert.Contains("value", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_IncludesDerivedResources_WhenResourcesUseBaseTypeOverrides()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new DerivedResourceSkill();
|
||||
|
||||
// Act
|
||||
var content = skill.Content;
|
||||
|
||||
// Assert
|
||||
Assert.Contains("<resources>", content);
|
||||
Assert.Contains("custom-resource", content);
|
||||
Assert.Contains("Custom resource description.", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_IncludesDerivedScripts_WhenScriptsUseBaseTypeOverrides()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new DerivedScriptSkill();
|
||||
|
||||
// Act
|
||||
var content = skill.Content;
|
||||
|
||||
// Assert
|
||||
Assert.Contains("<scripts>", content);
|
||||
Assert.Contains("custom-script", content);
|
||||
Assert.Contains("Custom script description.", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_OmitsParametersSchema_WhenDerivedScriptDoesNotProvideOne()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new DerivedScriptSkill();
|
||||
|
||||
// Act
|
||||
var content = skill.Content;
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("parameters_schema", content);
|
||||
}
|
||||
|
||||
#region Test skill classes
|
||||
|
||||
private sealed class MinimalClassSkill : AgentClassSkill
|
||||
{
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("minimal", "A minimal skill.");
|
||||
|
||||
protected override string Instructions => "Minimal skill body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => null;
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => null;
|
||||
}
|
||||
|
||||
private sealed class FullClassSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("full", "A full skill with resources and scripts.");
|
||||
|
||||
protected override string Instructions => "Full skill body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
[
|
||||
CreateResource("test-resource", "resource content"),
|
||||
];
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
[
|
||||
CreateScript("TestScript", TestScript),
|
||||
];
|
||||
|
||||
private static string TestScript(double value) =>
|
||||
JsonSerializer.Serialize(new { result = value * 2 });
|
||||
}
|
||||
|
||||
private sealed class ResourceOnlySkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("resource-only", "Skill with resources only.");
|
||||
|
||||
protected override string Instructions => "Body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
[
|
||||
CreateResource("data", "some data"),
|
||||
];
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => null;
|
||||
}
|
||||
|
||||
private sealed class ScriptOnlySkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("script-only", "Skill with scripts only.");
|
||||
|
||||
protected override string Instructions => "Body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => null;
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
[
|
||||
CreateScript("ToUpper", (string input) => input.ToUpperInvariant()),
|
||||
];
|
||||
}
|
||||
|
||||
private sealed class DerivedResourceSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("derived-resource", "Skill with a derived resource type.");
|
||||
|
||||
protected override string Instructions => "Body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
|
||||
[
|
||||
new CustomResource("custom-resource", "Custom resource description."),
|
||||
];
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => null;
|
||||
}
|
||||
|
||||
private sealed class DerivedScriptSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("derived-script", "Skill with a derived script type.");
|
||||
|
||||
protected override string Instructions => "Body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => null;
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
|
||||
[
|
||||
new CustomScript("custom-script", "Custom script description."),
|
||||
];
|
||||
}
|
||||
|
||||
private sealed class LazyLoadedSkill : AgentClassSkill
|
||||
{
|
||||
private IReadOnlyList<AgentSkillResource>? _resources;
|
||||
private IReadOnlyList<AgentSkillScript>? _scripts;
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("lazy-loaded", "Skill with lazily created resources and scripts.");
|
||||
|
||||
protected override string Instructions => "Body.";
|
||||
|
||||
public int ResourceCreationCount { get; private set; }
|
||||
|
||||
public int ScriptCreationCount { get; private set; }
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??= this.CreateResources();
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??= this.CreateScripts();
|
||||
|
||||
private IReadOnlyList<AgentSkillResource> CreateResources()
|
||||
{
|
||||
this.ResourceCreationCount++;
|
||||
return [CreateResource("lazy-resource", "resource content")];
|
||||
}
|
||||
|
||||
private IReadOnlyList<AgentSkillScript> CreateScripts()
|
||||
{
|
||||
this.ScriptCreationCount++;
|
||||
return [CreateScript("LazyScript", () => "done")];
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CustomResource : AgentSkillResource
|
||||
{
|
||||
public CustomResource(string name, string? description = null)
|
||||
: base(name, description)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<object?>("resource-value");
|
||||
}
|
||||
|
||||
private sealed class CustomScript : AgentSkillScript
|
||||
{
|
||||
public CustomScript(string name, string? description = null)
|
||||
: base(name, description)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<object?> RunAsync(AgentSkill skill, Extensions.AI.AIFunctionArguments arguments, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<object?>("script-result");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -851,6 +851,61 @@ public sealed class AgentSkillsProviderTests : IDisposable
|
||||
Assert.Contains("First instructions.", content!.ToString()!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Constructor_ClassSkillsParams_ProvidesSkillsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new TestClassSkill("class-a", "Class A", "Class instructions.");
|
||||
var provider = new AgentSkillsProvider(skill);
|
||||
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
|
||||
|
||||
// Act
|
||||
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.Instructions);
|
||||
Assert.Contains("class-a", result.Instructions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Constructor_ClassSkillsEnumerable_ProvidesSkillsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var skills = new List<AgentClassSkill>
|
||||
{
|
||||
new TestClassSkill("enum-class-a", "Class A", "Instructions A."),
|
||||
new TestClassSkill("enum-class-b", "Class B", "Instructions B."),
|
||||
};
|
||||
var provider = new AgentSkillsProvider(skills);
|
||||
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
|
||||
|
||||
// Act
|
||||
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.Instructions);
|
||||
Assert.Contains("enum-class-a", result.Instructions);
|
||||
Assert.Contains("enum-class-b", result.Instructions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Constructor_ClassSkills_DeduplicatesAsync()
|
||||
{
|
||||
// Arrange — two class skills with the same name
|
||||
var skill1 = new TestClassSkill("dup-class", "First", "First instructions.");
|
||||
var skill2 = new TestClassSkill("dup-class", "Second", "Second instructions.");
|
||||
var provider = new AgentSkillsProvider(skill1, skill2);
|
||||
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
|
||||
|
||||
// Act
|
||||
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
|
||||
var loadSkillTool = result.Tools!.First(t => t.Name == "load_skill") as AIFunction;
|
||||
var content = await loadSkillTool!.InvokeAsync(new AIFunctionArguments(new Dictionary<string, object?> { ["skillName"] = "dup-class" }));
|
||||
|
||||
// Assert — only first occurrence survives
|
||||
Assert.Contains("First instructions.", content!.ToString()!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A test skill source that counts how many times <see cref="GetSkillsAsync"/> is called.
|
||||
/// </summary>
|
||||
@@ -872,4 +927,23 @@ public sealed class AgentSkillsProviderTests : IDisposable
|
||||
return Task.FromResult(this._skills);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestClassSkill : AgentClassSkill
|
||||
{
|
||||
private readonly string _instructions;
|
||||
|
||||
public TestClassSkill(string name, string description, string instructions)
|
||||
{
|
||||
this.Frontmatter = new AgentSkillFrontmatter(name, description);
|
||||
this._instructions = instructions;
|
||||
}
|
||||
|
||||
public override AgentSkillFrontmatter Frontmatter { get; }
|
||||
|
||||
protected override string Instructions => this._instructions;
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources => null;
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts => null;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -35,13 +35,13 @@ internal abstract class AgentProvider(IConfiguration configuration)
|
||||
{
|
||||
Uri foundryEndpoint = new(this.GetSetting(TestSettings.AzureAIProjectEndpoint));
|
||||
|
||||
await foreach (AgentVersion agent in this.CreateAgentsAsync(foundryEndpoint))
|
||||
await foreach (ProjectsAgentVersion agent in this.CreateAgentsAsync(foundryEndpoint))
|
||||
{
|
||||
Console.WriteLine($"Created agent: {agent.Name}:{agent.Version}");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint);
|
||||
protected abstract IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint);
|
||||
|
||||
protected string GetSetting(string settingName) =>
|
||||
configuration[settingName] ??
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class FunctionToolAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
protected override async IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
MenuPlugin menuPlugin = new();
|
||||
AIFunction[] functions =
|
||||
@@ -33,9 +33,9 @@ internal sealed class FunctionToolAgentProvider(IConfiguration configuration) :
|
||||
agentDescription: "Provides information about the restaurant menu");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefineMenuAgent(AIFunction[] functions)
|
||||
private DeclarativeAgentDefinition DefineMenuAgent(AIFunction[] functions)
|
||||
{
|
||||
PromptAgentDefinition agentDefinition =
|
||||
DeclarativeAgentDefinition agentDefinition =
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class MarketingAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
protected override async IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed class MarketingAgentProvider(IConfiguration configuration) : Age
|
||||
agentDescription: "Editor agent for Marketing workflow");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefineAnalystAgent() =>
|
||||
private DeclarativeAgentDefinition DefineAnalystAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
@@ -47,13 +47,13 @@ internal sealed class MarketingAgentProvider(IConfiguration configuration) : Age
|
||||
""",
|
||||
Tools =
|
||||
{
|
||||
//AgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
//ProjectsAgentTool.CreateBingGroundingTool( // TODO: Use Bing Grounding when available
|
||||
// new BingGroundingSearchToolParameters(
|
||||
// [new BingGroundingSearchConfiguration(this.GetSetting(Settings.FoundryGroundingTool))]))
|
||||
}
|
||||
};
|
||||
|
||||
private PromptAgentDefinition DefineWriterAgent() =>
|
||||
private DeclarativeAgentDefinition DefineWriterAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
@@ -64,7 +64,7 @@ internal sealed class MarketingAgentProvider(IConfiguration configuration) : Age
|
||||
"""
|
||||
};
|
||||
|
||||
private PromptAgentDefinition DefineEditorAgent() =>
|
||||
private DeclarativeAgentDefinition DefineEditorAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class MathChatAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
protected override async IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
|
||||
@@ -29,7 +29,7 @@ internal sealed class MathChatAgentProvider(IConfiguration configuration) : Agen
|
||||
agentDescription: "Teacher agent for MathChat workflow");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefineStudentAgent() =>
|
||||
private DeclarativeAgentDefinition DefineStudentAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
@@ -41,7 +41,7 @@ internal sealed class MathChatAgentProvider(IConfiguration configuration) : Agen
|
||||
"""
|
||||
};
|
||||
|
||||
private PromptAgentDefinition DefineTeacherAgent() =>
|
||||
private DeclarativeAgentDefinition DefineTeacherAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class PoemAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
protected override async IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
|
||||
@@ -23,7 +23,7 @@ internal sealed class PoemAgentProvider(IConfiguration configuration) : AgentPro
|
||||
agentDescription: "Authors original poems");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefinePoemAgent() =>
|
||||
private DeclarativeAgentDefinition DefinePoemAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class TestAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
protected override async IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
|
||||
@@ -23,6 +23,6 @@ internal sealed class TestAgentProvider(IConfiguration configuration) : AgentPro
|
||||
agentDescription: "Basic agent");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefineMenuAgent() =>
|
||||
private DeclarativeAgentDefinition DefineMenuAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName));
|
||||
}
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class VisionAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
protected override async IAsyncEnumerable<ProjectsAgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
|
||||
|
||||
@@ -23,7 +23,7 @@ internal sealed class VisionAgentProvider(IConfiguration configuration) : AgentP
|
||||
agentDescription: "Use computer vision to describe an image or document.");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefineVisionAgent() =>
|
||||
private DeclarativeAgentDefinition DefineVisionAgent() =>
|
||||
new(this.GetSetting(TestSettings.AzureAIModelDeploymentName))
|
||||
{
|
||||
Instructions =
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user