chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Skills Auto-Approval — Configure Auto-Approval Rules for Skill Tools
|
||||
|
||||
This sample demonstrates how to configure **auto-approval rules** for skill
|
||||
tools using `ToolApprovalMiddleware`. Every tool exposed by `SkillsProvider`
|
||||
(`load_skill`, `read_skill_resource`, and `run_skill_script`) requires host
|
||||
approval by default. Auto-approval rules let you selectively bypass the approval
|
||||
prompt for safe operations.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. A code-defined unit-converter skill (with a resource and a script) is registered via `SkillsProvider`.
|
||||
2. The agent installs `ToolApprovalMiddleware` with `SkillsProvider.read_only_tools_auto_approval_rule`.
|
||||
3. The read-only tools (`load_skill`, `read_skill_resource`) are approved automatically.
|
||||
4. `run_skill_script` still requires explicit approval and is handled with the standard `result.user_input_requests` loop.
|
||||
|
||||
## Auto-Approval Rules
|
||||
|
||||
`SkillsProvider` exposes two static rules to pass to `ToolApprovalMiddleware(auto_approval_rules=[...])`:
|
||||
|
||||
- **`SkillsProvider.read_only_tools_auto_approval_rule`** — approves only the read-only tools (`load_skill`, `read_skill_resource`), while still prompting for `run_skill_script`.
|
||||
- **`SkillsProvider.all_tools_auto_approval_rule`** — approves every skill tool, including `run_skill_script` (no manual approval loop needed).
|
||||
|
||||
Both rules reject any call carrying a `server_label`, so they stay scoped to this provider's local tools and never auto-approve a same-named hosted tool.
|
||||
|
||||
> **Note:** To use auto-approval rules, the agent must have `ToolApprovalMiddleware` in its middleware stack.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **`ToolApprovalMiddleware(auto_approval_rules=[...])`** — Drives the approval handshake and applies the rules
|
||||
- **`SkillsProvider.read_only_tools_auto_approval_rule`** — Auto-approves read-only skill tools
|
||||
- **`SkillsProvider.all_tools_auto_approval_rule`** — Auto-approves all skill tools
|
||||
- **`SkillsProvider.LOAD_SKILL_TOOL_NAME` / `READ_SKILL_RESOURCE_TOOL_NAME` / `RUN_SKILL_SCRIPT_TOOL_NAME`** — Tool-name constants for building custom rules
|
||||
|
||||
## Running the Sample
|
||||
|
||||
### Prerequisites
|
||||
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Set the required environment variables in a `.env` file (see `python/.env.example`):
|
||||
|
||||
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
|
||||
- `FOUNDRY_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
|
||||
|
||||
### Authentication
|
||||
|
||||
This sample uses `AzureCliCredential` for authentication. Run `az login` in your terminal before running the sample.
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
cd python
|
||||
uv run samples/02-agents/skills/skills_auto_approval/skills_auto_approval.py
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Skill Tool Approval Sample](../script_approval/) — manual human-in-the-loop approval
|
||||
- [Code-Defined Skills Sample](../code_defined_skill/)
|
||||
- [File-Based Skills Sample](../file_based_skill/)
|
||||
- [Agent Skills Specification](https://agentskills.io/)
|
||||
@@ -0,0 +1,191 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from textwrap import dedent
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
Content,
|
||||
InlineSkill,
|
||||
InlineSkillResource,
|
||||
Message,
|
||||
SkillFrontmatter,
|
||||
SkillsProvider,
|
||||
ToolApprovalMiddleware,
|
||||
)
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from dotenv import load_dotenv
|
||||
|
||||
"""
|
||||
Skills Auto-Approval — Configure auto-approval rules for skill tools
|
||||
|
||||
Every tool exposed by :class:`SkillsProvider` (``load_skill``,
|
||||
``read_skill_resource``, and ``run_skill_script``) requires host approval by
|
||||
default. Rather than prompting for every call, this sample uses
|
||||
:class:`ToolApprovalMiddleware` with a static auto-approval rule so that the
|
||||
read-only tools are approved automatically while script execution still
|
||||
requires explicit user approval.
|
||||
|
||||
How it works:
|
||||
1. A code-defined unit-converter skill (with a resource and a script) is
|
||||
registered via SkillsProvider.
|
||||
2. The agent installs ``ToolApprovalMiddleware`` with
|
||||
``SkillsProvider.read_only_tools_auto_approval_rule``. This auto-approves
|
||||
``load_skill`` and ``read_skill_resource`` while still prompting for
|
||||
``run_skill_script``.
|
||||
3. The application handles the remaining ``run_skill_script`` approval requests
|
||||
via the standard ``result.user_input_requests`` loop.
|
||||
|
||||
Available auto-approval rules:
|
||||
- ``SkillsProvider.read_only_tools_auto_approval_rule`` — approves only the
|
||||
read-only tools (``load_skill``, ``read_skill_resource``).
|
||||
- ``SkillsProvider.all_tools_auto_approval_rule`` — approves every skill tool,
|
||||
including ``run_skill_script`` (no manual approval loop needed).
|
||||
|
||||
To use auto-approval rules, the agent must have ``ToolApprovalMiddleware`` in
|
||||
its middleware stack.
|
||||
|
||||
Prerequisites:
|
||||
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- FOUNDRY_MODEL (defaults to "gpt-4o-mini").
|
||||
"""
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# A code-defined unit-converter skill with a resource (read-only) and a script.
|
||||
unit_converter_skill = InlineSkill(
|
||||
frontmatter=SkillFrontmatter(
|
||||
name="unit-converter", description="Convert between common units using a conversion factor"
|
||||
),
|
||||
instructions=dedent("""\
|
||||
Use this skill when the user asks to convert between units.
|
||||
|
||||
1. Review the conversion-tables resource to find the factor for the
|
||||
requested conversion.
|
||||
2. Use the convert script, passing the value and factor from the table.
|
||||
"""),
|
||||
resources=[
|
||||
InlineSkillResource(
|
||||
name="conversion-tables",
|
||||
content=dedent("""\
|
||||
# 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 |
|
||||
"""),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@unit_converter_skill.script(name="convert", description="Convert a value: result = value × factor")
|
||||
def convert_units(value: float, factor: float, **kwargs: Any) -> str:
|
||||
"""Convert a value using a multiplication factor: result = value × factor.
|
||||
|
||||
Args:
|
||||
value: The numeric value to convert.
|
||||
factor: Conversion factor from the conversion table.
|
||||
**kwargs: Runtime keyword arguments from ``agent.run()``.
|
||||
|
||||
Returns:
|
||||
JSON string with the inputs and converted result.
|
||||
"""
|
||||
result = round(value * factor, 2)
|
||||
return json.dumps({"value": value, "factor": factor, "result": result})
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run the skills auto-approval demo."""
|
||||
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
|
||||
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
|
||||
|
||||
client = FoundryChatClient(
|
||||
project_endpoint=endpoint,
|
||||
model=deployment,
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
skills_provider = SkillsProvider(unit_converter_skill)
|
||||
|
||||
# Install ToolApprovalMiddleware with the read-only auto-approval rule.
|
||||
# load_skill and read_skill_resource are approved automatically; the agent
|
||||
# only pauses for run_skill_script.
|
||||
#
|
||||
# To approve every skill tool without prompting, swap the rule for
|
||||
# SkillsProvider.all_tools_auto_approval_rule (the manual approval loop
|
||||
# below then becomes a no-op).
|
||||
approval_middleware = ToolApprovalMiddleware(
|
||||
auto_approval_rules=[SkillsProvider.read_only_tools_auto_approval_rule]
|
||||
)
|
||||
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions="You are a helpful assistant that can convert units.",
|
||||
context_providers=[skills_provider],
|
||||
middleware=[approval_middleware],
|
||||
) as agent:
|
||||
session = agent.create_session()
|
||||
|
||||
print("Converting units with skill tools and read-only auto-approval")
|
||||
print("-" * 60)
|
||||
|
||||
query = "How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query, session=session)
|
||||
|
||||
# Read-only tools (load_skill, read_skill_resource) were auto-approved.
|
||||
# Only run_skill_script reaches this loop and needs explicit approval.
|
||||
# Collect a response for every request and send them in one run so the
|
||||
# loop always makes progress.
|
||||
while result.user_input_requests:
|
||||
approval_responses: list[Content] = []
|
||||
for request in result.user_input_requests:
|
||||
if request.function_call is None:
|
||||
# Not a function-approval request; reject it so the run can proceed.
|
||||
approval_responses.append(request.to_function_approval_response(approved=False))
|
||||
continue
|
||||
print("\nApproval needed:")
|
||||
print(f" Function: {request.function_call.name}")
|
||||
print(f" Arguments: {request.function_call.arguments}")
|
||||
|
||||
# In a real application, prompt the user here.
|
||||
approved = True
|
||||
print(f" Decision: {'Approved' if approved else 'Rejected'}")
|
||||
approval_responses.append(request.to_function_approval_response(approved=approved))
|
||||
|
||||
result = await agent.run(Message(role="user", contents=approval_responses), session=session)
|
||||
|
||||
print(f"\nAgent: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
Converting units with skill tools and read-only auto-approval
|
||||
------------------------------------------------------------
|
||||
User: How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?
|
||||
|
||||
Approval needed:
|
||||
Function: run_skill_script
|
||||
Arguments: {"skill_name": "unit-converter", "script_name": "convert", ...}
|
||||
Decision: Approved
|
||||
|
||||
Agent: Here are your conversions:
|
||||
|
||||
1. 26.2 miles -> 42.16 km (a marathon distance)
|
||||
2. 75 kg -> 165.35 lbs
|
||||
"""
|
||||
Reference in New Issue
Block a user