Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7c7f7477a | |||
| a94db111a8 | |||
| fdc71075b7 | |||
| e59a31c96c | |||
| 0d53d11bc6 | |||
| 87210686b3 | |||
| ae09be1eed | |||
| 07ddabbef7 | |||
| 7e5ba70884 | |||
| 6dd30950c1 | |||
| c89a539c02 | |||
| 6dfcbc5c62 | |||
| 4272d90051 | |||
| 9a565f2bf8 | |||
| 9fd3d29e09 | |||
| 6968a7fc59 | |||
| 4d4db7f501 | |||
| 730bcee9ea | |||
| a1c37b69e0 | |||
| cb8cef3ef6 | |||
| f1d838fc5e | |||
| d5c5fb9d3d | |||
| 846c963e85 | |||
| e0274b764e | |||
| 7749823393 | |||
| d09451408f | |||
| daac8c15f3 | |||
| 62ff5ac79e | |||
| 231b35da55 | |||
| 772c6fd921 | |||
| 82e8653f95 | |||
| 3c3feb8705 | |||
| b8b43798b9 | |||
| d9ec5eaab4 | |||
| e3b64fdc47 | |||
| 3a5bbb5f8e | |||
| a7332d69a8 | |||
| e57e9455b3 | |||
| d97bc4fe39 | |||
| 5d57b10b9f | |||
| 8cd71dd4f6 | |||
| 5e5dd87c91 | |||
| d0be98d649 | |||
| 802fe13053 | |||
| d75f2286f4 | |||
| ce74c84bdb | |||
| 4fb1fb615a | |||
| 41a9c54bbe | |||
| 9f1ee23a4b | |||
| 336a19fd32 | |||
| 0283fd00a1 | |||
| 5627dc0493 | |||
| 1df47667ea | |||
| 91f639a694 | |||
| a9e5f6d798 | |||
| ea7ae1cc00 | |||
| e049bb5691 | |||
| dd4b7ff475 | |||
| d5c15f2fe1 | |||
| 4cf7ace446 | |||
| 5fff0df2af | |||
| acb28a63b5 | |||
| f2d02e58b3 | |||
| 36420c515e | |||
| 1109d0bf64 | |||
| e030fb53de | |||
| e6ebba1884 | |||
| 7051a4920d | |||
| 15df1152fc | |||
| a2018b40f9 | |||
| e4b89373f1 | |||
| 88f0b23fb0 | |||
| 9ba6b3a94e | |||
| 2999f7416f | |||
| 09791533cf | |||
| 7f2e19ca2f | |||
| 7b6f582b13 | |||
| dc60722cee | |||
| 2f5a76ab1d |
@@ -109,6 +109,9 @@ jobs:
|
||||
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }}
|
||||
AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }}
|
||||
# Foundry
|
||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
|
||||
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
|
||||
|
||||
- name: Write Job Summary
|
||||
if: always()
|
||||
|
||||
@@ -8,7 +8,6 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: python-dependency-maintenance
|
||||
@@ -320,46 +319,113 @@ jobs:
|
||||
git push --force-with-lease --set-upstream origin "${BRANCH}"
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create or update pull request with GitHub CLI
|
||||
- name: Create or update dependency maintenance tracking issue
|
||||
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)"
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const branch = "automation/python-dependency-maintenance"
|
||||
const prTitle = "Python: chore: update dependencies"
|
||||
const issueTitle = "Python dependency maintenance PR needed"
|
||||
const owner = context.repo.owner
|
||||
const repo = context.repo.repo
|
||||
|
||||
cat > "${PR_BODY_FILE}" <<'EOF'
|
||||
### Motivation & Context
|
||||
const branchRef = await github.rest.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${branch}`,
|
||||
})
|
||||
const branchSha = branchRef.data.object.sha
|
||||
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`
|
||||
const compareUrl = `${context.serverUrl}/${owner}/${repo}/compare/main...${branch}`
|
||||
|
||||
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.
|
||||
const prBody = [
|
||||
"### 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.',
|
||||
].join("\n")
|
||||
|
||||
### Description & Review Guide
|
||||
const prBodyFence = "```"
|
||||
const command = [
|
||||
"PR_BODY_FILE=\"$(mktemp)\"",
|
||||
`cat > "$PR_BODY_FILE" <<'EOF'`,
|
||||
prBody,
|
||||
"EOF",
|
||||
"gh pr create --repo microsoft/agent-framework --base main \\",
|
||||
` --head ${owner}:${branch} \\`,
|
||||
` --title "${prTitle}" \\`,
|
||||
" --body-file \"$PR_BODY_FILE\"",
|
||||
].join("\n")
|
||||
|
||||
- **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. -->
|
||||
const issueBody = [
|
||||
"The Python dependency maintenance workflow generated and validated dependency updates, then pushed them to the automation branch.",
|
||||
"",
|
||||
`- Branch: \`${branch}\``,
|
||||
`- Commit: \`${branchSha}\``,
|
||||
`- Compare: ${compareUrl}`,
|
||||
`- Workflow run: ${runUrl}`,
|
||||
"",
|
||||
"GitHub Actions is not permitted to create pull requests in this repository, so a maintainer needs to create the PR manually.",
|
||||
"",
|
||||
"### Create the PR",
|
||||
"",
|
||||
"```bash",
|
||||
command,
|
||||
"```",
|
||||
"",
|
||||
"### Generated PR body",
|
||||
"",
|
||||
prBodyFence,
|
||||
prBody,
|
||||
prBodyFence,
|
||||
].join("\n")
|
||||
|
||||
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
per_page: 100,
|
||||
})
|
||||
const existingIssue = openIssues.find((issue) => !issue.pull_request && issue.title === issueTitle)
|
||||
|
||||
### 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
|
||||
if (existingIssue) {
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: existingIssue.number,
|
||||
title: issueTitle,
|
||||
body: issueBody,
|
||||
})
|
||||
core.info(`Updated issue #${existingIssue.number}: ${issueTitle}`)
|
||||
} else {
|
||||
const createdIssue = await github.rest.issues.create({
|
||||
owner,
|
||||
repo,
|
||||
title: issueTitle,
|
||||
body: issueBody,
|
||||
})
|
||||
core.info(`Created issue #${createdIssue.data.number}: ${issueTitle}`)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ trigger:
|
||||
kind: OnConversationStart
|
||||
id: workflow_demo
|
||||
actions:
|
||||
|
||||
|
||||
- kind: InvokeAzureAgent
|
||||
id: question_student
|
||||
conversationId: =System.ConversationId
|
||||
|
||||
@@ -43,6 +43,11 @@ FIDES (Flow Integrity Deterministic Enforcement System) is a label-based securit
|
||||
3. **Variable Indirection** — `ContentVariableStore` and `VariableReferenceContent` for physical isolation of untrusted content from the LLM context.
|
||||
4. **Quarantined Execution** — `quarantined_llm` and `inspect_variable` tools for isolated processing of untrusted data with audit logging.
|
||||
|
||||
In addition, remote MCP integrations are secured through two mechanisms:
|
||||
|
||||
- **Hint-based tool auto-labeling**: MCP `ToolAnnotations` (`readOnlyHint`, `openWorldHint`, etc.) are mapped to FIDES tool properties (`source_integrity`, `accepts_untrusted`, `max_allowed_confidentiality`).
|
||||
- **Server `_meta.ifc` result labels**: MCP result metadata is parsed into per-item `security_label` values, so provider-supplied IFC labels are enforced by middleware.
|
||||
|
||||
### Consequences
|
||||
|
||||
- Good, because it provides deterministic security guarantees about what untrusted content can influence.
|
||||
@@ -117,6 +122,13 @@ Monitor agent behavior and block suspicious actions post-facto.
|
||||
- Uses existing `FunctionMiddleware` base class.
|
||||
- Attaches labels via `additional_properties` (no schema changes).
|
||||
- Leverages `SerializationMixin` for label persistence.
|
||||
- Integrates MCP hint/result metadata through `additional_properties` keys (`max_allowed_confidentiality`, `source_integrity`, `__mcp_result_meta__`) without transport-specific policy code in core middleware.
|
||||
|
||||
### MCP-Specific Security Notes
|
||||
|
||||
- `SecureMCPToolProxy` applies `apply_mcp_security_labels(...)` automatically when connecting an MCP tool or URL.
|
||||
- For servers like the GitHub MCP server (with `X-MCP-Features: ifc_labels`), `_meta.ifc` labels are considered authoritative for per-result label assignment.
|
||||
- Tools that are not explicitly `readOnlyHint=True` are treated as potential sinks and default to `max_allowed_confidentiality=PUBLIC` to prevent exfiltration.
|
||||
|
||||
|
||||
### Backwards Compatibility
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
status: accepted
|
||||
status: superseded by [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md)
|
||||
contact: rogerbarreto
|
||||
date: 2026-05-07
|
||||
date: 2026-06-29
|
||||
deciders: rogerbarreto
|
||||
consulted: []
|
||||
informed: []
|
||||
@@ -9,6 +9,8 @@ informed: []
|
||||
|
||||
# Hosted session identity context for Foundry Hosting
|
||||
|
||||
> **Superseded by [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md).** `Azure.AI.AgentServer.*` 2.0.0 (responses protocol `2.0.0`) replaced `ResponseContext.Isolation` (`UserIsolationKey` / `ChatIsolationKey`, headers `x-agent-user-isolation-key` / `x-agent-chat-isolation-key`) with `ResponseContext.PlatformContext` (`UserIdKey` / `CallId`, headers `x-agent-user-id` / `x-agent-foundry-call-id`). The chat isolation key was removed and `HostedSessionContext` is now user-only. This ADR is retained as the historical record of the original design.
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Server-hosted Foundry agents need a way to scope per-user state (most notably `FoundryMemoryProvider` memories) by the end user that initiated the request. The Foundry platform already injects `x-agent-user-isolation-key` and `x-agent-chat-isolation-key` headers on every Responses request, but the agent-framework hosting layer did not surface those values to `AIContextProvider` instances. The provider's `stateInitializer` only received an `AgentSession?` with no identity attached, so per-user scoping was impossible without out-of-band plumbing.
|
||||
|
||||
@@ -0,0 +1,641 @@
|
||||
---
|
||||
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
|
||||
@@ -0,0 +1,356 @@
|
||||
---
|
||||
status: accepted
|
||||
contact: eavanvalkenburg
|
||||
date: 2026-06-19
|
||||
deciders: eavanvalkenburg, moonbox3, TaoChenOSU, chetantoshnival
|
||||
consulted: westey-m
|
||||
informed:
|
||||
---
|
||||
|
||||
# Python identity lifetimes for sessions, tasks, and continuation
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Python `AgentSession` currently carries a local `session_id`, an optional opaque service continuation
|
||||
`service_session_id`, and provider state. `service_session_id` is any service-owned value that lets that service continue
|
||||
a conversation, session, or thread; chat clients happen to map it through the abstract `conversation_id` ChatOption, but
|
||||
other agent types can use it differently. It is not a generic correlation field, and generic correlation should not
|
||||
require parsing or understanding that opaque service-owned value.
|
||||
|
||||
The related issues mix values with different lifetimes:
|
||||
|
||||
- **Session / conversation identity**: values that group a multi-turn interaction. Examples: A2A `context_id`, OpenAI
|
||||
Responses `conversation` (`conv_*`) or response-chain continuation (`previous_response_id`).
|
||||
- **Task identity**: values that identify a protocol task and may affect future protocol calls. Example: A2A `task_id`.
|
||||
- **Message / response identity**: values that identify an output message or response. Examples: A2A `message_id` /
|
||||
`artifact_id`, OpenAI Responses response id (`resp_*`).
|
||||
- **Continuation token**: a framework resume payload for in-progress work. It may contain the same underlying value as a
|
||||
protocol id, such as A2A `task_id`, but it only exists when there is an unfinished operation to resume.
|
||||
|
||||
These values should not automatically live in the same object just because they all help "continue" something. A value
|
||||
belongs in `AgentSession` only when it is needed to continue future calls across turns. A value that identifies one
|
||||
result belongs on the response or message. A value that resumes in-progress work belongs in a `ContinuationToken`.
|
||||
|
||||
An `AgentSession` created for one agent is not expected to be guaranteed to work against another agent. When a session is
|
||||
used with an incompatible agent, protocol, or service, the framework should still help users understand what is wrong as
|
||||
early as possible, preferably before calling out to the remote service.
|
||||
|
||||
For #4673, native conversation identity propagation should be based on `AgentSession` where the value is durable session
|
||||
state. For #4893, A2A `context_id` and `task_id` need a coherent Agent Framework mapping.
|
||||
|
||||
AG-UI is out of scope for the decision. Its `thread_id` already maps to `AgentSession.session_id` in the normal wrapper
|
||||
path, and `run_id` is wrapper-owned event correlation. If AG-UI run correlation needs framework telemetry integration
|
||||
later, that should be handled as a run-context/telemetry design, not as session identity.
|
||||
|
||||
### Concrete gap example
|
||||
|
||||
At the protocol level, the durable continuation payload shapes are different:
|
||||
|
||||
```json
|
||||
// A2A: future calls may need multiple durable protocol fields
|
||||
{
|
||||
"context_id": "ctx_123",
|
||||
"task_id": "task_789",
|
||||
"task_state": "input_required"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// OpenAI Responses: future calls usually need one continuation value
|
||||
{
|
||||
"previous_response_id": "resp_abc123"
|
||||
}
|
||||
```
|
||||
|
||||
The gap is that A2A continuation state is multi-field while OpenAI continuation is
|
||||
typically single-field.
|
||||
|
||||
## Current implementation notes
|
||||
|
||||
- A2A currently has `A2AAgentSession`, but `A2AAgent.create_session(...)` does not automatically return it.
|
||||
- A2A currently mirrors `context_id` into `service_session_id`; that is current behavior, not necessarily the target
|
||||
abstraction.
|
||||
- A2A `task_id` is not just cosmetic correlation. It is used for `task_id` when a task is `INPUT_REQUIRED`, for
|
||||
`reference_task_ids` when refining a previous task, and inside `A2AContinuationToken` for in-progress tasks.
|
||||
- `RawAgent._prepare_run_context(...)` currently forwards `active_session.service_session_id` as chat `conversation_id`,
|
||||
so any non-string or formatted value affects existing chat-client paths.
|
||||
- `OpenAIChatClient` maps chat options `conversation_id` to the Responses API as `previous_response_id` for `resp_*`,
|
||||
`conversation` for `conv_*`, and defaults unrecognized strings to `previous_response_id`. When `store` is not `False`,
|
||||
it returns `response.conversation.id` when available, otherwise `response.id`, as the next service continuation value.
|
||||
- For Responses API, the response id (`resp_*`) is also the response/message identity surfaced as
|
||||
`ChatResponse.response_id`; when used for continuation on the next request, it becomes the `previous_response_id`
|
||||
value.
|
||||
- Python A2A has not been released as stable yet, so its session factory or session shape can still be adjusted before
|
||||
release.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Preserve `AgentSession.session_id` as the local/client conversation identity.
|
||||
- Preserve `AgentSession.service_session_id` as an opaque service-owned continuation handle.
|
||||
- Keep `AgentSession` for durable state needed across turns, not per-run bookkeeping.
|
||||
- Store values needed by future calls in durable session state; keep values that only resume in-progress work in
|
||||
`ContinuationToken`.
|
||||
- Fix the current confusion where session, task, response, and continuation values can be treated as interchangeable
|
||||
because they all participate in "continuing" something.
|
||||
- Make the implementation following this ADR preserve the lifetime split clearly: future-call state, in-progress resume
|
||||
tokens, response/message ids, and protocol event correlation must not be silently mixed.
|
||||
- Expose durable continuation state in a typed way when future calls depend on it.
|
||||
- Let telemetry correlate runs without parsing opaque service continuation handles.
|
||||
- Reuse existing run/context surfaces before introducing a new identity abstraction.
|
||||
- Keep MCP and other remote tool boundaries safe: framework identity must not be forwarded to remote tools unless an
|
||||
existing explicit opt-in mechanism says so.
|
||||
- Keep existing `AgentSession.to_dict()` / `from_dict()` migration and compatibility straightforward.
|
||||
- Stay close to .NET where there is already behavior to match, especially A2A's `ContextId`, `TaskId`, and `TaskState`.
|
||||
- Detect incompatible session identity shapes as early as practical, preferably before a remote service call.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not design a provider-agnostic conversation creation API here. That is tracked separately in #6622.
|
||||
- Do not make `service_session_id` a generic telemetry or run-correlation field.
|
||||
- Do not introduce a new identity object if existing run/context objects can carry the selected per-run correlation value.
|
||||
- Do not make a session from one agent guaranteed to work against another agent.
|
||||
- Do not optimize the public `agent.run(...)` API for protocol-wrapper internals.
|
||||
|
||||
## Remaining question: durable shape for additional continuation state
|
||||
|
||||
- Option A: Use protocol-specific `AgentSession` subclasses.
|
||||
- Option B: Extend `service_session_id` with richer service-owned values.
|
||||
- Option C: Add a dedicated dict for additional session details.
|
||||
- Option D: Store additional durable state inside `AgentSession.state`.
|
||||
|
||||
### Option A: Use protocol-specific `AgentSession` subclasses
|
||||
|
||||
Each protocol or agent type that needs additional durable state keeps a specialized `AgentSession` subclass. For A2A,
|
||||
that means keeping `A2AAgentSession` for A2A-specific durable state and changing `A2AAgent.create_session(...)` to return
|
||||
that type.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
# First call returns a task that future A2A messages may need to reference.
|
||||
session = await a2a_agent.create_session()
|
||||
|
||||
response = await a2a_agent.run(
|
||||
message,
|
||||
session=session,
|
||||
)
|
||||
|
||||
# A2AAgent updates durable A2A protocol state from the returned task/status payload.
|
||||
# The user does not set these manually.
|
||||
assert isinstance(session, A2AAgentSession)
|
||||
assert session.task_id is not None
|
||||
assert session.task_state is not None
|
||||
|
||||
# Later call reuses the durable A2A session state. A2AAgent decides whether to send task_id
|
||||
# for INPUT_REQUIRED or reference_task_ids for task refinement.
|
||||
next_response = await a2a_agent.run(
|
||||
next_message,
|
||||
session=session,
|
||||
)
|
||||
```
|
||||
|
||||
- Good, because protocol-specific state stays in a protocol-specific type.
|
||||
- Good, because it aligns with .NET A2A's `A2AAgentSession` shape.
|
||||
- Good, because Python A2A can still make this pre-release session factory adjustment.
|
||||
- Good, because `task_state` does not get promoted to a base `AgentSession` concept.
|
||||
- Bad, because generic consumers cannot read protocol-specific state without knowing about the subclass or a helper API.
|
||||
- Bad, because it depends on each subclass consistently setting shared session fields such as `service_session_id` where
|
||||
those are part of the shared abstraction.
|
||||
|
||||
### Option B: Extend `service_session_id` with richer service-owned values
|
||||
|
||||
Keep the common `service_session_id` case as a plain string. When an agent/service needs more than one service-owned
|
||||
continuation value, allow `service_session_id` to be a typed structured value, such as a `TypedDict`. The main session ID
|
||||
used for `gen_ai.conversation.id` should still be extracted by the owning agent, not inferred by generic telemetry code.
|
||||
|
||||
Examples:
|
||||
|
||||
```python
|
||||
simple_session = AgentSession(
|
||||
service_session_id="resp_123",
|
||||
)
|
||||
|
||||
structured_session = AgentSession(
|
||||
service_session_id=A2AServiceSessionId(
|
||||
context_id="ctx_123",
|
||||
task_id="task_789",
|
||||
task_state=TaskState.TASK_STATE_WORKING,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
- Good, because the common case remains a plain string and stays simple.
|
||||
- Good, because richer service-owned continuation state stays under the existing continuation property.
|
||||
- Good, because a structured value can make framework-side validation possible before a value is sent back to a service.
|
||||
- Good, because A2A can keep `context_id`, `task_id`, and `task_state` together as the service/protocol-owned continuation
|
||||
value without adding A2A fields to base `AgentSession`.
|
||||
- Neutral, because telemetry needs an agent-owned extractor to pick the `gen_ai.conversation.id` value from either a
|
||||
string or structured `service_session_id`.
|
||||
- Neutral, because Python A2A would need a pre-release adjustment to stop relying on `A2AAgentSession` for these fields.
|
||||
- Bad, because changing the `service_session_id` type is a compatibility risk for users, providers, serialization, and
|
||||
tests.
|
||||
- Bad, because every path that sends `service_session_id` back to a service must consistently extract/adapt the
|
||||
service-owned continuation component.
|
||||
|
||||
### Option C: Add a dedicated dict for additional session details
|
||||
|
||||
Keep `service_session_id` as the primary opaque service-owned continuation handle, and add a separate dictionary for
|
||||
additional durable protocol/service values that need to travel with the session.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
session = AgentSession(
|
||||
service_session_id="ctx_123",
|
||||
session_details={
|
||||
"task_id": "task_456",
|
||||
"task_state": TaskState.TASK_STATE_WORKING,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
- Good, because the main service continuation handle stays a plain `service_session_id` string.
|
||||
- Good, because extra state has an explicit home and does not overload `service_session_id`.
|
||||
- Good, because generic consumers can look in one documented place for additional session-scoped values.
|
||||
- Neutral, because helper APIs can hide the raw dictionary access.
|
||||
- Bad, because this still introduces string-keyed state unless the dict values are wrapped by typed helpers.
|
||||
- Bad, because it adds another public session field that needs serialization, naming, and compatibility rules.
|
||||
- Bad, because generic consumers still need to understand the shape or use helpers for the selected agent/session type.
|
||||
|
||||
### Option D: Store additional durable state inside `AgentSession.state`
|
||||
|
||||
Keep base `AgentSession` unchanged and store additional durable continuation/protocol state under namespaced keys in
|
||||
`session.state`.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
session = AgentSession(session_id="ctx_123")
|
||||
|
||||
session.state["a2a"] = {
|
||||
"task_id": "task_456",
|
||||
"task_state": TaskState.TASK_STATE_WORKING,
|
||||
}
|
||||
```
|
||||
|
||||
- Good, because it avoids new public fields and avoids a subclass requirement.
|
||||
- Good, because `AgentSession.state` already exists for provider/session state.
|
||||
- Neutral, because helper APIs can hide the raw dictionary access.
|
||||
- Bad, because stringly typed state is easier to corrupt and harder to validate.
|
||||
- Bad, because generic consumers need helper APIs anyway; directly reading nested dictionaries is not a good abstraction.
|
||||
- Bad, because users may accidentally overwrite or persist invalid protocol state.
|
||||
|
||||
## Decision
|
||||
|
||||
Chosen decision criteria for the future: **split identity by lifecycle**.
|
||||
|
||||
When a protocol emits an id/token, place it by answering "what lifecycle does this value serve?":
|
||||
|
||||
- **Future-call continuation state** -> durable session state. Examples: A2A `context_id` + `task_id` + `task_state`;
|
||||
OpenAI Responses `previous_response_id`/`conversation`.
|
||||
- **Single-result identity** -> response/message object only. Examples: OpenAI `resp_*`, A2A `message_id`,
|
||||
A2A `artifact_id`.
|
||||
- **Resume unfinished work** -> `ContinuationToken` only. Example: a token carrying in-progress task resume data.
|
||||
- **Run-start-only request fields** -> run method arguments/options, not durable session state. Example: A2A
|
||||
`reference_task_ids` for a specific follow-up/refinement request.
|
||||
- **Per-run correlation/telemetry** -> protocol wrapper or run context, not `AgentSession`. Example: wrapper-managed
|
||||
`run_id` used only for tracing/events.
|
||||
|
||||
Durable-state option decision: **Option B: Extend `service_session_id` with richer service-owned values**.
|
||||
This does **not** add a new top-level identity abstraction; it keeps continuation identity under
|
||||
`service_session_id` and keeps run correlation in existing run/telemetry context.
|
||||
The immediate implementation gap is mainly in A2A mapping clarity, but the lifecycle split applies
|
||||
consistently across providers.
|
||||
|
||||
To support telemetry, `BaseAgent` should expose a method that accepts an `AgentSession | None` and returns the value to
|
||||
use for `gen_ai.conversation.id`. The default implementation should return `session.service_session_id` when it is a
|
||||
string. Agents that use a structured `service_session_id`, such as `A2AAgent`, should override that method and return the
|
||||
appropriate primary session/context value.
|
||||
|
||||
## Appendix: A2A `task_id` and `reference_task_ids` implementation check
|
||||
|
||||
The A2A protocol distinguishes a message's `task_id` from `reference_task_ids`:
|
||||
|
||||
- `task_id` associates the message with a specific task.
|
||||
- `reference_task_ids` provides additional task context, for example when a new task refines or follows up on the result
|
||||
of a previous task.
|
||||
|
||||
The protocol does not appear to prescribe that `task_id` and `reference_task_ids` are mutually exclusive. If both are
|
||||
present, the natural reading is that the message is associated with one task while also referencing other tasks for
|
||||
context. The serving agent decides how to interpret that context.
|
||||
|
||||
The Python implementation should check and likely adjust the current behavior:
|
||||
|
||||
- `task_id` should be updated by the current run when the remote A2A service returns a task/status payload.
|
||||
- `task_id` should remain durable A2A session state when needed for future calls, for example when a task is
|
||||
`INPUT_REQUIRED`.
|
||||
- `reference_task_ids` should be a run parameter / caller intent for the current request, not implicit durable session
|
||||
continuation state.
|
||||
- A follow-up/refinement request should pass explicit `reference_task_ids` when it wants to reference previous tasks.
|
||||
- If both session `task_id` and run `reference_task_ids` are present, the wrapper should preserve the protocol
|
||||
distinction rather than treating one as a replacement for the other.
|
||||
- If no `reference_task_ids` are supplied, the wrapper should not automatically infer them from the last session task
|
||||
unless we deliberately keep that convenience for compatibility.
|
||||
|
||||
## Appendix: implementation notes for Option B
|
||||
|
||||
The exact names are implementation details, but the shape should be:
|
||||
|
||||
```python
|
||||
class A2AServiceSessionId(TypedDict):
|
||||
context_id: str
|
||||
task_id: str | None
|
||||
task_state: TaskState | None
|
||||
|
||||
|
||||
class AgentSession:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
session_id: str | None = None,
|
||||
service_session_id: str | ServiceSessionId | None = None,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
class BaseAgent:
|
||||
def _get_otel_conversation_id(self, session: AgentSession | None) -> str | None:
|
||||
service_session_id = session.service_session_id if session else None
|
||||
return service_session_id if isinstance(service_session_id, str) else None
|
||||
|
||||
|
||||
class A2AAgent(BaseAgent):
|
||||
def _get_otel_conversation_id(self, session: AgentSession | None) -> str | None:
|
||||
service_session_id = session.service_session_id if session else None
|
||||
if isinstance(service_session_id, Mapping):
|
||||
return service_session_id.get("context_id")
|
||||
return service_session_id if isinstance(service_session_id, str) else None
|
||||
|
||||
|
||||
class AgentTelemetryLayer:
|
||||
def _trace_agent_invocation(...):
|
||||
attributes = _get_span_attributes(
|
||||
...,
|
||||
thread_id=self._get_otel_conversation_id(session),
|
||||
...,
|
||||
)
|
||||
```
|
||||
|
||||
This keeps the OpenTelemetry extraction decision with the agent that owns the service continuation shape. Generic OTel
|
||||
code should not parse structured `service_session_id` values directly.
|
||||
|
||||
`AgentSession` must also be updated so `service_session_id` can store either the current string value or a structured
|
||||
service-owned value. Serialization must preserve both shapes, and existing serialized sessions with string
|
||||
`service_session_id` must continue to round-trip unchanged.
|
||||
|
||||
## More Information
|
||||
|
||||
Related work and issues:
|
||||
|
||||
- #4673: native conversation ID propagation.
|
||||
- #4893: align A2A protocol concepts with Agent Framework session/continuation concepts.
|
||||
- #2931: Foundry-specific conversation creation helper, split into a separate Python PR.
|
||||
- #6622: broader provider-agnostic conversation creation API discussion requiring .NET sync.
|
||||
- [ADR-0015](0015-agent-run-context.md): AgentRunContext for Agent Run.
|
||||
- [ADR-0018](0018-agentthread-serialization.md): AgentSession serialization.
|
||||
- [ADR-0026](0026-hosted-session-identity-context.md): hosted session identity context.
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
status: accepted
|
||||
contact: rogerbarreto
|
||||
date: 2026-06-29
|
||||
deciders: rogerbarreto
|
||||
consulted: []
|
||||
informed: []
|
||||
---
|
||||
|
||||
# Hosted platform context (user id + call id) for Foundry Hosting on AgentServer 2.0
|
||||
|
||||
Supersedes [ADR-0026](0026-hosted-session-identity-context.md).
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
[ADR-0026](0026-hosted-session-identity-context.md) sourced the hosted-agent end-user identity from `ResponseContext.Isolation` (an `IsolationContext` typed `UserIsolationKey` / `ChatIsolationKey`), injected by the platform as the `x-agent-user-isolation-key` and `x-agent-chat-isolation-key` headers.
|
||||
|
||||
`Azure.AI.AgentServer.*` 2.0.0 (responses protocol `2.0.0`) removes that surface. `ResponseContext.Isolation` is gone; the platform now exposes `ResponseContext.PlatformContext` (a `PlatformContext` typed `UserIdKey` and `CallId`), populated from the `x-agent-user-id` and `x-agent-foundry-call-id` headers. The chat isolation key no longer exists, and a new per-request **call id** is introduced that first-party Foundry services (the toolbox proxy in particular) require on outbound calls to resolve the server-side-stored caller context. The hosting layer in `Microsoft.Agents.AI.Foundry.Hosting` had to migrate to this contract without changing the public shape that samples and providers depend on.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Track the breaking `Azure.AI.AgentServer.*` 2.0.0 surface (`PlatformContext` replacing `Isolation`) while keeping the same per-user partitioning guarantees from ADR-0026.
|
||||
- Keep the change **internal**: existing hosted samples and `AIContextProvider`s must not need code changes. `session.GetHostedContext().UserId`, `HostedSessionIsolationKeyProvider`, and `AddFoundryResponses` stay source-compatible.
|
||||
- Forward the new per-request call id verbatim on outbound calls to Foundry first-party services so per-user toolbox OAuth consent and other server-side caller-context lookups keep working.
|
||||
- Remain resilient on protocol `1.0.0`: when only the legacy headers are present, `UserIdKey` still resolves and `CallId` is simply absent.
|
||||
- Preserve the strict-resume tamper defense from ADR-0026 with identity now reduced to user only.
|
||||
|
||||
## Considered Options
|
||||
|
||||
For the identity source:
|
||||
|
||||
1. **Map `ResponseContext.PlatformContext.UserIdKey`** into the existing `HostedSessionContext` (user only), keeping ADR-0026's storage shape and read accessor.
|
||||
2. Keep a `ChatId` slot on `HostedSessionContext` for backward source-compatibility, populated from `CallId` or left null.
|
||||
|
||||
For the call id propagation:
|
||||
|
||||
A. **A request-scoped ambient (`HostedCallContext`, an `AsyncLocal<string?>`)** set by the handler and re-applied before each egress point, read by the outbound delegating handler.
|
||||
B. Thread the call id through every method signature down to the toolbox bearer handler.
|
||||
|
||||
For session keying (previously implied by the conversation/chat pairing):
|
||||
|
||||
I. **`HostedConversationKey`** resolving a stable partition from `conversation_id ?? partition(previous_response_id) ?? partition(responseId)`.
|
||||
II. Continue keying on the container session id (`FOUNDRY_AGENT_SESSION_ID`).
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen: **Option 1** for identity, **Option A** for call id, **Option I** for session keying.
|
||||
|
||||
Rationale:
|
||||
|
||||
- **`ChatId` dropped (Option 2 rejected).** The platform no longer supplies a chat key; carrying a synthetic one would invent identity the trust boundary does not provide. `HostedSessionContext` becomes user-only (`HostedSessionContext(string userId)` / `UserId`), and the strict-resume check validates `UserId` alone. The corresponding `HostedFoundryMemoryProviderScopes` values `PerChat` and `PerUserAndChat` are removed; `PerUser` is retained.
|
||||
- **Ambient call id (Option B rejected).** Writing `HostedCallContext.CallId` inside the streaming `async IAsyncEnumerable` iterator is reverted across each `yield`, so a single up-front assignment is lost before the toolbox/MCP egress runs. The handler therefore captures `context.PlatformContext?.CallId` once and **re-applies it immediately before each egress point**; `FoundryToolboxBearerTokenHandler` forwards it as `x-agent-foundry-call-id`. The ambient is request-scoped and never leaks into the caller's execution context (guarded by a unit test).
|
||||
- **`HostedConversationKey` (Option II rejected).** One container serves many conversations for its lifetime, so the container session id cannot key per-conversation state. The partition key is derived from the conversation/`previous_response_id`/minted response id instead.
|
||||
|
||||
Implementation summary in `Microsoft.Agents.AI.Foundry.Hosting`:
|
||||
|
||||
| Type | Visibility | Change vs ADR-0026 |
|
||||
|---|---|---|
|
||||
| `HostedSessionContext` | public sealed | Now user-only (`UserId`); `ChatId` removed. |
|
||||
| `PlatformHostedSessionIsolationKeyProvider` | internal sealed | Maps `context.PlatformContext.UserIdKey` (was `context.Isolation.UserIsolationKey` / `ChatIsolationKey`). |
|
||||
| `HostedCallContext` | internal static | New. Request-scoped `AsyncLocal<string?>` holding the `x-agent-foundry-call-id` value. |
|
||||
| `HostedConversationKey` | internal | New. Resolves the per-conversation partition key. |
|
||||
| `FoundryToolboxBearerTokenHandler` | internal | Now also forwards `x-agent-foundry-call-id` outbound. |
|
||||
| `HostedFoundryMemoryProviderScopes` | public | `PerChat` / `PerUserAndChat` removed; `PerUser` kept. |
|
||||
|
||||
Package manifests bump the responses container protocol to `2.0.0` (invocations stays `1.0.0`).
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Per-user memory partitioning and the strict-resume tamper defense from ADR-0026 are preserved with no public API churn for samples or providers.
|
||||
- Per-user toolbox OAuth consent and other server-side caller-context lookups keep working because the per-request call id is forwarded on egress.
|
||||
- Works unchanged on protocol `1.0.0` (no call id) and `2.0.0`.
|
||||
|
||||
Negative:
|
||||
|
||||
- `HostedSessionContext.ChatId` and the `PerChat` / `PerUserAndChat` memory scopes are removed; any out-of-tree consumer that referenced them must move to user-scoped partitioning.
|
||||
- The call id must be re-applied before every egress point because of the async-iterator `AsyncLocal` revert; a missed re-apply silently drops the header. This is covered by unit tests.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- HMAC tamper signatures over the persisted context remain unimplemented; equality comparison against `ResponseContext.PlatformContext` on every request is sufficient because the platform sets the header at the trust boundary.
|
||||
- The per-request `User` field on `CreateResponse` is still intentionally not consumed.
|
||||
@@ -8,6 +8,8 @@
|
||||
- **Context Provider Pattern** - `SecureAgentConfig` extends `ContextProvider`, injecting tools, instructions, and middleware automatically
|
||||
- **Automatic Variable Hiding** - UNTRUSTED content is automatically hidden without requiring manual intervention
|
||||
- **Per-Item Embedded Labels** - Tools return `list[Content]` with `Content.from_text()` for proper label propagation
|
||||
- **SecureMCPToolProxy Auto-Labeling** - MCP tools are labeled automatically from MCP `ToolAnnotations` hints
|
||||
- **MCP `_meta.ifc` Support** - Per-result IFC labels from servers (for example GitHub MCP with `X-MCP-Features: ifc_labels`) are parsed and enforced
|
||||
- **SecureAgentConfig** - One-line secure agent configuration via `context_providers=[config]`
|
||||
- **Data Exfiltration Prevention** - `max_allowed_confidentiality` prevents sensitive data leakage
|
||||
- **Message-Level Label Tracking** (Phase 1) - Track labels on every message in the conversation
|
||||
@@ -23,6 +25,7 @@ The FIDES defense system consists of seven main components:
|
||||
5. **Security Tools** - Specialized tools for safe handling of untrusted content (`quarantined_llm`, `inspect_variable`)
|
||||
6. **SecureAgentConfig** - Context provider for easy secure agent configuration
|
||||
7. **Message-Level Label Tracking** - Track labels on every message in the conversation (Phase 1)
|
||||
8. **MCP Tool/Result Label Integration** - MCP hint-based tool labeling and `_meta.ifc` result label parsing
|
||||
|
||||
## Implementation Details
|
||||
|
||||
@@ -184,6 +187,17 @@ agent = Agent(
|
||||
)
|
||||
```
|
||||
|
||||
### 7. MCP Labeling Pipeline (Hints + `_meta.ifc`)
|
||||
|
||||
FIDES now secures remote MCP integration end-to-end:
|
||||
|
||||
- **Tool labels from hints**: `apply_mcp_security_labels(...)` maps MCP hints (`readOnlyHint`, `openWorldHint`) to FIDES tool properties.
|
||||
- **Safe sink defaults**: tools not explicitly marked `readOnlyHint=True` are treated as potential sinks and receive `max_allowed_confidentiality=public`.
|
||||
- **Result labels from metadata**: MCP result `_meta` is propagated via `__mcp_result_meta__`; `_meta.ifc` is parsed into `security_label` per result item.
|
||||
- **`SecureMCPToolProxy` convenience**: wraps MCP tools/URLs and applies this labeling automatically on connect.
|
||||
|
||||
This behavior is used with the GitHub MCP server when `X-MCP-Features: ifc_labels` is passed, which causes the server to return IFC labels in `_meta` (for example `{"ifc": {"integrity": "untrusted", "confidentiality": "public"}}`).
|
||||
|
||||
## Security Properties
|
||||
|
||||
### Deterministic Defense
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../.github/skills/pull-requests
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
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`](../../../../.github/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}
|
||||
```
|
||||
@@ -23,9 +23,9 @@
|
||||
<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.AI.AgentServer.Core" Version="1.0.0-beta.26" />
|
||||
<PackageVersion Include="Azure.AI.AgentServer.Invocations" Version="1.0.0-beta.5" />
|
||||
<PackageVersion Include="Azure.AI.AgentServer.Responses" Version="1.0.0-beta.6" />
|
||||
<PackageVersion Include="Azure.Search.Documents" Version="12.0.0" />
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="2.1.0-beta.3" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.10" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Solution>
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<BuildType Name="Debug" />
|
||||
<BuildType Name="Publish" />
|
||||
@@ -117,9 +117,12 @@
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step06_McpBasedSkills/Agent_Step06_McpBasedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step07_SkillsAutoApproval/Agent_Step07_SkillsAutoApproval.csproj" />
|
||||
</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/BuildYourOwnClaw/Claw_Step02_WorkingWithData/Claw_Step02_WorkingWithData.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" />
|
||||
@@ -192,10 +195,10 @@
|
||||
<File Path="samples/02-agents/AgentWithMemory/README.md" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_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" />
|
||||
<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" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentProviders/openai/">
|
||||
<File Path="samples/02-agents/AgentProviders/openai/README.md" />
|
||||
@@ -217,6 +220,7 @@
|
||||
<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" />
|
||||
@@ -239,10 +243,10 @@
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Declarative/">
|
||||
<File Path="samples/03-workflows/Declarative/README.md" />
|
||||
<Project Path="samples/03-workflows/Declarative/AotCheckpointing/AotCheckpointing.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/ExecuteCode/ExecuteCode.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj" />
|
||||
@@ -365,6 +369,7 @@
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/HostedWorkflowSimple.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/Hosted-Toolbox-AuthPaths-Client/Hosted-Toolbox-AuthPaths-Client.csproj" />
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/SessionFilesClient.csproj" />
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/SimpleAgent.csproj" />
|
||||
</Folder>
|
||||
@@ -615,9 +620,9 @@
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.AspNetCore/Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj" />
|
||||
<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" />
|
||||
@@ -626,13 +631,13 @@
|
||||
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Tools.Shell/Microsoft.Agents.AI.Tools.Shell.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj" />
|
||||
<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/">
|
||||
@@ -683,11 +688,11 @@
|
||||
<Project Path="tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Tools.Shell.UnitTests/Microsoft.Agents.AI.Tools.Shell.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.UnitTests/Microsoft.Agents.AI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Valkey.UnitTests/Microsoft.Agents.AI.Valkey.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj" />
|
||||
<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>
|
||||
|
||||
|
||||
@@ -1052,8 +1052,8 @@ internal static class AgentsSamples
|
||||
{
|
||||
Name = "FoundryAgent_Step15_ComputerUse",
|
||||
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step15_ComputerUse",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT", "AZURE_AI_COMPUTER_USE_DEPLOYMENT_NAME"],
|
||||
OptionalEnvironmentVariables = [],
|
||||
ExpectedOutputDescription = ["The output should show a computer automation session processing simulated browser screenshots with iteration steps and a final response describing search results."],
|
||||
},
|
||||
|
||||
|
||||
@@ -18,13 +18,14 @@
|
||||
// Note: By default, this tool expects sample build outputs to already exist.
|
||||
// Pre-build the solution before running, or pass --build to avoid missing build output failures.
|
||||
//
|
||||
// Required environment variables (for AI-powered samples):
|
||||
// AZURE_OPENAI_ENDPOINT
|
||||
// AZURE_OPENAI_DEPLOYMENT_NAME (optional, defaults to gpt-5-mini)
|
||||
// Required environment variables (for AI-powered verification):
|
||||
// FOUNDRY_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
// FOUNDRY_MODEL — Model deployment name (optional, defaults to gpt-5.4-mini)
|
||||
|
||||
using System.Diagnostics;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using VerifySamples;
|
||||
|
||||
var options = VerifyOptions.Parse(args);
|
||||
@@ -43,14 +44,33 @@ if (!File.Exists(Path.Combine(dotnetRoot, "agent-framework-dotnet.slnx")))
|
||||
}
|
||||
|
||||
// Set up the AI verifier
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5-mini";
|
||||
var foundryEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT");
|
||||
var foundryModel = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
OpenAI.Chat.ChatClient? chatClient = null;
|
||||
if (!string.IsNullOrEmpty(endpoint))
|
||||
AIAgent? verifierAgent = null;
|
||||
if (!string.IsNullOrEmpty(foundryEndpoint))
|
||||
{
|
||||
chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
verifierAgent = new AIProjectClient(new Uri(foundryEndpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(
|
||||
model: foundryModel,
|
||||
instructions: """
|
||||
You are a test output verifier. You will be given:
|
||||
1. The actual stdout output of a program
|
||||
2. The stderr output (if any)
|
||||
3. A list of expectations about what the output should contain or demonstrate
|
||||
|
||||
Your job is to determine whether the actual output satisfies each expectation.
|
||||
Be reasonable — the output comes from an LLM so exact wording won't match, but the
|
||||
semantic intent should be clearly satisfied.
|
||||
|
||||
In your response, you MUST:
|
||||
- Always provide ai_reasoning with a brief overall assessment.
|
||||
- Always provide exactly one entry in expectation_results for each expectation,
|
||||
in the same order as the input list.
|
||||
- For each expectation_results entry, echo the expectation text in the expectation
|
||||
field and explain your assessment in the detail field, citing evidence from the output.
|
||||
""",
|
||||
name: "OutputVerifier");
|
||||
}
|
||||
|
||||
// Set up optional log file writer
|
||||
@@ -61,11 +81,13 @@ if (options.LogFilePath is not null)
|
||||
await logWriter.WriteHeaderAsync();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Foundry endpoint: {foundryEndpoint ?? "(not set — AI verification disabled)"}, Model: {foundryModel}");
|
||||
|
||||
try
|
||||
{
|
||||
// Run all samples
|
||||
var reporter = new ConsoleReporter();
|
||||
var verifier = new SampleVerifier(chatClient);
|
||||
var verifier = new SampleVerifier(verifierAgent);
|
||||
var orchestrator = new VerificationOrchestrator(verifier, reporter, dotnetRoot, TimeSpan.FromMinutes(3), logWriter, buildSamples: options.BuildSamples);
|
||||
|
||||
var run = await orchestrator.RunAllAsync(options.Samples, options.MaxParallelism);
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
@@ -17,33 +15,12 @@ internal sealed class SampleVerifier
|
||||
private readonly AIAgent? _verifierAgent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a verifier. If <paramref name="chatClient"/> is provided,
|
||||
/// Creates a verifier. If <paramref name="verifierAgent"/> is provided,
|
||||
/// AI-based verification is available for non-deterministic samples.
|
||||
/// </summary>
|
||||
public SampleVerifier(ChatClient? chatClient = null)
|
||||
public SampleVerifier(AIAgent? verifierAgent = null)
|
||||
{
|
||||
if (chatClient is not null)
|
||||
{
|
||||
this._verifierAgent = chatClient.AsAIAgent(
|
||||
instructions: """
|
||||
You are a test output verifier. You will be given:
|
||||
1. The actual stdout output of a program
|
||||
2. The stderr output (if any)
|
||||
3. A list of expectations about what the output should contain or demonstrate
|
||||
|
||||
Your job is to determine whether the actual output satisfies each expectation.
|
||||
Be reasonable — the output comes from an LLM so exact wording won't match, but the
|
||||
semantic intent should be clearly satisfied.
|
||||
|
||||
In your response, you MUST:
|
||||
- Always provide ai_reasoning with a brief overall assessment.
|
||||
- Always provide exactly one entry in expectation_results for each expectation,
|
||||
in the same order as the input list.
|
||||
- For each expectation_results entry, echo the expectation text in the expectation
|
||||
field and explain your assessment in the detail field, citing evidence from the output.
|
||||
""",
|
||||
name: "OutputVerifier");
|
||||
}
|
||||
this._verifierAgent = verifierAgent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -30,8 +30,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_StartHere_02_AgentsInWorkflows",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/02_AgentsInWorkflows",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show agent responses from a translation workflow.",
|
||||
@@ -43,8 +43,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_StartHere_03_AgentWorkflowPatterns",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/03_AgentWorkflowPatterns",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["sequential"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription =
|
||||
@@ -81,8 +81,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_StartHere_06_MixedWorkflowAgentsAndExecutors",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/06_MixedWorkflowAgentsAndExecutors",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What is 2 plus 2?"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription =
|
||||
@@ -96,8 +96,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_StartHere_07_WriterCriticWorkflow",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/07_WriterCriticWorkflow",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
MustContain = ["=== Writer-Critic Iteration Workflow ==="],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
@@ -115,8 +115,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Agents_CustomAgentExecutors",
|
||||
ProjectPath = "samples/03-workflows/Agents/CustomAgentExecutors",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show custom workflow events including slogan generation and feedback.",
|
||||
@@ -128,8 +128,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Agents_FoundryAgent",
|
||||
ProjectPath = "samples/03-workflows/Agents/FoundryAgent",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Requires Azure AI Foundry project endpoint.",
|
||||
},
|
||||
|
||||
@@ -137,8 +137,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Agents_GroupChatToolApproval",
|
||||
ProjectPath = "samples/03-workflows/Agents/GroupChatToolApproval",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
MustContain = ["Starting group chat workflow for software deployment..."],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
@@ -153,8 +153,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Agents_WorkflowAsAnAgent",
|
||||
ProjectPath = "samples/03-workflows/Agents/WorkflowAsAnAgent",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["hello", "exit"],
|
||||
InputDelayMs = 5000,
|
||||
ExpectedOutputDescription =
|
||||
@@ -219,8 +219,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Concurrent_Concurrent",
|
||||
ProjectPath = "samples/03-workflows/Concurrent/Concurrent",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show results from concurrent agent processing.",
|
||||
@@ -247,8 +247,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_ConditionalEdges_01_EdgeCondition",
|
||||
ProjectPath = "samples/03-workflows/ConditionalEdges/01_EdgeCondition",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show an email being classified as spam or not spam and processed accordingly.",
|
||||
@@ -260,8 +260,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_ConditionalEdges_02_SwitchCase",
|
||||
ProjectPath = "samples/03-workflows/ConditionalEdges/02_SwitchCase",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show an ambiguous email being classified as spam, not spam, or uncertain.",
|
||||
@@ -273,8 +273,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_ConditionalEdges_03_MultiSelection",
|
||||
ProjectPath = "samples/03-workflows/ConditionalEdges/03_MultiSelection",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show an email being classified and potentially routed to multiple handlers.",
|
||||
@@ -371,8 +371,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Observability_WorkflowAsAnAgent",
|
||||
ProjectPath = "samples/03-workflows/Observability/WorkflowAsAnAgent",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Interactive console with ReadLine loop; requires OTLP endpoint.",
|
||||
},
|
||||
|
||||
@@ -384,7 +384,7 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_ConfirmInput",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ConfirmInput",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
Inputs = ["hello", "hello"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a confirmation prompt and a user response."],
|
||||
@@ -394,10 +394,10 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_CustomerSupport",
|
||||
ProjectPath = "samples/03-workflows/Declarative/CustomerSupport",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
Inputs = ["My laptop won't start"],
|
||||
InputDelayMs = 3000,
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["My laptop won't start", "The laptop is now working, thank you!"],
|
||||
InputDelayMs = 5000,
|
||||
ExpectedOutputDescription = ["The output should show a customer support workflow processing a laptop issue, with agent responses providing troubleshooting or support."],
|
||||
},
|
||||
|
||||
@@ -405,26 +405,16 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_DeepResearch",
|
||||
ProjectPath = "samples/03-workflows/Declarative/DeepResearch",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Requires external weather API (wttr.in).",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_ExecuteCode",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ExecuteCode",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
Inputs = ["What is 12 * 34?"],
|
||||
InputDelayMs = 5000,
|
||||
ExpectedOutputDescription = ["The output should show a declarative workflow executing generated code, processing a math question and producing a result."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_ExecuteWorkflow",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ExecuteWorkflow",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
SkipReason = "Requires a workflow file path as a CLI argument.",
|
||||
},
|
||||
|
||||
@@ -432,8 +422,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_FunctionTools",
|
||||
ProjectPath = "samples/03-workflows/Declarative/FunctionTools",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What are today's specials?", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow calling function tools (e.g. a menu plugin) to answer a question about restaurant specials."],
|
||||
@@ -443,7 +433,7 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_HostedWorkflow",
|
||||
ProjectPath = "samples/03-workflows/Declarative/HostedWorkflow",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
SkipReason = "Hosts a persistent workflow server that does not exit.",
|
||||
},
|
||||
|
||||
@@ -451,9 +441,9 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_InputArguments",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InputArguments",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
Inputs = ["I'd like to visit Seattle", "EXIT"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["I'd like to visit Seattle", "Seattle, WA", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow capturing location input and providing travel-related information about Seattle."],
|
||||
},
|
||||
@@ -462,8 +452,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_InvokeFunctionTool",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InvokeFunctionTool",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What's the soup of the day?", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow invoking a function tool (e.g. a menu plugin) to answer a question about the soup of the day."],
|
||||
@@ -473,8 +463,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_InvokeFoundryToolboxMcp",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InvokeFoundryToolboxMcp",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME", "FOUNDRY_TOOLBOX_NAME", "FOUNDRY_AGENT_TOOLSET_API_VERSION"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL", "FOUNDRY_TOOLBOX_NAME", "FOUNDRY_AGENT_TOOLSET_API_VERSION"],
|
||||
Inputs = ["How do I use Azure OpenAI with my data?"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow using Foundry Toolbox MCP tools to search Microsoft Learn documentation and web search to provide a summary of results."],
|
||||
@@ -484,8 +474,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_InvokeMcpTool",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InvokeMcpTool",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["Search for .NET tutorials on Microsoft Learn"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow using MCP tools to search Microsoft Learn documentation and provide a summary of results."],
|
||||
@@ -495,8 +485,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_Marketing",
|
||||
ProjectPath = "samples/03-workflows/Declarative/Marketing",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["A smart water bottle that tracks hydration"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a marketing workflow generating content about a smart water bottle product."],
|
||||
@@ -506,8 +496,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_StudentTeacher",
|
||||
ProjectPath = "samples/03-workflows/Declarative/StudentTeacher",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What is 18 + 27?"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a student-teacher workflow where a student asks a math question and a teacher provides the answer."],
|
||||
@@ -517,8 +507,8 @@ internal static class WorkflowSamples
|
||||
{
|
||||
Name = "Workflow_Declarative_ToolApproval",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ToolApproval",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["Search for .NET tutorials", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow using an MCP tool with approval to search Microsoft Learn, followed by an exit from the input loop."],
|
||||
|
||||
@@ -12,13 +12,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.200",
|
||||
"version": "10.0.301",
|
||||
"rollForward": "minor",
|
||||
"allowPrerelease": false
|
||||
},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -1,14 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.10.0</VersionPrefix>
|
||||
<VersionPrefix>1.12.0</VersionPrefix>
|
||||
<RCNumber>1</RCNumber>
|
||||
<DateSuffix>260610</DateSuffix>
|
||||
<DateSuffix>260629</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.10.0</GitTag>
|
||||
<GitTag>1.12.0</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -22,24 +22,34 @@ var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini
|
||||
// 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.");
|
||||
// Create a separate IChatClient for the memory component to use for structured extraction.
|
||||
// The memory component calls the model with a ResponseFormat (JSON schema) to extract user info.
|
||||
// Using a dedicated client here avoids mixing side-channel extraction calls with the agent's
|
||||
// conversation history, and avoids the chicken-and-egg problem of needing an IChatClient
|
||||
// before the main agent is constructed.
|
||||
IChatClient extractionClient =
|
||||
new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(model);
|
||||
|
||||
// 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
|
||||
// user info object, so each session will have its own memory.
|
||||
// Create the agent with instructions and the custom memory context provider.
|
||||
// The memory component is attached to all sessions created by the agent. Here each new memory
|
||||
// component will have its own user info object, so each session will have its own memory.
|
||||
// In real world applications/services, where the user info would be persisted in a database,
|
||||
// and preferably shared between multiple sessions used by the same user, ensure that the
|
||||
// factory reads the user id from the current context and scopes the memory component
|
||||
// and its storage to that user id.
|
||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
AIAgent agent = projectClient.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
|
||||
AIContextProviders = [new UserInfoMemory(chatClient)]
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
ModelId = model,
|
||||
Instructions = "You are a friendly assistant. Always address the user by their name.",
|
||||
},
|
||||
AIContextProviders = [new UserInfoMemory(extractionClient)]
|
||||
});
|
||||
|
||||
// Create a new session for the conversation.
|
||||
@@ -118,10 +128,17 @@ namespace SampleApp
|
||||
// Try and extract the user name and age from the message if we don't have it already and it's a user message.
|
||||
if ((userInfo.UserName is null || userInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
|
||||
{
|
||||
// The Foundry Responses API requires the model name in the request body.
|
||||
// Retrieve it from the client's metadata so callers don't need to pass it separately.
|
||||
var modelId = this._chatClient.GetService<ChatClientMetadata>()?.DefaultModelId
|
||||
?? throw new InvalidOperationException(
|
||||
"Could not retrieve DefaultModelId from the extraction IChatClient. " +
|
||||
"Ensure the client was created with a model ID (e.g., via projectClient.AsAIAgent(...)).");
|
||||
var result = await this._chatClient.GetResponseAsync<UserInfo>(
|
||||
context.RequestMessages,
|
||||
new ChatOptions()
|
||||
{
|
||||
ModelId = modelId,
|
||||
Instructions = "Extract the user's name and age from the message if present. If not present return nulls."
|
||||
},
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
@@ -224,6 +224,12 @@ dotnet run
|
||||
- **Thread IDs** (as `ConversationId`) for conversation context
|
||||
- **Run IDs** (as `ResponseId`) for tracking individual executions
|
||||
|
||||
## Security considerations
|
||||
|
||||
`ConversationId` keeps request/response continuity. It is not proof that the caller owns that conversation. In multi-user deployments, authenticate each AG-UI request and authorize conversation access using your application's real boundary, such as the authenticated user, tenant, or workspace.
|
||||
|
||||
If your ASP.NET Core host shares session storage across users, pair `MapAGUI` with an isolation strategy such as `UseClaimsBasedSessionIsolation(...)` so the storage key includes a principal-specific dimension instead of relying on the conversation identifier alone.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Refused
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="OpenAI" />
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
|
||||
@@ -25,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Azure.Monitor.OpenTelemetry.Exporter;
|
||||
using Microsoft.Agents.AI;
|
||||
@@ -96,8 +96,8 @@ Console.WriteLine("""
|
||||
Type your message and press Enter. Type 'exit' or empty message to quit.
|
||||
""");
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT environment variable is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT environment variable is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// Log application startup
|
||||
appLogger.LogInformation("OpenTelemetry Aspire Demo application started");
|
||||
@@ -112,20 +112,19 @@ static async Task<string> GetWeatherAsync([Description("The location to get the
|
||||
// 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.
|
||||
using var instrumentedChatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsIChatClient() // Converts a native OpenAI SDK ChatClient into a Microsoft.Extensions.AI.IChatClient
|
||||
.AsBuilder()
|
||||
.UseFunctionInvocation()
|
||||
.UseOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the chat client level
|
||||
.Build();
|
||||
|
||||
appLogger.LogInformation("Creating Agent with OpenTelemetry instrumentation");
|
||||
// Create the agent with the instrumented chat client
|
||||
var agent = new ChatClientAgent(instrumentedChatClient,
|
||||
name: "OpenTelemetryDemoAgent",
|
||||
instructions: "You are a helpful assistant that provides concise and informative responses.",
|
||||
tools: [AIFunctionFactory.Create(GetWeatherAsync)])
|
||||
var agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(
|
||||
model: deploymentName,
|
||||
instructions: "You are a helpful assistant that provides concise and informative responses.",
|
||||
name: "OpenTelemetryDemoAgent",
|
||||
tools: [AIFunctionFactory.Create(GetWeatherAsync)],
|
||||
clientFactory: client => client
|
||||
.AsBuilder()
|
||||
.UseFunctionInvocation()
|
||||
.UseOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the chat client level
|
||||
.Build())
|
||||
.AsBuilder()
|
||||
.UseOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the agent level
|
||||
.Build();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# OpenTelemetry Aspire Demo with Azure OpenAI
|
||||
# OpenTelemetry Aspire Demo with Microsoft Foundry
|
||||
|
||||
This demo showcases the integration of OpenTelemetry with the Microsoft Agent Framework using Azure OpenAI and .NET Aspire Dashboard for telemetry visualization.
|
||||
This demo showcases the integration of OpenTelemetry with the Microsoft Agent Framework using Microsoft Foundry and the .NET Aspire Dashboard for telemetry visualization.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -15,7 +15,7 @@ The demo consists of three main components:
|
||||
```mermaid
|
||||
graph TD
|
||||
A["Console App<br/>(Interactive)"] --> B["Agent Framework<br/>with OpenTel<br/>Instrumentation"]
|
||||
B --> C["Azure OpenAI<br/>Service"]
|
||||
B --> C["Microsoft Foundry<br/>Project"]
|
||||
A --> D["Aspire Dashboard<br/>(OpenTelemetry Visualization)"]
|
||||
B --> D
|
||||
```
|
||||
@@ -23,21 +23,21 @@ graph TD
|
||||
## Prerequisites
|
||||
|
||||
- .NET 10 SDK or later
|
||||
- Azure OpenAI service endpoint and deployment configured
|
||||
- Microsoft Foundry project endpoint and model configured
|
||||
- Azure CLI installed and authenticated (for Azure credential authentication)
|
||||
- Docker installed (for running Aspire Dashboard)
|
||||
- [Optional] Application Insights and Grafana
|
||||
|
||||
## Configuration
|
||||
|
||||
### Azure OpenAI Setup
|
||||
### Microsoft Foundry Setup
|
||||
Set the following environment variables:
|
||||
```powershell
|
||||
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
|
||||
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
|
||||
$env:FOUNDRY_PROJECT_ENDPOINT="https://<your-project>.services.ai.azure.com/api/projects/<your-project>"
|
||||
$env:FOUNDRY_MODEL="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
|
||||
```
|
||||
|
||||
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource.
|
||||
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Foundry project.
|
||||
|
||||
### [Optional] Application Insights Setup
|
||||
Set the following environment variables:
|
||||
@@ -56,7 +56,7 @@ The easiest way to run the demo is using the provided PowerShell script:
|
||||
```
|
||||
|
||||
This script will automatically:
|
||||
- ✅ Check prerequisites (Docker, Azure OpenAI configuration)
|
||||
- ✅ Check prerequisites (Docker, Foundry configuration)
|
||||
- 🔨 Build the console application
|
||||
- 🐳 Start the Aspire Dashboard via Docker (with anonymous access)
|
||||
- ⏳ Wait for dashboard to be ready (polls port until listening)
|
||||
@@ -124,7 +124,7 @@ You:
|
||||
3. Each trace contains:
|
||||
- An outer span for the entire agent interaction
|
||||
- Inner spans from the Agent Framework's OpenTelemetry instrumentation
|
||||
- Spans from HTTP calls to Azure OpenAI
|
||||
- Spans from HTTP calls to Microsoft Foundry
|
||||
|
||||
### Metrics
|
||||
1. Navigate to the **Metrics** tab
|
||||
@@ -158,7 +158,7 @@ Open dashboard in Azure portal: <https://aka.ms/amg/dash/af-workflow>
|
||||
- **Telemetry correlation** across the entire request flow
|
||||
|
||||
### Agent Framework Features
|
||||
- **ChatClientAgent** with Azure OpenAI integration
|
||||
- **ChatClientAgent** created from `AIProjectClient`
|
||||
- **OpenTelemetry wrapper** using `.WithOpenTelemetry()`
|
||||
- **Conversation threading** for multi-turn conversations
|
||||
- **Error handling** with telemetry correlation
|
||||
@@ -182,7 +182,7 @@ Complete demo startup script that handles everything automatically.
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **Automatic configuration detection** - Checks for Azure OpenAI configuration
|
||||
- **Automatic configuration detection** - Checks for Foundry configuration
|
||||
- **Project building** - Automatically builds projects before running
|
||||
- **Error handling** - Provides clear error messages if something goes wrong
|
||||
- **Multi-window support** - Opens dashboard in separate window for better experience
|
||||
@@ -201,10 +201,10 @@ If you encounter port binding errors, try:
|
||||
2. Or kill any processes using the conflicting ports
|
||||
|
||||
### Authentication Issues
|
||||
- Ensure your Azure OpenAI endpoint is correctly configured
|
||||
- Ensure your Foundry project endpoint is correctly configured
|
||||
- Check that the environment variables are set in the correct terminal session
|
||||
- Verify you're logged in with Azure CLI (`az login`) and have access to the Azure OpenAI resource
|
||||
- Ensure the Azure OpenAI deployment name matches your actual deployment
|
||||
- Verify you're logged in with Azure CLI (`az login`) and have access to the Foundry project
|
||||
- Ensure the `FOUNDRY_MODEL` value matches an enabled model in your Foundry project
|
||||
|
||||
### Build Issues
|
||||
- Ensure you're using .NET 10.0 SDK
|
||||
@@ -216,7 +216,7 @@ If you encounter port binding errors, try:
|
||||
```
|
||||
AgentOpenTelemetry/
|
||||
├── AgentOpenTelemetry.csproj # Project file with dependencies
|
||||
├── Program.cs # Main application with Azure OpenAI agent integration
|
||||
├── Program.cs # Main application with Foundry AIProjectClient agent integration
|
||||
├── start-demo.ps1 # PowerShell script to start the demo
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
|
||||
// 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.
|
||||
// You must dissable client side conversation storage for clients that support it
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ List<AITool> agentTools = [.. mcpTools.Cast<AITool>()];
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName,
|
||||
instructions: "You are a helpful assistant that can help with Microsoft documentation questions. Use the Microsoft Learn MCP tool to search for documentation.",
|
||||
instructions: "You are a helpful assistant that can help with Microsoft documentation questions. Use the Microsoft Learn MCP tool to search for documentation. In the output, indicate which tool you used if any.",
|
||||
name: "DocsAgent",
|
||||
tools: agentTools);
|
||||
|
||||
|
||||
+31
@@ -8,6 +8,7 @@ using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Assistants;
|
||||
using OpenAI.Responses;
|
||||
|
||||
const string AgentInstructions = "You are a personal math tutor. When asked a math question, write and run code using the python tool to answer the question.";
|
||||
const string AgentName = "CoderAgent-RAPI";
|
||||
@@ -19,11 +20,41 @@ string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// The easiest way to add the hosted code interpreter is as follows:
|
||||
/*
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(
|
||||
deploymentName,
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
tools: [new HostedCodeInterpreterTool() { Inputs = [] }]);
|
||||
*/
|
||||
|
||||
// However, by default the reponses API does not return the output items from the hosted code interpreter tool.
|
||||
// This is generally fine but for this sample we want to explicitly request those in the response generation configuration.
|
||||
AIAgent agent = aiProjectClient
|
||||
.GetProjectOpenAIClient()
|
||||
.GetProjectResponsesClient()
|
||||
.AsIChatClient(deploymentName)
|
||||
.AsBuilder()
|
||||
.ConfigureOptions(x =>
|
||||
{
|
||||
var previousFactory = x.RawRepresentationFactory;
|
||||
x.RawRepresentationFactory = state =>
|
||||
{
|
||||
var responseOptions = previousFactory?.Invoke(state) as CreateResponseOptions ?? new CreateResponseOptions();
|
||||
|
||||
// Ensure that the response includes tool output items from the hosted code interpreter
|
||||
responseOptions.IncludedProperties.Add(IncludedResponseProperty.CodeInterpreterCallOutputs);
|
||||
|
||||
return responseOptions;
|
||||
};
|
||||
})
|
||||
.Build()
|
||||
.AsAIAgent(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
tools: [new HostedCodeInterpreterTool() { Inputs = [] }]);
|
||||
|
||||
AgentResponse response = await agent.RunAsync("I need to solve the equation sin(x) + x^2 = 42");
|
||||
|
||||
|
||||
+10
-1
@@ -12,7 +12,7 @@ using Microsoft.Extensions.AI;
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
const string AgentInstructions = "You are a helpful assistant that can use the countries API to retrieve information about countries by their currency code.";
|
||||
const string AgentInstructions = "You are a helpful assistant that can use the countries API to retrieve information about countries by their currency code. When calling the API, always pass fields=name to limit the response to just country names.";
|
||||
// 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.
|
||||
@@ -58,6 +58,15 @@ OpenApiFunctionDefinition CreateOpenAPIFunctionDefinition()
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"in": "query",
|
||||
"description": "Comma-separated list of fields to include in the response (e.g., name,currencies)",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
+37
@@ -64,6 +64,43 @@ AgentResponse response = await agent.RunAsync("Write a small .NET 10 C# hello wo
|
||||
Console.WriteLine(response);
|
||||
```
|
||||
|
||||
## Approving or denying tool execution
|
||||
|
||||
The GitHub Copilot SDK owns the tool-calling loop for this provider, so approval is enforced through the SDK's
|
||||
native pre-execution hook rather than the Agent Framework chat-client approval round-trip.
|
||||
|
||||
When you register a tool wrapped in `ApprovalRequiredAIFunction`, `GitHubCopilotAgent` installs a default
|
||||
`SessionConfig.Hooks.OnPreToolUse` hook that returns `"ask"` for that tool and defers (`null`) for all other tools.
|
||||
The `"ask"` decision routes to your `SessionConfig.OnPermissionRequest` handler, where you approve or deny the call
|
||||
(this also fires even for tools configured with `SkipPermission = true`):
|
||||
|
||||
```csharp
|
||||
using GitHub.Copilot;
|
||||
|
||||
AIFunction deleteFile = AIFunctionFactory.Create(DeleteFile, "DeleteFile", "Deletes a file.");
|
||||
|
||||
SessionConfig sessionConfig = new()
|
||||
{
|
||||
// Wrapping the tool marks it approval-required; the agent turns this into an "ask" at OnPreToolUse.
|
||||
Tools = [new ApprovalRequiredAIFunction(deleteFile)],
|
||||
|
||||
// OnPermissionRequest decides the "asked" tools (and Copilot's built-in shell/file/URL prompts).
|
||||
OnPermissionRequest = (request, invocation) =>
|
||||
{
|
||||
// Surface to a human, check policy, etc.
|
||||
bool approved = AskHuman(request);
|
||||
return Task.FromResult(approved
|
||||
? PermissionDecision.ApproveOnce()
|
||||
: PermissionDecision.Reject("Denied by user."));
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
> **⚠️ If you provide your own `OnPreToolUse` hook**, it takes precedence and the agent does **not** install its
|
||||
> default approval hook. In that case **you are fully responsible** for enforcing approval — including for any
|
||||
> `ApprovalRequiredAIFunction` you register (e.g. by returning a `"deny"` or `"ask"` `PreToolUseHookOutput`). The
|
||||
> agent logs a warning when it detects an approval-required tool that your hook must handle.
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
To get streaming responses:
|
||||
|
||||
+3
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,17 +9,13 @@
|
||||
<NoWarn>$(NoWarn);MAAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SubprocessScriptRunner.cs" Link="SubprocessScriptRunner.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy skills directory to output -->
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
//
|
||||
// This sample uses a unit-converter skill that converts between miles, kilometers, pounds, and kilograms.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// --- Configuration ---
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- Skills Provider ---
|
||||
// Discovers skills from the 'skills' directory containing SKILL.md files.
|
||||
@@ -29,18 +28,17 @@ var skillsProvider = new AgentSkillsProvider(
|
||||
// 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()
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "UnitConverterAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant that can convert units.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName);
|
||||
});
|
||||
|
||||
// --- Example: Unit conversion ---
|
||||
Console.WriteLine("Converting units with file-based skills");
|
||||
|
||||
+2
-6
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -10,12 +10,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
// 3. Code scripts — executable delegates the agent can invoke directly
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// --- Configuration ---
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- Build the code-defined skill ---
|
||||
var unitConverterSkill = new AgentInlineSkill(
|
||||
@@ -70,18 +69,17 @@ var skillsProvider = new AgentSkillsProvider(unitConverterSkill);
|
||||
// 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()
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "UnitConverterAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant that can convert units.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName);
|
||||
});
|
||||
|
||||
// --- Example: Unit conversion ---
|
||||
Console.WriteLine("Converting units with code-defined skills");
|
||||
|
||||
+2
-6
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -10,12 +10,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// --- Configuration ---
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- Class-Based Skill ---
|
||||
// Instantiate the skill class.
|
||||
@@ -25,18 +24,17 @@ var skillsProvider = new AgentSkillsProvider(unitConverter);
|
||||
// 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()
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "UnitConverterAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant that can convert units.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName);
|
||||
});
|
||||
|
||||
// --- Example: Unit conversion ---
|
||||
Console.WriteLine("Converting units with class-based skills");
|
||||
|
||||
+3
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,17 +9,13 @@
|
||||
<NoWarn>$(NoWarn);MAAI001;IDE0051</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SubprocessScriptRunner.cs" Link="SubprocessScriptRunner.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy skills directory to output -->
|
||||
|
||||
@@ -15,15 +15,14 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// --- Configuration ---
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
|
||||
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- 1. Code-Defined Skill: volume-converter ---
|
||||
var volumeConverterSkill = new AgentInlineSkill(
|
||||
@@ -67,18 +66,17 @@ var skillsProvider = new AgentSkillsProviderBuilder()
|
||||
// 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()
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "MultiConverterAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant that can convert units, volumes, and temperatures.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName);
|
||||
});
|
||||
|
||||
// --- Example: Use all three skills ---
|
||||
Console.WriteLine("Converting with mixed skills (file + code + class)");
|
||||
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -10,13 +10,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -15,15 +15,14 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// --- Configuration ---
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- DI Container ---
|
||||
// Register application services that skill resources and scripts can resolve at execution time.
|
||||
@@ -83,19 +82,18 @@ var skillsProvider = new AgentSkillsProvider(distanceSkill, weightSkill);
|
||||
// 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()
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(
|
||||
options: new ChatClientAgentOptions
|
||||
{
|
||||
Name = "UnitConverterAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant that can convert units.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName,
|
||||
services: serviceProvider);
|
||||
|
||||
// --- Example: Unit conversion ---
|
||||
|
||||
+1
-2
@@ -10,7 +10,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
@@ -18,7 +17,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Mcp\Microsoft.Agents.AI.Mcp.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// to discover and inject the skill into a ChatClientAgent.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -20,7 +20,6 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Server;
|
||||
using OpenAI.Responses;
|
||||
|
||||
if (args.Length > 0 && args[0] == "--server")
|
||||
{
|
||||
@@ -29,9 +28,9 @@ if (args.Length > 0 && args[0] == "--server")
|
||||
}
|
||||
|
||||
// --- Configuration ---
|
||||
string openAiEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string openAiEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
|
||||
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- MCP client + skill discovery ---
|
||||
// Launch this same assembly as a stdio MCP server in a child process.
|
||||
@@ -54,18 +53,17 @@ var skillsProvider = new AgentSkillsProviderBuilder()
|
||||
// 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(openAiEndpoint), new DefaultAzureCredential())
|
||||
.GetResponsesClient()
|
||||
AIAgent agent = new AIProjectClient(new Uri(openAiEndpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "SkillsAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant. Use available skills to answer the user.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName);
|
||||
});
|
||||
|
||||
// --- Run ---
|
||||
Console.WriteLine(new string('-', 60));
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);MAAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SubprocessScriptRunner.cs" Link="SubprocessScriptRunner.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy skills directory to output -->
|
||||
<ItemGroup>
|
||||
<None Include="skills\**\*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to configure auto-approval rules for skill tools using the
|
||||
// UseToolApproval middleware. It builds on the file-based skills pattern from Step01, adding
|
||||
// ToolApprovalAgent middleware with auto-approval rules so that read-only skill operations
|
||||
// (load_skill, read_skill_resource) are approved automatically while script execution
|
||||
// (run_skill_script) still requires explicit user approval.
|
||||
//
|
||||
// All tools exposed by AgentSkillsProvider always require approval by default.
|
||||
// Auto-approval rules let you selectively bypass the approval prompt for safe operations.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
// --- Configuration ---
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
|
||||
// --- Skills Provider ---
|
||||
// Discovers skills from the 'skills' directory containing SKILL.md files.
|
||||
// The script runner runs file-based scripts (e.g. Python) as local subprocesses.
|
||||
var skillsProvider = new AgentSkillsProvider(
|
||||
Path.Combine(AppContext.BaseDirectory, "skills"),
|
||||
SubprocessScriptRunner.RunAsync);
|
||||
|
||||
// --- Agent Setup ---
|
||||
// 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
|
||||
{
|
||||
Name = "UnitConverterAgent",
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are a helpful assistant that can convert units.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
},
|
||||
model: deploymentName)
|
||||
.AsBuilder()
|
||||
.UseToolApproval(new ToolApprovalAgentOptions
|
||||
{
|
||||
// Auto-approve read-only skill tools (load_skill, read_skill_resource).
|
||||
// run_skill_script will still require explicit user approval.
|
||||
AutoApprovalRules = [AgentSkillsProvider.ReadOnlyToolsAutoApprovalRule],
|
||||
})
|
||||
.Build();
|
||||
|
||||
// For other auto-approval options (all tools, custom lambdas, combining providers),
|
||||
// see the README.md in this sample directory.
|
||||
|
||||
// --- Example: Unit conversion with auto-approval ---
|
||||
Console.WriteLine("Converting units with file-based skills and auto-approval");
|
||||
Console.WriteLine(new string('-', 60));
|
||||
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
AgentResponse response = await agent.RunAsync(
|
||||
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?",
|
||||
session);
|
||||
|
||||
// Handle any pending approval requests (only script execution should require approval)
|
||||
List<ToolApprovalRequestContent> approvalRequests = response.Messages
|
||||
.SelectMany(m => m.Contents)
|
||||
.OfType<ToolApprovalRequestContent>()
|
||||
.ToList();
|
||||
|
||||
while (approvalRequests.Count > 0)
|
||||
{
|
||||
List<ChatMessage> userInputResponses = approvalRequests
|
||||
.ConvertAll(functionApprovalRequest =>
|
||||
{
|
||||
var toolCall = (FunctionCallContent)functionApprovalRequest.ToolCall;
|
||||
Console.WriteLine($"Approval required for: {toolCall.Name}. Reply Y to approve:");
|
||||
bool approved = Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved)]);
|
||||
});
|
||||
|
||||
response = await agent.RunAsync(userInputResponses, session);
|
||||
approvalRequests = response.Messages
|
||||
.SelectMany(m => m.Contents)
|
||||
.OfType<ToolApprovalRequestContent>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Agent: {response.Text}");
|
||||
@@ -0,0 +1,102 @@
|
||||
# Skills Auto-Approval Sample
|
||||
|
||||
This sample demonstrates how to configure **auto-approval rules** for skill tools using the `UseToolApproval` middleware and `AgentSkillsProvider`'s built-in approval rules.
|
||||
|
||||
It builds on the [file-based skills sample](../Agent_Step01_FileBasedSkills/) by adding `ToolApprovalAgent` middleware that auto-approves read-only skill operations while still prompting for script execution.
|
||||
|
||||
## What it demonstrates
|
||||
|
||||
- All tools exposed by `AgentSkillsProvider` (`load_skill`, `read_skill_resource`, `run_skill_script`) always require approval by default
|
||||
- Multiple ways to configure auto-approval (see below)
|
||||
- Handling approval prompts for script execution via `ToolApprovalRequestContent`
|
||||
|
||||
## Configuring Auto-Approval
|
||||
|
||||
Auto-approval rules are passed to `ToolApprovalAgentOptions.AutoApprovalRules` when calling `UseToolApproval`. Rules are evaluated in order; the first rule returning `true` auto-approves the call.
|
||||
|
||||
### Option 1: Built-in read-only rule
|
||||
|
||||
Auto-approves `load_skill` and `read_skill_resource` while still prompting for `run_skill_script`:
|
||||
|
||||
```csharp
|
||||
.UseToolApproval(new ToolApprovalAgentOptions
|
||||
{
|
||||
AutoApprovalRules = [AgentSkillsProvider.ReadOnlyToolsAutoApprovalRule],
|
||||
})
|
||||
```
|
||||
|
||||
### Option 2: Built-in all-tools rule
|
||||
|
||||
Auto-approves all three skill tools without prompting:
|
||||
|
||||
```csharp
|
||||
.UseToolApproval(new ToolApprovalAgentOptions
|
||||
{
|
||||
AutoApprovalRules = [AgentSkillsProvider.AllToolsAutoApprovalRule],
|
||||
})
|
||||
```
|
||||
|
||||
### Option 3: Custom lambda rule
|
||||
|
||||
Provide your own logic as a `Func<FunctionCallContent, ValueTask<bool>>`. For example, to auto-approve only `load_skill`:
|
||||
|
||||
```csharp
|
||||
.UseToolApproval(new ToolApprovalAgentOptions
|
||||
{
|
||||
AutoApprovalRules =
|
||||
[
|
||||
(FunctionCallContent functionCall) =>
|
||||
new ValueTask<bool>(functionCall.Name == AgentSkillsProvider.LoadSkillToolName),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Combining rules from multiple providers
|
||||
|
||||
When using multiple providers (e.g., skills + file access), combine their rules in a single list:
|
||||
|
||||
```csharp
|
||||
.UseToolApproval(new ToolApprovalAgentOptions
|
||||
{
|
||||
AutoApprovalRules =
|
||||
[
|
||||
AgentSkillsProvider.ReadOnlyToolsAutoApprovalRule,
|
||||
FileAccessProvider.ReadOnlyToolsAutoApprovalRule,
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## Skills Included
|
||||
|
||||
### unit-converter
|
||||
|
||||
Converts between common units (miles↔km, pounds↔kg) using a multiplication factor.
|
||||
|
||||
- `references/conversion-table.md` — Conversion factor table
|
||||
- `scripts/convert.py` — Python script that performs the conversion
|
||||
|
||||
## Running the Sample
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- .NET 10.0 SDK
|
||||
- Azure OpenAI endpoint with a deployed model
|
||||
- Python 3 installed and available as `python3` on your PATH
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
|
||||
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
- `load_skill` and `read_skill_resource` calls are auto-approved (no user prompt)
|
||||
- `run_skill_script` calls prompt the user for approval before executing
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: unit-converter
|
||||
description: Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
When the user requests a unit conversion:
|
||||
1. First, review `references/conversion-table.md` to find the correct factor
|
||||
2. Run the `scripts/convert.py` script with `--value <number> --factor <factor>` (e.g. `--value 26.2 --factor 1.60934`)
|
||||
3. Present the converted value clearly with both units
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# Conversion Tables
|
||||
|
||||
Formula: **result = value × factor**
|
||||
|
||||
| From | To | Factor |
|
||||
|-------------|-------------|----------|
|
||||
| miles | kilometers | 1.60934 |
|
||||
| kilometers | miles | 0.621371 |
|
||||
| pounds | kilograms | 0.453592 |
|
||||
| kilograms | pounds | 2.20462 |
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Unit conversion script
|
||||
# Converts a value using a multiplication factor: result = value × factor
|
||||
#
|
||||
# Usage:
|
||||
# python scripts/convert.py --value 26.2 --factor 1.60934
|
||||
# python scripts/convert.py --value 75 --factor 2.20462
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convert a value using a multiplication factor.",
|
||||
epilog="Examples:\n"
|
||||
" python scripts/convert.py --value 26.2 --factor 1.60934\n"
|
||||
" python scripts/convert.py --value 75 --factor 2.20462",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument("--value", type=float, required=True, help="The numeric value to convert.")
|
||||
parser.add_argument("--factor", type=float, required=True, help="The conversion factor from the table.")
|
||||
args = parser.parse_args()
|
||||
|
||||
result = round(args.value * args.factor, 4)
|
||||
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+1
-6
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hyperlight\Microsoft.Agents.AI.Hyperlight.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+5
-7
@@ -4,14 +4,13 @@
|
||||
// code interpreter: the model can write and execute arbitrary Python code to
|
||||
// answer quantitative questions without calling any additional tools.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hyperlight;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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 guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH") ?? throw new InvalidOperationException("HYPERLIGHT_PYTHON_GUEST_PATH is not set.");
|
||||
|
||||
using var codeAct = new HyperlightCodeActProvider(HyperlightCodeActProviderOptions.CreateForWasm(guestPath));
|
||||
@@ -19,13 +18,12 @@ using var codeAct = new HyperlightCodeActProvider(HyperlightCodeActProviderOptio
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful assistant. When the user asks something quantitative, write Python and call `execute_code` instead of guessing." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful assistant. When the user asks something quantitative, write Python and call `execute_code` instead of guessing." },
|
||||
AIContextProviders = [codeAct],
|
||||
});
|
||||
|
||||
|
||||
+1
-6
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hyperlight\Microsoft.Agents.AI.Hyperlight.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+5
-7
@@ -7,15 +7,14 @@
|
||||
// ApprovalRequiredAIFunction so any code that reaches it requires user approval
|
||||
// for the entire execute_code invocation.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hyperlight;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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 guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH") ?? throw new InvalidOperationException("HYPERLIGHT_PYTHON_GUEST_PATH is not set.");
|
||||
|
||||
AIFunction fetchDocs = AIFunctionFactory.Create(
|
||||
@@ -42,13 +41,12 @@ using var codeAct = new HyperlightCodeActProvider(options);
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful assistant. Prefer orchestrating your work in a single `execute_code` block using `call_tool(...)` over issuing many direct tool calls." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful assistant. Prefer orchestrating your work in a single `execute_code` block using `call_tool(...)` over issuing many direct tool calls." },
|
||||
AIContextProviders = [codeAct],
|
||||
});
|
||||
|
||||
|
||||
+1
-6
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hyperlight\Microsoft.Agents.AI.Hyperlight.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+5
-7
@@ -5,15 +5,14 @@
|
||||
// when you want a fixed tool surface for the agent's lifetime and don't need
|
||||
// the per-run snapshot/registry semantics of HyperlightCodeActProvider.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hyperlight;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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 guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH") ?? throw new InvalidOperationException("HYPERLIGHT_PYTHON_GUEST_PATH is not set.");
|
||||
|
||||
AIFunction calculate = AIFunctionFactory.Create(
|
||||
@@ -34,10 +33,9 @@ var instructions =
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: instructions, tools: [executeCode]);
|
||||
.AsAIAgent(model: deploymentName, instructions: instructions, tools: [executeCode]);
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("What is 12.3 * 4.5? Use the multiply tool from within `execute_code`."));
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,14 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+10
-11
@@ -3,17 +3,18 @@
|
||||
// This sample shows how to create and use a simple AI agent that stores chat messages in a vector store using the ChatHistoryMemoryProvider.
|
||||
// It can then use the chat history from prior conversations to inform responses in new conversations.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
|
||||
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 embeddingDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-large";
|
||||
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Create a vector store to store the chat messages in.
|
||||
// For demonstration purposes, we are using an in-memory vector store.
|
||||
@@ -23,19 +24,17 @@ VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions
|
||||
// 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.
|
||||
EmbeddingGenerator = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
EmbeddingGenerator = aiProjectClient
|
||||
.GetProjectOpenAIClient()
|
||||
.GetEmbeddingClient(embeddingDeploymentName)
|
||||
.AsIEmbeddingGenerator()
|
||||
});
|
||||
|
||||
// Create the agent and add the ChatHistoryMemoryProvider to store chat messages in the vector store.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
AIAgent agent = aiProjectClient
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
AIContextProviders = [new ChatHistoryMemoryProvider(
|
||||
vectorStore,
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Mem0\Microsoft.Agents.AI.Mem0.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+7
-10
@@ -6,15 +6,13 @@
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Mem0;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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 mem0ServiceUri = Environment.GetEnvironmentVariable("MEM0_ENDPOINT") ?? throw new InvalidOperationException("MEM0_ENDPOINT is not set.");
|
||||
var mem0ApiKey = Environment.GetEnvironmentVariable("MEM0_API_KEY") ?? throw new InvalidOperationException("MEM0_API_KEY is not set.");
|
||||
@@ -24,16 +22,15 @@ using HttpClient mem0HttpClient = new();
|
||||
mem0HttpClient.BaseAddress = new Uri(mem0ServiceUri);
|
||||
mem0HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", mem0ApiKey);
|
||||
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
AIAgent agent = aiProjectClient
|
||||
.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
|
||||
// The stateInitializer can be used to customize the Mem0 scope per session and it will be called each time a session
|
||||
// is encountered by the Mem0Provider that does not already have Mem0Provider state stored on the session.
|
||||
// If each session should have its own Mem0 scope, you can create a new id per session via the stateInitializer, e.g.:
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Valkey\Microsoft.Agents.AI.Valkey.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+5
-7
@@ -8,16 +8,15 @@
|
||||
// docker run -d --name valkey -p 6379:6379 valkey/valkey:latest
|
||||
// - Azure OpenAI endpoint and deployment configured via environment variables
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Valkey;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
using Valkey.Glide;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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 valkeyConnection = Environment.GetEnvironmentVariable("VALKEY_CONNECTION") ?? "localhost:6379";
|
||||
|
||||
var connection = await ConnectionMultiplexer.ConnectAsync(valkeyConnection);
|
||||
@@ -33,11 +32,10 @@ var historyProvider = new ValkeyChatHistoryProvider(
|
||||
MaxMessages = 20
|
||||
});
|
||||
|
||||
AIAgent historyAgent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
AIAgent historyAgent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful assistant that remembers our conversation." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful assistant that remembers our conversation." },
|
||||
ChatHistoryProvider = historyProvider
|
||||
});
|
||||
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,14 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+9
-10
@@ -5,30 +5,30 @@
|
||||
// When the agent is invoked, it searches the vector store for relevant older messages and
|
||||
// prepends them as a "memory" context message before the recent session history.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using OpenAI.Chat;
|
||||
using SampleApp;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
|
||||
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 embeddingDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-large";
|
||||
|
||||
// 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 credential = new DefaultAzureCredential();
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Create a vector store to store overflow chat messages.
|
||||
// For demonstration purposes, we are using an in-memory vector store.
|
||||
// Replace this with a persistent vector store implementation for production scenarios.
|
||||
VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions()
|
||||
{
|
||||
EmbeddingGenerator = new AzureOpenAIClient(new Uri(endpoint), credential)
|
||||
EmbeddingGenerator = aiProjectClient
|
||||
.GetProjectOpenAIClient()
|
||||
.GetEmbeddingClient(embeddingDeploymentName)
|
||||
.AsIEmbeddingGenerator()
|
||||
});
|
||||
@@ -49,11 +49,10 @@ var boundedProvider = new BoundedChatHistoryProvider(
|
||||
searchScope: new() { UserId = "UID1" }));
|
||||
|
||||
// Create the agent with the bounded chat history provider.
|
||||
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), credential)
|
||||
.GetChatClient(deploymentName)
|
||||
AIAgent agent = aiProjectClient
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful assistant. Answer questions concisely." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful assistant. Answer questions concisely." },
|
||||
Name = "Assistant",
|
||||
ChatHistoryProvider = boundedProvider,
|
||||
});
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,14 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,30 +5,29 @@
|
||||
// The TextSearchProvider runs a search against the vector store via the TextSearchStore before each model invocation and injects the results into the model context.
|
||||
// The TextSearchStore is a sample store implementation that hardcodes a storage schema and uses the vector store to store and retrieve documents.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Samples;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
|
||||
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 embeddingDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-large";
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AzureOpenAIClient azureOpenAIClient = new(
|
||||
AIProjectClient aiProjectClient = new(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential());
|
||||
|
||||
// Create an In-Memory vector store that uses the Azure OpenAI embedding model to generate embeddings.
|
||||
// Create an In-Memory vector store that uses the Azure AI Foundry embedding model to generate embeddings.
|
||||
VectorStore vectorStore = new InMemoryVectorStore(new()
|
||||
{
|
||||
EmbeddingGenerator = azureOpenAIClient.GetEmbeddingClient(embeddingDeploymentName).AsIEmbeddingGenerator()
|
||||
EmbeddingGenerator = aiProjectClient.GetProjectOpenAIClient().GetEmbeddingClient(embeddingDeploymentName).AsIEmbeddingGenerator()
|
||||
});
|
||||
|
||||
// Create a store that defines a storage schema, and uses the vector store to store and retrieve documents.
|
||||
@@ -60,11 +59,10 @@ TextSearchProviderOptions textSearchOptions = new()
|
||||
};
|
||||
|
||||
// Create the AI agent with the TextSearchProvider as the AI context provider.
|
||||
AIAgent agent = azureOpenAIClient
|
||||
.GetChatClient(deploymentName)
|
||||
AIAgent agent = aiProjectClient
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
|
||||
AIContextProviders = [new TextSearchProvider(SearchAdapter, textSearchOptions)],
|
||||
// Since we are using ChatCompletion which stores chat history locally, we can also add a message filter
|
||||
// that removes messages produced by the TextSearchProvider before they are added to the chat history, so that
|
||||
|
||||
+1
-3
@@ -9,14 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+9
-11
@@ -4,33 +4,32 @@
|
||||
// While the sample is using Qdrant, it can easily be replaced with any other vector store that implements the Microsoft.Extensions.VectorData abstractions.
|
||||
// The TextSearchProvider runs a search against the vector store before each model invocation and injects the results into the model context.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.Qdrant;
|
||||
using OpenAI.Chat;
|
||||
using Qdrant.Client;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
|
||||
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 embeddingDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-large";
|
||||
var afOverviewUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/overview/index.md";
|
||||
var afMigrationUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/migration-guide/from-semantic-kernel/index.md";
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AzureOpenAIClient azureOpenAIClient = new(
|
||||
AIProjectClient aiProjectClient = new(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential());
|
||||
|
||||
// Create a Qdrant vector store that uses the Azure OpenAI embedding model to generate embeddings.
|
||||
// Create a Qdrant vector store that uses the Azure AI Foundry embedding model to generate embeddings.
|
||||
QdrantClient client = new("localhost");
|
||||
VectorStore vectorStore = new QdrantVectorStore(client, ownsClient: true, new()
|
||||
{
|
||||
EmbeddingGenerator = azureOpenAIClient.GetEmbeddingClient(embeddingDeploymentName).AsIEmbeddingGenerator()
|
||||
EmbeddingGenerator = aiProjectClient.GetProjectOpenAIClient().GetEmbeddingClient(embeddingDeploymentName).AsIEmbeddingGenerator()
|
||||
});
|
||||
|
||||
// Create a collection and upsert some text into it.
|
||||
@@ -69,11 +68,10 @@ TextSearchProviderOptions textSearchOptions = new()
|
||||
};
|
||||
|
||||
// Create the AI agent with the TextSearchProvider as the AI context provider.
|
||||
AIAgent agent = azureOpenAIClient
|
||||
.GetChatClient(deploymentName)
|
||||
AIAgent agent = aiProjectClient
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief." },
|
||||
AIContextProviders = [new TextSearchProvider(SearchAdapter, textSearchOptions)],
|
||||
// Configure a filter on the InMemoryChatHistoryProvider so that we don't persist the messages produced by the TextSearchProvider in chat history.
|
||||
// The default is to persist all messages except those that came from chat history in the first place.
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+5
-7
@@ -6,14 +6,13 @@
|
||||
// The provider invokes the custom search function
|
||||
// before each model invocation and injects the results into the model context.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
TextSearchProviderOptions textSearchOptions = new()
|
||||
{
|
||||
@@ -25,13 +24,12 @@ TextSearchProviderOptions textSearchOptions = new()
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
|
||||
AIContextProviders = [new TextSearchProvider(MockSearchAsync, textSearchOptions)]
|
||||
});
|
||||
|
||||
|
||||
+3
-5
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -20,15 +20,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.19.0" />
|
||||
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc4" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.6.0" />
|
||||
<PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.2.0" />
|
||||
<PackageReference Include="Neo4j.AgentFramework.GraphRAG" Version="0.1.0-preview.2" />
|
||||
<PackageReference Include="Neo4j.Driver" Version="5.28.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.21.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.100">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Neo4j.AgentFramework.GraphRAG;
|
||||
using Neo4j.Driver;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
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 neo4jUri = Environment.GetEnvironmentVariable("NEO4J_URI") ?? throw new InvalidOperationException("NEO4J_URI is not set.");
|
||||
var neo4jUsername = Environment.GetEnvironmentVariable("NEO4J_USERNAME") ?? "neo4j";
|
||||
var neo4jPassword = Environment.GetEnvironmentVariable("NEO4J_PASSWORD") ?? throw new InvalidOperationException("NEO4J_PASSWORD is not set.");
|
||||
@@ -48,15 +48,14 @@ await using var provider = new Neo4jContextProvider(
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsIChatClient()
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant that answers questions using Neo4j graph context."
|
||||
},
|
||||
AIContextProviders = [provider]
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+12
-11
@@ -1,20 +1,22 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use a ChatClientAgent with function tools that require a human in the loop for approvals.
|
||||
// It shows both non-streaming and streaming agent interactions using menu-related tools.
|
||||
// If the agent is hosted in a service, with a remote user, combine this sample with the Persisted Conversations sample to persist the chat history
|
||||
// while the agent is waiting for user input.
|
||||
// Function Tools with Approvals — Human-in-the-loop tool execution
|
||||
//
|
||||
// This sample demonstrates how to use function tools that require human
|
||||
// approval before execution. It shows both non-streaming and streaming
|
||||
// agent interactions using menu-related tools.
|
||||
// If the agent is hosted in a service, combine this with the Persisted
|
||||
// Conversations sample to persist chat history while waiting for user input.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// Create a sample function tool that the agent can use.
|
||||
[Description("Get the weather for a given location.")]
|
||||
@@ -26,11 +28,10 @@ static string GetWeather([Description("The location to get the weather for.")] s
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]);
|
||||
.AsAIAgent(model: deploymentName, instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]);
|
||||
|
||||
// Call the agent and check if there are any function approval requests to handle.
|
||||
// For simplicity, we are assuming here that only function approvals are pending.
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to configure ChatClientAgent to produce structured output.
|
||||
// Structured Output — Configure agents to return typed JSON
|
||||
//
|
||||
// This sample shows how to configure a ChatClientAgent to produce
|
||||
// structured output using JSON schema constraints with Azure AI Foundry.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Extensions.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
using SampleApp;
|
||||
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
|
||||
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// Create chat client to be used by chat client agents.
|
||||
// Create AI Project client to be used by chat client agents.
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
ChatClient chatClient = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Demonstrates how to work with structured output via ResponseFormat with the non-generic RunAsync method.
|
||||
// This approach is useful when:
|
||||
@@ -31,35 +31,36 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
// and passes it as text to another agent as input, without the need for the caller to directly work with the structured output.
|
||||
// b. The type of the structured output is not known at compile time, so the generic RunAsync<T> method cannot be used.
|
||||
// c. The type of the structured output is represented by JSON schema only, without a corresponding class or type in the code.
|
||||
await UseStructuredOutputWithResponseFormatAsync(chatClient);
|
||||
await UseStructuredOutputWithResponseFormatAsync(aiProjectClient, deploymentName);
|
||||
|
||||
// Demonstrates how to work with structured output via the generic RunAsync<T> method.
|
||||
// This approach is useful when the caller needs to directly work with the structured output in the code
|
||||
// via an instance of the corresponding class or type and the type is known at compile time.
|
||||
await UseStructuredOutputWithRunAsync(chatClient);
|
||||
await UseStructuredOutputWithRunAsync(aiProjectClient, deploymentName);
|
||||
|
||||
// Demonstrates how to work with structured output when streaming using the RunStreamingAsync method.
|
||||
await UseStructuredOutputWithRunStreamingAsync(chatClient);
|
||||
await UseStructuredOutputWithRunStreamingAsync(aiProjectClient, deploymentName);
|
||||
|
||||
// Demonstrates how to add structured output support to agents that don't natively support it using the structured output middleware.
|
||||
// This approach is useful when working with agents that don't support structured output natively, or agents using models
|
||||
// that don't have the capability to produce structured output, allowing you to still leverage structured output features by transforming
|
||||
// the text output from the agent into structured data using a chat client.
|
||||
await UseStructuredOutputWithMiddlewareAsync(chatClient);
|
||||
await UseStructuredOutputWithMiddlewareAsync(aiProjectClient, deploymentName);
|
||||
|
||||
static async Task UseStructuredOutputWithResponseFormatAsync(ChatClient chatClient)
|
||||
static async Task UseStructuredOutputWithResponseFormatAsync(AIProjectClient aiProjectClient, string deploymentName)
|
||||
{
|
||||
Console.WriteLine("=== Structured Output with ResponseFormat ===");
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
Name = "HelpfulAssistant",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant.",
|
||||
// Specify CityInfo as the type parameter of ForJsonSchema to indicate the expected structured output from the agent.
|
||||
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<CityInfo>()
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<CityInfo>()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -81,12 +82,12 @@ static async Task UseStructuredOutputWithResponseFormatAsync(ChatClient chatClie
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
static async Task UseStructuredOutputWithRunAsync(ChatClient chatClient)
|
||||
static async Task UseStructuredOutputWithRunAsync(AIProjectClient aiProjectClient, string deploymentName)
|
||||
{
|
||||
Console.WriteLine("=== Structured Output with RunAsync<T> ===");
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = chatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName, name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
|
||||
|
||||
// Set CityInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke it with some unstructured input.
|
||||
AgentResponse<CityInfo> response = await agent.RunAsync<CityInfo>("Provide information about the capital of France.");
|
||||
@@ -99,19 +100,20 @@ static async Task UseStructuredOutputWithRunAsync(ChatClient chatClient)
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
static async Task UseStructuredOutputWithRunStreamingAsync(ChatClient chatClient)
|
||||
static async Task UseStructuredOutputWithRunStreamingAsync(AIProjectClient aiProjectClient, string deploymentName)
|
||||
{
|
||||
Console.WriteLine("=== Structured Output with RunStreamingAsync ===");
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
Name = "HelpfulAssistant",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deploymentName,
|
||||
Instructions = "You are a helpful assistant.",
|
||||
// Specify CityInfo as the type parameter of ForJsonSchema to indicate the expected structured output from the agent.
|
||||
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<CityInfo>()
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<CityInfo>()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,12 +131,12 @@ static async Task UseStructuredOutputWithRunStreamingAsync(ChatClient chatClient
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
static async Task UseStructuredOutputWithMiddlewareAsync(ChatClient chatClient)
|
||||
static async Task UseStructuredOutputWithMiddlewareAsync(AIProjectClient aiProjectClient, string deploymentName)
|
||||
{
|
||||
Console.WriteLine("=== Structured Output with UseStructuredOutput Middleware ===");
|
||||
|
||||
// Create chat client that will transform the agent text response into structured output.
|
||||
IChatClient meaiChatClient = chatClient.AsIChatClient();
|
||||
IChatClient meaiChatClient = aiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClientForModel(deploymentName).AsIChatClientWithStoredOutputDisabled(deploymentName);
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = meaiChatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,26 +2,27 @@
|
||||
|
||||
#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with a conversation that can be persisted to disk.
|
||||
// Persisted Conversations — Save and restore chat history to disk
|
||||
//
|
||||
// This sample shows how to persist an agent conversation to disk
|
||||
// so it can be resumed across process restarts.
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// Create 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 AzureOpenAIClient(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(model: deploymentName, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Start a new session for the agent conversation.
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,14 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+15
-11
@@ -2,23 +2,25 @@
|
||||
|
||||
#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with custom ChatHistoryProvider that stores chat history in a custom storage location.
|
||||
// The state of the custom ChatHistoryProvider (SessionDbKey) is stored in the AgentSession's StateBag, so that when the session is resumed later,
|
||||
// the chat history can be retrieved from the custom storage location.
|
||||
// Third-Party Chat History Storage — Custom ChatHistoryProvider
|
||||
//
|
||||
// This sample shows how to use a custom ChatHistoryProvider that stores
|
||||
// chat history in an external location. The provider's state (SessionDbKey)
|
||||
// is stored in AgentSession.StateBag so conversations can be resumed later.
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Extensions.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using OpenAI.Chat;
|
||||
using SampleApp;
|
||||
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// Create a vector store to store the chat messages in.
|
||||
// Replace this with a vector store implementation of your choice if you want to persist the chat history to disk.
|
||||
@@ -28,16 +30,18 @@ VectorStore vectorStore = new InMemoryVectorStore();
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.GetProjectOpenAIClient()
|
||||
.GetProjectResponsesClient()
|
||||
.AsIChatClientWithStoredOutputDisabled(deploymentName)
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
// Create a new ChatHistoryProvider for this agent that stores chat history in a vector store.
|
||||
ChatHistoryProvider = new VectorChatHistoryProvider(vectorStore)
|
||||
ChatHistoryProvider = new VectorChatHistoryProvider(vectorStore),
|
||||
});
|
||||
|
||||
// Start a new session for the agent conversation.
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,16 +9,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with Azure OpenAI as the backend that logs telemetry using OpenTelemetry.
|
||||
// Agent Observability — OpenTelemetry tracing with Azure AI Foundry
|
||||
//
|
||||
// This sample shows how to instrument an AI agent with OpenTelemetry
|
||||
// for distributed tracing and telemetry logging.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Azure.Monitor.OpenTelemetry.Exporter;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Chat;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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 applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
|
||||
|
||||
// Create TracerProvider with console exporter
|
||||
@@ -30,9 +32,8 @@ using var tracerProvider = tracerProviderBuilder.Build();
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker")
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: deploymentName, instructions: "You are good at telling jokes.", name: "Joker")
|
||||
.AsBuilder()
|
||||
.UseOpenTelemetry(sourceName: sourceName)
|
||||
.Build();
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,14 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,38 +2,30 @@
|
||||
|
||||
#pragma warning disable CA1812
|
||||
|
||||
// This sample shows how to use dependency injection to register an AIAgent and use it from a hosted service with a user input chat loop.
|
||||
// Dependency Injection — Register and resolve agents via DI
|
||||
//
|
||||
// This sample shows how to use dependency injection to register an
|
||||
// AIAgent and consume it from a hosted service with a chat loop.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// Create a host builder that we will register services with and then run.
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Add agent options to the service collection.
|
||||
builder.Services.AddSingleton(new ChatClientAgentOptions() { Name = "Joker", ChatOptions = new() { Instructions = "You are good at telling jokes." } });
|
||||
|
||||
// Add a chat client to the service collection.
|
||||
// Create the AI agent from the Azure AI Foundry project client.
|
||||
// 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.
|
||||
builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsIChatClient());
|
||||
|
||||
// Add the AI agent to the service collection.
|
||||
builder.Services.AddSingleton<AIAgent>((sp) => new ChatClientAgent(
|
||||
chatClient: sp.GetRequiredKeyedService<IChatClient>("AzureOpenAI"),
|
||||
options: sp.GetRequiredService<ChatClientAgentOptions>()));
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(model: deploymentName, name: "Joker", instructions: "You are good at telling jokes.");
|
||||
builder.Services.AddSingleton(agent);
|
||||
|
||||
// Add a sample service that will use the agent to respond to user input.
|
||||
builder.Services.AddHostedService<SampleService>();
|
||||
|
||||
+2
-6
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,12 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to use Image Multi-Modality with an AI agent.
|
||||
// Using Images — Multimodal input with an AI agent
|
||||
//
|
||||
// This sample shows how to send image content to an AI agent
|
||||
// for vision-based analysis.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var deploymentName = System.Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "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 agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
var agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(
|
||||
name: "VisionAgent",
|
||||
instructions: "You are a helpful agent that can analyze images");
|
||||
model: deploymentName,
|
||||
instructions: "You are a helpful agent that can analyze images",
|
||||
name: "VisionAgent");
|
||||
|
||||
ChatMessage message = new(ChatRole.User, [
|
||||
new TextContent("What do you see in this image?"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Using Images with AI Agents
|
||||
|
||||
This sample demonstrates how to use image multi-modality with an AI agent. It shows how to create a vision-enabled agent that can analyze and describe images using Azure OpenAI.
|
||||
This sample demonstrates how to use image multi-modality with an AI agent. It shows how to create a vision-enabled agent that can analyze and describe images using Microsoft Foundry with `AIProjectClient`.
|
||||
|
||||
## What this sample demonstrates
|
||||
|
||||
@@ -13,13 +13,13 @@ This sample demonstrates how to use image multi-modality with an AI agent. It sh
|
||||
|
||||
- **Vision Agent**: Creates an agent specifically instructed to analyze images
|
||||
- **Multimodal Input**: Combines text questions with image uri in a single message
|
||||
- **Azure OpenAI Integration**: Uses AzureOpenAI LLM agents
|
||||
- **Microsoft Foundry Integration**: Uses `AIProjectClient` to create a Foundry-backed agent
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running this sample, ensure you have:
|
||||
|
||||
1. An Azure OpenAI project set up
|
||||
1. A Microsoft Foundry project set up
|
||||
2. A compatible model deployment (e.g., gpt-5.4-mini)
|
||||
3. Azure CLI installed and authenticated
|
||||
|
||||
@@ -28,8 +28,8 @@ Before running this sample, ensure you have:
|
||||
Set the following environment variables:
|
||||
|
||||
```powershell
|
||||
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI endpoint
|
||||
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Replace with your model deployment name (optional, defaults to gpt-5.4-mini)
|
||||
$env:FOUNDRY_PROJECT_ENDPOINT="https://<your-project>.services.ai.azure.com/api/projects/<your-project>" # Replace with your Foundry project endpoint
|
||||
$env:FOUNDRY_MODEL="gpt-5.4-mini" # Replace with your model name (optional, defaults to gpt-5.4-mini)
|
||||
```
|
||||
|
||||
## Run the sample
|
||||
@@ -49,4 +49,3 @@ The sample will:
|
||||
2. Send a message containing both text ("What do you see in this image?") and a Uri image of a green walk
|
||||
3. The agent will analyze the image and provide a description
|
||||
4. Clean up resources by deleting the thread and agent
|
||||
|
||||
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -10,13 +10,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a Azure OpenAI AI agent as a function tool.
|
||||
// Agent as Function Tool — Use one agent as a tool for another
|
||||
//
|
||||
// This sample shows how to create an AI agent and expose it as a
|
||||
// function tool that another agent can call.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
[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 chat client and agent, and provide the function tool to the agent.
|
||||
// Create the agent and provide the function tool to it.
|
||||
// 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 weatherAgent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
AIAgent weatherAgent = aiProjectClient
|
||||
.AsAIAgent(
|
||||
model: deploymentName,
|
||||
instructions: "You answer questions about the weather.",
|
||||
name: "WeatherAgent",
|
||||
description: "An agent that answers questions about the weather.",
|
||||
tools: [AIFunctionFactory.Create(GetWeather)]);
|
||||
|
||||
// Create the main agent, and provide the weather agent as a function tool.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You are a helpful assistant who responds in French.", tools: [weatherAgent.AsAIFunction()]);
|
||||
AIAgent agent = aiProjectClient
|
||||
.AsAIAgent(
|
||||
model: deploymentName,
|
||||
instructions: "You are a helpful assistant who responds in French.",
|
||||
tools: [weatherAgent.AsAIFunction()]);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
|
||||
|
||||
+2
-6
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,12 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+11
-10
@@ -1,31 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use background responses with ChatClientAgent and Azure OpenAI Responses for long-running operations.
|
||||
// It shows polling for completion using continuation tokens, function calling during background operations,
|
||||
// and persisting/restoring agent state between polling cycles.
|
||||
// Background Responses with Tools — Long-running operations with persistence
|
||||
//
|
||||
// This sample demonstrates how to use background responses with ChatClientAgent
|
||||
// for long-running operations. It shows polling for completion using continuation
|
||||
// tokens, function calling during background operations, and persisting/restoring
|
||||
// agent state between polling cycles.
|
||||
|
||||
#pragma warning disable CA1050 // Declare types in namespaces
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
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 stateStore = new Dictionary<string, JsonElement?>();
|
||||
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetResponsesClient()
|
||||
.AsAIAgent(
|
||||
model: deploymentName,
|
||||
name: "SpaceNovelWriter",
|
||||
@@ -39,7 +40,7 @@ AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
|
||||
// Start the initial run.
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about a team of astronauts exploring an uncharted galaxy.", session, options);
|
||||
AgentResponse response = await agent.RunAsync("Write a short story about a team of astronauts exploring an uncharted galaxy.", session, options);
|
||||
|
||||
// Poll for background responses until complete.
|
||||
while (response.ContinuationToken is not null)
|
||||
|
||||
+2
-3
@@ -9,13 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows multiple middleware layers working together with Azure OpenAI:
|
||||
// Middleware — Chain multiple middleware layers on an agent
|
||||
//
|
||||
// This sample shows multiple middleware layers working together with Azure AI Foundry:
|
||||
// chat client (global/per-request), agent run (PII filtering and guardrails),
|
||||
// function invocation (logging and result overrides), human-in-the-loop
|
||||
// approval workflows for sensitive function calls, and MessageAIContextProvider
|
||||
@@ -8,21 +10,20 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
// Get Microsoft Foundry configuration from environment variables
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
// Get Azure AI Foundry configuration from environment variables
|
||||
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";
|
||||
|
||||
// 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 azureOpenAIClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
[Description("Get the weather for a given location.")]
|
||||
static string GetWeather([Description("The location to get the weather for.")] string location)
|
||||
@@ -33,12 +34,14 @@ static string GetDateTime()
|
||||
=> DateTimeOffset.Now.ToString();
|
||||
|
||||
// Adding middleware to the chat client level and building an agent on top of it
|
||||
var originalAgent = azureOpenAIClient.AsIChatClient()
|
||||
.AsBuilder()
|
||||
.Use(getResponseFunc: ChatClientMiddleware, getStreamingResponseFunc: null)
|
||||
.BuildAIAgent(
|
||||
instructions: "You are an AI assistant that helps people find information.",
|
||||
tools: [AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime))]);
|
||||
var originalAgent = aiProjectClient.AsAIAgent(
|
||||
model: deploymentName,
|
||||
instructions: "You are an AI assistant that helps people find information.",
|
||||
tools: [AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime))],
|
||||
clientFactory: (chatClient) => chatClient
|
||||
.AsBuilder()
|
||||
.Use(getResponseFunc: ChatClientMiddleware, getStreamingResponseFunc: null)
|
||||
.Build());
|
||||
|
||||
// Adding middleware to the agent level
|
||||
var middlewareEnabledAgent = originalAgent
|
||||
@@ -117,11 +120,13 @@ Console.WriteLine($"Context-enriched response: {contextResponse}");
|
||||
// In this case we are attaching an AIContextProvider that only adds messages.
|
||||
Console.WriteLine("\n\n=== Example 6: AIContextProvider on chat client pipeline ===");
|
||||
|
||||
var chatClientProviderAgent = azureOpenAIClient.AsIChatClient()
|
||||
.AsBuilder()
|
||||
.UseAIContextProviders(new DateTimeContextProvider())
|
||||
.BuildAIAgent(
|
||||
instructions: "You are an AI assistant that helps people find information.");
|
||||
var chatClientProviderAgent = aiProjectClient.AsAIAgent(
|
||||
model: deploymentName,
|
||||
instructions: "You are an AI assistant that helps people find information.",
|
||||
clientFactory: (chatClient) => chatClient
|
||||
.AsBuilder()
|
||||
.UseAIContextProviders(new DateTimeContextProvider())
|
||||
.Build());
|
||||
|
||||
var chatClientContextResponse = await chatClientProviderAgent.RunAsync("Is it almost time for lunch?");
|
||||
Console.WriteLine($"Chat client context-enriched response: {chatClientContextResponse}");
|
||||
|
||||
@@ -7,7 +7,7 @@ This sample demonstrates how to add middleware to intercept:
|
||||
|
||||
## What This Sample Shows
|
||||
|
||||
1. Azure OpenAI integration via `AzureOpenAIClient` and `DefaultAzureCredential`
|
||||
1. Microsoft Foundry integration via `AIProjectClient` and `DefaultAzureCredential`
|
||||
2. Chat client middleware using `ChatClientBuilder.Use(...)`
|
||||
3. Agent run middleware (PII redaction and wording guardrails)
|
||||
4. Function invocation middleware (logging and overriding a tool result)
|
||||
@@ -26,8 +26,8 @@ Attempting to use function middleware on agents that do not wrap a ChatClientAge
|
||||
## Prerequisites
|
||||
|
||||
1. Environment variables:
|
||||
- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint
|
||||
- `AZURE_OPENAI_DEPLOYMENT_NAME`: Chat deployment name (optional; defaults to `gpt-5.4-mini`)
|
||||
- `FOUNDRY_PROJECT_ENDPOINT`: Your Foundry project endpoint
|
||||
- `FOUNDRY_MODEL`: Model name (optional; defaults to `gpt-5.4-mini`)
|
||||
2. Sign in with Azure CLI (PowerShell):
|
||||
```powershell
|
||||
az login
|
||||
@@ -40,4 +40,3 @@ Use PowerShell:
|
||||
cd dotnet/samples/02-agents/Agents/Agent_Step11_Middleware
|
||||
dotnet run
|
||||
```
|
||||
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// Plugins — Use plugin classes with dependency injection
|
||||
//
|
||||
// This sample shows how to use plugins with an AI agent. Plugin classes can
|
||||
// depend on other services that need to be injected. In this sample, the
|
||||
// AgentPlugin class uses the WeatherProvider and CurrentTimeProvider classes
|
||||
@@ -9,15 +11,14 @@
|
||||
// as AI functions. The AsAITools method of the plugin class shows how to specify
|
||||
// which methods should be exposed to the AI agent.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// Create a service collection to hold the agent plugin and its dependencies.
|
||||
ServiceCollection services = new();
|
||||
@@ -30,11 +31,11 @@ IServiceProvider serviceProvider = services.BuildServiceProvider();
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(
|
||||
model: deploymentName,
|
||||
instructions: "You are a helpful assistant that helps people find information.",
|
||||
name: "Assistant",
|
||||
tools: [.. serviceProvider.GetRequiredService<AgentPlugin>().AsAITools()],
|
||||
|
||||
+2
-7
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -9,13 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to use a chat history reducer to keep the context within model size limits.
|
||||
// Any implementation of Microsoft.Extensions.AI.IChatReducer can be used to customize how the chat history is reduced.
|
||||
// NOTE: this feature is only supported where the chat history is stored locally, such as with OpenAI Chat Completion.
|
||||
// Where the chat history is stored server side, such as with Microsoft Foundry Agents, the service must manage the chat history size.
|
||||
// Chat Reduction — Keep conversation context within model limits
|
||||
//
|
||||
// This sample shows how to use a chat history reducer to keep the context
|
||||
// within model size limits. Any IChatReducer implementation can customize
|
||||
// how the chat history is reduced.
|
||||
// NOTE: This feature is only supported where chat history is stored locally
|
||||
// (e.g. OpenAI Chat Completion). For server-side history (e.g. Foundry Agents),
|
||||
// the service manages chat history size.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Extensions.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// Construct the agent, and provide a factory to create an in-memory chat message store with a reducer that keeps only the last 2 non-system messages.
|
||||
// You must dissable client side conversation storage for clients that support it.
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.GetProjectOpenAIClient()
|
||||
.GetProjectResponsesClient()
|
||||
.AsIChatClientWithStoredOutputDisabled(deploymentName)
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
ChatOptions = new() { ModelId = deploymentName, Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
ChatHistoryProvider = new InMemoryChatHistoryProvider(new() { ChatReducer = new MessageCountingChatReducer(2) })
|
||||
});
|
||||
|
||||
+1
-5
@@ -9,12 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to use background responses with ChatClientAgent and Azure OpenAI Responses.
|
||||
// Background Responses — Asynchronous agent execution with polling
|
||||
//
|
||||
// This sample shows how to use background responses with ChatClientAgent
|
||||
// and Azure AI Foundry for non-blocking agent execution.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var 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";
|
||||
|
||||
// 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(
|
||||
AIAgent agent = new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetResponsesClient()
|
||||
.AsAIAgent(model: deploymentName);
|
||||
.AsAIAgent(model: deploymentName, instructions: "You are a helpful assistant.");
|
||||
|
||||
// Enable background responses (only supported by OpenAI Responses at this time).
|
||||
AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
@@ -53,8 +54,13 @@ await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a ve
|
||||
// Output each update.
|
||||
Console.Write(update.Text);
|
||||
|
||||
// Track last update.
|
||||
lastReceivedUpdate = update;
|
||||
// Track the last update that carries a resumable continuation token.
|
||||
// Lifecycle events like response.completed return null tokens (response is finished),
|
||||
// so we only update our reference when a token is actually present.
|
||||
if (update.ContinuationToken is not null)
|
||||
{
|
||||
lastReceivedUpdate = update;
|
||||
}
|
||||
|
||||
// Simulate connection loss after first piece of content received.
|
||||
if (update.Text.Length > 0)
|
||||
|
||||
+1
-3
@@ -9,9 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Agents.ObjectModel" />
|
||||
<PackageReference Include="Microsoft.Agents.ObjectModel.Json" />
|
||||
<PackageReference Include="Microsoft.Agents.ObjectModel.PowerFx" />
|
||||
@@ -19,7 +17,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Declarative\Microsoft.Agents.AI.Declarative.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user