mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c0f0ec99a | ||
|
|
6185ba2125 | ||
|
|
dcc1eeac36 | ||
|
|
3f2096595f | ||
|
|
45a9da5523 | ||
|
|
6364c05efc | ||
|
|
bbb871e4cd | ||
|
|
7d7b8dd1a4 | ||
|
|
2f51a5ca78 | ||
|
|
ad5749c92a | ||
|
|
ed6b290457 | ||
|
|
63039cb748 | ||
|
|
3e7c94699f | ||
|
|
6320443969 |
@@ -34,7 +34,7 @@ runs:
|
|||||||
|
|
||||||
- name: Test Copilot CLI
|
- name: Test Copilot CLI
|
||||||
shell: bash
|
shell: bash
|
||||||
run: copilot --version && copilot -p "What can you do in one sentence?"
|
run: copilot -p "What can you do in one sentence?"
|
||||||
|
|
||||||
- name: Azure CLI Login
|
- name: Azure CLI Login
|
||||||
uses: azure/login@v2
|
uses: azure/login@v2
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
name: Setup Local MCP Server
|
|
||||||
description: Start and validate a local streamable HTTP MCP server for integration tests
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
fallback_url:
|
|
||||||
description: Existing LOCAL_MCP_URL value to keep as a fallback if local startup fails
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
host:
|
|
||||||
description: Host interface to bind the local MCP server
|
|
||||||
required: false
|
|
||||||
default: '127.0.0.1'
|
|
||||||
port:
|
|
||||||
description: Port to bind the local MCP server
|
|
||||||
required: false
|
|
||||||
default: '8011'
|
|
||||||
mount_path:
|
|
||||||
description: Mount path for the local streamable HTTP MCP endpoint
|
|
||||||
required: false
|
|
||||||
default: '/mcp'
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
effective_url:
|
|
||||||
description: Local MCP URL when startup succeeds, otherwise the provided fallback URL
|
|
||||||
value: ${{ steps.start.outputs.effective_url }}
|
|
||||||
local_url:
|
|
||||||
description: URL of the local MCP server
|
|
||||||
value: ${{ steps.start.outputs.local_url }}
|
|
||||||
started:
|
|
||||||
description: Whether the local MCP server started and passed validation
|
|
||||||
value: ${{ steps.start.outputs.started }}
|
|
||||||
pid:
|
|
||||||
description: PID of the local MCP server process when startup succeeded
|
|
||||||
value: ${{ steps.start.outputs.pid }}
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: composite
|
|
||||||
steps:
|
|
||||||
- name: Start and validate local MCP server
|
|
||||||
id: start
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
host="${{ inputs.host }}"
|
|
||||||
port="${{ inputs.port }}"
|
|
||||||
mount_path="${{ inputs.mount_path }}"
|
|
||||||
fallback_url="${{ inputs.fallback_url }}"
|
|
||||||
|
|
||||||
if [[ ! "$mount_path" =~ ^/ ]]; then
|
|
||||||
mount_path="/$mount_path"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local_url="http://${host}:${port}${mount_path}"
|
|
||||||
health_url="http://${host}:${port}/healthz"
|
|
||||||
log_file="$RUNNER_TEMP/local-mcp-server.log"
|
|
||||||
pid_file="$RUNNER_TEMP/local-mcp-server.pid"
|
|
||||||
rm -f "$log_file" "$pid_file"
|
|
||||||
|
|
||||||
server_pid="$(
|
|
||||||
python3 - "$GITHUB_WORKSPACE/python" "$log_file" "$host" "$port" "$mount_path" <<'PY'
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
workspace, log_file, host, port, mount_path = sys.argv[1:]
|
|
||||||
|
|
||||||
with open(log_file, "w", encoding="utf-8") as log:
|
|
||||||
process = subprocess.Popen(
|
|
||||||
[
|
|
||||||
"uv",
|
|
||||||
"run",
|
|
||||||
"python",
|
|
||||||
"scripts/local_mcp_streamable_http_server.py",
|
|
||||||
"--host",
|
|
||||||
host,
|
|
||||||
"--port",
|
|
||||||
port,
|
|
||||||
"--mount-path",
|
|
||||||
mount_path,
|
|
||||||
],
|
|
||||||
cwd=workspace,
|
|
||||||
stdout=log,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
start_new_session=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(process.pid)
|
|
||||||
PY
|
|
||||||
)"
|
|
||||||
echo "$server_pid" > "$pid_file"
|
|
||||||
|
|
||||||
started=false
|
|
||||||
for _ in $(seq 1 30); do
|
|
||||||
if curl --silent --fail "$health_url" >/dev/null; then
|
|
||||||
started=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$server_pid" 2>/dev/null; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$started" == "true" ]]; then
|
|
||||||
if ! (
|
|
||||||
cd "$GITHUB_WORKSPACE/python"
|
|
||||||
LOCAL_MCP_URL="$local_url" uv run python - <<'PY'
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import os
|
|
||||||
|
|
||||||
from agent_framework import Content, MCPStreamableHTTPTool
|
|
||||||
|
|
||||||
|
|
||||||
def result_to_text(result: str | list[Content]) -> str:
|
|
||||||
if isinstance(result, str):
|
|
||||||
return result
|
|
||||||
return "\n".join(content.text for content in result if content.type == "text" and content.text)
|
|
||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
|
||||||
tool = MCPStreamableHTTPTool(
|
|
||||||
name="local_ci_mcp",
|
|
||||||
url=os.environ["LOCAL_MCP_URL"],
|
|
||||||
approval_mode="never_require",
|
|
||||||
)
|
|
||||||
|
|
||||||
async with tool:
|
|
||||||
assert tool.functions, "Local MCP server did not expose any tools."
|
|
||||||
result = result_to_text(await tool.functions[0].invoke(query="What is Agent Framework?"))
|
|
||||||
assert result, "Local MCP server returned an empty response."
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(main())
|
|
||||||
PY
|
|
||||||
); then
|
|
||||||
started=false
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
effective_url="$local_url"
|
|
||||||
pid="$server_pid"
|
|
||||||
|
|
||||||
if [[ "$started" != "true" ]]; then
|
|
||||||
effective_url="$fallback_url"
|
|
||||||
pid=""
|
|
||||||
if kill -0 "$server_pid" 2>/dev/null; then
|
|
||||||
kill -TERM -- "-$server_pid" 2>/dev/null || kill -TERM "$server_pid" || true
|
|
||||||
sleep 1
|
|
||||||
kill -KILL -- "-$server_pid" 2>/dev/null || kill -KILL "$server_pid" || true
|
|
||||||
fi
|
|
||||||
echo "Local MCP server was unavailable; continuing with fallback LOCAL_MCP_URL."
|
|
||||||
if [[ -f "$log_file" ]]; then
|
|
||||||
tail -n 100 "$log_file" || true
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Using local MCP server at $local_url"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "started=$started" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "local_url=$local_url" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "effective_url=$effective_url" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "pid=$pid" >> "$GITHUB_OUTPUT"
|
|
||||||
@@ -41,7 +41,8 @@ ENFORCED_TARGETS: set[str] = {
|
|||||||
"packages.purview.agent_framework_purview",
|
"packages.purview.agent_framework_purview",
|
||||||
"packages.anthropic.agent_framework_anthropic",
|
"packages.anthropic.agent_framework_anthropic",
|
||||||
"packages.azure-ai-search.agent_framework_azure_ai_search",
|
"packages.azure-ai-search.agent_framework_azure_ai_search",
|
||||||
"packages.openai.agent_framework_openai",
|
"packages.core.agent_framework.azure",
|
||||||
|
"packages.core.agent_framework.openai",
|
||||||
# Individual files (if you want to enforce specific files instead of whole packages)
|
# Individual files (if you want to enforce specific files instead of whole packages)
|
||||||
"packages/core/agent_framework/observability.py",
|
"packages/core/agent_framework/observability.py",
|
||||||
# Add more targets here as coverage improves
|
# Add more targets here as coverage improves
|
||||||
|
|||||||
@@ -60,10 +60,9 @@ jobs:
|
|||||||
environment: integration
|
environment: integration
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
env:
|
env:
|
||||||
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_EMBEDDINGS_MODEL_ID: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
||||||
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -82,8 +81,8 @@ jobs:
|
|||||||
- name: Test with pytest (OpenAI integration)
|
- name: Test with pytest (OpenAI integration)
|
||||||
run: >
|
run: >
|
||||||
uv run pytest --import-mode=importlib
|
uv run pytest --import-mode=importlib
|
||||||
packages/openai/tests
|
packages/core/tests/openai
|
||||||
-m "integration and not azure"
|
-m integration
|
||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
--retries 2 --retry-delay 5
|
--retries 2 --retry-delay 5
|
||||||
@@ -97,8 +96,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }}
|
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -123,9 +121,7 @@ jobs:
|
|||||||
- name: Test with pytest (Azure OpenAI integration)
|
- name: Test with pytest (Azure OpenAI integration)
|
||||||
run: >
|
run: >
|
||||||
uv run pytest --import-mode=importlib
|
uv run pytest --import-mode=importlib
|
||||||
packages/openai/tests/openai/test_openai_chat_completion_client_azure.py
|
packages/core/tests/azure
|
||||||
packages/openai/tests/openai/test_openai_chat_client_azure.py
|
|
||||||
packages/openai/tests/openai/test_openai_embedding_client_azure.py
|
|
||||||
-m integration
|
-m integration
|
||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
@@ -155,13 +151,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ env.UV_PYTHON }}
|
python-version: ${{ env.UV_PYTHON }}
|
||||||
os: ${{ runner.os }}
|
os: ${{ runner.os }}
|
||||||
- name: Start local MCP server
|
|
||||||
id: local-mcp
|
|
||||||
uses: ./.github/actions/setup-local-mcp-server
|
|
||||||
with:
|
|
||||||
fallback_url: ${{ env.LOCAL_MCP_URL }}
|
|
||||||
- name: Prefer local MCP URL when available
|
|
||||||
run: echo "LOCAL_MCP_URL=${{ steps.local-mcp.outputs.effective_url }}" >> "$GITHUB_ENV"
|
|
||||||
- name: Test with pytest (Anthropic, Ollama, MCP integration)
|
- name: Test with pytest (Anthropic, Ollama, MCP integration)
|
||||||
run: >
|
run: >
|
||||||
uv run pytest --import-mode=importlib
|
uv run pytest --import-mode=importlib
|
||||||
@@ -172,26 +161,6 @@ jobs:
|
|||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
--retries 2 --retry-delay 5
|
--retries 2 --retry-delay 5
|
||||||
- name: Stop local MCP server
|
|
||||||
if: always()
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
server_pid="${{ steps.local-mcp.outputs.pid }}"
|
|
||||||
if [[ -z "$server_pid" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$server_pid" 2>/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
kill -TERM -- "-$server_pid" 2>/dev/null || kill -TERM "$server_pid" 2>/dev/null || true
|
|
||||||
for _ in $(seq 1 10); do
|
|
||||||
if ! kill -0 "$server_pid" 2>/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
kill -KILL -- "-$server_pid" 2>/dev/null || kill -KILL "$server_pid" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Azure Functions + Durable Task integration tests
|
# Azure Functions + Durable Task integration tests
|
||||||
python-tests-functions:
|
python-tests-functions:
|
||||||
@@ -201,16 +170,12 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
env:
|
env:
|
||||||
UV_PYTHON: "3.11"
|
UV_PYTHON: "3.11"
|
||||||
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
|
||||||
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
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_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
FUNCTIONS_WORKER_RUNTIME: "python"
|
FUNCTIONS_WORKER_RUNTIME: "python"
|
||||||
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
|
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
|
||||||
AzureWebJobsStorage: "UseDevelopmentStorage=true"
|
AzureWebJobsStorage: "UseDevelopmentStorage=true"
|
||||||
@@ -244,23 +209,18 @@ jobs:
|
|||||||
packages/durabletask/tests/integration_tests
|
packages/durabletask/tests/integration_tests
|
||||||
-m integration
|
-m integration
|
||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
-x
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
--timeout=360 --session-timeout=900 --timeout_method thread
|
|
||||||
--retries 2 --retry-delay 5
|
--retries 2 --retry-delay 5
|
||||||
|
|
||||||
# Foundry integration tests
|
# Azure AI integration tests
|
||||||
python-tests-foundry:
|
python-tests-azure-ai:
|
||||||
name: Python Integration Tests - Foundry
|
name: Python Integration Tests - Azure AI
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
env:
|
env:
|
||||||
AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
|
||||||
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
|
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
|
|
||||||
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_AGENT_NAME }}
|
|
||||||
FOUNDRY_AGENT_VERSION: ${{ vars.FOUNDRY_AGENT_VERSION }}
|
|
||||||
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
|
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -284,13 +244,7 @@ jobs:
|
|||||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
run: >
|
run: uv run --directory packages/azure-ai poe integration-tests -n logical --dist worksteal --timeout=120 --session-timeout=900 --timeout_method thread --retries 2 --retry-delay 5
|
||||||
uv run pytest --import-mode=importlib
|
|
||||||
packages/foundry/tests
|
|
||||||
-m integration
|
|
||||||
-n logical --dist worksteal
|
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
|
||||||
--retries 2 --retry-delay 5
|
|
||||||
|
|
||||||
# Azure Cosmos integration tests
|
# Azure Cosmos integration tests
|
||||||
python-tests-cosmos:
|
python-tests-cosmos:
|
||||||
@@ -347,7 +301,7 @@ jobs:
|
|||||||
python-tests-azure-openai,
|
python-tests-azure-openai,
|
||||||
python-tests-misc-integration,
|
python-tests-misc-integration,
|
||||||
python-tests-functions,
|
python-tests-functions,
|
||||||
python-tests-foundry,
|
python-tests-azure-ai,
|
||||||
python-tests-cosmos
|
python-tests-cosmos
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ jobs:
|
|||||||
filters: |
|
filters: |
|
||||||
python:
|
python:
|
||||||
- 'python/**'
|
- 'python/**'
|
||||||
- '.github/actions/setup-local-mcp-server/**'
|
|
||||||
- '.github/workflows/python-merge-tests.yml'
|
|
||||||
- '.github/workflows/python-integration-tests.yml'
|
|
||||||
core:
|
core:
|
||||||
- 'python/packages/core/agent_framework/_*.py'
|
- 'python/packages/core/agent_framework/_*.py'
|
||||||
- 'python/packages/core/agent_framework/_workflows/**'
|
- 'python/packages/core/agent_framework/_workflows/**'
|
||||||
@@ -57,28 +54,20 @@ jobs:
|
|||||||
- 'python/packages/core/agent_framework/observability.py'
|
- 'python/packages/core/agent_framework/observability.py'
|
||||||
openai:
|
openai:
|
||||||
- 'python/packages/core/agent_framework/openai/**'
|
- 'python/packages/core/agent_framework/openai/**'
|
||||||
- 'python/packages/openai/**'
|
- 'python/packages/core/tests/openai/**'
|
||||||
- 'python/samples/**/providers/openai/**'
|
|
||||||
azure:
|
azure:
|
||||||
- 'python/packages/openai/**'
|
|
||||||
- 'python/packages/core/agent_framework/azure/**'
|
- 'python/packages/core/agent_framework/azure/**'
|
||||||
- 'python/samples/**/providers/azure/**'
|
- 'python/packages/core/tests/azure/**'
|
||||||
misc:
|
misc:
|
||||||
- 'python/packages/anthropic/**'
|
- 'python/packages/anthropic/**'
|
||||||
- 'python/packages/ollama/**'
|
- 'python/packages/ollama/**'
|
||||||
- 'python/packages/core/agent_framework/_mcp.py'
|
- 'python/packages/core/agent_framework/_mcp.py'
|
||||||
- 'python/packages/core/tests/core/test_mcp.py'
|
- 'python/packages/core/tests/core/test_mcp.py'
|
||||||
- 'python/scripts/local_mcp_streamable_http_server.py'
|
|
||||||
- '.github/actions/setup-local-mcp-server/**'
|
|
||||||
- '.github/workflows/python-merge-tests.yml'
|
|
||||||
- '.github/workflows/python-integration-tests.yml'
|
|
||||||
functions:
|
functions:
|
||||||
- 'python/packages/azurefunctions/**'
|
- 'python/packages/azurefunctions/**'
|
||||||
- 'python/packages/durabletask/**'
|
- 'python/packages/durabletask/**'
|
||||||
azure-ai:
|
azure-ai:
|
||||||
- 'python/packages/azure-ai/**'
|
- 'python/packages/azure-ai/**'
|
||||||
- 'python/packages/foundry/**'
|
|
||||||
- 'python/samples/**/providers/foundry/**'
|
|
||||||
cosmos:
|
cosmos:
|
||||||
- 'python/packages/azure-cosmos/**'
|
- 'python/packages/azure-cosmos/**'
|
||||||
# run only if 'python' files were changed
|
# run only if 'python' files were changed
|
||||||
@@ -139,10 +128,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_EMBEDDINGS_MODEL_ID: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
||||||
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -158,8 +146,8 @@ jobs:
|
|||||||
- name: Test with pytest (OpenAI integration)
|
- name: Test with pytest (OpenAI integration)
|
||||||
run: >
|
run: >
|
||||||
uv run pytest --import-mode=importlib
|
uv run pytest --import-mode=importlib
|
||||||
packages/openai/tests
|
packages/core/tests/openai
|
||||||
-m "integration and not azure"
|
-m integration
|
||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
--retries 2 --retry-delay 5
|
--retries 2 --retry-delay 5
|
||||||
@@ -194,8 +182,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }}
|
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -218,9 +205,7 @@ jobs:
|
|||||||
- name: Test with pytest (Azure OpenAI integration)
|
- name: Test with pytest (Azure OpenAI integration)
|
||||||
run: >
|
run: >
|
||||||
uv run pytest --import-mode=importlib
|
uv run pytest --import-mode=importlib
|
||||||
packages/openai/tests/openai/test_openai_chat_completion_client_azure.py
|
packages/core/tests/azure
|
||||||
packages/openai/tests/openai/test_openai_chat_client_azure.py
|
|
||||||
packages/openai/tests/openai/test_openai_embedding_client_azure.py
|
|
||||||
-m integration
|
-m integration
|
||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
@@ -268,13 +253,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ env.UV_PYTHON }}
|
python-version: ${{ env.UV_PYTHON }}
|
||||||
os: ${{ runner.os }}
|
os: ${{ runner.os }}
|
||||||
- name: Start local MCP server
|
|
||||||
id: local-mcp
|
|
||||||
uses: ./.github/actions/setup-local-mcp-server
|
|
||||||
with:
|
|
||||||
fallback_url: ${{ env.LOCAL_MCP_URL }}
|
|
||||||
- name: Prefer local MCP URL when available
|
|
||||||
run: echo "LOCAL_MCP_URL=${{ steps.local-mcp.outputs.effective_url }}" >> "$GITHUB_ENV"
|
|
||||||
- name: Test with pytest (Anthropic, Ollama, MCP integration)
|
- name: Test with pytest (Anthropic, Ollama, MCP integration)
|
||||||
run: >
|
run: >
|
||||||
uv run pytest --import-mode=importlib
|
uv run pytest --import-mode=importlib
|
||||||
@@ -286,26 +264,6 @@ jobs:
|
|||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
--retries 2 --retry-delay 5
|
--retries 2 --retry-delay 5
|
||||||
working-directory: ./python
|
working-directory: ./python
|
||||||
- name: Stop local MCP server
|
|
||||||
if: always()
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
server_pid="${{ steps.local-mcp.outputs.pid }}"
|
|
||||||
if [[ -z "$server_pid" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$server_pid" 2>/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
kill -TERM -- "-$server_pid" 2>/dev/null || kill -TERM "$server_pid" 2>/dev/null || true
|
|
||||||
for _ in $(seq 1 10); do
|
|
||||||
if ! kill -0 "$server_pid" 2>/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
kill -KILL -- "-$server_pid" 2>/dev/null || kill -KILL "$server_pid" 2>/dev/null || true
|
|
||||||
- name: Surface failing tests
|
- name: Surface failing tests
|
||||||
if: always()
|
if: always()
|
||||||
uses: pmeier/pytest-results-action@v0.7.2
|
uses: pmeier/pytest-results-action@v0.7.2
|
||||||
@@ -330,16 +288,12 @@ jobs:
|
|||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
UV_PYTHON: "3.11"
|
UV_PYTHON: "3.11"
|
||||||
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
|
||||||
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
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_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
FUNCTIONS_WORKER_RUNTIME: "python"
|
FUNCTIONS_WORKER_RUNTIME: "python"
|
||||||
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
|
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
|
||||||
AzureWebJobsStorage: "UseDevelopmentStorage=true"
|
AzureWebJobsStorage: "UseDevelopmentStorage=true"
|
||||||
@@ -371,8 +325,7 @@ jobs:
|
|||||||
packages/durabletask/tests/integration_tests
|
packages/durabletask/tests/integration_tests
|
||||||
-m integration
|
-m integration
|
||||||
-n logical --dist worksteal
|
-n logical --dist worksteal
|
||||||
-x
|
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||||
--timeout=360 --session-timeout=900 --timeout_method thread
|
|
||||||
--retries 2 --retry-delay 5
|
--retries 2 --retry-delay 5
|
||||||
working-directory: ./python
|
working-directory: ./python
|
||||||
- name: Surface failing tests
|
- name: Surface failing tests
|
||||||
@@ -385,8 +338,8 @@ jobs:
|
|||||||
fail-on-empty: false
|
fail-on-empty: false
|
||||||
title: Functions integration test results
|
title: Functions integration test results
|
||||||
|
|
||||||
python-tests-foundry:
|
python-tests-azure-ai:
|
||||||
name: Python Integration Tests - Foundry
|
name: Python Tests - Azure AI
|
||||||
needs: paths-filter
|
needs: paths-filter
|
||||||
if: >
|
if: >
|
||||||
github.event_name != 'pull_request' &&
|
github.event_name != 'pull_request' &&
|
||||||
@@ -399,10 +352,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
|
||||||
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
|
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
|
|
||||||
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_AGENT_NAME }}
|
|
||||||
FOUNDRY_AGENT_VERSION: ${{ vars.FOUNDRY_AGENT_VERSION }}
|
|
||||||
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
|
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -424,13 +373,7 @@ jobs:
|
|||||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
run: >
|
run: uv run --directory packages/azure-ai poe integration-tests -n logical --dist worksteal --timeout=120 --session-timeout=900 --timeout_method thread --retries 2 --retry-delay 5
|
||||||
uv run pytest --import-mode=importlib
|
|
||||||
packages/foundry/tests
|
|
||||||
-m integration
|
|
||||||
-n logical --dist worksteal
|
|
||||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
|
||||||
--retries 2 --retry-delay 5
|
|
||||||
working-directory: ./python
|
working-directory: ./python
|
||||||
- name: Test Azure AI samples
|
- name: Test Azure AI samples
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
@@ -517,7 +460,7 @@ jobs:
|
|||||||
python-tests-azure-openai,
|
python-tests-azure-openai,
|
||||||
python-tests-misc-integration,
|
python-tests-misc-integration,
|
||||||
python-tests-functions,
|
python-tests-functions,
|
||||||
python-tests-foundry,
|
python-tests-azure-ai,
|
||||||
python-tests-cosmos,
|
python-tests-cosmos,
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ jobs:
|
|||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
# Required configuration for get-started samples
|
# Required configuration for get-started samples
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -41,8 +43,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
|
echo "AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=$AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
||||||
|
echo "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=$AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" >> .env
|
||||||
|
|
||||||
- name: Run sample validation
|
- name: Run sample validation
|
||||||
run: |
|
run: |
|
||||||
@@ -60,20 +64,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
# Foundry configuration
|
# Azure AI configuration
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# Azure OpenAI configuration
|
# Azure OpenAI configuration
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME || vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
|
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
|
||||||
# OpenAI configuration
|
# OpenAI configuration
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
# GitHub MCP
|
# GitHub MCP
|
||||||
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
|
||||||
# Observability
|
# Observability
|
||||||
ENABLE_INSTRUMENTATION: "true"
|
ENABLE_INSTRUMENTATION: "true"
|
||||||
defaults:
|
defaults:
|
||||||
@@ -92,10 +96,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
|
echo "AZURE_AI_MODEL_DEPLOYMENT_NAME=$AZURE_AI_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
||||||
echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
|
echo "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=$AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=$AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME" >> .env
|
||||||
echo "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=$AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME" >> .env
|
echo "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=$AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME" >> .env
|
||||||
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
||||||
echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
||||||
@@ -119,7 +124,6 @@ jobs:
|
|||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__CHATMODELID }}
|
|
||||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
defaults:
|
defaults:
|
||||||
@@ -139,7 +143,6 @@ jobs:
|
|||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
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_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
||||||
echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
|
echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
|
||||||
|
|
||||||
@@ -154,14 +157,15 @@ jobs:
|
|||||||
name: validation-report-02-agents-openai
|
name: validation-report-02-agents-openai
|
||||||
path: python/samples/sample_validation/reports/
|
path: python/samples/sample_validation/reports/
|
||||||
|
|
||||||
validate-02-agents-azure:
|
validate-02-agents-azure-openai:
|
||||||
name: Validate 02-agents/providers/azure
|
name: Validate 02-agents/providers/azure_openai
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION || '' }}
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -178,19 +182,100 @@ jobs:
|
|||||||
|
|
||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
||||||
echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
|
echo "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=$AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" >> .env
|
||||||
echo "AZURE_OPENAI_API_VERSION=$AZURE_OPENAI_API_VERSION" >> .env
|
echo "AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=$AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME" >> .env
|
||||||
|
|
||||||
- name: Run sample validation
|
- name: Run sample validation
|
||||||
run: |
|
run: |
|
||||||
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/azure --save-report --report-name 02-agents-azure
|
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/azure_openai --save-report --report-name 02-agents-azure-openai
|
||||||
|
|
||||||
- name: Upload validation report
|
- name: Upload validation report
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: validation-report-02-agents-azure
|
name: validation-report-02-agents-azure-openai
|
||||||
|
path: python/samples/sample_validation/reports/
|
||||||
|
|
||||||
|
validate-02-agents-azure-ai:
|
||||||
|
name: Validate 02-agents/providers/azure_ai
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: integration
|
||||||
|
env:
|
||||||
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
|
AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
|
AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
|
||||||
|
BING_CONNECTION_ID: ${{ secrets.BING_CONNECTION_ID }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: python
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup environment
|
||||||
|
uses: ./.github/actions/sample-validation-setup
|
||||||
|
with:
|
||||||
|
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
os: ${{ runner.os }}
|
||||||
|
|
||||||
|
- name: Create .env for samples
|
||||||
|
run: |
|
||||||
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
|
echo "AZURE_AI_MODEL_DEPLOYMENT_NAME=$AZURE_AI_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME=$AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME=$AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "BING_CONNECTION_ID=$BING_CONNECTION_ID" >> .env
|
||||||
|
|
||||||
|
- name: Run sample validation
|
||||||
|
run: |
|
||||||
|
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/azure_ai --save-report --report-name 02-agents-azure-ai
|
||||||
|
|
||||||
|
- name: Upload validation report
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: validation-report-02-agents-azure-ai
|
||||||
|
path: python/samples/sample_validation/reports/
|
||||||
|
|
||||||
|
validate-02-agents-azure-ai-agent:
|
||||||
|
name: Validate 02-agents/providers/azure_ai_agent
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: integration
|
||||||
|
env:
|
||||||
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: python
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup environment
|
||||||
|
uses: ./.github/actions/sample-validation-setup
|
||||||
|
with:
|
||||||
|
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
os: ${{ runner.os }}
|
||||||
|
|
||||||
|
- name: Create .env for samples
|
||||||
|
run: |
|
||||||
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
|
echo "AZURE_AI_MODEL_DEPLOYMENT_NAME=$AZURE_AI_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
|
|
||||||
|
- name: Run sample validation
|
||||||
|
run: |
|
||||||
|
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/azure_ai_agent --save-report --report-name 02-agents-azure-ai-agent
|
||||||
|
|
||||||
|
- name: Upload validation report
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: validation-report-02-agents-azure-ai-agent
|
||||||
path: python/samples/sample_validation/reports/
|
path: python/samples/sample_validation/reports/
|
||||||
|
|
||||||
validate-02-agents-anthropic:
|
validate-02-agents-anthropic:
|
||||||
@@ -261,7 +346,7 @@ jobs:
|
|||||||
|
|
||||||
validate-02-agents-amazon:
|
validate-02-agents-amazon:
|
||||||
name: Validate 02-agents/providers/amazon
|
name: Validate 02-agents/providers/amazon
|
||||||
if: false # Temporarily disabled - requires AWS credentials
|
if: false # Temporarily disabled - requires AWS credentials
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
@@ -293,7 +378,7 @@ jobs:
|
|||||||
|
|
||||||
validate-02-agents-ollama:
|
validate-02-agents-ollama:
|
||||||
name: Validate 02-agents/providers/ollama
|
name: Validate 02-agents/providers/ollama
|
||||||
if: false # Temporarily disabled - requires local Ollama server
|
if: false # Temporarily disabled - requires local Ollama server
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
@@ -323,16 +408,11 @@ jobs:
|
|||||||
name: validation-report-02-agents-ollama
|
name: validation-report-02-agents-ollama
|
||||||
path: python/samples/sample_validation/reports/
|
path: python/samples/sample_validation/reports/
|
||||||
|
|
||||||
validate-02-agents-foundry:
|
validate-02-agents-foundry-local:
|
||||||
name: Validate 02-agents/providers/foundry
|
name: Validate 02-agents/providers/foundry_local
|
||||||
if: false # Temporarily disabled - provider folder also contains the local Foundry sample
|
if: false # Temporarily disabled - requires local Foundry setup
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
|
||||||
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_AGENT_NAME || '' }}
|
|
||||||
FOUNDRY_AGENT_VERSION: ${{ vars.FOUNDRY_AGENT_VERSION || '' }}
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -347,27 +427,20 @@ jobs:
|
|||||||
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
os: ${{ runner.os }}
|
os: ${{ runner.os }}
|
||||||
|
|
||||||
- name: Create .env for samples
|
|
||||||
run: |
|
|
||||||
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
|
|
||||||
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
|
|
||||||
echo "FOUNDRY_AGENT_NAME=$FOUNDRY_AGENT_NAME" >> .env
|
|
||||||
echo "FOUNDRY_AGENT_VERSION=$FOUNDRY_AGENT_VERSION" >> .env
|
|
||||||
|
|
||||||
- name: Run sample validation
|
- name: Run sample validation
|
||||||
run: |
|
run: |
|
||||||
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/foundry --save-report --report-name 02-agents-foundry
|
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/foundry_local --save-report --report-name 02-agents-foundry-local
|
||||||
|
|
||||||
- name: Upload validation report
|
- name: Upload validation report
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: validation-report-02-agents-foundry
|
name: validation-report-02-agents-foundry-local
|
||||||
path: python/samples/sample_validation/reports/
|
path: python/samples/sample_validation/reports/
|
||||||
|
|
||||||
validate-02-agents-copilotstudio:
|
validate-02-agents-copilotstudio:
|
||||||
name: Validate 02-agents/providers/copilotstudio
|
name: Validate 02-agents/providers/copilotstudio
|
||||||
if: false # Temporarily disabled - requires Copilot Studio setup
|
if: false # Temporarily disabled - requires Copilot Studio setup
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
@@ -441,8 +514,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
# Azure AI configuration
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
|
# Azure OpenAI configuration
|
||||||
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -459,8 +537,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
|
echo "AZURE_AI_MODEL_DEPLOYMENT_NAME=$AZURE_AI_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
||||||
|
echo "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=$AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=$AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME" >> .env
|
||||||
|
|
||||||
- name: Run sample validation
|
- name: Run sample validation
|
||||||
run: |
|
run: |
|
||||||
@@ -475,12 +556,16 @@ jobs:
|
|||||||
|
|
||||||
validate-04-hosting:
|
validate-04-hosting:
|
||||||
name: Validate 04-hosting
|
name: Validate 04-hosting
|
||||||
if: false # Temporarily disabled because of sample complexity
|
if: false # Temporarily disabled because of sample complexity
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
# Azure AI configuration
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
|
# Azure OpenAI configuration
|
||||||
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# A2A configuration
|
# A2A configuration
|
||||||
A2A_AGENT_HOST: http://localhost:5001/
|
A2A_AGENT_HOST: http://localhost:5001/
|
||||||
defaults:
|
defaults:
|
||||||
@@ -510,22 +595,23 @@ jobs:
|
|||||||
|
|
||||||
validate-05-end-to-end:
|
validate-05-end-to-end:
|
||||||
name: Validate 05-end-to-end
|
name: Validate 05-end-to-end
|
||||||
if: false # Temporarily disabled because of sample complexity
|
if: false # Temporarily disabled because of sample complexity
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
# Azure AI configuration
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# Azure OpenAI configuration
|
# Azure OpenAI configuration
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# Azure AI Search (for evaluation samples)
|
# Azure AI Search (for evaluation samples)
|
||||||
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
|
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
|
||||||
AZURE_SEARCH_API_KEY: ${{ secrets.AZURE_SEARCH_API_KEY }}
|
AZURE_SEARCH_API_KEY: ${{ secrets.AZURE_SEARCH_API_KEY }}
|
||||||
AZURE_SEARCH_INDEX_NAME: ${{ secrets.AZURE_SEARCH_INDEX_NAME }}
|
AZURE_SEARCH_INDEX_NAME: ${{ secrets.AZURE_SEARCH_INDEX_NAME }}
|
||||||
# Evaluation sample
|
# Evaluation sample
|
||||||
FOUNDRY_MODEL_WORKFLOW: ${{ vars.FOUNDRY_MODEL_WORKFLOW || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_MODEL_DEPLOYMENT_NAME_WORKFLOW: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
FOUNDRY_MODEL_EVAL: ${{ vars.FOUNDRY_MODEL_EVAL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -556,16 +642,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
# Azure AI configuration
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# Azure OpenAI configuration
|
# Azure OpenAI configuration
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
# OpenAI configuration
|
# OpenAI configuration
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -582,10 +668,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
|
echo "AZURE_AI_MODEL_DEPLOYMENT_NAME=$AZURE_AI_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
||||||
echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
|
echo "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=$AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" >> .env
|
||||||
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
||||||
echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
||||||
echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
|
echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
|
||||||
@@ -606,16 +692,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
|
# Azure AI configuration
|
||||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||||
|
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# Azure OpenAI configuration
|
# Azure OpenAI configuration
|
||||||
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
|
||||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||||
|
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
|
||||||
# OpenAI configuration
|
# OpenAI configuration
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
|
|
||||||
# Copilot Studio
|
# Copilot Studio
|
||||||
COPILOTSTUDIOAGENT__ENVIRONMENTID: ${{ secrets.COPILOTSTUDIOAGENT__ENVIRONMENTID }}
|
COPILOTSTUDIOAGENT__ENVIRONMENTID: ${{ secrets.COPILOTSTUDIOAGENT__ENVIRONMENTID }}
|
||||||
COPILOTSTUDIOAGENT__SCHEMANAME: ${{ secrets.COPILOTSTUDIOAGENT__SCHEMANAME }}
|
COPILOTSTUDIOAGENT__SCHEMANAME: ${{ secrets.COPILOTSTUDIOAGENT__SCHEMANAME }}
|
||||||
@@ -637,10 +724,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Create .env for samples
|
- name: Create .env for samples
|
||||||
run: |
|
run: |
|
||||||
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
|
echo "AZURE_AI_PROJECT_ENDPOINT=$AZURE_AI_PROJECT_ENDPOINT" >> .env
|
||||||
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
|
echo "AZURE_AI_MODEL_DEPLOYMENT_NAME=$AZURE_AI_MODEL_DEPLOYMENT_NAME" >> .env
|
||||||
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" >> .env
|
||||||
echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AZURE_OPENAI_DEPLOYMENT_NAME" >> .env
|
echo "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=$AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" >> .env
|
||||||
|
echo "AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=$AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME" >> .env
|
||||||
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
|
||||||
echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
echo "OPENAI_CHAT_MODEL_ID=$OPENAI_CHAT_MODEL_ID" >> .env
|
||||||
echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
|
echo "OPENAI_RESPONSES_MODEL_ID=$OPENAI_RESPONSES_MODEL_ID" >> .env
|
||||||
@@ -668,12 +756,14 @@ jobs:
|
|||||||
- validate-01-get-started
|
- validate-01-get-started
|
||||||
- validate-02-agents
|
- validate-02-agents
|
||||||
- validate-02-agents-openai
|
- validate-02-agents-openai
|
||||||
- validate-02-agents-azure
|
- validate-02-agents-azure-openai
|
||||||
|
- validate-02-agents-azure-ai
|
||||||
|
- validate-02-agents-azure-ai-agent
|
||||||
- validate-02-agents-anthropic
|
- validate-02-agents-anthropic
|
||||||
- validate-02-agents-github-copilot
|
- validate-02-agents-github-copilot
|
||||||
- validate-02-agents-amazon
|
- validate-02-agents-amazon
|
||||||
- validate-02-agents-ollama
|
- validate-02-agents-ollama
|
||||||
- validate-02-agents-foundry
|
- validate-02-agents-foundry-local
|
||||||
- validate-02-agents-copilotstudio
|
- validate-02-agents-copilotstudio
|
||||||
- validate-02-agents-custom
|
- validate-02-agents-custom
|
||||||
- validate-03-workflows
|
- validate-03-workflows
|
||||||
|
|||||||
+8
-47
@@ -74,37 +74,6 @@ Contributions must maintain API signature and behavioral compatibility. Contribu
|
|||||||
that include breaking changes will be rejected. Please file an issue to discuss
|
that include breaking changes will be rejected. Please file an issue to discuss
|
||||||
your idea or change if you believe that a breaking change is warranted.
|
your idea or change if you believe that a breaking change is warranted.
|
||||||
|
|
||||||
#### Automated API Compatibility Validation
|
|
||||||
|
|
||||||
The .NET projects use [Package Validation](https://learn.microsoft.com/dotnet/fundamentals/package-validation/overview)
|
|
||||||
to automatically detect API breaking changes. This validation runs during `dotnet build`
|
|
||||||
(Release configuration) and `dotnet pack`, comparing the current API surface against the
|
|
||||||
latest published NuGet baseline version.
|
|
||||||
|
|
||||||
**What gets validated:** By default, packable RC packages (`IsReleaseCandidate=true`) and
|
|
||||||
GA packages (`IsGenerallyAvailable=true`) that have a published NuGet baseline and do not
|
|
||||||
override validation settings are automatically validated. The shared baseline version and
|
|
||||||
default validation settings are defined in `dotnet/nuget/nuget-package.props`, but
|
|
||||||
individual projects may opt out (for example by setting `EnablePackageValidation=false`).
|
|
||||||
|
|
||||||
**If the build fails with CP errors (e.g., CP0001, CP0002):**
|
|
||||||
|
|
||||||
1. **Unintentional breaking change** — Refactor your code to maintain backward compatibility.
|
|
||||||
2. **Intentional breaking change** (approved by maintainers) — Generate a suppression file:
|
|
||||||
```bash
|
|
||||||
dotnet build <project>.csproj -c Release /p:ApiCompatGenerateSuppressionFile=true
|
|
||||||
```
|
|
||||||
This creates or updates a `CompatibilitySuppressions.xml` in the project directory.
|
|
||||||
Include this file in your PR with justification for the breaking change.
|
|
||||||
|
|
||||||
**After each release:**
|
|
||||||
|
|
||||||
1. Delete all `CompatibilitySuppressions.xml` files from validated projects.
|
|
||||||
2. Update `PackageValidationBaselineVersion` in `dotnet/nuget/nuget-package.props` to the
|
|
||||||
newly published version.
|
|
||||||
|
|
||||||
For more details, see the [Package Validation diagnostic IDs](https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids).
|
|
||||||
|
|
||||||
### Suggested Workflow
|
### Suggested Workflow
|
||||||
|
|
||||||
We use and recommend the following workflow:
|
We use and recommend the following workflow:
|
||||||
@@ -123,30 +92,22 @@ We use and recommend the following workflow:
|
|||||||
"issue-123" or "githubhandle-issue".
|
"issue-123" or "githubhandle-issue".
|
||||||
4. Make and commit your changes to your branch.
|
4. Make and commit your changes to your branch.
|
||||||
5. Add new tests corresponding to your change, if applicable.
|
5. Add new tests corresponding to your change, if applicable.
|
||||||
6. Run the relevant scripts in [the section below](#development-setup) to ensure that your build is clean and all tests are passing.
|
6. Run the relevant scripts in [the section below](#development-scripts) to ensure that your build is clean and all tests are passing.
|
||||||
7. Create a PR against the repository's **main** branch.
|
7. Create a PR against the repository's **main** branch.
|
||||||
- State in the description what issue or improvement your change is addressing.
|
- State in the description what issue or improvement your change is addressing.
|
||||||
- Verify that all the Continuous Integration checks are passing.
|
- Verify that all the Continuous Integration checks are passing.
|
||||||
8. Wait for feedback or approval of your changes from the code maintainers.
|
8. Wait for feedback or approval of your changes from the code maintainers.
|
||||||
9. When area owners have signed off, and all checks are green, your PR will be merged.
|
9. When area owners have signed off, and all checks are green, your PR will be merged.
|
||||||
|
|
||||||
### Development Setup
|
### Development scripts
|
||||||
|
|
||||||
Each language has its own dev setup guide, coding standards, and build scripts:
|
The scripts below are used to build, test, and lint within the project.
|
||||||
|
|
||||||
- **Python**: [Dev Setup](./python/DEV_SETUP.md) · [Coding Standard](./python/CODING_STANDARD.md) · [README](./python/README.md)
|
- Python: see [python/DEV_SETUP.md](./python/DEV_SETUP.md).
|
||||||
- From the `./python` directory:
|
- .NET:
|
||||||
- Build: `uv run poe build`
|
- Build: `dotnet build`
|
||||||
- Unit tests: `uv run poe test -A -m "not integration"`
|
- Test: `dotnet test`
|
||||||
- Integration tests: `uv run poe test -A -m integration` (requires API keys/endpoints)
|
- Linting (auto-fix): `dotnet format`
|
||||||
- Format + lint: `uv run poe syntax`
|
|
||||||
- All checks: `uv run poe check`
|
|
||||||
- **.NET**: [README](./dotnet/README.md) · [Agent Instructions](./dotnet/AGENTS.md)
|
|
||||||
- From the `./dotnet` directory:
|
|
||||||
- Build: `dotnet build`
|
|
||||||
- Unit tests: `dotnet test --filter-query "/*UnitTests*/*/*/*"`
|
|
||||||
- Integration tests: `dotnet test --filter-query "/*IntegrationTests*/*/*/*"` (requires API keys/endpoints)
|
|
||||||
- Linting (auto-fix): `dotnet format`
|
|
||||||
|
|
||||||
### PR - CI Process
|
### PR - CI Process
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Welcome to Microsoft Agent Framework!
|
# Welcome to Microsoft Agent Framework!
|
||||||
|
|
||||||
[](https://discord.gg/b5zjErwbQM)
|
[](https://discord.gg/b5zjErwbQM)
|
||||||
[](https://learn.microsoft.com/en-us/agent-framework/)
|
[](https://learn.microsoft.com/en-us/agent-framework/)
|
||||||
[](https://pypi.org/project/agent-framework/)
|
[](https://pypi.org/project/agent-framework/)
|
||||||
[](https://www.nuget.org/profiles/MicrosoftAgentFramework/)
|
[](https://www.nuget.org/profiles/MicrosoftAgentFramework/)
|
||||||
@@ -137,21 +137,24 @@ var agent = new OpenAIClient("<apikey>")
|
|||||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a simple Agent, using Microsoft Foundry with token-based auth, that writes a haiku about the Microsoft Agent Framework
|
Create a simple Agent, using Azure OpenAI Responses with token based auth, that writes a haiku about the Microsoft Agent Framework
|
||||||
|
|
||||||
```c#
|
```c#
|
||||||
// dotnet add package Microsoft.Agents.AI.AzureAI --prerelease
|
// dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
|
||||||
// dotnet add package Azure.Identity
|
// dotnet add package Azure.Identity
|
||||||
// Use `az login` to authenticate with Azure CLI
|
// Use `az login` to authenticate with Azure CLI
|
||||||
using Azure.AI.Projects;
|
using System.ClientModel.Primitives;
|
||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
|
using OpenAI;
|
||||||
|
using OpenAI.Responses;
|
||||||
|
|
||||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
// Replace <resource> and gpt-4o-mini with your Azure OpenAI resource name and deployment name.
|
||||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
var agent = new OpenAIClient(
|
||||||
|
new BearerTokenPolicy(new AzureCliCredential(), "https://ai.azure.com/.default"),
|
||||||
var agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
new OpenAIClientOptions() { Endpoint = new Uri("https://<resource>.openai.azure.com/openai/v1") })
|
||||||
.AsAIAgent(model: deploymentName, name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
.GetResponsesClient("gpt-4o-mini")
|
||||||
|
.AsAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||||
|
|
||||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||||
```
|
```
|
||||||
@@ -160,43 +163,15 @@ Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Fram
|
|||||||
|
|
||||||
### Python
|
### Python
|
||||||
|
|
||||||
- [Getting Started](./python/samples/01-get-started): progressive tutorial from hello-world to hosting
|
- [Getting Started with Agents](./python/samples/01-get-started): progressive tutorial from hello-world to hosting
|
||||||
- [Agent Concepts](./python/samples/02-agents): deep-dive samples by topic (tools, middleware, providers, etc.)
|
- [Agent Concepts](./python/samples/02-agents): deep-dive samples by topic (tools, middleware, providers, etc.)
|
||||||
- [Workflows](./python/samples/03-workflows): workflow creation and integration with agents
|
- [Getting Started with Workflows](./python/samples/03-workflows): workflow creation and integration with agents
|
||||||
- [Hosting](./python/samples/04-hosting): A2A, Azure Functions, Durable Task hosting
|
|
||||||
- [End-to-End](./python/samples/05-end-to-end): full applications, evaluation, and demos
|
|
||||||
|
|
||||||
### .NET
|
### .NET
|
||||||
|
|
||||||
- [Getting Started](./dotnet/samples/01-get-started): progressive tutorial from hello agent to hosting
|
- [Getting Started with Agents](./dotnet/samples/02-agents/Agents): basic agent creation and tool usage
|
||||||
- [Agent Concepts](./dotnet/samples/02-agents/Agents): basic agent creation and tool usage
|
- [Agent Provider Samples](./dotnet/samples/02-agents/AgentProviders): samples showing different agent providers
|
||||||
- [Agent Providers](./dotnet/samples/02-agents/AgentProviders): samples showing different agent providers
|
- [Workflow Samples](./dotnet/samples/03-workflows): advanced multi-agent patterns and workflow orchestration
|
||||||
- [Workflows](./dotnet/samples/03-workflows): advanced multi-agent patterns and workflow orchestration
|
|
||||||
- [Hosting](./dotnet/samples/04-hosting): A2A, Durable Agents, Durable Workflows
|
|
||||||
- [End-to-End](./dotnet/samples/05-end-to-end): full applications and demos
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
| Problem | Cause | Fix |
|
|
||||||
|---------|-------|-----|
|
|
||||||
| Authentication errors when using Azure credentials | Not signed in to Azure CLI | Run `az login` before starting your app |
|
|
||||||
| API key errors | Wrong or missing API key | Verify the key and ensure it's for the correct resource/provider |
|
|
||||||
|
|
||||||
> **Tip:** `DefaultAzureCredential` is convenient for development but in production, consider using a specific credential (e.g., `ManagedIdentityCredential`) to avoid latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
The samples typically read configuration from environment variables. Common required variables:
|
|
||||||
|
|
||||||
| Variable | Used by | Purpose |
|
|
||||||
|----------|---------|---------|
|
|
||||||
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI samples | Your Azure OpenAI resource URL |
|
|
||||||
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Azure OpenAI samples | Model deployment name (e.g. `gpt-4o-mini`) |
|
|
||||||
| `AZURE_AI_PROJECT_ENDPOINT` | Microsoft Foundry samples | Your Microsoft Foundry project endpoint |
|
|
||||||
| `AZURE_AI_MODEL_DEPLOYMENT_NAME` | Microsoft Foundry samples | Model deployment name |
|
|
||||||
| `OPENAI_API_KEY` | OpenAI (non-Azure) samples | Your OpenAI platform API key |
|
|
||||||
|
|
||||||
## Contributor Resources
|
## Contributor Resources
|
||||||
|
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
---
|
|
||||||
status: accepted
|
|
||||||
contact: rogerbarreto
|
|
||||||
date: 2026-03-06
|
|
||||||
deciders: rogerbarreto, alliscode
|
|
||||||
consulted: ""
|
|
||||||
informed: ""
|
|
||||||
---
|
|
||||||
|
|
||||||
# Foundry agent surface stays centered on `ChatClientAgent`
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
The Microsoft Foundry integration exposes two distinct usage patterns:
|
|
||||||
|
|
||||||
1. Direct Responses usage, where callers provide model, instructions, and tools at runtime.
|
|
||||||
2. Server-side versioned agents, where callers create and manage `AgentVersion` resources through `AIProjectClient.Agents`.
|
|
||||||
|
|
||||||
We briefly explored adding public wrapper types such as `FoundryAgent`, `FoundryVersionedAgent`, and `FoundryResponsesChatClient` to make those paths feel more specialized. That direction created extra public types, duplicated existing `ChatClientAgent` behavior, and pushed samples toward compatibility helpers instead of the native Azure SDK flow.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
Keep the public surface centered on `ChatClientAgent`.
|
|
||||||
|
|
||||||
- Direct Responses scenarios use `AIProjectClient.AsAIAgent(...)`.
|
|
||||||
- Server-side versioned scenarios use native `AIProjectClient.Agents` APIs to create or retrieve agent resources, then wrap `AgentRecord` or `AgentVersion` with `AIProjectClient.AsAIAgent(...)`.
|
|
||||||
- Compatibility helpers such as `AIProjectClient.CreateAIAgentAsync(...)` and `AIProjectClient.GetAIAgentAsync(...)` remain only as obsolete migration shims.
|
|
||||||
- Public wrapper types `FoundryAgent`, `FoundryVersionedAgent`, `FoundryResponsesChatClient`, and `FoundryResponsesChatClientAgent` are not part of the chosen direction.
|
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
- `ChatClientAgent` is already the framework abstraction used everywhere else.
|
|
||||||
- `AIProjectClient` is the native Azure SDK entry point for versioned agent lifecycle operations.
|
|
||||||
- A single agent abstraction avoids parallel type hierarchies for the same backend.
|
|
||||||
- Samples become clearer when they show either:
|
|
||||||
- direct Responses construction via `AIProjectClient.AsAIAgent(...)`, or
|
|
||||||
- native Foundry resource management via `AIProjectClient.Agents`.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
### Direct Responses path
|
|
||||||
|
|
||||||
Use the convenience overloads on `AIProjectClient`:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), credential);
|
|
||||||
|
|
||||||
ChatClientAgent agent = aiProjectClient.AsAIAgent(
|
|
||||||
model: deploymentName,
|
|
||||||
instructions: "You are good at telling jokes.",
|
|
||||||
name: "JokerAgent");
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use composed `ChatClientAgent`
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
ProjectResponsesClient projectResponsesClient = new(new Uri(endpoint), new DefaultAzureCredential(), new AgentReference($"model:{deploymentName}"));
|
|
||||||
|
|
||||||
ChatClientAgent agent = new(
|
|
||||||
chatClient: projectResponsesClient.AsIChatClient(),
|
|
||||||
instructions: "You are good at telling jokes.",
|
|
||||||
name: "JokerAgent");
|
|
||||||
```
|
|
||||||
|
|
||||||
This path is code-first and does not create a persistent server-side agent.
|
|
||||||
|
|
||||||
### Versioned agent path
|
|
||||||
|
|
||||||
Use the convenience overloads on `AIProjectClient`:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), credential);
|
|
||||||
|
|
||||||
AgentVersion version = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
|
||||||
"JokerAgent",
|
|
||||||
new AgentVersionCreationOptions(
|
|
||||||
new PromptAgentDefinition(deploymentName)
|
|
||||||
{
|
|
||||||
Instructions = "You are good at telling jokes."
|
|
||||||
}));
|
|
||||||
|
|
||||||
ChatClientAgent agent = aiProjectClient.AsAIAgent(version);
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use composed `ChatClientAgent`
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), credential);
|
|
||||||
|
|
||||||
AgentVersion version = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
|
||||||
"JokerAgent",
|
|
||||||
new AgentVersionCreationOptions(
|
|
||||||
new PromptAgentDefinition(deploymentName)
|
|
||||||
{
|
|
||||||
Instructions = "You are good at telling jokes."
|
|
||||||
}));
|
|
||||||
|
|
||||||
ProjectResponsesClient projectResponsesClient = aiProjectClient
|
|
||||||
.GetProjectOpenAIClient()
|
|
||||||
.GetProjectResponsesClientForAgent(new AgentReference(version.Name, version.Version));
|
|
||||||
|
|
||||||
ChatClientAgent agent = new(
|
|
||||||
chatClient: projectResponsesClient.AsIChatClient(),
|
|
||||||
name: "JokerAgent");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Samples
|
|
||||||
|
|
||||||
- `FoundryAgents/` samples show the direct Responses path with `AIProjectClient.AsAIAgent(...)`.
|
|
||||||
- `FoundryVersionedAgents/` samples should show native `AIProjectClient.Agents` create/get/delete flows plus `AsAIAgent(...)`.
|
|
||||||
|
|
||||||
### Compatibility APIs
|
|
||||||
|
|
||||||
Obsolete helper extensions remain only to ease migration of existing code. New samples and new guidance should not be written against them.
|
|
||||||
|
|
||||||
## Rejected direction
|
|
||||||
|
|
||||||
Do not introduce or preserve separate public wrapper types whose main purpose is to forward to `ChatClientAgent` while carrying Foundry-specific naming.
|
|
||||||
|
|
||||||
That approach:
|
|
||||||
|
|
||||||
- duplicates lifecycle concepts already present on `AIProjectClient`,
|
|
||||||
- fragments the public API,
|
|
||||||
- complicates samples and docs,
|
|
||||||
- and makes migration harder by encouraging wrapper-specific affordances.
|
|
||||||
+1
-1
@@ -462,7 +462,7 @@ class FoundryEvals:
|
|||||||
### Azure AI: FoundryEvals Constants
|
### Azure AI: FoundryEvals Constants
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from agent_framework.foundry import FoundryEvals
|
from agent_framework_azure_ai import FoundryEvals
|
||||||
|
|
||||||
evaluators = [FoundryEvals.RELEVANCE, FoundryEvals.TOOL_CALL_ACCURACY]
|
evaluators = [FoundryEvals.RELEVANCE, FoundryEvals.TOOL_CALL_ACCURACY]
|
||||||
```
|
```
|
||||||
@@ -1,960 +0,0 @@
|
|||||||
status: proposed
|
|
||||||
date: 2026-03-23
|
|
||||||
contact: sergeymenshykh
|
|
||||||
deciders: rbarreto, westey-m, eavanvalkenburg
|
|
||||||
---
|
|
||||||
|
|
||||||
# Agent Skills: Multi-Source Architecture
|
|
||||||
|
|
||||||
## Context and Problem Statement
|
|
||||||
|
|
||||||
The Agent Framework needs a skills system that lets agents discover and use domain-specific knowledge, reference documents, and executable scripts. Skills can originate from different sources — filesystem directories (SKILL.md files), inline C# code, or reusable class libraries — and the framework must support all three uniformly while allowing extensibility, composition, and filtering.
|
|
||||||
|
|
||||||
## Decision Drivers
|
|
||||||
|
|
||||||
- Skills must be definable from multiple sources: filesystem, inline code, reusable classes, etc
|
|
||||||
- Common abstractions are needed so the provider and builder work uniformly regardless of skill origin
|
|
||||||
- File-based scripts must support user-defined executors, enabling custom runtimes and languages; code/class-based scripts execute in-process as C# delegates
|
|
||||||
- Skills must be filterable so consumers can include or exclude specific skills based on defined criteria
|
|
||||||
- Multiple skill sources must be composable into a single provider
|
|
||||||
- It must be possible to add custom skill sources (e.g., databases, REST APIs, package registries) by implementing a common abstraction
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Model-Facing Tools
|
|
||||||
|
|
||||||
Skills are presented to the model as up to three tools that progressively disclose skill content. The system prompt lists available skill names and descriptions; the model then calls these tools on demand:
|
|
||||||
|
|
||||||
- **`load_skill(skillName)`** — returns the full skill body (instructions, listed resources, listed scripts)
|
|
||||||
- **`read_skill_resource(skillName, resourceName)`** — reads a supplementary resource (file-based or code-defined) associated with a skill
|
|
||||||
- **`run_skill_script(skillName, scriptName, arguments?)`** — executes a script associated with a skill; only registered when at least one skill contains scripts
|
|
||||||
|
|
||||||
Each tool delegates to the corresponding method on the resolved `AgentSkill` — calling `Resource.ReadAsync()` or `Script.RunAsync()` respectively.
|
|
||||||
|
|
||||||
If skills have no scripts defined, the `run_skill_script` tool is **not advertised** to the model and instructions related to script execution are **not included** in the default skills instructions.
|
|
||||||
|
|
||||||
### Abstract Base Types
|
|
||||||
|
|
||||||
The architecture defines four abstract base types that all skill variants implement:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public abstract class AgentSkill
|
|
||||||
{
|
|
||||||
public abstract AgentSkillFrontmatter Frontmatter { get; }
|
|
||||||
public abstract string Content { get; }
|
|
||||||
public abstract IReadOnlyList<AgentSkillResource>? Resources { get; }
|
|
||||||
public abstract IReadOnlyList<AgentSkillScript>? Scripts { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class AgentSkillResource
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string? Description { get; }
|
|
||||||
public abstract Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class AgentSkillScript
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string? Description { get; }
|
|
||||||
public abstract Task<object?> RunAsync(AgentSkill skill, AIFunctionArguments arguments, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class AgentSkillsSource
|
|
||||||
{
|
|
||||||
public abstract Task<IList<AgentSkill>> GetSkillsAsync(CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Skill metadata is captured via `AgentSkillFrontmatter`:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentSkillFrontmatter
|
|
||||||
{
|
|
||||||
public AgentSkillFrontmatter(string name, string description) { ... }
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
public string Description { get; }
|
|
||||||
public string? License { get; set; }
|
|
||||||
public string? Compatibility { get; set; }
|
|
||||||
public string? AllowedTools { get; set; }
|
|
||||||
public AdditionalPropertiesDictionary? Metadata { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The type hierarchy at a glance:
|
|
||||||
|
|
||||||
```
|
|
||||||
AgentSkill (abstract) AgentSkillsSource (abstract)
|
|
||||||
├── AgentFileSkill ├── AgentFileSkillsSource (public)
|
|
||||||
└── [Programmatic] ├── AgentInMemorySkillsSource (public)
|
|
||||||
├── AgentInlineSkill ├── AggregatingAgentSkillsSource (public)
|
|
||||||
└── AgentClassSkill (abstract) └── DelegatingAgentSkillsSource (abstract, public)
|
|
||||||
├── FilteringAgentSkillsSource (public)
|
|
||||||
AgentSkillResource (abstract) ├── CachingAgentSkillsSource (public)
|
|
||||||
├── AgentFileSkillResource └── DeduplicatingAgentSkillsSource (public)
|
|
||||||
└── AgentInlineSkillResource
|
|
||||||
AgentSkillScript (abstract)
|
|
||||||
├── AgentFileSkillScript
|
|
||||||
└── AgentInlineSkillScript
|
|
||||||
```
|
|
||||||
|
|
||||||
There are two top-level categories of skills:
|
|
||||||
|
|
||||||
1. **File-Based Skills** — discovered from `SKILL.md` files on the filesystem. Resources and scripts are files in subdirectories.
|
|
||||||
2. **Programmatic Skills** — defined in C# code. These are further divided into:
|
|
||||||
- **Inline Skills** — built at runtime via the `AgentInlineSkill` class and its fluent API. Ideal for quick, agent-specific skill definitions.
|
|
||||||
- **Class-Based Skills** — defined as reusable C# classes that subclass `AgentClassSkill`. Ideal for packaging skills as shared libraries or NuGet packages.
|
|
||||||
|
|
||||||
Both programmatic skill types use `AgentInlineSkillResource` and `AgentInlineSkillScript` for their resources and scripts. They are typically served by `AgentInMemorySkillsSource`, which accepts any `AgentSkill` and is not limited to programmatic skills.
|
|
||||||
|
|
||||||
### File-Based Skills
|
|
||||||
|
|
||||||
File-based skills are authored as `SKILL.md` files on disk. Resources and scripts are discovered from corresponding subfolders within the skill directory.
|
|
||||||
|
|
||||||
**`AgentFileSkill`** — A filesystem-based skill discovered from a directory containing a `SKILL.md` file. Parsed from YAML frontmatter; content is the raw markdown body. Resources and scripts are discovered from files in corresponding subfolders:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentFileSkill : AgentSkill
|
|
||||||
{
|
|
||||||
internal AgentFileSkill(
|
|
||||||
AgentSkillFrontmatter frontmatter, string content, string path,
|
|
||||||
IReadOnlyList<AgentSkillResource>? resources = null,
|
|
||||||
IReadOnlyList<AgentSkillScript>? scripts = null) { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`AgentFileSkillResource`** — A file-based skill resource. Reads content from a file on disk relative to the skill directory:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
internal sealed class AgentFileSkillResource : AgentSkillResource
|
|
||||||
{
|
|
||||||
public AgentFileSkillResource(string name, string fullPath) { ... }
|
|
||||||
|
|
||||||
public string FullPath { get; }
|
|
||||||
|
|
||||||
public override Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return File.ReadAllTextAsync(FullPath, Encoding.UTF8, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`AgentFileSkillScript`** — A file-based skill script that represents a script file on disk. Delegates execution to an external `AgentFileSkillScriptRunner` callback (e.g., runs Python/shell via `Process.Start`). Throws `NotSupportedException` if no executor is configured:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public delegate Task<object?> AgentFileSkillScriptRunner(
|
|
||||||
AgentFileSkill skill, AgentFileSkillScript script,
|
|
||||||
AIFunctionArguments arguments, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
public sealed class AgentFileSkillScript : AgentSkillScript
|
|
||||||
{
|
|
||||||
private readonly AgentFileSkillScriptRunner _executor;
|
|
||||||
|
|
||||||
internal AgentFileSkillScript(string name, string fullPath, AgentFileSkillScriptRunner executor)
|
|
||||||
: base(name) { ... }
|
|
||||||
|
|
||||||
public override async Task<object?> RunAsync(AgentSkill skill, AIFunctionArguments arguments, ...)
|
|
||||||
{
|
|
||||||
|
|
||||||
return await _executor(fileSkill, this, arguments, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The executor can be provided at the **provider level** via `AgentSkillsProviderBuilder.UseFileScriptRunner(executor)` and optionally overridden for a **particular file skill** or for a **set of skills** at the file skill source level, giving fine-grained control over how different scripts are executed.
|
|
||||||
|
|
||||||
**`AgentFileSkillsSource`** — A skill source that discovers skills from filesystem directories containing `SKILL.md` files. Recursively scans directories (max 2 levels), validates frontmatter, and enforces path traversal and symlink security checks:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed partial class AgentFileSkillsSource : AgentSkillsSource
|
|
||||||
{
|
|
||||||
public AgentFileSkillsSource(
|
|
||||||
IEnumerable<string> skillPaths,
|
|
||||||
AgentFileSkillScriptRunner scriptRunner,
|
|
||||||
AgentFileSkillsSourceOptions? options = null,
|
|
||||||
ILoggerFactory? loggerFactory = null) { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`AgentFileSkillsSourceOptions`** — Configuration options for `AgentFileSkillsSource`. Allows customizing the allowed file extensions for resources and scripts without adding constructor parameters:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentFileSkillsSourceOptions
|
|
||||||
{
|
|
||||||
public IEnumerable<string>? AllowedResourceExtensions { get; set; }
|
|
||||||
public IEnumerable<string>? AllowedScriptExtensions { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example** — A file-based skill on disk and how it is added to a source:
|
|
||||||
|
|
||||||
```
|
|
||||||
skills/
|
|
||||||
└── unit-converter/
|
|
||||||
├── SKILL.md # frontmatter + instructions
|
|
||||||
├── resources/
|
|
||||||
│ └── conversion-table.csv # discovered as a resource
|
|
||||||
└── scripts/
|
|
||||||
└── convert.py # discovered as a script
|
|
||||||
```
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var source = new AgentFileSkillsSource(skillPaths: ["./skills"], scriptRunner: SubprocessScriptRunner.RunAsync);
|
|
||||||
|
|
||||||
var provider = new AgentSkillsProvider(source);
|
|
||||||
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
AIContextProviders = [provider],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Programmatic Skills
|
|
||||||
|
|
||||||
Programmatic skills are defined in C# code rather than discovered from the filesystem. There are two kinds: **inline** and **class-based**. Both use `AgentInlineSkillResource` and `AgentInlineSkillScript` for resources and scripts, and are held by a single `AgentInMemorySkillsSource`.
|
|
||||||
|
|
||||||
**`AgentInMemorySkillsSource`** — A general-purpose skill source that holds any `AgentSkill` instances in memory. Although commonly used for programmatic skills (`AgentInlineSkill` and `AgentClassSkill`), it accepts any `AgentSkill` subclass and is not restricted to code-defined skills:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentInMemorySkillsSource : AgentSkillsSource
|
|
||||||
{
|
|
||||||
public AgentInMemorySkillsSource(
|
|
||||||
IEnumerable<AgentSkill> skills,
|
|
||||||
ILoggerFactory? loggerFactory = null) { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Inline Skills
|
|
||||||
|
|
||||||
Inline skills are built at runtime via the `AgentInlineSkill` class and its fluent API. They are ideal for quick, agent-specific skill definitions where a full class hierarchy would be overkill.
|
|
||||||
|
|
||||||
**`AgentInlineSkill`** — A skill defined entirely in code. Resources can be static values or functions; scripts are always functions. Constructed with name, description, and instructions, then extended with resources and scripts:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentInlineSkill : AgentSkill
|
|
||||||
{
|
|
||||||
public AgentInlineSkill(string name, string description, string instructions, string? license = null, string? compatibility = null, ...) { ... }
|
|
||||||
public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions) { ... }
|
|
||||||
|
|
||||||
public AgentInlineSkill AddResource(object value, string name, string? description = null);
|
|
||||||
public AgentInlineSkill AddResource(Delegate handler, string name, string? description = null);
|
|
||||||
public AgentInlineSkill AddScript(Delegate handler, string name, string? description = null);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`AgentInlineSkillResource`** — A skill resource that wraps a static value:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentInlineSkillResource : AgentSkillResource
|
|
||||||
{
|
|
||||||
public AgentInlineSkillResource(object value, string name, string? description = null)
|
|
||||||
: base(name, description)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return Task.FromResult<object?>(_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`AgentInlineSkillResource`** — A skill resource backed by a delegate. The delegate is invoked via an `AIFunction` each time `ReadAsync` is called, producing a dynamic (computed) value:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentInlineSkillResource : AgentSkillResource
|
|
||||||
{
|
|
||||||
public AgentInlineSkillResource(Delegate handler, string name, string? description = null)
|
|
||||||
: base(name, description)
|
|
||||||
{
|
|
||||||
_function = AIFunctionFactory.Create(handler, name: name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await _function.InvokeAsync(new AIFunctionArguments() { Services = serviceProvider }, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`AgentInlineSkillScript`** — A skill script backed by a delegate via an `AIFunction`:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class AgentInlineSkillScript : AgentSkillScript
|
|
||||||
{
|
|
||||||
private readonly AIFunction _function;
|
|
||||||
|
|
||||||
public AgentInlineSkillScript(Delegate handler, string name, string? description = null)
|
|
||||||
: base(name, description)
|
|
||||||
{
|
|
||||||
_function = AIFunctionFactory.Create(handler, name: name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonElement? ParametersSchema => _function.JsonSchema;
|
|
||||||
|
|
||||||
public override async Task<object?> RunAsync(AgentSkill skill, AIFunctionArguments arguments, ...)
|
|
||||||
{
|
|
||||||
return await _function.InvokeAsync(arguments, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example** — Creating an inline skill with a resource and script, then adding it to a source:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var skill = new AgentInlineSkill(
|
|
||||||
name: "unit-converter",
|
|
||||||
description: "Converts between measurement units.",
|
|
||||||
instructions: """
|
|
||||||
Use this skill to convert values between metric and imperial units.
|
|
||||||
Refer to the conversion-table resource for supported unit pairs.
|
|
||||||
Run the convert script to perform conversions.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
.AddResource("kg=2.205lb, m=3.281ft, L=0.264gal", "conversion-table", "Supported unit pairs")
|
|
||||||
.AddScript(Convert, "convert", "Converts a value between units");
|
|
||||||
|
|
||||||
var source = new AgentInMemorySkillsSource([skill]);
|
|
||||||
|
|
||||||
var provider = new AgentSkillsProvider(source);
|
|
||||||
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
AIContextProviders = [provider],
|
|
||||||
});
|
|
||||||
|
|
||||||
static string Convert(double value, double factor)
|
|
||||||
=> JsonSerializer.Serialize(new { result = Math.Round(value * factor, 4) });
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Class-Based Skills
|
|
||||||
|
|
||||||
Class-based skills are designed for packaging skills as reusable libraries. Users subclass `AgentClassSkill` and override properties. Unlike inline skills, class-based skills are self-contained, can live in shared libraries or NuGet packages, and are well-suited for dependency injection.
|
|
||||||
|
|
||||||
**`AgentClassSkill`** — An abstract base class for defining skills as reusable C# classes that bundle all skill components (frontmatter, instructions, resources, scripts) together. Designed for packaging skills as distributable libraries:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public abstract class AgentClassSkill : AgentSkill
|
|
||||||
{
|
|
||||||
public abstract string Instructions { get; }
|
|
||||||
|
|
||||||
// Content is auto-synthesized from Frontmatter + Instructions + Resources + Scripts
|
|
||||||
public override string Content =>
|
|
||||||
SkillContentBuilder.BuildContent(Frontmatter.Name, Frontmatter.Description,
|
|
||||||
SkillContentBuilder.BuildBody(Instructions, Resources, Scripts));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example** — Defining a class-based skill and adding it to a source:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class UnitConverterSkill : AgentClassSkill
|
|
||||||
{
|
|
||||||
public override AgentSkillFrontmatter Frontmatter { get; } =
|
|
||||||
new("unit-converter", "Converts between measurement units.");
|
|
||||||
|
|
||||||
public override string Instructions => """
|
|
||||||
Use this skill to convert values between metric and imperial units.
|
|
||||||
Refer to the conversion-table resource for supported unit pairs.
|
|
||||||
Run the convert script to perform conversions.
|
|
||||||
""";
|
|
||||||
|
|
||||||
public override IReadOnlyList<AgentSkillResource>? Resources { get; } =
|
|
||||||
[
|
|
||||||
new AgentInlineSkillResource("kg=2.205lb, m=3.281ft", "conversion-table"),
|
|
||||||
];
|
|
||||||
|
|
||||||
public override IReadOnlyList<AgentSkillScript>? Scripts { get; } =
|
|
||||||
[
|
|
||||||
new AgentInlineSkillScript(Convert, "convert"),
|
|
||||||
];
|
|
||||||
|
|
||||||
private static string Convert(double value, double factor)
|
|
||||||
=> JsonSerializer.Serialize(new { result = Math.Round(value * factor, 4) });
|
|
||||||
}
|
|
||||||
|
|
||||||
var source = new AgentInMemorySkillsSource([new UnitConverterSkill()]);
|
|
||||||
|
|
||||||
var provider = new AgentSkillsProvider(source);
|
|
||||||
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
AIContextProviders = [provider],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Filtering, Caching, and Deduplication
|
|
||||||
|
|
||||||
The following subsections present alternative approaches for handling filtering, caching, and deduplication of skills across multiple sources.
|
|
||||||
|
|
||||||
### Via Composition
|
|
||||||
|
|
||||||
In this approach, the `AgentSkillsProvider` accepts a **single** `AgentSkillsSource`. Multiple sources are composed externally via an aggregate source, and cross-cutting concerns like filtering, caching, and deduplication are implemented as **source decorators** — subclasses of `DelegatingAgentSkillsSource` that intercept `GetSkillsAsync()`.
|
|
||||||
|
|
||||||
**`FilteringAgentSkillsSource`** — A decorator that applies filter logic before returning results. The decorator pattern keeps filtering orthogonal to source implementations and allows composing multiple filters:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class FilteringAgentSkillsSource : DelegatingAgentSkillsSource
|
|
||||||
{
|
|
||||||
private readonly Func<AgentSkill, bool> _predicate;
|
|
||||||
|
|
||||||
public FilteringAgentSkillsSource(AgentSkillsSource innerSource, Func<AgentSkill, bool> predicate)
|
|
||||||
: base(innerSource)
|
|
||||||
{
|
|
||||||
_predicate = predicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IList<AgentSkill>> GetSkillsAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var skills = await this.InnerSource.GetSkillsAsync(cancellationToken);
|
|
||||||
return skills.Where(_predicate).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`CachingAgentSkillsSource`** — A decorator that caches skills after the first load, keeping the provider stateless and giving consumers control over caching granularity per source. For example, file-based skills (expensive to discover) can be cached while code-defined skills remain uncached:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class CachingAgentSkillsSource : DelegatingAgentSkillsSource
|
|
||||||
{
|
|
||||||
private IList<AgentSkill>? _cached;
|
|
||||||
|
|
||||||
public CachingAgentSkillsSource(AgentSkillsSource innerSource)
|
|
||||||
: base(innerSource)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IList<AgentSkill>> GetSkillsAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return _cached ??= await this.InnerSource.GetSkillsAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Deduplication** is similarly implemented as a decorator (`DeduplicatingAgentSkillsSource`) that deduplicates by name (case-insensitive, first-one-wins) and logs a warning for skipped duplicates.
|
|
||||||
|
|
||||||
**Example** — Combining file-based and code-defined sources with filtering and caching:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var fileSource = new CachingAgentSkillsSource(new AgentFileSkillsSource(["./skills"]));
|
|
||||||
var codeSource = new AgentInMemorySkillsSource([myCodeSkill]);
|
|
||||||
|
|
||||||
var compositeSource = new FilteringAgentSkillsSource(
|
|
||||||
new AggregatingAgentSkillsSource([fileSource, codeSource]),
|
|
||||||
filter: s => s.Frontmatter.Name != "internal");
|
|
||||||
|
|
||||||
var provider = new AgentSkillsProvider(compositeSource);
|
|
||||||
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
AIContextProviders = [provider],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
- Clean single-responsibility: the provider serves skills, sources provide them.
|
|
||||||
- Caching, filtering, and deduplication are composable as source decorators — each concern is a separate, testable wrapper.
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
- DI is less flexible: multiple `AgentSkillsSource` implementations registered in the container cannot be auto-injected into the provider. The consumer must manually compose them via an aggregate source.
|
|
||||||
- Increased public API surface: requires additional public classes (aggregate source, caching decorators, filtering decorators) that consumers need to learn and use.
|
|
||||||
|
|
||||||
### Via AgentSkillsProvider
|
|
||||||
|
|
||||||
In this approach, the `AgentSkillsProvider` accepts **`IEnumerable<AgentSkillsSource>`** and handles aggregation, filtering, caching, and deduplication internally.
|
|
||||||
|
|
||||||
The provider aggregates skills from all registered sources, deduplicates by name (case-insensitive, first-one-wins), caches the result after the first load, and optionally applies filtering via a predicate on `AgentSkillsProviderOptions`. Duplicate skill names are logged as warnings.
|
|
||||||
|
|
||||||
**Example** — Registering multiple sources directly with the provider:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Conceptual example — in practice, use AgentSkillsProviderBuilder
|
|
||||||
var fileSource = new AgentFileSkillsSource(["./skills"]);
|
|
||||||
var codeSource = new AgentInMemorySkillsSource([myCodeSkill]);
|
|
||||||
|
|
||||||
var provider = new AgentSkillsProvider(
|
|
||||||
sources: [fileSource, codeSource],
|
|
||||||
options: new AgentSkillsProviderOptions
|
|
||||||
{
|
|
||||||
Filter = s => s.Frontmatter.Name != "internal",
|
|
||||||
});
|
|
||||||
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
AIContextProviders = [provider],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
- DI-friendly: register multiple `AgentSkillsSource` implementations in the container, and they are all auto-injected into `AgentSkillsProvider` via `IEnumerable<AgentSkillsSource>`.
|
|
||||||
- Smaller public API surface: no need for aggregate source, caching decorators, or filtering decorator classes — these concerns are handled internally by the provider.
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
- The provider takes on multiple responsibilities — aggregation, caching, deduplication, and filtering.
|
|
||||||
- Less granular caching control: caching is all-or-nothing across sources rather than per-source as with decorators.
|
|
||||||
- Less extensible: new behaviors (e.g., ordering, TTL expiration) require modifying the provider rather than adding a decorator.
|
|
||||||
|
|
||||||
### Builder Pattern
|
|
||||||
|
|
||||||
**`AgentSkillsProviderBuilder`** provides a fluent API for composing skills from multiple sources. The builder centralizes configuration — script executors, approval callbacks, prompt templates, and filtering — so consumers don't need to know the underlying source types.
|
|
||||||
|
|
||||||
The builder internally decides how to wire up the object graph: it creates the appropriate source instances, applies caching and filtering, and returns a fully configured `AgentSkillsProvider`. This keeps the setup code concise while still allowing fine-grained control when needed.
|
|
||||||
|
|
||||||
**Example** — Using the builder to combine multiple source types with configuration:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var provider = new AgentSkillsProviderBuilder()
|
|
||||||
.UseFileSkill("./skills") // file-based source
|
|
||||||
.UseInlineSkills(codeSkill) // code-defined source
|
|
||||||
.UseClassSkills(new ClassSkill()) // class-based source
|
|
||||||
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync) // script runner
|
|
||||||
.UseScriptApproval() // optional human-in-the-loop
|
|
||||||
.UsePromptTemplate(customTemplate) // optional prompt customization
|
|
||||||
.UseFilter(s => s.Frontmatter.Name != "internal") // optional skill filtering
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
AIContextProviders = [provider],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding a Custom Skill Type
|
|
||||||
|
|
||||||
The skills framework is designed for extensibility. While file-based and inline skills cover common
|
|
||||||
scenarios, you can introduce entirely new skill types by subclassing the four base classes:
|
|
||||||
|
|
||||||
| Base class | Purpose |
|
|
||||||
|-----------------------|-----------------------------------------------------|
|
|
||||||
| `AgentSkillsSource` | Discovers and loads skills from a particular origin |
|
|
||||||
| `AgentSkill` | Holds metadata, content, resources, and scripts |
|
|
||||||
| `AgentSkillResource` | Provides supplementary content to a skill |
|
|
||||||
| `AgentSkillScript` | Represents an executable action within a skill |
|
|
||||||
|
|
||||||
The example below implements a **cloud-based skill type** where skills, resources, and scripts are
|
|
||||||
all stored in and executed through a remote cloud service (e.g., Azure Blob Storage + Azure Functions).
|
|
||||||
|
|
||||||
### Step 1 — Define a custom resource
|
|
||||||
|
|
||||||
A `CloudSkillResource` reads resource content from a cloud storage endpoint instead of the local
|
|
||||||
filesystem:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
/// <summary>
|
|
||||||
/// A skill resource backed by a cloud storage endpoint.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CloudSkillResource : AgentSkillResource
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public CloudSkillResource(string name, Uri blobUri, HttpClient httpClient, string? description = null)
|
|
||||||
: base(name, description)
|
|
||||||
{
|
|
||||||
BlobUri = blobUri ?? throw new ArgumentNullException(nameof(blobUri));
|
|
||||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the URI of the cloud blob that holds this resource's content.
|
|
||||||
/// </summary>
|
|
||||||
public Uri BlobUri { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override async Task<object?> ReadAsync(
|
|
||||||
IServiceProvider? serviceProvider = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await _httpClient.GetStringAsync(BlobUri, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2 — Define a custom script
|
|
||||||
|
|
||||||
A `CloudSkillScript` executes a script by calling a cloud function endpoint, passing arguments as
|
|
||||||
the request body:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
/// <summary>
|
|
||||||
/// A skill script executed via a cloud function endpoint.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CloudSkillScript : AgentSkillScript
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public CloudSkillScript(string name, Uri functionUri, HttpClient httpClient, string? description = null)
|
|
||||||
: base(name, description)
|
|
||||||
{
|
|
||||||
FunctionUri = functionUri ?? throw new ArgumentNullException(nameof(functionUri));
|
|
||||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the URI of the cloud function that runs this script.
|
|
||||||
/// </summary>
|
|
||||||
public Uri FunctionUri { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override async Task<object?> RunAsync(
|
|
||||||
AgentSkill skill,
|
|
||||||
AIFunctionArguments arguments,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var json = JsonSerializer.Serialize(arguments);
|
|
||||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
||||||
var response = await _httpClient.PostAsync(FunctionUri, content, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3 — Define a custom skill
|
|
||||||
|
|
||||||
A `CloudSkill` bundles cloud-specific metadata (e.g., the base endpoint) with the standard skill
|
|
||||||
shape:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="AgentSkill"/> whose content, resources, and scripts are stored in a cloud service.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CloudSkill : AgentSkill
|
|
||||||
{
|
|
||||||
public CloudSkill(
|
|
||||||
AgentSkillFrontmatter frontmatter,
|
|
||||||
string content,
|
|
||||||
Uri endpoint,
|
|
||||||
IReadOnlyList<AgentSkillResource>? resources = null,
|
|
||||||
IReadOnlyList<AgentSkillScript>? scripts = null)
|
|
||||||
{
|
|
||||||
Frontmatter = frontmatter ?? throw new ArgumentNullException(nameof(frontmatter));
|
|
||||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
|
||||||
Endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint));
|
|
||||||
Resources = resources;
|
|
||||||
Scripts = scripts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AgentSkillFrontmatter Frontmatter { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string Content { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the base cloud endpoint for this skill.
|
|
||||||
/// </summary>
|
|
||||||
public Uri Endpoint { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override IReadOnlyList<AgentSkillResource>? Resources { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override IReadOnlyList<AgentSkillScript>? Scripts { get; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4 — Define a custom source
|
|
||||||
|
|
||||||
A `CloudSkillsSource` discovers skills from a cloud catalog API and constructs `CloudSkill`
|
|
||||||
instances with their associated resources and scripts:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
/// <summary>
|
|
||||||
/// A skill source that discovers and loads skills from a cloud catalog API.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CloudSkillsSource : AgentSkillsSource
|
|
||||||
{
|
|
||||||
private readonly Uri _catalogUri;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public CloudSkillsSource(Uri catalogUri, HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_catalogUri = catalogUri ?? throw new ArgumentNullException(nameof(catalogUri));
|
|
||||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override async Task<IList<AgentSkill>> GetSkillsAsync(
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
// Fetch the skill catalog from the cloud service.
|
|
||||||
var json = await _httpClient.GetStringAsync(_catalogUri, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
var catalog = JsonSerializer.Deserialize<CloudSkillCatalog>(json)!;
|
|
||||||
|
|
||||||
var skills = new List<AgentSkill>();
|
|
||||||
|
|
||||||
foreach (var entry in catalog.Skills)
|
|
||||||
{
|
|
||||||
var frontmatter = new AgentSkillFrontmatter(entry.Name, entry.Description);
|
|
||||||
|
|
||||||
// Build cloud-backed resources.
|
|
||||||
var resources = entry.Resources
|
|
||||||
.Select(r => new CloudSkillResource(r.Name, r.BlobUri, _httpClient, r.Description))
|
|
||||||
.ToList<AgentSkillResource>();
|
|
||||||
|
|
||||||
// Build cloud-backed scripts.
|
|
||||||
var scripts = entry.Scripts
|
|
||||||
.Select(s => new CloudSkillScript(s.Name, s.FunctionUri, _httpClient, s.Description))
|
|
||||||
.ToList<AgentSkillScript>();
|
|
||||||
|
|
||||||
skills.Add(new CloudSkill(frontmatter, entry.Content, entry.Endpoint, resources, scripts));
|
|
||||||
}
|
|
||||||
|
|
||||||
return skills;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5 — Register with the builder
|
|
||||||
|
|
||||||
Use `UseSource` to wire the custom source into the provider:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var httpClient = new HttpClient();
|
|
||||||
|
|
||||||
var provider = new AgentSkillsProviderBuilder()
|
|
||||||
.UseSource(new CloudSkillsSource(
|
|
||||||
new Uri("https://my-service.example.com/skills/catalog"),
|
|
||||||
httpClient))
|
|
||||||
// Mix with other source types if needed:
|
|
||||||
.UseFileSkill("/local/skills", scriptRunner)
|
|
||||||
.UseInlineSkills(someInlineSkill)
|
|
||||||
.Build();
|
|
||||||
```
|
|
||||||
|
|
||||||
The `AgentSkillsProvider` handles all skill types uniformly — any combination of file-based, inline,
|
|
||||||
class-based, and custom skills can coexist in the same provider. Custom skills automatically
|
|
||||||
participate in the model-facing tools (`load_skill`, `read_skill_resource`, `run_skill_script`),
|
|
||||||
filtering, deduplication, and caching — no additional integration work is required.
|
|
||||||
|
|
||||||
## Script Representation: `AgentSkillScript` vs `AIFunction`
|
|
||||||
|
|
||||||
Two approaches were considered for representing executable scripts within skills:
|
|
||||||
|
|
||||||
### Option A — Custom `AgentSkillScript` abstract base class (original design)
|
|
||||||
|
|
||||||
Scripts are modeled as a custom `AgentSkillScript` abstract class with `Name`, `Description`, and
|
|
||||||
`RunAsync(AgentSkill, AIFunctionArguments, CancellationToken)`. Concrete implementations:
|
|
||||||
`AgentInlineSkillScript` (wraps a delegate/`AIFunction`) and `AgentFileSkillScript` (wraps a file path + executor delegate).
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Base type
|
|
||||||
public abstract class AgentSkillScript
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string? Description { get; }
|
|
||||||
public abstract Task<object?> RunAsync(AgentSkill skill, AIFunctionArguments arguments, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentSkill exposes scripts as:
|
|
||||||
public abstract IReadOnlyList<AgentSkillScript>? Scripts { get; }
|
|
||||||
|
|
||||||
// Inline script wraps an AIFunction internally
|
|
||||||
var script = new AgentInlineSkillScript(ConvertUnits, "convert");
|
|
||||||
|
|
||||||
// Pre-built AIFunction must be wrapped
|
|
||||||
var script = new AgentInlineSkillScript(myAIFunction);
|
|
||||||
|
|
||||||
// Class-based skill declares scripts as:
|
|
||||||
public override IReadOnlyList<AgentSkillScript>? Scripts { get; } =
|
|
||||||
[
|
|
||||||
new AgentInlineSkillScript(ConvertUnits, "convert"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Provider executes scripts by passing the owning skill:
|
|
||||||
await script.RunAsync(skill, arguments, cancellationToken);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
|
|
||||||
- **Explicit skill context at execution time.** `RunAsync` receives the owning `AgentSkill`, so any script can access skill metadata or resources during execution without requiring construction-time wiring.
|
|
||||||
- **Self-contained abstraction.** A dedicated type communicates clearly that scripts are a skills-framework concept, separate from general-purpose AI functions.
|
|
||||||
- **Easier extensibility for custom script types.** Third-party implementations can subclass `AgentSkillScript` and access the owning skill in `RunAsync` without special setup.
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
|
|
||||||
- **Wrapper overhead.** `AgentInlineSkillScript` is a thin pass-through around `AIFunction` — it adds a class, a constructor, and an indirection layer for no behavioral difference.
|
|
||||||
- **Parallel abstraction.** `AgentSkillScript` and `AIFunction` serve overlapping purposes (named callable with arguments), creating two parallel hierarchies for the same concept.
|
|
||||||
- **Friction for consumers.** Users who already have `AIFunction` instances must wrap them in `AgentInlineSkillScript` to use them as scripts, adding ceremony.
|
|
||||||
|
|
||||||
### Option B — Reuse `AIFunction` directly
|
|
||||||
|
|
||||||
Scripts are represented as `AIFunction` (from `Microsoft.Extensions.AI`). `AgentSkill.Scripts` returns
|
|
||||||
`IReadOnlyList<AIFunction>?`. `AgentInlineSkillScript` is eliminated entirely — callers use
|
|
||||||
`AIFunctionFactory.Create(delegate, name: ...)` or pass `AIFunction` instances directly.
|
|
||||||
`AgentFileSkillScript` becomes an `AIFunction` subclass that captures its owning `AgentFileSkill` via
|
|
||||||
an internal back-reference set during construction.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// AgentSkill exposes scripts as AIFunction directly:
|
|
||||||
public abstract IReadOnlyList<AIFunction>? Scripts { get; }
|
|
||||||
|
|
||||||
// Inline scripts use AIFunctionFactory — no wrapper class needed
|
|
||||||
var skill = new AgentInlineSkill("my-skill", "desc", "instructions");
|
|
||||||
skill.AddScript(ConvertUnits, "convert"); // delegate
|
|
||||||
skill.AddScript(myAIFunction); // pre-built AIFunction — no wrapping
|
|
||||||
|
|
||||||
// Class-based skill declares scripts as:
|
|
||||||
public override IReadOnlyList<AIFunction>? Scripts { get; } =
|
|
||||||
[
|
|
||||||
AIFunctionFactory.Create(ConvertUnits, name: "convert"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Provider executes scripts via standard AIFunction invocation:
|
|
||||||
await script.InvokeAsync(arguments, cancellationToken);
|
|
||||||
|
|
||||||
// File-based scripts extend AIFunction and capture the owning skill internally:
|
|
||||||
public sealed class AgentFileSkillScript : AIFunction
|
|
||||||
{
|
|
||||||
internal AgentFileSkill? Skill { get; set; } // set by AgentFileSkill constructor
|
|
||||||
|
|
||||||
protected override async ValueTask<object?> InvokeCoreAsync(
|
|
||||||
AIFunctionArguments arguments, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await _executor(Skill!, this, arguments, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
|
|
||||||
- **Fewer types.** Eliminates `AgentSkillScript` and `AgentInlineSkillScript`, reducing the public API surface by two classes.
|
|
||||||
- **Seamless interop.** Any `AIFunction` — whether from `AIFunctionFactory`, a custom subclass, or an external library — can be used as a skill script with zero wrapping.
|
|
||||||
- **Consistent with `Microsoft.Extensions.AI` ecosystem.** Scripts share the same type as tool functions used by `IChatClient` and `FunctionInvokingChatClient`, reducing conceptual overhead for developers already familiar with the ecosystem.
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
|
|
||||||
- **No owning-skill context in invocation signature.** `AIFunction.InvokeAsync` does not accept an `AgentSkill` parameter, so `AgentFileSkillScript` must capture its owning skill via an internal setter during construction. This adds a construction-order dependency: the skill must set the back-reference on its scripts.
|
|
||||||
- **Custom script types lose automatic skill access.** Third-party `AIFunction` subclasses that need the owning skill must implement their own mechanism (e.g., constructor injection, closure capture) instead of receiving it as a method parameter.
|
|
||||||
- **Semantic overloading.** `AIFunction` now means both "a tool the model can call" and "a script within a skill", which could blur the distinction for framework users.
|
|
||||||
|
|
||||||
## Resource Representation: `AgentSkillResource` vs `AIFunction`
|
|
||||||
|
|
||||||
Two approaches were considered for representing skill resources (supplementary content such as references, assets, or dynamic data):
|
|
||||||
|
|
||||||
### Option A — Custom `AgentSkillResource` abstract base class (original design)
|
|
||||||
|
|
||||||
Resources are modeled as a custom `AgentSkillResource` abstract class with `Name`, `Description`, and
|
|
||||||
`ReadAsync(IServiceProvider?, CancellationToken)`. Concrete implementations:
|
|
||||||
`AgentInlineSkillResource` (static value, delegate, or `AIFunction` wrapper) and `AgentFileSkillResource` (reads file content from disk).
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Base type
|
|
||||||
public abstract class AgentSkillResource
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string? Description { get; }
|
|
||||||
public abstract Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentSkill exposes resources as:
|
|
||||||
public abstract IReadOnlyList<AgentSkillResource>? Resources { get; }
|
|
||||||
|
|
||||||
// Static resource
|
|
||||||
var resource = new AgentInlineSkillResource("static content", "my-resource");
|
|
||||||
|
|
||||||
// Dynamic resource (delegate)
|
|
||||||
var resource = new AgentInlineSkillResource((IServiceProvider sp) => GetData(sp), "my-resource");
|
|
||||||
|
|
||||||
// Pre-built AIFunction must be wrapped
|
|
||||||
var resource = new AgentInlineSkillResource(myAIFunction);
|
|
||||||
|
|
||||||
// Class-based skill declares resources as:
|
|
||||||
public override IReadOnlyList<AgentSkillResource>? Resources { get; } =
|
|
||||||
[
|
|
||||||
new AgentInlineSkillResource("# Conversion Tables\n...", "conversion-table"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Provider reads resources via:
|
|
||||||
await resource.ReadAsync(serviceProvider, cancellationToken);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
|
|
||||||
- **Clear semantic distinction.** A dedicated `AgentSkillResource` type distinguishes resources (data providers) from scripts (executable actions), making the API self-documenting.
|
|
||||||
- **Purpose-built API.** `ReadAsync` communicates intent better than `InvokeAsync` for a data-access operation.
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
|
|
||||||
- **Wrapper overhead.** `AgentInlineSkillResource` wraps `AIFunction` internally for delegate/function cases — adding a class and indirection for no behavioral difference.
|
|
||||||
- **Parallel abstraction.** `AgentSkillResource` and `AIFunction` serve overlapping purposes (named callable that returns data), creating two parallel hierarchies.
|
|
||||||
- **Friction for consumers.** Users who already have `AIFunction` instances must wrap them in `AgentInlineSkillResource`, adding ceremony.
|
|
||||||
|
|
||||||
### Option B — Reuse `AIFunction` directly
|
|
||||||
|
|
||||||
Resources are represented as `AIFunction`. `AgentSkill.Resources` returns `IReadOnlyList<AIFunction>?`.
|
|
||||||
`AgentInlineSkillResource` becomes an `AIFunction` subclass (retained as a convenience for the static-value
|
|
||||||
pattern: `new AgentInlineSkillResource("data", "name")`). `AgentFileSkillResource` becomes an `AIFunction`
|
|
||||||
subclass that reads file content.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// AgentSkill exposes resources as AIFunction directly:
|
|
||||||
public abstract IReadOnlyList<AIFunction>? Resources { get; }
|
|
||||||
|
|
||||||
// Static resource — AgentInlineSkillResource is retained as a convenience AIFunction subclass
|
|
||||||
var resource = new AgentInlineSkillResource("static content", "my-resource");
|
|
||||||
|
|
||||||
// Dynamic resource — AgentInlineSkillResource wraps delegate as AIFunction
|
|
||||||
var resource = new AgentInlineSkillResource((IServiceProvider sp) => GetData(sp), "my-resource");
|
|
||||||
|
|
||||||
// Pre-built AIFunction can be used directly — no wrapping needed
|
|
||||||
skill.AddResource(myAIFunction);
|
|
||||||
|
|
||||||
// Class-based skill declares resources as:
|
|
||||||
public override IReadOnlyList<AIFunction>? Resources { get; } =
|
|
||||||
[
|
|
||||||
new AgentInlineSkillResource("# Conversion Tables\n...", "conversion-table"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Provider reads resources via standard AIFunction invocation:
|
|
||||||
await resource.InvokeAsync(arguments, cancellationToken);
|
|
||||||
|
|
||||||
// File-based resources extend AIFunction directly:
|
|
||||||
internal sealed class AgentFileSkillResource : AIFunction
|
|
||||||
{
|
|
||||||
public string FullPath { get; }
|
|
||||||
|
|
||||||
protected override async ValueTask<object?> InvokeCoreAsync(
|
|
||||||
AIFunctionArguments arguments, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await File.ReadAllTextAsync(FullPath, Encoding.UTF8, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
|
|
||||||
- **Fewer base types.** Eliminates the `AgentSkillResource` abstract class, reducing the public API surface.
|
|
||||||
- **Seamless interop.** Any `AIFunction` can be used as a skill resource with zero wrapping.
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
|
|
||||||
- **Loss of semantic distinction.** Resources and scripts are now both `AIFunction`, which could make it less obvious which list a function belongs to when reading code.
|
|
||||||
- **Static values require a wrapper.** Unlike the original `ReadAsync` which could return a stored value directly, `AIFunction.InvokeAsync` implies invocation. `AgentInlineSkillResource` is retained as a convenience subclass to handle the static-value case, so this is not eliminated — just moved to a different class.
|
|
||||||
|
|
||||||
## Decision Outcome
|
|
||||||
|
|
||||||
### 1. Keep `AgentSkillResource` and `AgentSkillScript` (Option A for both sections)
|
|
||||||
|
|
||||||
We are staying with the custom `AgentSkillResource` and `AgentSkillScript` model classes instead of reusing `AIFunction`:
|
|
||||||
|
|
||||||
- **Resources have no parameters.** If a consumer provides an `AIFunction` with parameters, those parameters will never be advertised to the LLM, and the resulting call will fail.
|
|
||||||
- **Approval breaks for `AIFunction`-based representations.** When a resource or script represented by an `AIFunction` is configured with approval, the second approval invocation will not work correctly.
|
|
||||||
- **Injecting the owning skill into an `AIFunction`-based script is problematic.** Constructor injection would introduce a circular reference between the skill and the script. An internal property setter is possible but adds coupling.
|
|
||||||
|
|
||||||
### 2. Make all agent skill classes internal
|
|
||||||
|
|
||||||
All agent-skill-related classes are made `internal` to minimize the public API surface while the feature matures. We can reconsider and promote types to `public` later based on community signal.
|
|
||||||
|
|
||||||
This leaves two public entry points:
|
|
||||||
|
|
||||||
- **`AgentSkillsProvider`** — use directly when all skills come from a single source and filtering is not needed.
|
|
||||||
- **`AgentSkillsProviderBuilder`** — use when mixing skill types or when filtering support is required.
|
|
||||||
|
|
||||||
### 3. Caching at provider level
|
|
||||||
|
|
||||||
Caching of tools and instructions is implemented inside `AgentSkillsProvider` rather than as an external decorator. Recreating tools and instructions on every provider call is wasteful, and a caching decorator sitting outside the provider would not have the information needed to cache them effectively.
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
---
|
|
||||||
status: accepted
|
|
||||||
contact: eavanvalkenburg
|
|
||||||
date: 2026-03-20
|
|
||||||
deciders: eavanvalkenburg, sphenry, chetantoshnival
|
|
||||||
consulted: taochenosu, moonbox3, dmytrostruk, giles17, alliscode
|
|
||||||
---
|
|
||||||
|
|
||||||
# Provider-Leading Client Design & OpenAI Package Extraction
|
|
||||||
|
|
||||||
## Context and Problem Statement
|
|
||||||
|
|
||||||
The `agent-framework-core` package currently bundles OpenAI and Azure OpenAI client implementations along with their dependencies (`openai`, `azure-identity`, `azure-ai-projects`, `packaging`). This makes core heavier than necessary for users who don't use OpenAI, and it conflates the core abstractions with a specific provider implementation. Additionally, the current class naming (`OpenAIResponsesClient`, `OpenAIChatClient`) is based on the underlying OpenAI API names rather than what users actually want to do, making discoverability harder for newcomers.
|
|
||||||
|
|
||||||
## Decision Drivers
|
|
||||||
|
|
||||||
- **Lightweight core**: Core should only contain abstractions, middleware infrastructure, and telemetry — no provider-specific code or dependencies.
|
|
||||||
- **Discoverability-first**: Import namespaces should guide users to the right client. `from agent_framework.openai import ...` should surface all OpenAI-related clients; `from agent_framework.azure import ...` should surface Foundry, Azure AI, and other Azure-specific classes.
|
|
||||||
- **Provider-leading naming**: The primary client name should reflect the provider, not the underlying API. The Responses API is now the recommended default for OpenAI, so its client should be called `OpenAIChatClient` (not `OpenAIResponsesClient`).
|
|
||||||
- **Clean separation of concerns**: Azure-specific deprecated wrappers belong in the azure-ai package, not in the OpenAI package.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
- **Keep OpenAI in core**: Simpler but keeps core heavy; doesn't help discoverability.
|
|
||||||
- **Extract OpenAI with Azure wrappers in the OpenAI package**: Keeps Azure OpenAI wrappers alongside OpenAI code, but pollutes the OpenAI package with Azure concerns.
|
|
||||||
- **Extract OpenAI, place Azure wrappers in azure-ai**: Clean separation; the OpenAI package has zero Azure dependencies; deprecated Azure wrappers live in a single file in azure-ai for easy future deletion.
|
|
||||||
|
|
||||||
## Decision Outcome
|
|
||||||
|
|
||||||
Chosen option: "Extract OpenAI, place Azure wrappers in azure-ai", because it achieves the lightest core, cleanest OpenAI package, and the most maintainable deprecation path.
|
|
||||||
|
|
||||||
Key changes:
|
|
||||||
|
|
||||||
1. **New `agent-framework-openai` package** with dependencies on `agent-framework-core`, `openai`, and `packaging` only.
|
|
||||||
2. **Class renames**: `OpenAIResponsesClient` → `OpenAIChatClient` (Responses API), `OpenAIChatClient` → `OpenAIChatCompletionClient` (Chat Completions API). Old names remain as deprecated aliases.
|
|
||||||
3. **Deprecated classes**: `OpenAIAssistantsClient`, all `AzureOpenAI*Client` classes, `AzureAIClient`, `AzureAIAgentClient`, and `AzureAIProjectAgentProvider` are marked deprecated.
|
|
||||||
4. **New `FoundryChatClient`** in azure-ai for Azure AI Foundry Responses API access, built on `RawFoundryChatClient(RawOpenAIChatClient)`.
|
|
||||||
5. **All deprecated `AzureOpenAI*` classes** consolidated into a single file (`_deprecated_azure_openai.py`) in the azure-ai package for clean future deletion.
|
|
||||||
6. **Core's `agent_framework.openai` and `agent_framework.azure` namespaces** become lazy-loading gateways, preserving backward-compatible import paths while removing hard dependencies.
|
|
||||||
7. **Unified `model` parameter** replaces `model_id` (OpenAI), `deployment_name` (Azure OpenAI), and `model_deployment_name` (Azure AI) across all client constructors. The term `model` is intentionally generic: it naturally maps to an OpenAI model name *and* to an Azure OpenAI deployment name, making it straightforward to use `OpenAIChatClient` with either OpenAI or Azure OpenAI backends (via `AsyncAzureOpenAI`). Environment variables are similarly unified (e.g., `OPENAI_MODEL` instead of separate `OPENAI_RESPONSES_MODEL_ID` / `OPENAI_CHAT_MODEL_ID`).
|
|
||||||
8. **`FoundryAgent`** replaces the pattern of `Agent(client=AzureAIClient(...))` for connecting to pre-configured agents in Azure AI Foundry (PromptAgents and HostedAgents). The underlying `RawFoundryAgentChatClient` is an implementation detail — most users interact only with `FoundryAgent`. `AzureAIAgentClient` is separately deprecated as it refers to the V1 Agents Service API. See below for design rationale.
|
|
||||||
|
|
||||||
### Foundry Agent Design: `FoundryAgentClient` vs `FoundryAgent`
|
|
||||||
|
|
||||||
The existing `AzureAIClient` combines two concerns: CRUD lifecycle management (creating/deleting agents on the service) and runtime communication (sending messages via the Responses API). The new design removes CRUD entirely — users connect to agents that already exist in Foundry.
|
|
||||||
|
|
||||||
**Two approaches were considered:**
|
|
||||||
|
|
||||||
**Option A — `FoundryAgentClient` only (public ChatClient):**
|
|
||||||
Users compose `Agent(client=FoundryAgentClient(...), tools=[...])`. This follows the universal `Agent(client=X)` pattern used by every other provider. However, a "client" that wraps a named remote agent (with `agent_name` as a constructor param) is semantically odd — clients typically wrap a model endpoint, not a specific agent.
|
|
||||||
|
|
||||||
**Option B — `FoundryAgent` (Agent subclass) + private `_FoundryAgentChatClient` and public `RawFoundryAgentChatClient`:**
|
|
||||||
Users write `FoundryAgent(agent_name="my-agent", ...)` for the common case. Internally, `FoundryAgent` creates a `_FoundryAgentChatClient` and passes it to the standard `Agent` base class. For advanced customization, users pass `client_type=RawFoundryAgentChatClient` (or a custom subclass) to control the client middleware layers. The `Agent(client=RawFoundryAgentChatClient(...))` composition pattern still works for users who prefer it.
|
|
||||||
|
|
||||||
**Chosen option: Option B**, because:
|
|
||||||
- The common case (`FoundryAgent(...)`) is a single object with no boilerplate.
|
|
||||||
- `client_type=` gives full control over client middleware without parameter duplication — the agent forwards connection params to the client internally.
|
|
||||||
- `RawFoundryAgent(RawAgent)` and `FoundryAgent(Agent)` mirror the established `RawAgent`/`Agent` pattern.
|
|
||||||
- Runtime validation (only `FunctionTool` allowed) lives in `RawFoundryAgentChatClient._prepare_options`, ensuring it applies regardless of how the client is used — through `FoundryAgent`, `Agent(client=...)`, or any custom composition.
|
|
||||||
|
|
||||||
**Public classes:**
|
|
||||||
- `RawFoundryAgentChatClient(RawOpenAIChatClient)` — Responses API client that injects agent reference and validates tools. Extension point for custom client middleware.
|
|
||||||
- `RawFoundryAgent(RawAgent)` — Agent without agent-level middleware/telemetry.
|
|
||||||
- `FoundryAgent(AgentTelemetryLayer, AgentMiddlewareLayer, RawFoundryAgent)` — Recommended production agent.
|
|
||||||
|
|
||||||
**Internal (private):**
|
|
||||||
- `_FoundryAgentChatClient` — Full client with function invocation, chat middleware, and telemetry layers. Created automatically by `FoundryAgent`; users customize via `client_type=RawFoundryAgentChatClient` or a custom subclass.
|
|
||||||
|
|
||||||
**Deprecated:**
|
|
||||||
- `AzureAIClient` — replaced by `FoundryAgent` (which uses `FoundryAgentClient` internally).
|
|
||||||
- `AzureAIAgentClient` — refers to V1 Agents Service API, no direct replacement.
|
|
||||||
- `AzureAIProjectAgentProvider` — replaced by `FoundryAgent`.
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
---
|
|
||||||
status: accepted
|
|
||||||
contact: westey-m
|
|
||||||
date: 2026-03-23
|
|
||||||
deciders: sergeymenshykh, markwallace, rbarreto, dmytrostruk, westey-m, eavanvalkenburg, stephentoub
|
|
||||||
consulted:
|
|
||||||
informed:
|
|
||||||
---
|
|
||||||
|
|
||||||
# Chat History Persistence Consistency
|
|
||||||
|
|
||||||
## Context and Problem Statement
|
|
||||||
|
|
||||||
When using `ChatClientAgent` with tools, the `FunctionInvokingChatClient` (FIC) loops multiple times — service call → tool execution → service call → … — before producing a final response. There are two points of discrepancy between how chat history is stored by the framework's `ChatHistoryProvider` and how the underlying AI service stores chat history (e.g., OpenAI Responses with `store=true`):
|
|
||||||
|
|
||||||
1. **Persistence timing**: The AI service persists messages after *each* service call within the FIC loop. The `ChatHistoryProvider` currently persists messages only once, at the *end* of the full agent run (after all FIC loop iterations complete).
|
|
||||||
|
|
||||||
2. **Trailing `FunctionResultContent` storage**: When tool calling is terminated mid-loop (e.g., via `FunctionInvokingChatClient` termination filters), the final response from the agent may contain `FunctionResultContent` that was never sent to a subsequent service call. The AI service never stores this trailing `FunctionResultContent`, but the `ChatHistoryProvider` currently stores all response content, including the trailing `FunctionResultContent`.
|
|
||||||
|
|
||||||
These discrepancies mean that a `ChatHistoryProvider`-managed conversation and a service-managed conversation can diverge in content and structure, even when processing the same interactions.
|
|
||||||
|
|
||||||
### Practical Impact: Resuming After Tool-Call Termination
|
|
||||||
|
|
||||||
Today, users of `AIAgent` get different behaviors depending on whether chat history is stored service-side or in a `ChatHistoryProvider`. This creates concrete challenges — for example, when the function call loop is terminated and the user wants to resume the conversation in a subsequent run. With service-stored history, the trailing `FunctionResultContent` is never persisted, so the last stored message is the `FunctionCallContent` from the service. With `ChatHistoryProvider`-stored history, the trailing `FunctionResultContent` *is* persisted. The user cannot know whether the last `FunctionResultContent` is in the chat history or not without inspecting the storage mechanism, making it difficult to write resumption logic that works correctly regardless of the storage backend.
|
|
||||||
|
|
||||||
### Relationship Between the Two Discrepancies
|
|
||||||
|
|
||||||
The persistence timing and `FunctionResultContent` trimming behaviors are interrelated:
|
|
||||||
|
|
||||||
- **Per-service-call persistence**: When messages are persisted after each individual service call, trailing `FunctionResultContent` trimming is unnecessary. If tool calling is terminated, the `FunctionResultContent` from the terminated call was never sent to a subsequent service call, so it is never persisted. The per-service-call approach naturally matches the service's behavior.
|
|
||||||
|
|
||||||
- **Per-run persistence**: When messages are batched and persisted at the end of the full run, trailing `FunctionResultContent` trimming becomes necessary to match the service's behavior. Without trimming, the stored history contains `FunctionResultContent` that the service would never have stored.
|
|
||||||
|
|
||||||
## Decision Drivers
|
|
||||||
|
|
||||||
- **A. Consistency**: The default behavior of `ChatHistoryProvider` should produce stored history that closely matches what the underlying AI service would store, minimizing surprise when switching between framework-managed and service-managed chat history.
|
|
||||||
- **B. Atomicity**: A run that fails mid-way through a multi-step tool-calling loop should not leave chat history in a partially-updated state, unless the user explicitly opts into that behavior.
|
|
||||||
- **C. Recoverability**: For long-running tool-calling loops, it should be possible to recover intermediate progress if the process is interrupted, rather than losing all work from the current run.
|
|
||||||
- **D. Simplicity**: The default behavior should be easy to understand and predict for most users, without requiring knowledge of the FIC loop internals.
|
|
||||||
- **E. Flexibility**: Regardless of the chosen default, users should be able to opt into the alternative behavior.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
- Option 1: Per-run persistence with opt-in FRC (FunctionResultContent) trimming
|
|
||||||
- Option 2: Opt-in per-service-call persistence (via `RequirePerServiceCallChatHistoryPersistence`)
|
|
||||||
|
|
||||||
## Pros and Cons of the Options
|
|
||||||
|
|
||||||
### Option 1: Per-run persistence with opt-in FRC trimming
|
|
||||||
|
|
||||||
Keep the current default behavior of persisting chat history only at the end of the full agent run. Add `FunctionResultContent` trimming as an opt-in behavior to improve consistency with service storage.
|
|
||||||
|
|
||||||
- Good, because runs are atomic — chat history is only updated when the full run succeeds, satisfying driver B.
|
|
||||||
- Good, because the mental model is simple: one run = one history update, satisfying driver D.
|
|
||||||
- Good, because trimming trailing `FunctionResultContent` improves consistency with service storage, partially satisfying driver A.
|
|
||||||
- Bad, because the default persistence timing still differs from the service's behavior (per-run vs. per-service-call), only partially satisfying driver A.
|
|
||||||
- Bad, because if the process crashes mid-loop, all intermediate progress from the current run is lost, not satisfying driver C.
|
|
||||||
- Bad, because this option alone does not provide a way for users to opt into per-service-call persistence, not satisfying driver E.
|
|
||||||
|
|
||||||
### Option 2: Opt-in per-service-call persistence (via `RequirePerServiceCallChatHistoryPersistence`)
|
|
||||||
|
|
||||||
Introduce an optional RequirePerServiceCallChatHistoryPersistence setting to persist chat history after each individual service call within the FIC loop, matching the AI service's behavior. Trailing `FunctionResultContent` trimming is unnecessary with this approach (it is naturally handled).
|
|
||||||
|
|
||||||
Settings:
|
|
||||||
- `RequirePerServiceCallChatHistoryPersistence` = `true`
|
|
||||||
|
|
||||||
- Good, because the stored history matches the service's behavior when opting in for both timing and content, fully satisfying driver A.
|
|
||||||
- Good, because intermediate progress is preserved if the process is interrupted, satisfying driver C.
|
|
||||||
- Good, because no separate `FunctionResultContent` trimming logic is needed, reducing complexity.
|
|
||||||
- Bad, because chat history may be left in an incomplete state if the run fails mid-loop (e.g., `FunctionCallContent` stored without corresponding `FunctionResultContent`), not satisfying driver B. A subsequent run cannot proceed without manually providing the missing `FunctionResultContent`.
|
|
||||||
- Bad, because the mental model is more complex: a single run may produce multiple history updates, partially failing driver D.
|
|
||||||
- Neutral, because users can opt out to per-run persistence if they prefer atomicity, satisfying driver E.
|
|
||||||
|
|
||||||
## Decision Outcome
|
|
||||||
|
|
||||||
Chosen option: **Option 2: Opt-in per-service-call persistence (via `RequirePerServiceCallChatHistoryPersistence`)**. The existing per-run persistence behavior is retained as-is, requiring no changes from users. Per-service-call persistence is available as an opt-in feature via the `RequirePerServiceCallChatHistoryPersistence` setting. This satisfies drivers B (atomicity) and D (simplicity) for the common case, while fully satisfying driver A (consistency) for users who opt into simulated service-stored behavior. Users who need per-service-call persistence for recoverability (driver C) can enable it explicitly.
|
|
||||||
|
|
||||||
### Configuration Matrix
|
|
||||||
|
|
||||||
The behavior depends on the combination of `UseProvidedChatClientAsIs` and `RequirePerServiceCallChatHistoryPersistence`:
|
|
||||||
|
|
||||||
| `UseProvidedChatClientAsIs` | `RequirePerServiceCallChatHistoryPersistence` | Behavior |
|
|
||||||
|---|---|---|
|
|
||||||
| `false` (default) | `false` (default) | **Per-run persistence.** Messages are persisted at the end of the full agent run via the `ChatHistoryProvider`. |
|
|
||||||
| `false` | `true` | **Per-service-call persistence (simulated).** A `PerServiceCallChatHistoryPersistingChatClient` middleware is automatically injected into the chat client pipeline between `FunctionInvokingChatClient` and the leaf `IChatClient`. Messages are persisted after each service call. A sentinel `ConversationId` causes FIC to treat the conversation as service-managed. |
|
|
||||||
| `true` | `false` | **Per-run persistence.** No middleware is injected because the user has provided a custom chat client stack. Messages are persisted at the end of the run. |
|
|
||||||
| `true` | `true` | **User responsibility.** The system checks whether the custom chat client stack includes a `PerServiceCallChatHistoryPersistingChatClient`. If not, a warning is emitted — the user is expected to have added their own per-service-call persistence mechanism. End-of-run persistence is skipped. |
|
|
||||||
|
|
||||||
### Consequences
|
|
||||||
|
|
||||||
- Good, because per-run persistence is atomic by default — chat history is only updated when the full run succeeds, satisfying driver B.
|
|
||||||
- Good, because the default mental model is simple: one run = one history update, satisfying driver D.
|
|
||||||
- Good, because users who opt into `RequirePerServiceCallChatHistoryPersistence` get stored history that matches the service's behavior for both timing and content, fully satisfying driver A.
|
|
||||||
- Good, because per-service-call persistence preserves intermediate progress if the process is interrupted, satisfying driver C when opted in.
|
|
||||||
- Good, because no separate `FunctionResultContent` trimming logic is needed when per-service-call persistence is active — it is naturally handled.
|
|
||||||
- Good, because conflict detection (configurable via `ThrowOnChatHistoryProviderConflict`, `WarnOnChatHistoryProviderConflict`, `ClearOnChatHistoryProviderConflict`) prevents misconfiguration when a service returns a `ConversationId` alongside a configured `ChatHistoryProvider`.
|
|
||||||
- Bad, because per-service-call persistence (when opted in) may leave chat history in an incomplete state if the run fails mid-loop (e.g., `FunctionCallContent` stored without corresponding `FunctionResultContent`), requiring manual recovery in rare cases.
|
|
||||||
- Neutral, because users who want per-service-call consistency can opt in via `RequirePerServiceCallChatHistoryPersistence = true`, satisfying driver E.
|
|
||||||
- Neutral, because increased write frequency from per-service-call persistence may impact performance for some storage backends; this can be mitigated with a caching decorator.
|
|
||||||
|
|
||||||
### Implementation Notes
|
|
||||||
|
|
||||||
#### Conversation ID Consistency
|
|
||||||
|
|
||||||
When `RequirePerServiceCallChatHistoryPersistence` is enabled, the `PerServiceCallChatHistoryPersistingChatClient`
|
|
||||||
decorator also updates `session.ConversationId` after each service call. This handles two scenarios:
|
|
||||||
|
|
||||||
1. **Framework-managed chat history** — the decorator sets a sentinel `ConversationId` on the response
|
|
||||||
so that `FunctionInvokingChatClient` treats the conversation as service-managed (clearing accumulated
|
|
||||||
history between iterations and not injecting duplicate `FunctionCallContent` during approval processing).
|
|
||||||
|
|
||||||
2. **Service-stored chat history** — when the service returns a real `ConversationId`, the decorator
|
|
||||||
updates `session.ConversationId` immediately after each service call, rather than deferring the update
|
|
||||||
to the end of the run. This ensures intermediate ConversationId changes are captured even if the
|
|
||||||
process is interrupted mid-loop.
|
|
||||||
|
|
||||||
For some service-stored scenarios (e.g., the Conversations API with the Responses API), there is only
|
|
||||||
one thread with one ID, so every service call returns the same ConversationId and this per-call update
|
|
||||||
makes no practical difference. Enabling `RequirePerServiceCallChatHistoryPersistence` ensures consistent
|
|
||||||
per-service-call behavior across all service types regardless of how they manage ConversationIds.
|
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<IsReleaseCandidate>false</IsReleaseCandidate>
|
<IsReleaseCandidate>false</IsReleaseCandidate>
|
||||||
<IsGenerallyAvailable>false</IsGenerallyAvailable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Configurations>
|
<Configurations>
|
||||||
<BuildType Name="Debug" />
|
<BuildType Name="Debug" />
|
||||||
<BuildType Name="Publish" />
|
<BuildType Name="Publish" />
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
<Project Path="samples/02-agents/Agents/Agent_Step16_Declarative/Agent_Step16_Declarative.csproj" />
|
<Project Path="samples/02-agents/Agents/Agent_Step16_Declarative/Agent_Step16_Declarative.csproj" />
|
||||||
<Project Path="samples/02-agents/Agents/Agent_Step17_AdditionalAIContext/Agent_Step17_AdditionalAIContext.csproj" />
|
<Project Path="samples/02-agents/Agents/Agent_Step17_AdditionalAIContext/Agent_Step17_AdditionalAIContext.csproj" />
|
||||||
<Project Path="samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Agent_Step18_CompactionPipeline.csproj" />
|
<Project Path="samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Agent_Step18_CompactionPipeline.csproj" />
|
||||||
<Project Path="samples/02-agents/Agents/Agent_Step19_InFunctionLoopCheckpointing/Agent_Step19_InFunctionLoopCheckpointing.csproj" />
|
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Samples/02-agents/DeclarativeAgents/">
|
<Folder Name="/Samples/02-agents/DeclarativeAgents/">
|
||||||
<Project Path="samples/02-agents/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
|
<Project Path="samples/02-agents/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
|
||||||
@@ -77,8 +76,6 @@
|
|||||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
|
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
|
||||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
|
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
|
||||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/03_WorkflowHITL/03_WorkflowHITL.csproj" />
|
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/03_WorkflowHITL/03_WorkflowHITL.csproj" />
|
||||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/04_WorkflowMcpTool/04_WorkflowMcpTool.csproj" />
|
|
||||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/05_WorkflowAndAgents.csproj" />
|
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Samples/GettingStarted/">
|
<Folder Name="/Samples/GettingStarted/">
|
||||||
<File Path="samples/GettingStarted/README.md" />
|
<File Path="samples/GettingStarted/README.md" />
|
||||||
@@ -104,8 +101,7 @@
|
|||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Samples/02-agents/AgentSkills/">
|
<Folder Name="/Samples/02-agents/AgentSkills/">
|
||||||
<File Path="samples/02-agents/AgentSkills/README.md" />
|
<File Path="samples/02-agents/AgentSkills/README.md" />
|
||||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Agent_Step01_FileBasedSkills.csproj" />
|
<Project Path="samples/02-agents/AgentSkills/Agent_Step01_BasicSkills/Agent_Step01_BasicSkills.csproj" />
|
||||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Agent_Step02_CodeDefinedSkills.csproj" />
|
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
|
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
|
||||||
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
|
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
|
||||||
@@ -122,34 +118,6 @@
|
|||||||
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
|
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
|
||||||
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step04_UsingSkills/Agent_Anthropic_Step04_UsingSkills.csproj" />
|
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step04_UsingSkills/Agent_Anthropic_Step04_UsingSkills.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Samples/02-agents/AgentsWithFoundry/">
|
|
||||||
<File Path="samples/02-agents/AgentsWithFoundry/README.md" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj" />
|
|
||||||
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/Samples/02-agents/AgentWithMemory/">
|
<Folder Name="/Samples/02-agents/AgentWithMemory/">
|
||||||
<File Path="samples/02-agents/AgentWithMemory/README.md" />
|
<File Path="samples/02-agents/AgentWithMemory/README.md" />
|
||||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
|
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
|
||||||
@@ -172,6 +140,35 @@
|
|||||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj" />
|
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj" />
|
||||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj" />
|
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
<Folder Name="/Samples/02-agents/FoundryAgents/">
|
||||||
|
<File Path="samples/02-agents/FoundryAgents/README.md" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Evaluations_Step01_RedTeaming/FoundryAgents_Evaluations_Step01_RedTeaming.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Evaluations_Step02_SelfReflection/FoundryAgents_Evaluations_Step02_SelfReflection.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step01.1_Basics/FoundryAgents_Step01.1_Basics.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step01.2_Running/FoundryAgents_Step01.2_Running.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/FoundryAgents_Step02_MultiturnConversation.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/FoundryAgents_Step03_UsingFunctionTools.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/FoundryAgents_Step04_UsingFunctionToolsWithApprovals.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step05_StructuredOutput/FoundryAgents_Step05_StructuredOutput.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step06_PersistedConversations/FoundryAgents_Step06_PersistedConversations.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step07_Observability/FoundryAgents_Step07_Observability.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step08_DependencyInjection/FoundryAgents_Step08_DependencyInjection.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/FoundryAgents_Step09_UsingMcpClientAsTools.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step10_UsingImages/FoundryAgents_Step10_UsingImages.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/FoundryAgents_Step11_AsFunctionTool.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step12_Middleware/FoundryAgents_Step12_Middleware.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step13_Plugins/FoundryAgents_Step13_Plugins.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/FoundryAgents_Step14_CodeInterpreter.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step15_ComputerUse/FoundryAgents_Step15_ComputerUse.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step16_FileSearch/FoundryAgents_Step16_FileSearch.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step17_OpenAPITools/FoundryAgents_Step17_OpenAPITools.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step18_BingCustomSearch/FoundryAgents_Step18_BingCustomSearch.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step19_SharePoint/FoundryAgents_Step19_SharePoint.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step20_MicrosoftFabric/FoundryAgents_Step20_MicrosoftFabric.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step21_WebSearch/FoundryAgents_Step21_WebSearch.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/FoundryAgents_Step22_MemorySearch.csproj" />
|
||||||
|
<Project Path="samples/02-agents/FoundryAgents/FoundryAgents_Step23_LocalMCP/FoundryAgents_Step23_LocalMCP.csproj" />
|
||||||
|
</Folder>
|
||||||
<Folder Name="/Samples/02-agents/ModelContextProtocol/">
|
<Folder Name="/Samples/02-agents/ModelContextProtocol/">
|
||||||
<File Path="samples/02-agents/ModelContextProtocol/README.md" />
|
<File Path="samples/02-agents/ModelContextProtocol/README.md" />
|
||||||
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj" />
|
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj" />
|
||||||
@@ -318,8 +315,8 @@
|
|||||||
<Folder Name="/Samples/05-end-to-end/AspNetAgentAuthorization/">
|
<Folder Name="/Samples/05-end-to-end/AspNetAgentAuthorization/">
|
||||||
<File Path="samples/05-end-to-end/AspNetAgentAuthorization/docker-compose.yml" />
|
<File Path="samples/05-end-to-end/AspNetAgentAuthorization/docker-compose.yml" />
|
||||||
<File Path="samples/05-end-to-end/AspNetAgentAuthorization/README.md" />
|
<File Path="samples/05-end-to-end/AspNetAgentAuthorization/README.md" />
|
||||||
<Project Path="samples/05-end-to-end/AspNetAgentAuthorization/RazorWebClient/RazorWebClient.csproj" />
|
|
||||||
<Project Path="samples/05-end-to-end/AspNetAgentAuthorization/Service/Service.csproj" />
|
<Project Path="samples/05-end-to-end/AspNetAgentAuthorization/Service/Service.csproj" />
|
||||||
|
<Project Path="samples/05-end-to-end/AspNetAgentAuthorization/RazorWebClient/RazorWebClient.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Solution Items/">
|
<Folder Name="/Solution Items/">
|
||||||
<File Path=".editorconfig" />
|
<File Path=".editorconfig" />
|
||||||
|
|||||||
@@ -2,19 +2,17 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Central version prefix - applies to all nuget packages. -->
|
<!-- Central version prefix - applies to all nuget packages. -->
|
||||||
<VersionPrefix>1.0.0</VersionPrefix>
|
<VersionPrefix>1.0.0</VersionPrefix>
|
||||||
<RCNumber>5</RCNumber>
|
<RCNumber>4</RCNumber>
|
||||||
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
|
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
|
||||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260330.1</PackageVersion>
|
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260311.1</PackageVersion>
|
||||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260330.1</PackageVersion>
|
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260311.1</PackageVersion>
|
||||||
<GitTag>1.0.0-rc5</GitTag>
|
<GitTag>1.0.0-rc4</GitTag>
|
||||||
|
|
||||||
<Configurations>Debug;Release;Publish</Configurations>
|
<Configurations>Debug;Release;Publish</Configurations>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
|
|
||||||
<!-- Package validation. Baseline Version should be the latest version available on NuGet. -->
|
<!-- Package validation. Baseline Version should be the latest version available on NuGet. -->
|
||||||
<PackageValidationBaselineVersion>1.0.0-rc4</PackageValidationBaselineVersion>
|
<PackageValidationBaselineVersion>0.0.1</PackageValidationBaselineVersion>
|
||||||
<!-- Enable validation for RC packages and GA packages -->
|
|
||||||
<EnablePackageValidation Condition="'$(IsReleaseCandidate)' == 'true' OR '$(IsGenerallyAvailable)' == 'true'">true</EnablePackageValidation>
|
|
||||||
<!-- Validate assembly attributes only for Publish builds -->
|
<!-- Validate assembly attributes only for Publish builds -->
|
||||||
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003</NoWarn>
|
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003</NoWarn>
|
||||||
<!-- Do not validate reference assemblies -->
|
<!-- Do not validate reference assemblies -->
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringCompa
|
|||||||
|
|
||||||
if (approvalRequest.AdditionalProperties != null)
|
if (approvalRequest.AdditionalProperties != null)
|
||||||
{
|
{
|
||||||
approvalResponse.AdditionalProperties = [];
|
approvalResponse.AdditionalProperties = new AdditionalPropertiesDictionary();
|
||||||
foreach (var kvp in approvalRequest.AdditionalProperties)
|
foreach (var kvp in approvalRequest.AdditionalProperties)
|
||||||
{
|
{
|
||||||
approvalResponse.AdditionalProperties[kvp.Key] = kvp.Value;
|
approvalResponse.AdditionalProperties[kvp.Key] = kvp.Value;
|
||||||
|
|||||||
+4
-4
@@ -131,9 +131,9 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
|||||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
|
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
|
||||||
approvalCalls.Remove(functionResult.CallId);
|
approvalCalls.Remove(functionResult.CallId);
|
||||||
}
|
}
|
||||||
else
|
else if (transformedContents != null)
|
||||||
{
|
{
|
||||||
transformedContents?.Add(content);
|
transformedContents.Add(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,10 +155,10 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
|||||||
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
||||||
result.Add(newMessage);
|
result.Add(newMessage);
|
||||||
}
|
}
|
||||||
else
|
else if (result != null)
|
||||||
{
|
{
|
||||||
// We're already copying messages, so copy this unchanged message too
|
// We're already copying messages, so copy this unchanged message too
|
||||||
result?.Add(message);
|
result.Add(message);
|
||||||
}
|
}
|
||||||
// If result is null, we haven't made any changes yet, so keep processing
|
// If result is null, we haven't made any changes yet, so keep processing
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-8
@@ -57,10 +57,16 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
|||||||
throw new InvalidOperationException("Invalid request_approval tool call");
|
throw new InvalidOperationException("Invalid request_approval tool call");
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = (toolCall.Arguments.TryGetValue("request", out var reqObj) &&
|
var request = toolCall.Arguments.TryGetValue("request", out var reqObj) &&
|
||||||
reqObj is JsonElement argsElement &&
|
reqObj is JsonElement argsElement &&
|
||||||
argsElement.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalRequest))) is ApprovalRequest approvalRequest &&
|
argsElement.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalRequest))) is ApprovalRequest approvalRequest &&
|
||||||
approvalRequest != null ? approvalRequest : null) ?? throw new InvalidOperationException("Failed to deserialize approval request from tool call");
|
approvalRequest != null ? approvalRequest : null;
|
||||||
|
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to deserialize approval request from tool call");
|
||||||
|
}
|
||||||
|
|
||||||
return new ToolApprovalRequestContent(
|
return new ToolApprovalRequestContent(
|
||||||
requestId: request.ApprovalId,
|
requestId: request.ApprovalId,
|
||||||
new FunctionCallContent(
|
new FunctionCallContent(
|
||||||
@@ -71,11 +77,17 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
|||||||
|
|
||||||
private static ToolApprovalResponseContent ConvertToolResultToApprovalResponse(FunctionResultContent result, ToolApprovalRequestContent approval, JsonSerializerOptions jsonSerializerOptions)
|
private static ToolApprovalResponseContent ConvertToolResultToApprovalResponse(FunctionResultContent result, ToolApprovalRequestContent approval, JsonSerializerOptions jsonSerializerOptions)
|
||||||
{
|
{
|
||||||
var approvalResponse = (result.Result is JsonElement je ?
|
var approvalResponse = result.Result is JsonElement je ?
|
||||||
(ApprovalResponse?)je.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
|
(ApprovalResponse?)je.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
|
||||||
result.Result is string str ?
|
result.Result is string str ?
|
||||||
(ApprovalResponse?)JsonSerializer.Deserialize(str, jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
|
(ApprovalResponse?)JsonSerializer.Deserialize(str, jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
|
||||||
result.Result as ApprovalResponse) ?? throw new InvalidOperationException("Failed to deserialize approval response from tool result");
|
result.Result as ApprovalResponse;
|
||||||
|
|
||||||
|
if (approvalResponse == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to deserialize approval response from tool result");
|
||||||
|
}
|
||||||
|
|
||||||
return approval.CreateResponse(approvalResponse.Approved);
|
return approval.CreateResponse(approvalResponse.Approved);
|
||||||
}
|
}
|
||||||
#pragma warning restore MEAI001
|
#pragma warning restore MEAI001
|
||||||
@@ -109,7 +121,7 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
|||||||
// Track approval ID to original call ID mapping
|
// Track approval ID to original call ID mapping
|
||||||
_ = new Dictionary<string, string>();
|
_ = new Dictionary<string, string>();
|
||||||
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||||
Dictionary<string, ToolApprovalRequestContent> trackedRequestApprovalToolCalls = []; // Remote approvals
|
Dictionary<string, ToolApprovalRequestContent> trackedRequestApprovalToolCalls = new(); // Remote approvals
|
||||||
for (int messageIndex = 0; messageIndex < messages.Count; messageIndex++)
|
for (int messageIndex = 0; messageIndex < messages.Count; messageIndex++)
|
||||||
{
|
{
|
||||||
var message = messages[messageIndex];
|
var message = messages[messageIndex];
|
||||||
@@ -134,7 +146,7 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (content is FunctionResultContent toolResult &&
|
else if (content is FunctionResultContent toolResult &&
|
||||||
trackedRequestApprovalToolCalls.TryGetValue(toolResult.CallId, out var approval))
|
trackedRequestApprovalToolCalls.TryGetValue(toolResult.CallId, out var approval) == true)
|
||||||
{
|
{
|
||||||
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
||||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, j);
|
transformedContents ??= CopyContentsUpToIndex(message.Contents, j);
|
||||||
@@ -149,9 +161,9 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
|||||||
AdditionalProperties = message.AdditionalProperties
|
AdditionalProperties = message.AdditionalProperties
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else if (result != null)
|
||||||
{
|
{
|
||||||
result?.Add(message);
|
result.Add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,9 +72,10 @@ internal sealed class StatefulAgent<TState> : DelegatingAIAgent
|
|||||||
if (content is DataContent dataContent && dataContent.MediaType == "application/json")
|
if (content is DataContent dataContent && dataContent.MediaType == "application/json")
|
||||||
{
|
{
|
||||||
// Deserialize the state
|
// Deserialize the state
|
||||||
if (JsonSerializer.Deserialize(
|
TState? newState = JsonSerializer.Deserialize(
|
||||||
dataContent.Data.Span,
|
dataContent.Data.Span,
|
||||||
this._jsonSerializerOptions.GetTypeInfo(typeof(TState))) is TState newState)
|
this._jsonSerializerOptions.GetTypeInfo(typeof(TState))) as TState;
|
||||||
|
if (newState != null)
|
||||||
{
|
{
|
||||||
this.State = newState;
|
this.State = newState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ using OpenTelemetry.Trace;
|
|||||||
|
|
||||||
#region Setup Telemetry
|
#region Setup Telemetry
|
||||||
|
|
||||||
// Source name for this sample's custom ActivitySource and Meter; other instrumentation uses their own sources/categories.
|
|
||||||
const string SourceName = "OpenTelemetryAspire.ConsoleApp";
|
const string SourceName = "OpenTelemetryAspire.ConsoleApp";
|
||||||
const string ServiceName = "AgentOpenTelemetry";
|
const string ServiceName = "AgentOpenTelemetry";
|
||||||
|
|
||||||
@@ -41,6 +40,7 @@ var resource = ResourceBuilder.CreateDefault()
|
|||||||
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
|
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
|
||||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName, serviceVersion: "1.0.0"))
|
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName, serviceVersion: "1.0.0"))
|
||||||
.AddSource(SourceName) // Our custom activity source
|
.AddSource(SourceName) // Our custom activity source
|
||||||
|
.AddSource("*Microsoft.Agents.AI") // Agent Framework telemetry
|
||||||
.AddHttpClientInstrumentation() // Capture HTTP calls to OpenAI
|
.AddHttpClientInstrumentation() // Capture HTTP calls to OpenAI
|
||||||
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint));
|
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint));
|
||||||
|
|
||||||
@@ -54,7 +54,8 @@ using var tracerProvider = tracerProviderBuilder.Build();
|
|||||||
// Setup metrics with resource and instrument name filtering
|
// Setup metrics with resource and instrument name filtering
|
||||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName, serviceVersion: "1.0.0"))
|
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName, serviceVersion: "1.0.0"))
|
||||||
.AddMeter(SourceName) // Our custom meter source
|
.AddMeter(SourceName) // Our custom meter
|
||||||
|
.AddMeter("*Microsoft.Agents.AI") // Agent Framework metrics
|
||||||
.AddHttpClientInstrumentation() // HTTP client metrics
|
.AddHttpClientInstrumentation() // HTTP client metrics
|
||||||
.AddRuntimeInstrumentation() // .NET runtime metrics
|
.AddRuntimeInstrumentation() // .NET runtime metrics
|
||||||
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint))
|
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint))
|
||||||
@@ -127,7 +128,7 @@ var agent = new ChatClientAgent(instrumentedChatClient,
|
|||||||
instructions: "You are a helpful assistant that provides concise and informative responses.",
|
instructions: "You are a helpful assistant that provides concise and informative responses.",
|
||||||
tools: [AIFunctionFactory.Create(GetWeatherAsync)])
|
tools: [AIFunctionFactory.Create(GetWeatherAsync)])
|
||||||
.AsBuilder()
|
.AsBuilder()
|
||||||
.UseOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the agent level
|
.UseOpenTelemetry(SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the agent level
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var session = await agent.CreateSessionAsync();
|
var session = await agent.CreateSessionAsync();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using Azure.AI.Projects;
|
|||||||
using Azure.AI.Projects.Agents;
|
using Azure.AI.Projects.Agents;
|
||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
using Microsoft.Agents.AI.AzureAI;
|
|
||||||
|
|
||||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
||||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||||
@@ -31,18 +30,14 @@ var createdAgentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: J
|
|||||||
// agentVersion.Name = <agentName>
|
// agentVersion.Name = <agentName>
|
||||||
|
|
||||||
// You can use an AIAgent with an already created server side agent version.
|
// You can use an AIAgent with an already created server side agent version.
|
||||||
FoundryAgent existingJokerAgent = aiProjectClient.AsAIAgent(createdAgentVersion);
|
AIAgent existingJokerAgent = aiProjectClient.AsAIAgent(createdAgentVersion);
|
||||||
|
|
||||||
// You can also create another AIAgent version by providing the same name with a different definition.
|
// You can also create another AIAgent version by providing the same name with a different definition.
|
||||||
AgentVersion newJokerAgentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
AIAgent newJokerAgent = await aiProjectClient.CreateAIAgentAsync(name: JokerName, model: deploymentName, instructions: "You are extremely hilarious at telling jokes.");
|
||||||
JokerName,
|
|
||||||
new AgentVersionCreationOptions(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are extremely hilarious at telling jokes." }));
|
|
||||||
FoundryAgent newJokerAgent = aiProjectClient.AsAIAgent(newJokerAgentVersion);
|
|
||||||
|
|
||||||
// You can also get the AIAgent latest version just providing its name.
|
// You can also get the AIAgent latest version just providing its name.
|
||||||
AgentRecord jokerAgentRecord = await aiProjectClient.Agents.GetAgentAsync(JokerName);
|
AIAgent jokerAgentLatest = await aiProjectClient.GetAIAgentAsync(name: JokerName);
|
||||||
FoundryAgent jokerAgentLatest = aiProjectClient.AsAIAgent(jokerAgentRecord);
|
var latestAgentVersion = jokerAgentLatest.GetService<AgentVersion>()!;
|
||||||
AgentVersion latestAgentVersion = jokerAgentRecord.GetLatestVersion();
|
|
||||||
|
|
||||||
// The AIAgent version can be accessed via the GetService method.
|
// The AIAgent version can be accessed via the GetService method.
|
||||||
Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
|
Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
|
||||||
|
|||||||
-4
@@ -14,10 +14,6 @@
|
|||||||
<PackageReference Include="Azure.Identity" />
|
<PackageReference Include="Azure.Identity" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="..\SubprocessScriptRunner.cs" Link="SubprocessScriptRunner.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
|
||||||
|
// This sample demonstrates how to use Agent Skills with a ChatClientAgent.
|
||||||
|
// Agent Skills are modular packages of instructions and resources that extend an agent's capabilities.
|
||||||
|
// Skills follow the progressive disclosure pattern: advertise -> load -> read resources.
|
||||||
|
//
|
||||||
|
// This sample includes the expense-report skill:
|
||||||
|
// - Policy-based expense filing with references and assets
|
||||||
|
|
||||||
|
using Azure.AI.OpenAI;
|
||||||
|
using Azure.Identity;
|
||||||
|
using Microsoft.Agents.AI;
|
||||||
|
using OpenAI.Responses;
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||||
|
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||||
|
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||||
|
|
||||||
|
// --- Skills Provider ---
|
||||||
|
// Discovers skills from the 'skills' directory and makes them available to the agent
|
||||||
|
var skillsProvider = new FileAgentSkillsProvider(skillPath: Path.Combine(AppContext.BaseDirectory, "skills"));
|
||||||
|
|
||||||
|
// --- Agent Setup ---
|
||||||
|
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||||
|
.GetResponsesClient()
|
||||||
|
.AsAIAgent(new ChatClientAgentOptions
|
||||||
|
{
|
||||||
|
Name = "SkillsAgent",
|
||||||
|
ChatOptions = new()
|
||||||
|
{
|
||||||
|
Instructions = "You are a helpful assistant.",
|
||||||
|
},
|
||||||
|
AIContextProviders = [skillsProvider],
|
||||||
|
},
|
||||||
|
model: deploymentName);
|
||||||
|
|
||||||
|
// --- Example 1: Expense policy question (loads FAQ resource) ---
|
||||||
|
Console.WriteLine("Example 1: Checking expense policy FAQ");
|
||||||
|
Console.WriteLine("---------------------------------------");
|
||||||
|
AgentResponse response1 = await agent.RunAsync("Are tips reimbursable? I left a 25% tip on a taxi ride and want to know if that's covered.");
|
||||||
|
Console.WriteLine($"Agent: {response1.Text}\n");
|
||||||
|
|
||||||
|
// --- Example 2: Filing an expense report (multi-turn with template asset) ---
|
||||||
|
Console.WriteLine("Example 2: Filing an expense report");
|
||||||
|
Console.WriteLine("---------------------------------------");
|
||||||
|
AgentSession session = await agent.CreateSessionAsync();
|
||||||
|
AgentResponse response2 = await agent.RunAsync("I had 3 client dinners and a $1,200 flight last week. Return a draft expense report and ask about any missing details.",
|
||||||
|
session);
|
||||||
|
Console.WriteLine($"Agent: {response2.Text}\n");
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Agent Skills Sample
|
||||||
|
|
||||||
|
This sample demonstrates how to use **Agent Skills** with a `ChatClientAgent` in the Microsoft Agent Framework.
|
||||||
|
|
||||||
|
## What are Agent Skills?
|
||||||
|
|
||||||
|
Agent Skills are modular packages of instructions and resources that enable AI agents to perform specialized tasks. They follow the [Agent Skills specification](https://agentskills.io/) and implement the progressive disclosure pattern:
|
||||||
|
|
||||||
|
1. **Advertise**: Skills are advertised with name + description (~100 tokens per skill)
|
||||||
|
2. **Load**: Full instructions are loaded on-demand via `load_skill` tool
|
||||||
|
3. **Resources**: References and other files loaded via `read_skill_resource` tool
|
||||||
|
|
||||||
|
## Skills Included
|
||||||
|
|
||||||
|
### expense-report
|
||||||
|
Policy-based expense filing with spending limits, receipt requirements, and approval workflows.
|
||||||
|
- `references/POLICY_FAQ.md` — Detailed expense policy Q&A
|
||||||
|
- `assets/expense-report-template.md` — Submission template
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent_Step01_BasicSkills/
|
||||||
|
├── Program.cs
|
||||||
|
├── Agent_Step01_BasicSkills.csproj
|
||||||
|
└── skills/
|
||||||
|
└── expense-report/
|
||||||
|
├── SKILL.md
|
||||||
|
├── references/
|
||||||
|
│ └── POLICY_FAQ.md
|
||||||
|
└── assets/
|
||||||
|
└── expense-report-template.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Sample
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- .NET 10.0 SDK
|
||||||
|
- Azure OpenAI endpoint with a deployed model
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
1. Set environment variables:
|
||||||
|
```bash
|
||||||
|
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
|
||||||
|
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the sample:
|
||||||
|
```bash
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
The sample runs two examples:
|
||||||
|
|
||||||
|
1. **Expense policy FAQ** — Asks about tip reimbursement; the agent loads the expense-report skill and reads the FAQ resource
|
||||||
|
2. **Filing an expense report** — Multi-turn conversation to draft an expense report using the template asset
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
- [Agent Skills Specification](https://agentskills.io/)
|
||||||
|
- [Microsoft Agent Framework Documentation](../../../../../docs/)
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: expense-report
|
||||||
|
description: File and validate employee expense reports according to Contoso company policy. Use when asked about expense submissions, reimbursement rules, receipt requirements, spending limits, or expense categories.
|
||||||
|
metadata:
|
||||||
|
author: contoso-finance
|
||||||
|
version: "2.1"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expense Report
|
||||||
|
|
||||||
|
## Categories and Limits
|
||||||
|
|
||||||
|
| Category | Limit | Receipt | Approval |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Meals — solo | $50/day | >$25 | No |
|
||||||
|
| Meals — team/client | $75/person | Always | Manager if >$200 total |
|
||||||
|
| Lodging | $250/night | Always | Manager if >3 nights |
|
||||||
|
| Ground transport | $100/day | >$15 | No |
|
||||||
|
| Airfare | Economy | Always | Manager; VP if >$1,500 |
|
||||||
|
| Conference/training | $2,000/event | Always | Manager + L&D |
|
||||||
|
| Office supplies | $100 | Yes | No |
|
||||||
|
| Software/subscriptions | $50/month | Yes | Manager if >$200/year |
|
||||||
|
|
||||||
|
## Filing Process
|
||||||
|
|
||||||
|
1. Collect receipts — must show vendor, date, amount, payment method.
|
||||||
|
2. Categorize per table above.
|
||||||
|
3. Use template: [assets/expense-report-template.md](assets/expense-report-template.md).
|
||||||
|
4. For client/team meals: list attendee names and business purpose.
|
||||||
|
5. Submit — auto-approved if <$500; manager if $500–$2,000; VP if >$2,000.
|
||||||
|
6. Reimbursement: 10 business days via direct deposit.
|
||||||
|
|
||||||
|
## Policy Rules
|
||||||
|
|
||||||
|
- Submit within 30 days of transaction.
|
||||||
|
- Alcohol is never reimbursable.
|
||||||
|
- Foreign currency: convert to USD at transaction-date rate; note original currency and amount.
|
||||||
|
- Mixed personal/business travel: only business portion reimbursable; provide comparison quotes.
|
||||||
|
- Lost receipts (>$25): file Lost Receipt Affidavit from Finance. Max 2 per quarter.
|
||||||
|
- For policy questions not covered above, consult the FAQ: [references/POLICY_FAQ.md](references/POLICY_FAQ.md). Answers should be based on what this document and the FAQ state.
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Expense Report Template
|
||||||
|
|
||||||
|
| Date | Category | Vendor | Description | Amount (USD) | Original Currency | Original Amount | Attendees | Business Purpose | Receipt Attached |
|
||||||
|
|------|----------|--------|-------------|--------------|-------------------|-----------------|-----------|------------------|------------------|
|
||||||
|
| | | | | | | | | | Yes or No |
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
# Expense Policy — Frequently Asked Questions
|
||||||
|
|
||||||
|
## Meals
|
||||||
|
|
||||||
|
**Q: Can I expense coffee or snacks during the workday?**
|
||||||
|
A: Daily coffee/snacks under $10 are not reimbursable (considered personal). Coffee purchased during a client meeting or team working session is reimbursable as a team meal.
|
||||||
|
|
||||||
|
**Q: What if a team dinner exceeds the per-person limit?**
|
||||||
|
A: The $75/person limit applies as a guideline. Overages up to 20% are accepted with a written justification (e.g., "client dinner at venue chosen by client"). Overages beyond 20% require pre-approval from your VP.
|
||||||
|
|
||||||
|
**Q: Do I need to list every attendee?**
|
||||||
|
A: Yes. For client meals, list the client's name and company. For team meals, list all employee names. For groups over 10, you may attach a separate attendee list.
|
||||||
|
|
||||||
|
## Travel
|
||||||
|
|
||||||
|
**Q: Can I book a premium economy or business class flight?**
|
||||||
|
A: Economy class is the standard. Premium economy is allowed for flights over 6 hours. Business class requires VP pre-approval and is generally reserved for flights over 10 hours or medical accommodation.
|
||||||
|
|
||||||
|
**Q: What about ride-sharing (Uber/Lyft) vs. rental cars?**
|
||||||
|
A: Use ride-sharing for trips under 30 miles round-trip. Rent a car for multi-day travel or when ride-sharing would exceed $100/day. Always choose the compact/standard category unless traveling with 3+ people.
|
||||||
|
|
||||||
|
**Q: Are tips reimbursable?**
|
||||||
|
A: Tips up to 20% are reimbursable for meals, taxi/ride-share, and hotel housekeeping. Tips above 20% require justification.
|
||||||
|
|
||||||
|
## Lodging
|
||||||
|
|
||||||
|
**Q: What if the $250/night limit isn't enough for the city I'm visiting?**
|
||||||
|
A: For high-cost cities (New York, San Francisco, London, Tokyo, Sydney), the limit is automatically increased to $350/night. No additional approval is needed. For other locations where rates are unusually high (e.g., during a major conference), request a per-trip exception from your manager before booking.
|
||||||
|
|
||||||
|
**Q: Can I stay with friends/family instead and get a per-diem?**
|
||||||
|
A: No. Contoso reimburses actual lodging costs only, not per-diems.
|
||||||
|
|
||||||
|
## Subscriptions and Software
|
||||||
|
|
||||||
|
**Q: Can I expense a personal productivity tool?**
|
||||||
|
A: Software must be directly related to your job function. Tools like IDE licenses, design software, or project management apps are reimbursable. General productivity apps (note-taking, personal calendar) are not, unless your manager confirms a business need in writing.
|
||||||
|
|
||||||
|
**Q: What about annual subscriptions?**
|
||||||
|
A: Annual subscriptions over $200 require manager approval before purchase. Submit the approval email with your expense report.
|
||||||
|
|
||||||
|
## Receipts and Documentation
|
||||||
|
|
||||||
|
**Q: My receipt is faded/damaged. What do I do?**
|
||||||
|
A: Try to obtain a duplicate from the vendor. If not possible, submit a Lost Receipt Affidavit (available from the Finance SharePoint site). You're limited to 2 affidavits per quarter.
|
||||||
|
|
||||||
|
**Q: Do I need a receipt for parking meters or tolls?**
|
||||||
|
A: For amounts under $15, no receipt is required — just note the date, location, and amount. For $15 and above, a receipt or bank/credit card statement excerpt is required.
|
||||||
|
|
||||||
|
## Approval and Reimbursement
|
||||||
|
|
||||||
|
**Q: My manager is on leave. Who approves my report?**
|
||||||
|
A: Expense reports can be approved by your skip-level manager or any manager designated as an alternate approver in the expense system.
|
||||||
|
|
||||||
|
**Q: Can I submit expenses from a previous quarter?**
|
||||||
|
A: The standard 30-day window applies. Expenses older than 30 days require a written explanation and VP approval. Expenses older than 90 days are not reimbursable except in extraordinary circumstances (extended leave, medical emergency) with CFO approval.
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample demonstrates how to use file-based Agent Skills with a ChatClientAgent.
|
|
||||||
// Skills are discovered from SKILL.md files on disk and follow the progressive disclosure pattern:
|
|
||||||
// 1. Advertise — skill names and descriptions in the system prompt
|
|
||||||
// 2. Load — full instructions loaded on demand via load_skill tool
|
|
||||||
// 3. Read resources — reference files read via read_skill_resource tool
|
|
||||||
// 4. Run scripts — scripts executed via run_skill_script tool with a subprocess executor
|
|
||||||
//
|
|
||||||
// This sample uses a unit-converter skill that converts between miles, kilometers, pounds, and kilograms.
|
|
||||||
|
|
||||||
using Azure.AI.OpenAI;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using OpenAI.Responses;
|
|
||||||
|
|
||||||
// --- Configuration ---
|
|
||||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
|
||||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
|
||||||
|
|
||||||
// --- Skills Provider ---
|
|
||||||
// Discovers skills from the 'skills' directory containing SKILL.md files.
|
|
||||||
// The script runner runs file-based scripts (e.g. Python) as local subprocesses.
|
|
||||||
var skillsProvider = new AgentSkillsProvider(
|
|
||||||
Path.Combine(AppContext.BaseDirectory, "skills"),
|
|
||||||
SubprocessScriptRunner.RunAsync);
|
|
||||||
// --- Agent Setup ---
|
|
||||||
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
|
||||||
.GetResponsesClient()
|
|
||||||
.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
Name = "UnitConverterAgent",
|
|
||||||
ChatOptions = new()
|
|
||||||
{
|
|
||||||
Instructions = "You are a helpful assistant that can convert units.",
|
|
||||||
},
|
|
||||||
AIContextProviders = [skillsProvider],
|
|
||||||
},
|
|
||||||
model: deploymentName);
|
|
||||||
|
|
||||||
// --- Example: Unit conversion ---
|
|
||||||
Console.WriteLine("Converting units with file-based skills");
|
|
||||||
Console.WriteLine(new string('-', 60));
|
|
||||||
|
|
||||||
AgentResponse response = await agent.RunAsync(
|
|
||||||
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?");
|
|
||||||
|
|
||||||
Console.WriteLine($"Agent: {response.Text}");
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# File-Based Agent Skills Sample
|
|
||||||
|
|
||||||
This sample demonstrates how to use **file-based Agent Skills** with a `ChatClientAgent`.
|
|
||||||
|
|
||||||
## What it demonstrates
|
|
||||||
|
|
||||||
- Discovering skills from `SKILL.md` files on disk via `AgentFileSkillsSource`
|
|
||||||
- The progressive disclosure pattern: advertise → load → read resources → run scripts
|
|
||||||
- Using the `AgentSkillsProvider` constructor with a skill directory path and script executor
|
|
||||||
- Running file-based scripts (Python) via a subprocess-based executor
|
|
||||||
|
|
||||||
## Skills Included
|
|
||||||
|
|
||||||
### unit-converter
|
|
||||||
|
|
||||||
Converts between common units (miles↔km, pounds↔kg) using a multiplication factor.
|
|
||||||
|
|
||||||
- `references/conversion-table.md` — Conversion factor table
|
|
||||||
- `scripts/convert.py` — Python script that performs the conversion
|
|
||||||
|
|
||||||
## Running the Sample
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- .NET 10.0 SDK
|
|
||||||
- Azure OpenAI endpoint with a deployed model
|
|
||||||
- Python 3 installed and available as `python3` on your PATH
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
|
|
||||||
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
|
|
||||||
### Expected Output
|
|
||||||
|
|
||||||
```
|
|
||||||
Converting units with file-based skills
|
|
||||||
------------------------------------------------------------
|
|
||||||
Agent: Here are your conversions:
|
|
||||||
|
|
||||||
1. **26.2 miles → 42.16 km** (a marathon distance)
|
|
||||||
2. **75 kg → 165.35 lbs**
|
|
||||||
```
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: unit-converter
|
|
||||||
description: Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
When the user requests a unit conversion:
|
|
||||||
1. First, review `references/conversion-table.md` to find the correct factor
|
|
||||||
2. Run the `scripts/convert.py` script with `--value <number> --factor <factor>` (e.g. `--value 26.2 --factor 1.60934`)
|
|
||||||
3. Present the converted value clearly with both units
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
# Conversion Tables
|
|
||||||
|
|
||||||
Formula: **result = value Ă— factor**
|
|
||||||
|
|
||||||
| From | To | Factor |
|
|
||||||
|-------------|-------------|----------|
|
|
||||||
| miles | kilometers | 1.60934 |
|
|
||||||
| kilometers | miles | 0.621371 |
|
|
||||||
| pounds | kilograms | 0.453592 |
|
|
||||||
| kilograms | pounds | 2.20462 |
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
# Unit conversion script
|
|
||||||
# Converts a value using a multiplication factor: result = value Ă— factor
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# python scripts/convert.py --value 26.2 --factor 1.60934
|
|
||||||
# python scripts/convert.py --value 75 --factor 2.20462
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Convert a value using a multiplication factor.",
|
|
||||||
epilog="Examples:\n"
|
|
||||||
" python scripts/convert.py --value 26.2 --factor 1.60934\n"
|
|
||||||
" python scripts/convert.py --value 75 --factor 2.20462",
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
)
|
|
||||||
parser.add_argument("--value", type=float, required=True, help="The numeric value to convert.")
|
|
||||||
parser.add_argument("--factor", type=float, required=True, help="The conversion factor from the table.")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
result = round(args.value * args.factor, 4)
|
|
||||||
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<NoWarn>$(NoWarn);MAAI001</NoWarn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Azure.AI.OpenAI" />
|
|
||||||
<PackageReference Include="Azure.Identity" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample demonstrates how to define Agent Skills entirely in code using AgentInlineSkill.
|
|
||||||
// No SKILL.md files are needed — skills, resources, and scripts are all defined programmatically.
|
|
||||||
//
|
|
||||||
// Three approaches are shown using a unit-converter skill:
|
|
||||||
// 1. Static resources — inline content provided via AddResource
|
|
||||||
// 2. Dynamic resources — computed at runtime via a factory delegate
|
|
||||||
// 3. Code scripts — executable delegates the agent can invoke directly
|
|
||||||
|
|
||||||
using System.Text.Json;
|
|
||||||
using Azure.AI.OpenAI;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using OpenAI.Responses;
|
|
||||||
|
|
||||||
// --- Configuration ---
|
|
||||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
|
||||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
|
||||||
|
|
||||||
// --- Build the code-defined skill ---
|
|
||||||
var unitConverterSkill = new AgentInlineSkill(
|
|
||||||
name: "unit-converter",
|
|
||||||
description: "Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.",
|
|
||||||
instructions: """
|
|
||||||
Use this skill when the user asks to convert between units.
|
|
||||||
|
|
||||||
1. Review the conversion-table resource to find the factor for the requested conversion.
|
|
||||||
2. Check the conversion-policy resource for rounding and formatting rules.
|
|
||||||
3. Use the convert script, passing the value and factor from the table.
|
|
||||||
""")
|
|
||||||
// 1. Static Resource: conversion tables
|
|
||||||
.AddResource(
|
|
||||||
"conversion-table",
|
|
||||||
"""
|
|
||||||
# Conversion Tables
|
|
||||||
|
|
||||||
Formula: **result = value Ă— factor**
|
|
||||||
|
|
||||||
| From | To | Factor |
|
|
||||||
|-------------|-------------|----------|
|
|
||||||
| miles | kilometers | 1.60934 |
|
|
||||||
| kilometers | miles | 0.621371 |
|
|
||||||
| pounds | kilograms | 0.453592 |
|
|
||||||
| kilograms | pounds | 2.20462 |
|
|
||||||
""")
|
|
||||||
// 2. Dynamic Resource: conversion policy (computed at runtime)
|
|
||||||
.AddResource("conversion-policy", () =>
|
|
||||||
{
|
|
||||||
const int Precision = 4;
|
|
||||||
return $"""
|
|
||||||
# Conversion Policy
|
|
||||||
|
|
||||||
**Decimal places:** {Precision}
|
|
||||||
**Format:** Always show both the original and converted values with units
|
|
||||||
**Generated at:** {DateTime.UtcNow:O}
|
|
||||||
""";
|
|
||||||
})
|
|
||||||
// 3. Code Script: convert
|
|
||||||
.AddScript("convert", (double value, double factor) =>
|
|
||||||
{
|
|
||||||
double result = Math.Round(value * factor, 4);
|
|
||||||
return JsonSerializer.Serialize(new { value, factor, result });
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Skills Provider ---
|
|
||||||
var skillsProvider = new AgentSkillsProvider(unitConverterSkill);
|
|
||||||
|
|
||||||
// --- Agent Setup ---
|
|
||||||
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
|
||||||
.GetResponsesClient()
|
|
||||||
.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
Name = "UnitConverterAgent",
|
|
||||||
ChatOptions = new()
|
|
||||||
{
|
|
||||||
Instructions = "You are a helpful assistant that can convert units.",
|
|
||||||
},
|
|
||||||
AIContextProviders = [skillsProvider],
|
|
||||||
},
|
|
||||||
model: deploymentName);
|
|
||||||
|
|
||||||
// --- Example: Unit conversion ---
|
|
||||||
Console.WriteLine("Converting units with code-defined skills");
|
|
||||||
Console.WriteLine(new string('-', 60));
|
|
||||||
|
|
||||||
AgentResponse response = await agent.RunAsync(
|
|
||||||
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?");
|
|
||||||
|
|
||||||
Console.WriteLine($"Agent: {response.Text}");
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# Code-Defined Agent Skills Sample
|
|
||||||
|
|
||||||
This sample demonstrates how to define **Agent Skills entirely in code** using `AgentInlineSkill`.
|
|
||||||
|
|
||||||
## What it demonstrates
|
|
||||||
|
|
||||||
- Creating skills programmatically with `AgentInlineSkill` — no SKILL.md files needed
|
|
||||||
- **Static resources** via `AddResource` with inline content
|
|
||||||
- **Dynamic resources** via `AddResource` with a factory delegate (computed at runtime)
|
|
||||||
- **Code scripts** via `AddScript` with a delegate handler
|
|
||||||
- Using the `AgentSkillsProvider` constructor with inline skills
|
|
||||||
|
|
||||||
## Skills Included
|
|
||||||
|
|
||||||
### unit-converter (code-defined)
|
|
||||||
|
|
||||||
Converts between common units using multiplication factors. Defined entirely in C# code:
|
|
||||||
|
|
||||||
- `conversion-table` — Static resource with factor table
|
|
||||||
- `conversion-policy` — Dynamic resource with formatting rules (generated at runtime)
|
|
||||||
- `convert` — Script that performs `value × factor` conversion
|
|
||||||
|
|
||||||
## Running the Sample
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- .NET 10.0 SDK
|
|
||||||
- Azure OpenAI endpoint with a deployed model
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
|
|
||||||
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
|
|
||||||
### Expected Output
|
|
||||||
|
|
||||||
```
|
|
||||||
Converting units with code-defined skills
|
|
||||||
------------------------------------------------------------
|
|
||||||
Agent: Here are your conversions:
|
|
||||||
|
|
||||||
1. **26.2 miles → 42.16 km** (a marathon distance)
|
|
||||||
2. **75 kg → 165.35 lbs**
|
|
||||||
```
|
|
||||||
@@ -1,24 +1,7 @@
|
|||||||
# AgentSkills Samples
|
# AgentSkills Samples
|
||||||
|
|
||||||
Samples demonstrating Agent Skills capabilities. Each sample shows a different way to define and use skills.
|
Samples demonstrating Agent Skills capabilities.
|
||||||
|
|
||||||
| Sample | Description |
|
| Sample | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| [Agent_Step01_FileBasedSkills](Agent_Step01_FileBasedSkills/) | Define skills as `SKILL.md` files on disk with reference documents. Uses a unit-converter skill. |
|
| [Agent_Step01_BasicSkills](Agent_Step01_BasicSkills/) | Using Agent Skills with a ChatClientAgent, including progressive disclosure and skill resources |
|
||||||
| [Agent_Step02_CodeDefinedSkills](Agent_Step02_CodeDefinedSkills/) | Define skills entirely in C# code using `AgentInlineSkill`, with static/dynamic resources and scripts. |
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### File-Based vs Code-Defined Skills
|
|
||||||
|
|
||||||
| Aspect | File-Based | Code-Defined |
|
|
||||||
|--------|-----------|--------------|
|
|
||||||
| Definition | `SKILL.md` files on disk | `AgentInlineSkill` instances in C# |
|
|
||||||
| Resources | All files in skill directory (filtered by extension) | `AddResource` (static value or delegate-backed) |
|
|
||||||
| Scripts | Supported via script executor delegate | `AddScript` delegates |
|
|
||||||
| Discovery | Automatic from directory path | Explicit via constructor |
|
|
||||||
| Dynamic content | No (static files only) | Yes (factory delegates) |
|
|
||||||
| Reusability | Copy skill directory | Inline or shared instances |
|
|
||||||
|
|
||||||
For single-source scenarios, use the `AgentSkillsProvider` constructors directly. To combine multiple skill types, use the `AgentSkillsProviderBuilder`.
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// Sample subprocess-based skill script runner.
|
|
||||||
// Executes file-based skill scripts as local subprocesses.
|
|
||||||
// This is provided for demonstration purposes only.
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Executes file-based skill scripts as local subprocesses.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This runner uses the script's absolute path, converts the arguments
|
|
||||||
/// to CLI flags, and returns captured output. It is intended for
|
|
||||||
/// demonstration purposes only.
|
|
||||||
/// </remarks>
|
|
||||||
internal static class SubprocessScriptRunner
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Runs a skill script as a local subprocess.
|
|
||||||
/// </summary>
|
|
||||||
public static async Task<object?> RunAsync(
|
|
||||||
AgentFileSkill skill,
|
|
||||||
AgentFileSkillScript script,
|
|
||||||
AIFunctionArguments arguments,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (!File.Exists(script.FullPath))
|
|
||||||
{
|
|
||||||
return $"Error: Script file not found: {script.FullPath}";
|
|
||||||
}
|
|
||||||
|
|
||||||
string extension = Path.GetExtension(script.FullPath);
|
|
||||||
string? interpreter = extension switch
|
|
||||||
{
|
|
||||||
".py" => "python3",
|
|
||||||
".js" => "node",
|
|
||||||
".sh" => "bash",
|
|
||||||
".ps1" => "pwsh",
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
WorkingDirectory = Path.GetDirectoryName(script.FullPath) ?? ".",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (interpreter is not null)
|
|
||||||
{
|
|
||||||
startInfo.FileName = interpreter;
|
|
||||||
startInfo.ArgumentList.Add(script.FullPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
startInfo.FileName = script.FullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments is not null)
|
|
||||||
{
|
|
||||||
foreach (var (key, value) in arguments)
|
|
||||||
{
|
|
||||||
if (value is bool boolValue)
|
|
||||||
{
|
|
||||||
if (boolValue)
|
|
||||||
{
|
|
||||||
startInfo.ArgumentList.Add(NormalizeKey(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value is not null)
|
|
||||||
{
|
|
||||||
startInfo.ArgumentList.Add(NormalizeKey(key));
|
|
||||||
startInfo.ArgumentList.Add(value.ToString()!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process? process = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process = Process.Start(startInfo);
|
|
||||||
if (process is null)
|
|
||||||
{
|
|
||||||
return $"Error: Failed to start process for script '{script.Name}'.";
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<string> outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
|
|
||||||
Task<string> errorTask = process.StandardError.ReadToEndAsync(cancellationToken);
|
|
||||||
|
|
||||||
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
string output = await outputTask.ConfigureAwait(false);
|
|
||||||
string error = await errorTask.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(error))
|
|
||||||
{
|
|
||||||
output += $"\nStderr:\n{error}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.ExitCode != 0)
|
|
||||||
{
|
|
||||||
output += $"\nScript exited with code {process.ExitCode}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.IsNullOrEmpty(output) ? "(no output)" : output.Trim();
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// Kill the process on cancellation to avoid leaving orphaned subprocesses.
|
|
||||||
process?.Kill(entireProcessTree: true);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return $"Error: Failed to execute script '{script.Name}': {ex.Message}";
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
process?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Normalizes a parameter key to a consistent --flag format.
|
|
||||||
/// Models may return keys with or without leading dashes (e.g., "value" vs "--value").
|
|
||||||
/// </summary>
|
|
||||||
private static string NormalizeKey(string key) => "--" + key.TrimStart('-');
|
|
||||||
}
|
|
||||||
+10
-3
@@ -5,13 +5,20 @@
|
|||||||
using Anthropic;
|
using Anthropic;
|
||||||
using Anthropic.Core;
|
using Anthropic.Core;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
|
using Microsoft.Extensions.AI;
|
||||||
|
|
||||||
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
|
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
|
||||||
var model = Environment.GetEnvironmentVariable("ANTHROPIC_CHAT_MODEL_NAME") ?? "claude-haiku-4-5";
|
var model = Environment.GetEnvironmentVariable("ANTHROPIC_CHAT_MODEL_NAME") ?? "claude-haiku-4-5";
|
||||||
|
|
||||||
AIAgent agent =
|
AIAgent agent = new AnthropicClient(new ClientOptions { ApiKey = apiKey })
|
||||||
new AnthropicClient(new ClientOptions { ApiKey = apiKey })
|
|
||||||
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
||||||
|
|
||||||
// Invoke the agent and output the text result.
|
// Invoke the agent and output the text result.
|
||||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
var response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||||
|
Console.WriteLine(response);
|
||||||
|
|
||||||
|
// Invoke the agent with streaming support.
|
||||||
|
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||||
|
{
|
||||||
|
Console.WriteLine(update);
|
||||||
|
}
|
||||||
|
|||||||
+3
-11
@@ -11,7 +11,6 @@ using System.Text.Json;
|
|||||||
using Azure.AI.Projects;
|
using Azure.AI.Projects;
|
||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
using Microsoft.Agents.AI.AzureAI;
|
|
||||||
using Microsoft.Agents.AI.FoundryMemory;
|
using Microsoft.Agents.AI.FoundryMemory;
|
||||||
|
|
||||||
string foundryEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
string foundryEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
||||||
@@ -20,9 +19,6 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLO
|
|||||||
string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
|
string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
|
||||||
|
|
||||||
// Create an AIProjectClient for Foundry with Azure Identity authentication.
|
// Create an AIProjectClient for Foundry with Azure Identity authentication.
|
||||||
// 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.
|
|
||||||
DefaultAzureCredential credential = new();
|
DefaultAzureCredential credential = new();
|
||||||
AIProjectClient projectClient = new(new Uri(foundryEndpoint), credential);
|
AIProjectClient projectClient = new(new Uri(foundryEndpoint), credential);
|
||||||
|
|
||||||
@@ -37,15 +33,11 @@ FoundryMemoryProvider memoryProvider = new(
|
|||||||
memoryStoreName,
|
memoryStoreName,
|
||||||
stateInitializer: _ => new(new FoundryMemoryProviderScope("sample-user-123")));
|
stateInitializer: _ => new(new FoundryMemoryProviderScope("sample-user-123")));
|
||||||
|
|
||||||
FoundryAgent agent = projectClient.AsAIAgent(
|
AIAgent agent = await projectClient.CreateAIAgentAsync(deploymentName,
|
||||||
new ChatClientAgentOptions()
|
options: new ChatClientAgentOptions()
|
||||||
{
|
{
|
||||||
Name = "TravelAssistantWithFoundryMemory",
|
Name = "TravelAssistantWithFoundryMemory",
|
||||||
ChatOptions = new()
|
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
|
||||||
{
|
|
||||||
ModelId = deploymentName,
|
|
||||||
Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details."
|
|
||||||
},
|
|
||||||
AIContextProviders = [memoryProvider]
|
AIContextProviders = [memoryProvider]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Agent Framework Retrieval Augmented Generation (RAG)
|
# Agent Framework Retrieval Augmented Generation (RAG)
|
||||||
|
|
||||||
These samples show how to create an agent with the Agent Framework that uses Memory to remember previous conversations or facts from previous conversations.
|
These samples show how to create an agent with the Agent Framework that uses Memory to remember previous conversations or facts from previous conversations.
|
||||||
|
|
||||||
@@ -10,4 +10,4 @@ These samples show how to create an agent with the Agent Framework that uses Mem
|
|||||||
|[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 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.|
|
||||||
|[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.|
|
|[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](../FoundryAgents/FoundryAgents_Step22_MemorySearch/) - demonstrates using the built-in Memory Search tool with Azure Foundry Agents.
|
||||||
|
|||||||
@@ -4,14 +4,28 @@
|
|||||||
|
|
||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
using OpenAI.Responses;
|
using OpenAI;
|
||||||
|
using OpenAI.Chat;
|
||||||
|
|
||||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
|
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
|
||||||
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
|
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
|
||||||
|
|
||||||
AIAgent agent =
|
AIAgent agent = new OpenAIClient(apiKey)
|
||||||
new ResponsesClient(new ApiKeyCredential(apiKey))
|
.GetChatClient(model)
|
||||||
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||||
|
|
||||||
// Once you have the agent, you can invoke it like any other AIAgent.
|
UserChatMessage chatMessage = new("Tell me a joke about a pirate.");
|
||||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
|
||||||
|
// Invoke the agent and output the text result.
|
||||||
|
ChatCompletion chatCompletion = await agent.RunAsync([chatMessage]);
|
||||||
|
Console.WriteLine(chatCompletion.Content.Last().Text);
|
||||||
|
|
||||||
|
// Invoke the agent with streaming support.
|
||||||
|
AsyncCollectionResult<StreamingChatCompletionUpdate> completionUpdates = agent.RunStreamingAsync([chatMessage]);
|
||||||
|
await foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)
|
||||||
|
{
|
||||||
|
if (completionUpdate.ContentUpdate.Count > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine(completionUpdate.ContentUpdate[0].Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+4
-16
@@ -73,28 +73,16 @@ foreach (ClientResult result in getConversationItemsResults.GetRawPages())
|
|||||||
using JsonDocument getConversationItemsResultAsJson = JsonDocument.Parse(result.GetRawResponse().Content.ToString());
|
using JsonDocument getConversationItemsResultAsJson = JsonDocument.Parse(result.GetRawResponse().Content.ToString());
|
||||||
foreach (JsonElement element in getConversationItemsResultAsJson.RootElement.GetProperty("data").EnumerateArray())
|
foreach (JsonElement element in getConversationItemsResultAsJson.RootElement.GetProperty("data").EnumerateArray())
|
||||||
{
|
{
|
||||||
// Skip non-message items (e.g. tool calls, reasoning) that lack a "role" property
|
|
||||||
if (!element.TryGetProperty("role"u8, out var roleElement))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string messageId = element.GetProperty("id"u8).ToString();
|
string messageId = element.GetProperty("id"u8).ToString();
|
||||||
string messageRole = roleElement.ToString();
|
string messageRole = element.GetProperty("role"u8).ToString();
|
||||||
Console.WriteLine($" Message ID: {messageId}");
|
Console.WriteLine($" Message ID: {messageId}");
|
||||||
Console.WriteLine($" Message Role: {messageRole}");
|
Console.WriteLine($" Message Role: {messageRole}");
|
||||||
|
|
||||||
if (element.TryGetProperty("content"u8, out var contentElement))
|
foreach (var content in element.GetProperty("content").EnumerateArray())
|
||||||
{
|
{
|
||||||
foreach (var content in contentElement.EnumerateArray())
|
string messageContentText = content.GetProperty("text"u8).ToString();
|
||||||
{
|
Console.WriteLine($" Message Text: {messageContentText}");
|
||||||
if (content.TryGetProperty("text"u8, out var textElement))
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Message Text: {textElement}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ using Qdrant.Client;
|
|||||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
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 deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||||
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
|
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
|
||||||
var afOverviewUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/overview/index.md";
|
var afOverviewUrl = "https://github.com/MicrosoftDocs/semantic-kernel-docs/blob/main/agent-framework/overview/agent-framework-overview.md";
|
||||||
var afMigrationUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/migration-guide/from-semantic-kernel/index.md";
|
var afMigrationUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/migration-guide/from-semantic-kernel/index.md";
|
||||||
|
|
||||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||||
|
|||||||
+8
-16
@@ -4,13 +4,11 @@
|
|||||||
|
|
||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using Azure.AI.Projects;
|
using Azure.AI.Projects;
|
||||||
using Azure.AI.Projects.Agents;
|
|
||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
using Microsoft.Agents.AI.AzureAI;
|
using Microsoft.Extensions.AI;
|
||||||
using OpenAI;
|
using OpenAI;
|
||||||
using OpenAI.Files;
|
using OpenAI.Files;
|
||||||
using OpenAI.Responses;
|
|
||||||
using OpenAI.VectorStores;
|
using OpenAI.VectorStores;
|
||||||
|
|
||||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
||||||
@@ -39,20 +37,14 @@ ClientResult<VectorStore> vectorStoreCreate = await vectorStoreClient.CreateVect
|
|||||||
FileIds = { uploadResult.Value.Id }
|
FileIds = { uploadResult.Value.Id }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use the native OpenAI SDK FileSearchTool directly with the vector store ID.
|
var fileSearchTool = new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreCreate.Value.Id)] };
|
||||||
#pragma warning disable OPENAI001
|
|
||||||
FileSearchTool fileSearchTool = new([vectorStoreCreate.Value.Id]);
|
|
||||||
#pragma warning restore OPENAI001
|
|
||||||
|
|
||||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
AIAgent agent = await aiProjectClient
|
||||||
"AskContoso",
|
.CreateAIAgentAsync(
|
||||||
new AgentVersionCreationOptions(
|
model: deploymentName,
|
||||||
new PromptAgentDefinition(model: deploymentName)
|
name: "AskContoso",
|
||||||
{
|
instructions: "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
|
||||||
Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
|
tools: [fileSearchTool]);
|
||||||
Tools = { fileSearchTool }
|
|
||||||
}));
|
|
||||||
FoundryAgent agent = aiProjectClient.AsAIAgent(agentVersion);
|
|
||||||
|
|
||||||
AgentSession session = await agent.CreateSessionAsync();
|
AgentSession session = await agent.CreateSessionAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// This sample shows how to expose an AI agent as an MCP tool.
|
// This sample shows how to expose an AI agent as an MCP tool.
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
using Azure.AI.Projects;
|
||||||
using Azure.AI.Projects.Agents;
|
|
||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -19,17 +18,11 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYME
|
|||||||
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||||
|
|
||||||
// Create a server side agent and expose it as an AIAgent.
|
// Create a server side agent and expose it as an AIAgent.
|
||||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
AIAgent agent = await aiProjectClient.CreateAIAgentAsync(
|
||||||
"Joker",
|
model: deploymentName,
|
||||||
new AgentVersionCreationOptions(
|
instructions: "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.",
|
||||||
new PromptAgentDefinition(model: deploymentName)
|
name: "Joker",
|
||||||
{
|
description: "An agent that tells jokes.");
|
||||||
Instructions = "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.",
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Description = "An agent that tells jokes.",
|
|
||||||
});
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(agentVersion);
|
|
||||||
|
|
||||||
// Convert the agent to an AIFunction and then to an MCP tool.
|
// Convert the agent to an AIFunction and then to an MCP tool.
|
||||||
// The agent name and description will be used as the mcp tool name and description.
|
// The agent name and description will be used as the mcp tool name and description.
|
||||||
|
|||||||
-6
@@ -16,11 +16,5 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Assets\walkway.jpg">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ var agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential(
|
|||||||
|
|
||||||
ChatMessage message = new(ChatRole.User, [
|
ChatMessage message = new(ChatRole.User, [
|
||||||
new TextContent("What do you see in this image?"),
|
new TextContent("What do you see in this image?"),
|
||||||
await DataContent.LoadFromAsync("Assets/walkway.jpg"),
|
new UriContent("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", "image/jpeg")
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var session = await agent.CreateSessionAsync();
|
var session = await agent.CreateSessionAsync();
|
||||||
|
|||||||
@@ -189,9 +189,9 @@ async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, Agent
|
|||||||
// Regex patterns for PII detection (simplified for demonstration)
|
// Regex patterns for PII detection (simplified for demonstration)
|
||||||
Regex[] piiPatterns =
|
Regex[] piiPatterns =
|
||||||
[
|
[
|
||||||
MyRegex(), // Phone number (e.g., 123-456-7890)
|
new(@"\b\d{3}-\d{3}-\d{4}\b", RegexOptions.Compiled), // Phone number (e.g., 123-456-7890)
|
||||||
EmailRegex(), // Email address
|
new(@"\b[\w\.-]+@[\w\.-]+\.\w+\b", RegexOptions.Compiled), // Email address
|
||||||
FullNameRegex() // Full name (e.g., John Doe)
|
new(@"\b[A-Z][a-z]+\s[A-Z][a-z]+\b", RegexOptions.Compiled) // Full name (e.g., John Doe)
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (var pattern in piiPatterns)
|
foreach (var pattern in piiPatterns)
|
||||||
@@ -309,15 +309,3 @@ internal sealed class DateTimeContextProvider : MessageAIContextProvider
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class Program
|
|
||||||
{
|
|
||||||
[GeneratedRegex(@"\b\d{3}-\d{3}-\d{4}\b", RegexOptions.Compiled)]
|
|
||||||
private static partial Regex MyRegex();
|
|
||||||
|
|
||||||
[GeneratedRegex(@"\b[\w\.-]+@[\w\.-]+\.\w+\b", RegexOptions.Compiled)]
|
|
||||||
private static partial Regex EmailRegex();
|
|
||||||
|
|
||||||
[GeneratedRegex(@"\b[A-Z][a-z]+\s[A-Z][a-z]+\b", RegexOptions.Compiled)]
|
|
||||||
private static partial Regex FullNameRegex();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ var bingConnectionId = Environment.GetEnvironmentVariable("AZURE_AI_BING_CONNECT
|
|||||||
PersistentAgentsAdministrationClientOptions persistentAgentsClientOptions = new();
|
PersistentAgentsAdministrationClientOptions persistentAgentsClientOptions = new();
|
||||||
persistentAgentsClientOptions.Retry.NetworkTimeout = TimeSpan.FromMinutes(20);
|
persistentAgentsClientOptions.Retry.NetworkTimeout = TimeSpan.FromMinutes(20);
|
||||||
|
|
||||||
|
// Get a client to create/retrieve server side agents with.
|
||||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||||
// Get a client to create/retrieve server side agents with.
|
|
||||||
PersistentAgentsClient persistentAgentsClient = new(endpoint, new DefaultAzureCredential(), persistentAgentsClientOptions);
|
PersistentAgentsClient persistentAgentsClient = new(endpoint, new DefaultAzureCredential(), persistentAgentsClientOptions);
|
||||||
|
|
||||||
// Define and configure the Deep Research tool.
|
// Define and configure the Deep Research tool.
|
||||||
|
|||||||
@@ -23,14 +23,12 @@ Before running this sample, ensure you have:
|
|||||||
|
|
||||||
Pay special attention to the purple `Note` boxes in the Azure documentation.
|
Pay special attention to the purple `Note` boxes in the Azure documentation.
|
||||||
|
|
||||||
**Note**: The Bing Grounding Connection ID must be the **full ARM resource URI** from the project, not just the connection name. It has the following format:
|
**Note**: The Bing Connection ID must be from the **project**, not the resource. It has the following format:
|
||||||
|
|
||||||
```
|
```
|
||||||
/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/projects/<project>/connections/<connection-name>
|
/subscriptions/<sub_id>/resourceGroups/<rg_name>/providers/<provider_name>/accounts/<account_name>/projects/<project_name>/connections/<connection_name>
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find this in the Azure AI Foundry portal under **Management > Connected resources**, or retrieve it programmatically via the connections API (`.id` property).
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
Set the following environment variables:
|
Set the following environment variables:
|
||||||
@@ -39,8 +37,8 @@ Set the following environment variables:
|
|||||||
# Replace with your Azure AI Foundry project endpoint
|
# Replace with your Azure AI Foundry project endpoint
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/"
|
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/"
|
||||||
|
|
||||||
# Replace with your Bing Grounding connection ID (full ARM resource URI)
|
# Replace with your Bing connection ID from the project
|
||||||
$env:AZURE_AI_BING_CONNECTION_ID="/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/projects/<project>/connections/<connection-name>"
|
$env:AZURE_AI_BING_CONNECTION_ID="/subscriptions/.../connections/your-bing-connection"
|
||||||
|
|
||||||
# Optional, defaults to o3-deep-research
|
# Optional, defaults to o3-deep-research
|
||||||
$env:AZURE_AI_REASONING_DEPLOYMENT_NAME="o3-deep-research"
|
$env:AZURE_AI_REASONING_DEPLOYMENT_NAME="o3-deep-research"
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
|
|||||||
Func<Task<string[]>> loadNextThreeCalendarEvents = async () =>
|
Func<Task<string[]>> loadNextThreeCalendarEvents = async () =>
|
||||||
{
|
{
|
||||||
// In a real implementation, this method would connect to a calendar service
|
// In a real implementation, this method would connect to a calendar service
|
||||||
return
|
return new string[]
|
||||||
[
|
{
|
||||||
"Doctor's appointment today at 15:00",
|
"Doctor's appointment today at 15:00",
|
||||||
"Team meeting today at 17:00",
|
"Team meeting today at 17:00",
|
||||||
"Birthday party today at 20:00"
|
"Birthday party today at 20:00"
|
||||||
];
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create an agent with an AI context provider attached that aggregates two other providers:
|
// Create an agent with an AI context provider attached that aggregates two other providers:
|
||||||
@@ -87,7 +87,7 @@ namespace SampleApp
|
|||||||
internal sealed class TodoListAIContextProvider : AIContextProvider
|
internal sealed class TodoListAIContextProvider : AIContextProvider
|
||||||
{
|
{
|
||||||
private static List<string> GetTodoItems(AgentSession? session)
|
private static List<string> GetTodoItems(AgentSession? session)
|
||||||
=> session?.StateBag.GetValue<List<string>>(nameof(TodoListAIContextProvider)) ?? [];
|
=> session?.StateBag.GetValue<List<string>>(nameof(TodoListAIContextProvider)) ?? new List<string>();
|
||||||
|
|
||||||
private static void SetTodoItems(AgentSession? session, List<string> items)
|
private static void SetTodoItems(AgentSession? session, List<string> items)
|
||||||
=> session?.StateBag.SetValue(nameof(TodoListAIContextProvider), items);
|
=> session?.StateBag.SetValue(nameof(TodoListAIContextProvider), items);
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample demonstrates how the ChatClientAgent persists chat history after each individual
|
|
||||||
// call to the AI service, using the RequirePerServiceCallChatHistoryPersistence option.
|
|
||||||
// When an agent uses tools, FunctionInvokingChatClient may loop multiple times
|
|
||||||
// (service call → tool execution → service call), and intermediate messages (tool calls and
|
|
||||||
// results) are persisted after each service call. This allows you to inspect or recover them
|
|
||||||
// even if the process is interrupted mid-loop, but may also result in chat history that is not
|
|
||||||
// yet finalized (e.g., tool calls without results) being persisted, which may be undesirable in some cases.
|
|
||||||
//
|
|
||||||
// To use end-of-run persistence instead (atomic run semantics), remove the
|
|
||||||
// RequirePerServiceCallChatHistoryPersistence = true setting (or set it to false). End-of-run
|
|
||||||
// persistence is the default behavior.
|
|
||||||
//
|
|
||||||
// The sample runs two multi-turn conversations: one using non-streaming (RunAsync) and one
|
|
||||||
// using streaming (RunStreamingAsync), to demonstrate correct behavior in both modes.
|
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Azure.AI.OpenAI;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
using OpenAI.Responses;
|
|
||||||
|
|
||||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
|
||||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
|
||||||
var store = Environment.GetEnvironmentVariable("AZURE_OPENAI_RESPONSES_STORE") ?? "false";
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AzureOpenAIClient openAIClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
// Define multiple tools so the model makes several tool calls in a single run.
|
|
||||||
[Description("Get the current weather for a city.")]
|
|
||||||
static string GetWeather([Description("The city name.")] string city) =>
|
|
||||||
city.ToUpperInvariant() switch
|
|
||||||
{
|
|
||||||
"SEATTLE" => "Seattle: 55°F, cloudy with light rain.",
|
|
||||||
"NEW YORK" => "New York: 72°F, sunny and warm.",
|
|
||||||
"LONDON" => "London: 48°F, overcast with fog.",
|
|
||||||
"DUBLIN" => "Dublin: 43°F, overcast with fog.",
|
|
||||||
_ => $"{city}: weather data not available."
|
|
||||||
};
|
|
||||||
|
|
||||||
[Description("Get the current time in a city.")]
|
|
||||||
static string GetTime([Description("The city name.")] string city) =>
|
|
||||||
city.ToUpperInvariant() switch
|
|
||||||
{
|
|
||||||
"SEATTLE" => "Seattle: 9:00 AM PST",
|
|
||||||
"NEW YORK" => "New York: 12:00 PM EST",
|
|
||||||
"LONDON" => "London: 5:00 PM GMT",
|
|
||||||
"DUBLIN" => "Dublin: 5:00 PM GMT",
|
|
||||||
_ => $"{city}: time data not available."
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the agent — per-service-call persistence is enabled via RequirePerServiceCallChatHistoryPersistence.
|
|
||||||
// The in-memory ChatHistoryProvider is used by default when the service does not require service stored chat
|
|
||||||
// history, so for those cases, we can inspect the chat history via session.TryGetInMemoryChatHistory().
|
|
||||||
IChatClient chatClient = string.Equals(store, "TRUE", StringComparison.OrdinalIgnoreCase) ?
|
|
||||||
openAIClient.GetResponsesClient().AsIChatClient(deploymentName) :
|
|
||||||
openAIClient.GetResponsesClient().AsIChatClientWithStoredOutputDisabled(deploymentName);
|
|
||||||
AIAgent agent = chatClient.AsAIAgent(
|
|
||||||
new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
Name = "WeatherAssistant",
|
|
||||||
RequirePerServiceCallChatHistoryPersistence = true,
|
|
||||||
ChatOptions = new()
|
|
||||||
{
|
|
||||||
Instructions = "You are a helpful assistant. When asked about multiple cities, call the appropriate tool for each city.",
|
|
||||||
Tools = [AIFunctionFactory.Create(GetWeather), AIFunctionFactory.Create(GetTime)]
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await RunNonStreamingAsync();
|
|
||||||
await RunStreamingAsync();
|
|
||||||
|
|
||||||
async Task RunNonStreamingAsync()
|
|
||||||
{
|
|
||||||
int lastChatHistorySize = 0;
|
|
||||||
string lastConversationId = string.Empty;
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.WriteLine("\n=== Non-Streaming Mode ===");
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
AgentSession session = await agent.CreateSessionAsync();
|
|
||||||
|
|
||||||
// First turn — ask about multiple cities so the model calls tools.
|
|
||||||
const string Prompt = "What's the weather and time in Seattle, New York, and London?";
|
|
||||||
PrintUserMessage(Prompt);
|
|
||||||
|
|
||||||
var response = await agent.RunAsync(Prompt, session);
|
|
||||||
PrintAgentResponse(response.Text);
|
|
||||||
PrintChatHistory(session, "After run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
|
|
||||||
// Second turn — follow-up to verify chat history is correct.
|
|
||||||
const string FollowUp1 = "And Dublin?";
|
|
||||||
PrintUserMessage(FollowUp1);
|
|
||||||
|
|
||||||
response = await agent.RunAsync(FollowUp1, session);
|
|
||||||
PrintAgentResponse(response.Text);
|
|
||||||
PrintChatHistory(session, "After second run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
|
|
||||||
// Third turn — follow-up to verify chat history is correct.
|
|
||||||
const string FollowUp2 = "Which city is the warmest?";
|
|
||||||
PrintUserMessage(FollowUp2);
|
|
||||||
|
|
||||||
response = await agent.RunAsync(FollowUp2, session);
|
|
||||||
PrintAgentResponse(response.Text);
|
|
||||||
PrintChatHistory(session, "After third run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task RunStreamingAsync()
|
|
||||||
{
|
|
||||||
int lastChatHistorySize = 0;
|
|
||||||
string lastConversationId = string.Empty;
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.WriteLine("\n=== Streaming Mode ===");
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
AgentSession session = await agent.CreateSessionAsync();
|
|
||||||
|
|
||||||
// First turn — ask about multiple cities so the model calls tools.
|
|
||||||
const string Prompt = "What's the weather and time in Seattle, New York, and London?";
|
|
||||||
PrintUserMessage(Prompt);
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
||||||
Console.Write("\n[Agent] ");
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
await foreach (var update in agent.RunStreamingAsync(Prompt, session))
|
|
||||||
{
|
|
||||||
Console.Write(update);
|
|
||||||
|
|
||||||
// During streaming we should be able to see updates to the chat history
|
|
||||||
// before the full run completes, as each service call is made and persisted.
|
|
||||||
PrintChatHistory(session, "During run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
PrintChatHistory(session, "After run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
|
|
||||||
// Second turn — follow-up to verify chat history is correct.
|
|
||||||
const string FollowUp1 = "And Dublin?";
|
|
||||||
PrintUserMessage(FollowUp1);
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
||||||
Console.Write("\n[Agent] ");
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
await foreach (var update in agent.RunStreamingAsync(FollowUp1, session))
|
|
||||||
{
|
|
||||||
Console.Write(update);
|
|
||||||
|
|
||||||
// During streaming we should be able to see updates to the chat history
|
|
||||||
// before the full run completes, as each service call is made and persisted.
|
|
||||||
PrintChatHistory(session, "During second run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
PrintChatHistory(session, "After second run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
|
|
||||||
// Third turn — follow-up to verify chat history is correct.
|
|
||||||
const string FollowUp2 = "Which city is the warmest?";
|
|
||||||
PrintUserMessage(FollowUp2);
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
||||||
Console.Write("\n[Agent] ");
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
await foreach (var update in agent.RunStreamingAsync(FollowUp2, session))
|
|
||||||
{
|
|
||||||
Console.Write(update);
|
|
||||||
|
|
||||||
// During streaming we should be able to see updates to the chat history
|
|
||||||
// before the full run completes, as each service call is made and persisted.
|
|
||||||
PrintChatHistory(session, "During third run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
PrintChatHistory(session, "After third run", ref lastChatHistorySize, ref lastConversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintUserMessage(string message)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
||||||
Console.Write("\n[User] ");
|
|
||||||
Console.ResetColor();
|
|
||||||
Console.WriteLine(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintAgentResponse(string? text)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
||||||
Console.Write("\n[Agent] ");
|
|
||||||
Console.ResetColor();
|
|
||||||
Console.WriteLine(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to print the current chat history from the session.
|
|
||||||
void PrintChatHistory(AgentSession session, string label, ref int lastChatHistorySize, ref string lastConversationId)
|
|
||||||
{
|
|
||||||
if (session.TryGetInMemoryChatHistory(out var history) && history.Count != lastChatHistorySize)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
|
||||||
Console.WriteLine($"\n [{label} — Chat history: {history.Count} message(s)]");
|
|
||||||
foreach (var msg in history)
|
|
||||||
{
|
|
||||||
var preview = msg.Text?.Length > 80 ? msg.Text[..80] + "…" : msg.Text;
|
|
||||||
var contentTypes = string.Join(", ", msg.Contents.Select(c => c.GetType().Name));
|
|
||||||
Console.WriteLine($" {msg.Role,-12} | {(string.IsNullOrWhiteSpace(preview) ? $"[{contentTypes}]" : preview)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
lastChatHistorySize = history.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session is ChatClientAgentSession ccaSession && ccaSession.ConversationId is not null && ccaSession.ConversationId != lastConversationId)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
|
||||||
Console.WriteLine($" [{label} — Conversation ID: {ccaSession.ConversationId}]");
|
|
||||||
Console.ResetColor();
|
|
||||||
lastConversationId = ccaSession.ConversationId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# In-Function-Loop Checkpointing
|
|
||||||
|
|
||||||
This sample demonstrates how `ChatClientAgent` can persist chat history after each individual call to the AI service using the `RequirePerServiceCallChatHistoryPersistence` option. This per-service-call persistence ensures intermediate progress is saved during the function invocation loop.
|
|
||||||
|
|
||||||
## What This Sample Shows
|
|
||||||
|
|
||||||
When an agent uses tools, the `FunctionInvokingChatClient` loops multiple times (service call → tool execution → service call → …). By enabling `RequirePerServiceCallChatHistoryPersistence = true`, chat history is persisted after each service call via the `PerServiceCallChatHistoryPersistingChatClient` decorator:
|
|
||||||
|
|
||||||
- A `PerServiceCallChatHistoryPersistingChatClient` decorator is inserted into the chat client pipeline
|
|
||||||
- Before each service call, the decorator loads history from the `ChatHistoryProvider` and prepends it to the request
|
|
||||||
- After each service call, the decorator notifies the `ChatHistoryProvider` (and any `AIContextProvider` instances) with the new messages
|
|
||||||
- Only **new** messages are sent to providers on each notification — messages that were already persisted in an earlier call within the same run are deduplicated automatically
|
|
||||||
|
|
||||||
By default (without `RequirePerServiceCallChatHistoryPersistence`), chat history is persisted at the end of the full agent run instead. To use per-service-call persistence, set `RequirePerServiceCallChatHistoryPersistence = true` on `ChatClientAgentOptions`.
|
|
||||||
|
|
||||||
With `RequirePerServiceCallChatHistoryPersistence` = true, the behavior matches that of chat history stored in the underlying AI service exactly.
|
|
||||||
|
|
||||||
Per-service-call persistence is useful for:
|
|
||||||
- **Crash recovery** — if the process is interrupted mid-loop, the intermediate tool calls and results are already persisted
|
|
||||||
- **Observability** — you can inspect the chat history while the agent is still running (e.g., during streaming)
|
|
||||||
- **Long-running tool loops** — agents with many sequential tool calls benefit from incremental persistence
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The sample asks the agent about the weather and time in three cities. The model calls the `GetWeather` and `GetTime` tools for each city, resulting in multiple service calls within a single `RunStreamingAsync` invocation. After the run completes, the sample prints the full chat history to show all the intermediate messages that were persisted along the way.
|
|
||||||
|
|
||||||
### Pipeline Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
ChatClientAgent
|
|
||||||
└─ FunctionInvokingChatClient (handles tool call loop)
|
|
||||||
└─ PerServiceCallChatHistoryPersistingChatClient (persists after each service call)
|
|
||||||
└─ Leaf IChatClient (Azure OpenAI)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Azure OpenAI service endpoint and model deployment
|
|
||||||
- Azure CLI installed and authenticated
|
|
||||||
|
|
||||||
**Note**: This sample uses `DefaultAzureCredential`. Sign in with `az login` before running. For production, prefer a specific credential such as `ManagedIdentityCredential`. For more information, see the [Azure CLI authentication documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Required
|
|
||||||
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running the Sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/Agents/Agent_Step19_InFunctionLoopCheckpointing
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
|
|
||||||
The sample runs two conversation turns:
|
|
||||||
|
|
||||||
1. **First turn** — asks about weather and time in three cities. The model calls `GetWeather` and `GetTime` tools (potentially in parallel or sequentially), then provides a summary. The chat history dump after the run shows all the intermediate tool call and result messages.
|
|
||||||
|
|
||||||
2. **Second turn** — asks a follow-up question ("Which city is the warmest?") that uses the persisted conversation context. The chat history dump shows the full accumulated conversation.
|
|
||||||
|
|
||||||
The chat history printout uses `session.TryGetInMemoryChatHistory()` to inspect the in-memory storage.
|
|
||||||
@@ -45,7 +45,6 @@ Before you begin, ensure you have the following prerequisites:
|
|||||||
|[Declarative agent](./Agent_Step16_Declarative/)|This sample demonstrates how to declaratively define an agent.|
|
|[Declarative agent](./Agent_Step16_Declarative/)|This sample demonstrates how to declaratively define an agent.|
|
||||||
|[Providing additional AI Context to an agent using multiple AIContextProviders](./Agent_Step17_AdditionalAIContext/)|This sample demonstrates how to inject additional AI context into a ChatClientAgent using multiple custom AIContextProvider components that are attached to the agent.|
|
|[Providing additional AI Context to an agent using multiple AIContextProviders](./Agent_Step17_AdditionalAIContext/)|This sample demonstrates how to inject additional AI context into a ChatClientAgent using multiple custom AIContextProvider components that are attached to the agent.|
|
||||||
|[Using compaction pipeline with an agent](./Agent_Step18_CompactionPipeline/)|This sample demonstrates how to use a compaction pipeline to efficiently limit the size of the conversation history for an agent.|
|
|[Using compaction pipeline with an agent](./Agent_Step18_CompactionPipeline/)|This sample demonstrates how to use a compaction pipeline to efficiently limit the size of the conversation history for an agent.|
|
||||||
|[In-function-loop checkpointing](./Agent_Step19_InFunctionLoopCheckpointing/)|This sample demonstrates how to persist chat history after each service call during a tool-calling loop, enabling crash recovery and mid-run observability.|
|
|
||||||
|
|
||||||
## Running the samples from the console
|
## Running the samples from the console
|
||||||
|
|
||||||
|
|||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
// 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:
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
const string JokerName = "JokerAgent";
|
|
||||||
|
|
||||||
// Create the AIProjectClient to manage server-side agents.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential());
|
|
||||||
|
|
||||||
// Create a server-side agent version using the native SDK.
|
|
||||||
AgentVersion agentVersion = await aiProjectClient.Agents.CreateAgentVersionAsync(
|
|
||||||
JokerName,
|
|
||||||
new AgentVersionCreationOptions(
|
|
||||||
new PromptAgentDefinition(model: deploymentName)
|
|
||||||
{
|
|
||||||
Instructions = "You are good at telling jokes.",
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Wrap the agent version as a FoundryAgent using the AsAIAgent extension.
|
|
||||||
FoundryAgent agent = aiProjectClient.AsAIAgent(agentVersion);
|
|
||||||
|
|
||||||
// Once you have the agent, you can invoke it like any other AIAgent.
|
|
||||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
|
||||||
|
|
||||||
// Cleanup: deletes the agent and all its versions.
|
|
||||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
# Agent Step 00 - FoundryAgent Lifecycle
|
|
||||||
|
|
||||||
This sample demonstrates the full lifecycle of a `FoundryAgent` backed by a server-side versioned agent in Microsoft Foundry: create → run → delete.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- A Microsoft Foundry project endpoint
|
|
||||||
- A model deployment name (defaults to `gpt-4o-mini`)
|
|
||||||
- Azure CLI installed and authenticated
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
| Variable | Description | Required |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `AZURE_AI_PROJECT_ENDPOINT` | Microsoft Foundry project endpoint | Yes |
|
|
||||||
| `AZURE_AI_MODEL_DEPLOYMENT_NAME` | Model deployment name | No (defaults to `gpt-4o-mini`) |
|
|
||||||
|
|
||||||
## Running the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step00_FoundryAgentLifecycle
|
|
||||||
```
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to create and run a basic agent with AIProjectClient.AsAIAgent(...).
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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 AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
|
||||||
.AsAIAgent(model: deploymentName, instructions: "You are good at telling jokes.", name: "JokerAgent");
|
|
||||||
|
|
||||||
// Once you have the agent, you can invoke it like any other AIAgent.
|
|
||||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
# Creating and Running a Basic Agent with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to create and run a basic AI agent using the `ChatClientAgent`, which uses the Microsoft Foundry Responses API directly without creating server-side agent definitions.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating a `ChatClientAgent` with instructions and a model
|
|
||||||
- Running a simple single-turn conversation
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before you begin, ensure you have the following prerequisites:
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- 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 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"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
Navigate to the AgentsWithFoundry sample directory and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step01_Basics
|
|
||||||
```
|
|
||||||
|
|
||||||
## Alternative: Composable approach
|
|
||||||
|
|
||||||
You can also create the same agent by composing the underlying `IChatClient` directly. This gives you full control over the chat client pipeline:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
AIAgent agent = new ChatClientAgent(
|
|
||||||
chatClient: aiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClient().AsIChatClient(deploymentName),
|
|
||||||
instructions: "You are good at telling jokes.",
|
|
||||||
name: "JokerAgent");
|
|
||||||
```
|
|
||||||
|
|
||||||
This approach is useful when you need to customize the chat client pipeline or swap providers (e.g., Anthropic, OpenAI) while keeping the same agent code.
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to create a multi-turn conversation agent using sessions.
|
|
||||||
// Context is preserved across multiple runs via response ID chaining in the session.
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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 AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
|
||||||
.AsAIAgent(deploymentName, instructions: "You are good at telling jokes.", name: "JokerAgent");
|
|
||||||
|
|
||||||
// Create a session to maintain context across multiple runs.
|
|
||||||
AgentSession session = await agent.CreateSessionAsync();
|
|
||||||
|
|
||||||
// First turn
|
|
||||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
|
||||||
|
|
||||||
// Second turn — the agent remembers the first turn via the 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));
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
# Multi-turn Conversation
|
|
||||||
|
|
||||||
This sample demonstrates how to implement multi-turn conversations where context is preserved across multiple agent runs using sessions and response ID chaining.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating an agent with instructions
|
|
||||||
- Using sessions to maintain conversation context across multiple runs
|
|
||||||
- Response ID chaining for multi-turn conversations
|
|
||||||
- No server-side conversation creation required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before you begin, ensure you have the following prerequisites:
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- 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 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"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
Navigate to the AgentsWithFoundry sample directory and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step02.1_MultiturnConversation
|
|
||||||
```
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to use server-side conversations with a FoundryAgent.
|
|
||||||
// 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.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";
|
|
||||||
|
|
||||||
// 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())
|
|
||||||
.AsAIAgent(deploymentName, instructions: "You are good at telling jokes.", name: "JokerAgent");
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
// Streaming with server-side conversation context.
|
|
||||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Tell me another joke, but about a ninja this time.", session))
|
|
||||||
{
|
|
||||||
Console.Write(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
# Multi-turn Conversation with Server-Side Conversations
|
|
||||||
|
|
||||||
This sample demonstrates how to use server-side conversations with a `FoundryAgent`. Server-side conversations persist on the Foundry service and are visible in the Foundry Project UI, making them ideal when you need conversation history to be stored and accessible server-side.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating a `FoundryAgent` with instructions
|
|
||||||
- Using `CreateConversationSessionAsync` to create a server-side `ProjectConversation`
|
|
||||||
- Multi-turn conversations with both text and streaming output
|
|
||||||
- Server-side conversation persistence visible in the Foundry Project UI
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before you begin, ensure you have the following prerequisites:
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- 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 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"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
Navigate to the AgentsWithFoundry sample directory and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step02.2_MultiturnWithServerConversations
|
|
||||||
```
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample demonstrates how to use function tools.
|
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
|
|
||||||
[Description("Get the weather for a given location.")]
|
|
||||||
static string GetWeather([Description("The location to get the weather for.")] string location)
|
|
||||||
=> $"The weather in {location} is cloudy with a high of 15°C.";
|
|
||||||
|
|
||||||
// Define the function tool.
|
|
||||||
AITool tool = AIFunctionFactory.Create(GetWeather);
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
// Create a AIAgent with function tools.
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName,
|
|
||||||
instructions: "You are a helpful assistant that can get weather information.",
|
|
||||||
name: "WeatherAssistant",
|
|
||||||
tools: [tool]);
|
|
||||||
|
|
||||||
// Non-streaming agent interaction with function tools.
|
|
||||||
AgentSession session = await agent.CreateSessionAsync();
|
|
||||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", session));
|
|
||||||
|
|
||||||
// Streaming agent interaction with function tools.
|
|
||||||
session = await agent.CreateSessionAsync();
|
|
||||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", session))
|
|
||||||
{
|
|
||||||
Console.Write(update);
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Using Function Tools with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to use function tools with the `ChatClientAgent`, allowing the agent to call custom functions to retrieve information.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating function tools using `AIFunctionFactory`
|
|
||||||
- Passing function tools to a `ChatClientAgent`
|
|
||||||
- Running agents with function tools (text output)
|
|
||||||
- Running agents with function tools (streaming output)
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before you begin, ensure you have the following prerequisites:
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- 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 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"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
Navigate to the AgentsWithFoundry sample directory and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step03_UsingFunctionTools
|
|
||||||
```
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
# Using Function Tools with Approvals via the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to use function tools that require human-in-the-loop approval before execution.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating function tools that require approval using `ApprovalRequiredAIFunction`
|
|
||||||
- Handling approval requests from the agent
|
|
||||||
- Passing approval responses back to the agent
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step04_UsingFunctionToolsWithApprovals
|
|
||||||
```
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Structured Output with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to configure an agent to produce structured output using JSON schema.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Using `RunAsync<T>()` to get typed structured output from the agent
|
|
||||||
- Deserializing streamed responses into structured types
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step05_StructuredOutput
|
|
||||||
```
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
# Persisted Conversations with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to persist and resume agent conversations using session serialization.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Serializing agent sessions to JSON for persistence
|
|
||||||
- Saving and loading sessions from disk
|
|
||||||
- Resuming conversations with preserved context
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step06_PersistedConversations
|
|
||||||
```
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Observability with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to add OpenTelemetry observability to an agent using console and Azure Monitor exporters.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Configuring OpenTelemetry tracing with console exporter
|
|
||||||
- Optional Azure Application Insights integration
|
|
||||||
- Using `.AsBuilder().UseOpenTelemetry()` to add telemetry to the agent
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
$env:APPLICATIONINSIGHTS_CONNECTION_STRING="..." # Optional
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step07_Observability
|
|
||||||
```
|
|
||||||
-83
@@ -1,83 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to use dependency injection to register a AIAgent and use it from a hosted service.
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using SampleApp;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName,
|
|
||||||
instructions: "You are good at telling jokes.",
|
|
||||||
name: "JokerAgent");
|
|
||||||
|
|
||||||
// Create a host builder that we will register services with and then run.
|
|
||||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
|
||||||
|
|
||||||
// Add the AI agent to the service collection.
|
|
||||||
builder.Services.AddSingleton(agent);
|
|
||||||
|
|
||||||
// Add a sample service that will use the agent to respond to user input.
|
|
||||||
builder.Services.AddHostedService<SampleService>();
|
|
||||||
|
|
||||||
// Build and run the host.
|
|
||||||
using IHost host = builder.Build();
|
|
||||||
await host.RunAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
namespace SampleApp
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A sample service that uses an AI agent to respond to user input.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class SampleService(AIAgent agent, IHostApplicationLifetime appLifetime) : IHostedService
|
|
||||||
{
|
|
||||||
private AgentSession? _session;
|
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
this._session = await agent.CreateSessionAsync(cancellationToken);
|
|
||||||
_ = this.RunAsync(appLifetime.ApplicationStopping);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await Task.Delay(100, cancellationToken);
|
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Console.WriteLine("\nAgent: Ask me to tell you a joke about a specific topic. To exit just press Ctrl+C or enter without any input.\n");
|
|
||||||
Console.Write("> ");
|
|
||||||
string? input = Console.ReadLine();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
|
||||||
{
|
|
||||||
appLifetime.StopApplication();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, this._session, cancellationToken: cancellationToken))
|
|
||||||
{
|
|
||||||
Console.Write(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
Console.WriteLine("\nShutting down...");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Dependency Injection with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to register a `ChatClientAgent` in a dependency injection container and use it from a hosted service.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Registering `ChatClientAgent` as an `AIAgent` in the service collection
|
|
||||||
- Using the agent from a `IHostedService` with an interactive chat loop
|
|
||||||
- Streaming responses in a hosted service context
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step08_DependencyInjection
|
|
||||||
```
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to use MCP client tools with an agent.
|
|
||||||
// It connects to the Microsoft Learn MCP server via HTTP and uses its tools.
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
using ModelContextProtocol.Client;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// Connect to the Microsoft Learn MCP server via HTTP (Streamable HTTP transport).
|
|
||||||
Console.WriteLine("Connecting to MCP server at https://learn.microsoft.com/api/mcp ...");
|
|
||||||
|
|
||||||
await using McpClient mcpClient = await McpClient.CreateAsync(new HttpClientTransport(new()
|
|
||||||
{
|
|
||||||
Endpoint = new Uri("https://learn.microsoft.com/api/mcp"),
|
|
||||||
Name = "Microsoft Learn MCP",
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Retrieve the list of tools available on the MCP server.
|
|
||||||
IList<McpClientTool> mcpTools = await mcpClient.ListToolsAsync();
|
|
||||||
Console.WriteLine($"MCP tools available: {string.Join(", ", mcpTools.Select(t => t.Name))}");
|
|
||||||
|
|
||||||
List<AITool> agentTools = [.. mcpTools.Cast<AITool>()];
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName,
|
|
||||||
instructions: "You are a helpful assistant that can help with Microsoft documentation questions. Use the Microsoft Learn MCP tool to search for documentation.",
|
|
||||||
name: "DocsAgent",
|
|
||||||
tools: agentTools);
|
|
||||||
|
|
||||||
Console.WriteLine($"Agent '{agent.Name}' created. Asking a question...\n");
|
|
||||||
|
|
||||||
const string Prompt = "How does one create an Azure storage account using az cli?";
|
|
||||||
Console.WriteLine($"User: {Prompt}\n");
|
|
||||||
Console.WriteLine($"Agent: {await agent.RunAsync(Prompt)}");
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
# Using MCP Client as Tools with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use MCP (Model Context Protocol) client tools with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Connecting to an MCP server via HTTP client transport
|
|
||||||
- Retrieving MCP tools and passing them to a `ChatClientAgent`
|
|
||||||
- Using MCP tools for agent interactions without server-side agent creation
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
- Node.js installed (for npx/MCP server)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="assets\walkway.jpg">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Using Images with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to use image multi-modality with an agent.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Loading images using `DataContent.LoadFromAsync`
|
|
||||||
- Sending images alongside text to the agent
|
|
||||||
- Streaming the agent's image analysis response
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and a vision-capable model deployment (e.g., `gpt-4o`)
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step10_UsingImages
|
|
||||||
```
|
|
||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
-15
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Agent as a Function Tool with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates how to use one agent as a function tool for another agent.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating a specialized agent (weather) with function tools
|
|
||||||
- Exposing an agent as a function tool using `.AsAIFunction()`
|
|
||||||
- Composing agents where one agent delegates to another
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step11_AsFunctionTool
|
|
||||||
```
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Middleware with the Responses API
|
|
||||||
|
|
||||||
This sample demonstrates multiple middleware layers working together: PII filtering, guardrails, function invocation logging, and human-in-the-loop approval.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Agent-level run middleware (PII filtering, guardrail enforcement)
|
|
||||||
- Function-level middleware (logging, result overrides)
|
|
||||||
- Human-in-the-loop approval workflows for sensitive function calls
|
|
||||||
- Using `.AsBuilder().Use()` to compose middleware
|
|
||||||
- No server-side agent creation or cleanup required
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd dotnet/samples/02-agents/AgentsWithFoundry
|
|
||||||
dotnet run --project .\Agent_Step12_Middleware
|
|
||||||
```
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to use plugins with an AI agent. Plugin classes can
|
|
||||||
// depend on other services that need to be injected. In this sample, the
|
|
||||||
// AgentPlugin class uses the WeatherProvider and CurrentTimeProvider classes
|
|
||||||
// to get weather and current time information. Both services are registered
|
|
||||||
// in the service collection and injected into the plugin.
|
|
||||||
// Plugin classes may have many methods, but only some are intended to be used
|
|
||||||
// as AI functions. The AsAITools method of the plugin class shows how to specify
|
|
||||||
// which methods should be exposed to the AI agent.
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using SampleApp;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
const string AssistantInstructions = "You are a helpful assistant that helps people find information.";
|
|
||||||
const string AssistantName = "PluginAssistant";
|
|
||||||
|
|
||||||
// Create a service collection to hold the agent plugin and its dependencies.
|
|
||||||
ServiceCollection services = new();
|
|
||||||
services.AddSingleton<WeatherProvider>();
|
|
||||||
services.AddSingleton<CurrentTimeProvider>();
|
|
||||||
services.AddSingleton<AgentPlugin>(); // The plugin depends on WeatherProvider and CurrentTimeProvider registered above.
|
|
||||||
|
|
||||||
IServiceProvider serviceProvider = services.BuildServiceProvider();
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
// Create a ChatClientAgent with the options-based constructor to pass services.
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions
|
|
||||||
{
|
|
||||||
Name = AssistantName,
|
|
||||||
ChatOptions = new() { ModelId = deploymentName, Instructions = AssistantInstructions, Tools = serviceProvider.GetRequiredService<AgentPlugin>().AsAITools().ToList() }
|
|
||||||
},
|
|
||||||
services: serviceProvider);
|
|
||||||
|
|
||||||
// Invoke the agent and output the text result.
|
|
||||||
AgentSession session = await agent.CreateSessionAsync();
|
|
||||||
Console.WriteLine(await agent.RunAsync("Tell me current time and weather in Seattle.", session));
|
|
||||||
|
|
||||||
namespace SampleApp
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The agent plugin that provides weather and current time information.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class AgentPlugin
|
|
||||||
{
|
|
||||||
private readonly WeatherProvider _weatherProvider;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AgentPlugin"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="weatherProvider">The weather provider to get weather information.</param>
|
|
||||||
public AgentPlugin(WeatherProvider weatherProvider)
|
|
||||||
{
|
|
||||||
this._weatherProvider = weatherProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the weather information for the specified location.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method demonstrates how to use the dependency that was injected into the plugin class.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="location">The location to get the weather for.</param>
|
|
||||||
/// <returns>The weather information for the specified location.</returns>
|
|
||||||
public string GetWeather(string location)
|
|
||||||
{
|
|
||||||
return this._weatherProvider.GetWeather(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current date and time for the specified location.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method demonstrates how to resolve a dependency using the service provider passed to the method.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="sp">The service provider to resolve the <see cref="CurrentTimeProvider"/>.</param>
|
|
||||||
/// <param name="location">The location to get the current time for.</param>
|
|
||||||
/// <returns>The current date and time as a <see cref="DateTimeOffset"/>.</returns>
|
|
||||||
public DateTimeOffset GetCurrentTime(IServiceProvider sp, string location)
|
|
||||||
{
|
|
||||||
CurrentTimeProvider currentTimeProvider = sp.GetRequiredService<CurrentTimeProvider>();
|
|
||||||
return currentTimeProvider.GetCurrentTime(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the functions provided by this plugin.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// In real world scenarios, a class may have many methods and only a subset of them may be intended to be exposed as AI functions.
|
|
||||||
/// This method demonstrates how to explicitly specify which methods should be exposed to the AI agent.
|
|
||||||
/// </remarks>
|
|
||||||
/// <returns>The functions provided by this plugin.</returns>
|
|
||||||
public IEnumerable<AITool> AsAITools()
|
|
||||||
{
|
|
||||||
yield return AIFunctionFactory.Create(this.GetWeather);
|
|
||||||
yield return AIFunctionFactory.Create(this.GetCurrentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class WeatherProvider
|
|
||||||
{
|
|
||||||
private readonly string _weatherSummary = "cloudy with a high of 15°C";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The weather provider that returns weather information.
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the weather information for the specified location.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The weather information is hardcoded for demonstration purposes.
|
|
||||||
/// In a real application, this could call a weather API to get actual weather data.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="location">The location to get the weather for.</param>
|
|
||||||
/// <returns>The weather information for the specified location.</returns>
|
|
||||||
public string GetWeather(string location)
|
|
||||||
{
|
|
||||||
return $"The weather in {location} is {this._weatherSummary}.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class CurrentTimeProvider
|
|
||||||
{
|
|
||||||
private readonly TimeProvider _timeProvider = TimeProvider.System;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides the current date and time.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This class returns the current date and time using the system's clock.
|
|
||||||
/// </remarks>
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current date and time.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="location">The location to get the current time for (not used in this implementation).</param>
|
|
||||||
/// <returns>The current date and time as a <see cref="DateTimeOffset"/>.</returns>
|
|
||||||
public DateTimeOffset GetCurrentTime(string location)
|
|
||||||
{
|
|
||||||
return this._timeProvider.GetLocalNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Using Plugins with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use plugins with a `ChatClientAgent` using the Responses API directly, with dependency injection for plugin services.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Creating plugin classes with injected dependencies
|
|
||||||
- Registering services and building a service provider
|
|
||||||
- Passing `services` to the `ChatClientAgent` via the options-based constructor
|
|
||||||
- Using `AIFunctionFactory` to expose plugin methods as AI tools
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Code Interpreter with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use the Code Interpreter tool with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Using `HostedCodeInterpreterTool` with `ChatClientAgent`
|
|
||||||
- Extracting code input and output from agent responses
|
|
||||||
- Handling code interpreter annotations and file citations
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Computer Use with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use the Computer Use tool with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Using `FoundryAITool.CreateComputerTool()` with `ChatClientAgent`
|
|
||||||
- Processing computer call actions (click, type, key press)
|
|
||||||
- Managing the computer use interaction loop with screenshots
|
|
||||||
- Handling the Azure Agents API workaround for `previous_response_id` with `computer_call_output`
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="computer-use-preview"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# File Search with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use the File Search tool with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Uploading files and creating vector stores via `AIProjectClient`
|
|
||||||
- Using `HostedFileSearchTool` with `ChatClientAgent`
|
|
||||||
- Handling file citation annotations in agent responses
|
|
||||||
- Cleaning up file resources after use
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Azure.Identity" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# OpenAPI Tools with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use OpenAPI tools with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Defining an OpenAPI specification inline
|
|
||||||
- Creating an `OpenAPIFunctionDefinition` for the REST Countries API
|
|
||||||
- Using `FoundryAITool.CreateOpenApiTool()` with `ChatClientAgent`
|
|
||||||
- Server-side execution of OpenAPI tool calls
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Bing Custom Search with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use the Bing Custom Search tool with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Configuring `BingCustomSearchToolParameters` with connection ID and instance name
|
|
||||||
- Using `FoundryAITool.CreateBingCustomSearchTool()` with `ChatClientAgent`
|
|
||||||
- Processing search results from agent responses
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
- Bing Custom Search resource configured with a connection ID
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
$env:AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID="your-connection-id" # The full ARM resource URI, e.g., "/subscriptions/.../connections/your-bing-connection"
|
|
||||||
$env:AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME="your-instance-name" # The Bing Custom Search configuration name (from Azure portal)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Finding the connection ID and instance name
|
|
||||||
|
|
||||||
- **Connection ID** (`AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID`): The full ARM resource URI including the `/projects/<name>/connections/<connection-name>` segment. Find the connection name in your Foundry project under **Management center** → **Connected resources**.
|
|
||||||
- **Instance Name** (`AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME`): The **configuration name** from your Bing Custom Search resource (Azure portal → your Bing Custom Search resource → **Configurations**). This is _not_ the Azure resource name or the connection name — it's the name of the specific search configuration that defines which domains/sites to search against.
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Azure.Identity" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# SharePoint Grounding with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use the SharePoint Grounding tool with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Configuring `SharePointGroundingToolOptions` with project connections
|
|
||||||
- Using `FoundryAITool.CreateSharepointTool()` with `ChatClientAgent`
|
|
||||||
- Displaying grounding annotations from agent responses
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
- SharePoint connection configured in your Microsoft Foundry project
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
$env:SHAREPOINT_PROJECT_CONNECTION_ID="your-sharepoint-connection-id" # The full ARM resource URI, e.g., "/subscriptions/.../connections/SharepointTestTool"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Azure.Identity" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to use Microsoft Fabric Tool with a ChatClientAgent.
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.AI.Projects.Agents;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Agents.AI.AzureAI;
|
|
||||||
|
|
||||||
string fabricConnectionId = Environment.GetEnvironmentVariable("FABRIC_PROJECT_CONNECTION_ID") ?? throw new InvalidOperationException("FABRIC_PROJECT_CONNECTION_ID is not set.");
|
|
||||||
|
|
||||||
const string AgentInstructions = "You are a helpful assistant with access to Microsoft Fabric data. Answer questions based on data available through your Fabric connection.";
|
|
||||||
|
|
||||||
// Configure Microsoft Fabric tool options with project connection
|
|
||||||
var fabricToolOptions = new FabricDataAgentToolOptions();
|
|
||||||
fabricToolOptions.ProjectConnections.Add(new ToolProjectConnection(fabricConnectionId));
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
// Create a AIAgent with Microsoft Fabric tool.
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName,
|
|
||||||
instructions: AgentInstructions,
|
|
||||||
name: "FabricAgent-RAPI",
|
|
||||||
tools: [FoundryAITool.CreateMicrosoftFabricTool(fabricToolOptions)]);
|
|
||||||
|
|
||||||
Console.WriteLine($"Created agent: {agent.Name}");
|
|
||||||
|
|
||||||
// Run the agent with a sample query
|
|
||||||
AgentResponse response = await agent.RunAsync("What data is available in the connected Fabric workspace?");
|
|
||||||
|
|
||||||
Console.WriteLine("\n=== Agent Response ===");
|
|
||||||
foreach (var message in response.Messages)
|
|
||||||
{
|
|
||||||
Console.WriteLine(message.Text);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Microsoft Fabric with the Responses API
|
|
||||||
|
|
||||||
This sample shows how to use the Microsoft Fabric tool with a `ChatClientAgent` using the Responses API directly.
|
|
||||||
|
|
||||||
## What this sample demonstrates
|
|
||||||
|
|
||||||
- Configuring `FabricDataAgentToolOptions` with project connections
|
|
||||||
- Using `FoundryAITool.CreateMicrosoftFabricTool()` with `ChatClientAgent`
|
|
||||||
- Querying data available through a Fabric connection
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 10 SDK or later
|
|
||||||
- Microsoft Foundry service endpoint and deployment configured
|
|
||||||
- Azure CLI installed and authenticated (`az login`)
|
|
||||||
- Microsoft Fabric connection configured in your Microsoft Foundry project
|
|
||||||
|
|
||||||
Set the following environment variables:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
|
||||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
|
|
||||||
$env:FABRIC_PROJECT_CONNECTION_ID="your-fabric-connection-id" # The full ARM resource URI, e.g., "/subscriptions/.../connections/FabricTestTool"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run the sample
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Azure.Identity" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
|
|
||||||
// This sample shows how to use the Web Search Tool with a ChatClientAgent.
|
|
||||||
|
|
||||||
using Azure.AI.Projects;
|
|
||||||
using Azure.Identity;
|
|
||||||
using Microsoft.Agents.AI;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
using OpenAI.Responses;
|
|
||||||
|
|
||||||
const string AgentInstructions = "You are a helpful assistant that can search the web to find current information and answer questions accurately.";
|
|
||||||
const string AgentName = "WebSearchAgent-RAPI";
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
||||||
|
|
||||||
// Create a AIAgent with HostedWebSearchTool.
|
|
||||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName,
|
|
||||||
instructions: AgentInstructions,
|
|
||||||
name: AgentName,
|
|
||||||
tools: [new HostedWebSearchTool()]);
|
|
||||||
|
|
||||||
AgentResponse response = await agent.RunAsync("What's the weather today in Seattle?");
|
|
||||||
|
|
||||||
// Get the text response
|
|
||||||
Console.WriteLine($"Response: {response.Text}");
|
|
||||||
|
|
||||||
// Getting any annotations/citations generated by the web search tool
|
|
||||||
foreach (AIAnnotation annotation in response.Messages.SelectMany(m => m.Contents).SelectMany(c => c.Annotations ?? []))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Annotation: {annotation}");
|
|
||||||
if (annotation.RawRepresentation is UriCitationMessageAnnotation urlCitation)
|
|
||||||
{
|
|
||||||
Console.WriteLine($$"""
|
|
||||||
Title: {{urlCitation.Title}}
|
|
||||||
URL: {{urlCitation.Uri}}
|
|
||||||
""");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user