Compare commits

..

1 Commits

Author SHA1 Message Date
Giles Odigwe 193697ef40 Fix AzureFunctions integration tests — set FUNCTIONS_WORKER_RUNTIME
Azure Functions Core Tools v4 can no longer auto-detect the worker
runtime when local.settings.json is absent. Add the required
FUNCTIONS_WORKER_RUNTIME=dotnet-isolated environment variable to
both StartFunctionApp helpers and re-enable the skipped tests.

Fixes: https://github.com/microsoft/agent-framework/issues/6402

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-10 12:08:15 -07:00
1072 changed files with 17362 additions and 56244 deletions
+1 -4
View File
@@ -20,10 +20,7 @@ ignorePatterns:
- pattern: "https://your-resource.openai.azure.com/"
- pattern: "http://host.docker.internal"
- pattern: "https://openai.github.io/openai-agents-js/openai/agents/classes/"
# dotnet.microsoft.com bot-blocks CI link checkers with intermittent 403s on any
# path (including localized variants like /en-us/download/...), so ignore the
# whole domain rather than just /download.
- pattern: "https:\/\/dotnet.microsoft.com"
- pattern: "https:\/\/dotnet.microsoft.com\/download"
- pattern: "https://github.com/Rel1cx/eslint-react"
# excludedDirs:
# Folders which include links to localhost, since it's not ignored with regular expressions
+1 -1
View File
@@ -1,7 +1,7 @@
name: .NET Bug Report
description: Report a bug in the Agent Framework .NET SDK
title: ".NET: [Bug]: "
labels: [".NET"]
labels: ["bug", ".NET"]
type: bug
body:
- type: textarea
+1 -1
View File
@@ -1,7 +1,7 @@
name: Python Bug Report
description: Report a bug in the Agent Framework Python SDK
title: "Python: [Bug]: "
labels: ["Python"]
labels: ["bug", "Python"]
type: bug
body:
- type: textarea
+3 -5
View File
@@ -24,14 +24,12 @@ updates:
- ".NET"
- "dependencies"
# Maintain dependencies for python.
# TODO: Remove these Python Dependabot entries after we have confidence in the
# Python dependency-maintenance workflow.
# Maintain dependencies for python
- package-ecosystem: "pip"
directory: "python/"
schedule:
interval: "weekly"
day: "thursday"
day: "monday"
labels:
- "python"
- "dependencies"
@@ -39,7 +37,7 @@ updates:
directory: "python/"
schedule:
interval: "weekly"
day: "thursday"
day: "monday"
labels:
- "python"
- "dependencies"
+5 -25
View File
@@ -1,43 +1,23 @@
### Motivation & Context
### Motivation and Context
<!-- Thank you for your contribution to the Agent Framework repo!
Please help reviewers and future users, providing the following information:
1. Why is this change required?
2. What problem does it solve?
3. What scenario does it contribute to?
4. If it fixes an open issue, please link to the issue below.
4. If it fixes an open issue, please link to the issue here.
-->
### Description & Review Guide
### Description
<!-- Describe your changes, the overall approach, the underlying design.
Highlight what you want the reviewers to focus on.
These notes will help understanding how your code works. Thanks! -->
- **What are the major changes?**
- **What is the impact of these changes?**
- **What do you want reviewers to focus on?**
<!-- NOTE FOR AUTOMATED/AI REVIEWERS: the "What do you want reviewers to focus on?"
item above is intended for human reviewers only. Automated/AI reviewers should
ignore it and review the entire change rather than narrowing scope to it. -->
### Related Issue
<!-- Which issue does this PR fix? Link it using a GitHub closing keyword so it is
closed automatically when this PR is merged, e.g. "Fixes #123" or "Closes #123".
PRs that are not linked to an issue may be closed, no matter how valid the change is.
Also check whether an open PR already exists for this issue; if so,
explain how this PR is different. -->
Fixes #
### Contribution Checklist
<!-- Before submitting this PR, please make sure: -->
- [ ] The code builds clean without any errors or warnings
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
- [ ] This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
- [x] **This is not a breaking change.** If it _is_ a breaking change, add the `breaking change` label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] **Is this a breaking change?** If yes, add "[BREAKING]" prefix to the title of the PR.
-253
View File
@@ -1,253 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
const BREAKING_CHANGE_LABEL = 'breaking change';
const BREAKING_PREFIX = '[BREAKING]';
const DEFAULT_PREFIX_LABELS = Object.freeze({
python: 'Python',
'.NET': '.NET',
});
const DEFAULT_BRACKET_PREFIX_LABELS = Object.freeze({
[BREAKING_CHANGE_LABEL]: BREAKING_PREFIX,
});
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getMatchingValueByKey(valuesByKey, keyToFind) {
const matchingKey = Object.keys(valuesByKey).find((key) => key.toLowerCase() === keyToFind.toLowerCase());
return matchingKey === undefined ? null : valuesByKey[matchingKey];
}
function getPrefixPattern(prefixes) {
return prefixes.map(escapeRegExp).join('|');
}
function canonicalizePrefix(prefix, prefixes) {
return prefixes.find((knownPrefix) => knownPrefix.toLowerCase() === prefix.toLowerCase()) ?? prefix;
}
function normalizeLeadingBracketPrefix(title, bracketPrefixes) {
const bracketPattern = getPrefixPattern(bracketPrefixes);
if (!bracketPattern) {
return title;
}
const leadingBracketPrefix = new RegExp(`^(${bracketPattern})(?=\\s|$)`, 'i');
return title.replace(
leadingBracketPrefix,
(bracketPrefix) => canonicalizePrefix(bracketPrefix, bracketPrefixes),
);
}
function parseLeadingTitlePrefix(title, titlePrefixes) {
const titlePrefixPattern = getPrefixPattern(titlePrefixes);
if (!titlePrefixPattern) {
return null;
}
const match = title.match(new RegExp(`^(${titlePrefixPattern}):\\s*`, 'i'));
if (!match) {
return null;
}
return {
prefix: canonicalizePrefix(match[1], titlePrefixes),
rest: title.slice(match[0].length).trimStart(),
};
}
function removeBracketPrefixToken(title, bracketPrefix) {
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
return title
.replace(new RegExp(`(^|\\s+)${bracketPrefixPattern}(?=\\s|$)`, 'ig'), '$1')
.replace(/\s{2,}/g, ' ')
.trim();
}
function addTitlePrefix(title, prefix, bracketPrefixes = Object.values(DEFAULT_BRACKET_PREFIX_LABELS)) {
const bracketPattern = getPrefixPattern(bracketPrefixes);
const prefixPattern = escapeRegExp(prefix);
if (bracketPattern) {
const bracketThenTitlePrefix = new RegExp(`^(${bracketPattern})(\\s+)(${prefixPattern})(?=:)`, 'i');
if (bracketThenTitlePrefix.test(title)) {
return title.replace(
bracketThenTitlePrefix,
(match, bracketPrefix, spacing) => `${canonicalizePrefix(bracketPrefix, bracketPrefixes)}${spacing}${prefix}`,
);
}
title = normalizeLeadingBracketPrefix(title, bracketPrefixes);
}
if (!title.startsWith(`${prefix}: `)) {
const existingTitlePrefix = new RegExp(`^${prefixPattern}:\\s*`, 'i');
if (existingTitlePrefix.test(title)) {
return title.replace(existingTitlePrefix, `${prefix}: `);
}
return `${prefix}: ${title}`;
}
return title;
}
function hasBracketPrefix(title, bracketPrefix, titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS)) {
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
const leadingBracketPrefix = new RegExp(`^${bracketPrefixPattern}(?=\\s|$)`, 'i');
if (leadingBracketPrefix.test(title)) {
return true;
}
const leadingTitlePrefix = parseLeadingTitlePrefix(title, titlePrefixes);
if (!leadingTitlePrefix) {
return false;
}
return leadingBracketPrefix.test(leadingTitlePrefix.rest);
}
function addBracketPrefix(title, bracketPrefix, titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS)) {
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
const leadingBracketPrefix = new RegExp(`^${bracketPrefixPattern}(?=\\s|$)`, 'i');
if (leadingBracketPrefix.test(title)) {
return title.replace(leadingBracketPrefix, bracketPrefix);
}
const leadingTitlePrefix = parseLeadingTitlePrefix(title, titlePrefixes);
if (leadingTitlePrefix) {
if (leadingBracketPrefix.test(leadingTitlePrefix.rest)) {
const normalizedRest = leadingTitlePrefix.rest.replace(leadingBracketPrefix, bracketPrefix);
return `${leadingTitlePrefix.prefix}: ${normalizedRest}`;
}
const titleWithoutBracketPrefix = removeBracketPrefixToken(leadingTitlePrefix.rest, bracketPrefix);
return `${leadingTitlePrefix.prefix}: ${bracketPrefix}`
+ (titleWithoutBracketPrefix ? ` ${titleWithoutBracketPrefix}` : '');
}
const titleWithoutBracketPrefix = removeBracketPrefixToken(title, bracketPrefix);
return `${bracketPrefix}${titleWithoutBracketPrefix ? ` ${titleWithoutBracketPrefix}` : ''}`;
}
function hasLabel(labels, labelName) {
return labels.some((label) => label.toLowerCase() === labelName.toLowerCase());
}
function getCurrentTitle(context) {
switch (context.eventName) {
case 'issues':
return context.payload.issue.title;
case 'pull_request_target':
return context.payload.pull_request.title;
default:
throw new Error(`Unrecognized eventName: ${context.eventName}`);
}
}
async function updateTitleForAddedLabel({
github,
context,
core,
prefixLabels = DEFAULT_PREFIX_LABELS,
bracketPrefixLabels = DEFAULT_BRACKET_PREFIX_LABELS,
}) {
const labelAdded = context.payload.label?.name;
if (!labelAdded) {
throw new Error('This script must be run from a labeled event.');
}
const currentTitle = getCurrentTitle(context);
let newTitle = null;
const titlePrefix = getMatchingValueByKey(prefixLabels, labelAdded);
if (titlePrefix !== null) {
newTitle = addTitlePrefix(currentTitle, titlePrefix, Object.values(bracketPrefixLabels));
}
const bracketPrefix = getMatchingValueByKey(bracketPrefixLabels, labelAdded);
if (bracketPrefix !== null) {
newTitle = addBracketPrefix(currentTitle, bracketPrefix, Object.values(prefixLabels));
}
if (newTitle === null) {
core.info(`No title prefix configured for label "${labelAdded}".`);
return { updated: false, newTitle: currentTitle };
}
if (newTitle === currentTitle) {
core.info(`Title already includes the prefix for label "${labelAdded}".`);
return { updated: false, newTitle };
}
switch (context.eventName) {
case 'issues':
await github.rest.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: newTitle,
});
break;
case 'pull_request_target':
await github.rest.pulls.update({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: newTitle,
});
break;
default:
throw new Error(`Unrecognized eventName: ${context.eventName}`);
}
return { updated: true, newTitle };
}
async function syncBreakingChangeLabelFromTitle({
github,
context,
core,
labelName = BREAKING_CHANGE_LABEL,
bracketPrefix = BREAKING_PREFIX,
titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS),
}) {
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
throw new Error('This script must be run from a pull_request_target event.');
}
const title = pullRequest.title || '';
if (!hasBracketPrefix(title, bracketPrefix, titlePrefixes)) {
core.info(`Title does not include ${bracketPrefix} in the title prefix.`);
return { added: false };
}
const labels = pullRequest.labels?.map((label) => label.name).filter(Boolean) ?? [];
if (hasLabel(labels, labelName)) {
core.info(`PR already has the "${labelName}" label.`);
return { added: false };
}
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [labelName],
});
return { added: true };
}
module.exports = {
addBracketPrefix,
addTitlePrefix,
hasBracketPrefix,
syncBreakingChangeLabelFromTitle,
updateTitleForAddedLabel,
};
-116
View File
@@ -1,116 +0,0 @@
---
name: pull-requests
description: >
Guidance for creating pull requests and handling PR review comments in the
Agent Framework repository. Use this when writing a PR description (filling out
the PR template) or when responding to and resolving review comments on an
existing PR.
---
# Pull Request Workflow
This skill covers two tasks: (1) writing a high-quality PR description, and
(2) handling review comments on an existing PR.
## 1. Writing the PR description
Always follow the repository PR template at
[`.github/pull_request_template.md`](../../pull_request_template.md). Keep its
exact structure and headings. Fill every section:
### `### Motivation & Context`
Explain *why* the change is needed: the problem it solves and the scenario it
contributes to. Describe the net change relative to `main` — this is implied, so
do **not** spell out "vs main" explicitly.
### `### Description & Review Guide`
Describe the changes, the overall approach, and the design. Answer the three
prompts:
- **What are the major changes?**
- **What is the impact of these changes?**
- **What do you want reviewers to focus on?** — This item is for **human
reviewers only**. Automated/AI reviewers must ignore it and review the entire
change rather than narrowing scope to it.
### `### Related Issue`
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
be closed regardless of how valid the change is. Before opening, confirm there is
no other open PR for the same issue; if there is, explain how this PR differs.
### `### Contribution Checklist`
Check every item that applies. For the breaking-change item:
- Leave **"This is not a breaking change."** checked for the common case.
- If the change **is** breaking, add the `breaking change` label **or** put
`[BREAKING]` in the title prefix, before or after a language prefix such as
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
automatically (see `.github/workflows/label-title-prefix.yml` and
`.github/workflows/label-pr.yml`).
### Do not
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
the checklist already cover validation status.
- Do **not** remove or reorder the template's headings.
### Creating the PR
Open new PRs as **drafts** until they are ready for review. Example:
```bash
gh pr create --repo microsoft/agent-framework --base main \
--head <your-fork-owner>:<branch> --draft \
--title "<concise title>" --body "<body following the template>"
```
## 2. Handling review comments
When a PR receives review comments, follow this sequence — **do not start editing
code before the user has reviewed the plan**:
1. **Review the comments.** Read every review comment and thread on the PR,
including inline code comments and general review summaries.
2. **Make a plan.** Produce a concrete plan describing how each comment will be
addressed (or why it should not be, with reasoning).
3. **Let the user review the plan.** Present the plan and wait for the user's
approval or adjustments before implementing anything.
4. **Implement.** Make the agreed changes.
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
was addressed (or the agreed outcome) — leave none unanswered.
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
comment has actually been addressed.
### Useful commands
List review comments and threads:
```bash
# Inline review comments
gh api repos/{owner}/{repo}/pulls/{pr}/comments
# Review threads with resolution state (GraphQL)
gh api graphql -f query='
query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$pr){
reviewThreads(first:100){
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
}
}
}
}' -F owner={owner} -F repo={repo} -F pr={pr}
```
Reply to an inline review comment:
```bash
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
-f body="Addressed in <commit>: <explanation>"
```
Resolve a review thread (needs the thread node id from the GraphQL query above):
```bash
gh api graphql -f query='
mutation($threadId:ID!){
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
}' -F threadId={thread_id}
```
+3 -4
View File
@@ -317,10 +317,9 @@ jobs:
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }}
AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }}
# Anthropic Models
# Disable Anthropic tests by not providing environment vars until 404 failure is resolved
# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# ANTHROPIC_CHAT_MODEL_NAME: ${{ vars.ANTHROPIC_CHAT_MODEL_NAME }}
# ANTHROPIC_REASONING_MODEL_NAME: ${{ vars.ANTHROPIC_REASONING_MODEL_NAME }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_CHAT_MODEL_NAME: ${{ vars.ANTHROPIC_CHAT_MODEL_NAME }}
ANTHROPIC_REASONING_MODEL_NAME: ${{ vars.ANTHROPIC_REASONING_MODEL_NAME }}
# Generate test reports and check coverage
- name: Generate test reports
@@ -88,7 +88,6 @@ jobs:
env:
COSMOSDB_ENDPOINT: https://localhost:8081
COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
OpenAI__ChatReasoningModelId: ${{ vars.OPENAI__CHATREASONINGMODELID }}
+5 -3
View File
@@ -2,7 +2,7 @@ name: Issue Triage
on:
issues:
types: [opened, typed]
types: [opened, labeled]
permissions:
contents: read
@@ -12,7 +12,9 @@ permissions:
concurrency:
group: >-
issue-triage-${{ github.repository }}-${{
github.event.issue.type.name == 'Bug' && github.event.issue.number
((github.event.action == 'opened' && contains(github.event.issue.labels.*.name, 'bug'))
|| (github.event.action == 'labeled' && github.event.label.name == 'bug'))
&& github.event.issue.number
|| github.run_id
}}
cancel-in-progress: true
@@ -26,7 +28,7 @@ env:
jobs:
team_check:
runs-on: ubuntu-latest
if: ${{ github.event.issue.type.name == 'Bug' }}
if: ${{ (github.event.action == 'opened' && contains(github.event.issue.labels.*.name, 'bug')) || (github.event.action == 'labeled' && github.event.label.name == 'bug') }}
outputs:
is_team_member: ${{ steps.check.outputs.is_team_member }}
issue_number: ${{ steps.issue.outputs.issue_number }}
+3 -1
View File
@@ -90,7 +90,9 @@ jobs:
// Check for issue type from issue form dropdown
const issueTypeField = getFormFieldValue(body, 'Type of Issue')
if (issueTypeField) {
if (issueTypeField === 'Feature Request') {
if (issueTypeField === 'Bug') {
labels.push("bug")
} else if (issueTypeField === 'Feature Request') {
labels.push("enhancement")
} else if (issueTypeField === 'Question') {
labels.push("question")
+1 -19
View File
@@ -6,34 +6,16 @@
# https://github.com/actions/labeler
name: Label pull request
on:
pull_request_target:
types: [opened, synchronize, reopened, edited]
on: [pull_request_target]
jobs:
add_label:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6
with:
repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
- name: Checkout scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
sparse-checkout: .github/scripts
fetch-depth: 1
persist-credentials: false
- name: "PR: add breaking change label from title"
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
script: |
const { syncBreakingChangeLabelFromTitle } = require('./.github/scripts/title_prefix.js');
await syncBreakingChangeLabelFromTitle({ github, context, core });
+50 -9
View File
@@ -15,17 +15,58 @@ jobs:
pull-requests: write
steps:
- name: Checkout scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
sparse-checkout: .github/scripts
fetch-depth: 1
persist-credentials: false
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
name: "Issue/PR: update title"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { updateTitleForAddedLabel } = require('./.github/scripts/title_prefix.js');
await updateTitleForAddedLabel({ github, context, core });
let prefixLabels = {
"python": "Python",
".NET": ".NET"
};
function addTitlePrefix(title, prefix)
{
// Update the title based on the label and prefix
// Check if the title starts with the prefix (case-sensitive)
if (!title.startsWith(prefix + ": ")) {
// If not, check if the first word is the label (case-insensitive)
if (title.match(new RegExp(`^${prefix}`, 'i'))) {
// If yes, replace it with the prefix (case-sensitive)
title = title.replace(new RegExp(`^${prefix}`, 'i'), prefix);
} else {
// If not, prepend the prefix to the title
title = prefix + ": " + title;
}
}
return title;
}
labelAdded = context.payload.label.name
// Check if the issue or PR has the label
if (labelAdded in prefixLabels) {
let prefix = prefixLabels[labelAdded];
switch(context.eventName) {
case 'issues':
github.rest.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: addTitlePrefix(context.payload.issue.title, prefix)
});
break
case 'pull_request_target':
github.rest.pulls.update({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: addTitlePrefix(context.payload.pull_request.title, prefix)
});
break
default:
core.setFailed('Unrecognited eventName: ' + context.eventName);
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
# "Cleanup artifacts", "Agent", "Prepare", and "Upload results" are check runs
# created by an org-level GitHub App (MSDO), not by any workflow in this repo.
# They are outside our control and their transient failures should not block merges.
IGNORED_NAMES: "CodeQL,CodeQL analysis (csharp),Cleanup artifacts,Agent,Prepare,Upload results,review"
IGNORED_NAMES: "CodeQL,CodeQL analysis (csharp),Cleanup artifacts,Agent,Prepare,Upload results"
with:
script: |
const timeoutSeconds = Number(process.env.TIMEOUT_SECONDS);
+6 -8
View File
@@ -6,10 +6,6 @@ on:
branches: ["main"]
paths:
- "python/**"
- "!python/AGENTS.md"
- "!python/**/AGENTS.md"
- "!python/.github/skills/*"
- "!python/.github/skills/**"
env:
# Configure a constant location for the uv cache
@@ -113,8 +109,8 @@ jobs:
- name: Run markdown code lint
run: uv run poe markdown-code-lint
test-typing:
name: Test Typing Checks
mypy:
name: Mypy Checks
if: "!cancelled()"
strategy:
fail-fast: false
@@ -139,5 +135,7 @@ jobs:
os: ${{ runner.os }}
env:
UV_CACHE_DIR: /tmp/.uv-cache
- name: Run tests/samples type checkers (mypy, pyrefly, ty)
run: uv run python scripts/workspace_poe_tasks.py ci-test-typing
- name: Run Mypy
env:
GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}
run: uv run python scripts/workspace_poe_tasks.py ci-mypy
@@ -1,365 +0,0 @@
name: Python - Dependency Maintenance
on:
workflow_dispatch:
schedule:
- cron: "0 4 * * 1"
permissions:
contents: write
issues: write
pull-requests: write
concurrency:
group: python-dependency-maintenance
cancel-in-progress: false
env:
UV_CACHE_DIR: /tmp/.uv-cache
jobs:
dependency-maintenance:
name: Dependency Maintenance
runs-on: ubuntu-latest
env:
# Match the existing Python dependency maintenance workflows. Reevaluate if package
# installability starts differing across supported Python versions.
UV_PYTHON: "3.13"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Set up python and install the project
uses: ./.github/actions/python-setup
with:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
env:
UV_CACHE_DIR: /tmp/.uv-cache
- name: Set dependency release cutoff
run: |
cutoff="$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')"
echo "DEPENDENCY_RELEASE_CUTOFF=${cutoff}" >> "$GITHUB_ENV"
echo "Using dependency release cutoff: ${cutoff}"
- name: Repin dev dependency declarations
run: uv run poe upgrade-dev-dependency-pins
working-directory: ./python
- name: Refresh lockfile after dev pin updates
run: uv lock
working-directory: ./python
- name: Save dev dependency changes
run: |
DEV_PATCH="${RUNNER_TEMP}/python-dev-dependency-updates.patch"
git diff -- python/pyproject.toml "python/packages/*/pyproject.toml" python/uv.lock > "${DEV_PATCH}"
if [ -s "${DEV_PATCH}" ]; then
echo "has_dev_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_dev_changes=false" >> "$GITHUB_OUTPUT"
fi
echo "patch=${DEV_PATCH}" >> "$GITHUB_OUTPUT"
id: dev_changes
- name: Run dependency bounds test scenarios
id: validate_bounds_test
continue-on-error: true
run: uv run poe validate-dependency-bounds-test --package "*"
working-directory: ./python
- name: Run dependency upper-bound validation
id: validate_ranges
if: steps.validate_bounds_test.outcome == 'success'
continue-on-error: true
run: uv run poe validate-dependency-bounds-project --mode upper --package "*"
working-directory: ./python
- name: Upload dependency validation reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dependency-maintenance-results
path: |
python/scripts/dependencies/dependency-bounds-test-results.json
python/scripts/dependencies/dependency-range-results.json
if-no-files-found: warn
- name: Create issue for failed dependency bounds test
if: steps.validate_bounds_test.outcome != 'success'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs")
const reportPath = "python/scripts/dependencies/dependency-bounds-test-results.json"
const owner = context.repo.owner
const repo = context.repo.repo
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const openIssueTitles = new Set(
openIssues.filter((issue) => !issue.pull_request).map((issue) => issue.title)
)
const formatError = (message) => String(message || "No error output captured.").replace(/```/g, "'''")
const title = "Dependency bounds test failed"
if (openIssueTitles.has(title)) {
core.info(`Issue already exists: ${title}`)
return
}
const bodyLines = [
"Automated dependency bounds test mode failed before dependency upper-bound validation could run.",
"",
"The weekly dependency maintenance workflow kept only dev dependency updates for the generated PR, if any, and skipped dependency range updates for this run.",
"",
]
if (fs.existsSync(reportPath)) {
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"))
const failedScenarios = (report.scenarios ?? []).filter((scenario) => scenario.status === "failed")
for (const scenario of failedScenarios) {
bodyLines.push(`### ${scenario.name} scenario (${scenario.resolution})`)
const failedPackages = (scenario.packages ?? []).filter((pkg) => pkg.status === "failed")
for (const pkg of failedPackages.slice(0, 10)) {
bodyLines.push(
"",
`- Package: \`${pkg.package_name}\``,
`- Project path: \`${pkg.project_path}\``,
"",
"```",
formatError(pkg.error).slice(0, 3500),
"```"
)
}
if (failedPackages.length > 10) {
bodyLines.push("", `_Additional failed packages omitted: ${failedPackages.length - 10}_`)
}
}
} else {
bodyLines.push(`No dependency bounds test report was found at \`${reportPath}\`.`)
}
bodyLines.push("", `Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`)
await github.rest.issues.create({
owner,
repo,
title,
body: bodyLines.join("\n"),
})
core.info(`Created issue: ${title}`)
- name: Create issues for failed dependency candidates
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs")
const reportPath = "python/scripts/dependencies/dependency-range-results.json"
if (!fs.existsSync(reportPath)) {
core.info(`No dependency range report found at ${reportPath}`)
return
}
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"))
const dependencyFailures = []
for (const packageResult of report.packages ?? []) {
for (const dependency of packageResult.dependencies ?? []) {
const candidateVersions = new Set(dependency.candidate_versions ?? [])
const failedAttempts = (dependency.attempts ?? []).filter(
(attempt) => attempt.status === "failed" && candidateVersions.has(attempt.trial_upper)
)
if (!failedAttempts.length) {
continue
}
const failuresByVersion = new Map()
for (const attempt of failedAttempts) {
const version = attempt.trial_upper || "unknown"
if (!failuresByVersion.has(version)) {
failuresByVersion.set(version, attempt.error || "No error output captured.")
}
}
dependencyFailures.push({
packageName: packageResult.package_name,
projectPath: packageResult.project_path,
dependencyName: dependency.name,
originalRequirements: dependency.original_requirements ?? [],
finalRequirements: dependency.final_requirements ?? [],
failedVersions: [...failuresByVersion.entries()].map(([version, error]) => ({ version, error })),
})
}
}
if (!dependencyFailures.length) {
core.info("No failing dependency candidates found.")
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const openIssueTitles = new Set(
openIssues.filter((issue) => !issue.pull_request).map((issue) => issue.title)
)
const formatError = (message) => String(message || "No error output captured.").replace(/```/g, "'''")
for (const failure of dependencyFailures) {
const title = `Dependency validation failed: ${failure.dependencyName} (${failure.packageName})`
if (openIssueTitles.has(title)) {
core.info(`Issue already exists: ${title}`)
continue
}
const visibleFailures = failure.failedVersions.slice(0, 5)
const omittedCount = failure.failedVersions.length - visibleFailures.length
const failureDetails = visibleFailures
.map(
(entry) =>
`- \`${entry.version}\`\n\n\`\`\`\n${formatError(entry.error).slice(0, 3500)}\n\`\`\``
)
.join("\n\n")
const body = [
"Automated dependency range validation found candidate versions that failed checks.",
"",
`- Package: \`${failure.packageName}\``,
`- Project path: \`${failure.projectPath}\``,
`- Dependency: \`${failure.dependencyName}\``,
`- Original requirements: ${
failure.originalRequirements.length
? failure.originalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
`- Final requirements after run: ${
failure.finalRequirements.length
? failure.finalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
"",
"### Failed versions and errors",
failureDetails,
omittedCount > 0 ? `\n_Additional failed versions omitted: ${omittedCount}_` : "",
"",
`Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`,
].join("\n")
await github.rest.issues.create({
owner,
repo,
title,
body,
})
openIssueTitles.add(title)
core.info(`Created issue: ${title}`)
}
- name: Keep only dev updates when range validation fails
if: steps.validate_bounds_test.outcome != 'success' || steps.validate_ranges.outcome != 'success'
env:
DEV_PATCH: ${{ steps.dev_changes.outputs.patch }}
HAS_DEV_CHANGES: ${{ steps.dev_changes.outputs.has_dev_changes }}
run: |
git restore python/pyproject.toml python/packages/*/pyproject.toml python/uv.lock
if [ "${HAS_DEV_CHANGES}" = "true" ]; then
git apply "${DEV_PATCH}"
fi
- name: Refresh lockfile after dependency range updates
if: steps.validate_bounds_test.outcome == 'success' && steps.validate_ranges.outcome == 'success'
run: uv lock
working-directory: ./python
- name: Install final dependency set
run: uv run poe install
working-directory: ./python
- name: Run final checks
run: uv run poe check
working-directory: ./python
- name: Run final typing
run: uv run poe typing
working-directory: ./python
- name: Commit and push dependency updates
id: commit_updates
run: |
BRANCH="automation/python-dependency-maintenance"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add python/pyproject.toml python/packages/*/pyproject.toml python/uv.lock
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No dependency updates to commit."
exit 0
fi
git commit -m "Python: chore: update dependencies"
git push --force-with-lease --set-upstream origin "${BRANCH}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create or update pull request with GitHub CLI
if: steps.commit_updates.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="automation/python-dependency-maintenance"
PR_TITLE="Python: chore: update dependencies"
PR_BODY_FILE="$(mktemp)"
cat > "${PR_BODY_FILE}" <<'EOF'
### Motivation & Context
This automated update keeps Python dependency metadata coherent across the uv workspace. Python dependencies can be declared in multiple `pyproject.toml` files, but the workspace has one shared `python/uv.lock`, so dependency maintenance should update and validate them together instead of through per-manifest Dependabot PRs.
### Description & Review Guide
- **What are the major changes?** Refresh Python dev dependency pins, update package dependency ranges when the bounds tooling succeeds, and refresh `python/uv.lock`.
- **What is the impact of these changes?** Keeps the Python workspace dependency set current while producing at most one dependency PR for the week. If dependency range validation fails, this PR contains only the dev dependency updates that still pass final validation, and separate issues track failed range candidates.
- **What do you want reviewers to focus on?** Review the generated dependency metadata changes and any dependency-range updates for package-specific compatibility concerns.
<!-- NOTE FOR AUTOMATED/AI REVIEWERS: the "What do you want reviewers to focus on?"
item above is intended for human reviewers only. Automated/AI reviewers should
ignore it and review the entire change rather than narrowing scope to it. -->
### Related Issue
No linked issue; this PR is generated by scheduled Python dependency maintenance.
### Contribution Checklist
- [x] The code builds clean without any errors or warnings
- [x] All unit tests pass, and I have added new tests where possible
- [x] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
- [ ] This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
- [x] **This is not a breaking change.** If it _is_ a breaking change, add the `breaking change` label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.
EOF
PR_NUMBER="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number')"
if [ -n "${PR_NUMBER}" ]; then
gh pr edit "${PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
else
gh pr create --base main --head "${BRANCH}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
fi
@@ -0,0 +1,216 @@
# Probe the highest allowed dependency versions, then open issues/PRs from the passing updates.
name: Python - Dependency Range Validation
on:
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
env:
UV_CACHE_DIR: /tmp/.uv-cache
jobs:
dependency-range-validation:
name: Dependency Range Validation
runs-on: ubuntu-latest
env:
# For now only run 3.13, if we do encounter situations where there are mismatches between packages and python versions (other then 3.10 and 3.14 which are known to not be able to install everything)
# then we will have to reevaluate.
UV_PYTHON: "3.13"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Set up python and install the project
uses: ./.github/actions/python-setup
with:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
env:
UV_CACHE_DIR: /tmp/.uv-cache
- name: Run dependency range validation
id: validate_ranges
# Keep workflow running so we can still publish diagnostics from this run.
continue-on-error: true
run: uv run poe validate-dependency-bounds-project --mode upper --package "*"
working-directory: ./python
- name: Upload dependency range report
# Always publish the report so failures are inspectable even when validation fails.
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dependency-range-results
path: python/scripts/dependencies/dependency-range-results.json
if-no-files-found: warn
- name: Create issues for failed dependency candidates
# Always process the report so failed candidates create actionable tracking issues.
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const fs = require("fs")
const reportPath = "python/scripts/dependencies/dependency-range-results.json"
if (!fs.existsSync(reportPath)) {
core.warning(`No dependency range report found at ${reportPath}`)
return
}
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"))
const dependencyFailures = []
for (const packageResult of report.packages ?? []) {
for (const dependency of packageResult.dependencies ?? []) {
const candidateVersions = new Set(dependency.candidate_versions ?? [])
const failedAttempts = (dependency.attempts ?? []).filter(
(attempt) => attempt.status === "failed" && candidateVersions.has(attempt.trial_upper)
)
if (!failedAttempts.length) {
continue
}
const failuresByVersion = new Map()
for (const attempt of failedAttempts) {
const version = attempt.trial_upper || "unknown"
if (!failuresByVersion.has(version)) {
failuresByVersion.set(version, attempt.error || "No error output captured.")
}
}
dependencyFailures.push({
packageName: packageResult.package_name,
projectPath: packageResult.project_path,
dependencyName: dependency.name,
originalRequirements: dependency.original_requirements ?? [],
finalRequirements: dependency.final_requirements ?? [],
failedVersions: [...failuresByVersion.entries()].map(([version, error]) => ({ version, error })),
})
}
}
if (!dependencyFailures.length) {
core.info("No failing dependency candidates found.")
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const openIssueTitles = new Set(
openIssues.filter((issue) => !issue.pull_request).map((issue) => issue.title)
)
const formatError = (message) => String(message || "No error output captured.").replace(/```/g, "'''")
for (const failure of dependencyFailures) {
const title = `Dependency validation failed: ${failure.dependencyName} (${failure.packageName})`
if (openIssueTitles.has(title)) {
core.info(`Issue already exists: ${title}`)
continue
}
const visibleFailures = failure.failedVersions.slice(0, 5)
const omittedCount = failure.failedVersions.length - visibleFailures.length
const failureDetails = visibleFailures
.map(
(entry) =>
`- \`${entry.version}\`\n\n\`\`\`\n${formatError(entry.error).slice(0, 3500)}\n\`\`\``
)
.join("\n\n")
const body = [
"Automated dependency range validation found candidate versions that failed checks.",
"",
`- Package: \`${failure.packageName}\``,
`- Project path: \`${failure.projectPath}\``,
`- Dependency: \`${failure.dependencyName}\``,
`- Original requirements: ${
failure.originalRequirements.length
? failure.originalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
`- Final requirements after run: ${
failure.finalRequirements.length
? failure.finalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
"",
"### Failed versions and errors",
failureDetails,
omittedCount > 0 ? `\n_Additional failed versions omitted: ${omittedCount}_` : "",
"",
`Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`,
].join("\n")
await github.rest.issues.create({
owner,
repo,
title,
body,
})
openIssueTitles.add(title)
core.info(`Created issue: ${title}`)
}
- name: Refresh lockfile
# Only refresh lockfile after a clean validation to avoid committing known-bad ranges.
if: steps.validate_ranges.outcome == 'success'
run: uv lock --upgrade
working-directory: ./python
- name: Commit and push dependency updates
id: commit_updates
if: steps.validate_ranges.outcome == 'success'
run: |
BRANCH="automation/python-dependency-range-updates"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add python/packages/*/pyproject.toml python/uv.lock
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No dependency updates to commit."
exit 0
fi
git commit -m "chore: update dependency ranges"
git push --force-with-lease --set-upstream origin "${BRANCH}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create or update pull request with GitHub CLI
# Only open/update PRs for validated updates to keep automation branches trustworthy.
if: steps.validate_ranges.outcome == 'success' && steps.commit_updates.outputs.has_changes == 'true'
run: |
BRANCH="automation/python-dependency-range-updates"
PR_TITLE="Python: chore: update dependency ranges"
PR_BODY_FILE="$(mktemp)"
cat > "${PR_BODY_FILE}" <<'EOF'
This PR was generated by the dependency range validation workflow.
- Ran `uv run poe validate-dependency-bounds-project --mode upper --package "*"`
- Updated package dependency bounds
- Refreshed `python/uv.lock` with `uv lock --upgrade`
EOF
PR_NUMBER="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number')"
if [ -n "${PR_NUMBER}" ]; then
gh pr edit "${PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
else
gh pr create --base main --head "${BRANCH}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
fi
@@ -0,0 +1,91 @@
name: Python - Dev Dependency Upgrade
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
env:
UV_CACHE_DIR: /tmp/.uv-cache
jobs:
upgrade-dev-dependencies:
name: Upgrade Dev Dependencies
runs-on: ubuntu-latest
env:
UV_PYTHON: "3.13"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Set up python and install the project
uses: ./.github/actions/python-setup
with:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
env:
UV_CACHE_DIR: /tmp/.uv-cache
- name: Upgrade dev dependencies and validate workspace
run: uv run poe upgrade-dev-dependencies
working-directory: ./python
- name: Commit and push dev dependency updates
id: commit_updates
run: |
BRANCH="automation/python-dev-dependency-updates"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add python/pyproject.toml python/packages/*/pyproject.toml python/uv.lock
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No dev dependency updates to commit."
exit 0
fi
git commit -F- <<'EOF'
Python: chore: upgrade dev dependencies
EOF
git push --force-with-lease --set-upstream origin "${BRANCH}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create or update pull request with GitHub CLI
if: steps.commit_updates.outputs.has_changes == 'true'
run: |
BRANCH="automation/python-dev-dependency-updates"
PR_TITLE="Python: chore: upgrade dev dependencies"
PR_BODY_FILE="$(mktemp)"
cat > "${PR_BODY_FILE}" <<'EOF'
### Motivation and Context
This automated update refreshes Python dev dependency pins across the workspace and reruns the repo validation gates before opening a pull request.
### Description
- Ran `uv run poe upgrade-dev-dependencies`
- Refreshed dev dependency pins in workspace `pyproject.toml` files
- Refreshed `python/uv.lock` with `uv lock --upgrade`
- Reinstalled from the frozen lockfile and reran `check`, `typing`, and `test`
### Contribution Checklist
- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
- [x] All unit tests pass, and I have added new tests where possible
- [ ] **Is this a breaking change?** If yes, add "[BREAKING]" prefix to the title of the PR.
EOF
PR_NUMBER="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number')"
if [ -n "${PR_NUMBER}" ]; then
gh pr edit "${PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
else
gh pr create --base main --head "${BRANCH}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
fi
+3 -4
View File
@@ -31,10 +31,6 @@ jobs:
filters: |
python:
- 'python/**'
- '!python/AGENTS.md'
- '!python/**/AGENTS.md'
- '!python/.github/skills/*'
- '!python/.github/skills/**'
# run only if 'python' files were changed
- name: python tests
if: steps.filter.outputs.python == 'true'
@@ -92,6 +88,9 @@ jobs:
- name: Run lab type checking
run: cd packages/lab && uv run poe pyright
- name: Run lab mypy
run: cd packages/lab && uv run poe mypy
# Surface failing tests
- name: Surface failing tests
if: always()
@@ -30,31 +30,21 @@ jobs:
merge-multiple: true
- name: Display structure of downloaded files
run: ls
- name: Read and validate PR number
# Keep the artifact handoff aligned with the workflow run that produced it.
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
- name: Read and set PR number
# Need to read the PR number from the file saved in the previous workflow
# because the workflow_run event does not have access to the PR number
# The PR number is needed to post the comment on the PR
run: |
if [ ! -s pr_number ]; then
echo "PR number file 'pr_number' is missing or empty"
exit 1
fi
ARTIFACT_PR_NUMBER=$(cat pr_number)
if ! [[ "$ARTIFACT_PR_NUMBER" =~ ^[0-9]+$ ]]; then
PR_NUMBER=$(cat pr_number)
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "::error::PR number file contains invalid content"
exit 1
fi
PR_HEAD_SHA=$(gh pr view "$ARTIFACT_PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')
if [ "$PR_HEAD_SHA" != "$RUN_HEAD_SHA" ]; then
echo "::error::PR head SHA does not match the triggering workflow run"
exit 1
fi
echo "PR_NUMBER=$ARTIFACT_PR_NUMBER" >> "$GITHUB_ENV"
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"
- name: Pytest coverage comment
id: coverageComment
uses: MishaKav/pytest-coverage-comment@26f986d2599c288bb62f623d29c2da98609e9cd4 # v1.6.0
-4
View File
@@ -5,10 +5,6 @@ on:
branches: ["main", "feature*"]
paths:
- "python/**"
- "!python/AGENTS.md"
- "!python/**/AGENTS.md"
- "!python/.github/skills/*"
- "!python/.github/skills/**"
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
-2
View File
@@ -206,7 +206,6 @@ temp*/
.temp/
# AI
**/.checkpoints/
.claude/
.omc/
.omx/
@@ -214,7 +213,6 @@ WARP.md
**/memory-bank/
**/projectBrief.md
**/tmpclaude*
.kiro/
# Dependency-bound validation reports
python/scripts/dependency-*-results.json
python/scripts/dependencies/dependency-*-results.json
@@ -1125,7 +1125,7 @@ Naming (Python): N/A (Composable Components)
Supports: N
Observation: No explicit middleware/filters; modularity allows composable units but no dedicated interception hooks or callbacks for custom reading/modification mid-execution.
No specific code examples available for interception.
For more details, see the official documentation: [Atomic Agents Docs](https://brainblend-ai.github.io/atomic-agents/). No specific code examples available for interception.
#### Smolagents (Hugging Face)
-144
View File
@@ -1,144 +0,0 @@
---
status: accepted
contact: eavanvalkenburg
date: 2026-06-11
deciders: eavanvalkenburg
---
# Python minimal hosting core and pluggable channels
## Context and Problem Statement
Agent Framework has several protocol-specific hosting surfaces. App authors who want one agent or workflow on multiple protocols must compose servers, routes, middleware, session handling, and lifecycle code by hand.
We will introduce a small Python hosting core that owns the common server shape and leaves protocol details inside channel packages. The first public contract must be intentionally narrow so Python can ship a base contract before adding identity linking, proactive delivery, or multicast behavior. Other language implementations may reuse the same conceptual boundary, but this ADR records the Python decision.
## Decision Drivers
- Keep the first host easy to explain: one app, one hostable target, one or more channels.
- Reuse Agent Framework's existing agent, workflow, session, history, and checkpoint primitives.
- Let channel packages own protocol parsing, protocol responses, authentication details, and native command surfaces.
- Make session continuity explicit through a channel-supplied `ChannelSession(isolation_key=...)`.
- Avoid approving cross-channel identity and delivery semantics before their safety model is reviewed.
## Considered Options
1. Keep only protocol-specific hosts.
2. Ship a large hosting core with identity linking, authorization, background delivery, active-channel routing, and multicast in v1.
3. Ship a minimal host/channel core now and track linking/multicast as follow-up work.
### Keep only protocol-specific hosts
- Good: no new abstraction or package surface.
- Neutral: each protocol can continue evolving independently.
- Bad: every multi-channel app still has to compose servers, lifecycle, and session handling by hand.
### Ship the large cross-channel host in v1
- Good: the richest cross-channel scenarios are available immediately.
- Neutral: the host becomes the natural place to demonstrate identity and delivery policy.
- Bad: v1 becomes a security-sensitive identity and delivery system before the safety model is reviewed.
### Ship the minimal core now
- Good: the host/channel boundary can be implemented, tested, and explained without solving linking and durable delivery at the same time.
- Neutral: apps that need richer behavior must build it locally or wait for ADR-0028 follow-up work.
- Bad: proactive delivery and multicast scenarios are deliberately absent from v1.
## Decision Outcome
Chosen option: **minimal host/channel core now, follow-up enhancements later**.
`AgentFrameworkHost` owns:
- one application object,
- one hostable target (`SupportsAgentRun` agent-compatible object or a `Workflow`), and
- one or more channels.
Channels own:
- contributed routes, middleware, commands, and lifecycle callbacks,
- protocol-native request parsing into `ChannelRequest`,
- protocol-native rendering of the originating response, and
- any channel-specific authentication or signature validation.
The host owns:
- route/lifecycle aggregation,
- invocation of the target,
- `ChannelSession(isolation_key=...)` to `AgentSession` resolution and caching,
- `reset_session(isolation_key=...)`,
- host-level middleware, including Foundry isolation middleware only when the Foundry hosting environment flag is present,
- invocation of per-channel hooks (`ChannelRunHook`, `ChannelResponseHook`, `ChannelStreamUpdateHook`), and
- workflow checkpoint wiring through an explicit `checkpoint_location`.
`ChannelIdentity`, when present, is request metadata only. In v1 it is not a linking, authorization, or delivery key.
### Trust boundary for `isolation_key`
The host treats `ChannelSession.isolation_key` as a session partition key, not as proof of identity. Channels or host middleware must authenticate and authorize any externally supplied value before passing it to the host. For example, a Responses caller must not be allowed to choose an arbitrary `previous_response_id` or header-derived key unless the platform or middleware has already established that the caller owns that conversation. The host deliberately does not infer that trust from the string itself.
### Hook ownership
Channels provide hook configuration and protocol-native context. The host invokes those hooks as part of the common invocation pipeline:
- `ChannelRunHook` runs after channel parsing and before target invocation.
- `ChannelResponseHook` runs after target invocation and before the originating channel serializes its response.
- `ChannelStreamUpdateHook` is applied by the host while the channel consumes streamed updates because streaming serialization is protocol-specific.
`ChannelStreamUpdateHook` is an update hook, not a final-response sanitizer. Channels that use it for redaction or filtering must also apply equivalent policy to any final response they render. Channels choose whether the response is streaming before run hooks execute.
This keeps hook call conventions centralized while leaving protocol payload parsing and response formatting in channel packages.
### State owned by v1
`state_dir` is limited to host-owned local files for reset-session aliases and workflow checkpoint path derivation. It does not store linked identities, active-channel state, response-routing state, continuation records, durable runner queues, or delivery attempts. Those storage concerns belong to ADR-0028.
## Non-goals for v1
The following are deliberately **not** part of the v1 contract:
- cross-channel identity linking (`IdentityLinker`, `local_identity_link`, or `agent-framework-hosting-entra`),
- identity allowlists or authorization policy (`IdentityAllowlist`, `AuthPolicy`),
- response routing beyond the originating channel (`ResponseTarget`, active channel, specific linked channel, `all_linked`),
- push or payload codecs (`ChannelPush`, `ChannelPushCodec`),
- background/continuation delivery,
- durable task runners (`DurableTaskRunner`, `InProcessTaskRunner`),
- retry/replay policy (`RetryPolicy`),
- fan-out, multicast, or all-linked delivery,
- confidentiality tiers and `LinkPolicy`, and
- a host-level multi-agent router.
These areas are follow-up enhancements covered by [ADR-0028](0028-hosting-linking-multicast-enhancements.md). They are not prerequisites for shipping or using the v1 host.
## Consequences
Positive:
- The host/channel model can be implemented and tested without designing a security-sensitive identity graph.
- Existing and new channel packages can share one Starlette app, middleware stack, lifecycle, and target invocation path.
- Session continuity is explicit and debuggable: two channels share history only when they produce the same `isolation_key`.
- Hook invocation is centralized in the host, so channels do not each invent the call convention.
Negative:
- Apps that need OAuth linking, allowlists, proactive messages, or multicast must continue to implement those behaviors outside the v1 host.
- Some richer cross-channel scenarios from the original design move to a separate decision and validation cycle.
- The host must document `isolation_key` trust clearly because it now provides the shared session boundary.
## Validation Gates
Before this ADR is accepted:
- A sample can expose one target on multiple channels with one `AgentFrameworkHost` and no handwritten Starlette route composition.
- Built-in channel tests prove that routes, commands, startup, and shutdown callbacks are contributed by channels and aggregated by the host.
- Session tests prove that identical `ChannelSession.isolation_key` values resolve to the same cached `AgentSession`, and `reset_session` rotates that mapping.
- Channel tests prove that each channel renders only its own originating response; there is no host-level push, multicast, or active-channel delivery path.
- Workflow tests or samples use an explicit `checkpoint_location`.
- Foundry isolation middleware is documented and covered by integration or contract tests, including the non-Foundry case where raw isolation headers are ignored.
- The v1 API and packages do not expose the removed symbols or packages listed in [Non-goals for v1](#non-goals-for-v1).
- The Python spec is updated to match this simplified contract and uses "public", "stable", or "released" terminology for Agent Framework APIs.
## More Information
- Follow-up linking and multicast ADR: [ADR-0028](0028-hosting-linking-multicast-enhancements.md)
@@ -1,132 +0,0 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-06-11
deciders: eavanvalkenburg
---
# Hosting linking and multicast enhancements
## Context and Problem Statement
[ADR-0027](0027-hosting-channels.md) defines the minimal v1 hosting core: originating-channel responses, explicit `ChannelSession.isolation_key`, and no host-level identity linking, push, multicast, background delivery, or durable runners.
This ADR tracks the richer cross-channel behaviors that were removed from v1. These enhancements are **follow-up work** and are **not prerequisites** for shipping, using, or stabilizing the v1 host/channel core.
## Decision Drivers
- Cross-channel continuity must not create accidental cross-user, cross-tenant, or cross-channel data leaks.
- Non-originating delivery must be observable, idempotent, retryable, and supportable.
- Protocol payloads must remain channel-native while still being safe to persist and replay.
- App authors need opt-in policy controls, not hidden defaults.
- The enhancement stack should layer on top of the v1 host without reshaping the minimal channel contract.
## Enhancement Areas
The follow-up design should cover these capabilities together because they share identity, storage, delivery, and replay concerns:
- **Cross-channel identity linking** — a user can connect multiple `ChannelIdentity` values to one channel-neutral `isolation_key`.
- **Authorization and allowlist policy** — channels or hosts can require verified identity, allow specific native identities or claims, and deny unknown callers.
- **Non-originating response delivery** — a run can respond somewhere other than the request's originating protocol when explicitly configured.
- **Active-channel routing** — delivery can target the most recently observed linked channel for an `isolation_key`.
- **Multicast / all-linked delivery** — delivery can fan out to every linked channel or a selected set.
- **Background runs and continuation tokens** — long-running requests can return immediately and complete later, with a polling/status fallback.
- **Durable delivery runners** — delivery work can survive process restarts and support dead-letter handling.
- **Retry and replay semantics** — delivery attempts are bounded, deduplicated, and safe to replay.
- **Payload serialization** — channel-specific payloads can be persisted, redacted, versioned, and reconstructed without losing protocol fidelity.
Candidate API names from the broader design (`IdentityLinker`, `IdentityAllowlist`, `AuthPolicy`, `ResponseTarget`, `ChannelPush`, `ChannelPushCodec`, `DurableTaskRunner`, `InProcessTaskRunner`, `RetryPolicy`, `LinkPolicy`) remain design vocabulary for this ADR. They are not approved v1 APIs.
## Considered Options
### Option A — Leave all behavior to applications
Applications implement linking, authorization, push, retry, and serialization independently.
- Good: the hosting core stays very small.
- Neutral: advanced apps can still build what they need.
- Bad: every app must solve the same security and delivery problems, likely inconsistently.
### Option B — Add the full enhancement stack to v1
The first host release includes linking, authorization, active channel, multicast, background runs, durable runners, and codecs.
- Good: the original cross-channel experience is available immediately.
- Neutral: samples can demonstrate rich end-to-end flows.
- Bad: v1 becomes security-sensitive, storage-heavy, and harder to stabilize.
### Option C — Layer opt-in enhancement packages after v1
Ship the minimal host first, then add linking, authorization, and delivery packages behind explicit configuration.
- Good: v1 remains simple while leaving room for a reviewed, supportable enhancement stack.
- Neutral: apps that need advanced delivery wait for follow-up packages.
- Bad: the first release does not satisfy proactive or all-linked scenarios.
### Option D — Build only platform-specific integrations
Implement linking and proactive delivery separately in Telegram, Activity Protocol, Discord, and future channels.
- Good: each package can match its protocol exactly.
- Neutral: some shared abstractions may emerge later.
- Bad: cross-channel behavior becomes fragmented and hard to reason about.
## Decision Outcome
Proposed direction: **Option C — layered opt-in enhancement packages after v1**.
The minimal host remains the foundation. Follow-up packages may add linking, authorization, delivery, and durable execution, but must be explicitly enabled and must pass the validation gates below before becoming part of the public contract.
## Safety Requirements
### Threat model
The design must account for:
- spoofed channel-native identities,
- stolen or replayed link challenges,
- cross-tenant or cross-confidentiality data leakage,
- unsolicited proactive messages,
- malicious payloads persisted for replay,
- denial-of-service through fan-out or retry storms, and
- privacy leakage through logs, metrics, or support tooling.
Required mitigations include verified identity claims where available, signed and expiring link challenges, explicit user consent, per-channel capability checks, default-deny policy options, tenant partitioning, and uninformative denial messages on shared channels.
### Idempotency and replay
Exactly-once delivery is not a realistic guarantee. The design must provide:
- stable run, continuation, and delivery-attempt identifiers,
- channel-level idempotency keys where protocols support them,
- bounded retry with jitter and explicit terminal states,
- replay windows and expiration,
- duplicate suppression for persisted attempts, and
- clear semantics for "delivered", "accepted by platform", and "observed by user".
### Storage
Enhancement storage must stay distinct from v1 `AgentSession` history and workflow checkpoints unless an implementation deliberately backs them with the same physical store.
Stored data should be schema-versioned, minimized, encrypted or otherwise protected as appropriate, and partitioned by tenant/project. Link records, continuation records, active-channel state, delivery attempts, dead letters, and serialized payloads need independent TTL and deletion policies.
### Observability and support
The design must include structured logs, traces, and metrics for link attempts, authorization decisions, delivery scheduling, retries, replay, and dead-letter outcomes. Logs must avoid message content and sensitive identity claims by default. Operators need a way to inspect, revoke, replay, or purge stuck records safely.
## Validation Gates
Before these enhancements are accepted:
- A reviewed threat model covers identity linking, authorization, non-originating delivery, multicast, and replay.
- Cross-channel linking tests prove a verified identity can link two channels and that unlink/deny paths do not leak information.
- Authorization tests cover native-id allowlists, verified-claim allowlists, default-deny behavior, and misconfiguration failures.
- Delivery tests cover originating-only, specific-channel, active-channel, selected-channel, and all-linked routing.
- Background/continuation tests cover polling fallback, cancellation or expiration, process restart, retry, and dead-letter behavior.
- Codec tests prove payloads are versioned, redacted where needed, backward compatible, and rejected safely when unknown.
- Multicast tests prove fan-out is bounded, independently retried, and idempotent per destination.
- Observability tests or manual validation prove support operators can correlate a request to delivery attempts without exposing sensitive content.
## Relationship to ADR-0027
ADR-0027 remains valid without any of these enhancements. This ADR extends the hosting model only after the safety, storage, and support requirements above are satisfied.
@@ -1,641 +0,0 @@
---
status: proposed
contact: sergeymenshykh
date: 2026-06-23
deciders: sergeymenshykh
---
# Skills Over MCP: Implementation Design Options
This document explores design options for two SEP-2640 features. The decisions are not yet finalized.
- **Part 1: MCP Resource Template Skills** - skills described by a URI template with variables that must be resolved before loading.
- **Part 2: Direct Skill References** - reading `skill://` URIs referenced directly (e.g., in server instructions) without being listed in the index.
## Part 1: MCP Resource Template Skills
### Context and Problem Statement
The `AgentMcpSkillsSource` currently only supports `skill-md` type entries from `skill://index.json` (support for `archive` type is planned). The SEP-2640 specification also defines `mcp-resource-template` entries: **parameterized skill namespaces** described by a URI template with variables (e.g., `{product}`) that resolve to concrete `SKILL.md` URIs. Rather than materializing every skill in the index, the template's variables must be resolved before a skill can be loaded.
### Index Entry Format
```json
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "git-workflow",
"type": "skill-md",
"description": "Follow this team's Git conventions for branching and commits",
"url": "skill://git-workflow/SKILL.md"
},
{
"type": "mcp-resource-template",
"description": "Per-product documentation skill",
"url": "skill://docs/{product}/SKILL.md"
}
]
}
```
Key differences from `skill-md`:
| Field | `skill-md` | `mcp-resource-template` |
|-------|------------|-------------------------|
| `name` | Required (the skill name) | **Omitted** (represents many skills) |
| `type` | `"skill-md"` | `"mcp-resource-template"` |
| `url` | Concrete URI to `SKILL.md` | URI template with variables |
| `description` | Describes the skill | Describes the addressable skill space |
### Use Cases
Template skills address two scenarios where listing concrete skills is impractical:
- **Large skill catalogs** - too many skills to enumerate every entry in the index.
- **Dynamically generated skills** - skill content generated on the fly from parameters, so the set of valid skills is not known at index-creation time.
### How Template Skills Are Consumed
Per SEP-2640, the consumption flow relies on the MCP `completion/complete` method:
1. **Server registers a resource template** - The MCP server registers the same `url` value (e.g., `skill://docs/{product}/SKILL.md`) as an MCP [resource template](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-templates), wiring template variables to the [completion API](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion).
2. **Host reads `skill://index.json`** - Discovers the template entry with `type: "mcp-resource-template"`.
3. **Host surfaces template in UI** - Presents the template as an interactive discovery point where the user fills in variables.
4. **Host calls `completion/complete`** - For each template variable (e.g., `{product}`), the host calls the MCP completion API to get possible values from the server:
```json
{
"method": "completion/complete",
"params": {
"ref": {
"type": "ref/resource",
"uri": "skill://docs/{product}/SKILL.md"
},
"argument": {
"name": "product",
"value": ""
}
}
}
```
The server responds with possible completions:
```json
{
"completion": {
"values": ["widgets", "billing", "auth", "payments"],
"hasMore": false,
"total": 4
}
}
```
5. **User selects a value** - The user picks a value (e.g., `"billing"`) from the list.
6. **Host resolves the URI** - The template `skill://docs/{product}/SKILL.md` becomes the concrete URI `skill://docs/billing/SKILL.md`.
7. **Host reads the resolved skill** - Calls `resources/read` with the concrete URI and proceeds as with any `skill-md` skill.
### Potential Implementation Options
### Option 1: Callback on `AgentMcpSkillsSource` for Variable Value Selection
Add a callback to `AgentMcpSkillsSource` (or its options) that is invoked for each `mcp-resource-template` entry to let the caller select variable values.
**Flow:**
1. `AgentMcpSkillsSource.GetSkillsAsync()` reads `skill://index.json`
2. For each entry with `type: "mcp-resource-template"`:
- Parse the URI template to extract variable names (e.g., `{product}`)
- Call the MCP `completion/complete` API to get possible values for each variable
- Invoke the caller-provided callback with the variable name, description, and possible values
- The callback returns a selected value and a `bool` indicating whether to include the skill
3. Resolve the URI template with the selected values
4. Create an `AgentMcpSkill` from the resolved URI and add it to the skills list
**API sketch:**
```csharp
public delegate Task<(string? SelectedValue, bool IncludeSkill)> McpTemplateVariableSelector(
string templateDescription,
string variableName,
IReadOnlyList<string> possibleValues,
CancellationToken cancellationToken);
// Usage via builder:
var provider = new AgentSkillsProviderBuilder()
.UseMcpSkills(mcpClient, options => {
options.TemplateVariableSelector = async (description, variable, values, ct) =>
{
// Present to user, return selection
var selected = PromptUser(variable, values);
return (selected, IncludeSkill: selected is not null);
};
})
.Build();
```
**Pros:**
- Simple implementation
- Easy to understand and use
**Cons:**
- Cannot be used in server-side scenarios where there is no interactive user at skill-discovery time
- Does not integrate with the agent's conversational flow
---
### Option 2: Integrate into Agent Conversation via `ChatClientAgent` Decorator
Model the template variable resolution as a request/response interaction within the agent's conversational loop.
**Flow:**
1. A `DelegatingAIAgent` decorator (e.g., `McpTemplateSkillResolutionAgent`) intercepts `RunAsync`/`RunStreamingAsync` calls and checks whether the inner agent has an `AgentSkillsProvider` with an `AgentMcpSkillsSource` containing unresolved template entries. The check is performed via `GetService<AgentMcpSkillsSource>()` on the `AgentSkillsProvider`, which delegates to a `GetService` method on the `AgentSkillsSource` base class.
2. The decorator calls an internal member on `AgentMcpSkillsSource` to get the list of `mcp-resource-template` entries from the index. The `AgentMcpSkillsSource` needs to be extended with an internal member that exposes unresolved template entries separately from concrete skills.
3. For each template entry, the decorator calls an internal member on `AgentMcpSkillsSource` to retrieve possible values for the template's variables via the MCP `completion/complete` API.
4. For each variable needing resolution, the decorator returns an `McpResourceTemplateValueRequestContent` (inherits from MEAI's `InputRequestContent`) in the agent response - bypassing the call to the inner agent. The content carries the template description, variable name, and possible values.
5. The user app receives the response, identifies the `McpResourceTemplateValueRequestContent` content type, and displays UI to the user showing the variable name and possible values, or forwards it further downstream if the user app is a service.
6. The user selects a value, and the user app calls the agent again with a corresponding `McpResourceTemplateValueResponseContent` (inherits from MEAI's `InputResponseContent`) containing the selected value. The `RequestId` property (inherited from the base classes) correlates the response with the original request.
7. The decorator identifies the response content and provides the resolved values to `AgentMcpSkillsSource` so it can use them when constructing concrete skills.
8. Having resolved all template variables, the decorator calls `RunAsync`/`RunStreamingAsync` on the inner agent.
9. The inner agent invokes the `AgentSkillsProvider`, which calls `AgentMcpSkillsSource.GetSkillsAsync()`. The source now has all resolved variable values and constructs concrete `AgentMcpSkill` instances from the resolved URIs, so it can provide the skill content if requested by the model.
**API sketch:**
```csharp
// New content types inheriting from MEAI's InputRequestContent/InputResponseContent:
public sealed class McpResourceTemplateValueRequestContent : InputRequestContent
{
public string TemplateDescription { get; }
public string VariableName { get; }
public IReadOnlyList<string> PossibleValues { get; }
public string TemplateUrl { get; }
}
public sealed class McpResourceTemplateValueResponseContent : InputResponseContent
{
public string SelectedValue { get; }
public string TemplateUrl { get; }
}
// Decorator usage:
var provider = new AgentSkillsProviderBuilder()
.UseMcpSkills(mcpClient)
.Build();
AIAgent agent = new ChatClientAgent(chatClient, new ChatClientAgentOptions
{
AIContextProviders = [provider],
});
agent = new McpTemplateSkillResolutionAgent(agent);
```
**Pros:**
- Works in server-side scenarios
- Fits the existing `DelegatingAIAgent` decorator pattern
- Can be composed with other decorators (tool approval, etc.)
**Cons:**
- Complex implementation
- Requires user app awareness of the new content types
- Users need to know that an additional decorator is required for handling MCP template skills, in addition to registering the MCP skills source
- Resolved template variable values must be persisted across conversation turns so the decorator does not re-prompt on subsequent agent runs within the same session
**Note:** This writeup is high-level and may miss details that could change the design. A POC would be needed to validate the approach.
### Open Questions
1. **Completion API limit** - The MCP completion API returns at most 100 values per request and provides no offset/cursor mechanism for enumeration. If a variable has more than 100 possible values, it's unclear how to retrieve the rest - the API only supports prefix-based filtering (typeahead), not bulk pagination.
2. **Multi-variable templates** - A template like `skill://{org}/{product}/SKILL.md` has multiple variables. Should they be resolved sequentially (org first, then product - since product values may depend on org) or presented together?
3. **Caching** - Should resolved template values be saved in the `AgentSession` so the user isn't re-prompted on every agent run? How should they be persisted between sessions?
---
## Part 2: Direct Skill References
This part covers how to let the model read `skill://` URIs referenced directly (e.g., in an MCP server's `instructions`, in a resource, or in another skill's content) without being listed in `skill://index.json`.
### How MCP Skills and Relative Links Work Today
The `AgentMcpSkillsSource` discovers skills by reading the well-known `skill://index.json` resource from the MCP server:
```json
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "unit-converter",
"type": "skill-md",
"description": "Convert between common units.",
"url": "skill://unit-converter/SKILL.md"
},
{
"name": "currency-converter",
"type": "skill-md",
"description": "Convert between world currencies using live rates.",
"url": "skill://currency-converter/SKILL.md"
}
]
}
```
For each `skill-md` entry it creates an `AgentMcpSkill` instance - frontmatter (name/description) comes straight from the entry. The `AgentSkillsProvider` lists the discovered skills in the model's context (name + description):
```xml
<available_skills>
<skill>
<name>unit-converter</name>
<description>Convert between common units.</description>
</skill>
<skill>
<name>currency-converter</name>
<description>Convert between world currencies using live rates.</description>
</skill>
</available_skills>
```
It also provides functions to the model so it can load a skill and access its resources:
```csharp
// Loads the full content of a specific skill.
load_skill(string skillName)
// Reads a resource associated with a skill (references, assets, dynamic data).
read_skill_resource(string skillName, string resourceName)
```
The model calls `load_skill("unit-converter")` and receives the skill content:
```markdown
---
name: unit-converter
description: Convert between common units.
---
## Usage
For the full conversion table, see references/units-table.md.
```
The skill body references `references/units-table.md` by relative path. The model calls `read_skill_resource("unit-converter", "references/units-table.md")` and receives the resource content:
```markdown
# Unit Conversion Table
| From | To | Factor |
| miles | km | 1.60934 |
| kg | lbs | 2.20462 |
```
### Direct Reference Examples
A `skill://` URI can appear in any of these locations:
**Server instructions** - the MCP server advertises a skill the model should load:
```text
Follow our coding standards. Load skill://code-standards/SKILL.md for details.
```
**A skill body** - a skill's `SKILL.md` links to a sibling resource:
```markdown
---
name: code-standards
description: Coding standards and conventions.
---
## Naming
Follow the naming rules in skill://code-standards/references/naming.md.
```
**A resource** - the linked resource holds the actual content:
```markdown
# Naming Rules
- Use PascalCase for public members and type names.
- Use camelCase for locals and parameters.
- Prefix interfaces with `I` (e.g. `ISkillReader`).
- Suffix async methods with `Async`.
For examples, see skill://code-standards/references/naming-examples.md.
```
How can the model access content by direct reference?
### Function for Reading Direct Skill References
### Option 1: Extend existing `load_skill` and `read_skill_resource` functions
```csharp
// Added optional 'origin' and a direct skill:// URI is passed in 'skillName'.
load_skill(string skillName, string? origin = null)
// Added optional 'origin', made 'skillName' optional, and a direct skill:// URI is passed in 'resourceName'.
read_skill_resource(string resourceName, string? skillName = null, string? origin = null)
```
The optional `origin` identifies the source/MCP server that should handle the direct URI.
| Case | Call |
|------|------|
| Load skill | `load_skill("commit-guidelines")` |
| Relative resource | `read_skill_resource("commit-guidelines", "examples/COMMIT_EXAMPLES.md")` |
| `skill://` link (skill) | `load_skill(skillName: "skill://commit-guidelines/SKILL.md", origin: "DirectRefServer")` |
| `skill://` link (resource) | `read_skill_resource(resourceName: "skill://commit-guidelines/examples/COMMIT_EXAMPLES.md", origin: "DirectRefServer")` |
**Pros:**
- No new functions added: existing tool surface stays at two functions.
**Cons:**
- Unreliable on some models (gpt-4o, gpt-4.1-mini): it often omits `origin` when it should not or calls the wrong function.
- Optional parameters create silent ambiguity - the model can pass `origin` for non-MCP skills or omit it for `skill://` URIs.
### Option 2 (Proposed): Add a dedicated `read_skill_uri` function alongside existing ones
```csharp
// Existing functions stay unchanged.
load_skill(string skillName)
read_skill_resource(string skillName, string resourceName)
// New function added alongside: reads content by direct skill:// URI.
read_skill_uri(string uri, string origin)
```
| Case | Call |
|------|------|
| Load skill | `load_skill("commit-guidelines")` |
| Relative resource | `read_skill_resource("commit-guidelines", "examples/COMMIT_EXAMPLES.md")` |
| `skill://` link (skill) | `read_skill_uri(uri: "skill://commit-guidelines/SKILL.md", origin:"DirectRefServer")` |
| `skill://` link (resource) | `read_skill_uri(uri: "skill://commit-guidelines/examples/COMMIT_EXAMPLES.md", origin: "DirectRefServer")` |
**Pros:**
- Purely additive - no changes to existing functions needed; `read_skill_uri` can be deferred and added later when direct `skill://` reference support is needed.
- Granular approval: each function can have its own approval gate (like the existing `ScriptApproval` for `run_skill_script`), making per-operation approval for skill loading, resource reading, and direct URI access straightforward to add.
- Both `uri` and `origin` are required - no silent misuse through optional parameters.
- Clean split: `load_skill`/`read_skill_resource` for named skills, `read_skill_uri` for `skill://` links - no parameter ambiguity.
**Cons:**
- Three read functions (`load_skill`, `read_skill_resource`, `read_skill_uri`), not counting `run_skill_script`: larger tool surface than a single-function design.
### Option 3: Collapse `load_skill` and `read_skill_resource` into a single `read_resource` function
```csharp
// Single entrypoint for all skill content. 'uri' is required; 'origin' is optional.
read_resource(string uri, string? origin = null)
```
- `uri` - what to read: a skill name, a relative resource path, or a `skill://` link.
- `origin` - determines how `uri` is interpreted:
- **omitted** → load skill by name (`uri` is the skill name).
- **skill name** → read a relative resource (`uri` is the path within that skill).
- **server name** → read content by the `skill://` link (`uri` is handled by the source identified by the `[Origin: X]` marker).
Dispatch is ordered: null `origin` routes to Case 1; if `origin` names a known skill, routes to Case 2; otherwise tries to find an `ISkillUriReader` whose `CanRead` returns true for `origin` (Case 3).
| Case | Call |
|------|------|
| Load skill | `read_resource(uri: "commit-guidelines")` |
| Relative resource | `read_resource(uri: "examples/COMMIT_EXAMPLES.md", origin: "commit-guidelines")` |
| `skill://` link (skill) | `read_resource(uri: "skill://commit-guidelines/SKILL.md", origin: "DirectRefServer")` |
| `skill://` link (resource) | `read_resource(uri: "skill://commit-guidelines/examples/COMMIT_EXAMPLES.md", origin: "DirectRefServer")` |
**Pros:**
- Minimal tool surface: one read function instead of two or three (not counting `run_skill_script`) reduces token usage and gives the model fewer choices.
**Cons:**
- No per-operation approval: all cases (skill loading, resource reading, direct URI access) share one function, so approval cannot be scoped to individual operations.
- Unreliable on gpt-4.1-mini: omits `origin` when reading `skill://` links, passes skill name as `origin` when loading a plain skill (should be omitted), and hallucinates resource names (e.g. `API_SPECIFICATION.md`) that do not exist.
---
### Origin Marker
A `skill://` URI does not carry an origin, but the model needs to provide one when reading it. The `origin` is what routes the read call to the source that can handle the URI - the provider uses it to pick the matching source. Since the URI itself carries no such hint, the MCP source injects an `[Origin: ...]` marker wherever a `skill://` URI appears, so the model can read it back and pass it as the `origin` argument.
The marker is only added when the content actually contains `skill://` references. If a piece of content (server instructions, a skill body, or a resource) has no `skill://` URIs, there is nothing for the model to read back, so no marker is injected.
Into **server instructions**, which may mention `skill://` URIs directly:
```
[Origin: code-standards-server]
Follow our coding standards. Load skill://code-standards/SKILL.md for details.
```
Into **skill bodies**, since a `SKILL.md` may reference other `skill://` URIs (a resource file or a related skill):
```
[Origin: code-standards-server]
# Code Standards
For naming conventions, load skill://code-standards/references/naming.md.
```
Into **skill resources**, since a resource may itself reference further `skill://` URIs:
```
[Origin: code-standards-server]
# Naming Rules
- Use PascalCase for public members and type names.
- Use camelCase for locals and parameters.
For examples, see skill://code-standards/references/naming-examples.md.
```
---
### Read-by-URI Capability: Interface vs Base Class Virtual Methods
Now let's look at how an `AgentSkillsSource` can opt in to reading `skill://` URIs and signal that capability to the provider.
### Option 1: New `ISkillUriReader` interface
```csharp
public interface ISkillUriReader
{
// Returns true if this reader can handle the given skill:// URI from the given origin.
bool CanRead(string uri, string origin);
// Reads and returns the content for the given skill:// URI.
Task<object?> ReadByUriAsync(string uri, string origin, CancellationToken cancellationToken = default);
}
```
Sources that support direct `skill://` URI reads - such as `AgentMcpSkillsSource` - implement this interface to opt in.
The provider discovers readers via a service locator and dispatches to the first that can handle the URI:
```csharp
// Discover all registered readers.
var readers = source.GetService<IEnumerable<ISkillUriReader>>();
// Pick the first reader that can handle the URI.
var reader = readers.FirstOrDefault(r => r.CanRead(uri, origin))
?? throw new InvalidOperationException($"No reader can handle URI '{uri}' from origin '{origin}'.");
// Delegate the read to it.
return await reader.ReadByUriAsync(uri, origin, cancellationToken);
```
The provider may treat a source implementing `ISkillUriReader` as the signal to advertise `read_skill_uri`: if at least one registered source implements the interface, the function is exposed to the model; otherwise it is not.
### Option 2 (Proposed): Virtual methods on `AgentSkillsSource` base class
```csharp
public abstract class AgentSkillsSource
{
// New members for reading by URI.
// Whether this source can read by URI; drives whether read_skill_uri is advertised. Off by default.
public virtual bool SupportsReadByUri => false;
// Returns true if this source can handle the given skill:// URI from the given origin.
public virtual bool CanReadByUri(string uri, string origin) => false;
// Reads and returns the content for the given skill:// URI.
public virtual Task<object?> ReadByUriAsync(string uri, string origin, CancellationToken cancellationToken = default)
=> Task.FromResult<object?>(null);
// Existing member.
public abstract Task<IList<AgentSkills>> GetSkillsAsync(CancellationToken cancellationToken = default);
}
```
Sources opt in by overriding, and the provider calls them directly:
```csharp
// AgentMcpSkillsSource opts in by overriding the virtuals.
public override bool SupportsReadByUri => true;
// Handles the URI when its origin matches this source's MCP server.
public override bool CanReadByUri(string uri, string origin)
=> string.Equals(origin, this.Origin, StringComparison.OrdinalIgnoreCase);
// Reads content by skill:// URI from the MCP server.
public override Task<string?> ReadByUriAsync(string uri, string origin, CancellationToken cancellationToken)
=> /* resolve uri via the MCP server identified by origin */;
```
All sources inherit the methods, so there is no type signal - `SupportsReadByUri` fills that role. The function is advertised when any registered source returns `true`.
### Comparison
| Aspect | Option 1: Interface | Option 2: Base class virtual methods |
|--------|---------------------|--------------------------------------|
| Discovery | Service locator | Direct call on source |
| Advertising signal | Interface implementation | `SupportsReadByUri` flag |
| Adding new members | Breaking change | Non-breaking |
| Complexity | Higher | Lower |
---
### Include MCP Server Instructions Into Agent Instructions
MCP server instructions may contain the `skill://` references the model needs, so we want to surface them in the agent's instructions. But they can also carry system prompts or behavioral directives irrelevant to the agent, polluting context - so inclusion is **opt-in** via the `IncludeServerInstructions` option:
```csharp
public sealed class AgentMcpSkillsSourceOptions
{
// When true, the MCP server's instructions are injected into the agent instructions. Off by default.
public bool IncludeServerInstructions { get; set; }
}
builder.UseMcpSkills(mcpClient, options => options.IncludeServerInstructions = true);
```
When enabled, the instructions travel alongside the discovered skills on `AgentSkillsResult`:
```csharp
public class AgentSkillsResult
{
// The skills discovered from the source.
public IList<AgentSkill> Skills { get; }
// The MCP server instructions, when IncludeServerInstructions is enabled; otherwise null.
public string? Instructions { get; }
}
```
The `AgentSkillsProvider` then appends them to its own skill-usage guidance when building the agent's instructions:
```csharp
var result = await source.GetSkillsAsync(cancellationToken);
var instructions = DefaultSkillsInstructionPrompt;
if (!string.IsNullOrWhiteSpace(result.Instructions))
{
// Combine the provider's skill-usage guidance with the server instructions.
instructions += Environment.NewLine + result.Instructions;
}
```
### Enabling Direct Skill References
Following direct `skill://` references is **disabled by default** and activated via an option. When enabled, the provider advertises the read function to the model, and the source injects the `[Origin: ...]` marker into all content provided by the MCP server that contains `skill://` references. When disabled, no function is advertised and no marker is injected.
```csharp
public sealed class AgentMcpSkillsSourceOptions
{
public bool EnableDirectReferences { get; set; }
}
builder.UseMcpSkills(mcpClient, options => options.EnableDirectReferences = true);
```
## Decision Outcome
### Template Variable Resolution: Callback vs Decorator (Part 1)
**Postponed.** Deferring this decision until:
- We have a concrete list of scenarios that require template variable resolution.
- The skills-over-MCP spec is released (it is still a draft, so the design may change).
- There is a strong signal of demand from users or the ecosystem.
### Function for Reading Direct Skill References (Part 2)
**Postponed.** Leaning toward **Option 2 - dedicated `read_skill_uri` function alongside existing ones** (purely additive, and each function can have its own approval gate for granular per-operation approval), but deferring the decision until:
- The skills-over-MCP spec is released (it is still a draft, so the design may change).
- There is a strong signal of demand from users or the ecosystem.
### Read-by-URI Capability: Interface vs Base Class (Part 2)
**Postponed.** Leaning toward **Option 2 - virtual methods on `AgentSkillsSource`** (non-breaking, lower complexity, and a natural fit with the existing base class hierarchy), but deferring the decision until:
- The skills-over-MCP spec is released (it is still a draft, so the design may change).
- There is a strong signal of demand from users or the ecosystem.
The method naming (`SupportsReadByUri`, `CanReadByUri`, `ReadByUriAsync`) should also be abstracted a little more before adoption, so the same members can be reused when a similar direct-reference concept is needed for other skill types (e.g. file skills).
## References
- [SEP-2640: Skills Extension](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) - Draft proposal
- [SEP-2640 Implementation Guidelines: Model-Driven Resource Loading](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/sep-draft-skills-extension.md#hosts-model-driven-resource-loading)
- [MCP Completion API](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) - Used for template variable resolution
- [MCP Resource Templates](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-templates)
- [Skills Over MCP Working Group](https://github.com/modelcontextprotocol/experimental-ext-skills)
- [Open Question #4: Multi-server skill dependencies](https://github.com/modelcontextprotocol/experimental-ext-skills/issues/39)
- [Anthropic Agent Skills - Overview](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) - Prior art: single skill entrypoint + generic file reads
- [Anthropic Agent Skills in the SDK](https://code.claude.com/docs/en/agent-sdk/skills) - The `Skill` tool exposed to the model
-320
View File
@@ -1,320 +0,0 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-06-11
deciders: eavanvalkenburg
---
# Python hosting core and pluggable channels
## Scope
This specification is the Python implementation plan for [ADR-0027](../decisions/0027-hosting-channels.md). It documents the simplified v1 host/channel contract only.
The v1 contract is:
- `AgentFrameworkHost` owns one Starlette app, one hostable target, and one or more channels.
- A hostable target is either a `SupportsAgentRun`-compatible agent or a `Workflow`.
- Channels contribute routes, middleware, commands, and lifecycle callbacks.
- Channels parse protocol-native input into `ChannelRequest`.
- Channels render their own originating response.
- Session continuity is explicit: a channel supplies `ChannelSession(isolation_key=...)`, and the host resolves/caches an `AgentSession` for that key.
- The host invokes `ChannelRunHook` and `ChannelResponseHook`; channels provide hook configuration and protocol context.
The host does not link identities, route responses to other channels, run background continuations, or multicast in v1. Those enhancements are tracked in [ADR-0028](../decisions/0028-hosting-linking-multicast-enhancements.md).
## Goals
- Let an app expose one agent or workflow on multiple protocols without handwritten Starlette composition.
- Keep protocol parsing and response formatting inside channel packages.
- Provide one session-resolution path shared by all channels.
- Keep the channel authoring surface small enough for new channels to implement.
- Preserve full-fidelity agent and workflow results until a channel decides how to render them.
## Non-goals for v1
The following are removed from the v1 implementation pass:
- `IdentityLinker`, `IdentityAllowlist`, `AuthPolicy`, and `LinkPolicy`
- `ResponseTarget`, active-channel routing, `all_linked`, fan-out, and multicast
- `ChannelPush` and `ChannelPushCodec`
- `DurableTaskRunner`, `InProcessTaskRunner`, and `RetryPolicy`
- continuation tokens and background delivery
- confidentiality tiers
- `agent-framework-hosting-entra`
- `local_identity_link`
These are follow-up design topics, not hidden requirements of the v1 host.
## Packages
| Package | Import surface | Contents |
|---|---|---|
| `agent-framework-hosting` | `agent_framework_hosting` | `AgentFrameworkHost`, channel protocols, key request/result types, hooks, `reset_session`, state-path helpers. |
| `agent-framework-hosting-responses` | `agent_framework_hosting_responses` | `ResponsesChannel`. |
| `agent-framework-hosting-invocations` | `agent_framework_hosting_invocations` | `InvocationsChannel`. |
| `agent-framework-hosting-telegram` | `agent_framework_hosting_telegram` | `TelegramChannel` and Telegram command helpers. |
| `agent-framework-hosting-activity-protocol` | `agent_framework_hosting_activity_protocol` | `ActivityProtocolChannel` for Activity Protocol over Azure Bot Service. |
| `agent-framework-hosting-discord` | `agent_framework_hosting_discord` | `DiscordChannel` and Discord command/interaction helpers. |
| `agent-framework-foundry-hosting` | `agent_framework.foundry_hosting` | Foundry isolation middleware and Foundry-backed hosting helpers usable with the v1 host. |
Channel packages may depend on their native SDKs. The core hosting package should not depend on channel SDKs or on top-level legacy protocol hosts.
## Key Types
### `AgentFrameworkHost`
The host constructor accepts:
- `target`: one `SupportsAgentRun`-compatible object or one `Workflow`
- `channels`: one or more `Channel` instances
- optional Starlette middleware
- optional `state_dir`
- optional workflow `checkpoint_location`
The host exposes:
- `app`: the canonical Starlette ASGI application
- `serve(...)`: a convenience wrapper for local serving
- `reset_session(isolation_key: str)`: rotate the cached `AgentSession` for a host-tracked conversation
`state_dir` is narrowed to v1 host-owned local files only:
- session aliases (`isolation_key` to current `AgentSession` id), and
- workflow checkpoint paths when the app chooses the host-provided file layout.
It is not a store for identity links, continuations, active-channel state, delivery attempts, or multicast payloads.
Externally supplied isolation keys are trusted only after the channel or host middleware has authenticated and authorized the caller. The host uses `isolation_key` as a partition key; the string itself is not proof of identity or ownership.
### `Channel`
A channel implements a small protocol:
- declare a stable channel id/name,
- contribute routes, middleware, commands, and lifecycle callbacks,
- parse inbound protocol data into `ChannelRequest`,
- call the host through `ChannelContext.run(...)` or `ChannelContext.run_stream(...)`, and
- serialize the returned result to the originating protocol response.
Channels own protocol authentication, signature validation, native command registration, and protocol-specific error bodies.
### `ChannelContribution`
`ChannelContribution` is the channel's host-facing contribution:
- Starlette routes and optional middleware,
- native command descriptors,
- startup and shutdown callbacks, and
- any channel-local metadata needed by the package.
The host aggregates contributions but does not interpret protocol payloads.
### `ChannelRequest`
`ChannelRequest` is the host-neutral request envelope produced by a channel. It carries:
- target input,
- optional `ChannelSession`,
- optional `ChannelIdentity`,
- options and attributes produced by the channel, and
- request metadata useful to hooks and context providers.
The host may pass attributes through to context providers and middleware. Channels should treat attributes as a documented extension bag, not as a cross-channel delivery contract.
### `ChannelSession`
`ChannelSession(isolation_key=...)` is the only v1 session-continuity mechanism.
When a request contains an isolation key:
1. The host looks up or creates the cached `AgentSession` for that key.
2. The target runs with that `AgentSession` when the target is an agent.
3. `reset_session(isolation_key)` rotates the alias so the next request starts a new conversation.
If two channels produce the same isolation key on the same host, they share the same cached session. If they produce different keys, they do not share session state.
### `ChannelIdentity`
`ChannelIdentity` is optional request metadata such as channel id, native user id, tenant id, claims, or display attributes.
In v1, `ChannelIdentity` does not link channels, authorize callers, select delivery destinations, or imply that two identities should share an `AgentSession`. A channel that wants shared history must still produce the same `ChannelSession.isolation_key`.
### Hooks
Hooks are optional and channel-owned:
- `ChannelRunHook`: runs after channel parsing and before host invocation; returns the `ChannelRequest` to execute.
- `ChannelResponseHook`: runs after target completion and before the originating channel renders a one-shot response.
- `ChannelStreamUpdateHook`: the host applies it to streamed updates before the originating channel serializes the stream.
Common uses include adapting chat text into workflow inputs, enforcing deployment-specific options, flattening rich output for text-only protocols, or filtering streamed updates for a protocol. Stream update hooks are update-only; they do not automatically sanitize `get_final_response()` output. Channels choose their response transport from the parsed protocol request before invoking run hooks.
### `HostedRunResult`
`HostedRunResult[T]` wraps the target's full-fidelity result plus the resolved `AgentSession | None`.
- Agent targets produce `HostedRunResult[AgentResponse]`.
- Workflow targets produce `HostedRunResult[WorkflowRunResult]`.
The host does not flatten, filter, or translate the result. Each channel decides how much of the result its protocol can carry.
## Host Behavior
1. `AgentFrameworkHost` builds one Starlette app and asks each channel for its contribution.
2. A channel route receives a protocol-native request.
3. The channel validates/parses the native payload and creates `ChannelRequest`.
4. The channel passes the request, optional `ChannelRunHook`, and protocol-native context to the host.
5. The host invokes `ChannelRunHook`, if configured, and receives the prepared request.
6. The host resolves an `AgentSession` from `ChannelSession.isolation_key` when present.
7. The host invokes the agent or workflow target.
8. The host wraps the result in `HostedRunResult` or the streaming equivalent.
9. The host invokes `ChannelResponseHook`, if configured, for non-streaming/final response shaping.
10. The host applies stream update hooks while the channel consumes streams; the channel renders the originating protocol response.
There is no host-level route from one channel's request to another channel's response in v1.
## Workflow Checkpoints
Workflow checkpointing is explicit. Apps either configure checkpoint storage on the workflow itself or pass a `checkpoint_location` to the host so the workflow dispatch path can use the intended file location.
`state_dir` may provide a conventional location for workflow checkpoint files, but checkpointing is still opt-in and separate from agent session history. Checkpoints are workflow-runtime state, not channel state and not identity-link state.
## Foundry Isolation Middleware
V1 keeps Foundry isolation as middleware rather than as a channel-linking feature.
The middleware is installed only when the Foundry hosting environment flag is present. In that environment it reads Foundry-provided isolation values at the trusted hosting boundary, exposes them as read-only request context for Foundry-aware history or memory providers, and rejects unsafe session resumes when the live isolation context does not match persisted session context. Outside Foundry, raw isolation headers are ignored unless an app supplies its own trusted middleware.
This middleware does not create cross-channel identity links and does not authorize non-Foundry channels.
## Current Channels
### Responses
`ResponsesChannel` exposes the OpenAI-compatible Responses API shape. It maps request body fields such as input, options, and conversation identifiers into `ChannelRequest`, and it renders Responses-compatible one-shot or streaming responses.
Responses session continuity uses a channel-selected `isolation_key`, commonly derived from a response/conversation id, caller-provided session id, Foundry isolation context, or deployment-specific request metadata.
### Invocations
`InvocationsChannel` exposes an invocation endpoint for server-side callers and tools. It maps the request body into `ChannelRequest` and renders the invocation result on the same HTTP response.
Invocations is useful for typed workflow inputs because a `ChannelRunHook` can translate the request body into the workflow's expected input type.
### Telegram
`TelegramChannel` supports webhook or polling transport, native command registration, and message rendering back to the originating Telegram chat.
The channel chooses a default `isolation_key` from Telegram-native data such as chat id, user id, or a configured user/chat scope. A `/new` or equivalent command may call `reset_session` for that isolation key.
### Activity Protocol
`ActivityChannel` supports Activity Protocol requests, typically through Azure Bot Service for Teams, Web Chat, and other Bot Framework-fronted surfaces.
The channel maps incoming `Activity` objects to `ChannelRequest` and renders a reply activity to the originating conversation. Proactive Activity delivery, active-channel routing, and all-linked fan-out are not v1 host semantics.
### Discord
`DiscordChannel` supports Discord messages, slash commands, and interactions as channel-native input.
The channel maps Discord-native user, guild, channel, thread, and interaction data into `ChannelRequest` metadata and a configured `ChannelSession.isolation_key`. It renders the result to the originating Discord response path.
## High-level Samples
### One agent on Responses
```python
host = AgentFrameworkHost(
target=agent,
channels=[ResponsesChannel()],
)
app = host.app
```
### One agent on multiple channels
```python
host = AgentFrameworkHost(
target=agent,
channels=[
ResponsesChannel(),
InvocationsChannel(),
TelegramChannel(bot_token=os.environ["TELEGRAM_BOT_TOKEN"]),
],
)
host.serve(host="localhost", port=8000)
```
The host owns one Starlette app. Each channel contributes its own routes and renders its own response.
### Adapting a request before execution
```python
from dataclasses import replace
def enforce_options(request: ChannelRequest) -> ChannelRequest:
options = dict(request.options or {})
options["temperature"] = 0
return replace(request, options=options)
host = AgentFrameworkHost(
target=agent,
channels=[ResponsesChannel(run_hook=enforce_options)],
)
```
### Workflow with explicit checkpoints
```python
host = AgentFrameworkHost(
target=workflow,
channels=[InvocationsChannel(run_hook=adapt_to_workflow_input)],
checkpoint_location=Path("./.af-hosting/workflow_checkpoints"),
)
```
The hook adapts channel-native input to the workflow's typed input. Checkpoints use the explicit workflow checkpoint location, not identity-link or delivery storage.
### Message channel reset command
```python
async def new_chat(context):
if context.request.session is not None:
await context.host.reset_session(context.request.session.isolation_key)
await context.reply("Started a new conversation.")
```
Telegram, Activity Protocol, and Discord can expose equivalent native commands when their protocols support them.
## Follow-up Enhancements
See [ADR-0028](../decisions/0028-hosting-linking-multicast-enhancements.md) for the deferred design covering:
- cross-channel identity linking,
- authorization and allowlists,
- non-originating response delivery,
- active-channel routing,
- multicast and all-linked delivery,
- background runs and continuation tokens,
- durable delivery runners,
- retry/replay semantics, and
- payload serialization.
Those enhancements must layer on top of this v1 contract without requiring v1 users to adopt them.
## Validation Gates
The Python implementation should be considered complete when:
- a sample uses one `AgentFrameworkHost` with multiple channels and no manual Starlette route composition,
- each current channel has contract tests for route contribution, lifecycle, request parsing, hooks, and originating response rendering,
- session tests prove shared `isolation_key` values share an `AgentSession` and `reset_session` rotates it,
- workflow tests or samples use explicit `checkpoint_location`,
- Foundry isolation middleware is covered by integration or contract tests,
- no v1 package exposes the removed linking, multicast, durable-runner, or continuation APIs, and
- this spec and ADR-0027 remain aligned.
-1
View File
@@ -1 +0,0 @@
../../../.github/skills/pull-requests
+2 -4
View File
@@ -1,4 +1,4 @@
---
---
name: verify-samples-tool
description: How to use the verify-samples tool to run, verify, and manage sample definitions in the Agent Framework repository. Use this when adding, updating, or running sample verification.
---
@@ -157,7 +157,7 @@ new SampleDefinition
new SampleDefinition
{
Name = "Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -223,5 +223,3 @@ new SampleDefinition
SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
},
```
-4
View File
@@ -10,10 +10,6 @@ See `./.github/skills/build-and-test/SKILL.md` for detailed instructions on buil
See `./.github/skills/project-structure/SKILL.md` for an overview of the project structure.
## Pull Requests
See `./.github/skills/pull-requests/SKILL.md` for guidance on writing PR descriptions and handling/resolving PR review comments.
### Core types
- `AIAgent`: The abstract base class that all agents derive from, providing common methods for interacting with an agent.
+16 -22
View File
@@ -12,7 +12,7 @@
<ItemGroup>
<!-- Aspire.* -->
<PackageVersion Include="Anthropic" Version="12.20.0" />
<PackageVersion Include="Anthropic.Foundry" Version="0.6.0" />
<PackageVersion Include="Anthropic.Foundry" Version="0.5.0" />
<PackageVersion Include="Aspire.Hosting" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
<PackageVersion Include="Aspire.Azure.AI.Inference" Version="13.1.0-preview.1.25616.3" />
@@ -21,16 +21,15 @@
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0" />
<PackageVersion Include="MessagePack" Version="3.1.7" /> <!-- Transitive dependency of Aspire pinned to newer version due to vulnerability in 2.5.192 -->
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.AgentServer.Core" Version="1.0.0-beta.25" />
<PackageVersion Include="Azure.AI.AgentServer.Invocations" Version="1.0.0-beta.4" />
<PackageVersion Include="Azure.AI.AgentServer.Responses" Version="1.0.0-beta.5" />
<PackageVersion Include="Azure.Search.Documents" Version="12.0.0" />
<PackageVersion Include="Azure.AI.Projects" Version="2.1.0-beta.3" />
<PackageVersion Include="Azure.AI.Projects" Version="2.1.0-beta.2" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.10" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageVersion Include="Azure.Core" Version="1.57.0" />
<PackageVersion Include="Azure.Core" Version="1.56.0" />
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
<PackageVersion Include="DotNetEnv" Version="3.1.1" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.5.0" />
@@ -42,19 +41,19 @@
<!-- Newtonsoft.Json -->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.8" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.6" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="Microsoft.Bcl.Memory" Version="10.0.5" />
<PackageVersion Include="System.ClientModel" Version="1.13.0" />
<PackageVersion Include="System.ClientModel" Version="1.12.0" />
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.8" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.6" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.5" />
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.5" />
<PackageVersion Include="System.Text.Json" Version="10.0.8" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.8" />
<PackageVersion Include="System.Text.Json" Version="10.0.6" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.6" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
<!-- OpenTelemetry -->
@@ -73,12 +72,12 @@
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.5.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.5.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.3.0-preview.1.26109.11" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.5.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Compliance.Abstractions" Version="10.5.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
@@ -87,12 +86,12 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
@@ -139,15 +138,10 @@
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
<!-- Valkey -->
<!-- Redis -->
<PackageVersion Include="StackExchange.Redis" Version="2.10.1" />
<!-- Valkey -->
<PackageVersion Include="Valkey.Glide" Version="1.1.0" />
<!-- Console UX -->
<PackageVersion Include="Spectre.Console" Version="0.49.1" />
<!-- AWS -->
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.6.10" />
<!-- Test -->
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.22" />
+59 -76
View File
@@ -1,4 +1,4 @@
<Solution>
<Solution>
<Configurations>
<BuildType Name="Debug" />
<BuildType Name="Publish" />
@@ -22,19 +22,20 @@
</Folder>
<Folder Name="/Samples/02-agents/AgentProviders/">
<File Path="samples/02-agents/AgentProviders/README.md" />
<Project Path="samples/02-agents/AgentProviders/a2a/Agent_With_A2A/Agent_With_A2A.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
<Project Path="samples/02-agents/AgentProviders/custom/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
<Project Path="samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot/Agent_With_GitHubCopilot.csproj" />
<Project Path="samples/02-agents/AgentProviders/google-gemini/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
<Project Path="samples/02-agents/AgentProviders/ollama/Agent_With_Ollama/Agent_With_Ollama.csproj" />
<Project Path="samples/02-agents/AgentProviders/onnx/Agent_With_ONNX/Agent_With_ONNX.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_GitHubCopilot/Agent_With_GitHubCopilot.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
</Folder>
<Folder Name="/Samples/05-end-to-end/DevUIAspireIntegration/">
<Project Path="samples/05-end-to-end/DevUIAspireIntegration/DevUIIntegration.AppHost/DevUIIntegration.AppHost.csproj" />
@@ -120,7 +121,6 @@
</Folder>
<Folder Name="/Samples/02-agents/Harness/">
<File Path="samples/02-agents/Harness/README.md" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step01_MeetYourClaw/Claw_Step01_MeetYourClaw.csproj" />
<Project Path="samples/02-agents/Harness/ConsoleReactiveComponents/ConsoleReactiveComponents.csproj" />
<Project Path="samples/02-agents/Harness/ConsoleReactiveFramework/ConsoleReactiveFramework.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Shared_Console/Harness_Shared_Console.csproj" />
@@ -129,7 +129,6 @@
<Project Path="samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Harness_Step02_Research_WithBackgroundAgents.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Step03_DataProcessing/Harness_Step03_DataProcessing.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Step04_CodeExecution/Harness_Step04_CodeExecution.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Step05_Loop/Harness_Step05_Loop.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
@@ -139,43 +138,43 @@
<File Path="samples/02-agents/DevUI/README.md" />
<Project Path="samples/02-agents/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentProviders/anthropic/">
<File Path="samples/02-agents/AgentProviders/anthropic/README.md" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step04_UsingSkills/Agent_Anthropic_Step04_UsingSkills.csproj" />
<Folder Name="/Samples/02-agents/AgentWithAnthropic/">
<File Path="samples/02-agents/AgentWithAnthropic/README.md" />
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.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" />
</Folder>
<Folder Name="/Samples/02-agents/AgentProviders/foundry/">
<File Path="samples/02-agents/AgentProviders/foundry/README.md" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step24_CodeInterpreterFileDownload/Agent_Step24_CodeInterpreterFileDownload.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Agent_Step25_FoundryToolboxMcp.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Agent_Step26_FoundryToolboxMcpSkills.csproj" />
<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" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/Agent_Step24_CodeInterpreterFileDownload.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/Agent_Step25_FoundryToolboxMcp.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/Agent_Step26_FoundryToolboxMcpSkills.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/Evaluation/">
<Project Path="samples/02-agents/Evaluation/Evaluation_CustomEvals/Evaluation_CustomEvals.csproj" />
@@ -195,17 +194,15 @@
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/AgentWithMemory_Step03_MemoryUsingValkey.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentProviders/openai/">
<File Path="samples/02-agents/AgentProviders/openai/README.md" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step06_CodeInterpreterFileDownload/Agent_OpenAI_Step06_CodeInterpreterFileDownload.csproj" />
<Folder Name="/Samples/02-agents/AgentWithOpenAI/">
<File Path="samples/02-agents/AgentWithOpenAI/README.md" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step06_CodeInterpreterFileDownload/Agent_OpenAI_Step06_CodeInterpreterFileDownload.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentWithRAG/">
<File Path="samples/02-agents/AgentWithRAG/README.md" />
@@ -218,7 +215,6 @@
<Folder Name="/Samples/02-agents/ModelContextProtocol/">
<File Path="samples/02-agents/ModelContextProtocol/README.md" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_LongRunningTask_Client/Agent_MCP_LongRunningTask_Client.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_PerRun_AuthHeaders/Agent_MCP_PerRun_AuthHeaders.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj" />
@@ -333,9 +329,6 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/HostedLocalTools.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct/HostedLocalCodeAct.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/HostedMcpTools.csproj" />
</Folder>
@@ -351,9 +344,6 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/HostedToolbox.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/Hosted-Toolbox-AuthPaths.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/HostedToolboxMcpSkills.csproj" />
</Folder>
@@ -413,7 +403,6 @@
<Folder Name="/Samples/05-end-to-end/Evaluation/">
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Evaluation_ConversationSplits.csproj" />
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Evaluation_FoundryQuality.csproj" />
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_FoundryRubric/Evaluation_FoundryRubric.csproj" />
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Evaluation_MixedProviders.csproj" />
</Folder>
<Folder Name="/Samples/05-end-to-end/A2AClientServer/">
@@ -622,7 +611,6 @@
<Project Path="src/Microsoft.Agents.AI.Hosting.AspNetCore/Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hyperlight/Microsoft.Agents.AI.Hyperlight.csproj" />
<Project Path="src/Microsoft.Agents.AI.LocalCodeAct/Microsoft.Agents.AI.LocalCodeAct.csproj" />
<Project Path="src/Microsoft.Agents.AI.Mcp/Microsoft.Agents.AI.Mcp.csproj" />
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
@@ -634,7 +622,6 @@
<Project Path="src/Microsoft.Agents.AI.Workflows.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
<Project Path="src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj" />
</Folder>
<Folder Name="/Tests/" />
<Folder Name="/Tests/IntegrationTests/">
@@ -678,7 +665,6 @@
<Project Path="tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hyperlight.UnitTests/Microsoft.Agents.AI.Hyperlight.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/Microsoft.Agents.AI.LocalCodeAct.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj" />
@@ -689,8 +675,5 @@
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Valkey.UnitTests/Microsoft.Agents.AI.Valkey.UnitTests.csproj" />
</Folder>
</Solution>
+61 -48
View File
@@ -14,7 +14,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_CustomImplementation",
ProjectPath = "samples/02-agents/AgentProviders/custom/Agent_With_CustomImplementation",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_CustomImplementation",
RequiredEnvironmentVariables = [],
ExpectedOutputDescription =
[
@@ -27,7 +27,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -40,7 +40,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_AzureOpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIResponses",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -50,10 +50,23 @@ internal static class AgentsSamples
],
},
new SampleDefinition
{
Name = "Agent_With_AzureAIAgentsPersistent",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
[
"The output should contain a joke about a pirate.",
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "Agent_With_AzureAIProject",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureAIProject",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIProject",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
MustContain = ["Latest agent version id:"],
@@ -67,7 +80,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_AzureFoundryModel",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureFoundryModel",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_API_KEY", "AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -477,12 +490,12 @@ internal static class AgentsSamples
],
},
// ── Foundry ───────────────────────────────────────────────────────────
// ── AgentsWithFoundry ────────────────────────────────────────────────
new SampleDefinition
{
Name = "FoundryAgent_Step00_FoundryAgentLifecycle",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step00_FoundryAgentLifecycle",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -495,7 +508,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step01_Basics",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step01_Basics",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -508,7 +521,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step02.1_MultiturnConversation",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step02.1_MultiturnConversation",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -522,7 +535,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step02.2_MultiturnWithServerConversations",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step02.2_MultiturnWithServerConversations",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -535,7 +548,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -550,7 +563,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step04_UsingFunctionToolsWithApprovals",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step04_UsingFunctionToolsWithApprovals",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["Y", "Y", "Y"],
@@ -566,7 +579,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step05_StructuredOutput",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step05_StructuredOutput",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
MustContain = ["Assistant Output:", "Name:"],
@@ -581,7 +594,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step06_PersistedConversations",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step06_PersistedConversations",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -594,7 +607,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step08_DependencyInjection",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step08_DependencyInjection",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["Tell me a joke about a pirate", ""],
@@ -609,7 +622,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step10_UsingImages",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step10_UsingImages",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -623,7 +636,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step11_AsFunctionTool",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step11_AsFunctionTool",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -637,7 +650,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step12_Middleware",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step12_Middleware",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["Y", "Y", "Y"],
@@ -653,7 +666,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step13_Plugins",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step13_Plugins",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -667,7 +680,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step14_CodeInterpreter",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step14_CodeInterpreter",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -682,7 +695,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step16_FileSearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step16_FileSearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
MustContain = ["--- Running File Search Agent ---"],
@@ -696,7 +709,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step17_OpenAPITools",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step17_OpenAPITools",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -720,7 +733,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_A2A",
ProjectPath = "samples/02-agents/AgentProviders/a2a/Agent_With_A2A",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_A2A",
RequiredEnvironmentVariables = ["A2A_AGENT_HOST"],
SkipReason = "Requires an external A2A agent host.",
},
@@ -728,7 +741,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_Anthropic",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_With_Anthropic",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_Anthropic",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME", "ANTHROPIC_RESOURCE"],
ExpectedOutputDescription =
@@ -741,7 +754,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_GitHubCopilot",
ProjectPath = "samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_GitHubCopilot",
RequiredEnvironmentVariables = [],
// The sample prompts for shell command approval; provide "Y" for each possible permission request
Inputs = ["Y", "Y", "Y"],
@@ -756,7 +769,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_GoogleGemini",
ProjectPath = "samples/02-agents/AgentProviders/google-gemini/Agent_With_GoogleGemini",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_GoogleGemini",
RequiredEnvironmentVariables = ["GOOGLE_GENAI_API_KEY"],
OptionalEnvironmentVariables = ["GOOGLE_GENAI_MODEL"],
MustContain =
@@ -774,7 +787,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_ONNX",
ProjectPath = "samples/02-agents/AgentProviders/onnx/Agent_With_ONNX",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_ONNX",
RequiredEnvironmentVariables = ["ONNX_MODEL_PATH"],
SkipReason = "Requires local ONNX model.",
},
@@ -782,7 +795,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_Ollama",
ProjectPath = "samples/02-agents/AgentProviders/ollama/Agent_With_Ollama",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_Ollama",
RequiredEnvironmentVariables = ["OLLAMA_ENDPOINT", "OLLAMA_MODEL_NAME"],
SkipReason = "Requires local Ollama server.",
},
@@ -790,7 +803,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_OpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_With_OpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -803,7 +816,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_OpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_With_OpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIResponses",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -843,7 +856,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step01_Running",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step01_Running",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step01_Running",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -857,7 +870,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
MustContain =
@@ -880,7 +893,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -894,7 +907,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step04_UsingSkills",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step04_UsingSkills",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step04_UsingSkills",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
MustContain =
@@ -921,7 +934,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step01_Running",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step01_Running",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step01_Running",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -934,7 +947,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
MustContain =
@@ -954,7 +967,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step03_CreateFromChatClient",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step03_CreateFromChatClient",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -968,7 +981,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -982,7 +995,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step05_Conversation",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step05_Conversation",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
MustContain =
@@ -1029,7 +1042,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step07_Observability",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step07_Observability",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME", "APPLICATIONINSIGHTS_CONNECTION_STRING"],
SkipReason = "Requires Application Insights / OpenTelemetry infrastructure.",
@@ -1038,7 +1051,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step09_UsingMcpClientAsTools",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step09_UsingMcpClientAsTools",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -1051,7 +1064,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step15_ComputerUse",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step15_ComputerUse",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription = ["The output should show a computer automation session processing simulated browser screenshots with iteration steps and a final response describing search results."],
@@ -1060,7 +1073,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step18_BingCustomSearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step18_BingCustomSearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID", "AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME"],
SkipReason = "Requires Bing Custom Search connection.",
},
@@ -1068,7 +1081,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step19_SharePoint",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step19_SharePoint",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "SHAREPOINT_PROJECT_CONNECTION_ID"],
SkipReason = "Requires SharePoint connection.",
},
@@ -1076,7 +1089,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step20_MicrosoftFabric",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step20_MicrosoftFabric",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "FABRIC_PROJECT_CONNECTION_ID"],
SkipReason = "Requires Microsoft Fabric connection.",
},
@@ -1084,7 +1097,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step21_WebSearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step21_WebSearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -1097,7 +1110,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step22_MemorySearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step22_MemorySearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_EMBEDDING_DEPLOYMENT_NAME"],
OptionalEnvironmentVariables = ["AZURE_AI_MEMORY_STORE_ID"],
MustContain = ["Agent created with Memory Search tool. Starting conversation..."],
@@ -1111,7 +1124,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step23_LocalMCP",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step23_LocalMCP",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription = ["The output should show an agent using the Microsoft Learn MCP server to search for documentation and provide a response."],
+10 -10
View File
@@ -26,8 +26,8 @@ internal static class GetStartedSamples
{
Name = "01_hello_agent",
ProjectPath = "samples/01-get-started/01_hello_agent",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
[
"The output should contain a joke about a pirate.",
@@ -40,8 +40,8 @@ internal static class GetStartedSamples
{
Name = "02_add_tools",
ProjectPath = "samples/01-get-started/02_add_tools",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
MustContain = [],
ExpectedOutputDescription =
[
@@ -56,8 +56,8 @@ internal static class GetStartedSamples
{
Name = "03_multi_turn",
ProjectPath = "samples/01-get-started/03_multi_turn",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
[
"The output should contain a joke about a pirate.",
@@ -71,8 +71,8 @@ internal static class GetStartedSamples
{
Name = "04_memory",
ProjectPath = "samples/01-get-started/04_memory",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
MustContain =
[
">> Use session with blank memory",
@@ -97,8 +97,8 @@ internal static class GetStartedSamples
{
Name = "06_host_your_agent",
ProjectPath = "samples/01-get-started/06_host_your_agent",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
SkipReason = "Requires Azure Functions Core Tools runtime and starts a web server.",
},
];
+3 -3
View File
@@ -1,14 +1,14 @@
<Project>
<PropertyGroup>
<!-- Central version prefix - applies to all nuget packages. -->
<VersionPrefix>1.11.0</VersionPrefix>
<VersionPrefix>1.9.0</VersionPrefix>
<RCNumber>1</RCNumber>
<DateSuffix>260622</DateSuffix>
<DateSuffix>260603</DateSuffix>
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).$(DateSuffix).1</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.$(DateSuffix).1</PackageVersion>
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
<GitTag>1.11.0</GitTag>
<GitTag>1.9.0</GitTag>
<Configurations>Debug;Release;Publish</Configurations>
<IsPackable>true</IsPackable>
@@ -9,12 +9,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,19 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a simple AI agent with AIProjectClient as the backend.
// This sample shows how to create and use a simple AI agent with Azure OpenAI as the backend.
using Azure.AI.Projects;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// 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: model, instructions: "You are good at telling jokes.", name: "Joker");
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent and output the text result.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
@@ -9,12 +9,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,27 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use an AIProjectClient agent with function tools.
// It shows both non-streaming and streaming agent interactions using weather tools.
// This sample demonstrates how to use a ChatClientAgent with function tools.
// It shows both non-streaming and streaming agent interactions using menu-related tools.
using System.ComponentModel;
using Azure.AI.Projects;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
[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.";
// Create the agent and provide the function tool to the agent.
// Create the chat client and agent, and provide the function tool to the agent.
// 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: model, instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
// Non-streaming agent interaction with function tools.
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
@@ -9,12 +9,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -2,18 +2,22 @@
// This sample shows how to create and use a simple AI agent with a multi-turn conversation.
using Azure.AI.Projects;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// 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: model, instructions: "You are good at telling jokes.", name: "Joker");
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent with a multi-turn conversation, where the context is preserved in the session object.
AgentSession session = await agent.CreateSessionAsync();
@@ -9,12 +9,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -8,26 +8,23 @@
using System.Text;
using System.Text.Json;
using Azure.AI.Projects;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
using SampleApp;
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// 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.
var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
// Get the underlying IChatClient to use for the memory component.
// The memory provider needs direct IChatClient access for structured extraction.
IChatClient chatClient = projectClient
.AsAIAgent(new ChatClientAgentOptions { ChatOptions = new() { ModelId = model } })
.GetService<IChatClient>()
?? throw new InvalidOperationException("Could not retrieve IChatClient from AIProjectClient agent.");
ChatClient chatClient = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName);
// Create the agent and provide a factory to add our custom memory component to
// all sessions created by the agent. Here each new memory component will have its own
@@ -39,7 +36,7 @@ IChatClient chatClient = projectClient
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
{
ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
AIContextProviders = [new UserInfoMemory(chatClient)]
AIContextProviders = [new UserInfoMemory(chatClient.AsIChatClient())]
});
// Create a new session for the conversation.
@@ -21,10 +21,11 @@
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -4,32 +4,36 @@
//
// Prerequisites:
// - Azure Functions Core Tools
// - Foundry project endpoint and credentials
// - Azure OpenAI resource
//
// Environment variables:
// FOUNDRY_PROJECT_ENDPOINT
// FOUNDRY_MODEL (defaults to "gpt-5.4-mini")
// AZURE_OPENAI_ENDPOINT
// AZURE_OPENAI_DEPLOYMENT_NAME (defaults to "gpt-5.4-mini")
//
// Run with: func start
// Then call: POST http://localhost:7071/api/agents/HostedAgent/run
using Azure.AI.Projects;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
// Set up an AI agent following the standard Microsoft Agent Framework pattern.
// 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: model, instructions: "You are a helpful assistant hosted in Azure Functions.", name: "HostedAgent");
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(
instructions: "You are a helpful assistant hosted in Azure Functions.",
name: "HostedAgent");
// Configure the function app to host the AI agent.
// This will automatically generate HTTP API endpoints for the agent.
@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
</ItemGroup>
</Project>
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
# Creating an AIAgent with Anthropic
# Creating an AIAgent with Anthropic
This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service.
@@ -51,4 +51,3 @@ $env:ANTHROPIC_CHAT_MODEL_NAME="claude-haiku-4-5" # Optional, defaults to claud
```
**Note**: When using Microsoft Foundry with Azure CLI, make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
@@ -9,12 +9,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI.Persistent\Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable CS0618 // Type or member is obsolete - sample uses deprecated PersistentAgentsClientExtensions
// This sample shows how to create and use a simple AI agent with Microsoft Foundry Agents as the backend.
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
const string JokerName = "Joker";
const string JokerInstructions = "You are good at telling jokes.";
// Get a client to create/retrieve server side agents with.
// 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.
var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential());
// You can create a server side persistent agent with the Azure.AI.Agents.Persistent SDK.
var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync(
model: deploymentName,
name: JokerName,
instructions: JokerInstructions);
// You can retrieve an already created server side persistent agent as an AIAgent.
AIAgent agent1 = await persistentAgentsClient.GetAIAgentAsync(agentMetadata.Value.Id);
// You can also create a server side persistent agent and return it as an AIAgent directly.
AIAgent agent2 = await persistentAgentsClient.CreateAIAgentAsync(
model: deploymentName,
name: JokerName,
instructions: JokerInstructions);
// You can then invoke the agent like any other AIAgent.
AgentSession session = await agent1.CreateSessionAsync();
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", session));
// Cleanup for sample purposes.
await persistentAgentsClient.Administration.DeleteAgentAsync(agent1.Id);
await persistentAgentsClient.Administration.DeleteAgentAsync(agent2.Id);
@@ -0,0 +1,26 @@
# Classic Foundry Agents
This sample demonstrates how to create an agent using the classic Foundry Agents experience.
# Classic vs New Foundry Agents
Below is a comparison between the classic and new Foundry Agents approaches:
[Migration Guide](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/migrate?view=foundry)
# 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" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
```
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -8,8 +8,8 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
const string JokerName = "JokerAgent";
@@ -1,4 +1,4 @@
# New Foundry Agents
# New Foundry Agents
This sample demonstrates how to create an agent using the new Foundry Agents experience.
@@ -21,6 +21,6 @@ Before you begin, ensure you have the following prerequisites:
Set the following environment variables:
```powershell
$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:FOUNDRY_MODEL="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
```
@@ -13,7 +13,7 @@
</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>
</Project>
@@ -13,7 +13,7 @@ using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "Phi-4-mini-instruct";
var model = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "Phi-4-mini-instruct";
// Since we are using the OpenAI Client SDK, we need to override the default endpoint to point to Microsoft Foundry.
var clientOptions = new OpenAIClientOptions() { Endpoint = new Uri(endpoint) };
@@ -1,4 +1,4 @@
## Overview
## Overview
This sample shows how to use the OpenAI SDK to create and use a simple AI agent with any model hosted in Microsoft Foundry.
@@ -13,7 +13,7 @@ Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Microsoft Foundry resource
- A model deployment in your Microsoft Foundry resource. This example defaults to using the `Phi-4-mini-instruct` model,
so if you want to use a different model, ensure that you set your `FOUNDRY_MODEL` environment
so if you want to use a different model, ensure that you set your `AZURE_AI_MODEL_DEPLOYMENT_NAME` environment
variable to the name of your deployed model.
- An API key or role based authentication to access the Microsoft Foundry resource
@@ -30,5 +30,5 @@ $env:AZURE_OPENAI_ENDPOINT="https://ai-foundry-<myresourcename>.services.ai.azur
$env:AZURE_OPENAI_API_KEY="************"
# Optional, defaults to Phi-4-mini-instruct
$env:FOUNDRY_MODEL="Phi-4-mini-instruct"
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="Phi-4-mini-instruct"
```
@@ -14,7 +14,7 @@
</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>
</Project>
@@ -14,7 +14,7 @@
</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>
</Project>
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -14,7 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.GitHub.Copilot\Microsoft.Agents.AI.GitHub.Copilot.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.GitHub.Copilot\Microsoft.Agents.AI.GitHub.Copilot.csproj" />
</ItemGroup>
</Project>
@@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -9,7 +9,7 @@
</PropertyGroup>
<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>
</Project>
@@ -9,7 +9,7 @@
</PropertyGroup>
<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>
</Project>
@@ -1,8 +1,7 @@
# Creating an AIAgent with various providers
# Creating an AIAgent instance for various providers
These samples show how to create an AIAgent instance using various providers,
organized by provider. This is not an exhaustive list, but shows a variety of
the more popular options.
These samples show how to create an AIAgent instance using various providers.
This is not an exhaustive list, but shows a variety of the more popular options.
For other samples that demonstrate how to use AIAgent instances,
see the [Getting Started With Agents](../Agents/README.md) samples.
@@ -11,88 +10,54 @@ see the [Getting Started With Agents](../Agents/README.md) samples.
See the README.md for each sample for the prerequisites for that sample.
## Providers
## Samples
### [A2A](./a2a/)
|Sample|Description|
|---|---|
|[Creating an AIAgent with A2A](./Agent_With_A2A/)|This sample demonstrates how to create AIAgent for an existing A2A agent.|
|[Creating an AIAgent with Anthropic](./Agent_With_Anthropic/)|This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Agents.Persistent](./Agent_With_AzureAIAgentsPersistent/)|This sample demonstrates how to create a Foundry Persistent agent and expose it as an AIAgent using the Azure.AI.Agents.Persistent SDK|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Project](./Agent_With_AzureAIProject/)|This sample demonstrates how to create an Foundry Project agent and expose it as an AIAgent using the Azure.AI.Project SDK|
|[Creating an AIAgent with Foundry Model](./Agent_With_AzureFoundryModel/)|This sample demonstrates how to use any model deployed to Microsoft Foundry to create an AIAgent|
|[Creating an AIAgent with Azure OpenAI ChatCompletion](./Agent_With_AzureOpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using Azure OpenAI ChatCompletion as the underlying inference service|
|[Creating an AIAgent with Azure OpenAI Responses](./Agent_With_AzureOpenAIResponses/)|This sample demonstrates how to create an AIAgent using Azure OpenAI Responses as the underlying inference service|
|[Creating an AIAgent with a custom implementation](./Agent_With_CustomImplementation/)|This sample demonstrates how to create an AIAgent with a custom implementation|
|[Creating an AIAgent with GitHub Copilot](./Agent_With_GitHubCopilot/)|This sample demonstrates how to create an AIAgent using GitHub Copilot SDK as the underlying inference service|
|[Creating an AIAgent with Ollama](./Agent_With_Ollama/)|This sample demonstrates how to create an AIAgent using Ollama as the underlying inference service|
|[Creating an AIAgent with ONNX](./Agent_With_ONNX/)|This sample demonstrates how to create an AIAgent using ONNX as the underlying inference service|
|[Creating an AIAgent with OpenAI ChatCompletion](./Agent_With_OpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using OpenAI ChatCompletion as the underlying inference service|
|[Creating an AIAgent with OpenAI Responses](./Agent_With_OpenAIResponses/)|This sample demonstrates how to create an AIAgent using OpenAI Responses as the underlying inference service|
| Sample | Description |
| --- | --- |
| [Agent with A2A](./a2a/Agent_With_A2A/) | Create an AIAgent for an existing A2A agent |
## Running the samples from the console
### [Anthropic](./anthropic/)
To run the samples, navigate to the desired sample directory, e.g.
| Sample | Description |
| --- | --- |
| [Agent with Anthropic](./anthropic/Agent_With_Anthropic/) | Create an AIAgent using Anthropic Claude models |
| [Running](./anthropic/Agent_Anthropic_Step01_Running/) | Basic Anthropic agent usage |
| [Reasoning](./anthropic/Agent_Anthropic_Step02_Reasoning/) | Using Anthropic reasoning capabilities |
| [Function Tools](./anthropic/Agent_Anthropic_Step03_UsingFunctionTools/) | Using function tools with Anthropic |
| [Skills](./anthropic/Agent_Anthropic_Step04_UsingSkills/) | Using skills with Anthropic agents |
```powershell
cd AIAgent_With_AzureOpenAIChatCompletion
```
### [Azure](./azure/)
Set the required environment variables as documented in the sample readme.
If the variables are not set, you will be prompted for the values when running the samples.
Execute the following command to build the sample:
| Sample | Description |
| --- | --- |
| [Azure AI Project](./azure/Agent_With_AzureAIProject/) | Create a Foundry Project agent using the Azure.AI.Project SDK |
| [Azure Foundry Model](./azure/Agent_With_AzureFoundryModel/) | Use any model deployed to Microsoft Foundry |
| [Azure OpenAI ChatCompletion](./azure/Agent_With_AzureOpenAIChatCompletion/) | Create an AIAgent using Azure OpenAI ChatCompletion |
| [Azure OpenAI Responses](./azure/Agent_With_AzureOpenAIResponses/) | Create an AIAgent using Azure OpenAI Responses |
```powershell
dotnet build
```
### [Custom](./custom/)
Execute the following command to run the sample:
| Sample | Description |
| --- | --- |
| [Custom Implementation](./custom/Agent_With_CustomImplementation/) | Create an AIAgent with a custom implementation |
```powershell
dotnet run --no-build
```
### [Foundry](./foundry/)
See [foundry/README.md](./foundry/README.md) for the full list of Foundry agent samples,
covering basics, function tools, structured output, middleware, MCP, code interpreter, and more.
### [GitHub Copilot](./github-copilot/)
| Sample | Description |
| --- | --- |
| [GitHub Copilot](./github-copilot/Agent_With_GitHubCopilot/) | Create an AIAgent using GitHub Copilot SDK |
### [Google Gemini](./google-gemini/)
| Sample | Description |
| --- | --- |
| [Google Gemini](./google-gemini/Agent_With_GoogleGemini/) | Create an AIAgent using Google Gemini |
### [Ollama](./ollama/)
| Sample | Description |
| --- | --- |
| [Ollama](./ollama/Agent_With_Ollama/) | Create an AIAgent using Ollama |
### [ONNX](./onnx/)
| Sample | Description |
| --- | --- |
| [ONNX](./onnx/Agent_With_ONNX/) | Create an AIAgent using ONNX Runtime |
### [OpenAI](./openai/)
| Sample | Description |
| --- | --- |
| [OpenAI ChatCompletion](./openai/Agent_With_OpenAIChatCompletion/) | Create an AIAgent using OpenAI ChatCompletion |
| [OpenAI Responses](./openai/Agent_With_OpenAIResponses/) | Create an AIAgent using OpenAI Responses |
| [Running](./openai/Agent_OpenAI_Step01_Running/) | Basic OpenAI agent usage |
| [Reasoning](./openai/Agent_OpenAI_Step02_Reasoning/) | Using OpenAI reasoning capabilities |
| [Create from ChatClient](./openai/Agent_OpenAI_Step03_CreateFromChatClient/) | Create agent from IChatClient |
| [Create from Response Client](./openai/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/) | Create agent from OpenAI Response client |
| [Conversation](./openai/Agent_OpenAI_Step05_Conversation/) | Multi-turn conversations with OpenAI |
| [Code Interpreter](./openai/Agent_OpenAI_Step06_CodeInterpreterFileDownload/) | Code interpreter with file downloads |
## Running the samples
Navigate to a sample directory and run:
Or just build and run in one step:
```powershell
dotnet run
```
Set the required environment variables as documented in each sample's README.
If the variables are not set, you will be prompted for the values when running the samples.
## Running the samples from Visual Studio
Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
You will be prompted for any required environment variables if they are not already set.
@@ -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.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="ModelContextProtocol" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -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.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -26,9 +26,6 @@ var skillsProvider = new AgentSkillsProvider(
SubprocessScriptRunner.RunAsync);
// --- Agent Setup ---
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
@@ -67,9 +67,6 @@ var unitConverterSkill = new AgentInlineSkill(
var skillsProvider = new AgentSkillsProvider(unitConverterSkill);
// --- Agent Setup ---
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions

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