diff --git a/.github/workflows/python-check-coverage.py b/.github/workflows/python-check-coverage.py
index af6d38ffea..e40c497c86 100644
--- a/.github/workflows/python-check-coverage.py
+++ b/.github/workflows/python-check-coverage.py
@@ -34,14 +34,15 @@ from dataclasses import dataclass
# (e.g., "packages/core/agent_framework/observability.py")
# =============================================================================
ENFORCED_TARGETS: set[str] = {
- # Packages
+ # Packages (sorted alphabetically)
+ "packages.anthropic.agent_framework_anthropic",
+ "packages.azure-ai-search.agent_framework_azure_ai_search",
"packages.azure-ai.agent_framework_azure_ai",
"packages.core.agent_framework",
"packages.core.agent_framework._workflows",
- "packages.purview.agent_framework_purview",
- "packages.anthropic.agent_framework_anthropic",
- "packages.azure-ai-search.agent_framework_azure_ai_search",
+ "packages.foundry.agent_framework_foundry",
"packages.openai.agent_framework_openai",
+ "packages.purview.agent_framework_purview",
# Individual files (if you want to enforce specific files instead of whole packages)
"packages/core/agent_framework/observability.py",
# Add more targets here as coverage improves
diff --git a/.github/workflows/python-integration-tests.yml b/.github/workflows/python-integration-tests.yml
index 71f4267e41..06720cc7dd 100644
--- a/.github/workflows/python-integration-tests.yml
+++ b/.github/workflows/python-integration-tests.yml
@@ -95,10 +95,10 @@ jobs:
environment: integration
timeout-minutes: 60
env:
- AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
- AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }}
+ AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
+ AZURE_OPENAI_RESPONSES_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_EMBEDDING_MODEL: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
defaults:
run:
@@ -139,7 +139,7 @@ jobs:
timeout-minutes: 60
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- ANTHROPIC_CHAT_MODEL_ID: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
+ ANTHROPIC_CHAT_MODEL: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
defaults:
run:
@@ -207,8 +207,8 @@ jobs:
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FUNCTIONS_WORKER_RUNTIME: "python"
@@ -256,7 +256,7 @@ jobs:
timeout-minutes: 60
env:
AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
- AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
+ AZURE_AI_MODEL: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_AGENT_NAME }}
diff --git a/.github/workflows/python-merge-tests.yml b/.github/workflows/python-merge-tests.yml
index 8c8abec84a..12b602d0eb 100644
--- a/.github/workflows/python-merge-tests.yml
+++ b/.github/workflows/python-merge-tests.yml
@@ -192,10 +192,10 @@ jobs:
runs-on: ubuntu-latest
environment: integration
env:
- AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
- AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }}
+ AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
+ AZURE_OPENAI_RESPONSES_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_EMBEDDING_MODEL: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
defaults:
run:
@@ -255,7 +255,7 @@ jobs:
environment: integration
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- ANTHROPIC_CHAT_MODEL_ID: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
+ ANTHROPIC_CHAT_MODEL: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
defaults:
run:
@@ -336,8 +336,8 @@ jobs:
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FUNCTIONS_WORKER_RUNTIME: "python"
@@ -398,7 +398,7 @@ jobs:
environment: integration
env:
AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
- AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
+ AZURE_AI_MODEL: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_AGENT_NAME }}
diff --git a/.github/workflows/python-sample-validation.yml b/.github/workflows/python-sample-validation.yml
index bbb3195d1a..57746935ee 100644
--- a/.github/workflows/python-sample-validation.yml
+++ b/.github/workflows/python-sample-validation.yml
@@ -65,12 +65,13 @@ jobs:
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
- AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME || vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_RESPONSES_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_EMBEDDING_MODEL: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME || vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
# OpenAI configuration
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
- OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
- OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
+ OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
+ OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
# GitHub MCP
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
@@ -95,11 +96,12 @@ jobs:
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
- echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
- echo "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=$AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME" >> .env
+ echo "AZURE_OPENAI_MODEL=$AZURE_OPENAI_MODEL" >> .env
+ echo "AZURE_OPENAI_RESPONSES_MODEL=$AZURE_OPENAI_RESPONSES_MODEL" >> .env
+ echo "AZURE_OPENAI_EMBEDDING_MODEL=$AZURE_OPENAI_EMBEDDING_MODEL" >> .env
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
- echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
- echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
+ echo "OPENAI_CHAT_MODEL=$OPENAI_CHAT_MODEL" >> .env
+ echo "OPENAI_RESPONSES_MODEL=$OPENAI_RESPONSES_MODEL" >> .env
echo "GITHUB_PAT=$GITHUB_PAT" >> .env
- name: Run sample validation
@@ -120,8 +122,8 @@ jobs:
env:
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
OPENAI_MODEL: ${{ vars.OPENAI__CHATMODELID }}
- OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
- OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
+ OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
+ OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
defaults:
run:
working-directory: python
@@ -140,8 +142,8 @@ jobs:
run: |
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
echo "OPENAI_MODEL=$OPENAI_MODEL" >> .env
- echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
- echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
+ echo "OPENAI_CHAT_MODEL=$OPENAI_CHAT_MODEL" >> .env
+ echo "OPENAI_RESPONSES_MODEL=$OPENAI_RESPONSES_MODEL" >> .env
- name: Run sample validation
run: |
@@ -160,7 +162,7 @@ jobs:
environment: integration
env:
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION || '' }}
defaults:
run:
@@ -179,7 +181,7 @@ jobs:
- name: Create .env for samples
run: |
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
- echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
+ echo "AZURE_OPENAI_MODEL=$AZURE_OPENAI_MODEL" >> .env
echo "AZURE_OPENAI_API_VERSION=$AZURE_OPENAI_API_VERSION" >> .env
- name: Run sample validation
@@ -199,7 +201,7 @@ jobs:
environment: integration
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- ANTHROPIC_CHAT_MODEL_ID: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
+ ANTHROPIC_CHAT_MODEL: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
defaults:
run:
working-directory: python
@@ -217,7 +219,7 @@ jobs:
- name: Create .env for samples
run: |
echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> .env
- echo "ANTHROPIC_CHAT_MODEL_ID=$ANTHROPIC_CHAT_MODEL_ID" >> .env
+ echo "ANTHROPIC_CHAT_MODEL=$ANTHROPIC_CHAT_MODEL" >> .env
- name: Run sample validation
run: |
@@ -265,7 +267,7 @@ jobs:
runs-on: ubuntu-latest
environment: integration
env:
- BEDROCK_CHAT_MODEL_ID: ${{ vars.BEDROCK__CHATMODELID }}
+ BEDROCK_CHAT_MODEL: ${{ vars.BEDROCK__CHATMODELID }}
defaults:
run:
working-directory: python
@@ -518,7 +520,7 @@ jobs:
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# Azure AI Search (for evaluation samples)
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
AZURE_SEARCH_API_KEY: ${{ secrets.AZURE_SEARCH_API_KEY }}
@@ -560,11 +562,11 @@ jobs:
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# OpenAI configuration
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
- OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
- OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
+ OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
+ OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
defaults:
run:
@@ -585,10 +587,10 @@ jobs:
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
- echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
+ echo "AZURE_OPENAI_MODEL=$AZURE_OPENAI_MODEL" >> .env
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
- echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
- echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
+ echo "OPENAI_CHAT_MODEL=$OPENAI_CHAT_MODEL" >> .env
+ echo "OPENAI_RESPONSES_MODEL=$OPENAI_RESPONSES_MODEL" >> .env
- name: Run sample validation
run: |
@@ -610,11 +612,11 @@ jobs:
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
- AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
+ AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# OpenAI configuration
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
- OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
- OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
+ OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
+ OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
# Copilot Studio
COPILOTSTUDIOAGENT__ENVIRONMENTID: ${{ secrets.COPILOTSTUDIOAGENT__ENVIRONMENTID }}
@@ -640,10 +642,10 @@ jobs:
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
- echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
+ echo "AZURE_OPENAI_MODEL=$AZURE_OPENAI_MODEL" >> .env
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
- echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
- echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
+ echo "OPENAI_CHAT_MODEL=$OPENAI_CHAT_MODEL" >> .env
+ echo "OPENAI_RESPONSES_MODEL=$OPENAI_RESPONSES_MODEL" >> .env
echo "COPILOTSTUDIOAGENT__ENVIRONMENTID=$COPILOTSTUDIOAGENT__ENVIRONMENTID" >> .env
echo "COPILOTSTUDIOAGENT__SCHEMANAME=$COPILOTSTUDIOAGENT__SCHEMANAME" >> .env
echo "COPILOTSTUDIOAGENT__TENANTID=$COPILOTSTUDIOAGENT__TENANTID" >> .env
diff --git a/README.md b/README.md
index c5cac3ce4b..759f899e20 100644
--- a/README.md
+++ b/README.md
@@ -94,23 +94,23 @@ Create a simple Azure Responses Agent that writes a haiku about the Microsoft Ag
# Use `az login` to authenticate with Azure CLI
import os
import asyncio
-from agent_framework.azure import AzureOpenAIResponsesClient
+from agent_framework import Agent
+from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
async def main():
- # Initialize a chat agent with Azure OpenAI Responses
+ # Initialize a chat agent with Microsoft Foundry
# the endpoint, deployment name, and api version can be set via environment variables
- # or they can be passed in directly to the AzureOpenAIResponsesClient constructor
- agent = AzureOpenAIResponsesClient(
- # endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
- # deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
- # api_version=os.environ["AZURE_OPENAI_API_VERSION"],
- # api_key=os.environ["AZURE_OPENAI_API_KEY"], # Optional if using AzureCliCredential
- credential=AzureCliCredential(), # Optional, if using api_key
- ).as_agent(
- name="HaikuBot",
- instructions="You are an upbeat assistant that writes beautifully.",
+ # or they can be passed in directly to the FoundryChatClient constructor
+ agent = Agent(
+ client=FoundryChatClient(
+ credential=AzureCliCredential(),
+ # project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
+ # model=os.environ["FOUNDRY_MODEL_DEPLOYMENT_NAME"],
+ ),
+ name="HaikuBot",
+ instructions="You are an upbeat assistant that writes beautifully.",
)
print(await agent.run("Write a haiku about Microsoft Agent Framework."))
diff --git a/declarative-agents/agent-samples/foundry/MicrosoftLearnAgent.yaml b/declarative-agents/agent-samples/foundry/MicrosoftLearnAgent.yaml
index 8e15340351..af20bbf18b 100644
--- a/declarative-agents/agent-samples/foundry/MicrosoftLearnAgent.yaml
+++ b/declarative-agents/agent-samples/foundry/MicrosoftLearnAgent.yaml
@@ -3,13 +3,13 @@ name: MicrosoftLearnAgent
description: Microsoft Learn Agent
instructions: You answer questions by searching the Microsoft Learn content only.
model:
- id: =Env.AZURE_FOUNDRY_PROJECT_MODEL_ID
+ id: =Env.FOUNDRY_MODEL
options:
temperature: 0.9
topP: 0.95
connection:
kind: remote
- endpoint: =Env.AZURE_FOUNDRY_PROJECT_ENDPOINT
+ endpoint: =Env.FOUNDRY_PROJECT_ENDPOINT
tools:
- kind: mcp
name: microsoft_learn
diff --git a/dotnet/.github/skills/project-structure/SKILL.md b/dotnet/.github/skills/project-structure/SKILL.md
index 01dcafabf8..6ec9476039 100644
--- a/dotnet/.github/skills/project-structure/SKILL.md
+++ b/dotnet/.github/skills/project-structure/SKILL.md
@@ -12,8 +12,8 @@ dotnet/
│ ├── Microsoft.Agents.AI.Abstractions/ # Core AI agent abstractions
│ ├── Microsoft.Agents.AI.A2A/ # Agent-to-Agent (A2A) provider
│ ├── Microsoft.Agents.AI.OpenAI/ # OpenAI provider
-│ ├── Microsoft.Agents.AI.AzureAI/ # Azure AI Foundry Agents (v2) provider
-│ ├── Microsoft.Agents.AI.AzureAI.Persistent/ # Legacy Azure AI Foundry Agents (v1) provider
+│ ├── Microsoft.Agents.AI.Foundry/ # Microsoft Foundry Agents (v2) provider
+│ ├── Microsoft.Agents.AI.AzureAI.Persistent/ # Legacy Microsoft Foundry Agents (v1) provider
│ ├── Microsoft.Agents.AI.Anthropic/ # Anthropic provider
│ ├── Microsoft.Agents.AI.Workflows/ # Workflow orchestration
│ └── ... # Other packages
diff --git a/dotnet/.github/skills/verify-samples-tool/SKILL.md b/dotnet/.github/skills/verify-samples-tool/SKILL.md
new file mode 100644
index 0000000000..49878467d7
--- /dev/null
+++ b/dotnet/.github/skills/verify-samples-tool/SKILL.md
@@ -0,0 +1,213 @@
+---
+name: verify-samples-tool
+description: How to use the verify-samples tool to run, verify, and manage sample definitions in the Agent Framework repository. Use this when adding, updating, or running sample verification.
+---
+
+# verify-samples Tool
+
+The `verify-samples` project (`dotnet/eng/verify-samples/`) is an automated tool that runs sample projects and verifies their output using deterministic checks and AI-powered verification.
+
+## Running verify-samples
+
+```bash
+cd dotnet
+
+# Run all samples across all categories
+dotnet run --project eng/verify-samples -- --log results.log --csv results.csv
+
+# Run a specific category
+dotnet run --project eng/verify-samples -- --category 02-agents --log results.log
+
+# Run specific samples by name
+dotnet run --project eng/verify-samples -- Agent_Step02_StructuredOutput Agent_Step09_AsFunctionTool
+
+# Control parallelism (default 8)
+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
+```
+
+### Required Environment Variables
+
+The tool itself needs:
+- `AZURE_OPENAI_ENDPOINT` — for the AI verification agent
+- `AZURE_OPENAI_DEPLOYMENT_NAME` (optional, defaults to `gpt-5-mini`)
+
+Individual samples require their own env vars (e.g., `AZURE_AI_PROJECT_ENDPOINT`). The tool automatically checks and skips samples with missing env vars.
+
+### Output Files
+
+- `--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
+
+## Sample Categories
+
+Definitions are in the `dotnet/eng/verify-samples/` directory:
+
+| Category | Config File | Registered Key |
+|----------|-------------|----------------|
+| 01-get-started | `GetStartedSamples.cs` | `01-get-started` |
+| 02-agents | `AgentsSamples.cs` | `02-agents` |
+| 03-workflows | `WorkflowSamples.cs` | `03-workflows` |
+
+Categories are registered in `VerifyOptions.cs` in the `s_sampleSets` dictionary.
+
+## SampleDefinition Properties
+
+Each sample is defined as a `SampleDefinition` in the appropriate config file. Key properties:
+
+```csharp
+new SampleDefinition
+{
+ // Required: Display name for the sample
+ Name = "Agent_Step02_StructuredOutput",
+
+ // Required: Relative path from dotnet/ to the sample project directory
+ ProjectPath = "samples/02-agents/Agents/Agent_Step02_StructuredOutput",
+
+ // Environment variables the sample requires (throws if missing)
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+
+ // Environment variables with defaults that would prompt on console if unset
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+
+ // Skip this sample with a reason (for structural issues only)
+ SkipReason = null, // or "Requires external service X."
+
+ // Deterministic checks: substrings that must appear in stdout
+ MustContain = ["=== Section Header ==="],
+
+ // Substrings that must NOT appear in stdout
+ MustNotContain = [],
+
+ // If true, only MustContain checks are used (no AI verification)
+ IsDeterministic = false,
+
+ // AI verification: natural-language descriptions of expected output
+ // Each entry describes one aspect to verify independently
+ ExpectedOutputDescription =
+ [
+ "The output should show structured person information with Name, Age, and Occupation fields.",
+ "The output should not contain error messages or stack traces.",
+ ],
+
+ // Stdin inputs to feed to the sample (for interactive samples)
+ Inputs = ["Y", "Y", "Y"],
+
+ // Delay between stdin inputs in ms (default 2000, increase for LLM calls between inputs)
+ InputDelayMs = 3000,
+}
+```
+
+## How to Add a New Sample Definition
+
+1. **Check the sample's Program.cs** to understand:
+ - What environment variables it reads (look for `GetEnvironmentVariable`)
+ - Whether it needs stdin input (look for `Console.ReadLine`, `Application.GetInput`)
+ - Whether it has an external loop (look for `EXIT` patterns in YAML workflows)
+ - What output it produces (section headers, markers, expected behavior)
+ - Whether it exits on its own or runs as a server
+
+2. **Choose the right verification strategy:**
+ - **Deterministic** (`IsDeterministic = true`): Use `MustContain` for samples with fixed output strings. No AI verification.
+ - **AI-verified** (default): Use `ExpectedOutputDescription` with semantic descriptions. Write expectations that are flexible enough for non-deterministic LLM output.
+ - **Both**: Use `MustContain` for fixed markers AND `ExpectedOutputDescription` for LLM-generated content.
+
+3. **Set `SkipReason` only for structural issues:**
+ - Web servers that don't exit
+ - Multi-process client/server architectures
+ - Samples requiring external infrastructure (MCP servers you can't reach, Docker, etc.)
+ - Do NOT skip for missing env vars — the tool checks those dynamically.
+
+4. **For interactive samples, provide `Inputs`:**
+ - Samples using `Application.GetInput(args)` need one initial input
+ - Samples with `Console.ReadLine()` approval loops need `"Y"` inputs
+ - YAML workflows with `externalLoop` need `"EXIT"` as the last input
+ - Set `InputDelayMs` to 3000-8000ms for samples with LLM calls between inputs
+
+5. **Add the definition** to the appropriate config file (e.g., `AgentsSamples.cs`) in the `All` list.
+
+6. **Register new categories** (if needed) in `VerifyOptions.cs` `s_sampleSets` dictionary.
+
+### Writing Good ExpectedOutputDescription
+
+- Write descriptions that are **semantically flexible** — LLM output varies between runs
+- Each array entry should describe **one independent aspect** to verify
+- Always include `"The output should not contain error messages or stack traces."` as the last entry
+- Avoid exact wording expectations — use "should mention", "should contain information about", "should show"
+- Bad: `"The output should say 'The weather in Amsterdam is cloudy with a high of 15°C'"`
+- Good: `"The output should contain weather information about Amsterdam mentioning cloudy weather with a high of 15°C."`
+
+### Example: Simple LLM Sample
+
+```csharp
+new SampleDefinition
+{
+ Name = "Agent_With_AzureOpenAIChatCompletion",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+},
+```
+
+### Example: Deterministic Sample
+
+```csharp
+new SampleDefinition
+{
+ Name = "Workflow_Declarative_GenerateCode",
+ ProjectPath = "samples/03-workflows/Declarative/GenerateCode",
+ IsDeterministic = true,
+ MustContain = ["WORKFLOW: Parsing", "WORKFLOW: Defined"],
+ ExpectedOutputDescription = ["The output should show a YAML workflow being parsed and C# code being generated from it."],
+},
+```
+
+### Example: Interactive Sample with Approval Loop
+
+```csharp
+new SampleDefinition
+{
+ Name = "FoundryAgent_Hosted_MCP",
+ ProjectPath = "samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Y", "Y", "Y", "Y", "Y"],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription = ["The output should show an agent using the Microsoft Learn MCP tool with approval prompts."],
+},
+```
+
+### Example: Declarative Workflow with External Loop
+
+```csharp
+new SampleDefinition
+{
+ Name = "Workflow_Declarative_FunctionTools",
+ ProjectPath = "samples/03-workflows/Declarative/FunctionTools",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["What are today's specials?", "EXIT"],
+ InputDelayMs = 8000,
+ ExpectedOutputDescription = ["The output should show a workflow calling function tools to answer a question about restaurant specials."],
+},
+```
+
+### Example: Skipped Sample
+
+```csharp
+new SampleDefinition
+{
+ Name = "Agent_MCP_Server",
+ ProjectPath = "samples/02-agents/ModelContextProtocol/Agent_MCP_Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
+},
+```
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index a11f112194..20ea58eaeb 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -7,6 +7,7 @@
+
@@ -171,6 +172,7 @@
+
@@ -476,13 +478,13 @@
-
+
-
+
@@ -493,7 +495,7 @@
-
+
@@ -504,11 +506,11 @@
-
+
-
+
@@ -524,12 +526,12 @@
-
+
-
+
diff --git a/dotnet/agent-framework-release.slnf b/dotnet/agent-framework-release.slnf
index 1c8f477b16..98f9dcf887 100644
--- a/dotnet/agent-framework-release.slnf
+++ b/dotnet/agent-framework-release.slnf
@@ -8,13 +8,13 @@
"src\\Microsoft.Agents.AI.Anthropic\\Microsoft.Agents.AI.Anthropic.csproj",
"src\\Microsoft.Agents.AI.GitHub.Copilot\\Microsoft.Agents.AI.GitHub.Copilot.csproj",
"src\\Microsoft.Agents.AI.AzureAI.Persistent\\Microsoft.Agents.AI.AzureAI.Persistent.csproj",
- "src\\Microsoft.Agents.AI.AzureAI\\Microsoft.Agents.AI.AzureAI.csproj",
+ "src\\Microsoft.Agents.AI.Foundry\\Microsoft.Agents.AI.Foundry.csproj",
"src\\Microsoft.Agents.AI.CopilotStudio\\Microsoft.Agents.AI.CopilotStudio.csproj",
"src\\Microsoft.Agents.AI.CosmosNoSql\\Microsoft.Agents.AI.CosmosNoSql.csproj",
"src\\Microsoft.Agents.AI.Declarative\\Microsoft.Agents.AI.Declarative.csproj",
"src\\Microsoft.Agents.AI.DevUI\\Microsoft.Agents.AI.DevUI.csproj",
"src\\Microsoft.Agents.AI.DurableTask\\Microsoft.Agents.AI.DurableTask.csproj",
- "src\\Microsoft.Agents.AI.FoundryMemory\\Microsoft.Agents.AI.FoundryMemory.csproj",
+
"src\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A\\Microsoft.Agents.AI.Hosting.A2A.csproj",
"src\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj",
@@ -24,7 +24,7 @@
"src\\Microsoft.Agents.AI.Mem0\\Microsoft.Agents.AI.Mem0.csproj",
"src\\Microsoft.Agents.AI.OpenAI\\Microsoft.Agents.AI.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Purview\\Microsoft.Agents.AI.Purview.csproj",
- "src\\Microsoft.Agents.AI.Workflows.Declarative.AzureAI\\Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj",
+ "src\\Microsoft.Agents.AI.Workflows.Declarative.Foundry\\Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative\\Microsoft.Agents.AI.Workflows.Declarative.csproj",
"src\\Microsoft.Agents.AI.Workflows.Generators\\Microsoft.Agents.AI.Workflows.Generators.csproj",
"src\\Microsoft.Agents.AI.Workflows\\Microsoft.Agents.AI.Workflows.csproj",
diff --git a/dotnet/eng/verify-samples/AgentsSamples.cs b/dotnet/eng/verify-samples/AgentsSamples.cs
new file mode 100644
index 0000000000..87707a384e
--- /dev/null
+++ b/dotnet/eng/verify-samples/AgentsSamples.cs
@@ -0,0 +1,1252 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// Defines the expected behavior for each sample in 02-agents.
+///
+internal static class AgentsSamples
+{
+ public static IReadOnlyList All { get; } =
+ [
+ // ── AgentProviders ──────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_CustomImplementation",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_CustomImplementation",
+ RequiredEnvironmentVariables = [],
+ ExpectedOutputDescription =
+ [
+ "The output should contain uppercased text, because the custom agent converts all text to uppercase.",
+ "There should be two outputs — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_AzureOpenAIChatCompletion",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_AzureOpenAIResponses",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIResponses",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain two separate joke responses about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_AzureAIAgentsPersistent",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_AzureAIProject",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIProject",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ MustContain = ["Latest agent version id:"],
+ ExpectedOutputDescription =
+ [
+ "The output should show a 'Latest agent version id:' line, then joke responses from the agent.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_AzureFoundryModel",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_API_KEY", "AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ── Agents ──────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step01_UsingFunctionToolsWithApprovals",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step01_UsingFunctionToolsWithApprovals",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ Inputs = ["Tell me a joke about a pirate", ""],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription =
+ [
+ "The output should show the agent responding to user input. The response may be about any topic — jokes, weather, or tool call results are all acceptable.",
+ "The output should not contain unhandled exception stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step02_StructuredOutput",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step02_StructuredOutput",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain =
+ [
+ "=== Structured Output with ResponseFormat ===",
+ "Assistant Output (JSON):",
+ "Assistant Output (Deserialized):",
+ "=== Structured Output with RunAsync ===",
+ "=== Structured Output with RunStreamingAsync ===",
+ "=== Structured Output with UseStructuredOutput Middleware ===",
+ "Name:",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should have four clearly separated sections for different structured output approaches.",
+ "The first section should include raw JSON output and then deserialized fields including 'Name:' with a city name.",
+ "Each subsequent section should also show 'Name:' followed by a city name.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step03_PersistedConversations",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step03_PersistedConversations",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = ["--- Serialized session ---"],
+ ExpectedOutputDescription =
+ [
+ "The output should start with a joke about a pirate.",
+ "After the joke there should be a '--- Serialized session ---' separator followed by a JSON block representing the serialized session state.",
+ "After the JSON block there should be a second response that retells the same joke in a pirate voice with emojis, demonstrating that context was preserved across serialization.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step04_3rdPartyChatHistoryStorage",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step04_3rdPartyChatHistoryStorage",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = ["--- Serialized session ---"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a pirate joke response and a '--- Serialized session ---' separator with session JSON.",
+ "It should show that the session was stored in a vector store, with a 'Session is stored in vector store under key:' line.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step06_DependencyInjection",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step06_DependencyInjection",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ Inputs = ["Tell me a joke about a pirate", ""],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate in response to the user's request.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step08_UsingImages",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step08_UsingImages",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should describe an image of a nature boardwalk/walkway scene.",
+ "It should mention elements like a wooden boardwalk or path, greenery or vegetation, and an outdoor or natural setting.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step09_AsFunctionTool",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step09_AsFunctionTool",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should be a response about the weather in Amsterdam, written in French.",
+ "The response should reference the tool result: cloudy weather with a high of 15°C.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step10_BackgroundResponsesWithToolsAndPersistence",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step10_BackgroundResponsesWithToolsAndPersistence",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a generated novel or story.",
+ "The output may include tool invocation messages like '[ResearchSpaceFacts]' or '[GenerateCharacterProfiles]'.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step11_Middleware",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step11_Middleware",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ // Example 4 prompts for approval; provide "Y" for each possible tool call
+ Inputs = ["Y", "Y", "Y"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription =
+ [
+ "The output should contain multiple examples demonstrating different middleware patterns.",
+ "It should include sections with '===' headers for different middleware examples.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step12_Plugins",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step12_Plugins",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain information about both the current time and the weather in Seattle.",
+ "The weather information should reference the plugin result: cloudy with a high of 15°C.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step13_ChatReduction",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step13_ChatReduction",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = ["Chat history has", "messages."],
+ ExpectedOutputDescription =
+ [
+ "The output should contain joke responses about a pirate, a robot, and a lemur.",
+ "Between each response there should be a 'Chat history has N messages.' line showing the message count.",
+ "There should be a fourth response after the user asks about the first joke. Due to chat reduction, the agent may not remember the pirate joke — any response is acceptable (including repeating another joke or saying it doesn't remember).",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step14_BackgroundResponses",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step14_BackgroundResponses",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a generated story or novel text about otters in space.",
+ "The text may appear in two parts: first a polled-to-completion result, then a streamed continuation.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step16_Declarative",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step16_Declarative",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a response in JSON format with 'language' and 'answer' fields, since the declarative agent is configured to respond in JSON.",
+ "The content should be a joke about a pirate in English.",
+ "There should be both a non-streaming and streaming response.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step17_AdditionalAIContext",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step17_AdditionalAIContext",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show a personal assistant managing a todo list across multiple turns.",
+ "The assistant should acknowledge adding items like picking up milk, taking Sally to soccer practice, and making a dentist appointment for Jimmy.",
+ "There should be a JSON block showing the serialized session state.",
+ "The final response should reference the calendar appointments (doctor at 15:00, team meeting at 17:00, birthday party at 20:00).",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step18_CompactionPipeline",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step18_CompactionPipeline",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = ["[User]", "[Agent]"],
+ ExpectedOutputDescription =
+ [
+ "The output should show a turn-by-turn conversation between [User] and [Agent] about shopping for electronics (laptops, keyboards, mice).",
+ "The output may include '[Messages: #N]' lines showing chat history compaction.",
+ "The agent should provide information about product prices from tool results.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step19_InFunctionLoopCheckpointing",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step19_InFunctionLoopCheckpointing",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME", "AZURE_OPENAI_RESPONSES_STORE"],
+ MustContain = ["=== Non-Streaming Mode ===", "=== Streaming Mode ==="],
+ ExpectedOutputDescription =
+ [
+ "The output should show non-streaming and streaming modes demonstrating in-function-loop checkpointing with multi-turn conversations.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ── AgentSkills ─────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step01_FileBasedSkills",
+ ProjectPath = "samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain =
+ [
+ "Converting units with file-based skills",
+ "Agent:",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should show the agent converting 26.2 miles to kilometers and 75 kilograms to pounds.",
+ "The response should contain approximate numeric values for both conversions.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ── AgentWithMemory ─────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "AgentWithMemory_Step01_ChatHistoryMemory",
+ ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME", "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain two joke responses.",
+ "The first joke should be about a pirate (as explicitly requested).",
+ "The second joke should also be pirate-themed or similar to what the user likes, since the memory system should recall the user's preference for pirate jokes from the first session.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "AgentWithMemory_Step04_MemoryUsingFoundry",
+ ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MEMORY_STORE_ID", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_EMBEDDING_DEPLOYMENT_NAME"],
+ MustContain =
+ [
+ ">> Setting up Foundry Memory Store",
+ ">> Serialize and deserialize the session to demonstrate persisted state",
+ ">> Start a new session that shares the same Foundry Memory scope",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should show a Foundry Memory Store being set up and processing updates.",
+ "After serialization/deserialization, the agent should recall previously learned information.",
+ "In the new session section, the agent should know facts from the earlier session due to shared Foundry Memory.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "AgentWithMemory_Step05_BoundedChatHistory",
+ ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME", "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
+ MustContain =
+ [
+ "--- Filling the session window",
+ "--- Next exchange will trigger overflow to vector store ---",
+ "--- Asking about overflowed information",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should demonstrate bounded chat history with overflow to a vector store.",
+ "After the window fills up and overflows, the agent should still be able to recall older information (like a favorite color) from the vector store.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ── AgentWithRAG ────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "AgentWithRAG_Step01_BasicTextRAG",
+ ProjectPath = "samples/02-agents/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME", "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
+ MustContain = [">> Asking about returns", ">> Asking about shipping", ">> Asking about product care"],
+ ExpectedOutputDescription =
+ [
+ "The returns section should mention a 30-day return policy, unused condition, and original packaging.",
+ "The shipping section should mention 3-5 business days for standard shipping.",
+ "The product care section should mention tent fabric maintenance tips like using lukewarm water, non-detergent soap, and air drying.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "AgentWithRAG_Step03_CustomRAGDataSource",
+ ProjectPath = "samples/02-agents/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = [">> Asking about returns", ">> Asking about shipping", ">> Asking about product care"],
+ ExpectedOutputDescription =
+ [
+ "The returns section should mention a 30-day return policy.",
+ "The shipping section should mention 3-5 business days for standard shipping.",
+ "The product care section should mention tent fabric maintenance tips.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "AgentWithRAG_Step04_FoundryServiceRAG",
+ ProjectPath = "samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ MustContain = [">> Asking about returns", ">> Asking about shipping", ">> Asking about product care"],
+ ExpectedOutputDescription =
+ [
+ "The returns section should mention a 30-day return policy.",
+ "The shipping section should mention standard shipping timeframes.",
+ "The product care section should mention tent fabric maintenance tips.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ── AgentsWithFoundry ────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step00_FoundryAgentLifecycle",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step01_Basics",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke response from the agent.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step02.1_MultiturnConversation",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain multiple joke responses showing a multi-turn conversation.",
+ "There should be both non-streaming and streaming responses, with the second turn in each building on the first (e.g., adding emojis or pirate voice).",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step02.2_MultiturnWithServerConversations",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should demonstrate server-side conversation sessions with non-streaming and streaming turns.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step03_UsingFunctionTools",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain weather information about Amsterdam from a function tool.",
+ "The response should mention cloudy weather with a high of 15°C (from the canned tool response).",
+ "There should be both a non-streaming and streaming response.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step04_UsingFunctionToolsWithApprovals",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Y", "Y", "Y"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription =
+ [
+ "The output should contain a prompt asking the user to approve a tool call, followed by weather information about Amsterdam.",
+ "The response should mention cloudy weather with a high of 15°C.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step05_StructuredOutput",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ MustContain = ["Assistant Output:", "Name:"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain structured person information with Name, Age, and Occupation fields.",
+ "There should be both a direct structured output and a streamed-then-deserialized output.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step06_PersistedConversations",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a pirate joke, then after session persistence, a second response retelling the joke in pirate voice with emojis.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step08_DependencyInjection",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Tell me a joke about a pirate", ""],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate in response to the user's request.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step10_UsingImages",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should describe an image of a nature walkway or boardwalk scene.",
+ "It should mention elements like a wooden path, greenery, and an outdoor setting.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step11_AsFunctionTool",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should be a response about the weather in Amsterdam, written in French.",
+ "The response should reference the tool result: cloudy weather with a high of 15°C.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step12_Middleware",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Y", "Y", "Y"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription =
+ [
+ "The output should contain multiple middleware examples with '===' section headers.",
+ "The human-in-the-loop example should show tool approval prompts and agent responses.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step13_Plugins",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain information about both the current time and the weather in Seattle.",
+ "The weather information should reference the plugin result: cloudy with a high of 15°C.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step14_CodeInterpreter",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show the code interpreter being used to solve sin(x) + x^2 = 42, including a 'Code Input:' section with Python code.",
+ "It should show a 'Code Input:' section with Python code for the math problem.",
+ "It may show a 'Code Tool Result:' section with computed answers, or annotations with file references.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step16_FileSearch",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ MustContain = ["--- Running File Search Agent ---"],
+ ExpectedOutputDescription =
+ [
+ "The output should show a file being uploaded and indexed in a vector store, then an agent answering a question based on the file content.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step17_OpenAPITools",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a list of countries or information about countries that use the EUR currency.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ── Skipped samples ─────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "AgentOpenTelemetry",
+ ProjectPath = "samples/02-agents/AgentOpenTelemetry",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Requires Aspire Dashboard / Docker for OpenTelemetry collection.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_A2A",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_A2A",
+ RequiredEnvironmentVariables = ["A2A_AGENT_HOST"],
+ SkipReason = "Requires an external A2A agent host.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_Anthropic",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_Anthropic",
+ RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
+ OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME", "ANTHROPIC_RESOURCE"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_GitHubCopilot",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_GitHubCopilot",
+ RequiredEnvironmentVariables = [],
+ // The sample prompts for shell command approval; provide "Y" for each possible permission request
+ Inputs = ["Y", "Y", "Y"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription =
+ [
+ "The output should contain a user prompt and a response listing files in the current directory.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_GoogleGemini",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_GoogleGemini",
+ RequiredEnvironmentVariables = ["GOOGLE_GENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["GOOGLE_GENAI_MODEL"],
+ MustContain =
+ [
+ "Google GenAI client based agent response:",
+ "Community client based agent response:",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should contain two labeled sections, each with a joke about a pirate from a different Gemini client.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_ONNX",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_ONNX",
+ RequiredEnvironmentVariables = ["ONNX_MODEL_PATH"],
+ SkipReason = "Requires local ONNX model.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_Ollama",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_Ollama",
+ RequiredEnvironmentVariables = ["OLLAMA_ENDPOINT", "OLLAMA_MODEL_NAME"],
+ 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",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_With_OpenAIResponses",
+ ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIResponses",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step05_Observability",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step05_Observability",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME", "APPLICATIONINSIGHTS_CONNECTION_STRING"],
+ SkipReason = "Requires Application Insights / OpenTelemetry infrastructure.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step07_AsMcpTool",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step07_AsMcpTool",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Step15_DeepResearch",
+ ProjectPath = "samples/02-agents/Agents/Agent_Step15_DeepResearch",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_BING_CONNECTION_ID"],
+ OptionalEnvironmentVariables = ["AZURE_AI_REASONING_DEPLOYMENT_NAME"],
+ SkipReason = "Requires Azure AI Foundry project with Bing search connection.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Anthropic_Step01_Running",
+ ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step01_Running",
+ RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
+ OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "There should be two responses — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Anthropic_Step02_Reasoning",
+ ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning",
+ RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
+ OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
+ MustContain =
+ [
+ "1. Non-streaming:",
+ "#### Start Thinking ####",
+ "#### End Thinking ####",
+ "#### Final Answer ####",
+ "Token usage:",
+ "2. Streaming",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The non-streaming section should show the agent's reasoning about a math problem, followed by a final answer.",
+ "The streaming section should show reasoning and a response about the theory of relativity.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Anthropic_Step03_UsingFunctionTools",
+ ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools",
+ RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
+ OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain information about the weather in Amsterdam.",
+ "There should be two responses — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_Anthropic_Step04_UsingSkills",
+ ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step04_UsingSkills",
+ RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
+ OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
+ MustContain =
+ [
+ "Creating a presentation about renewable energy...",
+ "#### Agent Response ####",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should show the agent creating a presentation about renewable energy.",
+ "There should be an agent response section with content about renewable energy sources.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "AgentWithMemory_Step02_MemoryUsingMem0",
+ ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_DEPLOYMENT_NAME", "MEM0_ENDPOINT", "MEM0_API_KEY"],
+ SkipReason = "Requires Mem0 service.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_OpenAI_Step01_Running",
+ ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step01_Running",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_OpenAI_Step02_Reasoning",
+ ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ MustContain =
+ [
+ "1. Non-streaming:",
+ "Token usage:",
+ "2. Streaming",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The non-streaming section should show the agent's reasoning about a math problem, followed by a final answer.",
+ "The streaming section should show reasoning and a response about the theory of relativity.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_OpenAI_Step03_CreateFromChatClient",
+ ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "There should be two responses — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
+ ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "There should be two responses — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_OpenAI_Step05_Conversation",
+ ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation",
+ RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
+ OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
+ MustContain =
+ [
+ "=== Multi-turn Conversation Demo ===",
+ "Conversation created.",
+ "Conversation ID:",
+ ],
+ ExpectedOutputDescription =
+ [
+ "The output should show a multi-turn conversation about France: capital, landmarks, and height of the most famous one.",
+ "The output should show the conversation history retrieved from the server.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "AgentWithRAG_Step02_CustomVectorStoreRAG",
+ ProjectPath = "samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME", "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
+ SkipReason = "Requires external Qdrant vector store.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "DeclarativeAgents_ChatClient",
+ ProjectPath = "samples/02-agents/DeclarativeAgents/ChatClient",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Requires command-line arguments (YAML file path) with no YAML files checked in.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "DevUI_Step01_BasicUsage",
+ ProjectPath = "samples/02-agents/DevUI/DevUI_Step01_BasicUsage",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "ASP.NET Core web server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step07_Observability",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME", "APPLICATIONINSIGHTS_CONNECTION_STRING"],
+ SkipReason = "Requires Application Insights / OpenTelemetry infrastructure.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step09_UsingMcpClientAsTools",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show an agent using the Microsoft Learn MCP tool to search or retrieve documentation.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step15_ComputerUse",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription = ["The output should show a computer automation session processing simulated browser screenshots with iteration steps and a final response describing search results."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step18_BingCustomSearch",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID", "AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME"],
+ SkipReason = "Requires Bing Custom Search connection.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step19_SharePoint",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "SHAREPOINT_PROJECT_CONNECTION_ID"],
+ SkipReason = "Requires SharePoint connection.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step20_MicrosoftFabric",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "FABRIC_PROJECT_CONNECTION_ID"],
+ SkipReason = "Requires Microsoft Fabric connection.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step21_WebSearch",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show an agent using web search to answer a question, with response text and citation annotations.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step22_MemorySearch",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_EMBEDDING_DEPLOYMENT_NAME"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MEMORY_STORE_ID"],
+ MustContain = ["Agent created with Memory Search tool. Starting conversation..."],
+ ExpectedOutputDescription =
+ [
+ "The output should show a memory store being created, memories stored from a prior conversation, and an agent querying those memories.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Step23_LocalMCP",
+ ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription = ["The output should show an agent using the Microsoft Learn MCP server to search for documentation and provide a response."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "FoundryAgent_Hosted_MCP",
+ ProjectPath = "samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Y", "Y", "Y", "Y", "Y"],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription = ["The output should contain a summary or information about Azure AI documentation from Microsoft Learn."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "ResponseAgent_Hosted_MCP",
+ ProjectPath = "samples/02-agents/ModelContextProtocol/ResponseAgent_Hosted_MCP",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ Inputs = ["Y", "Y", "Y", "Y", "Y"],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription = ["The output should contain a summary or information about Azure AI documentation from Microsoft Learn."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_MCP_Server",
+ ProjectPath = "samples/02-agents/ModelContextProtocol/Agent_MCP_Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Agent_MCP_Server_Auth",
+ ProjectPath = "samples/02-agents/ModelContextProtocol/Agent_MCP_Server_Auth",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step01_GettingStarted_Client",
+ ProjectPath = "samples/02-agents/AGUI/Step01_GettingStarted/Client",
+ RequiredEnvironmentVariables = [],
+ SkipReason = "Multi-process client/server architecture; requires AGUI server running.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step01_GettingStarted_Server",
+ ProjectPath = "samples/02-agents/AGUI/Step01_GettingStarted/Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "ASP.NET Core web server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step02_BackendTools_Client",
+ ProjectPath = "samples/02-agents/AGUI/Step02_BackendTools/Client",
+ RequiredEnvironmentVariables = [],
+ SkipReason = "Multi-process client/server architecture; requires AGUI server running.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step02_BackendTools_Server",
+ ProjectPath = "samples/02-agents/AGUI/Step02_BackendTools/Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "ASP.NET Core web server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step03_FrontendTools_Client",
+ ProjectPath = "samples/02-agents/AGUI/Step03_FrontendTools/Client",
+ RequiredEnvironmentVariables = [],
+ SkipReason = "Multi-process client/server architecture; requires AGUI server running.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step03_FrontendTools_Server",
+ ProjectPath = "samples/02-agents/AGUI/Step03_FrontendTools/Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "ASP.NET Core web server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step04_HumanInLoop_Client",
+ ProjectPath = "samples/02-agents/AGUI/Step04_HumanInLoop/Client",
+ RequiredEnvironmentVariables = [],
+ SkipReason = "Multi-process client/server architecture; requires AGUI server running.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step04_HumanInLoop_Server",
+ ProjectPath = "samples/02-agents/AGUI/Step04_HumanInLoop/Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "ASP.NET Core web server that does not exit on its own.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step05_StateManagement_Client",
+ ProjectPath = "samples/02-agents/AGUI/Step05_StateManagement/Client",
+ RequiredEnvironmentVariables = [],
+ SkipReason = "Multi-process client/server architecture; requires AGUI server running.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "AGUI_Step05_StateManagement_Server",
+ ProjectPath = "samples/02-agents/AGUI/Step05_StateManagement/Server",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "ASP.NET Core web server that does not exit on its own.",
+ },
+ ];
+}
diff --git a/dotnet/eng/verify-samples/ConsoleReporter.cs b/dotnet/eng/verify-samples/ConsoleReporter.cs
new file mode 100644
index 0000000000..0b21138d79
--- /dev/null
+++ b/dotnet/eng/verify-samples/ConsoleReporter.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// Thread-safe console output with sample-name prefixes and colored status.
+///
+internal sealed class ConsoleReporter
+{
+ private readonly object _lock = new();
+
+ ///
+ /// Writes a complete prefixed line atomically to the console.
+ ///
+ public void WriteLineWithPrefix(string sampleName, string message, ConsoleColor? color = null)
+ {
+ lock (this._lock)
+ {
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write($"[{sampleName}] ");
+ if (color.HasValue)
+ {
+ Console.ForegroundColor = color.Value;
+ }
+ else
+ {
+ Console.ResetColor();
+ }
+
+ Console.WriteLine(message);
+ Console.ResetColor();
+ }
+ }
+
+ ///
+ /// Prints the final summary table and elapsed time to the console.
+ ///
+ public void PrintSummary(
+ IReadOnlyList orderedResults,
+ IReadOnlyList<(string Name, string Reason)> skipped,
+ TimeSpan elapsed)
+ {
+ var passCount = orderedResults.Count(r => r.Passed);
+ var failCount = orderedResults.Count(r => !r.Passed);
+
+ Console.WriteLine();
+ Console.WriteLine(new string('─', 60));
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.WriteLine("SUMMARY");
+ Console.ResetColor();
+
+ foreach (var result in orderedResults)
+ {
+ Console.ForegroundColor = result.Passed ? ConsoleColor.Green : ConsoleColor.Red;
+ Console.Write(result.Passed ? " ✓ " : " ✗ ");
+ Console.ResetColor();
+ Console.WriteLine($"{result.SampleName}: {result.Summary}");
+ }
+
+ foreach (var (name, reason) in skipped)
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.Write(" ○ ");
+ Console.ResetColor();
+ Console.WriteLine($"{name}: Skipped — {reason}");
+ }
+
+ Console.WriteLine();
+ Console.Write("Results: ");
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.Write($"{passCount} passed");
+ Console.ResetColor();
+
+ if (failCount > 0)
+ {
+ Console.Write(", ");
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.Write($"{failCount} failed");
+ Console.ResetColor();
+ }
+
+ if (skipped.Count > 0)
+ {
+ Console.Write(", ");
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.Write($"{skipped.Count} skipped");
+ Console.ResetColor();
+ }
+
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine($"Elapsed: {elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}");
+ Console.ResetColor();
+ }
+}
diff --git a/dotnet/eng/verify-samples/CsvResultWriter.cs b/dotnet/eng/verify-samples/CsvResultWriter.cs
new file mode 100644
index 0000000000..9a1128dcba
--- /dev/null
+++ b/dotnet/eng/verify-samples/CsvResultWriter.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text;
+
+namespace VerifySamples;
+
+///
+/// Writes a CSV summary of sample verification results.
+///
+internal static class CsvResultWriter
+{
+ ///
+ /// Writes the results to a CSV file at the specified path.
+ ///
+ public static async Task WriteAsync(
+ string path,
+ IReadOnlyList orderedResults,
+ IReadOnlyList<(string Name, string Reason)> skipped,
+ IReadOnlyList samples)
+ {
+ var pathLookup = samples.ToDictionary(s => s.Name, s => s.ProjectPath);
+
+ var sb = new StringBuilder();
+ sb.AppendLine("Sample,ProjectPath,Status,FailedChecks,Failures");
+
+ foreach (var result in orderedResults)
+ {
+ var status = result.Passed ? "PASSED" : "FAILED";
+ var failedChecks = result.Failures.Count;
+ var failures = string.Join("; ", result.Failures);
+ pathLookup.TryGetValue(result.SampleName, out var projectPath);
+ sb.AppendLine($"{CsvEscape(result.SampleName)},{CsvEscape(projectPath ?? "")},{status},{failedChecks},{CsvEscape(failures)}");
+ }
+
+ foreach (var (name, reason) in skipped)
+ {
+ pathLookup.TryGetValue(name, out var projectPath);
+ sb.AppendLine($"{CsvEscape(name)},{CsvEscape(projectPath ?? "")},SKIPPED,0,{CsvEscape(reason)}");
+ }
+
+ await File.WriteAllTextAsync(path, sb.ToString());
+ }
+
+ ///
+ /// Escapes a value for CSV: wraps in quotes if it contains commas, quotes, or newlines.
+ ///
+ private static string CsvEscape(string value)
+ {
+ if (value.Contains('"') || value.Contains(',') || value.Contains('\n') || value.Contains('\r'))
+ {
+ return $"\"{value.Replace("\"", "\"\"")}\"";
+ }
+
+ return value;
+ }
+}
diff --git a/dotnet/eng/verify-samples/GetStartedSamples.cs b/dotnet/eng/verify-samples/GetStartedSamples.cs
new file mode 100644
index 0000000000..9298e39388
--- /dev/null
+++ b/dotnet/eng/verify-samples/GetStartedSamples.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// Defines the expected behavior for each sample in 01-get-started.
+///
+internal static class GetStartedSamples
+{
+ public static IReadOnlyList All { get; } =
+ [
+ new SampleDefinition
+ {
+ Name = "05_first_workflow",
+ ProjectPath = "samples/01-get-started/05_first_workflow",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "UppercaseExecutor: HELLO, WORLD!",
+ "ReverseTextExecutor: !DLROW ,OLLEH",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "01_hello_agent",
+ ProjectPath = "samples/01-get-started/01_hello_agent",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "There should be two separate joke responses — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "02_add_tools",
+ ProjectPath = "samples/01-get-started/02_add_tools",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = [],
+ ExpectedOutputDescription =
+ [
+ "The output should contain information about the weather in Amsterdam.",
+ "The response should mention that it is cloudy with a high of 15°C (or equivalent), since this comes from a tool that returns a canned response.",
+ "There should be two responses — one from a non-streaming call and one from a streaming call.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "03_multi_turn",
+ ProjectPath = "samples/01-get-started/03_multi_turn",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should contain a joke about a pirate.",
+ "After the initial joke, there should be a modified version that includes emojis and is told in the voice of a pirate's parrot.",
+ "The pattern repeats: first a non-streaming pirate joke + parrot version, then a streaming pirate joke + parrot version.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "04_memory",
+ ProjectPath = "samples/01-get-started/04_memory",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain =
+ [
+ ">> Use session with blank memory",
+ ">> Use deserialized session with previously created memories",
+ ">> Read memories using memory component",
+ "MEMORY - User Name:",
+ "MEMORY - User Age:",
+ ">> Use new session with previously created memories",
+ ],
+ ExpectedOutputDescription =
+ [
+ "In the 'Use session with blank memory' section, the agent should respond to the user's messages. It may ask for the user's name or age if not yet known.",
+ "In the 'Use deserialized session with previously created memories' section, the agent should correctly recall that the user's name is Ruaidhrí and age is 20.",
+ "The 'MEMORY - User Name:' line should show 'Ruaidhrí' (or a close transliteration).",
+ "The 'MEMORY - User Age:' line should show '20'.",
+ "In the 'Use new session with previously created memories' section, the agent should know the user's name and age from the transferred memory.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "06_host_your_agent",
+ ProjectPath = "samples/01-get-started/06_host_your_agent",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Requires Azure Functions Core Tools runtime and starts a web server.",
+ },
+ ];
+}
diff --git a/dotnet/eng/verify-samples/LogFileWriter.cs b/dotnet/eng/verify-samples/LogFileWriter.cs
new file mode 100644
index 0000000000..a46096f3d6
--- /dev/null
+++ b/dotnet/eng/verify-samples/LogFileWriter.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text;
+
+namespace VerifySamples;
+
+///
+/// Incrementally writes a sequential (non-interleaved) log file, appending after each sample completes.
+/// Thread-safe: multiple parallel tasks may call write methods concurrently.
+///
+internal sealed class LogFileWriter : IDisposable
+{
+ private readonly string _path;
+ private readonly SemaphoreSlim _writeLock = new(1, 1);
+
+ public LogFileWriter(string path)
+ {
+ this._path = path;
+ }
+
+ ///
+ public void Dispose()
+ {
+ this._writeLock.Dispose();
+ }
+
+ ///
+ /// Writes the log file header. Call once at the start of the run.
+ ///
+ public async Task WriteHeaderAsync()
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine($"Sample Verification Log — {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
+ sb.AppendLine(new string('═', 72));
+ sb.AppendLine();
+
+ await File.WriteAllTextAsync(this._path, sb.ToString());
+ }
+
+ ///
+ /// Appends a skipped-sample entry to the log file.
+ ///
+ public async Task WriteSkippedAsync(string name, string reason)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine($"── {name} ──");
+ sb.AppendLine($"Status: SKIPPED — {reason}");
+ sb.AppendLine();
+
+ await this.AppendAsync(sb.ToString());
+ }
+
+ ///
+ /// Appends a completed sample's full output section to the log file.
+ ///
+ public async Task WriteSampleResultAsync(VerificationResult result)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine(new string('─', 72));
+ sb.AppendLine($"── {result.SampleName} ──");
+ sb.AppendLine($"Status: {(result.Passed ? "PASSED" : "FAILED")}");
+ sb.AppendLine();
+
+ foreach (var line in result.LogLines)
+ {
+ sb.AppendLine(line);
+ }
+
+ sb.AppendLine();
+
+ if (!string.IsNullOrWhiteSpace(result.Stdout))
+ {
+ sb.AppendLine("--- stdout ---");
+ sb.AppendLine(result.Stdout.TrimEnd());
+ sb.AppendLine("--- end stdout ---");
+ sb.AppendLine();
+ }
+
+ if (!string.IsNullOrWhiteSpace(result.Stderr))
+ {
+ sb.AppendLine("--- stderr ---");
+ sb.AppendLine(result.Stderr.TrimEnd());
+ sb.AppendLine("--- end stderr ---");
+ sb.AppendLine();
+ }
+
+ if (result.Failures.Count > 0)
+ {
+ sb.AppendLine("Failures:");
+ foreach (var failure in result.Failures)
+ {
+ sb.AppendLine($" ✗ {failure}");
+ }
+
+ sb.AppendLine();
+ }
+
+ if (result.AIReasoning is not null)
+ {
+ sb.AppendLine("AI Reasoning:");
+ sb.AppendLine(result.AIReasoning);
+ sb.AppendLine();
+ }
+
+ await this.AppendAsync(sb.ToString());
+ }
+
+ ///
+ /// Appends the final summary section and elapsed time to the log file.
+ ///
+ public async Task WriteSummaryAsync(
+ IReadOnlyList 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(new string('═', 72));
+ sb.AppendLine("SUMMARY");
+ sb.AppendLine();
+
+ foreach (var result in orderedResults)
+ {
+ sb.AppendLine($" {(result.Passed ? "✓" : "✗")} {result.SampleName}: {result.Summary}");
+ }
+
+ foreach (var (name, reason) in skipped)
+ {
+ sb.AppendLine($" ○ {name}: Skipped — {reason}");
+ }
+
+ sb.AppendLine();
+ sb.AppendLine($"Results: {passCount} passed{(failCount > 0 ? $", {failCount} failed" : "")}{(skipped.Count > 0 ? $", {skipped.Count} skipped" : "")}");
+ sb.AppendLine($"Elapsed: {elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}");
+
+ await this.AppendAsync(sb.ToString());
+ }
+
+ private async Task AppendAsync(string text)
+ {
+ await this._writeLock.WaitAsync();
+ try
+ {
+ await File.AppendAllTextAsync(this._path, text);
+ }
+ finally
+ {
+ this._writeLock.Release();
+ }
+ }
+}
diff --git a/dotnet/eng/verify-samples/Program.cs b/dotnet/eng/verify-samples/Program.cs
new file mode 100644
index 0000000000..e1a3bd3170
--- /dev/null
+++ b/dotnet/eng/verify-samples/Program.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This tool runs the 01-get-started, 02-agents, and 03-workflows samples and verifies their output.
+// Deterministic samples are verified with exact string matching.
+// Non-deterministic (LLM) samples are verified using an agent-framework agent.
+//
+// Usage:
+// dotnet run # Run all samples
+// dotnet run -- 01_hello_agent 05_first_workflow # Run specific samples by name
+// dotnet run -- --category 01-get-started # Run the 01-get-started category
+// dotnet run -- --category 02-agents # Run the 02-agents category
+// dotnet run -- --category 03-workflows # Run the 03-workflows category
+// 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
+//
+// Required environment variables (for AI-powered samples):
+// AZURE_OPENAI_ENDPOINT
+// AZURE_OPENAI_DEPLOYMENT_NAME (optional, defaults to gpt-5-mini)
+
+using System.Diagnostics;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using VerifySamples;
+
+var options = VerifyOptions.Parse(args);
+if (options is null)
+{
+ return 1;
+}
+
+var stopwatch = Stopwatch.StartNew();
+
+// Resolve the dotnet/ root directory (verify-samples is at dotnet/eng/verify-samples/)
+var dotnetRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
+if (!File.Exists(Path.Combine(dotnetRoot, "agent-framework-dotnet.slnx")))
+{
+ dotnetRoot = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", ".."));
+}
+
+// Set up the AI verifier
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5-mini";
+
+OpenAI.Chat.ChatClient? chatClient = null;
+if (!string.IsNullOrEmpty(endpoint))
+{
+ chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
+ .GetChatClient(deploymentName);
+}
+
+// Set up optional log file writer
+LogFileWriter? logWriter = null;
+if (options.LogFilePath is not null)
+{
+ logWriter = new LogFileWriter(options.LogFilePath);
+ await logWriter.WriteHeaderAsync();
+}
+
+try
+{
+ // Run all samples
+ var reporter = new ConsoleReporter();
+ var verifier = new SampleVerifier(chatClient);
+ var orchestrator = new VerificationOrchestrator(verifier, reporter, dotnetRoot, TimeSpan.FromMinutes(3), logWriter);
+
+ var run = await orchestrator.RunAllAsync(options.Samples, options.MaxParallelism);
+
+ stopwatch.Stop();
+
+ // Print summary
+ var orderedResults = run.SampleOrder
+ .Where(run.Results.ContainsKey)
+ .Select(name => run.Results[name])
+ .ToList();
+
+ reporter.PrintSummary(orderedResults, run.Skipped, stopwatch.Elapsed);
+
+ // Write log file summary
+ if (logWriter is not null)
+ {
+ await logWriter.WriteSummaryAsync(orderedResults, run.Skipped, stopwatch.Elapsed);
+ Console.WriteLine($"Log written to: {options.LogFilePath}");
+ }
+
+ // Write CSV summary
+ if (options.CsvFilePath is not null)
+ {
+ await CsvResultWriter.WriteAsync(options.CsvFilePath, orderedResults, run.Skipped, options.Samples);
+ Console.WriteLine($"CSV written to: {options.CsvFilePath}");
+ }
+
+ return orderedResults.Any(r => !r.Passed) ? 1 : 0;
+}
+finally
+{
+ logWriter?.Dispose();
+}
diff --git a/dotnet/eng/verify-samples/SampleDefinition.cs b/dotnet/eng/verify-samples/SampleDefinition.cs
new file mode 100644
index 0000000000..5f5f69a40e
--- /dev/null
+++ b/dotnet/eng/verify-samples/SampleDefinition.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// Describes a sample to verify, including its expected output.
+///
+internal sealed class SampleDefinition
+{
+ ///
+ /// Display name for the sample (e.g., "01_hello_agent").
+ ///
+ public required string Name { get; init; }
+
+ ///
+ /// Relative path from the dotnet/ directory to the sample project directory.
+ ///
+ public required string ProjectPath { get; init; }
+
+ ///
+ /// Environment variables that the sample requires for a meaningful run.
+ /// The runner checks these before running and will skip the sample if any are unset,
+ /// recording a skip reason that indicates which required variables are missing.
+ ///
+ public string[] RequiredEnvironmentVariables { get; init; } = [];
+
+ ///
+ /// Environment variables that the sample can use but typically has fallbacks or defaults for.
+ /// If these are not set, the sample might prompt or behave interactively, which could cause
+ /// automated verification to hang. The runner checks these and skips the sample if they are unset
+ /// to avoid non-deterministic or blocking behavior in automated runs.
+ ///
+ public string[] OptionalEnvironmentVariables { get; init; } = [];
+
+ ///
+ /// If set, the sample is skipped with this reason.
+ /// Use only for structural reasons (e.g., web server, multi-process, needs external service).
+ /// Do NOT use for missing environment variables — those are checked dynamically.
+ ///
+ public string? SkipReason { get; init; }
+
+ ///
+ /// Substrings that must appear in stdout for the sample to pass.
+ /// Used for deterministic verification.
+ ///
+ public string[] MustContain { get; init; } = [];
+
+ ///
+ /// Substrings that must not appear in stdout for the sample to pass.
+ ///
+ public string[] MustNotContain { get; init; } = [];
+
+ ///
+ /// If true, entries cover the entire expected output —
+ /// no AI verification is needed.
+ ///
+ public bool IsDeterministic { get; init; }
+
+ ///
+ /// Natural-language description of what the sample output should look like.
+ /// Used by the AI verifier for non-deterministic samples.
+ /// Each entry describes one aspect of the expected output that should be verified.
+ ///
+ public string[] ExpectedOutputDescription { get; init; } = [];
+
+ ///
+ /// Sequence of stdin inputs to feed to the sample process.
+ /// Each entry is written as a line (followed by newline) to the process stdin.
+ /// A null entry inserts a delay without writing anything.
+ /// Inputs are sent with a short delay between each to allow the process to prompt.
+ ///
+ public string?[] Inputs { get; init; } = [];
+
+ ///
+ /// Delay in milliseconds between each input line. Default is 2000ms.
+ /// Increase for samples that need more time between prompts (e.g., LLM calls between inputs).
+ ///
+ public int InputDelayMs { get; init; } = 2000;
+}
diff --git a/dotnet/eng/verify-samples/SampleRunner.cs b/dotnet/eng/verify-samples/SampleRunner.cs
new file mode 100644
index 0000000000..0fabd82262
--- /dev/null
+++ b/dotnet/eng/verify-samples/SampleRunner.cs
@@ -0,0 +1,132 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics;
+
+namespace VerifySamples;
+
+///
+/// Result of running a sample process.
+///
+internal sealed record SampleRunResult(
+ string Stdout,
+ string Stderr,
+ int ExitCode,
+ TimeSpan Elapsed);
+
+///
+/// Runs a sample project via dotnet run and captures its output.
+///
+internal static class SampleRunner
+{
+ ///
+ /// Runs dotnet run --framework net10.0 in the given project directory.
+ ///
+ public static Task RunAsync(
+ string projectPath,
+ TimeSpan timeout,
+ CancellationToken cancellationToken = default)
+ => RunAsync(projectPath, "run --framework net10.0", timeout, inputs: null, inputDelayMs: 0, cancellationToken: cancellationToken);
+
+ ///
+ /// Runs dotnet run --framework net10.0 with stdin inputs.
+ ///
+ public static Task RunAsync(
+ string projectPath,
+ TimeSpan timeout,
+ string?[]? inputs,
+ int inputDelayMs = 2000,
+ CancellationToken cancellationToken = default)
+ => RunAsync(projectPath, "run --framework net10.0", timeout, inputs, inputDelayMs, cancellationToken);
+
+ ///
+ /// Runs an arbitrary dotnet command in the given working directory.
+ ///
+ public static async Task RunAsync(
+ string workingDirectory,
+ string dotnetArgs,
+ TimeSpan timeout,
+ string?[]? inputs = null,
+ int inputDelayMs = 0,
+ CancellationToken cancellationToken = default)
+ {
+ var psi = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = dotnetArgs,
+ WorkingDirectory = workingDirectory,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = inputs is { Length: > 0 },
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+
+ var sw = Stopwatch.StartNew();
+
+ using var process = new Process { StartInfo = psi };
+ process.Start();
+
+ var stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
+ var stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
+
+ // Feed stdin inputs with delays if configured
+ if (inputs is { Length: > 0 })
+ {
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ foreach (var input in inputs)
+ {
+ await Task.Delay(inputDelayMs, cancellationToken);
+ if (input is not null)
+ {
+ await process.StandardInput.WriteLineAsync(input.AsMemory(), cancellationToken);
+ await process.StandardInput.FlushAsync(cancellationToken);
+ }
+ }
+
+ process.StandardInput.Close();
+ }
+ catch (Exception ex) when (ex is IOException or ObjectDisposedException or OperationCanceledException)
+ {
+ // Process may have exited before all inputs were sent
+ }
+ }, cancellationToken);
+ }
+
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ cts.CancelAfter(timeout);
+
+ try
+ {
+ await process.WaitForExitAsync(cts.Token);
+ }
+ catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
+ {
+ // Timeout — kill the process
+ try
+ {
+ process.Kill(entireProcessTree: true);
+ }
+ catch
+ {
+ // Best effort
+ }
+
+ sw.Stop();
+ return new SampleRunResult(
+ Stdout: await stdoutTask,
+ Stderr: $"TIMEOUT: Sample did not complete within {timeout.TotalSeconds}s.\n{await stderrTask}",
+ ExitCode: -1,
+ Elapsed: sw.Elapsed);
+ }
+
+ sw.Stop();
+ return new SampleRunResult(
+ Stdout: await stdoutTask,
+ Stderr: await stderrTask,
+ ExitCode: process.ExitCode,
+ Elapsed: sw.Elapsed);
+ }
+}
diff --git a/dotnet/eng/verify-samples/SampleVerifier.cs b/dotnet/eng/verify-samples/SampleVerifier.cs
new file mode 100644
index 0000000000..9dc17b1769
--- /dev/null
+++ b/dotnet/eng/verify-samples/SampleVerifier.cs
@@ -0,0 +1,202 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using OpenAI.Chat;
+
+namespace VerifySamples;
+
+///
+/// Verifies sample output using deterministic checks and an AI agent
+/// for non-deterministic output validation.
+///
+internal sealed class SampleVerifier
+{
+ private readonly AIAgent? _verifierAgent;
+
+ ///
+ /// Creates a verifier. If is provided,
+ /// AI-based verification is available for non-deterministic samples.
+ ///
+ public SampleVerifier(ChatClient? chatClient = null)
+ {
+ if (chatClient is not null)
+ {
+ this._verifierAgent = chatClient.AsAIAgent(
+ instructions: """
+ You are a test output verifier. You will be given:
+ 1. The actual stdout output of a program
+ 2. A list of expectations about what the output should contain or demonstrate
+
+ Your job is to determine whether the actual output satisfies each expectation.
+ Be reasonable — the output comes from an LLM so exact wording won't match, but the
+ semantic intent should be clearly satisfied.
+ """,
+ name: "OutputVerifier");
+ }
+ }
+
+ ///
+ /// Verifies the output of a sample run against its definition.
+ ///
+ public async Task VerifyAsync(SampleDefinition sample, SampleRunResult run)
+ {
+ var failures = new List();
+
+ // 1. Exit code check
+ if (run.ExitCode != 0)
+ {
+ failures.Add($"Exit code was {run.ExitCode}, expected 0. Stderr: {Truncate(run.Stderr, 500)}");
+ }
+
+ // 2. Must-contain checks
+ foreach (var expected in sample.MustContain)
+ {
+ if (!run.Stdout.Contains(expected, StringComparison.Ordinal))
+ {
+ failures.Add($"Output missing expected substring: \"{expected}\"");
+ }
+ }
+
+ // 3. Must-not-contain checks
+ foreach (var unexpected in sample.MustNotContain)
+ {
+ if (run.Stdout.Contains(unexpected, StringComparison.Ordinal))
+ {
+ failures.Add($"Output contains unexpected substring: \"{unexpected}\"");
+ }
+ }
+
+ // 4. AI verification for non-deterministic samples
+ string? aiReasoning = null;
+ if (!sample.IsDeterministic && sample.ExpectedOutputDescription.Length > 0)
+ {
+ if (this._verifierAgent is null)
+ {
+ failures.Add("AI verification required but no AI agent configured (missing AZURE_OPENAI_ENDPOINT).");
+ }
+ else
+ {
+ var aiResult = await this.VerifyWithAIAsync(run.Stdout, sample.ExpectedOutputDescription);
+ aiReasoning = aiResult.Reasoning;
+
+ foreach (var unmet in aiResult.UnmetExpectations)
+ {
+ failures.Add($"AI expectation not met: {unmet}");
+ }
+ }
+ }
+
+ bool passed = failures.Count == 0;
+ return new VerificationResult
+ {
+ SampleName = sample.Name,
+ Passed = passed,
+ Summary = passed ? "All checks passed" : $"{failures.Count} check(s) failed",
+ Failures = failures,
+ AIReasoning = aiReasoning,
+ };
+ }
+
+ private async Task<(string Reasoning, List UnmetExpectations)> VerifyWithAIAsync(
+ string actualOutput,
+ string[] expectations)
+ {
+ var expectationList = string.Join("\n", expectations.Select((e, i) => $" {i + 1}. {e}"));
+ var prompt = $"""
+ Actual program output:
+ ---
+ {Truncate(actualOutput, 4000)}
+ ---
+
+ Expectations to verify:
+ {expectationList}
+
+ Does the output satisfy all expectations?
+ """;
+
+ try
+ {
+ var response = await this._verifierAgent!.RunAsync(prompt);
+ var result = response.Result;
+
+ if (result is null)
+ {
+ return ($"AI verification returned null result. Raw: {response.Text}", ["AI verification returned null result."]);
+ }
+
+ var reasoning = result.Reasoning ?? "(no reasoning provided)";
+
+ // Collect unmet expectations as individual failures
+ var unmet = new List();
+ if (result.ExpectationResults is { Count: > 0 })
+ {
+ foreach (var er in result.ExpectationResults.Where(er => !er.Met))
+ {
+ var detail = string.IsNullOrWhiteSpace(er.Detail) ? er.Expectation : $"{er.Expectation} — {er.Detail}";
+ unmet.Add(detail ?? "Unknown expectation");
+ }
+
+ // If the model flagged overall failure but all individual expectations were met,
+ // still treat as failure using the overall reasoning.
+ if (unmet.Count == 0 && !result.Pass)
+ {
+ unmet.Add(reasoning);
+ }
+ }
+ else if (!result.Pass)
+ {
+ // Fallback: no per-expectation detail but overall pass is false
+ unmet.Add(reasoning);
+ }
+
+ return (reasoning, unmet);
+ }
+ catch (Exception ex)
+ {
+ return ($"AI verification error: {ex.Message}", [$"AI verification error: {ex.Message}"]);
+ }
+ }
+
+ private static string Truncate(string text, int maxLength)
+ => text.Length <= maxLength ? text : text[..maxLength] + "... (truncated)";
+}
+
+///
+/// Structured response from the AI verification agent.
+///
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by JSON deserialization via RunAsync.")]
+internal sealed class AIVerificationResponse
+{
+ /// Whether all expectations were met.
+ [JsonPropertyName("pass")]
+ public bool Pass { get; set; }
+
+ /// Brief explanation of the overall assessment.
+ [JsonPropertyName("reasoning")]
+ public string? Reasoning { get; set; }
+
+ /// Per-expectation results.
+ [JsonPropertyName("expectation_results")]
+ public List? ExpectationResults { get; set; }
+}
+
+///
+/// Result for an individual expectation check.
+///
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by JSON deserialization via RunAsync.")]
+internal sealed class ExpectationResult
+{
+ /// The expectation text that was evaluated.
+ [JsonPropertyName("expectation")]
+ public string? Expectation { get; set; }
+
+ /// Whether this expectation was met.
+ [JsonPropertyName("met")]
+ public bool Met { get; set; }
+
+ /// Detail about how the expectation was or was not met.
+ [JsonPropertyName("detail")]
+ public string? Detail { get; set; }
+}
diff --git a/dotnet/eng/verify-samples/VerificationOrchestrator.cs b/dotnet/eng/verify-samples/VerificationOrchestrator.cs
new file mode 100644
index 0000000000..1ce805bc5a
--- /dev/null
+++ b/dotnet/eng/verify-samples/VerificationOrchestrator.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Concurrent;
+
+namespace VerifySamples;
+
+///
+/// Orchestrates sample verification: filters, runs in parallel, and collects results.
+///
+internal sealed class VerificationOrchestrator
+{
+ private readonly SampleVerifier _verifier;
+ private readonly ConsoleReporter _reporter;
+ private readonly LogFileWriter? _logWriter;
+ private readonly string _dotnetRoot;
+ private readonly TimeSpan _timeout;
+
+ public VerificationOrchestrator(
+ SampleVerifier verifier,
+ ConsoleReporter reporter,
+ string dotnetRoot,
+ TimeSpan timeout,
+ LogFileWriter? logWriter = null)
+ {
+ this._verifier = verifier;
+ this._reporter = reporter;
+ this._logWriter = logWriter;
+ this._dotnetRoot = dotnetRoot;
+ this._timeout = timeout;
+ }
+
+ ///
+ /// The result of running all samples through the orchestrator.
+ ///
+ internal sealed record RunAllResult(
+ ConcurrentDictionary Results,
+ List<(string Name, string Reason)> Skipped,
+ List SampleOrder);
+
+ ///
+ /// Filters samples, runs the runnable ones in parallel, and returns all results.
+ ///
+ public async Task RunAllAsync(
+ IReadOnlyList samples,
+ int maxParallelism)
+ {
+ var skipped = new List<(string Name, string Reason)>();
+ var runnableSamples = new List();
+ var sampleOrder = new List();
+
+ // Separate samples into skipped and runnable
+ foreach (var sample in samples)
+ {
+ sampleOrder.Add(sample.Name);
+
+ if (sample.SkipReason is not null)
+ {
+ skipped.Add((sample.Name, sample.SkipReason));
+ this._reporter.WriteLineWithPrefix(sample.Name, $"SKIPPED — {sample.SkipReason}", ConsoleColor.Yellow);
+
+ if (this._logWriter is not null)
+ {
+ await this._logWriter.WriteSkippedAsync(sample.Name, sample.SkipReason);
+ }
+
+ continue;
+ }
+
+ var missingRequired = sample.RequiredEnvironmentVariables
+ .Where(v => string.IsNullOrEmpty(Environment.GetEnvironmentVariable(v)))
+ .ToList();
+
+ var missingOptional = sample.OptionalEnvironmentVariables
+ .Where(v => string.IsNullOrEmpty(Environment.GetEnvironmentVariable(v)))
+ .ToList();
+
+ if (missingRequired.Count > 0 || missingOptional.Count > 0)
+ {
+ var reasons = new List();
+ if (missingRequired.Count > 0)
+ {
+ reasons.Add($"Missing required: {string.Join(", ", missingRequired)}");
+ }
+
+ if (missingOptional.Count > 0)
+ {
+ reasons.Add($"Missing optional (would cause console prompt hang): {string.Join(", ", missingOptional)}");
+ }
+
+ var skipReason = string.Join("; ", reasons);
+ skipped.Add((sample.Name, skipReason));
+ this._reporter.WriteLineWithPrefix(sample.Name, $"SKIPPED — {skipReason}", ConsoleColor.Yellow);
+
+ if (this._logWriter is not null)
+ {
+ await this._logWriter.WriteSkippedAsync(sample.Name, skipReason);
+ }
+
+ continue;
+ }
+
+ runnableSamples.Add(sample);
+ }
+
+ // Run samples in parallel
+ var results = new ConcurrentDictionary();
+ var semaphore = new SemaphoreSlim(maxParallelism);
+
+ this._reporter.WriteLineWithPrefix(
+ "runner", $"Running {runnableSamples.Count} samples (max {maxParallelism} parallel)...");
+
+ try
+ {
+ var tasks = runnableSamples.Select(sample => this.RunSingleAsync(sample, results, semaphore)).ToArray();
+ await Task.WhenAll(tasks);
+ }
+ finally
+ {
+ semaphore.Dispose();
+ }
+
+ return new RunAllResult(results, skipped, sampleOrder);
+ }
+
+ private async Task RunSingleAsync(
+ SampleDefinition sample,
+ ConcurrentDictionary results,
+ SemaphoreSlim semaphore)
+ {
+ await semaphore.WaitAsync();
+ try
+ {
+ var log = new List();
+ log.Add($"[{sample.Name}] Running...");
+ this._reporter.WriteLineWithPrefix(sample.Name, "Running...");
+
+ var projectPath = Path.Combine(this._dotnetRoot, sample.ProjectPath);
+ var run = sample.Inputs.Length > 0
+ ? await SampleRunner.RunAsync(projectPath, this._timeout, sample.Inputs, sample.InputDelayMs)
+ : await SampleRunner.RunAsync(projectPath, this._timeout);
+
+ log.Add($"[{sample.Name}] Completed ({run.Elapsed.TotalSeconds:F1}s, exit={run.ExitCode})");
+ this._reporter.WriteLineWithPrefix(
+ sample.Name, $"Completed ({run.Elapsed.TotalSeconds:F1}s, exit={run.ExitCode}). Verifying...");
+
+ var result = await this._verifier.VerifyAsync(sample, run);
+
+ if (result.Passed)
+ {
+ log.Add($"[{sample.Name}] PASSED");
+ this._reporter.WriteLineWithPrefix(sample.Name, "PASSED", ConsoleColor.Green);
+ }
+ else
+ {
+ log.Add($"[{sample.Name}] FAILED");
+ this._reporter.WriteLineWithPrefix(sample.Name, "FAILED", ConsoleColor.Red);
+ foreach (var failure in result.Failures)
+ {
+ log.Add($"[{sample.Name}] ✗ {failure}");
+ this._reporter.WriteLineWithPrefix(sample.Name, $" ✗ {failure}", ConsoleColor.Red);
+ }
+ }
+
+ if (result.AIReasoning is not null)
+ {
+ log.Add($"[{sample.Name}] AI: {result.AIReasoning}");
+ this._reporter.WriteLineWithPrefix(
+ sample.Name, $" AI: {Truncate(result.AIReasoning, 300)}", ConsoleColor.DarkGray);
+ }
+
+ var verificationResult = new VerificationResult
+ {
+ SampleName = result.SampleName,
+ Passed = result.Passed,
+ Summary = result.Summary,
+ Failures = result.Failures,
+ AIReasoning = result.AIReasoning,
+ Stdout = run.Stdout,
+ Stderr = run.Stderr,
+ LogLines = log,
+ };
+ results[sample.Name] = verificationResult;
+
+ if (this._logWriter is not null)
+ {
+ await this._logWriter.WriteSampleResultAsync(verificationResult);
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ private static string Truncate(string text, int maxLength)
+ => text.Length <= maxLength ? text : text[..maxLength] + "...";
+}
diff --git a/dotnet/eng/verify-samples/VerificationResult.cs b/dotnet/eng/verify-samples/VerificationResult.cs
new file mode 100644
index 0000000000..50a08f969e
--- /dev/null
+++ b/dotnet/eng/verify-samples/VerificationResult.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// The result of verifying a single sample.
+///
+internal sealed class VerificationResult
+{
+ public required string SampleName { get; init; }
+ public required bool Passed { get; init; }
+ public required string Summary { get; init; }
+ public List Failures { get; init; } = [];
+ public string? AIReasoning { get; init; }
+
+ ///
+ /// The sample's stdout output, captured for log file output.
+ ///
+ public string? Stdout { get; init; }
+
+ ///
+ /// The sample's stderr output, captured for log file output.
+ ///
+ public string? Stderr { get; init; }
+
+ ///
+ /// Per-sample log lines, buffered during parallel execution
+ /// and written sequentially to the log file.
+ ///
+ public List LogLines { get; init; } = [];
+}
diff --git a/dotnet/eng/verify-samples/VerifyOptions.cs b/dotnet/eng/verify-samples/VerifyOptions.cs
new file mode 100644
index 0000000000..c4e3cd1f59
--- /dev/null
+++ b/dotnet/eng/verify-samples/VerifyOptions.cs
@@ -0,0 +1,124 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// Parsed command-line options for the sample verification tool.
+///
+internal sealed class VerifyOptions
+{
+ ///
+ /// Maximum number of samples to run concurrently.
+ ///
+ public int MaxParallelism { get; init; } = 8;
+
+ ///
+ /// Path to write a CSV summary file, or null to skip.
+ ///
+ public string? CsvFilePath { get; init; }
+
+ ///
+ /// Path to write a sequential log file, or null to skip.
+ ///
+ public string? LogFilePath { get; init; }
+
+ ///
+ /// The filtered list of samples to process.
+ ///
+ public required IReadOnlyList Samples { get; init; }
+
+ ///
+ /// All known sample set registries, keyed by category name.
+ ///
+ private static readonly Dictionary> s_sampleSets =
+ new(StringComparer.OrdinalIgnoreCase)
+ {
+ ["01-get-started"] = GetStartedSamples.All,
+ ["02-agents"] = AgentsSamples.All,
+ ["03-workflows"] = WorkflowSamples.All,
+ };
+
+ ///
+ /// Parses command-line arguments and resolves the sample list.
+ /// Returns null and writes to stderr if the arguments are invalid.
+ ///
+ public static VerifyOptions? Parse(string[] args)
+ {
+ var argList = args.ToList();
+
+ var categoryFilter = ExtractArg(argList, "--category");
+ var logFilePath = ExtractArg(argList, "--log");
+ var csvFilePath = ExtractArg(argList, "--csv");
+
+ int maxParallelism = 8;
+ var parallelArg = ExtractArg(argList, "--parallel");
+ if (parallelArg is not null && int.TryParse(parallelArg, out var p) && p > 0)
+ {
+ maxParallelism = p;
+ }
+
+ HashSet? nameFilter = null;
+ if (argList.Count > 0)
+ {
+ nameFilter = argList.ToHashSet(StringComparer.OrdinalIgnoreCase);
+ }
+
+ // Build the sample list
+ IReadOnlyList samples;
+ if (categoryFilter is not null)
+ {
+ if (!s_sampleSets.TryGetValue(categoryFilter, out var categoryList))
+ {
+ Console.Error.WriteLine(
+ $"Unknown category '{categoryFilter}'. Available: {string.Join(", ", s_sampleSets.Keys)}");
+ return null;
+ }
+
+ samples = categoryList;
+ }
+ else
+ {
+ samples = s_sampleSets.Values.SelectMany(s => s).ToList();
+ }
+
+ if (nameFilter is not null)
+ {
+ samples = samples.Where(s => nameFilter.Contains(s.Name)).ToList();
+ }
+
+ if (samples.Count == 0)
+ {
+ var allNames = s_sampleSets.Values.SelectMany(s => s).Select(s => s.Name);
+ Console.Error.WriteLine($"No matching samples found. Available: {string.Join(", ", allNames)}");
+ return null;
+ }
+
+ return new VerifyOptions
+ {
+ MaxParallelism = maxParallelism,
+ LogFilePath = logFilePath,
+ CsvFilePath = csvFilePath,
+ Samples = samples,
+ };
+ }
+
+ private static string? ExtractArg(List list, string flag)
+ {
+ var idx = list.IndexOf(flag);
+ if (idx < 0)
+ {
+ return null;
+ }
+
+ if (idx + 1 >= list.Count)
+ {
+ Console.Error.WriteLine($"Missing value for {flag}.");
+ list.RemoveAt(idx);
+ return null;
+ }
+
+ var value = list[idx + 1];
+ list.RemoveRange(idx, 2);
+ return value;
+ }
+}
diff --git a/dotnet/eng/verify-samples/WorkflowSamples.cs b/dotnet/eng/verify-samples/WorkflowSamples.cs
new file mode 100644
index 0000000000..2842f4af89
--- /dev/null
+++ b/dotnet/eng/verify-samples/WorkflowSamples.cs
@@ -0,0 +1,525 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace VerifySamples;
+
+///
+/// Defines the expected behavior for each sample in 03-workflows.
+///
+internal static class WorkflowSamples
+{
+ public static IReadOnlyList All { get; } =
+ [
+ // ───────────────────────────────────────────────────────────────────
+ // _StartHere
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_01_Streaming",
+ ProjectPath = "samples/03-workflows/_StartHere/01_Streaming",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "UppercaseExecutor: HELLO, WORLD!",
+ "ReverseTextExecutor: !DLROW ,OLLEH",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_02_AgentsInWorkflows",
+ ProjectPath = "samples/03-workflows/_StartHere/02_AgentsInWorkflows",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show agent responses from a translation workflow.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_03_AgentWorkflowPatterns",
+ ProjectPath = "samples/03-workflows/_StartHere/03_AgentWorkflowPatterns",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ Inputs = ["sequential"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription =
+ [
+ "The output should show a sequential workflow pattern with multiple agents executing tasks in order.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_04_MultiModelService",
+ ProjectPath = "samples/03-workflows/_StartHere/04_MultiModelService",
+ RequiredEnvironmentVariables = ["BEDROCK_ACCESS_KEY", "BEDROCK_SECRET_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
+ SkipReason = "Requires multiple external provider API keys (Bedrock, Anthropic, OpenAI).",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_05_SubWorkflows",
+ ProjectPath = "samples/03-workflows/_StartHere/05_SubWorkflows",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "=== Sub-Workflow Demonstration ===",
+ "Final Output:",
+ "=== Main Workflow Completed ===",
+ "Sample Complete: Workflows can be composed hierarchically using sub-workflows",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_06_MixedWorkflowAgentsAndExecutors",
+ ProjectPath = "samples/03-workflows/_StartHere/06_MixedWorkflowAgentsAndExecutors",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ Inputs = ["What is 2 plus 2?"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription =
+ [
+ "The output should show agents and executors working together to process a user question.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_StartHere_07_WriterCriticWorkflow",
+ ProjectPath = "samples/03-workflows/_StartHere/07_WriterCriticWorkflow",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = ["=== Writer-Critic Iteration Workflow ==="],
+ ExpectedOutputDescription =
+ [
+ "The output should show a writer-critic iteration workflow with writer and critic sections.",
+ "The critic should either approve or request revisions.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Agents
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Agents_CustomAgentExecutors",
+ ProjectPath = "samples/03-workflows/Agents/CustomAgentExecutors",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show custom workflow events including slogan generation and feedback.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Agents_FoundryAgent",
+ ProjectPath = "samples/03-workflows/Agents/FoundryAgent",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ SkipReason = "Requires Azure AI Foundry project endpoint.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Agents_GroupChatToolApproval",
+ ProjectPath = "samples/03-workflows/Agents/GroupChatToolApproval",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ MustContain = ["Starting group chat workflow for software deployment..."],
+ ExpectedOutputDescription =
+ [
+ "The output should show a group chat workflow with QA and DevOps agents for software deployment.",
+ "There should be approval requests for tool calls.",
+ "The workflow should show interaction between QA and DevOps agents toward deployment.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Agents_WorkflowAsAnAgent",
+ ProjectPath = "samples/03-workflows/Agents/WorkflowAsAnAgent",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ Inputs = ["hello", "exit"],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription =
+ [
+ "The output should show a conversational workflow responding to the user's hello message.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Checkpoint
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Checkpoint_CheckpointAndRehydrate",
+ ProjectPath = "samples/03-workflows/Checkpoint/CheckpointAndRehydrate",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "Workflow completed with result:",
+ "Number of checkpoints created:",
+ "Hydrating a new workflow instance from the 6th checkpoint.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Checkpoint_CheckpointAndResume",
+ ProjectPath = "samples/03-workflows/Checkpoint/CheckpointAndResume",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "Workflow completed with result:",
+ "Number of checkpoints created:",
+ "Restoring from the 6th checkpoint.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Checkpoint_CheckpointWithHumanInTheLoop",
+ ProjectPath = "samples/03-workflows/Checkpoint/CheckpointWithHumanInTheLoop",
+ RequiredEnvironmentVariables = [],
+ Inputs = ["50", "25", "40", "45", "42", "50", "25", "40", "45", "42"],
+ InputDelayMs = 1000,
+ MustContain = ["found in"],
+ ExpectedOutputDescription =
+ [
+ "The output should show a number guessing game with higher/lower hints that eventually reaches the correct number.",
+ "The output should demonstrate checkpoint save and restore behavior.",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Concurrent
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Concurrent_Concurrent",
+ ProjectPath = "samples/03-workflows/Concurrent/Concurrent",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show results from concurrent agent processing.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Concurrent_MapReduce",
+ ProjectPath = "samples/03-workflows/Concurrent/MapReduce",
+ RequiredEnvironmentVariables = [],
+ MustContain =
+ [
+ "=== RUNNING WORKFLOW ===",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // ConditionalEdges
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_ConditionalEdges_01_EdgeCondition",
+ ProjectPath = "samples/03-workflows/ConditionalEdges/01_EdgeCondition",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show an email being classified as spam or not spam and processed accordingly.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_ConditionalEdges_02_SwitchCase",
+ ProjectPath = "samples/03-workflows/ConditionalEdges/02_SwitchCase",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show an ambiguous email being classified as spam, not spam, or uncertain.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_ConditionalEdges_03_MultiSelection",
+ ProjectPath = "samples/03-workflows/ConditionalEdges/03_MultiSelection",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ ExpectedOutputDescription =
+ [
+ "The output should show an email being classified and potentially routed to multiple handlers.",
+ "The output should not contain error messages or stack traces.",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // HumanInTheLoop
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_HumanInTheLoop_Basic",
+ ProjectPath = "samples/03-workflows/HumanInTheLoop/HumanInTheLoopBasic",
+ RequiredEnvironmentVariables = [],
+ Inputs = ["50", "25", "40", "45", "42"],
+ InputDelayMs = 1000,
+ MustContain = ["found in"],
+ ExpectedOutputDescription =
+ [
+ "The output should show a number guessing game with higher/lower hints that eventually reaches the correct number 42.",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Loop
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Loop",
+ ProjectPath = "samples/03-workflows/Loop",
+ RequiredEnvironmentVariables = [],
+ MustContain = ["Result:"],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // SharedStates
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_SharedStates",
+ ProjectPath = "samples/03-workflows/SharedStates",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "Total Paragraphs:",
+ "Total Words:",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Visualization
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Visualization",
+ ProjectPath = "samples/03-workflows/Visualization",
+ RequiredEnvironmentVariables = [],
+ IsDeterministic = true,
+ MustContain =
+ [
+ "Generating workflow visualization...",
+ "Mermaid string:",
+ "DiGraph string:",
+ ],
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Observability
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Observability_ApplicationInsights",
+ ProjectPath = "samples/03-workflows/Observability/ApplicationInsights",
+ RequiredEnvironmentVariables = ["APPLICATIONINSIGHTS_CONNECTION_STRING"],
+ SkipReason = "Requires Application Insights connection string.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Observability_AspireDashboard",
+ ProjectPath = "samples/03-workflows/Observability/AspireDashboard",
+ RequiredEnvironmentVariables = [],
+ SkipReason = "Requires Aspire Dashboard / OTLP endpoint.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Observability_WorkflowAsAnAgent",
+ ProjectPath = "samples/03-workflows/Observability/WorkflowAsAnAgent",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
+ SkipReason = "Interactive console with ReadLine loop; requires OTLP endpoint.",
+ },
+
+ // ───────────────────────────────────────────────────────────────────
+ // Declarative
+ // ───────────────────────────────────────────────────────────────────
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_ConfirmInput",
+ ProjectPath = "samples/03-workflows/Declarative/ConfirmInput",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ Inputs = ["hello", "hello"],
+ InputDelayMs = 8000,
+ ExpectedOutputDescription = ["The output should show a confirmation prompt and a user response."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_CustomerSupport",
+ ProjectPath = "samples/03-workflows/Declarative/CustomerSupport",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["My laptop won't start"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription = ["The output should show a customer support workflow processing a laptop issue, with agent responses providing troubleshooting or support."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_DeepResearch",
+ ProjectPath = "samples/03-workflows/Declarative/DeepResearch",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ SkipReason = "Requires external weather API (wttr.in).",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_ExecuteCode",
+ ProjectPath = "samples/03-workflows/Declarative/ExecuteCode",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ Inputs = ["What is 12 * 34?"],
+ InputDelayMs = 5000,
+ ExpectedOutputDescription = ["The output should show a declarative workflow executing generated code, processing a math question and producing a result."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_ExecuteWorkflow",
+ ProjectPath = "samples/03-workflows/Declarative/ExecuteWorkflow",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ SkipReason = "Requires a workflow file path as a CLI argument.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_FunctionTools",
+ ProjectPath = "samples/03-workflows/Declarative/FunctionTools",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["What are today's specials?", "EXIT"],
+ InputDelayMs = 8000,
+ ExpectedOutputDescription = ["The output should show a workflow calling function tools (e.g. a menu plugin) to answer a question about restaurant specials."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_GenerateCode",
+ ProjectPath = "samples/03-workflows/Declarative/GenerateCode",
+ IsDeterministic = true,
+ MustContain = ["WORKFLOW: Parsing", "WORKFLOW: Defined"],
+ ExpectedOutputDescription = ["The output should show a YAML workflow being parsed and C# code being generated from it."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_HostedWorkflow",
+ ProjectPath = "samples/03-workflows/Declarative/HostedWorkflow",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ SkipReason = "Hosts a persistent workflow server that does not exit.",
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_InputArguments",
+ ProjectPath = "samples/03-workflows/Declarative/InputArguments",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["I'd like to visit Seattle", "EXIT"],
+ InputDelayMs = 8000,
+ ExpectedOutputDescription = ["The output should show a workflow capturing location input and providing travel-related information about Seattle."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_InvokeFunctionTool",
+ ProjectPath = "samples/03-workflows/Declarative/InvokeFunctionTool",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["What's the soup of the day?", "EXIT"],
+ InputDelayMs = 8000,
+ ExpectedOutputDescription = ["The output should show a workflow invoking a function tool (e.g. a menu plugin) to answer a question about the soup of the day."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_InvokeMcpTool",
+ ProjectPath = "samples/03-workflows/Declarative/InvokeMcpTool",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Search for .NET tutorials on Microsoft Learn"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription = ["The output should show a workflow using MCP tools to search Microsoft Learn documentation and provide a summary of results."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_Marketing",
+ ProjectPath = "samples/03-workflows/Declarative/Marketing",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["A smart water bottle that tracks hydration"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription = ["The output should show a marketing workflow generating content about a smart water bottle product."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_StudentTeacher",
+ ProjectPath = "samples/03-workflows/Declarative/StudentTeacher",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["What is 18 + 27?"],
+ InputDelayMs = 3000,
+ ExpectedOutputDescription = ["The output should show a student-teacher workflow where a student asks a math question and a teacher provides the answer."],
+ },
+
+ new SampleDefinition
+ {
+ Name = "Workflow_Declarative_ToolApproval",
+ ProjectPath = "samples/03-workflows/Declarative/ToolApproval",
+ RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
+ Inputs = ["Search for .NET tutorials", "EXIT"],
+ InputDelayMs = 8000,
+ ExpectedOutputDescription = ["The output should show a workflow using an MCP tool with approval to search Microsoft Learn, followed by an exit from the input loop."],
+ },
+ ];
+}
diff --git a/dotnet/eng/verify-samples/verify-samples.csproj b/dotnet/eng/verify-samples/verify-samples.csproj
new file mode 100644
index 0000000000..f7f86ba90d
--- /dev/null
+++ b/dotnet/eng/verify-samples/verify-samples.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ false
+ false
+
+ $(NoWarn);CA2007
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentOpenTelemetry/Program.cs b/dotnet/samples/02-agents/AgentOpenTelemetry/Program.cs
index 8e1f4245b6..83445dc1fc 100644
--- a/dotnet/samples/02-agents/AgentOpenTelemetry/Program.cs
+++ b/dotnet/samples/02-agents/AgentOpenTelemetry/Program.cs
@@ -23,7 +23,7 @@ const string SourceName = "OpenTelemetryAspire.ConsoleApp";
const string ServiceName = "AgentOpenTelemetry";
// Configure OpenTelemetry for Aspire dashboard
-var otlpEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318";
+var otlpEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4317";
var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_Anthropic/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_Anthropic/README.md
index c1a569874b..3be31187e0 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_Anthropic/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_Anthropic/README.md
@@ -5,8 +5,8 @@ This sample demonstrates how to create an AIAgent using Anthropic Claude models
The sample supports three deployment scenarios:
1. **Anthropic Public API** - Direct connection to Anthropic's public API
-2. **Azure Foundry with API Key** - Anthropic models deployed through Azure Foundry using API key authentication
-3. **Azure Foundry with Azure CLI** - Anthropic models deployed through Azure Foundry using Azure CLI credentials
+2. **Microsoft Foundry with API Key** - Anthropic models deployed through Microsoft Foundry using API key authentication
+3. **Microsoft Foundry with Azure CLI** - Anthropic models deployed through Microsoft Foundry using Azure CLI credentials
## Prerequisites
@@ -25,29 +25,29 @@ $env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic A
$env:ANTHROPIC_CHAT_MODEL_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
```
-### For Azure Foundry with API Key
+### For Microsoft Foundry with API Key
-- Azure Foundry service endpoint and deployment configured
+- Microsoft Foundry service endpoint and deployment configured
- Anthropic API key
Set the following environment variables:
```powershell
-$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Azure Foundry resource name (subdomain before .services.ai.azure.com)
+$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Microsoft Foundry resource name (subdomain before .services.ai.azure.com)
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_CHAT_MODEL_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
```
-### For Azure Foundry with Azure CLI
+### For Microsoft Foundry with Azure CLI
-- Azure Foundry service endpoint and deployment configured
+- Microsoft Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
Set the following environment variables:
```powershell
-$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Azure Foundry resource name (subdomain before .services.ai.azure.com)
+$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Microsoft Foundry resource name (subdomain before .services.ai.azure.com)
$env:ANTHROPIC_CHAT_MODEL_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
```
-**Note**: When using Azure Foundry with Azure CLI, make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: When using Microsoft Foundry with Azure CLI, make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs
index 0603933dbf..a4b702be2c 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs
@@ -2,7 +2,7 @@
#pragma warning disable CS0618 // Type or member is obsolete - sample uses deprecated PersistentAgentsClientExtensions
-// This sample shows how to create and use a simple AI agent with Azure Foundry Agents as the backend.
+// This sample shows how to create and use a simple AI agent with Microsoft Foundry Agents as the backend.
using Azure.AI.Agents.Persistent;
using Azure.Identity;
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
index 969795d87f..7c92d809d3 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
@@ -13,14 +13,14 @@ Below is a comparison between the classic and new Foundry Agents approaches:
Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
-- Azure Foundry service endpoint and deployment configured
+- Microsoft Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Azure Foundry resource endpoint
+$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj
index a8deaa57b5..562ce0c37e 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs
index b2cd14f68a..355f4e1380 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs
@@ -1,19 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
-// This sample shows how to create and use a AI agents with Azure Foundry Agents as the backend.
+// This sample shows how to create and use AI agents with Microsoft Foundry Agents as the backend.
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerName = "JokerAgent";
-// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
+// Get a client to create/retrieve/delete server side agents with Microsoft Foundry Agents.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md
index 66fcbf8297..ec688df59d 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md
@@ -13,14 +13,14 @@ Below is a comparison between the classic and new Foundry Agents approaches:
Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
-- Azure Foundry service endpoint and deployment configured
+- Microsoft Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Azure Foundry resource endpoint
+$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs
index fe682d388a..556b52bf17 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
-// This sample shows how to use the OpenAI SDK to create and use a simple AI agent with any model hosted in Azure AI Foundry.
-// You could use models from Microsoft, OpenAI, DeepSeek, Hugging Face, Meta, xAI or any other model you have deployed in your Azure AI Foundry resource.
+// This sample shows how to use the OpenAI SDK to create and use a simple AI agent with any model hosted in Microsoft Foundry.
+// You could use models from Microsoft, OpenAI, DeepSeek, Hugging Face, Meta, xAI or any other model you have deployed in your Microsoft Foundry resource.
// Note: Ensure that you pick a model that suits your needs. For example, if you want to use function calling, ensure that the model you pick supports function calling.
using System.ClientModel;
@@ -15,7 +15,7 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
var model = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "Phi-4-mini-instruct";
-// Since we are using the OpenAI Client SDK, we need to override the default endpoint to point to Azure Foundry.
+// Since we are using the OpenAI Client SDK, we need to override the default endpoint to point to Microsoft Foundry.
var clientOptions = new OpenAIClientOptions() { Endpoint = new Uri(endpoint) };
// Create the OpenAI client with either an API key or Azure CLI credential.
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md
index 6d5b6badd7..9bc4d60881 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md
@@ -1,8 +1,8 @@
## Overview
-This sample shows how to use the OpenAI SDK to create and use a simple AI agent with any model hosted in Azure AI Foundry.
+This sample shows how to use the OpenAI SDK to create and use a simple AI agent with any model hosted in Microsoft Foundry.
-You could use models from Microsoft, OpenAI, DeepSeek, Hugging Face, Meta, xAI or any other model you have deployed in Azure AI Foundry.
+You could use models from Microsoft, OpenAI, DeepSeek, Hugging Face, Meta, xAI or any other model you have deployed in Microsoft Foundry.
**Note**: Ensure that you pick a model that suits your needs. For example, if you want to use function calling, ensure that the model you pick supports function calling.
@@ -11,19 +11,19 @@ You could use models from Microsoft, OpenAI, DeepSeek, Hugging Face, Meta, xAI o
Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
-- Azure AI Foundry resource
-- A model deployment in your Azure AI Foundry resource. This example defaults to using the `Phi-4-mini-instruct` model,
+- Microsoft Foundry resource
+- A model deployment in your Microsoft Foundry resource. This example defaults to using the `Phi-4-mini-instruct` model,
so if you want to use a different model, ensure that you set your `AZURE_AI_MODEL_DEPLOYMENT_NAME` environment
variable to the name of your deployed model.
-- An API key or role based authentication to access the Azure AI Foundry resource
+- An API key or role based authentication to access the Microsoft Foundry resource
See [here](https://learn.microsoft.com/en-us/azure/ai-foundry/quickstarts/get-started-code?tabs=csharp) for more info on setting up these prerequisites
Set the following environment variables:
```powershell
-# Replace with your Azure AI Foundry resource endpoint
-# Ensure that you have the "/openai/v1/" path in the URL, since this is required when using the OpenAI SDK to access Azure Foundry models.
+# Replace with your Microsoft Foundry resource endpoint
+# Ensure that you have the "/openai/v1/" path in the URL, since this is required when using the OpenAI SDK to access Microsoft Foundry models.
$env:AZURE_OPENAI_ENDPOINT="https://ai-foundry-.services.ai.azure.com/openai/v1/"
# Optional, defaults to using Azure CLI for authentication if not provided
diff --git a/dotnet/samples/02-agents/AgentProviders/README.md b/dotnet/samples/02-agents/AgentProviders/README.md
index 071722d50d..ee83f8c08c 100644
--- a/dotnet/samples/02-agents/AgentProviders/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/README.md
@@ -18,7 +18,7 @@ See the README.md for each sample for the prerequisites for that sample.
|[Creating an AIAgent with Anthropic](./Agent_With_Anthropic/)|This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Agents.Persistent](./Agent_With_AzureAIAgentsPersistent/)|This sample demonstrates how to create a Foundry Persistent agent and expose it as an AIAgent using the Azure.AI.Agents.Persistent SDK|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Project](./Agent_With_AzureAIProject/)|This sample demonstrates how to create an Foundry Project agent and expose it as an AIAgent using the Azure.AI.Project SDK|
-|[Creating an AIAgent with AzureFoundry Model](./Agent_With_AzureFoundryModel/)|This sample demonstrates how to use any model deployed to Azure Foundry to create an AIAgent|
+|[Creating an AIAgent with Foundry Model](./Agent_With_AzureFoundryModel/)|This sample demonstrates how to use any model deployed to Microsoft Foundry to create an AIAgent|
|[Creating an AIAgent with Azure OpenAI ChatCompletion](./Agent_With_AzureOpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using Azure OpenAI ChatCompletion as the underlying inference service|
|[Creating an AIAgent with Azure OpenAI Responses](./Agent_With_AzureOpenAIResponses/)|This sample demonstrates how to create an AIAgent using Azure OpenAI Responses as the underlying inference service|
|[Creating an AIAgent with a custom implementation](./Agent_With_CustomImplementation/)|This sample demonstrates how to create an AIAgent with a custom implementation|
diff --git a/dotnet/samples/02-agents/AgentWithAnthropic/README.md b/dotnet/samples/02-agents/AgentWithAnthropic/README.md
index 345c25142f..c2de7425d2 100644
--- a/dotnet/samples/02-agents/AgentWithAnthropic/README.md
+++ b/dotnet/samples/02-agents/AgentWithAnthropic/README.md
@@ -18,9 +18,9 @@ Before you begin, ensure you have the following prerequisites:
**Note**: These samples use Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
-## Using Anthropic with Azure Foundry
+## Using Anthropic with Microsoft Foundry
-To use Anthropic with Azure Foundry, you can check the sample [AgentProviders/Agent_With_Anthropic](../AgentProviders/Agent_With_Anthropic/README.md) for more details.
+To use Anthropic with Microsoft Foundry, you can check the sample [AgentProviders/Agent_With_Anthropic](../AgentProviders/Agent_With_Anthropic/README.md) for more details.
## Samples
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj
index 0b6c06a5a8..4c83380f90 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -14,8 +14,7 @@
-
-
+
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs
index d6410d3308..1132e9ef1d 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs
@@ -1,18 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to use the FoundryMemoryProvider to persist and recall memories for an agent.
-// The sample stores conversation messages in an Azure AI Foundry memory store and retrieves relevant
+// The sample stores conversation messages in a Microsoft Foundry memory store and retrieves relevant
// memories for subsequent invocations, even across new sessions.
//
-// Note: Memory extraction in Azure AI Foundry is asynchronous and takes time. This sample demonstrates
+// Note: Memory extraction in Microsoft Foundry is asynchronous and takes time. This sample demonstrates
// a simple polling approach to wait for memory updates to complete before querying.
using System.Text.Json;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
-using Microsoft.Agents.AI.FoundryMemory;
+using Microsoft.Agents.AI.Foundry;
string foundryEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? "memory-store-sample";
@@ -37,7 +36,7 @@ FoundryMemoryProvider memoryProvider = new(
memoryStoreName,
stateInitializer: _ => new(new FoundryMemoryProviderScope("sample-user-123")));
-FoundryAgent agent = projectClient.AsAIAgent(
+ChatClientAgent agent = projectClient.AsAIAgent(
new ChatClientAgentOptions()
{
Name = "TravelAssistantWithFoundryMemory",
@@ -62,7 +61,7 @@ await memoryProvider.EnsureStoredMemoriesDeletedAsync(session);
Console.WriteLine(await agent.RunAsync("Hi there! My name is Taylor and I'm planning a hiking trip to Patagonia in November.", session));
Console.WriteLine(await agent.RunAsync("I'm travelling with my sister and we love finding scenic viewpoints.", session));
-// Memory extraction in Azure AI Foundry is asynchronous and takes time to process.
+// Memory extraction in Microsoft Foundry is asynchronous and takes time to process.
// WhenUpdatesCompletedAsync polls all pending updates and waits for them to complete.
Console.WriteLine("\nWaiting for Foundry Memory to process updates...");
await memoryProvider.WhenUpdatesCompletedAsync();
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md
index bcc70b0103..b2d52e9837 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md
@@ -1,6 +1,6 @@
-# Agent with Memory Using Azure AI Foundry
+# Agent with Memory Using Microsoft Foundry
-This sample demonstrates how to create and run an agent that uses Azure AI Foundry's managed memory service to extract and retrieve individual memories across sessions.
+This sample demonstrates how to create and run an agent that uses Microsoft Foundry's managed memory service to extract and retrieve individual memories across sessions.
## Features Demonstrated
@@ -13,7 +13,7 @@ This sample demonstrates how to create and run an agent that uses Azure AI Found
## Prerequisites
-1. Azure subscription with Azure AI Foundry project
+1. Azure subscription with Microsoft Foundry project
2. Azure OpenAI resource with a chat model deployment (e.g., gpt-4o-mini) and an embedding model deployment (e.g., text-embedding-ada-002)
3. .NET 10.0 SDK
4. Azure CLI logged in (`az login`)
@@ -21,7 +21,7 @@ This sample demonstrates how to create and run an agent that uses Azure AI Found
## Environment Variables
```bash
-# Azure AI Foundry project endpoint and memory store name
+# Microsoft Foundry project endpoint and memory store name
export AZURE_AI_PROJECT_ENDPOINT="https://your-account.services.ai.azure.com/api/projects/your-project"
export AZURE_AI_MEMORY_STORE_ID="my_memory_store"
@@ -48,10 +48,10 @@ The agent will:
## Key Differences from Mem0
-| Aspect | Mem0 | Azure AI Foundry Memory |
+| Aspect | Mem0 | Microsoft Foundry Memory |
|--------|------|------------------------|
| Authentication | API Key | Azure Identity (DefaultAzureCredential) |
| Scope | ApplicationId, UserId, AgentId, ThreadId | Single `Scope` string |
| Memory Types | Single memory store | User Profile + Chat Summary |
-| Hosting | Mem0 cloud or self-hosted | Azure AI Foundry managed service |
+| Hosting | Mem0 cloud or self-hosted | Microsoft Foundry managed service |
| Store Creation | N/A (automatic) | Explicit via `EnsureMemoryStoreCreatedAsync` |
diff --git a/dotnet/samples/02-agents/AgentWithMemory/README.md b/dotnet/samples/02-agents/AgentWithMemory/README.md
index aa95012f68..c7f4510504 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/README.md
+++ b/dotnet/samples/02-agents/AgentWithMemory/README.md
@@ -7,7 +7,7 @@ These samples show how to create an agent with the Agent Framework that uses Mem
|[Chat History memory](./AgentWithMemory_Step01_ChatHistoryMemory/)|This sample demonstrates how to enable an agent to remember messages from previous conversations.|
|[Memory with MemoryStore](./AgentWithMemory_Step02_MemoryUsingMem0/)|This sample demonstrates how to create and run an agent that uses the Mem0 service to extract and retrieve individual memories.|
|[Custom Memory Implementation](../../01-get-started/04_memory/)|This sample demonstrates how to create a custom memory component and attach it to an agent.|
-|[Memory with Azure AI Foundry](./AgentWithMemory_Step04_MemoryUsingFoundry/)|This sample demonstrates how to create and run an agent that uses Azure AI Foundry's managed memory service to extract and retrieve individual memories.|
+|[Memory with Microsoft Foundry](./AgentWithMemory_Step04_MemoryUsingFoundry/)|This sample demonstrates how to create and run an agent that uses Microsoft Foundry's managed memory service to extract and retrieve individual memories.|
|[Bounded Chat History with Overflow](./AgentWithMemory_Step05_BoundedChatHistory/)|This sample demonstrates how to create a bounded chat history provider that overflows older messages to a vector store and recalls them as memories.|
-> **See also**: [Memory Search with Foundry Agents](../AgentsWithFoundry/Agent_Step22_MemorySearch/) - demonstrates using the built-in Memory Search tool with Azure Foundry agents.
+> **See also**: [Memory Search with Foundry Agents](../AgentsWithFoundry/Agent_Step22_MemorySearch/) - demonstrates using the built-in Memory Search tool with Microsoft Foundry agents.
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md
index 131adde82b..ec69136e14 100644
--- a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md
@@ -13,7 +13,7 @@ This sample uses Qdrant for the vector store, but this can easily be swapped out
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource.
- An existing Qdrant instance. You can use a managed service or run a local instance using Docker, but the sample assumes the instance is running locally.
-**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
+**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
**Note**: These samples use Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource and have the `Cognitive Services OpenAI Contributor` role. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj
index d90e1c394b..6a2bd7618c 100644
--- a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs
index e049618a72..7eb5bd5a39 100644
--- a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs
@@ -7,7 +7,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using OpenAI;
using OpenAI.Files;
using OpenAI.Responses;
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj
new file mode 100644
index 0000000000..a25c626323
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj
@@ -0,0 +1,54 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs
new file mode 100644
index 0000000000..5bf0918ab3
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Neo4j.AgentFramework.GraphRAG;
+using Neo4j.Driver;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+var neo4jUri = Environment.GetEnvironmentVariable("NEO4J_URI") ?? throw new InvalidOperationException("NEO4J_URI is not set.");
+var neo4jUsername = Environment.GetEnvironmentVariable("NEO4J_USERNAME") ?? "neo4j";
+var neo4jPassword = Environment.GetEnvironmentVariable("NEO4J_PASSWORD") ?? throw new InvalidOperationException("NEO4J_PASSWORD is not set.");
+var fulltextIndex = Environment.GetEnvironmentVariable("NEO4J_FULLTEXT_INDEX_NAME") ?? "search_chunks";
+
+const string RetrievalQuery = """
+ MATCH (node)-[:FROM_DOCUMENT]->(doc:Document)<-[:FILED]-(company:Company)
+ OPTIONAL MATCH (company)-[:FACES_RISK]->(risk:RiskFactor)
+ WITH node, score, company, doc, collect(DISTINCT risk.name)[0..5] AS risks
+ OPTIONAL MATCH (company)-[:MENTIONS]->(product:Product)
+ WITH node, score, company, doc, risks, collect(DISTINCT product.name)[0..5] AS products
+ RETURN
+ node.text AS text,
+ score,
+ company.name AS company,
+ company.ticker AS ticker,
+ doc.title AS title,
+ risks,
+ products
+ ORDER BY score DESC
+ """;
+
+await using var driver = GraphDatabase.Driver(new Uri(neo4jUri), AuthTokens.Basic(neo4jUsername, neo4jPassword));
+await driver.VerifyConnectivityAsync();
+
+await using var provider = new Neo4jContextProvider(
+ driver,
+ new Neo4jContextProviderOptions
+ {
+ IndexName = fulltextIndex,
+ IndexType = IndexType.Fulltext,
+ RetrievalQuery = RetrievalQuery,
+ TopK = 5,
+ ContextPrompt = "Use the retrieved Neo4j graph context to answer accurately and call out when context is missing."
+ });
+
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+AIAgent agent = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new DefaultAzureCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient()
+ .AsAIAgent(new ChatClientAgentOptions
+ {
+ ChatOptions = new()
+ {
+ Instructions = "You are a helpful assistant that answers questions using Neo4j graph context."
+ },
+ AIContextProviders = [provider]
+ });
+
+AgentSession session = await agent.CreateSessionAsync();
+
+foreach (var question in new[]
+{
+ "What products does Microsoft offer?",
+ "What risks does Apple face?",
+ "Tell me about NVIDIA's AI business and risk factors."
+})
+{
+ Console.WriteLine($">> {question}\n");
+ Console.WriteLine(await agent.RunAsync(question, session));
+ Console.WriteLine();
+}
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md
new file mode 100644
index 0000000000..418407e831
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md
@@ -0,0 +1,32 @@
+# Agent Framework Retrieval Augmented Generation (RAG) with Neo4j GraphRAG
+
+This sample demonstrates how to create and run an agent that uses the [Neo4j GraphRAG context provider](https://github.com/neo4j-labs/neo4j-maf-provider) with Microsoft Agent Framework for .NET.
+
+The sample uses a Neo4j fulltext index for retrieval and a Cypher `RetrievalQuery` to enrich results with related companies, products, and risk factors.
+
+## Prerequisites
+
+- .NET 10 SDK or later
+- Azure OpenAI endpoint and chat deployment
+- Azure CLI installed and authenticated
+- A Neo4j database with chunked documents and a fulltext index such as `search_chunks`
+
+## Environment variables
+
+```powershell
+$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
+$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:NEO4J_URI="neo4j+s://your-instance.databases.neo4j.io"
+$env:NEO4J_USERNAME="neo4j"
+$env:NEO4J_PASSWORD="your-password"
+$env:NEO4J_FULLTEXT_INDEX_NAME="search_chunks"
+```
+
+## Build and run
+
+```powershell
+dotnet build
+dotnet run --framework net10.0 --no-build
+```
+
+The sample issues a few questions against the graph-backed retrieval provider and prints the responses to the console.
diff --git a/dotnet/samples/02-agents/AgentWithRAG/README.md b/dotnet/samples/02-agents/AgentWithRAG/README.md
index d606ac767c..9633220bf1 100644
--- a/dotnet/samples/02-agents/AgentWithRAG/README.md
+++ b/dotnet/samples/02-agents/AgentWithRAG/README.md
@@ -8,3 +8,4 @@ These samples show how to create an agent with the Agent Framework that uses Ret
|[RAG with Vector Store and custom schema](./AgentWithRAG_Step02_CustomVectorStoreRAG/)|This sample demonstrates how to create and run an agent that uses Retrieval Augmented Generation (RAG) with a vector store. It also uses a custom schema for the documents stored in the vector store.|
|[RAG with custom RAG data source](./AgentWithRAG_Step03_CustomRAGDataSource/)|This sample demonstrates how to create and run an agent that uses Retrieval Augmented Generation (RAG) with a custom RAG data source.|
|[RAG with Foundry VectorStore service](./AgentWithRAG_Step04_FoundryServiceRAG/)|This sample demonstrates how to create and run an agent that uses Retrieval Augmented Generation (RAG) with the Foundry VectorStore service.|
+|[RAG with Neo4j GraphRAG](./AgentWithRAG_Step05_Neo4jGraphRAG/)|This sample demonstrates how to create and run an agent that uses a Neo4j-backed GraphRAG context provider with graph-enriched retrieval.|
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step02_StructuredOutput/README.md b/dotnet/samples/02-agents/Agents/Agent_Step02_StructuredOutput/README.md
index 5652fe9b0a..9b9411f17e 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step02_StructuredOutput/README.md
+++ b/dotnet/samples/02-agents/Agents/Agent_Step02_StructuredOutput/README.md
@@ -18,7 +18,7 @@ Before you begin, ensure you have the following prerequisites:
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource
-**Note**: This sample uses Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
+**Note**: This sample uses Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource and have the `Cognitive Services OpenAI Contributor` role. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Agent_Step07_AsMcpTool.csproj b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Agent_Step07_AsMcpTool.csproj
index 5239225499..a7df53251d 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Agent_Step07_AsMcpTool.csproj
+++ b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Agent_Step07_AsMcpTool.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md
index e35cf01e90..2feaee22e2 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md
+++ b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md
@@ -20,8 +20,8 @@ To use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector)
MCP Inspector is up and running at http://127.0.0.1:6274
```
1. Open a web browser and navigate to the URL displayed in the terminal. If not opened automatically, this will open the MCP Inspector interface.
-1. In the MCP Inspector interface, add the following environment variables to allow your MCP server to access Azure AI Foundry Project to create and run the agent:
- - AZURE_AI_PROJECT_ENDPOINT = https://your-resource.openai.azure.com/ # Replace with your Azure AI Foundry Project endpoint
+1. In the MCP Inspector interface, add the following environment variables to allow your MCP server to access Microsoft Foundry Project to create and run the agent:
+ - AZURE_AI_PROJECT_ENDPOINT = https://your-resource.openai.azure.com/ # Replace with your Microsoft Foundry Project endpoint
- AZURE_AI_MODEL_DEPLOYMENT_NAME = gpt-4o-mini # Replace with your model deployment name
1. Find and click the `Connect` button in the MCP Inspector interface to connect to the MCP server.
1. As soon as the connection is established, open the `Tools` tab in the MCP Inspector interface and select the `Joker` tool from the list.
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step11_Middleware/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step11_Middleware/Program.cs
index bab09bc886..f7f3602fd3 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step11_Middleware/Program.cs
+++ b/dotnet/samples/02-agents/Agents/Agent_Step11_Middleware/Program.cs
@@ -13,7 +13,7 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
-// Get Azure AI Foundry configuration from environment variables
+// Get Microsoft Foundry configuration from environment variables
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step13_ChatReduction/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step13_ChatReduction/Program.cs
index fe93ed785c..915bc5c376 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step13_ChatReduction/Program.cs
+++ b/dotnet/samples/02-agents/Agents/Agent_Step13_ChatReduction/Program.cs
@@ -3,7 +3,7 @@
// This sample shows how to use a chat history reducer to keep the context within model size limits.
// Any implementation of Microsoft.Extensions.AI.IChatReducer can be used to customize how the chat history is reduced.
// NOTE: this feature is only supported where the chat history is stored locally, such as with OpenAI Chat Completion.
-// Where the chat history is stored server side, such as with Azure Foundry Agents, the service must manage the chat history size.
+// Where the chat history is stored server side, such as with Microsoft Foundry Agents, the service must manage the chat history size.
using Azure.AI.OpenAI;
using Azure.Identity;
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs
index 11d3f561f4..d329816f13 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs
+++ b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs
@@ -2,7 +2,7 @@
#pragma warning disable CS0618 // Type or member is obsolete - sample uses deprecated PersistentAgentsClientExtensions
-// This sample shows how to create an Azure AI Foundry Agent with the Deep Research Tool.
+// This sample shows how to create a Microsoft Foundry Agent with the Deep Research Tool.
using Azure.AI.Agents.Persistent;
using Azure.Identity;
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md
index 1848b10826..b04e65cafc 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md
+++ b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md
@@ -11,10 +11,10 @@ Key features:
Before running this sample, ensure you have:
-1. An Azure AI Foundry project set up
+1. A Microsoft Foundry project set up
2. A deep research model deployment (e.g., o3-deep-research)
3. A model deployment (e.g., gpt-4o)
-4. A Bing Connection configured in your Azure AI Foundry project
+4. A Bing Connection configured in your Microsoft Foundry project
5. Azure CLI installed and authenticated
**Important**: Please visit the following documentation for detailed setup instructions:
@@ -29,14 +29,14 @@ Pay special attention to the purple `Note` boxes in the Azure documentation.
/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//projects//connections/
```
-You can find this in the Azure AI Foundry portal under **Management > Connected resources**, or retrieve it programmatically via the connections API (`.id` property).
+You can find this in the Microsoft Foundry portal under **Management > Connected resources**, or retrieve it programmatically via the connections API (`.id` property).
## Environment Variables
Set the following environment variables:
```powershell
-# Replace with your Azure AI Foundry project endpoint
+# Replace with your Microsoft Foundry project endpoint
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/"
# Replace with your Bing Grounding connection ID (full ARM resource URI)
diff --git a/dotnet/samples/02-agents/Agents/README.md b/dotnet/samples/02-agents/Agents/README.md
index c5258ba9f4..6cdf80625b 100644
--- a/dotnet/samples/02-agents/Agents/README.md
+++ b/dotnet/samples/02-agents/Agents/README.md
@@ -18,7 +18,7 @@ Before you begin, ensure you have the following prerequisites:
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource.
-**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
+**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
**Note**: These samples use Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource and have the `Cognitive Services OpenAI Contributor` role. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj
index d861331d9f..4c83380f90 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs
index c6b2d5c764..16c219e144 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create, use, and clean up a FoundryAgent backed by a server-side
-// versioned agent in Azure AI Foundry. It demonstrates the full lifecycle:
+// versioned agent in Microsoft Foundry. It demonstrates the full lifecycle:
// create agent version -> wrap as FoundryAgent -> run -> delete.
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj
index 5e73fd236a..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs
index 7a66555c18..6057d71895 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs
@@ -4,10 +4,10 @@
// Server-side conversations persist on the Foundry service and are visible in the Foundry Project UI.
// Use this when you need conversation history to be stored and accessible server-side.
+using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
@@ -15,12 +15,20 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLO
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
-FoundryAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
+AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
+
+ChatClientAgent agent = aiProjectClient
.AsAIAgent(deploymentName, instructions: "You are good at telling jokes.", name: "JokerAgent");
+ProjectConversationsClient conversationsClient = aiProjectClient
+ .GetProjectOpenAIClient()
+ .GetProjectConversationsClient();
+
+ProjectConversation conversation = (await conversationsClient.CreateProjectConversationAsync().ConfigureAwait(false)).Value;
+
// CreateConversationSessionAsync creates a server-side ProjectConversation
// that persists on the Foundry service and is visible in the Foundry Project UI.
-AgentSession session = await agent.CreateConversationSessionAsync();
+AgentSession session = await agent.CreateSessionAsync(conversation.Id);
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session));
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj
index 1189939bc0..60190545a1 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj
index 72af634725..fd54882035 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj
index 96cdf948fe..1f596a94a1 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj
index 6064cf9334..c2bb03bffd 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj
index 7367c1d2f8..6b4cb8f43e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,7 +9,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj
index b30baccd54..d1cfe0da4a 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj
index 1f5e37c1a3..5dfa730c8a 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj
index e11688b6ba..7d91cedca5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj
index f739f56123..7bd94bd716 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs
index 22f03e27b3..d79c1122b7 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs
@@ -5,7 +5,7 @@
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj
index e11688b6ba..7d91cedca5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj
index 4602e9c9e0..8671b3027c 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -14,7 +14,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs
index 5cc2720dd9..1968b89ca1 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs
@@ -6,7 +6,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj
index e11688b6ba..7d91cedca5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs
index 4ab548403d..e244bb3cb0 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs
@@ -6,7 +6,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
string connectionId = Environment.GetEnvironmentVariable("AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID") ?? throw new InvalidOperationException("AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID is not set.");
string instanceName = Environment.GetEnvironmentVariable("AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME") ?? throw new InvalidOperationException("AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME is not set.");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj
index e11688b6ba..7d91cedca5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs
index aafca6b8bd..a5f8bf845a 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs
@@ -6,7 +6,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
string sharepointConnectionId = Environment.GetEnvironmentVariable("SHAREPOINT_PROJECT_CONNECTION_ID") ?? throw new InvalidOperationException("SHAREPOINT_PROJECT_CONNECTION_ID is not set.");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj
index e11688b6ba..7d91cedca5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs
index 7c49afa7ee..69954b9483 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs
@@ -6,7 +6,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
string fabricConnectionId = Environment.GetEnvironmentVariable("FABRIC_PROJECT_CONNECTION_ID") ?? throw new InvalidOperationException("FABRIC_PROJECT_CONNECTION_ID is not set.");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj
index e11688b6ba..7d91cedca5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -13,7 +13,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj
index 4602e9c9e0..8671b3027c 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -14,7 +14,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs
index 9e1a902f29..f2b447e52e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs
@@ -9,7 +9,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj
index e51f57c439..53b8a3af34 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -14,7 +14,7 @@
-
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/README.md
index b5802053b3..8cc964e227 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/README.md
@@ -1,6 +1,6 @@
# Getting started with Foundry Agents
-These samples demonstrate how to use Azure AI Foundry with Agent Framework.
+These samples demonstrate how to use Microsoft Foundry with Agent Framework.
## Quick start
diff --git a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj
index d861331d9f..4c83380f90 100644
--- a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj
+++ b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
index bedf87b8c5..ffd576d273 100644
--- a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
+++ b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
-// This sample shows how to create and use a simple AI agent with Azure Foundry Agents as the backend, that uses a Hosted MCP Tool.
-// In this case the Azure Foundry Agents service will invoke any MCP tools as required. MCP tools are not invoked by the Agent Framework.
+// This sample shows how to create and use a simple AI agent with Microsoft Foundry Agents as the backend, that uses a Hosted MCP Tool.
+// In this case the Microsoft Foundry Agents service will invoke any MCP tools as required. MCP tools are not invoked by the Agent Framework.
// The sample first shows how to use MCP tools with auto approval, and then how to set up a tool that requires approval before it can be invoked and how to approve such a tool.
using Azure.AI.Projects;
diff --git a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
index a172ec63cf..d03833b826 100644
--- a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
+++ b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
@@ -3,14 +3,14 @@
Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
-- Azure Foundry service endpoint and deployment configured
+- Microsoft Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Azure Foundry resource endpoint
+$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" # Optional, defaults to gpt-4.1-mini
```
diff --git a/dotnet/samples/02-agents/ModelContextProtocol/README.md b/dotnet/samples/02-agents/ModelContextProtocol/README.md
index be1aa83513..9cbaecad4b 100644
--- a/dotnet/samples/02-agents/ModelContextProtocol/README.md
+++ b/dotnet/samples/02-agents/ModelContextProtocol/README.md
@@ -11,7 +11,7 @@ Before you begin, ensure you have the following prerequisites:
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource.
-**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
+**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
**Note**: These samples use Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource and have the `Cognitive Services OpenAI Contributor` role. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
diff --git a/dotnet/samples/03-workflows/Agents/FoundryAgent/FoundryAgent.csproj b/dotnet/samples/03-workflows/Agents/FoundryAgent/FoundryAgent.csproj
index a7648b7a10..dd6854fbfe 100644
--- a/dotnet/samples/03-workflows/Agents/FoundryAgent/FoundryAgent.csproj
+++ b/dotnet/samples/03-workflows/Agents/FoundryAgent/FoundryAgent.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs b/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs
index 5b1f59ac62..1ecbbae08e 100644
--- a/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs
+++ b/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs
@@ -4,19 +4,19 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace WorkflowFoundryAgentSample;
///
-/// This sample shows how to use Azure Foundry Agents within a workflow.
+/// This sample shows how to use Microsoft Foundry Agents within a workflow.
///
///
/// Pre-requisites:
/// - Foundational samples should be completed first.
-/// - An Azure Foundry project endpoint and model id.
+/// - A Microsoft Foundry project endpoint and model ID.
///
public static class Program
{
diff --git a/dotnet/samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj b/dotnet/samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj
index dac2f49921..e7a4c3a774 100644
--- a/dotnet/samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj
+++ b/dotnet/samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj b/dotnet/samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj
index 15a60ae665..80158364ae 100644
--- a/dotnet/samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj
+++ b/dotnet/samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj b/dotnet/samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj
index a8c70c5916..504b948396 100644
--- a/dotnet/samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj
+++ b/dotnet/samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/ExecuteCode/ExecuteCode.csproj b/dotnet/samples/03-workflows/Declarative/ExecuteCode/ExecuteCode.csproj
index 6a9c4957c2..c92a9ffbf1 100644
--- a/dotnet/samples/03-workflows/Declarative/ExecuteCode/ExecuteCode.csproj
+++ b/dotnet/samples/03-workflows/Declarative/ExecuteCode/ExecuteCode.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj b/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj
index fce40b64d4..243d6d3d52 100644
--- a/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj
+++ b/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj b/dotnet/samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj
index f890fb30a8..5ed2187e2f 100644
--- a/dotnet/samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj
+++ b/dotnet/samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj b/dotnet/samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj
index 17859dcb75..e2062e40e4 100644
--- a/dotnet/samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj
+++ b/dotnet/samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs b/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs
index 7852a8730f..1d688531df 100644
--- a/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs
+++ b/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs
@@ -8,7 +8,7 @@ using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
diff --git a/dotnet/samples/03-workflows/Declarative/InputArguments/InputArguments.csproj b/dotnet/samples/03-workflows/Declarative/InputArguments/InputArguments.csproj
index 45bc44eaf3..ae6a0046ef 100644
--- a/dotnet/samples/03-workflows/Declarative/InputArguments/InputArguments.csproj
+++ b/dotnet/samples/03-workflows/Declarative/InputArguments/InputArguments.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/InvokeFunctionTool/InvokeFunctionTool.csproj b/dotnet/samples/03-workflows/Declarative/InvokeFunctionTool/InvokeFunctionTool.csproj
index 67229da4b8..f2d23835a1 100644
--- a/dotnet/samples/03-workflows/Declarative/InvokeFunctionTool/InvokeFunctionTool.csproj
+++ b/dotnet/samples/03-workflows/Declarative/InvokeFunctionTool/InvokeFunctionTool.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/InvokeMcpTool.csproj b/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/InvokeMcpTool.csproj
index 317d93c4e9..ea122bd971 100644
--- a/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/InvokeMcpTool.csproj
+++ b/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/InvokeMcpTool.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/Program.cs b/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/Program.cs
index 61ce1afc70..7da862df92 100644
--- a/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/Program.cs
+++ b/dotnet/samples/03-workflows/Declarative/InvokeMcpTool/Program.cs
@@ -30,7 +30,7 @@ namespace Demo.Workflows.Declarative.InvokeMcpTool;
/// - Integrating with MCP-compatible services
///
///
-/// This sample uses the Microsoft Learn MCP server to search Azure documentation and the Azure foundry MCP server to get AI model details.
+/// This sample uses the Microsoft Learn MCP server to search Azure documentation and the Microsoft Foundry MCP server to get AI model details.
/// When you run the sample, provide an AI model (e.g. gpt-4.1-mini) as input,
/// The workflow will use the MCP tools to find relevant information about the model from Microsoft Learn and foundry, then an agent will summarize the results.
///
diff --git a/dotnet/samples/03-workflows/Declarative/Marketing/Marketing.csproj b/dotnet/samples/03-workflows/Declarative/Marketing/Marketing.csproj
index 86312551b5..ac4b61d7f3 100644
--- a/dotnet/samples/03-workflows/Declarative/Marketing/Marketing.csproj
+++ b/dotnet/samples/03-workflows/Declarative/Marketing/Marketing.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/README.md b/dotnet/samples/03-workflows/Declarative/README.md
index 2ad3e59c0d..6bd2c85824 100644
--- a/dotnet/samples/03-workflows/Declarative/README.md
+++ b/dotnet/samples/03-workflows/Declarative/README.md
@@ -6,7 +6,7 @@ to build a `Workflow` that may be executed using the same pattern as any code-ba
## Configuration
These samples must be configured to create and use agents your
-[Azure Foundry Project](https://learn.microsoft.com/azure/ai-foundry).
+[Microsoft Foundry Project](https://learn.microsoft.com/azure/ai-foundry).
### Settings
@@ -18,9 +18,9 @@ The configuraton required by the samples is:
|Setting Name| Description|
|:--|:--|
-|AZURE_AI_PROJECT_ENDPOINT| The endpoint URL of your Azure Foundry Project.|
+|AZURE_AI_PROJECT_ENDPOINT| The endpoint URL of your Microsoft Foundry Project.|
|AZURE_AI_MODEL_DEPLOYMENT_NAME| The name of the model deployment to use
-|AZURE_AI_BING_CONNECTION_ID| The name of the Bing Grounding connection configured in your Azure Foundry Project.|
+|AZURE_AI_BING_CONNECTION_ID| The name of the Bing Grounding connection configured in your Microsoft Foundry Project.|
To set your secrets with .NET Secret Manager:
@@ -42,13 +42,13 @@ To set your secrets with .NET Secret Manager:
dotnet user-secrets init
```
-4. Define setting that identifies your Azure Foundry Project (endpoint):
+4. Define setting that identifies your Microsoft Foundry Project (endpoint):
```
dotnet user-secrets set "AZURE_AI_PROJECT_ENDPOINT" "https://..."
```
-5. Define setting that identifies your Azure Foundry Model Deployment (endpoint):
+5. Define setting that identifies your Microsoft Foundry Model Deployment (endpoint):
```
dotnet user-secrets set "AZURE_AI_MODEL_DEPLOYMENT_NAME" "gpt-5"
@@ -70,7 +70,7 @@ $env:AZURE_AI_BING_CONNECTION_ID="mybinggrounding"
### Authorization
-Use [_Azure CLI_](https://learn.microsoft.com/cli/azure/authenticate-azure-cli) to authorize access to your Azure Foundry Project:
+Use [_Azure CLI_](https://learn.microsoft.com/cli/azure/authenticate-azure-cli) to authorize access to your Microsoft Foundry Project:
```
az login
diff --git a/dotnet/samples/03-workflows/Declarative/StudentTeacher/StudentTeacher.csproj b/dotnet/samples/03-workflows/Declarative/StudentTeacher/StudentTeacher.csproj
index a89dd44f20..d8375f70cd 100644
--- a/dotnet/samples/03-workflows/Declarative/StudentTeacher/StudentTeacher.csproj
+++ b/dotnet/samples/03-workflows/Declarative/StudentTeacher/StudentTeacher.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/Declarative/ToolApproval/ToolApproval.csproj b/dotnet/samples/03-workflows/Declarative/ToolApproval/ToolApproval.csproj
index a44e140f1f..e5cf939e15 100644
--- a/dotnet/samples/03-workflows/Declarative/ToolApproval/ToolApproval.csproj
+++ b/dotnet/samples/03-workflows/Declarative/ToolApproval/ToolApproval.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/dotnet/samples/03-workflows/README.md b/dotnet/samples/03-workflows/README.md
index 1ab52106ec..d17148d60d 100644
--- a/dotnet/samples/03-workflows/README.md
+++ b/dotnet/samples/03-workflows/README.md
@@ -26,7 +26,7 @@ Once completed, please proceed to the other samples listed below.
| Sample | Concepts |
|--------|----------|
-| [Foundry Agents in Workflows](./Agents/FoundryAgent) | Demonstrates using Azure Foundry agents in a workflow through `ChatClientAgent` |
+| [Foundry Agents in Workflows](./Agents/FoundryAgent) | Demonstrates using Microsoft Foundry agents in a workflow through `ChatClientAgent` |
| [Custom Agent Executors](./Agents/CustomAgentExecutors) | Shows how to create a custom agent executor for more complex scenarios |
| [Workflow as an Agent](./Agents/WorkflowAsAnAgent) | Illustrates how to encapsulate a workflow as an agent |
| [Group Chat with Tool Approval](./Agents/GroupChatToolApproval) | Shows multi-agent group chat with tool approval requests and human-in-the-loop interaction |
diff --git a/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/A2AServer.csproj b/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/A2AServer.csproj
index 5a7ef20208..98b7b293c8 100644
--- a/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/A2AServer.csproj
+++ b/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/A2AServer.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -23,7 +23,7 @@
-
+
diff --git a/dotnet/samples/05-end-to-end/A2AClientServer/README.md b/dotnet/samples/05-end-to-end/A2AClientServer/README.md
index cff5b40e2d..1f49d56f80 100644
--- a/dotnet/samples/05-end-to-end/A2AClientServer/README.md
+++ b/dotnet/samples/05-end-to-end/A2AClientServer/README.md
@@ -51,7 +51,7 @@ dotnet run --urls "http://localhost:5002;https://localhost:5012" --agentType "lo
### Configuring for use with Azure AI Agents
-You must create the agents in an Azure AI Foundry project and then provide the project endpoint and agents ids. The instructions for each agent are as follows:
+You must create the agents in a Microsoft Foundry project and then provide the project endpoint and agent IDs. The instructions for each agent are as follows:
- Invoice Agent
```
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/Program.cs b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/Program.cs
index 78a0aa62e9..329ad74ea2 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/Program.cs
+++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/Program.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Seattle Hotel Agent - A simple agent with a tool to find hotels in Seattle.
-// Uses Microsoft Agent Framework with Azure AI Foundry.
+// Uses Microsoft Agent Framework with Microsoft Foundry.
// Ready for deployment to Foundry Hosted Agent service.
using System.ClientModel.Primitives;
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/README.md b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/README.md
index c080331a87..58ea174718 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/README.md
+++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/README.md
@@ -4,7 +4,7 @@ This sample demonstrates how to build a hosted agent that uses local C# function
Key features:
- Defining local C# functions as agent tools using `AIFunctionFactory`
-- Using `AIProjectClient` to discover the OpenAI connection from the Azure AI Foundry project
+- Using `AIProjectClient` to discover the OpenAI connection from the Microsoft Foundry project
- Building a `ChatClientAgent` with custom instructions and tools
- Deploying to the Foundry Hosted Agent service
@@ -15,7 +15,7 @@ Key features:
Before running this sample, ensure you have:
1. .NET 10 SDK installed
-2. An Azure AI Foundry Project with a chat model deployed (e.g., gpt-4o-mini)
+2. A Microsoft Foundry Project with a chat model deployed (e.g., gpt-4o-mini)
3. Azure CLI installed and authenticated (`az login`)
## Environment Variables
@@ -23,7 +23,7 @@ Before running this sample, ensure you have:
Set the following environment variables:
```powershell
-# Replace with your Azure AI Foundry project endpoint
+# Replace with your Microsoft Foundry project endpoint
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project-name"
# Optional, defaults to gpt-4o-mini
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs
index 8d21eef20a..ca36341b97 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs
+++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates a multi-agent workflow with Writer and Reviewer agents
-// using Azure AI Foundry AIProjectClient and the Agent Framework WorkflowBuilder.
+// using Microsoft Foundry AIProjectClient and the Agent Framework WorkflowBuilder.
#pragma warning disable CA2252 // AIProjectClient and Agents API require opting into preview features
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/README.md b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/README.md
index 314320880b..4f589be86e 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/README.md
+++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/README.md
@@ -42,7 +42,7 @@ which provisions a REST API endpoint compatible with the OpenAI Responses protoc
Before running this sample, ensure you have:
-1. **Azure AI Foundry Project**
+1. **Microsoft Foundry Project**
- Project created.
- Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`)
- Note your project endpoint URL and model deployment name
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/agent.yaml b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/agent.yaml
index 70b82abf7c..672444cdd1 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/agent.yaml
+++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/agent.yaml
@@ -4,7 +4,7 @@ name: FoundryMultiAgent
displayName: "Foundry Multi-Agent Workflow"
description: >
A multi-agent workflow featuring a Writer and Reviewer that collaborate
- to create and refine content using Azure AI Foundry PersistentAgentsClient.
+ to create and refine content using Microsoft Foundry PersistentAgentsClient.
metadata:
authors:
- Microsoft Agent Framework Team
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/Program.cs b/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/Program.cs
index 80edf42089..185afd186f 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/Program.cs
+++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/Program.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Seattle Hotel Agent - A simple agent with a tool to find hotels in Seattle.
-// Uses Microsoft Agent Framework with Azure AI Foundry.
+// Uses Microsoft Agent Framework with Microsoft Foundry.
// Ready for deployment to Foundry Hosted Agent service.
#pragma warning disable CA2252 // AIProjectClient and Agents API require opting into preview features
@@ -70,17 +70,19 @@ string GetAvailableHotels(
// Build response
var result = new StringBuilder();
- result.AppendLine($"Available hotels in Seattle from {checkInDate} to {checkOutDate} ({nights} nights):");
- result.AppendLine();
+ result
+ .AppendLine($"Available hotels in Seattle from {checkInDate} to {checkOutDate} ({nights} nights):")
+ .AppendLine();
foreach (var hotel in availableHotels)
{
var totalCost = hotel.PricePerNight * nights;
- result.AppendLine($"**{hotel.Name}**");
- result.AppendLine($" Location: {hotel.Location}");
- result.AppendLine($" Rating: {hotel.Rating}/5");
- result.AppendLine($" ${hotel.PricePerNight}/night (Total: ${totalCost})");
- result.AppendLine();
+ result
+ .AppendLine($"**{hotel.Name}**")
+ .AppendLine($" Location: {hotel.Location}")
+ .AppendLine($" Rating: {hotel.Rating}/5")
+ .AppendLine($" ${hotel.PricePerNight}/night (Total: ${totalCost})")
+ .AppendLine();
}
return result.ToString();
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/README.md b/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/README.md
index 31f3fc1a9d..e9dbced0a7 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/README.md
+++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/README.md
@@ -39,7 +39,7 @@ which provisions a REST API endpoint compatible with the OpenAI Responses protoc
Before running this sample, ensure you have:
-1. **Azure AI Foundry Project**
+1. **Microsoft Foundry Project**
- Project created.
- Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`)
- Note your project endpoint URL and model deployment name
@@ -57,7 +57,7 @@ Before running this sample, ensure you have:
Set the following environment variables (matching `agent.yaml`):
-- `AZURE_AI_PROJECT_ENDPOINT` - Your Azure AI Foundry project endpoint URL (required)
+- `AZURE_AI_PROJECT_ENDPOINT` - Your Microsoft Foundry project endpoint URL (required)
- `MODEL_DEPLOYMENT_NAME` - The deployment name for your chat model (defaults to `gpt-4o-mini`)
**PowerShell:**
diff --git a/dotnet/samples/05-end-to-end/HostedAgents/README.md b/dotnet/samples/05-end-to-end/HostedAgents/README.md
index 919aa4b580..2042212a2e 100644
--- a/dotnet/samples/05-end-to-end/HostedAgents/README.md
+++ b/dotnet/samples/05-end-to-end/HostedAgents/README.md
@@ -20,7 +20,7 @@ Before running any sample, ensure you have:
1. **.NET 10 SDK** or later — [Download](https://dotnet.microsoft.com/download/dotnet/10.0)
2. **Azure CLI** installed — [Install guide](https://learn.microsoft.com/cli/azure/install-azure-cli)
-3. **Azure OpenAI** or **Azure AI Foundry project** with a chat model deployed (e.g., `gpt-4o-mini`)
+3. **Azure OpenAI** or **Microsoft Foundry project** with a chat model deployed (e.g., `gpt-4o-mini`)
### Authenticate with Azure CLI
@@ -39,14 +39,14 @@ Most samples require one or more of these environment variables:
|----------|---------|-------------|
| `AZURE_OPENAI_ENDPOINT` | Most samples | Your Azure OpenAI resource endpoint URL |
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Most samples | Chat model deployment name (defaults to `gpt-4o-mini`) |
-| `AZURE_AI_PROJECT_ENDPOINT` | AgentWithLocalTools, FoundryMultiAgent, FoundrySingleAgent | Azure AI Foundry project endpoint |
+| `AZURE_AI_PROJECT_ENDPOINT` | AgentWithLocalTools, FoundryMultiAgent, FoundrySingleAgent | Microsoft Foundry project endpoint |
| `MODEL_DEPLOYMENT_NAME` | AgentWithLocalTools, FoundryMultiAgent, FoundrySingleAgent | Chat model deployment name (defaults to `gpt-4o-mini`) |
See each sample's README for the specific variables required.
-## Azure AI Foundry Setup (for samples that use Foundry)
+## Microsoft Foundry Setup (for samples that use Foundry)
-Some samples (`AgentWithLocalTools`, `FoundrySingleAgent`, `FoundryMultiAgent`) connect to an Azure AI Foundry project. If you're using these samples, you'll need additional setup.
+Some samples (`AgentWithLocalTools`, `FoundrySingleAgent`, `FoundryMultiAgent`) connect to a Microsoft Foundry project. If you're using these samples, you'll need additional setup.
### Azure AI Developer Role
@@ -61,7 +61,7 @@ az role assignment create `
> **Note**: You need **Owner** or **User Access Administrator** permissions on the resource to assign roles. If you don't have this, you may need to request JIT (Just-In-Time) elevated access via [Azure PIM](https://portal.azure.com/#view/Microsoft_Azure_PIMCommon/ActivationMenuBlade/~/aadmigratedresource).
-For more details on permissions, see [Azure AI Foundry Permissions](https://aka.ms/FoundryPermissions).
+For more details on permissions, see [Microsoft Foundry Permissions](https://aka.ms/FoundryPermissions).
## Running a Sample
diff --git a/dotnet/samples/AGENTS.md b/dotnet/samples/AGENTS.md
index f515f531eb..7208facf36 100644
--- a/dotnet/samples/AGENTS.md
+++ b/dotnet/samples/AGENTS.md
@@ -28,7 +28,7 @@ dotnet/samples/
│ ├── AGUI/ # AG-UI protocol samples
│ ├── DeclarativeAgents/ # Declarative agent definitions
│ ├── DevUI/ # DevUI samples
-│ ├── AgentsWithFoundry/ # Azure AI Foundry samples (FoundryAgent + AsAIAgent extensions)
+│ ├── AgentsWithFoundry/ # Microsoft Foundry samples (FoundryAgent + AsAIAgent extensions)
│ └── ModelContextProtocol/ # MCP server/client patterns
├── 03-workflows/ # Workflow patterns
│ ├── _StartHere/ # Introductory workflow samples
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs
deleted file mode 100644
index 479ab894ae..0000000000
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs
+++ /dev/null
@@ -1,935 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.ClientModel;
-using System.ClientModel.Primitives;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using System.Text.RegularExpressions;
-using Azure.AI.Extensions.OpenAI;
-using Azure.AI.Projects.Agents;
-using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.Logging;
-using Microsoft.Shared.DiagnosticIds;
-using Microsoft.Shared.Diagnostics;
-using OpenAI;
-using OpenAI.Responses;
-
-namespace Azure.AI.Projects;
-
-///
-/// Provides extension methods for .
-///
-[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
-public static partial class AzureAIProjectChatClientExtensions
-{
- ///
- /// Uses an existing server side agent, wrapped as a using the provided and .
- ///
- /// The to create the with. Cannot be .
- /// The representing the name and version of the server side agent to create a for. Cannot be .
- /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.
- /// Provides a way to customize the creation of the underlying used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A instance that can be used to perform operations based on the latest version of the named Azure AI Agent.
- /// Thrown when or is .
- /// The agent with the specified name was not found.
- ///
- /// When instantiating a by using an , minimal information will be available about the agent in the instance level, and any logic that relies
- /// on to retrieve information about the agent like will receive as the result.
- ///
- public static FoundryAgent AsAIAgent(
- this AIProjectClient aiProjectClient,
- AgentReference agentReference,
- IList? tools = null,
- Func? clientFactory = null,
- IServiceProvider? services = null)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(agentReference);
- ThrowIfInvalidAgentName(agentReference.Name);
-
- var innerAgent = AsChatClientAgent(
- aiProjectClient,
- agentReference,
- new ChatClientAgentOptions()
- {
- Id = $"{agentReference.Name}:{agentReference.Version}",
- Name = agentReference.Name,
- ChatOptions = new() { Tools = tools },
- },
- clientFactory,
- services);
-
- return new FoundryAgent(aiProjectClient, innerAgent);
- }
-
- ///
- /// Asynchronously retrieves an existing server side agent, wrapped as a using the provided .
- ///
- /// The to create the with. Cannot be .
- /// The name of the server side agent to create a for. Cannot be or whitespace.
- /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.
- /// Provides a way to customize the creation of the underlying used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// The to monitor for cancellation requests. The default is .
- /// A instance that can be used to perform operations based on the latest version of the named Azure AI Agent.
- /// Thrown when or is .
- /// Thrown when is empty or whitespace, or when the agent with the specified name was not found.
- /// The agent with the specified name was not found.
- [Obsolete("Use native AIProjectClient agent APIs and AsAIAgent(AgentRecord/AgentVersion) instead.")]
- public static async Task GetAIAgentAsync(
- this AIProjectClient aiProjectClient,
- string name,
- IList? tools = null,
- Func? clientFactory = null,
- IServiceProvider? services = null,
- CancellationToken cancellationToken = default)
- {
- Throw.IfNull(aiProjectClient);
- ThrowIfInvalidAgentName(name);
-
- AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, name, cancellationToken).ConfigureAwait(false);
-
- return AsAIAgent(
- aiProjectClient,
- agentRecord,
- tools,
- clientFactory,
- services);
- }
-
- ///
- /// Uses an existing server side agent, wrapped as a using the provided and .
- ///
- /// The client used to interact with Azure AI Agents. Cannot be .
- /// The agent record to be converted. The latest version will be used. Cannot be .
- /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.
- /// Provides a way to customize the creation of the underlying used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A instance that can be used to perform operations based on the latest version of the Azure AI Agent.
- /// Thrown when or is .
- public static FoundryAgent AsAIAgent(
- this AIProjectClient aiProjectClient,
- AgentRecord agentRecord,
- IList? tools = null,
- Func? clientFactory = null,
- IServiceProvider? services = null)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(agentRecord);
-
- var allowDeclarativeMode = tools is not { Count: > 0 };
-
- var innerAgent = AsChatClientAgent(
- aiProjectClient,
- agentRecord,
- tools,
- clientFactory,
- !allowDeclarativeMode,
- services);
-
- return new FoundryAgent(aiProjectClient, innerAgent);
- }
-
- ///
- /// Uses an existing server side agent, wrapped as a using the provided and .
- ///
- /// The client used to interact with Azure AI Agents. Cannot be .
- /// The agent version to be converted. Cannot be .
- /// In-process invocable tools to be provided. If no tools are provided manual handling will be necessary to invoke in-process tools.
- /// Provides a way to customize the creation of the underlying used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A instance that can be used to perform operations based on the provided version of the Azure AI Agent.
- /// Thrown when or is .
- public static FoundryAgent AsAIAgent(
- this AIProjectClient aiProjectClient,
- AgentVersion agentVersion,
- IList? tools = null,
- Func? clientFactory = null,
- IServiceProvider? services = null)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(agentVersion);
-
- var allowDeclarativeMode = tools is not { Count: > 0 };
-
- var innerAgent = AsChatClientAgent(
- aiProjectClient,
- agentVersion,
- tools,
- clientFactory,
- !allowDeclarativeMode,
- services);
-
- return new FoundryAgent(aiProjectClient, innerAgent);
- }
-
- ///
- /// Asynchronously retrieves an existing server side agent, wrapped as a using the provided .
- ///
- /// The client used to manage and interact with AI agents. Cannot be .
- /// The options for creating the agent. Cannot be .
- /// A factory function to customize the creation of the chat client used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A to cancel the operation if needed.
- /// A instance that can be used to perform operations on the newly created agent.
- /// Thrown when or is .
- [Obsolete("Use native AIProjectClient agent APIs and AsAIAgent(AgentRecord/AgentVersion) instead.")]
- public static async Task GetAIAgentAsync(
- this AIProjectClient aiProjectClient,
- ChatClientAgentOptions options,
- Func? clientFactory = null,
- IServiceProvider? services = null,
- CancellationToken cancellationToken = default)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(options);
-
- if (string.IsNullOrWhiteSpace(options.Name))
- {
- throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
- }
-
- ThrowIfInvalidAgentName(options.Name);
-
- AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, options.Name, cancellationToken).ConfigureAwait(false);
- var agentVersion = agentRecord.GetLatestVersion();
-
- var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: !options.UseProvidedChatClientAsIs);
-
- return new FoundryAgent(
- aiProjectClient,
- AsChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services));
- }
-
- ///
- /// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a .
- ///
- /// The client used to manage and interact with AI agents. Cannot be .
- /// The name for the agent.
- /// The name of the model to use for the agent. Cannot be or whitespace.
- /// The instructions that guide the agent's behavior. Cannot be or whitespace.
- /// The description for the agent.
- /// The tools to use when interacting with the agent, this is required when using prompt agent definitions with tools.
- /// A factory function to customize the creation of the chat client used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A token to monitor for cancellation requests.
- /// A instance that can be used to perform operations on the newly created agent.
- /// Thrown when , , or is .
- /// Thrown when or is empty or whitespace.
- /// When using prompt agent definitions with tools the parameter needs to be provided.
- [Obsolete("Use native AIProjectClient.Agents APIs instead.")]
- public static Task CreateAIAgentAsync(
- this AIProjectClient aiProjectClient,
- string name,
- string model,
- string instructions,
- string? description = null,
- IList? tools = null,
- Func? clientFactory = null,
- IServiceProvider? services = null,
- CancellationToken cancellationToken = default)
- {
- Throw.IfNull(aiProjectClient);
- ThrowIfInvalidAgentName(name);
- Throw.IfNullOrWhitespace(model);
- Throw.IfNullOrWhitespace(instructions);
-
- return CreateAIAgentAsync(
- aiProjectClient,
- name,
- tools,
- new AgentVersionCreationOptions(new PromptAgentDefinition(model) { Instructions = instructions }) { Description = description },
- clientFactory,
- services,
- cancellationToken);
- }
-
- ///
- /// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a .
- ///
- /// The client used to manage and interact with AI agents. Cannot be .
- /// The name of the model to use for the agent. Cannot be or whitespace.
- /// The options for creating the agent. Cannot be .
- /// A factory function to customize the creation of the chat client used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A to cancel the operation if needed.
- /// A instance that can be used to perform operations on the newly created agent.
- /// Thrown when or is .
- /// Thrown when is empty or whitespace, or when the agent name is not provided in the options.
- [Obsolete("Use native AIProjectClient.Agents APIs instead.")]
- public static async Task CreateAIAgentAsync(
- this AIProjectClient aiProjectClient,
- string model,
- ChatClientAgentOptions options,
- Func? clientFactory = null,
- IServiceProvider? services = null,
- CancellationToken cancellationToken = default)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(options);
- Throw.IfNullOrWhitespace(model);
-
- if (string.IsNullOrWhiteSpace(options.Name))
- {
- throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
- }
-
- ThrowIfInvalidAgentName(options.Name);
-
- AgentVersion agentVersion = await CreateAgentVersionFromOptionsAsync(aiProjectClient, model, options, cancellationToken).ConfigureAwait(false);
-
- var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true);
-
- return new FoundryAgent(
- aiProjectClient,
- AsChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services));
- }
-
- ///
- /// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a .
- /// parameters.
- ///
- /// The client used to manage and interact with AI agents. Cannot be .
- /// The name for the agent.
- /// Settings that control the creation of the agent.
- /// A factory function to customize the creation of the chat client used by the agent.
- /// A token to monitor for cancellation requests.
- /// A instance that can be used to perform operations on the newly created agent.
- /// Thrown when or is .
- ///
- /// When using this extension method with a the tools are only declarative and not invocable.
- /// Invocation of any in-process tools will need to be handled manually.
- ///
- [Obsolete("Use native AIProjectClient.Agents APIs instead.")]
- public static Task CreateAIAgentAsync(
- this AIProjectClient aiProjectClient,
- string name,
- AgentVersionCreationOptions creationOptions,
- Func? clientFactory = null,
- CancellationToken cancellationToken = default)
- {
- Throw.IfNull(aiProjectClient);
- ThrowIfInvalidAgentName(name);
- Throw.IfNull(creationOptions);
-
- return CreateAIAgentAsync(
- aiProjectClient,
- name,
- tools: null,
- creationOptions,
- clientFactory,
- services: null,
- cancellationToken);
- }
-
- ///
- /// Creates a non-versioned backed by the project's Responses API using the specified model and instructions.
- ///
- /// The to use for Responses API calls. Cannot be .
- /// The model deployment name to use for the agent. Cannot be or whitespace.
- /// The instructions that guide the agent's behavior. Cannot be or whitespace.
- /// Optional name for the agent.
- /// Optional human-readable description for the agent.
- /// Optional collection of tools that the agent can invoke during conversations.
- /// Provides a way to customize the creation of the underlying used by the agent.
- /// Optional logger factory for creating loggers used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A backed by the project's Responses API.
- /// Thrown when is .
- /// Thrown when or is empty or whitespace.
- public static FoundryAgent AsAIAgent(
- this AIProjectClient aiProjectClient,
- string model,
- string instructions,
- string? name = null,
- string? description = null,
- IList? tools = null,
- Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null,
- IServiceProvider? services = null)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNullOrWhitespace(model);
- Throw.IfNullOrWhitespace(instructions);
-
- ChatClientAgentOptions options = new()
- {
- Name = name,
- Description = description,
- ChatOptions = new ChatOptions
- {
- ModelId = model,
- Instructions = instructions,
- Tools = tools,
- },
- };
-
- return new FoundryAgent(aiProjectClient, CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services));
- }
-
- ///
- /// Creates a non-versioned backed by the project's Responses API using the specified options.
- ///
- /// The to use for Responses API calls. Cannot be .
- /// Configuration options that control the agent's behavior. is required.
- /// Provides a way to customize the creation of the underlying used by the agent.
- /// Optional logger factory for creating loggers used by the agent.
- /// An optional to use for resolving services required by the instances being invoked.
- /// A backed by the project's Responses API.
- /// Thrown when or is .
- /// Thrown when does not specify .
- public static FoundryAgent AsAIAgent(
- this AIProjectClient aiProjectClient,
- ChatClientAgentOptions options,
- Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null,
- IServiceProvider? services = null)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(options);
-
- return new FoundryAgent(aiProjectClient, CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services));
- }
-
- #region Private
-
- private static readonly ModelReaderWriterOptions s_modelWriterOptionsWire = new("W");
-
- ///
- /// Asynchronously retrieves an agent record by name using the protocol method to inject user-agent headers.
- ///
- internal static async Task GetAgentRecordByNameAsync(AIProjectClient aiProjectClient, string agentName, CancellationToken cancellationToken)
- {
- ClientResult protocolResponse = await aiProjectClient.Agents.GetAgentAsync(agentName, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
- var rawResponse = protocolResponse.GetRawResponse();
- AgentRecord? result = ModelReaderWriter.Read(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
- return result ?? throw new InvalidOperationException($"Agent with name '{agentName}' not found.");
- }
-
- ///
- /// Asynchronously creates an agent version using the protocol method to inject user-agent headers.
- ///
- internal static async Task CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, CancellationToken cancellationToken)
- {
- BinaryData serializedOptions = ModelReaderWriter.Write(creationOptions, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
- BinaryContent content = BinaryContent.Create(serializedOptions);
- ClientResult protocolResponse = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, content, foundryFeatures: null, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
- var rawResponse = protocolResponse.GetRawResponse();
- AgentVersion? result = ModelReaderWriter.Read(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
- return result ?? throw new InvalidOperationException($"Failed to create agent version for agent '{agentName}'.");
- }
-
- private static async Task CreateAIAgentAsync(
- this AIProjectClient aiProjectClient,
- string name,
- IList? tools,
- AgentVersionCreationOptions creationOptions,
- Func? clientFactory,
- IServiceProvider? services,
- CancellationToken cancellationToken)
- {
- var allowDeclarativeMode = tools is not { Count: > 0 };
-
- if (!allowDeclarativeMode)
- {
- ApplyToolsToAgentDefinition(creationOptions.Definition, tools);
- }
-
- AgentVersion agentVersion = await CreateAgentVersionWithProtocolAsync(aiProjectClient, name, creationOptions, cancellationToken).ConfigureAwait(false);
-
- return new FoundryAgent(aiProjectClient, AsChatClientAgent(aiProjectClient, agentVersion, tools, clientFactory, !allowDeclarativeMode, services));
- }
-
- ///
- /// Creates an agent version with optional tool application, using the protocol method to inject user-agent headers.
- ///
- internal static async Task CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, IList? tools, CancellationToken cancellationToken)
- {
- if (tools is { Count: > 0 })
- {
- ApplyToolsToAgentDefinition(creationOptions.Definition, tools);
- }
-
- return await CreateAgentVersionWithProtocolAsync(aiProjectClient, agentName, creationOptions, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Creates an agent version from , mapping options to a .
- ///
- internal static async Task CreateAgentVersionFromOptionsAsync(
- AIProjectClient aiProjectClient,
- string model,
- ChatClientAgentOptions options,
- CancellationToken cancellationToken)
- {
- PromptAgentDefinition agentDefinition = new(model)
- {
- Instructions = options.ChatOptions?.Instructions,
- Temperature = options.ChatOptions?.Temperature,
- TopP = options.ChatOptions?.TopP,
- TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
- };
-
- if (options.ChatOptions?.Reasoning is { } reasoning)
- {
- agentDefinition.ReasoningOptions = ToResponseReasoningOptions(reasoning);
- }
- else if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions)
- {
- agentDefinition.ReasoningOptions = respCreationOptions.ReasoningOptions;
- }
-
- ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools);
-
- AgentVersionCreationOptions creationOptions = new(agentDefinition);
- if (!string.IsNullOrWhiteSpace(options.Description))
- {
- creationOptions.Description = options.Description;
- }
-
- return await CreateAgentVersionWithProtocolAsync(aiProjectClient, options.Name!, creationOptions, cancellationToken).ConfigureAwait(false);
- }
-
- /// Creates a with the specified options.
- internal static ChatClientAgent CreateChatClientAgent(
- AIProjectClient aiProjectClient,
- AgentVersion agentVersion,
- ChatClientAgentOptions agentOptions,
- Func? clientFactory,
- IServiceProvider? services)
- {
- IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentVersion, agentOptions.ChatOptions);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- return new ChatClientAgent(chatClient, agentOptions, services: services);
- }
-
- internal static ChatClientAgent CreateResponsesChatClientAgent(
- AIProjectClient aiProjectClient,
- ChatClientAgentOptions agentOptions,
- Func? clientFactory,
- ILoggerFactory? loggerFactory,
- IServiceProvider? services)
- {
- Throw.IfNull(aiProjectClient);
- Throw.IfNull(agentOptions);
- Throw.IfNull(agentOptions.ChatOptions);
- Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId);
-
- IChatClient chatClient = new AzureAIProjectResponsesChatClient(aiProjectClient, agentOptions.ChatOptions.ModelId);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
- }
-
- /// This method creates an with the specified ChatClientAgentOptions.
- private static ChatClientAgent AsChatClientAgent(
- AIProjectClient aiProjectClient,
- AgentVersion agentVersion,
- ChatClientAgentOptions agentOptions,
- Func? clientFactory,
- IServiceProvider? services)
- => CreateChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services);
-
- /// This method creates an with the specified ChatClientAgentOptions.
- private static ChatClientAgent AsChatClientAgent(
- AIProjectClient aiProjectClient,
- AgentRecord agentRecord,
- ChatClientAgentOptions agentOptions,
- Func? clientFactory,
- IServiceProvider? services)
- {
- IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentRecord, agentOptions.ChatOptions);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- return new ChatClientAgent(chatClient, agentOptions, services: services);
- }
-
- /// This method creates an with the specified ChatClientAgentOptions.
- private static ChatClientAgent AsChatClientAgent(
- AIProjectClient aiProjectClient,
- AgentReference agentReference,
- ChatClientAgentOptions agentOptions,
- Func? clientFactory,
- IServiceProvider? services)
- {
- IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentReference, defaultModelId: null, agentOptions.ChatOptions);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- return new ChatClientAgent(chatClient, agentOptions, services: services);
- }
-
- /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters.
- private static ChatClientAgent AsChatClientAgent(
- AIProjectClient AIProjectClient,
- AgentVersion agentVersion,
- IList? tools,
- Func? clientFactory,
- bool requireInvocableTools,
- IServiceProvider? services)
- => AsChatClientAgent(
- AIProjectClient,
- agentVersion,
- CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
- clientFactory,
- services);
-
- /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters.
- private static ChatClientAgent AsChatClientAgent(
- AIProjectClient AIProjectClient,
- AgentRecord agentRecord,
- IList? tools,
- Func? clientFactory,
- bool requireInvocableTools,
- IServiceProvider? services)
- => AsChatClientAgent(
- AIProjectClient,
- agentRecord,
- CreateChatClientAgentOptions(agentRecord.GetLatestVersion(), new ChatOptions() { Tools = tools }, requireInvocableTools),
- clientFactory,
- services);
-
- ///
- /// This method creates for the specified and the provided tools.
- ///
- /// The agent version.
- /// The to use when interacting with the agent.
- /// Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.
- /// The created .
- /// Thrown when the agent definition requires in-process tools but none were provided.
- /// Thrown when the agent definition required tools were not provided.
- ///
- /// 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.
- ///
- internal static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
- {
- var agentDefinition = agentVersion.Definition;
-
- List? agentTools = null;
- if (agentDefinition is PromptAgentDefinition { 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))
- {
- throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter.");
- }
-
- // Agregate all missing tools for a single error message.
- List? missingTools = null;
-
- // Check function tools
- foreach (ResponseTool responseTool in definitionTools)
- {
- if (responseTool is FunctionTool functionTool)
- {
- // Check if a tool with the same type and name exists in the provided tools.
- // Always prefer matching AIFunction when available, regardless of requireInvocableTools.
- var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name);
-
- if (matchingTool is not null)
- {
- (agentTools ??= []).Add(matchingTool!);
- continue;
- }
-
- if (requireInvocableTools)
- {
- (missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}");
- continue;
- }
- }
-
- (agentTools ??= []).Add(responseTool.AsAITool());
- }
-
- if (requireInvocableTools && missingTools is { Count: > 0 })
- {
- throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}");
- }
- }
-
- // Use the agent version's ID if available, otherwise generate one from name and version.
- // This handles cases where hosted agents (like MCP agents) may not have an ID assigned.
- var version = string.IsNullOrWhiteSpace(agentVersion.Version) ? "latest" : agentVersion.Version;
- var agentId = string.IsNullOrWhiteSpace(agentVersion.Id)
- ? $"{agentVersion.Name}:{version}"
- : agentVersion.Id;
-
- var agentOptions = new ChatClientAgentOptions()
- {
- Id = agentId,
- Name = agentVersion.Name,
- Description = agentVersion.Description,
- };
-
- if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
- {
- agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
- agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
- agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
- agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
- }
-
- if (agentTools is { Count: > 0 })
- {
- agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
- agentOptions.ChatOptions.Tools = agentTools;
- }
-
- return agentOptions;
- }
-
- ///
- /// Creates a new instance of configured for the specified agent version and
- /// optional base options.
- ///
- /// The agent version to use when configuring the chat client agent options.
- /// An optional instance whose relevant properties will be copied to the
- /// returned options. If , only default values are used.
- /// Specifies whether the returned options must include invocable tools. Set to to require
- /// invocable tools; otherwise, .
- /// A instance configured according to the specified parameters.
- internal static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatClientAgentOptions? options, bool requireInvocableTools)
- {
- var agentOptions = CreateChatClientAgentOptions(agentVersion, options?.ChatOptions, requireInvocableTools);
- if (options is not null)
- {
- agentOptions.AIContextProviders = options.AIContextProviders;
- agentOptions.ChatHistoryProvider = options.ChatHistoryProvider;
- agentOptions.UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs;
- }
-
- return agentOptions;
- }
-
- ///
- /// Adds the specified AI tools to a prompt agent definition, while also ensuring that all invocable tools are provided.
- ///
- /// The agent definition to which the tools will be applied. Must be a PromptAgentDefinition to support tools.
- /// A list of AI tools to add to the agent definition. If null or empty, no tools are added.
- /// Thrown if tools were provided but is not a .
- /// When providing functions, they need to be invokable AIFunctions.
- private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList? tools)
- {
- if (tools is { Count: > 0 })
- {
- if (agentDefinition is not PromptAgentDefinition promptAgentDefinition)
- {
- throw new ArgumentException("Only prompt agent definitions support tools.", nameof(agentDefinition));
- }
-
- // When tools are provided, those should represent the complete set of tools for the agent definition.
- // This is particularly important for existing agents so no duplication happens for what was already defined.
- promptAgentDefinition.Tools.Clear();
-
- foreach (var tool in tools)
- {
- // Ensure that any AIFunctions provided are In-Proc, not just the declarations.
- if (tool is not AIFunction && (
- tool.GetService() is not null // Declarative FunctionTool converted as AsAITool()
- || tool is AIFunctionDeclaration)) // AIFunctionDeclaration type
- {
- throw new InvalidOperationException("When providing functions, they need to be invokable AIFunctions. AIFunctions can be created correctly using AIFunctionFactory.Create");
- }
-
- promptAgentDefinition.Tools.Add(
- // If this is a converted ResponseTool as AITool, we can directly retrieve the ResponseTool instance from GetService.
- tool.GetService()
- // Otherwise we should be able to convert existing MEAI Tool abstractions into OpenAI ResponseTools
- ?? tool.AsOpenAIResponseTool()
- ?? throw new InvalidOperationException("The provided AITool could not be converted to a ResponseTool, ensure that the AITool was created using responseTool.AsAITool() extension."));
- }
- }
- }
-
- private static ResponseTextFormat? ToOpenAIResponseTextFormat(ChatResponseFormat? format, ChatOptions? options = null) =>
- format switch
- {
- ChatResponseFormatText => ResponseTextFormat.CreateTextFormat(),
-
- ChatResponseFormatJson jsonFormat when StrictSchemaTransformCache.GetOrCreateTransformedSchema(jsonFormat) is { } jsonSchema =>
- ResponseTextFormat.CreateJsonSchemaFormat(
- jsonFormat.SchemaName ?? "json_schema",
- BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, AgentClientJsonContext.Default.JsonElement)),
- jsonFormat.SchemaDescription,
- HasStrict(options?.AdditionalProperties)),
-
- ChatResponseFormatJson => ResponseTextFormat.CreateJsonObjectFormat(),
-
- _ => null,
- };
-
- /// Key into AdditionalProperties used to store a strict option.
- private const string StrictKey = "strictJsonSchema";
-
- /// Gets whether the properties specify that strict schema handling is desired.
- private static bool? HasStrict(IReadOnlyDictionary? additionalProperties) =>
- additionalProperties?.TryGetValue(StrictKey, out object? strictObj) is true &&
- strictObj is bool strictValue ?
- strictValue : null;
-
- ///
- /// Gets the JSON schema transformer cache conforming to OpenAI strict / structured output restrictions per
- /// https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas.
- ///
- private static AIJsonSchemaTransformCache StrictSchemaTransformCache { get; } = new(new()
- {
- DisallowAdditionalProperties = true,
- ConvertBooleanSchemas = true,
- MoveDefaultKeywordToDescription = true,
- RequireAllProperties = true,
- TransformSchemaNode = (ctx, node) =>
- {
- // Move content from common but unsupported properties to description. In particular, we focus on properties that
- // the AIJsonUtilities schema generator might produce and/or that are explicitly mentioned in the OpenAI documentation.
-
- if (node is JsonObject schemaObj)
- {
- StringBuilder? additionalDescription = null;
-
- ReadOnlySpan unsupportedProperties =
- [
- // Produced by AIJsonUtilities but not in allow list at https://platform.openai.com/docs/guides/structured-outputs#supported-properties:
- "contentEncoding", "contentMediaType", "not",
-
- // Explicitly mentioned at https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#key-ordering as being unsupported with some models:
- "minLength", "maxLength", "pattern", "format",
- "minimum", "maximum", "multipleOf",
- "patternProperties",
- "minItems", "maxItems",
-
- // Explicitly mentioned at https://learn.microsoft.com/azure/ai-services/openai/how-to/structured-outputs?pivots=programming-language-csharp&tabs=python-secure%2Cdotnet-entra-id#unsupported-type-specific-keywords
- // as being unsupported with Azure OpenAI:
- "unevaluatedProperties", "propertyNames", "minProperties", "maxProperties",
- "unevaluatedItems", "contains", "minContains", "maxContains", "uniqueItems",
- ];
-
- foreach (string propName in unsupportedProperties)
- {
- if (schemaObj[propName] is { } propNode)
- {
- _ = schemaObj.Remove(propName);
- AppendLine(ref additionalDescription, propName, propNode);
- }
- }
-
- if (additionalDescription is not null)
- {
- schemaObj["description"] = schemaObj["description"] is { } descriptionNode && descriptionNode.GetValueKind() == JsonValueKind.String ?
- $"{descriptionNode.GetValue()}{Environment.NewLine}{additionalDescription}" :
- additionalDescription.ToString();
- }
-
- return node;
-
- static void AppendLine(ref StringBuilder? sb, string propName, JsonNode propNode)
- {
- sb ??= new();
-
- if (sb.Length > 0)
- {
- _ = sb.AppendLine();
- }
-
- _ = sb.Append(propName).Append(": ").Append(propNode);
- }
- }
-
- return node;
- },
- });
-
- ///
- /// This class is a no-op implementation of to be used to honor the argument passed
- /// while triggering avoiding any unexpected exception on the caller implementation.
- ///
- private sealed class NoOpChatClient : IChatClient
- {
- public void Dispose() { }
-
- public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
- => Task.FromResult(new ChatResponse());
-
- public object? GetService(Type serviceType, object? serviceKey = null) => null;
-
- public async IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- yield return new ChatResponseUpdate();
- }
- }
- #endregion
-
-#if NET
- [GeneratedRegex("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")]
- private static partial Regex AgentNameValidationRegex();
-#else
- private static Regex AgentNameValidationRegex() => new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$");
-#endif
-
- internal static string ThrowIfInvalidAgentName(string? name)
- {
- Throw.IfNullOrWhitespace(name);
- if (!AgentNameValidationRegex().IsMatch(name))
- {
- throw new ArgumentException("Agent name must be 1-63 characters long, start and end with an alphanumeric character, and can only contain alphanumeric characters or hyphens.", nameof(name));
- }
- return name;
- }
-
- private static ResponseReasoningOptions? ToResponseReasoningOptions(ReasoningOptions reasoning)
- {
- ResponseReasoningEffortLevel? effortLevel = reasoning.Effort switch
- {
- ReasoningEffort.Low => ResponseReasoningEffortLevel.Low,
- ReasoningEffort.Medium => ResponseReasoningEffortLevel.Medium,
- ReasoningEffort.High => ResponseReasoningEffortLevel.High,
- ReasoningEffort.ExtraHigh => ResponseReasoningEffortLevel.High,
- _ => null,
- };
-
- ResponseReasoningSummaryVerbosity? summary = reasoning.Output switch
- {
- ReasoningOutput.Summary => ResponseReasoningSummaryVerbosity.Concise,
- ReasoningOutput.Full => ResponseReasoningSummaryVerbosity.Detailed,
- _ => null,
- };
-
- if (effortLevel is null && summary is null)
- {
- return null;
- }
-
- return new ResponseReasoningOptions
- {
- ReasoningEffortLevel = effortLevel,
- ReasoningSummaryVerbosity = summary,
- };
- }
-}
-
-[JsonSerializable(typeof(JsonElement))]
-internal sealed partial class AgentClientJsonContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClient.cs
similarity index 98%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClient.cs
index ec788233ed..b33af228b2 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClient.cs
@@ -1,7 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
@@ -10,7 +14,7 @@ using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
using OpenAI.Responses;
-namespace Microsoft.Agents.AI.AzureAI;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Provides a chat client implementation that integrates with Azure AI Agents, enabling chat interactions using
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs
new file mode 100644
index 0000000000..d83299de90
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs
@@ -0,0 +1,436 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using Azure.AI.Extensions.OpenAI;
+using Azure.AI.Projects.Agents;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Foundry;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+using OpenAI.Responses;
+
+namespace Azure.AI.Projects;
+
+///
+/// Provides extension methods for .
+///
+[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
+public static partial class AzureAIProjectChatClientExtensions
+{
+ ///
+ /// Uses an existing server side agent, wrapped as a using the provided and .
+ ///
+ /// The to create the with. Cannot be .
+ /// The representing the name and version of the server side agent to create a for. Cannot be .
+ /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// A instance that can be used to perform operations based on the latest version of the named Azure AI Agent.
+ /// Thrown when or is .
+ /// The agent with the specified name was not found.
+ ///
+ /// When instantiating a by using an , minimal information will be available about the agent in the instance level, and any logic that relies
+ /// on to retrieve information about the agent like will receive as the result.
+ ///
+ public static FoundryAgent AsAIAgent(
+ this AIProjectClient aiProjectClient,
+ AgentReference agentReference,
+ IList? tools = null,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNull(agentReference);
+ ThrowIfInvalidAgentName(agentReference.Name);
+
+ var innerAgent = AsChatClientAgent(
+ aiProjectClient,
+ agentReference,
+ new ChatClientAgentOptions()
+ {
+ Id = $"{agentReference.Name}:{agentReference.Version}",
+ Name = agentReference.Name,
+ ChatOptions = new() { Tools = tools },
+ },
+ clientFactory,
+ services);
+
+ return new FoundryAgent(aiProjectClient, innerAgent);
+ }
+
+ ///
+ /// Uses an existing server side agent, wrapped as a using the provided and .
+ ///
+ /// The client used to interact with Azure AI Agents. Cannot be .
+ /// The agent record to be converted. The latest version will be used. Cannot be .
+ /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// A instance that can be used to perform operations based on the latest version of the Azure AI Agent.
+ /// Thrown when or is .
+ public static FoundryAgent AsAIAgent(
+ this AIProjectClient aiProjectClient,
+ AgentRecord agentRecord,
+ IList? tools = null,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNull(agentRecord);
+
+ var allowDeclarativeMode = tools is not { Count: > 0 };
+
+ var innerAgent = AsChatClientAgent(
+ aiProjectClient,
+ agentRecord,
+ tools,
+ clientFactory,
+ !allowDeclarativeMode,
+ services);
+
+ return new FoundryAgent(aiProjectClient, innerAgent);
+ }
+
+ ///
+ /// Uses an existing server side agent, wrapped as a using the provided and .
+ ///
+ /// The client used to interact with Azure AI Agents. Cannot be .
+ /// The agent version to be converted. Cannot be .
+ /// In-process invocable tools to be provided. If no tools are provided manual handling will be necessary to invoke in-process tools.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// A instance that can be used to perform operations based on the provided version of the Azure AI Agent.
+ /// Thrown when or is .
+ public static FoundryAgent AsAIAgent(
+ this AIProjectClient aiProjectClient,
+ AgentVersion agentVersion,
+ IList? tools = null,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNull(agentVersion);
+
+ var allowDeclarativeMode = tools is not { Count: > 0 };
+
+ var innerAgent = AsChatClientAgent(
+ aiProjectClient,
+ agentVersion,
+ tools,
+ clientFactory,
+ !allowDeclarativeMode,
+ services);
+
+ return new FoundryAgent(aiProjectClient, innerAgent);
+ }
+
+ ///
+ /// Creates a non-versioned backed by the project's Responses API using the specified model and instructions.
+ ///
+ /// The to use for Responses API calls. Cannot be .
+ /// The model deployment name to use for the agent. Cannot be or whitespace.
+ /// The instructions that guide the agent's behavior. Cannot be or whitespace.
+ /// Optional name for the agent.
+ /// Optional human-readable description for the agent.
+ /// Optional collection of tools that the agent can invoke during conversations.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// Optional logger factory for creating loggers used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// A backed by the project's Responses API.
+ /// Thrown when is .
+ /// Thrown when or is empty or whitespace.
+ public static ChatClientAgent AsAIAgent(
+ this AIProjectClient aiProjectClient,
+ string model,
+ string instructions,
+ string? name = null,
+ string? description = null,
+ IList? tools = null,
+ Func? clientFactory = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNullOrWhitespace(model);
+ Throw.IfNullOrWhitespace(instructions);
+
+ ChatClientAgentOptions options = new()
+ {
+ Name = name,
+ Description = description,
+ ChatOptions = new ChatOptions
+ {
+ ModelId = model,
+ Instructions = instructions,
+ Tools = tools,
+ },
+ };
+
+ return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
+ }
+
+ ///
+ /// Creates a non-versioned backed by the project's Responses API using the specified options.
+ ///
+ /// The to use for Responses API calls. Cannot be .
+ /// Configuration options that control the agent's behavior. is required.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// Optional logger factory for creating loggers used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// A backed by the project's Responses API.
+ /// Thrown when or is .
+ /// Thrown when does not specify .
+ public static ChatClientAgent AsAIAgent(
+ this AIProjectClient aiProjectClient,
+ ChatClientAgentOptions options,
+ Func? clientFactory = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNull(options);
+
+ return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
+ }
+
+ #region Private
+
+ /// Creates a with the specified options.
+ private static ChatClientAgent CreateChatClientAgent(
+ AIProjectClient aiProjectClient,
+ AgentVersion agentVersion,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ IServiceProvider? services)
+ {
+ IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentVersion, agentOptions.ChatOptions);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, agentOptions, services: services);
+ }
+
+ private static ChatClientAgent CreateResponsesChatClientAgent(
+ AIProjectClient aiProjectClient,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ ILoggerFactory? loggerFactory,
+ IServiceProvider? services)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNull(agentOptions);
+ Throw.IfNull(agentOptions.ChatOptions);
+ Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId);
+
+ IChatClient chatClient = aiProjectClient
+ .GetProjectOpenAIClient()
+ .GetResponsesClient()
+ .AsIChatClient(agentOptions.ChatOptions.ModelId);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
+ }
+
+ /// This method creates an with the specified ChatClientAgentOptions.
+ private static ChatClientAgent AsChatClientAgent(
+ AIProjectClient aiProjectClient,
+ AgentVersion agentVersion,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ IServiceProvider? services)
+ => CreateChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services);
+
+ /// This method creates an with the specified ChatClientAgentOptions.
+ private static ChatClientAgent AsChatClientAgent(
+ AIProjectClient aiProjectClient,
+ AgentRecord agentRecord,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ IServiceProvider? services)
+ {
+ IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentRecord, agentOptions.ChatOptions);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, agentOptions, services: services);
+ }
+
+ /// This method creates an with the specified ChatClientAgentOptions.
+ private static ChatClientAgent AsChatClientAgent(
+ AIProjectClient aiProjectClient,
+ AgentReference agentReference,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ IServiceProvider? services)
+ {
+ IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentReference, defaultModelId: null, agentOptions.ChatOptions);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, agentOptions, services: services);
+ }
+
+ /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters.
+ private static ChatClientAgent AsChatClientAgent(
+ AIProjectClient AIProjectClient,
+ AgentVersion agentVersion,
+ IList? tools,
+ Func? clientFactory,
+ bool requireInvocableTools,
+ IServiceProvider? services)
+ => AsChatClientAgent(
+ AIProjectClient,
+ agentVersion,
+ CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
+ clientFactory,
+ services);
+
+ /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters.
+ private static ChatClientAgent AsChatClientAgent(
+ AIProjectClient AIProjectClient,
+ AgentRecord agentRecord,
+ IList? tools,
+ Func? clientFactory,
+ bool requireInvocableTools,
+ IServiceProvider? services)
+ => AsChatClientAgent(
+ AIProjectClient,
+ agentRecord,
+ CreateChatClientAgentOptions(agentRecord.GetLatestVersion(), new ChatOptions() { Tools = tools }, requireInvocableTools),
+ clientFactory,
+ services);
+
+ ///
+ /// This method creates for the specified and the provided tools.
+ ///
+ /// The agent version.
+ /// The to use when interacting with the agent.
+ /// Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.
+ /// The created .
+ /// Thrown when the agent definition requires in-process tools but none were provided.
+ /// Thrown when the agent definition required tools were not provided.
+ ///
+ /// 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.
+ ///
+ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
+ {
+ var agentDefinition = agentVersion.Definition;
+
+ List? agentTools = null;
+ if (agentDefinition is PromptAgentDefinition { 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))
+ {
+ throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter.");
+ }
+
+ // Agregate all missing tools for a single error message.
+ List? missingTools = null;
+
+ // Check function tools
+ foreach (ResponseTool responseTool in definitionTools)
+ {
+ if (responseTool is FunctionTool functionTool)
+ {
+ // Check if a tool with the same type and name exists in the provided tools.
+ // Always prefer matching AIFunction when available, regardless of requireInvocableTools.
+ var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name);
+
+ if (matchingTool is not null)
+ {
+ (agentTools ??= []).Add(matchingTool!);
+ continue;
+ }
+
+ if (requireInvocableTools)
+ {
+ (missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}");
+ continue;
+ }
+ }
+
+ (agentTools ??= []).Add(responseTool.AsAITool());
+ }
+
+ if (requireInvocableTools && missingTools is { Count: > 0 })
+ {
+ throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}");
+ }
+ }
+
+ // Use the agent version's ID if available, otherwise generate one from name and version.
+ // This handles cases where hosted agents (like MCP agents) may not have an ID assigned.
+ var version = string.IsNullOrWhiteSpace(agentVersion.Version) ? "latest" : agentVersion.Version;
+ var agentId = string.IsNullOrWhiteSpace(agentVersion.Id)
+ ? $"{agentVersion.Name}:{version}"
+ : agentVersion.Id;
+
+ var agentOptions = new ChatClientAgentOptions()
+ {
+ Id = agentId,
+ Name = agentVersion.Name,
+ Description = agentVersion.Description,
+ };
+
+ if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
+ {
+ agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
+ agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
+ agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
+ agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
+ }
+
+ if (agentTools is { Count: > 0 })
+ {
+ agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
+ agentOptions.ChatOptions.Tools = agentTools;
+ }
+
+ return agentOptions;
+ }
+
+#if NET
+ [GeneratedRegex("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")]
+ private static partial Regex AgentNameValidationRegex();
+#else
+ private static Regex AgentNameValidationRegex() => new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$");
+#endif
+
+ internal static string ThrowIfInvalidAgentName(string? name)
+ {
+ Throw.IfNullOrWhitespace(name);
+ if (!AgentNameValidationRegex().IsMatch(name))
+ {
+ throw new ArgumentException("Agent name must be 1-63 characters long, start and end with an alphanumeric character, and can only contain alphanumeric characters or hyphens.", nameof(name));
+ }
+ return name;
+ }
+}
+
+[JsonSerializable(typeof(JsonElement))]
+internal sealed partial class AgentClientJsonContext : JsonSerializerContext;
+
+#endregion
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectResponsesChatClient.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectResponsesChatClient.cs
similarity index 95%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectResponsesChatClient.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectResponsesChatClient.cs
index 48bb20d766..a768d102f8 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectResponsesChatClient.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectResponsesChatClient.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using Azure.AI.Projects;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
-namespace Microsoft.Agents.AI.AzureAI;
+namespace Microsoft.Agents.AI.Foundry;
#pragma warning disable OPENAI001
internal sealed class AzureAIProjectResponsesChatClient : DelegatingChatClient
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/CompatibilitySuppressions.xml b/dotnet/src/Microsoft.Agents.AI.Foundry/CompatibilitySuppressions.xml
similarity index 100%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/CompatibilitySuppressions.xml
rename to dotnet/src/Microsoft.Agents.AI.Foundry/CompatibilitySuppressions.xml
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/FoundryAITool.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAITool.cs
similarity index 99%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/FoundryAITool.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAITool.cs
index 80ed48e1df..79e221f02f 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/FoundryAITool.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAITool.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.AI;
@@ -8,7 +10,7 @@ using OpenAI.Responses;
#pragma warning disable OPENAI001
-namespace Microsoft.Agents.AI.AzureAI;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Provides factory methods for creating instances from Microsoft Foundry and OpenAI response tools.
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/FoundryAgent.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs
similarity index 89%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/FoundryAgent.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs
index 66fa93e39f..b1d97a5c9c 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/FoundryAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs
@@ -1,7 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.ClientModel;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Microsoft.Extensions.AI;
@@ -9,7 +13,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
-namespace Microsoft.Agents.AI.AzureAI;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Provides an that uses Microsoft Foundry for AI agent capabilities.
@@ -163,7 +167,29 @@ public sealed class FoundryAgent : DelegatingAIAgent
},
};
- return AzureAIProjectChatClientExtensions.CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
+ return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
+ }
+
+ private static ChatClientAgent CreateResponsesChatClientAgent(
+ AIProjectClient aiProjectClient,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ ILoggerFactory? loggerFactory,
+ IServiceProvider? services)
+ {
+ Throw.IfNull(aiProjectClient);
+ Throw.IfNull(agentOptions);
+ Throw.IfNull(agentOptions.ChatOptions);
+ Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId);
+
+ IChatClient chatClient = new AzureAIProjectResponsesChatClient(aiProjectClient, agentOptions.ChatOptions.ModelId);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
}
private static ChatClientAgent CreateInnerAgentFromEndpoint(
diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryJsonUtilities.cs
similarity index 84%
rename from dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryJsonUtilities.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryJsonUtilities.cs
index 1a0dd4f4e2..1ed4046de0 100644
--- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryJsonUtilities.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryJsonUtilities.cs
@@ -1,13 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
+using Microsoft.Shared.DiagnosticIds;
-namespace Microsoft.Agents.AI.FoundryMemory;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Provides JSON serialization utilities for the Foundry Memory provider.
///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
internal static class FoundryMemoryJsonUtilities
{
///
@@ -33,4 +36,5 @@ internal static class FoundryMemoryJsonUtilities
WriteIndented = false)]
[JsonSerializable(typeof(FoundryMemoryProviderScope))]
[JsonSerializable(typeof(FoundryMemoryProvider.State))]
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
internal partial class FoundryMemoryJsonContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProvider.cs
similarity index 99%
rename from dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProvider.cs
index 93b343b2de..cc92c40a41 100644
--- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProvider.cs
@@ -16,7 +16,7 @@ using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
using OpenAI.Responses;
-namespace Microsoft.Agents.AI.FoundryMemory;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Provides a Microsoft Foundry Memory backed that persists conversation messages as memories
@@ -27,7 +27,7 @@ namespace Microsoft.Agents.AI.FoundryMemory;
/// for new invocations using the memory search endpoint. Retrieved memories are injected as user messages
/// to the model, prefixed by a configurable context prompt.
///
-[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FoundryMemoryProvider : AIContextProvider
{
private const string DefaultContextPrompt = "## Memories\nConsider the following memories when answering user questions:";
diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProviderOptions.cs
similarity index 95%
rename from dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProviderOptions.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProviderOptions.cs
index cf4fb5ab15..668db44112 100644
--- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProviderOptions.cs
@@ -2,14 +2,17 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Compliance.Redaction;
+using Microsoft.Shared.DiagnosticIds;
-namespace Microsoft.Agents.AI.FoundryMemory;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Options for configuring the .
///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FoundryMemoryProviderOptions
{
///
diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProviderScope.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProviderScope.cs
similarity index 89%
rename from dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProviderScope.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProviderScope.cs
index 6646c482a3..769aff7370 100644
--- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProviderScope.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/FoundryMemoryProviderScope.cs
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
-namespace Microsoft.Agents.AI.FoundryMemory;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Allows scoping of memories for the .
@@ -13,6 +15,7 @@ namespace Microsoft.Agents.AI.FoundryMemory;
/// Common patterns include using a user ID, team ID, or other unique identifier
/// to partition memories across different contexts.
///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FoundryMemoryProviderScope
{
///
diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/AIProjectClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/MemoryStoreExtensions.cs
similarity index 93%
rename from dotnet/src/Microsoft.Agents.AI.FoundryMemory/AIProjectClientExtensions.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/Memory/MemoryStoreExtensions.cs
index 9e24703d92..a696a33e5a 100644
--- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/AIProjectClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Memory/MemoryStoreExtensions.cs
@@ -5,12 +5,12 @@ using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
-namespace Microsoft.Agents.AI.FoundryMemory;
+namespace Microsoft.Agents.AI.Foundry;
///
/// Internal extension methods for to provide MemoryStores helper operations.
///
-internal static class AIProjectClientExtensions
+internal static class MemoryStoreExtensions
{
///
/// Creates a memory store if it doesn't already exist.
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj b/dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj
similarity index 63%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj
rename to dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj
index 0cd8690126..03d84c9d47 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj
@@ -2,21 +2,29 @@
true
- enable
true
+ $(NoWarn);OPENAI001
+
+
+
+ false
+
+
true
true
+ true
+
@@ -30,4 +38,9 @@
Provides Microsoft Agent Framework support for Foundry Agents.
+
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/ProjectResponsesClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/ProjectResponsesClientExtensions.cs
similarity index 99%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/ProjectResponsesClientExtensions.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/ProjectResponsesClientExtensions.cs
index 5a899d5076..cd253776a8 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/ProjectResponsesClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/ProjectResponsesClientExtensions.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.AI;
using Microsoft.Shared.DiagnosticIds;
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/RequestOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/RequestOptionsExtensions.cs
similarity index 96%
rename from dotnet/src/Microsoft.Agents.AI.AzureAI/RequestOptionsExtensions.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry/RequestOptionsExtensions.cs
index 2705611b57..03e48e293b 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/RequestOptionsExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/RequestOptionsExtensions.cs
@@ -1,7 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel.Primitives;
+using System.Collections.Generic;
using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
namespace Microsoft.Agents.AI;
diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj b/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj
deleted file mode 100644
index 7abc3d0bcc..0000000000
--- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
- preview
- $(NoWarn);OPENAI001
-
-
-
- true
- true
- true
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Microsoft Agent Framework - Azure AI Foundry Memory integration
- Provides Azure AI Foundry Memory integration for Microsoft Agent Framework.
-
-
-
-
-
-
-
-
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/AzureAgentProvider.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/AzureAgentProvider.cs
similarity index 100%
rename from dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/AzureAgentProvider.cs
rename to dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/AzureAgentProvider.cs
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/CompatibilitySuppressions.xml b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/CompatibilitySuppressions.xml
similarity index 80%
rename from dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/CompatibilitySuppressions.xml
rename to dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/CompatibilitySuppressions.xml
index 3454984fae..fbf1db84c7 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/CompatibilitySuppressions.xml
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/CompatibilitySuppressions.xml
@@ -1,39 +1,39 @@
-
+
CP0002
M:Microsoft.Agents.AI.Workflows.Declarative.AzureAgentProvider.get_OpenAIClientOptions
- lib/net10.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
- lib/net10.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
+ lib/net10.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
+ lib/net10.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
true
CP0002
M:Microsoft.Agents.AI.Workflows.Declarative.AzureAgentProvider.get_OpenAIClientOptions
- lib/net472/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
- lib/net472/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
+ lib/net472/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
+ lib/net472/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
true
CP0002
M:Microsoft.Agents.AI.Workflows.Declarative.AzureAgentProvider.get_OpenAIClientOptions
- lib/net8.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
- lib/net8.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
+ lib/net8.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
+ lib/net8.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
true
CP0002
M:Microsoft.Agents.AI.Workflows.Declarative.AzureAgentProvider.get_OpenAIClientOptions
- lib/net9.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
- lib/net9.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
+ lib/net9.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
+ lib/net9.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
true
CP0002
M:Microsoft.Agents.AI.Workflows.Declarative.AzureAgentProvider.get_OpenAIClientOptions
- lib/netstandard2.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
- lib/netstandard2.0/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.dll
+ lib/netstandard2.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
+ lib/netstandard2.0/Microsoft.Agents.AI.Workflows.Declarative.Foundry.dll
true
\ No newline at end of file
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj
similarity index 70%
rename from dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj
rename to dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj
index 5bf9f6d29e..407593536e 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj
@@ -13,10 +13,16 @@
+
+
+
+ false
+
+
- Microsoft Agent Framework Declarative Workflows Azure AI
- Provides Microsoft Agent Framework support for declarative workflows for Azure AI Agents.
+ Microsoft Agent Framework Declarative Workflows Foundry
+ Provides Microsoft Agent Framework support for declarative workflows for Microsoft Foundry Agents.
@@ -24,7 +30,7 @@
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/ICheckpointingHandle.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/ICheckpointingHandle.cs
index 74ccd8edc3..f4e159a487 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/ICheckpointingHandle.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/ICheckpointingHandle.cs
@@ -21,6 +21,15 @@ internal interface ICheckpointingHandle
///
/// Restores the system state from the specified checkpoint asynchronously.
///
+ ///
+ /// This contract is used by live runtime restore paths. Implementations may re-emit pending
+ /// external request events as part of the restore once the active event stream is ready to
+ /// observe them.
+ ///
+ /// Initial resume paths that create a new event stream should restore state first and defer
+ /// any replay until after the subscriber is attached, rather than calling this contract
+ /// directly before the stream is ready.
+ ///
/// The checkpoint information that identifies the state to restore. Cannot be null.
/// A cancellation token that can be used to cancel the restore operation.
/// A that represents the asynchronous restore operation.
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandle.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandle.cs
index bda7e61a38..16cd61f6e1 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandle.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandle.cs
@@ -36,9 +36,10 @@ internal sealed class AsyncRunHandle : ICheckpointingHandle, IAsyncDisposable
this._eventStream.Start();
- // If there are already unprocessed messages (e.g., from a checkpoint restore that happened
- // before this handle was created), signal the run loop to start processing them
- if (stepRunner.HasUnprocessedMessages)
+ // If there are already unprocessed messages or unserviced requests (e.g., from a
+ // checkpoint restore that happened before this handle was created), signal the run
+ // loop to start processing them
+ if (stepRunner.HasUnprocessedMessages || stepRunner.HasUnservicedRequests)
{
this.SignalInputToRunLoop();
}
@@ -192,13 +193,17 @@ internal sealed class AsyncRunHandle : ICheckpointingHandle, IAsyncDisposable
{
streamingEventStream.ClearBufferedEvents();
}
+ else if (this._eventStream is LockstepRunEventStream lockstepEventStream)
+ {
+ lockstepEventStream.ClearBufferedEvents();
+ }
- // Restore the workflow state - this will republish unserviced requests as new events
+ // Restore the workflow state through the live runtime-restore path.
+ // This can re-emit pending requests into the already-active event stream.
await this._checkpointingHandle.RestoreCheckpointAsync(checkpointInfo, cancellationToken).ConfigureAwait(false);
- // After restore, signal the run loop to process any restored messages
- // This is necessary because ClearBufferedEvents() doesn't signal, and the restored
- // queued messages won't automatically wake up the run loop
+ // After restore, signal the run loop to process any restored messages. Initial resume
+ // paths handle this separately when they create the event stream after restoring state.
this.SignalInputToRunLoop();
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/ISuperStepRunner.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/ISuperStepRunner.cs
index 8de0dbd5e2..ea53526604 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/ISuperStepRunner.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/ISuperStepRunner.cs
@@ -27,6 +27,14 @@ internal interface ISuperStepRunner
ConcurrentEventSink OutgoingEvents { get; }
+ ///
+ /// Re-emits s for any pending external requests.
+ /// Called by event streams after subscribing to so that
+ /// requests restored from a checkpoint are observable even when the restore happened
+ /// before the subscription was active.
+ ///
+ ValueTask RepublishPendingEventsAsync(CancellationToken cancellationToken = default);
+
ValueTask RunSuperStepAsync(CancellationToken cancellationToken);
// This cannot be cancelled
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/LockstepRunEventStream.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/LockstepRunEventStream.cs
index 72e96efb10..cdd8cc7686 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/LockstepRunEventStream.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/LockstepRunEventStream.cs
@@ -15,6 +15,7 @@ internal sealed class LockstepRunEventStream : IRunEventStream
{
private readonly CancellationTokenSource _stopCancellation = new();
private readonly InputWaiter _inputWaiter = new();
+ private ConcurrentQueue _eventSink = new();
private int _isDisposed;
private readonly ISuperStepRunner _stepRunner;
@@ -35,6 +36,8 @@ internal sealed class LockstepRunEventStream : IRunEventStream
// doesn't leak into caller code via AsyncLocal.
Activity? previousActivity = Activity.Current;
+ this._stepRunner.OutgoingEvents.EventRaised += this.OnWorkflowEventAsync;
+
this._sessionActivity = this._stepRunner.TelemetryContext.StartWorkflowSessionActivity();
this._sessionActivity?.SetTag(Tags.WorkflowId, this._stepRunner.StartExecutorId)
.SetTag(Tags.SessionId, this._stepRunner.SessionId);
@@ -56,10 +59,6 @@ internal sealed class LockstepRunEventStream : IRunEventStream
using CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(this._stopCancellation.Token, cancellationToken);
- ConcurrentQueue eventSink = [];
-
- this._stepRunner.OutgoingEvents.EventRaised += OnWorkflowEventAsync;
-
// Re-establish session as parent so the run activity nests correctly.
Activity.Current = this._sessionActivity;
@@ -73,7 +72,31 @@ internal sealed class LockstepRunEventStream : IRunEventStream
runActivity?.AddEvent(new ActivityEvent(EventNames.WorkflowStarted));
// Emit WorkflowStartedEvent to the event stream for consumers
- eventSink.Enqueue(new WorkflowStartedEvent());
+ this._eventSink.Enqueue(new WorkflowStartedEvent());
+
+ // Re-emit any pending external requests that were restored from a checkpoint
+ // before this subscription was active. For non-resume starts this is a no-op.
+ // This runs after WorkflowStartedEvent so consumers always see the started event first.
+ await this._stepRunner.RepublishPendingEventsAsync(linkedSource.Token).ConfigureAwait(false);
+
+ // When resuming from a checkpoint with only pending requests (no queued messages),
+ // the inner processing loop won't execute, so we must drain events now.
+ // For normal starts this is a no-op since the inner loop handles the drain.
+ if (!this._stepRunner.HasUnprocessedMessages)
+ {
+ var (drainedEvents, shouldHalt) = this.DrainAndFilterEvents();
+ foreach (WorkflowEvent raisedEvent in drainedEvents)
+ {
+ yield return raisedEvent;
+ }
+
+ if (shouldHalt)
+ {
+ yield break;
+ }
+
+ this.RunStatus = this._stepRunner.HasUnservicedRequests ? RunStatus.PendingRequests : RunStatus.Idle;
+ }
do
{
@@ -107,26 +130,19 @@ internal sealed class LockstepRunEventStream : IRunEventStream
yield break; // Exit if cancellation is requested
}
- bool hadRequestHaltEvent = false;
- foreach (WorkflowEvent raisedEvent in Interlocked.Exchange(ref eventSink, []))
+ var (drainedEvents, shouldHalt) = this.DrainAndFilterEvents();
+
+ foreach (WorkflowEvent raisedEvent in drainedEvents)
{
if (linkedSource.Token.IsCancellationRequested)
{
yield break; // Exit if cancellation is requested
}
- // TODO: Do we actually want to interpret this as a termination request?
- if (raisedEvent is RequestHaltEvent)
- {
- hadRequestHaltEvent = true;
- }
- else
- {
- yield return raisedEvent;
- }
+ yield return raisedEvent;
}
- if (hadRequestHaltEvent || linkedSource.Token.IsCancellationRequested)
+ if (shouldHalt || linkedSource.Token.IsCancellationRequested)
{
// If we had a completion event, we are done.
yield break;
@@ -151,25 +167,23 @@ internal sealed class LockstepRunEventStream : IRunEventStream
finally
{
this.RunStatus = this._stepRunner.HasUnservicedRequests ? RunStatus.PendingRequests : RunStatus.Idle;
- this._stepRunner.OutgoingEvents.EventRaised -= OnWorkflowEventAsync;
// Explicitly dispose the Activity so Activity.Stop fires deterministically,
// regardless of how the async iterator enumerator is disposed.
runActivity?.Dispose();
}
- ValueTask OnWorkflowEventAsync(object? sender, WorkflowEvent e)
- {
- eventSink.Enqueue(e);
- return default;
- }
-
// If we are Idle or Ended, we should break out of the loop
// If we are PendingRequests and not blocking on pending requests, we should break out of the loop
// If cancellation is requested, we should break out of the loop
bool ShouldBreak() => this.RunStatus is RunStatus.Idle or RunStatus.Ended ||
- (this.RunStatus == RunStatus.PendingRequests && !blockOnPendingRequest) ||
- linkedSource.Token.IsCancellationRequested;
+ (this.RunStatus == RunStatus.PendingRequests && !blockOnPendingRequest) ||
+ linkedSource.Token.IsCancellationRequested;
+ }
+
+ internal void ClearBufferedEvents()
+ {
+ Interlocked.Exchange(ref this._eventSink, new ConcurrentQueue());
}
///
@@ -192,6 +206,7 @@ internal sealed class LockstepRunEventStream : IRunEventStream
if (Interlocked.Exchange(ref this._isDisposed, 1) == 0)
{
this._stopCancellation.Cancel();
+ this._stepRunner.OutgoingEvents.EventRaised -= this.OnWorkflowEventAsync;
// Stop the session activity
if (this._sessionActivity is not null)
@@ -207,4 +222,32 @@ internal sealed class LockstepRunEventStream : IRunEventStream
return default;
}
+
+ private ValueTask OnWorkflowEventAsync(object? sender, WorkflowEvent e)
+ {
+ this._eventSink.Enqueue(e);
+ return default;
+ }
+
+ // Atomically drains the event sink and separates workflow events from halt signals.
+ // Used by both the early-drain (resume with pending requests only) and
+ // the inner superstep drain to keep halt-detection logic in one place.
+ private (List Events, bool ShouldHalt) DrainAndFilterEvents()
+ {
+ List events = [];
+ bool shouldHalt = false;
+ foreach (WorkflowEvent e in Interlocked.Exchange(ref this._eventSink, new ConcurrentQueue()))
+ {
+ if (e is RequestHaltEvent)
+ {
+ shouldHalt = true;
+ }
+ else
+ {
+ events.Add(e);
+ }
+ }
+
+ return (events, shouldHalt);
+ }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StreamingRunEventStream.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StreamingRunEventStream.cs
index 6278f3446b..c6492270d8 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StreamingRunEventStream.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StreamingRunEventStream.cs
@@ -60,6 +60,10 @@ internal sealed class StreamingRunEventStream : IRunEventStream
// Subscribe to events - they will flow directly to the channel as they're raised
this._stepRunner.OutgoingEvents.EventRaised += OnEventRaisedAsync;
+ // Re-emit any pending external requests that were restored from a checkpoint
+ // before this subscription was active. For non-resume starts this is a no-op.
+ await this._stepRunner.RepublishPendingEventsAsync(linkedSource.Token).ConfigureAwait(false);
+
// Start the session-level activity that spans the entire run loop lifetime.
// Individual run-stage activities are nested within this session activity.
Activity? sessionActivity = this._stepRunner.TelemetryContext.StartWorkflowSessionActivity();
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessExecutionEnvironment.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessExecutionEnvironment.cs
index 1eccb391fd..a2561437ee 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessExecutionEnvironment.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessExecutionEnvironment.cs
@@ -50,10 +50,13 @@ public sealed class InProcessExecutionEnvironment : IWorkflowExecutionEnvironmen
return runner.BeginStreamAsync(this.ExecutionMode, cancellationToken);
}
- internal ValueTask ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable knownValidInputTypes, CancellationToken cancellationToken)
+ internal ValueTask ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable knownValidInputTypes, CancellationToken cancellationToken = default)
+ => this.ResumeRunAsync(workflow, fromCheckpoint, knownValidInputTypes, republishPendingEvents: true, cancellationToken);
+
+ internal ValueTask ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable knownValidInputTypes, bool republishPendingEvents, CancellationToken cancellationToken = default)
{
InProcessRunner runner = InProcessRunner.CreateTopLevelRunner(workflow, this.CheckpointManager, fromCheckpoint.SessionId, this.EnableConcurrentRuns, knownValidInputTypes);
- return runner.ResumeStreamAsync(this.ExecutionMode, fromCheckpoint, cancellationToken);
+ return runner.ResumeStreamAsync(this.ExecutionMode, fromCheckpoint, republishPendingEvents, cancellationToken);
}
///
@@ -104,6 +107,32 @@ public sealed class InProcessExecutionEnvironment : IWorkflowExecutionEnvironmen
return new(runHandle);
}
+ ///
+ /// Resumes a streaming workflow run from a checkpoint with control over whether
+ /// pending request events are republished through the event stream.
+ ///
+ /// The workflow to resume.
+ /// The checkpoint to resume from.
+ ///
+ /// When , any pending request events are republished through the event
+ /// stream after subscribing. When , the caller is responsible for
+ /// handling pending requests (e.g., already sends responses).
+ ///
+ /// Cancellation token.
+ internal async ValueTask ResumeStreamingInternalAsync(
+ Workflow workflow,
+ CheckpointInfo fromCheckpoint,
+ bool republishPendingEvents,
+ CancellationToken cancellationToken = default)
+ {
+ this.VerifyCheckpointingConfigured();
+
+ AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], republishPendingEvents, cancellationToken)
+ .ConfigureAwait(false);
+
+ return new(runHandle);
+ }
+
private async ValueTask BeginRunHandlingChatProtocolAsync(Workflow workflow,
TInput input,
string? sessionId = null,
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs
index f93b09ddf3..0daa9bf285 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs
@@ -71,6 +71,28 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
///
public string StartExecutorId { get; }
+ ///
+ /// Gating flag for deferred event republishing after checkpoint restore.
+ ///
+ ///
+ ///
+ /// Written with in
+ /// and consumed atomically with in
+ /// . The write does not need a full
+ /// memory barrier because it is sequenced before the constructor
+ /// by the in . The constructor is the
+ /// only code path that triggers consumption (via the event stream's subscribe and republish flow).
+ ///
+ ///
+ /// Note: also reads
+ /// in its constructor to signal the run loop, but that property reads from
+ /// 's request dictionary (restored during
+ /// ), not from this flag. The two are independent:
+ /// HasUnservicedRequests triggers the run loop; _needsRepublish triggers event emission.
+ ///
+ ///
+ private int _needsRepublish;
+
///
public WorkflowTelemetryContext TelemetryContext => this.Workflow.TelemetryContext;
@@ -145,7 +167,10 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
return new(new AsyncRunHandle(this, this, mode));
}
- public async ValueTask ResumeStreamAsync(ExecutionMode mode, CheckpointInfo fromCheckpoint, CancellationToken cancellationToken = default)
+ public ValueTask ResumeStreamAsync(ExecutionMode mode, CheckpointInfo fromCheckpoint, CancellationToken cancellationToken = default)
+ => this.ResumeStreamAsync(mode, fromCheckpoint, republishPendingEvents: true, cancellationToken);
+
+ public async ValueTask ResumeStreamAsync(ExecutionMode mode, CheckpointInfo fromCheckpoint, bool republishPendingEvents, CancellationToken cancellationToken = default)
{
this.RunContext.CheckEnded();
Throw.IfNull(fromCheckpoint);
@@ -154,7 +179,18 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
throw new InvalidOperationException("This runner was not configured with a CheckpointManager, so it cannot restore checkpoints.");
}
- await this.RestoreCheckpointAsync(fromCheckpoint, cancellationToken).ConfigureAwait(false);
+ // Restore checkpoint state without republishing pending request events.
+ // The event stream will republish them after subscribing so that events
+ // are never lost to an absent subscriber.
+ await this.RestoreCheckpointCoreAsync(fromCheckpoint, cancellationToken).ConfigureAwait(false);
+
+ if (republishPendingEvents)
+ {
+ // Signal the event stream to republish pending requests after subscribing.
+ // This is consumed atomically by RepublishPendingEventsAsync.
+ Volatile.Write(ref this._needsRepublish, 1);
+ }
+
return new AsyncRunHandle(this, this, mode);
}
@@ -163,6 +199,16 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
bool ISuperStepRunner.TryGetResponsePortExecutorId(string portId, out string? executorId)
=> this.RunContext.TryGetResponsePortExecutorId(portId, out executorId);
+ ValueTask ISuperStepRunner.RepublishPendingEventsAsync(CancellationToken cancellationToken)
+ {
+ if (Interlocked.Exchange(ref this._needsRepublish, 0) != 0)
+ {
+ return this.RunContext.RepublishUnservicedRequestsAsync(cancellationToken);
+ }
+
+ return default;
+ }
+
public bool IsCheckpointingEnabled => this.RunContext.IsCheckpointingEnabled;
public IReadOnlyList Checkpoints => this._checkpoints;
@@ -310,7 +356,31 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
this._checkpoints.Add(this._lastCheckpointInfo);
}
+ ///
+ /// Restores checkpoint state and re-emits any pending external request events.
+ ///
+ ///
+ /// This is the implementation used for runtime restores
+ /// where the event stream subscription is already active. For initial resumes,
+ /// calls
+ /// directly and defers republishing to the event stream.
+ ///
public async ValueTask RestoreCheckpointAsync(CheckpointInfo checkpointInfo, CancellationToken cancellationToken = default)
+ {
+ await this.RestoreCheckpointCoreAsync(checkpointInfo, cancellationToken).ConfigureAwait(false);
+
+ // Republish pending request events. This is safe for runtime restores where
+ // the event stream is already subscribed. For initial resumes the event stream
+ // handles republishing itself, so ResumeStreamAsync calls RestoreCheckpointCoreAsync directly.
+ await this.RunContext.RepublishUnservicedRequestsAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Restores checkpoint state (queued messages, executor state, edge state, etc.)
+ /// without republishing pending request events. The caller is responsible for
+ /// ensuring events are republished after an event subscriber is attached.
+ ///
+ private async ValueTask RestoreCheckpointCoreAsync(CheckpointInfo checkpointInfo, CancellationToken cancellationToken = default)
{
this.RunContext.CheckEnded();
Throw.IfNull(checkpointInfo);
@@ -335,11 +405,9 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
await this.RunContext.ImportStateAsync(checkpoint).ConfigureAwait(false);
Task executorNotifyTask = this.RunContext.NotifyCheckpointLoadedAsync(cancellationToken);
- ValueTask republishRequestsTask = this.RunContext.RepublishUnservicedRequestsAsync(cancellationToken);
await this.EdgeMap.ImportStateAsync(checkpoint).ConfigureAwait(false);
await Task.WhenAll(executorNotifyTask,
- republishRequestsTask.AsTask(),
restoreCheckpointIndexTask.AsTask()).ConfigureAwait(false);
this._lastCheckpointInfo = checkpointInfo;
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/RequestInfoExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/RequestInfoExecutor.cs
index b35d682f2c..52dae66b88 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/RequestInfoExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/RequestInfoExecutor.cs
@@ -14,6 +14,7 @@ internal sealed class RequestPortOptions;
internal sealed class RequestInfoExecutor : Executor
{
+ private const string WrappedRequestsStateKey = nameof(WrappedRequestsStateKey);
private readonly Dictionary _wrappedRequests = [];
private RequestPort Port { get; }
private IExternalRequestSink? RequestSink { get; set; }
@@ -124,22 +125,46 @@ internal sealed class RequestInfoExecutor : Executor
return null;
}
- if (this._allowWrapped && this._wrappedRequests.TryGetValue(message.RequestId, out ExternalRequest? originalRequest))
- {
- await context.SendMessageAsync(originalRequest.RewrapResponse(message), cancellationToken: cancellationToken).ConfigureAwait(false);
- }
- else
- {
- await context.SendMessageAsync(message, cancellationToken: cancellationToken).ConfigureAwait(false);
- }
-
if (!message.Data.IsType(this.Port.Response, out object? data))
{
throw this.Port.CreateExceptionForType(message);
}
- await context.SendMessageAsync(data, cancellationToken: cancellationToken).ConfigureAwait(false);
+ if (this._allowWrapped && this._wrappedRequests.TryGetValue(message.RequestId, out ExternalRequest? originalRequest))
+ {
+ await context.SendMessageAsync(originalRequest.RewrapResponse(message), cancellationToken: cancellationToken).ConfigureAwait(false);
+ this._wrappedRequests.Remove(message.RequestId);
+ }
+ else
+ {
+ await context.SendMessageAsync(message, cancellationToken: cancellationToken).ConfigureAwait(false);
+ await context.SendMessageAsync(data, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
return message;
}
+
+ protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ await context.QueueStateUpdateAsync(WrappedRequestsStateKey,
+ new Dictionary(this._wrappedRequests, StringComparer.Ordinal),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
+ await base.OnCheckpointingAsync(context, cancellationToken).ConfigureAwait(false);
+ }
+
+ protected internal override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ await base.OnCheckpointRestoredAsync(context, cancellationToken).ConfigureAwait(false);
+
+ this._wrappedRequests.Clear();
+
+ Dictionary wrappedRequests =
+ await context.ReadStateAsync>(WrappedRequestsStateKey, cancellationToken: cancellationToken)
+ .ConfigureAwait(false) ?? [];
+
+ foreach (KeyValuePair wrappedRequest in wrappedRequests)
+ {
+ this._wrappedRequests[wrappedRequest.Key] = wrappedRequest.Value;
+ }
+ }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/WorkflowHostExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/WorkflowHostExecutor.cs
index 107dc3fd7a..58e3a9e523 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/WorkflowHostExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/WorkflowHostExecutor.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@@ -23,6 +24,7 @@ internal class WorkflowHostExecutor : Executor, IAsyncDisposable
private InProcessRunner? _activeRunner;
private InMemoryCheckpointManager? _checkpointManager;
private readonly ExecutorOptions _options;
+ private readonly ConcurrentDictionary _pendingResponsePorts = new(StringComparer.Ordinal);
private ISuperStepJoinContext? _joinContext;
private string? _joinId;
@@ -163,6 +165,11 @@ internal class WorkflowHostExecutor : Executor, IAsyncDisposable
private ExternalResponse? CheckAndUnqualifyResponse([DisallowNull] ExternalResponse response)
{
+ if (this._pendingResponsePorts.TryRemove(response.RequestId, out RequestPortInfo? originalPort))
+ {
+ return response with { PortInfo = originalPort };
+ }
+
if (!Throw.IfNull(response).PortInfo.PortId.StartsWith($"{this.Id}.", StringComparison.Ordinal))
{
return null;
@@ -193,6 +200,7 @@ internal class WorkflowHostExecutor : Executor, IAsyncDisposable
break;
case RequestInfoEvent requestInfoEvt:
ExternalRequest request = requestInfoEvt.Request;
+ this._pendingResponsePorts[request.RequestId] = request.PortInfo;
resultTask = this._joinContext?.SendMessageAsync(this.Id, this.QualifyRequestPortId(request)).AsTask() ?? Task.CompletedTask;
break;
case WorkflowErrorEvent errorEvent:
@@ -246,9 +254,13 @@ internal class WorkflowHostExecutor : Executor, IAsyncDisposable
}
private const string CheckpointManagerStateKey = nameof(CheckpointManager);
+ private const string PendingResponsePortsStateKey = nameof(PendingResponsePortsStateKey);
protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
{
await context.QueueStateUpdateAsync(CheckpointManagerStateKey, this._checkpointManager, cancellationToken: cancellationToken).ConfigureAwait(false);
+ await context.QueueStateUpdateAsync(PendingResponsePortsStateKey,
+ new Dictionary(this._pendingResponsePorts, StringComparer.Ordinal),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
await base.OnCheckpointingAsync(context, cancellationToken).ConfigureAwait(false);
}
@@ -269,6 +281,15 @@ internal class WorkflowHostExecutor : Executor, IAsyncDisposable
await this.ResetAsync().ConfigureAwait(false);
}
+ this._pendingResponsePorts.Clear();
+ Dictionary pendingResponsePorts =
+ await context.ReadStateAsync>(PendingResponsePortsStateKey, cancellationToken: cancellationToken)
+ .ConfigureAwait(false) ?? [];
+ foreach (KeyValuePair pendingResponsePort in pendingResponsePorts)
+ {
+ this._pendingResponsePorts[pendingResponsePort.Key] = pendingResponsePort.Value;
+ }
+
await this.EnsureRunSendMessageAsync(resume: true, cancellationToken: cancellationToken).ConfigureAwait(false);
}
@@ -280,6 +301,8 @@ internal class WorkflowHostExecutor : Executor, IAsyncDisposable
this._run = null;
}
+ this._pendingResponsePorts.Clear();
+
if (this._activeRunner != null)
{
this._activeRunner.OutgoingEvents.EventRaised -= this.ForwardWorkflowEventAsync;
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs
index 715c232e7d..c1f81f0ecf 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs
@@ -19,7 +19,14 @@ namespace Microsoft.Agents.AI.Workflows;
internal sealed class WorkflowSession : AgentSession
{
private readonly Workflow _workflow;
- private readonly IWorkflowExecutionEnvironment _executionEnvironment;
+
+ ///
+ /// The execution environment for this session. Concrete type is required because
+ /// uses the internal
+ /// API.
+ ///
+ private readonly InProcessExecutionEnvironment _inProcEnvironment;
+
private readonly bool _includeExceptionDetails;
private readonly bool _includeWorkflowOutputsInResponse;
@@ -63,17 +70,22 @@ internal sealed class WorkflowSession : AgentSession
public WorkflowSession(Workflow workflow, string sessionId, IWorkflowExecutionEnvironment executionEnvironment, bool includeExceptionDetails = false, bool includeWorkflowOutputsInResponse = false)
{
this._workflow = Throw.IfNull(workflow);
- this._executionEnvironment = Throw.IfNull(executionEnvironment);
this._includeExceptionDetails = includeExceptionDetails;
this._includeWorkflowOutputsInResponse = includeWorkflowOutputsInResponse;
- if (VerifyCheckpointingConfiguration(executionEnvironment, out InProcessExecutionEnvironment? inProcEnv))
+ IWorkflowExecutionEnvironment env = Throw.IfNull(executionEnvironment);
+ if (VerifyCheckpointingConfiguration(env, out InProcessExecutionEnvironment? inProcEnv))
{
// We have an InProcessExecutionEnvironment which is not configured for checkpointing. Ensure it has an externalizable checkpoint manager,
// since we are responsible for maintaining the state.
- this._executionEnvironment = inProcEnv.WithCheckpointing(this.EnsureExternalizedInMemoryCheckpointing());
+ env = inProcEnv.WithCheckpointing(this.EnsureExternalizedInMemoryCheckpointing());
}
+ this._inProcEnvironment = env as InProcessExecutionEnvironment
+ ?? throw new InvalidOperationException(
+ $"WorkflowSession requires an {nameof(InProcessExecutionEnvironment)}, " +
+ $"but received {env.GetType().Name}.");
+
this.SessionId = Throw.IfNullOrEmpty(sessionId);
this.ChatHistoryProvider = new WorkflowChatHistoryProvider();
}
@@ -86,24 +98,30 @@ internal sealed class WorkflowSession : AgentSession
public WorkflowSession(Workflow workflow, JsonElement serializedSession, IWorkflowExecutionEnvironment executionEnvironment, bool includeExceptionDetails = false, bool includeWorkflowOutputsInResponse = false, JsonSerializerOptions? jsonSerializerOptions = null)
{
this._workflow = Throw.IfNull(workflow);
- this._executionEnvironment = Throw.IfNull(executionEnvironment);
this._includeExceptionDetails = includeExceptionDetails;
this._includeWorkflowOutputsInResponse = includeWorkflowOutputsInResponse;
+ IWorkflowExecutionEnvironment env = Throw.IfNull(executionEnvironment);
+
JsonMarshaller marshaller = new(jsonSerializerOptions);
SessionState sessionState = marshaller.Marshal(serializedSession);
this._inMemoryCheckpointManager = sessionState.CheckpointManager;
if (this._inMemoryCheckpointManager != null &&
- VerifyCheckpointingConfiguration(executionEnvironment, out InProcessExecutionEnvironment? inProcEnv))
+ VerifyCheckpointingConfiguration(env, out InProcessExecutionEnvironment? inProcEnv))
{
- this._executionEnvironment = inProcEnv.WithCheckpointing(this.EnsureExternalizedInMemoryCheckpointing());
+ env = inProcEnv.WithCheckpointing(this.EnsureExternalizedInMemoryCheckpointing());
}
else if (this._inMemoryCheckpointManager != null)
{
throw new ArgumentException("The session was saved with an externalized checkpoint manager, but the incoming execution environment does not support it.", nameof(executionEnvironment));
}
+ this._inProcEnvironment = env as InProcessExecutionEnvironment
+ ?? throw new InvalidOperationException(
+ $"WorkflowSession requires an {nameof(InProcessExecutionEnvironment)}, " +
+ $"but received {env.GetType().Name}.");
+
this.SessionId = sessionState.SessionId;
this.ChatHistoryProvider = new WorkflowChatHistoryProvider();
@@ -160,10 +178,15 @@ internal sealed class WorkflowSession : AgentSession
// and does not need to be checked again here.
if (this.LastCheckpoint is not null)
{
+ // Use the internal resume path that suppresses pending request republishing.
+ // WorkflowSession handles pending requests itself by converting matching responses
+ // via SendMessagesWithResponseConversionAsync, so event-stream republishing would
+ // cause unwanted duplicate events visible to the consumer.
StreamingRun run =
- await this._executionEnvironment
- .ResumeStreamingAsync(this._workflow,
+ await this._inProcEnvironment
+ .ResumeStreamingInternalAsync(this._workflow,
this.LastCheckpoint,
+ republishPendingEvents: false,
cancellationToken)
.ConfigureAwait(false);
@@ -172,7 +195,7 @@ internal sealed class WorkflowSession : AgentSession
return new ResumeRunResult(run, dispatchInfo);
}
- StreamingRun newRun = await this._executionEnvironment
+ StreamingRun newRun = await this._inProcEnvironment
.RunStreamingAsync(this._workflow,
messages,
this.SessionId,
diff --git a/dotnet/src/Shared/IntegrationTests/TestSettings.cs b/dotnet/src/Shared/IntegrationTests/TestSettings.cs
index 880db9d1cd..de5757314c 100644
--- a/dotnet/src/Shared/IntegrationTests/TestSettings.cs
+++ b/dotnet/src/Shared/IntegrationTests/TestSettings.cs
@@ -16,6 +16,7 @@ internal static class TestSettings
// Azure AI (Foundry)
public const string AzureAIBingConnectionId = "AZURE_AI_BING_CONNECTION_ID";
+ public const string AzureAIEmbeddingDeploymentName = "AZURE_AI_EMBEDDING_DEPLOYMENT_NAME";
public const string AzureAIMemoryStoreId = "AZURE_AI_MEMORY_STORE_ID";
public const string AzureAIModelDeploymentName = "AZURE_AI_MODEL_DEPLOYMENT_NAME";
public const string AzureAIProjectEndpoint = "AZURE_AI_PROJECT_ENDPOINT";
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientChatClientAgentRunStreamingTests.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientChatClientAgentRunStreamingTests.cs
deleted file mode 100644
index 0e507e44c1..0000000000
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientChatClientAgentRunStreamingTests.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.Threading.Tasks;
-using AgentConformance.IntegrationTests;
-
-namespace AzureAI.IntegrationTests;
-
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete AIProjectClientFixture
-[Obsolete("Use FoundryVersionedAgentRunTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests(() => new())
-{
- public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
- {
- Assert.Skip("No messages is not supported");
- return base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
- }
-}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientChatClientAgentRunTests.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientChatClientAgentRunTests.cs
deleted file mode 100644
index a0ee72ebd4..0000000000
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientChatClientAgentRunTests.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.Threading.Tasks;
-using AgentConformance.IntegrationTests;
-
-namespace AzureAI.IntegrationTests;
-
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete AIProjectClientFixture
-[Obsolete("Use FoundryVersionedAgentRunTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientChatClientAgentRunTests() : ChatClientAgentRunTests(() => new())
-{
- public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
- {
- Assert.Skip("No messages is not supported");
- return base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
- }
-}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj b/dotnet/tests/Foundry.IntegrationTests/Foundry.IntegrationTests.csproj
similarity index 83%
rename from dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
rename to dotnet/tests/Foundry.IntegrationTests/Foundry.IntegrationTests.csproj
index 2703360cb2..dbff50104c 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
+++ b/dotnet/tests/Foundry.IntegrationTests/Foundry.IntegrationTests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentChatClientRunStreamingTests.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentChatClientRunStreamingTests.cs
new file mode 100644
index 0000000000..c9128050fc
--- /dev/null
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentChatClientRunStreamingTests.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+
+namespace Foundry.IntegrationTests;
+
+public class FoundryVersionedAgentChatClientRunStreamingTests() : ChatClientAgentRunStreamingTests(() => new())
+{
+ public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
+ {
+ Assert.Skip("No messages is not supported");
+ return base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
+ }
+}
diff --git a/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentChatClientRunTests.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentChatClientRunTests.cs
new file mode 100644
index 0000000000..fff2ad529f
--- /dev/null
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentChatClientRunTests.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+
+namespace Foundry.IntegrationTests;
+
+public class FoundryVersionedAgentChatClientRunTests() : ChatClientAgentRunTests(() => new())
+{
+ public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
+ {
+ Assert.Skip("No messages is not supported");
+ return base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
+ }
+}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentCreateTests.cs
similarity index 70%
rename from dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentCreateTests.cs
index bc5f38acf2..160ab697f7 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentCreateTests.cs
@@ -1,7 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete extension methods
-
using System;
using System.IO;
using System.Threading.Tasks;
@@ -9,45 +7,43 @@ using AgentConformance.IntegrationTests.Support;
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using OpenAI.Files;
using OpenAI.Responses;
using Shared.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
-[Obsolete("Use FoundryVersionedAgentCreateTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientCreateTests
+///
+/// Integration tests for versioned creation via
+/// AIProjectClient.Agents.CreateAgentVersionAsync and AIProjectClient.AsAIAgent(AgentVersion).
+///
+public class FoundryVersionedAgentCreateTests
{
private readonly AIProjectClient _client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
- [Theory]
- [InlineData("CreateWithChatClientAgentOptionsAsync")]
- [InlineData("CreateWithFoundryOptionsAsync")]
- public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string createMechanism)
+ [Fact]
+ public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync()
{
// Arrange.
- string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("IntegrationTestAgent");
+ string AgentName = FoundryVersionedAgentFixture.GenerateUniqueAgentName("IntegrationTestAgent");
const string AgentDescription = "An agent created during integration tests";
const string AgentInstructions = "You are an integration test agent";
// Act.
- var agent = createMechanism switch
- {
- "CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
- model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName),
- options: new ChatClientAgentOptions()
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ AgentName,
+ new AgentVersionCreationOptions(
+ new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
{
- Name = AgentName,
- Description = AgentDescription,
- ChatOptions = new() { Instructions = AgentInstructions }
- }),
- "CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
- name: AgentName,
- creationOptions: new AgentVersionCreationOptions(new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName)) { Instructions = AgentInstructions }) { Description = AgentDescription }),
- _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
- };
+ Instructions = AgentInstructions
+ })
+ {
+ Description = AgentDescription
+ });
+
+ var agent = this._client.AsAIAgent(agentVersion);
try
{
@@ -72,12 +68,11 @@ public class AIProjectClientCreateTests
}
[Theory(Skip = "For manual testing only")]
- [InlineData("CreateWithChatClientAgentOptionsAsync")]
- [InlineData("CreateWithFoundryOptionsAsync")]
- public async Task CreateAgent_CreatesAgentWithVectorStoresAsync(string createMechanism)
+ [InlineData("FileSearchTool")]
+ public async Task CreateAgent_CreatesAgentWithVectorStoresAsync(string _)
{
// Arrange.
- string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("VectorStoreAgent");
+ string AgentName = FoundryVersionedAgentFixture.GenerateUniqueAgentName("VectorStoreAgent");
const string AgentInstructions = """
You are a helpful agent that can help fetch data from files you know about.
Use the File Search Tool to look up codes for words.
@@ -99,22 +94,19 @@ public class AIProjectClientCreateTests
);
var vectorStoreMetadata = await projectOpenAIClient.GetProjectVectorStoresClient().CreateVectorStoreAsync(options: new() { FileIds = { uploadedAgentFile.Id }, Name = "WordCodeLookup_VectorStore" });
- // Act.
- var agent = createMechanism switch
+ // Act — create agent version with FileSearch tool via native SDK, then wrap with AsAIAgent.
+ var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
{
- "CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
- model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName),
- name: AgentName,
- instructions: AgentInstructions,
- tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]),
- "CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
- model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName),
- name: AgentName,
- instructions: AgentInstructions,
- tools: [ResponseTool.CreateFileSearchTool(vectorStoreIds: [vectorStoreMetadata.Value.Id]).AsAITool()]),
- _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
+ Instructions = AgentInstructions,
+ Tools = { ResponseTool.CreateFileSearchTool(vectorStoreIds: [vectorStoreMetadata.Value.Id]) }
};
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ AgentName,
+ new AgentVersionCreationOptions(definition));
+
+ var agent = this._client.AsAIAgent(agentVersion);
+
try
{
// Assert.
@@ -132,13 +124,11 @@ public class AIProjectClientCreateTests
}
}
- [Theory]
- [InlineData("CreateWithChatClientAgentOptionsAsync")]
- [InlineData("CreateWithFoundryOptionsAsync")]
- public async Task CreateAgent_CreatesAgentWithCodeInterpreterAsync(string createMechanism)
+ [Fact]
+ public async Task CreateAgent_CreatesAgentWithCodeInterpreterAsync()
{
// Arrange.
- string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("CodeInterpreterAgent");
+ string AgentName = FoundryVersionedAgentFixture.GenerateUniqueAgentName("CodeInterpreterAgent");
const string AgentInstructions = """
You are a helpful coding agent. A Python file is provided. Use the Code Interpreter Tool to run the file
and report the SECRET_NUMBER value it prints. Respond only with the number.
@@ -158,24 +148,19 @@ public class AIProjectClientCreateTests
purpose: FileUploadPurpose.Assistants
);
- // Act.
- var agent = createMechanism switch
+ // Act — create agent version with CodeInterpreter tool via native SDK, then wrap with AsAIAgent.
+ var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
{
- // Hosted tool path (tools supplied via ChatClientAgentOptions)
- "CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
- model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName),
- name: AgentName,
- instructions: AgentInstructions,
- tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]),
- // Foundry (definitions + resources provided directly)
- "CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
- model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName),
- name: AgentName,
- instructions: AgentInstructions,
- tools: [ResponseTool.CreateCodeInterpreterTool(new CodeInterpreterToolContainer(CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration([uploadedCodeFile.Id]))).AsAITool()]),
- _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
+ Instructions = AgentInstructions,
+ Tools = { ResponseTool.CreateCodeInterpreterTool(new CodeInterpreterToolContainer(CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration([uploadedCodeFile.Id]))) }
};
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ AgentName,
+ new AgentVersionCreationOptions(definition));
+
+ var agent = this._client.AsAIAgent(agentVersion);
+
try
{
// Assert.
@@ -202,7 +187,7 @@ public class AIProjectClientCreateTests
public async Task AsAIAgent_WithOpenAPITool_NativeSDKCreation_InvokesServerSideToolAsync()
{
// Arrange — create agent version with OpenAPI tool using native Azure.AI.Projects SDK types.
- string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("OpenAPITestAgent");
+ string AgentName = FoundryVersionedAgentFixture.GenerateUniqueAgentName("OpenAPITestAgent");
const string AgentInstructions = "You are a helpful assistant that can use the countries API to retrieve information about countries by their currency code.";
const string CountriesOpenApiSpec = """
@@ -320,28 +305,29 @@ public class AIProjectClientCreateTests
}
}
- [Theory]
- [InlineData("CreateWithChatClientAgentOptionsAsync")]
- public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string createMechanism)
+ [Fact]
+ public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync()
{
// Arrange.
- string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("WeatherAgent");
+ string AgentName = FoundryVersionedAgentFixture.GenerateUniqueAgentName("WeatherAgent");
const string AgentInstructions = "You are a helpful weather assistant. Always call the GetWeather function to answer questions about weather.";
static string GetWeather(string location) => $"The weather in {location} is sunny with a high of 23C.";
var weatherFunction = AIFunctionFactory.Create(GetWeather);
- FoundryAgent agent = createMechanism switch
+ // 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))
{
- "CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
- model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName),
- options: new ChatClientAgentOptions()
- {
- Name = AgentName,
- ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
- }),
- _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
+ Instructions = AgentInstructions,
};
+ definition.Tools.Add(weatherFunction.AsOpenAIResponseTool());
+
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ AgentName,
+ new AgentVersionCreationOptions(definition));
+
+ FoundryAgent agent = this._client.AsAIAgent(agentVersion, tools: [weatherFunction]);
try
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentFixture.cs
similarity index 70%
rename from dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
rename to dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentFixture.cs
index 112c76571b..2c06404eb6 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentFixture.cs
@@ -1,7 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete extension methods
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,16 +8,21 @@ using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
+using Azure.AI.Projects.Agents;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
+using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
using Shared.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
-[Obsolete("Use FoundryVersionedAgentFixture instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientFixture : IChatClientAgentFixture
+///
+/// Integration test fixture that creates versioned Foundry agents via
+/// AIProjectClient.Agents.CreateAgentVersionAsync and wraps them
+/// with AIProjectClient.AsAIAgent(AgentVersion).
+///
+public class FoundryVersionedAgentFixture : IChatClientAgentFixture
{
private FoundryAgent _agent = null!;
private AIProjectClient _client = null!;
@@ -40,7 +43,6 @@ public class AIProjectClientFixture : IChatClientAgentFixture
if (chatClientSession.ConversationId?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) == true)
{
- // Conversation sessions do not persist message history.
return await this.GetChatHistoryFromConversationAsync(chatClientSession.ConversationId);
}
@@ -119,14 +121,48 @@ public class AIProjectClientFixture : IChatClientAgentFixture
string instructions = "You are a helpful assistant.",
IList? aiTools = null)
{
- return (await this._client.CreateAIAgentAsync(GenerateUniqueAgentName(name), model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName), instructions: instructions, tools: aiTools)).GetService()!;
+ var definition = new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
+ {
+ Instructions = instructions
+ };
+
+ // Register AIFunction tool definitions in the server-side agent definition so the model
+ // can invoke them. The local AIFunction implementations are matched by name via AsAIAgent.
+ if (aiTools is not null)
+ {
+ foreach (var tool in aiTools)
+ {
+ if (tool.AsOpenAIResponseTool() is ResponseTool responseTool)
+ {
+ definition.Tools.Add(responseTool);
+ }
+ }
+ }
+
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ GenerateUniqueAgentName(name),
+ new AgentVersionCreationOptions(definition));
+
+ return this._client.AsAIAgent(agentVersion, tools: aiTools).GetService()!;
}
public async Task CreateChatClientAgentAsync(ChatClientAgentOptions options)
{
options.Name ??= GenerateUniqueAgentName("HelpfulAssistant");
- return (await this._client.CreateAIAgentAsync(model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName), options)).GetService()!;
+ var definition = new PromptAgentDefinition(
+ options.ChatOptions?.ModelId ?? TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
+ {
+ Instructions = options.ChatOptions?.Instructions
+ };
+
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ options.Name,
+ new AgentVersionCreationOptions(definition) { Description = options.Description });
+
+ var agent = this._client.AsAIAgent(agentVersion, tools: options.ChatOptions?.Tools);
+
+ return agent.GetService()!;
}
public static string GenerateUniqueAgentName(string baseName) =>
@@ -174,13 +210,33 @@ public class AIProjectClientFixture : IChatClientAgentFixture
public virtual async ValueTask InitializeAsync()
{
this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
- this._agent = await this._client.CreateAIAgentAsync(GenerateUniqueAgentName("HelpfulAssistant"), model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName), instructions: "You are a helpful assistant.");
+
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ GenerateUniqueAgentName("HelpfulAssistant"),
+ new AgentVersionCreationOptions(
+ new PromptAgentDefinition(TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
+ {
+ Instructions = "You are a helpful assistant."
+ }));
+
+ this._agent = this._client.AsAIAgent(agentVersion);
}
public async Task InitializeAsync(ChatClientAgentOptions options)
{
this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
options.Name ??= GenerateUniqueAgentName("HelpfulAssistant");
- this._agent = await this._client.CreateAIAgentAsync(model: TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName), options);
+
+ var definition = new PromptAgentDefinition(
+ options.ChatOptions?.ModelId ?? TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName))
+ {
+ Instructions = options.ChatOptions?.Instructions
+ };
+
+ var agentVersion = await this._client.Agents.CreateAgentVersionAsync(
+ options.Name,
+ new AgentVersionCreationOptions(definition) { Description = options.Description });
+
+ this._agent = this._client.AsAIAgent(agentVersion, tools: options.ChatOptions?.Tools);
}
}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentRunTests.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentRunStreamingTests.cs
similarity index 57%
rename from dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentRunTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentRunStreamingTests.cs
index ad10ec5b7a..f5df9f1f42 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentRunTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentRunStreamingTests.cs
@@ -5,11 +5,9 @@ using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using Microsoft.Agents.AI;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete AIProjectClientFixture
-[Obsolete("Use FoundryVersionedAgentRunTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientAgentRunPreviousResponseTests() : RunTests(() => new())
+public class FoundryVersionedAgentRunStreamingPreviousResponseTests() : RunStreamingTests(() => new())
{
public override Task RunWithNoMessageDoesNotFailAsync()
{
@@ -18,8 +16,7 @@ public class AIProjectClientAgentRunPreviousResponseTests() : RunTests(() => new())
+public class FoundryVersionedAgentRunStreamingConversationTests() : RunStreamingTests(() => new())
{
public override Func> AgentRunOptionsFactory => async () =>
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentRunStreamingTests.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentRunTests.cs
similarity index 56%
rename from dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentRunStreamingTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentRunTests.cs
index 0f2b123fd9..9cefbd0f46 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentRunStreamingTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentRunTests.cs
@@ -5,11 +5,9 @@ using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using Microsoft.Agents.AI;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete AIProjectClientFixture
-[Obsolete("Use FoundryVersionedAgentRunTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientAgentRunStreamingPreviousResponseTests() : RunStreamingTests(() => new())
+public class FoundryVersionedAgentRunPreviousResponseTests() : RunTests(() => new())
{
public override Task RunWithNoMessageDoesNotFailAsync()
{
@@ -18,8 +16,7 @@ public class AIProjectClientAgentRunStreamingPreviousResponseTests() : RunStream
}
}
-[Obsolete("Use FoundryVersionedAgentRunTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientAgentRunStreamingConversationTests() : RunStreamingTests(() => new())
+public class FoundryVersionedAgentRunConversationTests() : RunTests(() => new())
{
public override Func> AgentRunOptionsFactory => async () =>
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentStructuredOutputRunTests.cs b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentStructuredOutputRunTests.cs
similarity index 71%
rename from dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentStructuredOutputRunTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentStructuredOutputRunTests.cs
index b3782a6601..015877df05 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentStructuredOutputRunTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/FoundryVersionedAgentStructuredOutputRunTests.cs
@@ -1,25 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
-using System;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete AIProjectClientFixture
-[Obsolete("Use FoundryVersionedAgentStructuredOutputRunTests instead. These tests exercise obsolete AIProjectClient extension methods.")]
-public class AIProjectClientAgentStructuredOutputRunTests() : StructuredOutputRunTests>(() => new AIProjectClientStructuredOutputFixture())
+public class FoundryVersionedAgentStructuredOutputRunTests() : StructuredOutputRunTests>(() => new FoundryVersionedAgentStructuredOutputFixture())
{
- private const string NotSupported = "AIProjectClient does not support specifying structured output type at invocation time.";
+ private const string NotSupported = "Versioned Foundry agents do not support specifying structured output type at invocation time.";
+ private const string ResponseFormatNotSupported = "AzureAIProjectChatClient clears ResponseFormat for versioned agents; structured output must be defined in the server-side agent definition.";
///
/// Verifies that response format provided at agent initialization is used when invoking RunAsync.
///
///
- [RetryFact(Constants.RetryCount, Constants.RetryDelay)]
+ [RetryFact(Constants.RetryCount, Constants.RetryDelay, Skip = ResponseFormatNotSupported)]
public async Task RunWithResponseFormatAtAgentInitializationReturnsExpectedResultAsync()
{
// Arrange
@@ -39,14 +37,14 @@ public class AIProjectClientAgentStructuredOutputRunTests() : StructuredOutputRu
}
///
- /// Verifies that generic RunAsync works with AIProjectClient when structured output is configured at agent initialization.
+ /// Verifies that generic RunAsync works with versioned Foundry agents when structured output is configured at agent initialization.
///
///
- /// AIProjectClient does not support specifying the structured output type at invocation time yet.
+ /// Versioned Foundry agents do not support specifying the structured output type at invocation time yet.
/// The type T provided to RunAsync<T> is ignored by AzureAIProjectChatClient and is only used
/// for deserializing the agent response by AgentResponse<T>.Result.
///
- [RetryFact(Constants.RetryCount, Constants.RetryDelay)]
+ [RetryFact(Constants.RetryCount, Constants.RetryDelay, Skip = ResponseFormatNotSupported)]
public async Task RunGenericWithResponseFormatAtAgentInitializationReturnsExpectedResultAsync()
{
// Arrange
@@ -88,10 +86,9 @@ public class AIProjectClientAgentStructuredOutputRunTests() : StructuredOutputRu
}
///
-/// Represents a fixture for testing AIProjectClient with structured output of type provided at agent initialization.
+/// Represents a fixture for testing versioned Foundry agents with structured output of type provided at agent initialization.
///
-[Obsolete("Use FoundryVersionedAgentStructuredOutputFixture instead.")]
-public class AIProjectClientStructuredOutputFixture : AIProjectClientFixture
+public class FoundryVersionedAgentStructuredOutputFixture : FoundryVersionedAgentFixture
{
public override async ValueTask InitializeAsync()
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs b/dotnet/tests/Foundry.IntegrationTests/Memory/FoundryMemoryProviderTests.cs
similarity index 57%
rename from dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/Memory/FoundryMemoryProviderTests.cs
index d6092f5231..9b9b39cbdf 100644
--- a/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/Memory/FoundryMemoryProviderTests.cs
@@ -1,14 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
-#pragma warning disable CS0618 // Tests intentionally exercise obsolete extension methods
-
using System;
using System.Threading.Tasks;
using Azure.AI.Projects;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Foundry;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
+using OpenAI.Responses;
using Shared.IntegrationTests;
-namespace Microsoft.Agents.AI.FoundryMemory.IntegrationTests;
+namespace Foundry.IntegrationTests.Memory;
///
/// Integration tests for against a configured Azure AI Foundry Memory service.
@@ -16,7 +19,6 @@ namespace Microsoft.Agents.AI.FoundryMemory.IntegrationTests;
///
/// These integration tests are skipped by default and require a live Azure AI Foundry Memory service.
/// The tests need to be updated to use the new AIAgent-based API pattern.
-/// Set to null to enable them after configuring the service.
///
public sealed class FoundryMemoryProviderTests : IDisposable
{
@@ -25,6 +27,7 @@ public sealed class FoundryMemoryProviderTests : IDisposable
private readonly AIProjectClient? _client;
private readonly string? _memoryStoreName;
private readonly string? _deploymentName;
+ private readonly string? _embeddingDeploymentName;
private bool _disposed;
public FoundryMemoryProviderTests()
@@ -38,13 +41,15 @@ public sealed class FoundryMemoryProviderTests : IDisposable
var endpoint = configuration[TestSettings.AzureAIProjectEndpoint];
var memoryStoreName = configuration[TestSettings.AzureAIMemoryStoreId];
var deploymentName = configuration[TestSettings.AzureAIModelDeploymentName];
+ var embeddingDeploymentName = configuration[TestSettings.AzureAIEmbeddingDeploymentName];
if (!string.IsNullOrWhiteSpace(endpoint) &&
!string.IsNullOrWhiteSpace(memoryStoreName))
{
- this._client = new AIProjectClient(new Uri(endpoint), TestAzureCliCredentials.CreateAzureCliCredential());
+ this._client = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
this._memoryStoreName = memoryStoreName;
this._deploymentName = deploymentName ?? "gpt-4.1-mini";
+ this._embeddingDeploymentName = embeddingDeploymentName ?? "text-embedding-ada-002";
}
}
@@ -57,8 +62,17 @@ public sealed class FoundryMemoryProviderTests : IDisposable
this._memoryStoreName!,
stateInitializer: _ => new(new FoundryMemoryProviderScope("it-user-1")));
- AIAgent agent = await this._client!.CreateAIAgentAsync(this._deploymentName!,
- options: new ChatClientAgentOptions { AIContextProviders = [memoryProvider] });
+ await memoryProvider.EnsureMemoryStoreCreatedAsync(this._deploymentName!, this._embeddingDeploymentName!);
+
+ AIAgent agent = this._client!.AsAIAgent(new ChatClientAgentOptions
+ {
+ ChatOptions = new ChatOptions
+ {
+ ModelId = this._deploymentName!,
+ Instructions = "You are a helpful assistant. Use known memories about the user when responding, and do not invent details."
+ },
+ AIContextProviders = [memoryProvider]
+ });
AgentSession session = await agent.CreateSessionAsync();
@@ -72,6 +86,15 @@ public sealed class FoundryMemoryProviderTests : IDisposable
await memoryProvider.WhenUpdatesCompletedAsync();
await Task.Delay(2000);
+ // Assert - verify memories were actually created in the store before querying via agent
+ var searchResult = await this._client!.MemoryStores.SearchMemoriesAsync(
+ this._memoryStoreName!,
+ new MemorySearchOptions("it-user-1")
+ {
+ Items = { ResponseItem.CreateUserMessageItem("Caoimhe") }
+ });
+ Assert.NotEmpty(searchResult.Value.Memories);
+
AgentResponse resultAfter = await agent.RunAsync("What is my name?", session);
// Cleanup
@@ -95,10 +118,27 @@ public sealed class FoundryMemoryProviderTests : IDisposable
this._memoryStoreName!,
stateInitializer: _ => new(new FoundryMemoryProviderScope("it-scope-b")));
- AIAgent agent1 = await this._client!.CreateAIAgentAsync(this._deploymentName!,
- options: new ChatClientAgentOptions { AIContextProviders = [memoryProvider1] });
- AIAgent agent2 = await this._client!.CreateAIAgentAsync(this._deploymentName!,
- options: new ChatClientAgentOptions { AIContextProviders = [memoryProvider2] });
+ await memoryProvider1.EnsureMemoryStoreCreatedAsync(this._deploymentName!, this._embeddingDeploymentName!);
+
+ AIAgent agent1 = this._client!.AsAIAgent(new ChatClientAgentOptions
+ {
+ ChatOptions = new ChatOptions
+ {
+ ModelId = this._deploymentName!,
+ Instructions = "You are a helpful assistant. Use known memories about the user when responding, and do not invent details."
+ },
+ AIContextProviders = [memoryProvider1]
+ });
+
+ AIAgent agent2 = this._client!.AsAIAgent(new ChatClientAgentOptions
+ {
+ ChatOptions = new ChatOptions
+ {
+ ModelId = this._deploymentName!,
+ Instructions = "You are a helpful assistant. Use known memories about the user when responding, and do not invent details."
+ },
+ AIContextProviders = [memoryProvider2]
+ });
AgentSession session1 = await agent1.CreateSessionAsync();
AgentSession session2 = await agent2.CreateSessionAsync();
@@ -111,8 +151,25 @@ public sealed class FoundryMemoryProviderTests : IDisposable
await memoryProvider1.WhenUpdatesCompletedAsync();
await Task.Delay(2000);
- AgentResponse result1 = await agent1.RunAsync("What is your name?", session1);
- AgentResponse result2 = await agent2.RunAsync("What is your name?", session2);
+ // Assert - verify memories were created in scope A but not in scope B
+ var searchResultA = await this._client!.MemoryStores.SearchMemoriesAsync(
+ this._memoryStoreName!,
+ new MemorySearchOptions("it-scope-a")
+ {
+ Items = { ResponseItem.CreateUserMessageItem("Caoimhe") }
+ });
+ Assert.NotEmpty(searchResultA.Value.Memories);
+
+ var searchResultB = await this._client.MemoryStores.SearchMemoriesAsync(
+ this._memoryStoreName!,
+ new MemorySearchOptions("it-scope-b")
+ {
+ Items = { ResponseItem.CreateUserMessageItem("Caoimhe") }
+ });
+ Assert.Empty(searchResultB.Value.Memories);
+
+ AgentResponse result1 = await agent1.RunAsync("What is my name?", session1);
+ AgentResponse result2 = await agent2.RunAsync("What is my name?", session2);
// Assert
Assert.Contains("Caoimhe", result1.Text);
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentChatClientRunStreamingTests.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentChatClientRunStreamingTests.cs
similarity index 93%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentChatClientRunStreamingTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentChatClientRunStreamingTests.cs
index 5895ceb8b9..c07509e04e 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentChatClientRunStreamingTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentChatClientRunStreamingTests.cs
@@ -3,7 +3,7 @@
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
public class ResponsesAgentChatClientRunStreamingTests() : ChatClientAgentRunStreamingTests(() => new())
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentChatClientRunTests.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentChatClientRunTests.cs
similarity index 92%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentChatClientRunTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentChatClientRunTests.cs
index d80b25deb2..100a3c001b 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentChatClientRunTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentChatClientRunTests.cs
@@ -3,7 +3,7 @@
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
public class ResponsesAgentChatClientRunTests() : ChatClientAgentRunTests(() => new())
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentExtensionCreateTests.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentExtensionCreateTests.cs
similarity index 79%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentExtensionCreateTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentExtensionCreateTests.cs
index cd8dc6cb03..af358a9e55 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentExtensionCreateTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentExtensionCreateTests.cs
@@ -3,13 +3,13 @@
using System;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
+using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
using Microsoft.Extensions.AI;
using Shared.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
///
/// Integration tests for non-versioned creation via extension methods.
@@ -30,16 +30,19 @@ public class ResponsesAgentExtensionCreateTests
const string AgentDescription = "Integration test agent created from AIProjectClient.AsAIAgent(model, instructions).";
const string VerificationToken = "integration-extension-ok";
- FoundryAgent agent = this._client.AsAIAgent(
+ ChatClientAgent agent = this._client.AsAIAgent(
model: Model,
instructions: $"You are a helpful assistant. When asked for verification, reply with exactly '{VerificationToken}'.",
name: AgentName,
description: AgentDescription);
- AgentSession session = await agent.CreateSessionAsync();
+ AgentSession? session = null;
try
{
+ var conversation = await CreateConversationAsync(this._client);
+ session = await agent.CreateSessionAsync(conversation.Id);
+
// Act
AgentResponse response = await agent.RunAsync("Return the verification token.", session);
@@ -47,7 +50,6 @@ public class ResponsesAgentExtensionCreateTests
Assert.NotNull(agent);
Assert.Equal(AgentName, agent.Name);
Assert.Equal(AgentDescription, agent.Description);
- Assert.Same(this._client, agent.GetService());
Assert.NotNull(agent.GetService());
Assert.Contains(VerificationToken, response.Text, StringComparison.OrdinalIgnoreCase);
}
@@ -73,19 +75,22 @@ public class ResponsesAgentExtensionCreateTests
},
};
- FoundryAgent agent = this._client.AsAIAgent(options);
- ChatClientAgentSession session = await agent.CreateConversationSessionAsync();
+ ChatClientAgent agent = this._client.AsAIAgent(options);
+
+ ChatClientAgentSession? session = null;
try
{
+ var conversation = await CreateConversationAsync(this._client);
+ session = ((await agent.CreateSessionAsync(conversation.Id)) as ChatClientAgentSession)!;
+
// Act
AgentResponse response = await agent.RunAsync("Return the verification token.", session);
// Assert
- Assert.StartsWith("conv_", session.ConversationId, StringComparison.OrdinalIgnoreCase);
+ Assert.StartsWith("conv_", session!.ConversationId, StringComparison.OrdinalIgnoreCase);
Assert.Equal(options.Name, agent.Name);
Assert.Equal(options.Description, agent.Description);
- Assert.Same(this._client, agent.GetService());
Assert.Contains(VerificationToken, response.Text, StringComparison.OrdinalIgnoreCase);
}
finally
@@ -94,8 +99,13 @@ public class ResponsesAgentExtensionCreateTests
}
}
- private static async Task DeleteSessionAsync(AIProjectClient client, AgentSession session)
+ private static async Task DeleteSessionAsync(AIProjectClient client, AgentSession? session)
{
+ if (session is null)
+ {
+ return;
+ }
+
ChatClientAgentSession typedSession = (ChatClientAgentSession)session;
if (typedSession.ConversationId?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) == true)
@@ -119,4 +129,10 @@ public class ResponsesAgentExtensionCreateTests
await DeleteResponseChainAsync(client, response.Value.PreviousResponseId);
}
}
+
+ private static async Task CreateConversationAsync(AIProjectClient client)
+ {
+ ProjectConversationsClient conversationsClient = client.GetProjectOpenAIClient().GetProjectConversationsClient();
+ return (await conversationsClient.CreateProjectConversationAsync()).Value!;
+ }
}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentFixture.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentFixture.cs
similarity index 98%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentFixture.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentFixture.cs
index ec678a00d9..7bd0da0d95 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentFixture.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentFixture.cs
@@ -9,19 +9,18 @@ using AgentConformance.IntegrationTests.Support;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.AzureAI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
using Shared.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
///
/// Integration test fixture that creates non-versioned Responses agents via the direct AIProjectClient.AsAIAgent(...) path.
///
public class ResponsesAgentFixture : IChatClientAgentFixture
{
- private FoundryAgent _agent = null!;
+ private ChatClientAgent _agent = null!;
private AIProjectClient _client = null!;
public IChatClient ChatClient => this._agent.GetService()!.ChatClient;
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentRunStreamingTests.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentRunStreamingTests.cs
similarity index 96%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentRunStreamingTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentRunStreamingTests.cs
index 5f21c316c4..09f5fe6b2e 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentRunStreamingTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentRunStreamingTests.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using Microsoft.Agents.AI;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
public class ResponsesAgentRunStreamingPreviousResponseTests() : RunStreamingTests(() => new())
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentRunTests.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentRunTests.cs
similarity index 96%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentRunTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentRunTests.cs
index 71460f7737..0635b0f4ac 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentRunTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentRunTests.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using Microsoft.Agents.AI;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
public class ResponsesAgentRunPreviousResponseTests() : RunTests(() => new())
{
diff --git a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentStructuredOutputRunTests.cs b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentStructuredOutputRunTests.cs
similarity index 99%
rename from dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentStructuredOutputRunTests.cs
rename to dotnet/tests/Foundry.IntegrationTests/ResponsesAgentStructuredOutputRunTests.cs
index 19ebdb4e28..a561fbae1b 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/ResponsesAgentStructuredOutputRunTests.cs
+++ b/dotnet/tests/Foundry.IntegrationTests/ResponsesAgentStructuredOutputRunTests.cs
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Shared.IntegrationTests;
-namespace AzureAI.IntegrationTests;
+namespace Foundry.IntegrationTests;
public class ResponsesAgentStructuredOutputRunTests() : StructuredOutputRunTests>(() => new())
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
deleted file mode 100644
index 5c954d30e8..0000000000
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
+++ /dev/null
@@ -1,3472 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.ClientModel;
-using System.ClientModel.Primitives;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using Azure.AI.Extensions.OpenAI;
-using Azure.AI.Projects;
-using Azure.AI.Projects.Agents;
-using Microsoft.Extensions.AI;
-using Moq;
-using OpenAI.Responses;
-
-namespace Microsoft.Agents.AI.AzureAI.UnitTests;
-
-#pragma warning disable CS0618
-///
-/// Unit tests for the class.
-///
-[Obsolete("Includes coverage for obsolete AIProjectClient compatibility extension methods.")]
-public sealed class AzureAIProjectChatClientExtensionsTests
-{
- #region AsAIAgent(AIProjectClient, model, instructions) Tests
-
- ///
- /// Verify that the non-versioned AsAIAgent overload throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public void AsAIAgent_WithModelAndInstructions_WithNullClient_ThrowsArgumentNullException()
- {
- // Arrange
- AIProjectClient? client = null;
-
- // Act & Assert
- ArgumentNullException exception = Assert.Throws(() =>
- client!.AsAIAgent("gpt-4o-mini", "You are helpful."));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that the non-versioned AsAIAgent overload creates a valid ChatClientAgent.
- ///
- [Fact]
- public void AsAIAgent_WithModelAndInstructions_CreatesChatClientAgent()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- List tools =
- [
- AIFunctionFactory.Create(() => "test", "test_function", "A test function")
- ];
-
- // Act
- FoundryAgent agent = client.AsAIAgent(
- "gpt-4o-mini",
- "You are helpful.",
- name: "test-agent",
- description: "A test agent",
- tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("test-agent", agent.Name);
- Assert.Equal("A test agent", agent.Description);
- Assert.Same(client, agent.GetService());
- Assert.NotNull(agent.GetService());
- }
-
- ///
- /// Verify that the non-versioned AsAIAgent overload applies the clientFactory.
- ///
- [Fact]
- public void AsAIAgent_WithModelAndInstructions_WithClientFactory_AppliesFactoryCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- TestChatClient? testChatClient = null;
-
- // Act
- FoundryAgent agent = client.AsAIAgent(
- "gpt-4o-mini",
- "You are helpful.",
- clientFactory: innerClient => testChatClient = new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- TestChatClient? retrievedTestClient = agent.GetService();
- Assert.NotNull(retrievedTestClient);
- Assert.Same(testChatClient, retrievedTestClient);
- }
-
- ///
- /// Verify that the options-based non-versioned AsAIAgent overload creates a valid ChatClientAgent.
- ///
- [Fact]
- public void AsAIAgent_WithOptions_CreatesChatClientAgent()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- ChatClientAgentOptions options = new()
- {
- Name = "options-agent",
- Description = "Agent from options",
- ChatOptions = new ChatOptions
- {
- ModelId = "gpt-4o-mini",
- Instructions = "You are helpful.",
- },
- };
-
- // Act
- FoundryAgent agent = client.AsAIAgent(options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("options-agent", agent.Name);
- Assert.Equal("Agent from options", agent.Description);
- Assert.Same(client, agent.GetService());
- }
-
- ///
- /// Verify that the non-versioned AsAIAgent overload adds the MEAI user-agent header to Responses API requests.
- ///
- [Fact]
- public async Task AsAIAgent_WithModelAndInstructions_UserAgentHeaderAddedToResponsesRequestsAsync()
- {
- // Arrange
- bool userAgentFound = false;
- using HttpHandlerAssert httpHandler = new(request =>
- {
- if (request.Headers.TryGetValues("User-Agent", out IEnumerable? values))
- {
- foreach (string value in values)
- {
- if (value.Contains("MEAI"))
- {
- userAgentFound = true;
- }
- }
- }
-
- if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
- {
- return new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent(
- TestDataUtil.GetOpenAIDefaultResponseJson(),
- Encoding.UTF8,
- "application/json")
- };
- }
-
- return new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent("{}", Encoding.UTF8, "application/json")
- };
- });
-
-#pragma warning disable CA5399
- using HttpClient httpClient = new(httpHandler);
-#pragma warning restore CA5399
-
- AIProjectClient aiProjectClient = new(
- new Uri("https://test.openai.azure.com/"),
- new FakeAuthenticationTokenProvider(),
- new() { Transport = new HttpClientPipelineTransport(httpClient) });
-
- FoundryAgent agent = aiProjectClient.AsAIAgent(
- "gpt-4o-mini",
- "You are helpful.");
-
- // Act
- AgentSession session = await agent.CreateSessionAsync();
- await agent.RunAsync("Hello", session);
-
- // Assert
- Assert.True(userAgentFound, "MEAI user-agent header was not found in any request");
- }
-
- #endregion
-
- #region AsAIAgent(AIProjectClient, AgentRecord) Tests
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public void AsAIAgent_WithAgentRecord_WithNullClient_ThrowsArgumentNullException()
- {
- // Arrange
- AIProjectClient? client = null;
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- client!.AsAIAgent(agentRecord));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when agentRecord is null.
- ///
- [Fact]
- public void AsAIAgent_WithAgentRecord_WithNullAgentRecord_ThrowsArgumentNullException()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent((AgentRecord)null!));
-
- Assert.Equal("agentRecord", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent with AgentRecord creates a valid agent.
- ///
- [Fact]
- public void AsAIAgent_WithAgentRecord_CreatesValidAgent()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act
- var agent = client.AsAIAgent(agentRecord);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("agent_abc123", agent.Name);
- }
-
- ///
- /// Verify that AsAIAgent with AgentRecord and clientFactory applies the factory.
- ///
- [Fact]
- public void AsAIAgent_WithAgentRecord_WithClientFactory_AppliesFactoryCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
- TestChatClient? testChatClient = null;
-
- // Act
- var agent = client.AsAIAgent(
- agentRecord,
- clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- var retrievedTestClient = agent.GetService();
- Assert.NotNull(retrievedTestClient);
- Assert.Same(testChatClient, retrievedTestClient);
- }
-
- #endregion
-
- #region AsAIAgent(AIProjectClient, AgentVersion) Tests
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public void AsAIAgent_WithAgentVersion_WithNullClient_ThrowsArgumentNullException()
- {
- // Arrange
- AIProjectClient? client = null;
- AgentVersion agentVersion = this.CreateTestAgentVersion();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- client!.AsAIAgent(agentVersion));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when agentVersion is null.
- ///
- [Fact]
- public void AsAIAgent_WithAgentVersion_WithNullAgentVersion_ThrowsArgumentNullException()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent((AgentVersion)null!));
-
- Assert.Equal("agentVersion", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent with AgentVersion creates a valid agent.
- ///
- [Fact]
- public void AsAIAgent_WithAgentVersion_CreatesValidAgent()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
-
- // Act
- var agent = client.AsAIAgent(agentVersion);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("agent_abc123", agent.Name);
- }
-
- ///
- /// Verify that AsAIAgent with AgentVersion and clientFactory applies the factory.
- ///
- [Fact]
- public void AsAIAgent_WithAgentVersion_WithClientFactory_AppliesFactoryCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
- TestChatClient? testChatClient = null;
-
- // Act
- var agent = client.AsAIAgent(
- agentVersion,
- clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- var retrievedTestClient = agent.GetService();
- Assert.NotNull(retrievedTestClient);
- Assert.Same(testChatClient, retrievedTestClient);
- }
-
- ///
- /// Verify that AsAIAgent with requireInvocableTools=true enforces invocable tools.
- ///
- [Fact]
- public void AsAIAgent_WithAgentVersion_WithRequireInvocableToolsTrue_EnforcesInvocableTools()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
- var tools = new List
- {
- AIFunctionFactory.Create(() => "test", "test_function", "A test function")
- };
-
- // Act
- var agent = client.AsAIAgent(agentVersion, tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that AsAIAgent with requireInvocableTools=false allows declarative functions.
- ///
- [Fact]
- public void AsAIAgent_WithAgentVersion_WithRequireInvocableToolsFalse_AllowsDeclarativeFunctions()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
-
- // Act - should not throw even without tools when requireInvocableTools is false
- var agent = client.AsAIAgent(agentVersion);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- #endregion
-
- #region GetAIAgentAsync(AIProjectClient, ChatClientAgentOptions) Tests
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when client is null.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_WithNullClient_ThrowsArgumentNullExceptionAsync()
- {
- // Arrange
- AIProjectClient? client = null;
- var options = new ChatClientAgentOptions { Name = "test-agent" };
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- client!.GetAIAgentAsync(options));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when options is null.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_WithNullOptions_ThrowsArgumentNullExceptionAsync()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.GetAIAgentAsync((ChatClientAgentOptions)null!));
-
- Assert.Equal("options", exception.ParamName);
- }
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions creates a valid agent.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_CreatesValidAgentAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent");
- var options = new ChatClientAgentOptions { Name = "test-agent" };
-
- // Act
- var agent = await client.GetAIAgentAsync(options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("test-agent", agent.Name);
- }
-
- #endregion
-
- #region AsAIAgent(AIProjectClient, string) Tests
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public void AsAIAgent_ByName_WithNullClient_ThrowsArgumentNullException()
- {
- // Arrange
- AIProjectClient? client = null;
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- client!.AsAIAgent("test-agent"));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when name is null.
- ///
- [Fact]
- public void AsAIAgent_ByName_WithNullName_ThrowsArgumentNullException()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent((string)null!));
-
- Assert.Equal("name", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent throws ArgumentException when name is empty.
- ///
- [Fact]
- public void AsAIAgent_ByName_WithEmptyName_ThrowsArgumentException()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent(string.Empty));
-
- Assert.Equal("name", exception.ParamName);
- }
-
- #endregion
-
- #region GetAIAgentAsync(AIProjectClient, string) Tests
-
- ///
- /// Verify that GetAIAgentAsync throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public async Task GetAIAgentAsync_ByName_WithNullClient_ThrowsArgumentNullExceptionAsync()
- {
- // Arrange
- AIProjectClient? client = null;
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- client!.GetAIAgentAsync("test-agent"));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that GetAIAgentAsync throws ArgumentNullException when name is null.
- ///
- [Fact]
- public async Task GetAIAgentAsync_ByName_WithNullName_ThrowsArgumentNullExceptionAsync()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.GetAIAgentAsync(name: null!));
-
- Assert.Equal("name", exception.ParamName);
- }
-
- ///
- /// Verify that GetAIAgentAsync throws InvalidOperationException when agent is not found.
- ///
- [Fact]
- public async Task GetAIAgentAsync_ByName_WithNonExistentAgent_ThrowsInvalidOperationExceptionAsync()
- {
- // Arrange
- var mockAgentOperations = new Mock();
- mockAgentOperations
- .Setup(c => c.GetAgentAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(ClientResult.FromOptionalValue((AgentRecord)null!, new MockPipelineResponse(200, BinaryData.FromString("null"))));
-
- var mockClient = new Mock();
- mockClient.SetupGet(c => c.Agents).Returns(mockAgentOperations.Object);
- mockClient.Setup(x => x.GetConnection(It.IsAny())).Returns(new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None));
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.GetAIAgentAsync("non-existent-agent"));
-
- Assert.Contains("not found", exception.Message);
- }
-
- #endregion
-
- #region AsAIAgent(AIProjectClient, AgentRecord) with tools Tests
-
- ///
- /// Verify that AsAIAgent with additional tools when the definition has no tools does not throw and results in an agent with no tools.
- ///
- [Fact]
- public void AsAIAgent_WithAgentRecordAndAdditionalTools_WhenDefinitionHasNoTools_ShouldNotThrow()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
- var tools = new List
- {
- AIFunctionFactory.Create(() => "test", "test_function", "A test function")
- };
-
- // Act
- var agent = client.AsAIAgent(agentRecord, tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var chatClient = agent.GetService();
- Assert.NotNull(chatClient);
- var agentVersion = chatClient.GetService();
- Assert.NotNull(agentVersion);
- var definition = Assert.IsType(agentVersion.Definition);
- Assert.Empty(definition.Tools);
- }
-
- ///
- /// Verify that AsAIAgent with null tools works correctly.
- ///
- [Fact]
- public void AsAIAgent_WithAgentRecordAndNullTools_WorksCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act
- var agent = client.AsAIAgent(agentRecord, tools: null);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("agent_abc123", agent.Name);
- }
-
- #endregion
-
- #region GetAIAgentAsync(AIProjectClient, string) with tools Tests
-
- ///
- /// Verify that GetAIAgentAsync with tools parameter creates an agent.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithNameAndTools_CreatesAgentAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var tools = new List
- {
- AIFunctionFactory.Create(() => "test", "test_function", "A test function")
- };
-
- // Act
- var agent = await client.GetAIAgentAsync("test-agent", tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with model and options creates a valid agent.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithModelAndOptions_CreatesValidAgentAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", instructions: "Test instructions");
- var options = new ChatClientAgentOptions
- {
- Name = "test-agent",
- ChatOptions = new() { Instructions = "Test instructions" }
- };
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("test-agent", agent.Name);
- Assert.Equal("Test instructions", agent.GetService()!.Instructions);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with model and options and clientFactory applies the factory.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithModelAndOptions_WithClientFactory_AppliesFactoryCorrectlyAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", instructions: "Test instructions");
- var options = new ChatClientAgentOptions
- {
- Name = "test-agent",
- ChatOptions = new() { Instructions = "Test instructions" }
- };
- TestChatClient? testChatClient = null;
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync(
- "test-model",
- options,
- clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- var retrievedTestClient = agent.GetService();
- Assert.NotNull(retrievedTestClient);
- Assert.Same(testChatClient, retrievedTestClient);
- }
-
- #endregion
-
- #region CreateAIAgentAsync(AIProjectClient, string, AgentDefinition) Tests
-
- ///
- /// Verify that CreateAIAgentAsync throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithAgentDefinition_WithNullClient_ThrowsArgumentNullExceptionAsync()
- {
- // Arrange
- AIProjectClient? client = null;
- var definition = new PromptAgentDefinition("test-model");
- var options = new AgentVersionCreationOptions(definition);
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- client!.CreateAIAgentAsync("agent-name", options));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that CreateAIAgentAsync throws ArgumentNullException when creationOptions is null.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithAgentDefinition_WithNullDefinition_ThrowsArgumentNullExceptionAsync()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.CreateAIAgentAsync(name: "agent-name", null!));
-
- Assert.Equal("creationOptions", exception.ParamName);
- }
-
- #endregion
-
- #region Tool Validation Tests
-
- ///
- /// Verify that CreateAIAgent creates an agent successfully.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithDefinition_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler();
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgent without tools parameter creates an agent successfully.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithoutToolsParameter_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
-
- var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgent without tools in definition creates an agent successfully.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithoutToolsInDefinition_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definition);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgent uses tools from the definition when no separate tools parameter is provided.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithDefinitionTools_UsesDefinitionToolsAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
-
- // Add a function tool to the definition
- definition.Tools.Add(ResponseTool.CreateFunctionTool("required_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
-
- // Create a response definition with the same tool
- var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- if (agentVersion.Definition is PromptAgentDefinition promptDef)
- {
- Assert.NotEmpty(promptDef.Tools);
- Assert.Single(promptDef.Tools);
- Assert.Equal("required_tool", (promptDef.Tools.First() as FunctionTool)?.FunctionName);
- }
- }
-
- ///
- /// Verify that CreateAIAgent creates an agent successfully when definition has a mix of custom and hosted tools.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithMixedToolsInDefinition_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
- definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
- definition.Tools.Add(new HostedWebSearchTool().GetService() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
- definition.Tools.Add(new HostedFileSearchTool().GetService() ?? new HostedFileSearchTool().AsOpenAIResponseTool());
-
- // Simulate agent definition response with the tools
- var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
- foreach (var tool in definition.Tools)
- {
- definitionResponse.Tools.Add(tool);
- }
-
- using var testClient = CreateTestAgentClientWithHandler(agentDefinitionResponse: definitionResponse);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- if (agentVersion.Definition is PromptAgentDefinition promptDef)
- {
- Assert.NotEmpty(promptDef.Tools);
- Assert.Equal(3, promptDef.Tools.Count);
- }
- }
-
- ///
- /// Verify that CreateAIAgentAsync when AI Tools are provided, uses them for the definition via http request.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithNameAndAITools_SendsToolDefinitionViaHttpAsync()
- {
- // Arrange
- using var httpHandler = new HttpHandlerAssert(async (request) =>
- {
- if (request.Content is not null)
- {
- var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
-
- Assert.Contains("required_tool", requestBody);
- }
-
- return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json") };
- });
-
-#pragma warning disable CA5399
- using var httpClient = new HttpClient(httpHandler);
-#pragma warning restore CA5399
-
- var client = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
-
- // Act
- var agent = await client.CreateAIAgentAsync(
- name: "test-agent",
- model: "test-model",
- instructions: "Test",
- tools: [AIFunctionFactory.Create(() => true, "required_tool")]);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- Assert.IsType(agentVersion.Definition);
- }
-
- ///
- /// Verify that when providing AITools with AsAIAgent, any additional tool that doesn't match the tools in agent definition are ignored.
- ///
- [Fact]
- public void AsAIAgent_AdditionalAITools_WhenNotInTheDefinitionAreIgnored()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentVersion = this.CreateTestAgentVersion();
-
- // Manually add tools to the definition to simulate inline tools
- if (agentVersion.Definition is PromptAgentDefinition promptDef)
- {
- promptDef.Tools.Add(ResponseTool.CreateFunctionTool("inline_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
- }
-
- var invocableInlineAITool = AIFunctionFactory.Create(() => "test", "inline_tool", "An invocable AIFunction for the inline function");
- var shouldBeIgnoredTool = AIFunctionFactory.Create(() => "test", "additional_tool", "An additional test function that should be ignored");
-
- // Act & Assert
- var agent = client.AsAIAgent(agentVersion, tools: [invocableInlineAITool, shouldBeIgnoredTool]);
- Assert.NotNull(agent);
- var version = agent.GetService();
- Assert.NotNull(version);
- var definition = Assert.IsType(version.Definition);
- Assert.NotEmpty(definition.Tools);
- Assert.NotNull(GetAgentChatOptions(agent));
- Assert.NotNull(GetAgentChatOptions(agent)!.Tools);
- Assert.Single(GetAgentChatOptions(agent)!.Tools!);
- Assert.Equal("inline_tool", (definition.Tools.First() as FunctionTool)?.FunctionName);
- }
-
- #endregion
-
- #region Inline Tools vs Parameter Tools Tests
-
- ///
- /// Verify that tools passed as parameters are accepted by AsAIAgent.
- ///
- [Fact]
- public void AsAIAgent_WithParameterTools_AcceptsTools()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
- var tools = new List
- {
- AIFunctionFactory.Create(() => "tool1", "param_tool_1", "First parameter tool"),
- AIFunctionFactory.Create(() => "tool2", "param_tool_2", "Second parameter tool")
- };
-
- // Act
- var agent = client.AsAIAgent(agentRecord, tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var chatClient = agent.GetService();
- Assert.NotNull(chatClient);
- var agentVersion = chatClient.GetService();
- Assert.NotNull(agentVersion);
- }
-
- ///
- /// Verify that CreateAIAgent with string parameters and tools creates an agent.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithStringParamsAndTools_CreatesAgentAsync()
- {
- // Arrange
- var tools = new List
- {
- AIFunctionFactory.Create(() => "weather", "string_param_tool", "Tool from string params")
- };
-
- var definitionResponse = GeneratePromptDefinitionResponse(new PromptAgentDefinition("test-model") { Instructions = "Test instructions" }, tools);
-
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync(
- "test-agent",
- "test-model",
- "Test instructions",
- tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- if (agentVersion.Definition is PromptAgentDefinition promptDef)
- {
- Assert.NotEmpty(promptDef.Tools);
- Assert.Single(promptDef.Tools);
- }
- }
-
- ///
- /// Verify that CreateAIAgentAsync with tools in definition creates an agent.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithDefinitionTools_CreatesAgentAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler();
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
- definition.Tools.Add(ResponseTool.CreateFunctionTool("async_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that GetAIAgentAsync with tools parameter creates an agent.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithToolsParameter_CreatesAgentAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var tools = new List
- {
- AIFunctionFactory.Create(() => "async_get_result", "async_get_tool", "An async get tool")
- };
-
- // Act
- var agent = await client.GetAIAgentAsync("test-agent", tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- #endregion
-
- #region Declarative Function Handling Tests
-
- ///
- /// Verifies that CreateAIAgent uses tools from definition when they are ResponseTool instances, resulting in successful agent creation.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithResponseToolsInDefinition_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
-
- var fabricToolOptions = new FabricDataAgentToolOptions();
- fabricToolOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
-
- var sharepointOptions = new SharePointGroundingToolOptions();
- sharepointOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
-
- var structuredOutputs = new StructuredOutputDefinition("name", "description", new Dictionary { ["schema"] = BinaryData.FromString(AIJsonUtilities.CreateJsonSchema(new { id = "test" }.GetType()).ToString()) }, false);
-
- // 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" }])));
-
- // 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);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- if (agentVersion.Definition is PromptAgentDefinition promptDef)
- {
- Assert.NotEmpty(promptDef.Tools);
- Assert.Equal(10, promptDef.Tools.Count);
- }
- }
-
- ///
- /// Verify that CreateAIAgentAsync accepts FunctionTools from definition.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithFunctionToolsInDefinition_AcceptsDeclarativeFunctionAsync()
- {
- // Arrange
- var functionTool = ResponseTool.CreateFunctionTool(
- functionName: "get_user_name",
- functionParameters: BinaryData.FromString("{}"),
- strictModeEnabled: false,
- functionDescription: "Gets the user's name, as used for friendly address."
- );
-
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
- definition.Tools.Add(functionTool);
-
- // Generate response with the declarative function
- var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
- definitionResponse.Tools.Add(functionTool);
-
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgentAsync accepts declarative functions from definition.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithDeclarativeFunctionFromDefinition_AcceptsDeclarativeFunctionAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler();
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
-
- // Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
- using var doc = JsonDocument.Parse("{}");
- var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
-
- // Add to definition
- definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgentAsync accepts declarative functions from definition.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithDeclarativeFunctionInDefinition_AcceptsDeclarativeFunctionAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
-
- // Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
- using var doc = JsonDocument.Parse("{}");
- var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
-
- // Add to definition
- definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
-
- // Generate response with the declarative function
- var definitionResponse = new PromptAgentDefinition("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);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- #endregion
-
- #region Options Generation Validation Tests
-
- ///
- /// Verify that ChatClientAgentOptions are generated correctly without tools.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_GeneratesCorrectChatClientAgentOptionsAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
-
- var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
-
- // Assert
- Assert.NotNull(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- Assert.Equal("test-agent", agentVersion.Name);
- Assert.Equal("Test instructions", (agentVersion.Definition as PromptAgentDefinition)?.Instructions);
- }
-
- ///
- /// Verify that GetAIAgentAsync with options preserves custom properties from input options.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_PreservesCustomPropertiesAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Custom instructions", description: "Custom description");
- var options = new ChatClientAgentOptions
- {
- Name = "test-agent",
- Description = "Custom description",
- ChatOptions = new ChatOptions { Instructions = "Custom instructions" }
- };
-
- // Act
- var agent = await client.GetAIAgentAsync(options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("test-agent", agent.Name);
- Assert.Equal("Custom instructions", agent.GetService()!.Instructions);
- Assert.Equal("Custom description", agent.Description);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with options and tools generates correct ChatClientAgentOptions.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithOptionsAndTools_GeneratesCorrectOptionsAsync()
- {
- // Arrange
- var tools = new List
- {
- AIFunctionFactory.Create(() => "result", "option_tool", "A tool from options")
- };
-
- var definitionResponse = GeneratePromptDefinitionResponse(
- new PromptAgentDefinition("test-model") { Instructions = "Test" },
- tools);
-
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
-
- var options = new ChatClientAgentOptions
- {
- Name = "test-agent",
- ChatOptions = new ChatOptions { Instructions = "Test", Tools = tools }
- };
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
-
- // Assert
- Assert.NotNull(agent);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- if (agentVersion.Definition is PromptAgentDefinition promptDef)
- {
- Assert.NotEmpty(promptDef.Tools);
- Assert.Single(promptDef.Tools);
- }
- }
-
- #endregion
-
- #region AgentName Validation Tests
-
- ///
- /// Verify that AsAIAgent throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public void AsAIAgent_ByName_WithInvalidAgentName_ThrowsArgumentException(string invalidName)
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent(invalidName));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- ///
- /// Verify that GetAIAgentAsync throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public async Task GetAIAgentAsync_ByName_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.GetAIAgentAsync(invalidName));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public async Task GetAIAgentAsync_WithOptions_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions { Name = invalidName };
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- client.GetAIAgentAsync(options));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- ///
- /// Verify that CreateAIAgentAsync throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public async Task CreateAIAgentAsync_WithBasicParams_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.CreateAIAgentAsync(invalidName, "model", "instructions"));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with AgentVersionCreationOptions throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public async Task CreateAIAgentAsync_WithAgentDefinition_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
- {
- // Arrange
- var mockClient = new Mock();
- var definition = new PromptAgentDefinition("test-model");
- var options = new AgentVersionCreationOptions(definition);
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- mockClient.Object.CreateAIAgentAsync(invalidName, options));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with ChatClientAgentOptions throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public async Task CreateAIAgentAsync_WithOptions_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions { Name = invalidName };
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync(() =>
- client.CreateAIAgentAsync("test-model", options));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- ///
- /// Verify that AsAIAgent with AgentReference throws ArgumentException when agent name is invalid.
- ///
- [Theory]
- [MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
- public void AsAIAgent_WithAgentReference_WithInvalidAgentName_ThrowsArgumentException(string invalidName)
- {
- // Arrange
- var mockClient = new Mock();
- var agentReference = new AgentReference(invalidName, "1");
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent(agentReference));
-
- Assert.Equal("name", exception.ParamName);
- Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
- }
-
- #endregion
-
- #region AzureAIChatClient Behavior Tests
-
- ///
- /// Verify that the underlying chat client created by extension methods can be wrapped with clientFactory.
- ///
- [Fact]
- public void AsAIAgent_WithClientFactory_WrapsUnderlyingChatClient()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
- int factoryCallCount = 0;
-
- // Act
- var agent = client.AsAIAgent(
- agentRecord,
- clientFactory: (innerClient) =>
- {
- factoryCallCount++;
- return new TestChatClient(innerClient);
- });
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal(1, factoryCallCount);
- var wrappedClient = agent.GetService();
- Assert.NotNull(wrappedClient);
- }
-
- ///
- /// Verify that clientFactory is called with the correct underlying chat client.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithClientFactory_ReceivesCorrectUnderlyingClientAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler();
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
- IChatClient? receivedClient = null;
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync(
- "test-agent",
- options,
- clientFactory: (innerClient) =>
- {
- receivedClient = innerClient;
- return new TestChatClient(innerClient);
- });
-
- // Assert
- Assert.NotNull(agent);
- Assert.NotNull(receivedClient);
- var wrappedClient = agent.GetService();
- Assert.NotNull(wrappedClient);
- }
-
- ///
- /// Verify that multiple clientFactory calls create independent wrapped clients.
- ///
- [Fact]
- public void AsAIAgent_MultipleCallsWithClientFactory_CreatesIndependentClients()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act
- var agent1 = client.AsAIAgent(
- agentRecord,
- clientFactory: (innerClient) => new TestChatClient(innerClient));
-
- var agent2 = client.AsAIAgent(
- agentRecord,
- clientFactory: (innerClient) => new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent1);
- Assert.NotNull(agent2);
- var client1 = agent1.GetService();
- var client2 = agent2.GetService();
- Assert.NotNull(client1);
- Assert.NotNull(client2);
- Assert.NotSame(client1, client2);
- }
-
- ///
- /// Verify that agent created with clientFactory maintains agent properties.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithClientFactory_PreservesAgentPropertiesAsync()
- {
- // Arrange
- const string AgentName = "test-agent";
- const string Model = "test-model";
- const string Instructions = "Test instructions";
- using var testClient = CreateTestAgentClientWithHandler(AgentName, Instructions);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync(
- AgentName,
- Model,
- Instructions,
- clientFactory: (innerClient) => new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal(AgentName, agent.Name);
- Assert.Equal(Instructions, agent.GetService()!.Instructions);
- var wrappedClient = agent.GetService();
- Assert.NotNull(wrappedClient);
- }
-
- ///
- /// Verify that agent created with clientFactory is created successfully.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithClientFactory_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
-
- var agentDefinitionResponse = GeneratePromptDefinitionResponse(definition, null);
- using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: agentDefinitionResponse);
-
- var options = new AgentVersionCreationOptions(definition);
-
- // Act
- var agent = await testClient.Client.CreateAIAgentAsync(
- "test-agent",
- options,
- clientFactory: (innerClient) => new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- var wrappedClient = agent.GetService();
- Assert.NotNull(wrappedClient);
- var agentVersion = agent.GetService();
- Assert.NotNull(agentVersion);
- }
-
- #endregion
-
- #region User-Agent Header Tests
-
- ///
- /// Verifies that the MEAI user-agent header is added to CreateAIAgentAsync POST requests
- /// via the protocol method's RequestOptions pipeline policy.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_UserAgentHeaderAddedToRequestsAsync()
- {
- using var httpHandler = new HttpHandlerAssert(request =>
- {
- Assert.Equal("POST", request.Method.Method);
-
- // Verify MEAI user-agent header is present on CreateAgentVersion POST request
- Assert.True(request.Headers.TryGetValues("User-Agent", out var userAgentValues));
- Assert.Contains(userAgentValues, v => v.Contains("MEAI"));
-
- return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json") };
- });
-
-#pragma warning disable CA5399
- using var httpClient = new HttpClient(httpHandler);
-#pragma warning restore CA5399
-
- // Arrange
- var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
-
- var agentOptions = new ChatClientAgentOptions { Name = "test-agent" };
-
- // Act
- var agent = await aiProjectClient.CreateAIAgentAsync("test", agentOptions);
-
- // Assert
- Assert.NotNull(agent);
- }
-
- ///
- /// Verifies that the user-agent header is added to asynchronous GetAIAgentAsync requests.
- ///
- [Fact]
- public async Task GetAIAgent_UserAgentHeaderAddedToRequestsAsync()
- {
- using var httpHandler = new HttpHandlerAssert(request =>
- {
- Assert.Equal("GET", request.Method.Method);
- Assert.Contains("MEAI", request.Headers.UserAgent.ToString());
-
- return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
- });
-
-#pragma warning disable CA5399
- using var httpClient = new HttpClient(httpHandler);
-#pragma warning restore CA5399
-
- // Arrange
- var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
-
- // Act
- var agent = await aiProjectClient.GetAIAgentAsync("test");
-
- // Assert
- Assert.NotNull(agent);
- }
-
- #endregion
-
- #region GetAIAgent(AIProjectClient, AgentReference) Tests
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
- ///
- [Fact]
- public void AsAIAgent_WithAgentReference_WithNullClient_ThrowsArgumentNullException()
- {
- // Arrange
- AIProjectClient? client = null;
- var agentReference = new AgentReference("test-name", "1");
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- client!.AsAIAgent(agentReference));
-
- Assert.Equal("aiProjectClient", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent throws ArgumentNullException when agentReference is null.
- ///
- [Fact]
- public void AsAIAgent_WithAgentReference_WithNullAgentReference_ThrowsArgumentNullException()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert
- var exception = Assert.Throws(() =>
- mockClient.Object.AsAIAgent((AgentReference)null!));
-
- Assert.Equal("agentReference", exception.ParamName);
- }
-
- ///
- /// Verify that AsAIAgent with AgentReference creates a valid agent.
- ///
- [Fact]
- public void AsAIAgent_WithAgentReference_CreatesValidAgent()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-name", "1");
-
- // Act
- var agent = client.AsAIAgent(agentReference);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("test-name", agent.Name);
- Assert.Equal("test-name:1", agent.Id);
- }
-
- ///
- /// Verify that AsAIAgent with AgentReference and clientFactory applies the factory.
- ///
- [Fact]
- public void AsAIAgent_WithAgentReference_WithClientFactory_AppliesFactoryCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-name", "1");
- TestChatClient? testChatClient = null;
-
- // Act
- var agent = client.AsAIAgent(
- agentReference,
- clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
-
- // Assert
- Assert.NotNull(agent);
- var retrievedTestClient = agent.GetService();
- Assert.NotNull(retrievedTestClient);
- Assert.Same(testChatClient, retrievedTestClient);
- }
-
- ///
- /// Verify that AsAIAgent with AgentReference sets the agent ID correctly.
- ///
- [Fact]
- public void AsAIAgent_WithAgentReference_SetsAgentIdCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-name", "2");
-
- // Act
- var agent = client.AsAIAgent(agentReference);
-
- // Assert
- Assert.NotNull(agent);
- Assert.Equal("test-name:2", agent.Id);
- }
-
- ///
- /// Verify that AsAIAgent with AgentReference and tools includes the tools in ChatOptions.
- ///
- [Fact]
- public void AsAIAgent_WithAgentReference_WithTools_IncludesToolsInChatOptions()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-name", "1");
- var tools = new List
- {
- AIFunctionFactory.Create(() => "test", "test_function", "A test function")
- };
-
- // Act
- var agent = client.AsAIAgent(agentReference, tools: tools);
-
- // Assert
- Assert.NotNull(agent);
- var chatOptions = GetAgentChatOptions(agent);
- Assert.NotNull(chatOptions);
- Assert.NotNull(chatOptions.Tools);
- Assert.Single(chatOptions.Tools);
- }
-
- #endregion
-
- #region GetService Tests
-
- ///
- /// Verify that GetService returns AgentRecord for agents created from AgentRecord.
- ///
- [Fact]
- public void GetService_WithAgentRecord_ReturnsAgentRecord()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act
- var agent = client.AsAIAgent(agentRecord);
- var retrievedRecord = agent.GetService();
-
- // Assert
- Assert.NotNull(retrievedRecord);
- Assert.Equal(agentRecord.Id, retrievedRecord.Id);
- }
-
- ///
- /// Verify that GetService returns null for AgentRecord when agent is created from AgentReference.
- ///
- [Fact]
- public void GetService_WithAgentReference_ReturnsNullForAgentRecord()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-name", "1");
-
- // Act
- var agent = client.AsAIAgent(agentReference);
- var retrievedRecord = agent.GetService();
-
- // Assert
- Assert.Null(retrievedRecord);
- }
-
- #endregion
-
- #region GetService Tests
-
- ///
- /// Verify that GetService returns AgentVersion for agents created from AgentVersion.
- ///
- [Fact]
- public void GetService_WithAgentVersion_ReturnsAgentVersion()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
-
- // Act
- var agent = client.AsAIAgent(agentVersion);
- var retrievedVersion = agent.GetService();
-
- // Assert
- Assert.NotNull(retrievedVersion);
- Assert.Equal(agentVersion.Id, retrievedVersion.Id);
- }
-
- ///
- /// Verify that GetService returns null for AgentVersion when agent is created from AgentReference.
- ///
- [Fact]
- public void GetService_WithAgentReference_ReturnsNullForAgentVersion()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-name", "1");
-
- // Act
- var agent = client.AsAIAgent(agentReference);
- var retrievedVersion = agent.GetService();
-
- // Assert
- Assert.Null(retrievedVersion);
- }
-
- #endregion
-
- #region ChatClientMetadata Tests
-
- ///
- /// Verify that ChatClientMetadata is properly populated for agents created from AgentRecord.
- ///
- [Fact]
- public void ChatClientMetadata_WithAgentRecord_IsPopulatedCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act
- var agent = client.AsAIAgent(agentRecord);
- var metadata = agent.GetService();
-
- // Assert
- Assert.NotNull(metadata);
- Assert.NotNull(metadata.DefaultModelId);
- }
-
- ///
- /// Verify that ChatClientMetadata.DefaultModelId is set from PromptAgentDefinition model property.
- ///
- [Fact]
- public void ChatClientMetadata_WithPromptAgentDefinition_SetsDefaultModelIdFromModel()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var definition = new PromptAgentDefinition("gpt-4-turbo")
- {
- Instructions = "Test instructions"
- };
- AgentRecord agentRecord = this.CreateTestAgentRecord(definition);
-
- // Act
- var agent = client.AsAIAgent(agentRecord);
- var metadata = agent.GetService();
-
- // Assert
- Assert.NotNull(metadata);
- // The metadata should contain the model information from the agent definition
- Assert.NotNull(metadata.DefaultModelId);
- Assert.Equal("gpt-4-turbo", metadata.DefaultModelId);
- }
-
- ///
- /// Verify that ChatClientMetadata is properly populated for agents created from AgentVersion.
- ///
- [Fact]
- public void ChatClientMetadata_WithAgentVersion_IsPopulatedCorrectly()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
-
- // Act
- var agent = client.AsAIAgent(agentVersion);
- var metadata = agent.GetService();
-
- // Assert
- Assert.NotNull(metadata);
- Assert.NotNull(metadata.DefaultModelId);
- Assert.Equal((agentVersion.Definition as PromptAgentDefinition)!.Model, metadata.DefaultModelId);
- }
-
- #endregion
-
- #region AgentReference Availability Tests
-
- ///
- /// Verify that GetService returns AgentReference for agents created from AgentReference.
- ///
- [Fact]
- public void GetService_WithAgentReference_ReturnsAgentReference()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("test-agent", "1.0");
-
- // Act
- var agent = client.AsAIAgent(agentReference);
- var retrievedReference = agent.GetService();
-
- // Assert
- Assert.NotNull(retrievedReference);
- Assert.Equal("test-agent", retrievedReference.Name);
- Assert.Equal("1.0", retrievedReference.Version);
- }
-
- ///
- /// Verify that GetService returns null for AgentReference when agent is created from AgentRecord.
- ///
- [Fact]
- public void GetService_WithAgentRecord_ReturnsAlsoAgentReference()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentRecord agentRecord = this.CreateTestAgentRecord();
-
- // Act
- var agent = client.AsAIAgent(agentRecord);
- var retrievedReference = agent.GetService();
-
- // Assert
- Assert.NotNull(retrievedReference);
- Assert.Equal(agentRecord.Name, retrievedReference.Name);
- }
-
- ///
- /// Verify that GetService returns null for AgentReference when agent is created from AgentVersion.
- ///
- [Fact]
- public void GetService_WithAgentVersion_ReturnsAlsoAgentReference()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- AgentVersion agentVersion = this.CreateTestAgentVersion();
-
- // Act
- var agent = client.AsAIAgent(agentVersion);
- var retrievedReference = agent.GetService();
-
- // Assert
- Assert.NotNull(retrievedReference);
- Assert.Equal(agentVersion.Name, retrievedReference.Name);
- }
-
- ///
- /// Verify that GetService returns AgentReference with correct version information.
- ///
- [Fact]
- public void GetService_WithAgentReference_ReturnsCorrectVersionInformation()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var agentReference = new AgentReference("versioned-agent", "3.5");
-
- // Act
- var agent = client.AsAIAgent(agentReference);
- var retrievedReference = agent.GetService();
-
- // Assert
- Assert.NotNull(retrievedReference);
- Assert.Equal("versioned-agent", retrievedReference.Name);
- Assert.Equal("3.5", retrievedReference.Version);
- }
-
- #endregion
-
- #region GetAIAgentAsync - Empty Name Tests
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is null.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_WithNullName_ThrowsArgumentExceptionAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions { Name = null };
-
- // Act & Assert
- ArgumentException exception = await Assert.ThrowsAsync(() =>
- client.GetAIAgentAsync(options));
-
- Assert.Equal("options", exception.ParamName);
- Assert.Contains("Agent name must be provided", exception.Message);
- }
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is empty.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions { Name = string.Empty };
-
- // Act & Assert
- ArgumentException exception = await Assert.ThrowsAsync(() =>
- client.GetAIAgentAsync(options));
-
- Assert.Equal("options", exception.ParamName);
- Assert.Contains("Agent name must be provided", exception.Message);
- }
-
- ///
- /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is whitespace.
- ///
- [Fact]
- public async Task GetAIAgentAsync_WithOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions { Name = " " };
-
- // Act & Assert
- ArgumentException exception = await Assert.ThrowsAsync(() =>
- client.GetAIAgentAsync(options));
-
- Assert.Equal("options", exception.ParamName);
- Assert.Contains("Agent name must be provided", exception.Message);
- }
-
- #endregion
-
- #region CreateAIAgentAsync - Empty Name Tests
-
- ///
- /// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is null.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithModelAndOptions_WithNullName_ThrowsArgumentExceptionAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions
- {
- Name = null,
- ChatOptions = new ChatOptions { Instructions = "Test" }
- };
-
- // Act & Assert
- ArgumentException exception = await Assert.ThrowsAsync(() =>
- client.CreateAIAgentAsync("test-model", options));
-
- Assert.Equal("options", exception.ParamName);
- Assert.Contains("Agent name must be provided", exception.Message);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is empty.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithModelAndOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions
- {
- Name = string.Empty,
- ChatOptions = new ChatOptions { Instructions = "Test" }
- };
-
- // Act & Assert
- ArgumentException exception = await Assert.ThrowsAsync(() =>
- client.CreateAIAgentAsync("test-model", options));
-
- Assert.Equal("options", exception.ParamName);
- Assert.Contains("Agent name must be provided", exception.Message);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is whitespace.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithModelAndOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
- {
- // Arrange
- AIProjectClient client = this.CreateTestAgentClient();
- var options = new ChatClientAgentOptions
- {
- Name = " ",
- ChatOptions = new ChatOptions { Instructions = "Test" }
- };
-
- // Act & Assert
- ArgumentException exception = await Assert.ThrowsAsync(() =>
- client.CreateAIAgentAsync("test-model", options));
-
- Assert.Equal("options", exception.ParamName);
- Assert.Contains("Agent name must be provided", exception.Message);
- }
-
- #endregion
-
- #region CreateAIAgentAsync - Response Format Tests
-
- ///
- /// Verify that CreateAIAgentAsync with ChatResponseFormatText response format creates agent successfully.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithTextResponseFormat_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler();
- var options = new ChatClientAgentOptions
- {
- Name = "test-agent",
- ChatOptions = new ChatOptions
- {
- Instructions = "Test",
- ResponseFormat = ChatResponseFormat.Text
- }
- };
-
- // Act
- FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///
- /// Verify that CreateAIAgentAsync with ChatResponseFormatJson response format without schema creates agent successfully.
- ///
- [Fact]
- public async Task CreateAIAgentAsync_WithJsonResponseFormatWithoutSchema_CreatesAgentSuccessfullyAsync()
- {
- // Arrange
- using var testClient = CreateTestAgentClientWithHandler();
- var options = new ChatClientAgentOptions
- {
- Name = "test-agent",
- ChatOptions = new ChatOptions
- {
- Instructions = "Test",
- ResponseFormat = ChatResponseFormat.Json
- }
- };
-
- // Act
- FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
-
- // Assert
- Assert.NotNull(agent);
- Assert.IsType(agent);
- }
-
- ///