mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Add Hosted-AgentSkills sample with Foundry Skills integration (#6013)
* .NET: Add Hosted-AgentSkills sample for Foundry Skills integration Add a new hosted agent sample that demonstrates how to load behavioral guidelines from Foundry Skills at startup using AgentSkillsProvider and the progressive disclosure pattern (advertise -> load on demand). The sample: - Downloads SKILL.md files from Foundry via ProjectAgentSkills SDK - Extracts ZIP archives with zip-slip protection - Wires skills into AgentSkillsProvider as an AIContextProvider - Hosts the agent via the Responses protocol Ships two Contoso Outdoors skills matching the Python sample (PR #5822): - support-style: tone, formatting, signature guidelines - escalation-policy: when and how to escalate tickets Includes convenience provisioning gated behind PROVISION_SAMPLE_SKILLS env var, clearly documented as NOT a production pattern. Closes #5776 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Add unit tests and integration test for Hosted-AgentSkills Unit tests (14 tests, all passing): - ZIP extraction with zip-slip guard (valid archive, traversal attack, sibling-prefix attack, directory entries) - Skill name validation (rejects dots, separators, traversal patterns) - AgentSkillsProvider with downloaded skills (advertises both skills, load_skill returns canary tokens, unknown skill returns error) Container integration test: - New 'agent-skills' scenario in the test container that creates Contoso Outdoors skills on disk and wires AgentSkillsProvider - AgentSkillsHostedAgentFixture + 4 integration tests verifying: - Routine questions load support-style skill (STYLE-CANARY-3318) - Escalation triggers load escalation-policy (ESC-CANARY-7742) - Skills are advertised in system prompt - load_skill tool is invoked via FunctionCallContent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Add smoke test, bootstrap, and docs for agent-skills integration - Add scripts/smoke.ps1 for local Docker smoke testing: builds the contributor image, runs the container, verifies both skills are loaded via canary tokens (STYLE-CANARY-3318, ESC-CANARY-7742) - Add 'agent-skills' to the bootstrap script scenario list - Add agent-skills row to the integration test README scenarios table - Exclude HostedAgentSkillsPatternTests from net472 (uses net8.0+ APIs) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Update commented-out package versions to latest across all hosted samples Update the end-user PackageReference versions (in the commented-out sections) from 1.0.0 to the current latest NuGet versions: - Microsoft.Agents.AI: 1.6.1 - Microsoft.Agents.AI.Foundry: 1.6.1-preview.260514.1 - Microsoft.Agents.AI.Foundry.Hosting: 1.6.1-preview.260514.1 - Microsoft.Agents.AI.Hosting: 1.6.1-preview.260514.1 - Microsoft.Agents.AI.OpenAI: 1.6.1 - Microsoft.Agents.AI.Workflows: 1.6.1 Also adds explicit versions to Hosted-Workflow-Handoff which had bare PackageReference entries without Version attributes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Fix broken markdown links in Hosted-AgentSkills README Remove references to non-existent ../../README.md. Replace with inline instructions matching other hosted samples that don't have a parent README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Use OS-appropriate string comparison in zip-slip guard Use Ordinal on Unix (case-sensitive FS) and OrdinalIgnoreCase on Windows to prevent case-based path bypass on Linux containers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -38,6 +38,7 @@ AIAgent agent = scenario switch
|
||||
"memory" => await CreateMemoryAgentAsync(projectClient, deployment).ConfigureAwait(false),
|
||||
"azure-search-rag" => CreateAzureSearchRagAgent(projectClient, deployment),
|
||||
"session-files" => CreateSessionFilesAgent(projectClient, deployment),
|
||||
"agent-skills" => CreateAgentSkillsAgent(projectClient, deployment),
|
||||
_ => throw new InvalidOperationException($"Unknown IT_SCENARIO '{scenario}'.")
|
||||
};
|
||||
|
||||
@@ -209,6 +210,77 @@ static async Task<AIAgent> CreateMemoryAgentAsync(AIProjectClient client, string
|
||||
});
|
||||
}
|
||||
|
||||
// Agent skills scenario. Uses AgentSkillsProvider with two bundled Contoso Outdoors skills
|
||||
// (support-style + escalation-policy). Skills are loaded from embedded SKILL.md files on disk,
|
||||
// simulating the download-from-Foundry pattern used by the Hosted-AgentSkills sample. When the
|
||||
// container starts, it writes the skills to a temp directory and wires AgentSkillsProvider over it.
|
||||
#pragma warning disable MEAI001 // AgentSkillsProvider is experimental
|
||||
static AIAgent CreateAgentSkillsAgent(AIProjectClient client, string deployment)
|
||||
{
|
||||
string skillsDir = Path.Combine(Path.GetTempPath(), "it-agent-skills-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(Path.Combine(skillsDir, "support-style"));
|
||||
Directory.CreateDirectory(Path.Combine(skillsDir, "escalation-policy"));
|
||||
|
||||
File.WriteAllText(Path.Combine(skillsDir, "support-style", "SKILL.md"),
|
||||
"""
|
||||
---
|
||||
name: support-style
|
||||
description: Contoso Outdoors customer-support tone and formatting guidelines.
|
||||
---
|
||||
|
||||
# Contoso Outdoors Support Style
|
||||
|
||||
You are speaking on behalf of Contoso Outdoors customer support.
|
||||
|
||||
## Voice
|
||||
|
||||
- Warm, concise, and confident.
|
||||
- Use the customer's name when known.
|
||||
- Sign every response with `— Contoso Outdoors Support`.
|
||||
|
||||
## Canary
|
||||
|
||||
To prove this skill was loaded, include the token `STYLE-CANARY-3318` on a
|
||||
separate line at the bottom of every response, prefixed with `# `.
|
||||
""");
|
||||
|
||||
File.WriteAllText(Path.Combine(skillsDir, "escalation-policy", "SKILL.md"),
|
||||
"""
|
||||
---
|
||||
name: escalation-policy
|
||||
description: When and how to escalate Contoso Outdoors customer-support tickets.
|
||||
---
|
||||
|
||||
# Contoso Outdoors Escalation Policy
|
||||
|
||||
## Escalate immediately when the customer
|
||||
|
||||
- Reports an injury or safety incident.
|
||||
- Mentions legal action, regulators, or the press.
|
||||
- Requests a refund larger than $500.
|
||||
|
||||
## How to escalate
|
||||
|
||||
1. Acknowledge the issue.
|
||||
2. Tell the customer you are escalating to a senior specialist.
|
||||
3. Provide the escalation reference `ESC-CANARY-7742`.
|
||||
""");
|
||||
|
||||
var skillsProvider = new AgentSkillsProvider(skillsDir, scriptRunner: null);
|
||||
|
||||
return client.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "agent-skills-agent",
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
ModelId = deployment,
|
||||
Instructions = "You are a customer-support assistant for Contoso Outdoors.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider]
|
||||
});
|
||||
}
|
||||
#pragma warning restore MEAI001
|
||||
|
||||
[Description("Returns the current UTC date and time as an ISO 8601 string.")]
|
||||
static string GetUtcNow() => DateTime.UtcNow.ToString("o");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user