mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
dc4bafbc1e
* .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>
85 lines
3.7 KiB
C#
85 lines
3.7 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Foundry.Hosting.IntegrationTests.Fixtures;
|
|
using Microsoft.Extensions.AI;
|
|
|
|
namespace Foundry.Hosting.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// Integration tests that exercise the Agent Skills pattern in a hosted agent container.
|
|
/// The container uses <see cref="Microsoft.Agents.AI.AgentSkillsProvider"/> with two
|
|
/// Contoso Outdoors skills (support-style, escalation-policy) to verify the progressive
|
|
/// disclosure flow: skills are advertised in the system prompt and loaded on demand via
|
|
/// the <c>load_skill</c> tool when the model decides they are relevant.
|
|
/// </summary>
|
|
[Trait("Category", "FoundryHostedAgents")]
|
|
public sealed class AgentSkillsHostedAgentTests(AgentSkillsHostedAgentFixture fixture) : IClassFixture<AgentSkillsHostedAgentFixture>
|
|
{
|
|
private readonly AgentSkillsHostedAgentFixture _fixture = fixture;
|
|
|
|
[Fact(Skip = "Pending TestContainer build and end to end smoke (step 5).")]
|
|
public async Task RoutineQuestion_LoadsSupportStyleSkillAsync()
|
|
{
|
|
// Arrange
|
|
var agent = this._fixture.Agent;
|
|
|
|
// Act — ask a routine support question that should trigger the support-style skill
|
|
var response = await agent.RunAsync(
|
|
"Hi, I am Alex. I just want to confirm I can return my tent within 30 days.");
|
|
|
|
// Assert — response should contain the canary token proving the skill was loaded
|
|
Assert.False(string.IsNullOrWhiteSpace(response.Text));
|
|
Assert.Contains("STYLE-CANARY-3318", response.Text);
|
|
}
|
|
|
|
[Fact(Skip = "Pending TestContainer build and end to end smoke (step 5).")]
|
|
public async Task EscalationTrigger_LoadsEscalationPolicySkillAsync()
|
|
{
|
|
// Arrange
|
|
var agent = this._fixture.Agent;
|
|
|
|
// Act — trigger an escalation (legal threat + refund > $500)
|
|
var response = await agent.RunAsync(
|
|
"I want a $750 refund on Order #A-1042 right now or I am calling my lawyer.");
|
|
|
|
// Assert — response should contain the escalation canary token
|
|
Assert.False(string.IsNullOrWhiteSpace(response.Text));
|
|
Assert.Contains("ESC-CANARY-7742", response.Text);
|
|
}
|
|
|
|
[Fact(Skip = "Pending TestContainer build and end to end smoke (step 5).")]
|
|
public async Task SkillsAreAdvertised_LoadSkillToolIsAvailableAsync()
|
|
{
|
|
// Arrange
|
|
var agent = this._fixture.Agent;
|
|
|
|
// Act — ask the model what skills are available (triggers system prompt inspection)
|
|
var response = await agent.RunAsync(
|
|
"List the skills you have access to. Just give me their names.");
|
|
|
|
// Assert — both skills should be mentioned (they are advertised in the system prompt)
|
|
Assert.False(string.IsNullOrWhiteSpace(response.Text));
|
|
Assert.Contains("support-style", response.Text);
|
|
Assert.Contains("escalation-policy", response.Text);
|
|
}
|
|
|
|
[Fact(Skip = "Pending TestContainer build and end to end smoke (step 5).")]
|
|
public async Task LoadSkill_InvokesToolAndReturnsContentAsync()
|
|
{
|
|
// Arrange
|
|
var agent = this._fixture.Agent;
|
|
|
|
// Act — ask a question that should load a specific skill
|
|
var response = await agent.RunAsync(
|
|
"I need to know the escalation policy for customer tickets. Load the escalation-policy skill and tell me the rules.");
|
|
|
|
// Assert — the response should reference the load_skill tool invocation
|
|
Assert.False(string.IsNullOrWhiteSpace(response.Text));
|
|
Assert.True(
|
|
response.Messages.Any(m => m.Contents.OfType<FunctionCallContent>().Any(fc => fc.Name == "load_skill")),
|
|
"Expected at least one load_skill FunctionCallContent in the response messages.");
|
|
}
|
|
}
|