mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Add Foundry Skills sample
This commit is contained in:
@@ -16,7 +16,8 @@ This directory contains samples that demonstrate how to use hosted [Agent Framew
|
||||
| 6 | [Files](responses/06_files/) | An agent demonstrating how to work with files in a hosted agent session, including uploading files to a hosted agent session and having the agent read and manipulate those files at runtime. |
|
||||
| 7 | [Observability](responses/07_observability/) | A sample demonstrating how to enable observability for the agent deployed to Foundry. |
|
||||
| 8 | [Azure AI Search RAG](responses/08_azure_search_rag/) | An agent with Retrieval Augmented Generation (RAG) capabilities backed by Azure AI Search, grounding answers in documents indexed in a pre-provisioned search index. |
|
||||
| 9 | [Using deployed agent](responses/using_deployed_agent.py) | A sample demonstrating how to invoke an agent that has already been deployed to Foundry, showing how to interact with a hosted agent in code. |
|
||||
| 9 | [Foundry Skills](responses/09_foundry_skills/) | An agent that uploads `SKILL.md` files to the Foundry Skills REST API and downloads them at startup, decoupling tone/policy guidelines from agent code. |
|
||||
| 10 | [Using deployed agent](responses/using_deployed_agent.py) | A sample demonstrating how to invoke an agent that has already been deployed to Foundry, showing how to interact with a hosted agent in code. |
|
||||
|
||||
### Invocations API
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
.venv
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
.env
|
||||
provision_skills.py
|
||||
skills
|
||||
downloaded_skills
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
FOUNDRY_PROJECT_ENDPOINT="..."
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
|
||||
# Comma-separated list of Foundry skill names to download at startup.
|
||||
SKILL_NAMES="support-style,escalation-policy"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . user_agent/
|
||||
WORKDIR /app/user_agent
|
||||
|
||||
RUN if [ -f requirements.txt ]; then \
|
||||
pip install -r requirements.txt; \
|
||||
else \
|
||||
echo "No requirements.txt found"; \
|
||||
fi
|
||||
|
||||
EXPOSE 8088
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# What this sample demonstrates
|
||||
|
||||
An [Agent Framework](https://github.com/microsoft/agent-framework) agent that loads its behavioral guidelines from [**Foundry Skills**](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/skills?view=foundry&pivots=python) at startup, hosted using the **Responses protocol**. Skills are authored once as `SKILL.md` files, uploaded to your Foundry project through `AIProjectClient.beta.skills`, and downloaded by the agent on boot so updates ship without code changes.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Authoring skills
|
||||
|
||||
Each skill is a Markdown file with a YAML front matter block. This sample ships two source skills under [`skills/`](skills/):
|
||||
|
||||
| Skill | Purpose |
|
||||
|---|---|
|
||||
| [`support-style`](skills/support-style/SKILL.md) | Voice, formatting, and signature rules for Contoso Outdoors support replies. |
|
||||
| [`escalation-policy`](skills/escalation-policy/SKILL.md) | When and how to escalate a customer ticket. |
|
||||
|
||||
Each `SKILL.md` includes a unique `*-CANARY-*` token that the model is asked to echo, so you can prove the skill was loaded from Foundry (not hallucinated) by checking the response.
|
||||
|
||||
> The `name` and `description` values in the YAML front matter must be **unquoted** — quoting them causes the Skills REST API to return HTTP 500 on import.
|
||||
|
||||
### Uploading skills with `AIProjectClient`
|
||||
|
||||
[`provision_skills.py`](provision_skills.py) walks `skills/*/SKILL.md`, packages each file as an in-memory ZIP (with `SKILL.md` at the archive root), and imports it through [`AIProjectClient.beta.skills.create_from_package`](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/skills?view=foundry&pivots=python#option-2-import-from-a-skillmd-zip). The client is constructed with `allow_preview=True` (Skills is a preview feature) and authenticates with `DefaultAzureCredential`. Existing skills are deleted first via `beta.skills.delete` so the script is safe to re-run after editing a `SKILL.md`, and `beta.skills.list` is called at the end to verify each skill round-trips.
|
||||
|
||||
### Downloading skills at agent startup
|
||||
|
||||
[`main.py`](main.py) reads the comma-separated `SKILL_NAMES` env var, opens an `AIProjectClient` (also with `allow_preview=True`), and for each skill name streams the ZIP archive from `beta.skills.download(name)` and unpacks it into a **separate runtime directory** at `downloaded_skills/<name>/` (kept distinct from the static `skills/` source folder so the two never get confused — `skills/` is the input to `provision_skills.py`, `downloaded_skills/` is the output of `main.py`'s bootstrap step).
|
||||
|
||||
A [`SkillsProvider`](../../../../../packages/core/agent_framework/_skills.py) is then built over `downloaded_skills/` and attached to the `Agent` as a context provider. The provider follows the [Agent Skills](https://agentskills.io/) progressive-disclosure pattern:
|
||||
|
||||
1. **Advertise** — skill names and descriptions are injected into the system prompt at session start (~100 tokens per skill).
|
||||
2. **Load** — the model calls the `load_skill` tool when it decides a skill is relevant to the user's turn, and the full `SKILL.md` body is returned.
|
||||
|
||||
This means the model only pays the token cost for a skill's full body when it actually needs it, and updating a skill in Foundry + restarting the agent is enough to pick up the change — no code redeploy required.
|
||||
|
||||
### Agent Hosting
|
||||
|
||||
The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An Azure AI Foundry project with a deployed model (e.g., `gpt-4.1-mini`)
|
||||
- Azure CLI logged in (`az login`)
|
||||
|
||||
### Required RBAC
|
||||
|
||||
Your identity (or the Managed Identity running the container in production) needs **Azure AI User** on the Foundry project scope. This single role covers both authoring skills with `provision_skills.py` and downloading them from `main.py`.
|
||||
|
||||
## Provisioning the skills (one time)
|
||||
|
||||
From this directory, with the venv activated and `az login` done:
|
||||
|
||||
```bash
|
||||
export FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
|
||||
python provision_skills.py
|
||||
```
|
||||
|
||||
Or in PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
|
||||
python provision_skills.py
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
Provisioning skill 'escalation-policy' from skills/escalation-policy/SKILL.md...
|
||||
Imported skill 'escalation-policy' (id=skill_..., has_blob=True).
|
||||
Provisioning skill 'support-style' from skills/support-style/SKILL.md...
|
||||
Imported skill 'support-style' (id=skill_..., has_blob=True).
|
||||
Done.
|
||||
```
|
||||
|
||||
Re-running the script after editing a `SKILL.md` re-imports the skill, replacing the previous version.
|
||||
|
||||
> To remove a skill manually, call `project.beta.skills.delete("<name>")` on an `AIProjectClient` constructed with `allow_preview=True`.
|
||||
|
||||
## Running the Agent Host
|
||||
|
||||
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
|
||||
|
||||
In addition to the standard environment variables, this sample requires:
|
||||
|
||||
```bash
|
||||
export SKILL_NAMES="support-style,escalation-policy"
|
||||
```
|
||||
|
||||
Or in PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:SKILL_NAMES="support-style,escalation-policy"
|
||||
```
|
||||
|
||||
You can also place these in a `.env` file next to `main.py` — see [`.env.example`](.env.example).
|
||||
|
||||
On startup you should see:
|
||||
|
||||
```text
|
||||
Downloading skill 'support-style' from Foundry...
|
||||
Downloading skill 'escalation-policy' from Foundry...
|
||||
```
|
||||
|
||||
The downloaded `SKILL.md` files land under `downloaded_skills/<name>/SKILL.md` next to `main.py`. This directory is recreated from scratch on every run, so deleting it manually is never necessary.
|
||||
|
||||
## Interacting with the agent
|
||||
|
||||
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
|
||||
|
||||
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Hi, I am Alex. I just want to confirm I can return my tent within 30 days."}'
|
||||
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "I want a $750 refund on Order #A-1042 right now or I am calling my lawyer."}'
|
||||
```
|
||||
|
||||
| Prompt mentions | Skill that should drive the response |
|
||||
|---|---|
|
||||
| Routine return / shipping / care question | Model loads `support-style` (canary `STYLE-CANARY-3318`) — no escalation. |
|
||||
| Injury, legal threat, press, or refund > $500 | Model loads `escalation-policy` (canary `ESC-CANARY-7742`) **and** `support-style`. |
|
||||
|
||||
Because skills are loaded on demand, the canary token in a response also proves the model actually invoked `load_skill` for the matching skill (not just saw its name in the advertised list).
|
||||
|
||||
## Deploying the Agent to Foundry
|
||||
|
||||
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
|
||||
|
||||
When deploying, make sure `SKILL_NAMES` is set in your `azd` environment so it gets injected into the hosted container per [`agent.manifest.yaml`](agent.manifest.yaml):
|
||||
|
||||
```bash
|
||||
azd env set SKILL_NAMES "support-style,escalation-policy"
|
||||
```
|
||||
|
||||
If it is not set, running `azd ai agent init -m <agent-manifest.yaml>` will prompt you to enter it interactively.
|
||||
|
||||
The deployed agent's Managed Identity needs **Azure AI User** on the Foundry project to download skills at startup. Make sure you have run `provision_skills.py` against the same Foundry project before deploying — otherwise the agent will fail to start with HTTP 404 on the skill download.
|
||||
|
||||
> The `skills/` source folder is **not** deployed to Foundry — only the downloaded skills are used at runtime. The `provision_skills.py` step is required to upload the skills to Foundry before the agent can download them.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
name: agent-framework-agent-foundry-skills-responses
|
||||
description: >
|
||||
An Agent Framework agent that downloads its instructions from the Foundry
|
||||
Skills REST API at startup, demonstrating how to decouple behavioral
|
||||
guidelines (tone, escalation policy, etc.) from agent code.
|
||||
metadata:
|
||||
tags:
|
||||
- Agent Framework
|
||||
- AI Agent Hosting
|
||||
- Azure AI AgentServer
|
||||
- Responses Protocol
|
||||
- Foundry Skills
|
||||
template:
|
||||
name: agent-framework-agent-foundry-skills-responses
|
||||
kind: hosted
|
||||
protocols:
|
||||
- protocol: responses
|
||||
version: 1.0.0
|
||||
environment_variables:
|
||||
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
|
||||
- name: SKILL_NAMES
|
||||
value: "{{SKILL_NAMES}}"
|
||||
parameters:
|
||||
properties:
|
||||
- name: SKILL_NAMES
|
||||
secret: false
|
||||
description: Comma-separated list of Foundry skill names to download at startup (e.g., support-style,escalation-policy)
|
||||
resources:
|
||||
- kind: model
|
||||
id: gpt-4.1-mini
|
||||
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
|
||||
kind: hosted
|
||||
name: agent-framework-agent-foundry-skills-responses
|
||||
protocols:
|
||||
- protocol: responses
|
||||
version: 1.0.0
|
||||
resources:
|
||||
cpu: "0.25"
|
||||
memory: "0.5Gi"
|
||||
environment_variables:
|
||||
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
|
||||
- name: SKILL_NAMES
|
||||
value: ${SKILL_NAMES}
|
||||
@@ -0,0 +1,100 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Foundry Skills hosted agent sample.
|
||||
|
||||
At startup, this agent downloads each Foundry Skill named in
|
||||
``SKILL_NAMES`` from the project's ``beta.skills`` API, unpacks each
|
||||
one into a separate runtime directory under ``downloaded_skills/``, and wires
|
||||
that directory into a :class:`SkillsProvider` so the agent advertises the
|
||||
skills to the model and loads them on demand (progressive disclosure).
|
||||
|
||||
Upload the skills to Foundry once with ``provision_skills.py`` before running
|
||||
this sample.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from agent_framework import Agent, SkillsProvider
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from agent_framework_foundry_hosting import ResponsesHostServer
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Runtime directory where skills downloaded from Foundry are unpacked.
|
||||
# Kept separate from the static ``skills/`` source folder so the two never
|
||||
# get confused: the source folder is the input to ``provision_skills.py``
|
||||
# and the runtime folder is the output of this script's bootstrap step.
|
||||
DOWNLOADED_SKILLS_DIR: Final = Path(__file__).parent / "downloaded_skills"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _bootstrap_skills(endpoint: str, skill_names: list[str], target_dir: Path) -> None:
|
||||
"""Download each named skill via ``project.beta.skills`` and unpack it as ``<target_dir>/<name>/SKILL.md``."""
|
||||
if target_dir.exists(): # noqa: ASYNC240
|
||||
shutil.rmtree(target_dir)
|
||||
target_dir.mkdir(parents=True) # noqa: ASYNC240
|
||||
|
||||
async with (
|
||||
DefaultAzureCredential() as credential,
|
||||
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project,
|
||||
):
|
||||
for name in skill_names:
|
||||
logger.info(f"Downloading skill '{name}' from Foundry...")
|
||||
stream = await project.beta.skills.download(name)
|
||||
zip_bytes = b"".join([chunk async for chunk in stream])
|
||||
skill_dir = target_dir / name
|
||||
skill_dir.mkdir()
|
||||
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
||||
zf.extractall(skill_dir)
|
||||
if not (skill_dir / "SKILL.md").is_file():
|
||||
raise RuntimeError(f"Downloaded archive for '{name}' did not contain a SKILL.md at the root.")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
|
||||
skill_names = [name.strip() for name in os.environ["SKILL_NAMES"].split(",") if name.strip()]
|
||||
if not skill_names:
|
||||
raise RuntimeError("SKILL_NAMES must list at least one skill name.")
|
||||
|
||||
# Pull the latest copy of each skill from Foundry into a runtime-only folder.
|
||||
await _bootstrap_skills(project_endpoint, skill_names, DOWNLOADED_SKILLS_DIR)
|
||||
|
||||
# Build a SkillsProvider over the unpacked folder. The provider advertises
|
||||
# each skill's name + description to the model and exposes the ``load_skill``
|
||||
# tool the model uses to retrieve the full SKILL.md body on demand. No
|
||||
# script_runner is configured because the skills in this sample are
|
||||
# instruction-only.
|
||||
skills_provider = SkillsProvider.from_paths(skill_paths=str(DOWNLOADED_SKILLS_DIR))
|
||||
|
||||
client = FoundryChatClient(
|
||||
project_endpoint=project_endpoint,
|
||||
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=DefaultAzureCredential(),
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
client=client,
|
||||
instructions="You are a customer-support assistant for Contoso Outdoors.",
|
||||
context_providers=[skills_provider],
|
||||
# History will be managed by the hosting infrastructure, thus there
|
||||
# is no need to store history by the service. Learn more at:
|
||||
# https://developers.openai.com/api/reference/resources/responses/methods/create
|
||||
default_options={"store": False},
|
||||
)
|
||||
server = ResponsesHostServer(agent)
|
||||
await server.run_async()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Provision Foundry Skills used by this sample.
|
||||
|
||||
For each ``skills/<name>/SKILL.md`` file in this directory, this script packages
|
||||
the file as an in-memory ZIP and imports it through the Foundry project's
|
||||
:class:`~azure.ai.projects.aio.AIProjectClient` so the skill becomes downloadable
|
||||
by any hosted agent in the project.
|
||||
|
||||
If a skill with the same name already exists in Foundry, it is deleted first
|
||||
so the script is safe to re-run after editing a ``SKILL.md`` file.
|
||||
|
||||
Usage (from this directory, with the venv activated and ``az login`` done):
|
||||
|
||||
python provision_skills.py
|
||||
|
||||
Required env vars (also read from a local ``.env`` file if present):
|
||||
|
||||
FOUNDRY_PROJECT_ENDPOINT e.g. https://<account>.services.ai.azure.com/api/projects/<project>
|
||||
|
||||
Your identity needs the ``Azure AI User`` role on the Foundry project.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
from dotenv import load_dotenv
|
||||
|
||||
SKILLS_DIR = Path(__file__).parent / "skills"
|
||||
|
||||
|
||||
def _zip_skill_md(skill_md: Path) -> bytes:
|
||||
"""Return the bytes of a ZIP archive containing ``SKILL.md`` at the root."""
|
||||
buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(buffer, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr("SKILL.md", skill_md.read_text(encoding="utf-8"))
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
async def _delete_skill_if_exists(project: AIProjectClient, name: str) -> None:
|
||||
try:
|
||||
await project.beta.skills.delete(name)
|
||||
except ResourceNotFoundError:
|
||||
return
|
||||
print(f" Deleted existing skill '{name}'.")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
load_dotenv()
|
||||
|
||||
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
|
||||
|
||||
skill_files = sorted(SKILLS_DIR.glob("*/SKILL.md"))
|
||||
if not skill_files:
|
||||
raise RuntimeError(f"No SKILL.md files found under {SKILLS_DIR}.")
|
||||
|
||||
async with (
|
||||
DefaultAzureCredential() as credential,
|
||||
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project,
|
||||
):
|
||||
for skill_md in skill_files:
|
||||
name = skill_md.parent.name
|
||||
print(f"Provisioning skill '{name}' from {skill_md.relative_to(SKILLS_DIR.parent)}...")
|
||||
await _delete_skill_if_exists(project, name)
|
||||
imported = await project.beta.skills.create_from_package(_zip_skill_md(skill_md))
|
||||
print(f" Imported skill '{imported.name}' (id={imported.skill_id}, has_blob={imported.has_blob}).")
|
||||
|
||||
print("Verifying skills via project.beta.skills.list()...")
|
||||
listed = {skill.name: skill async for skill in project.beta.skills.list()}
|
||||
for skill_md in skill_files:
|
||||
name = skill_md.parent.name
|
||||
skill = listed.get(name)
|
||||
if skill is None:
|
||||
raise RuntimeError(f"Skill '{name}' was imported but is not present in the project listing.")
|
||||
print(
|
||||
f" OK '{skill.name}': id={skill.skill_id}, "
|
||||
f"description={skill.description!r}, has_blob={skill.has_blob}"
|
||||
)
|
||||
|
||||
print("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
agent-framework
|
||||
agent-framework-foundry-hosting
|
||||
azure-ai-projects
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: escalation-policy
|
||||
description: When and how to escalate Contoso Outdoors customer-support tickets.
|
||||
---
|
||||
|
||||
# Contoso Outdoors Escalation Policy
|
||||
|
||||
You must follow this escalation policy on every conversation.
|
||||
|
||||
## Escalate immediately when the customer
|
||||
|
||||
- Reports an injury, allergic reaction, or other safety incident.
|
||||
- Mentions legal action, regulators, or the press.
|
||||
- Has waited more than 14 days for a refund that was already approved.
|
||||
- Requests a refund larger than $500.
|
||||
|
||||
## How to escalate
|
||||
|
||||
1. Acknowledge the issue in one sentence.
|
||||
2. Tell the customer you are escalating to a senior specialist.
|
||||
3. Provide the escalation reference `ESC-CANARY-7742` and the SLA: a senior
|
||||
specialist will reply within 1 business day.
|
||||
4. Do not promise a specific outcome (refund, replacement, compensation) on
|
||||
escalated tickets — only the senior specialist can commit to one.
|
||||
|
||||
## Do not escalate
|
||||
|
||||
- Routine returns within the standard 30-day window.
|
||||
- Shipping status questions.
|
||||
- Product care and usage questions.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
---
|
||||
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 — never apologetic in a hand-wringing way.
|
||||
- Use the customer's name when it is known.
|
||||
- Sign every response with `— Contoso Outdoors Support`.
|
||||
|
||||
## Formatting
|
||||
|
||||
- Keep replies to 1–3 short paragraphs unless the customer asks for detail.
|
||||
- Use bullet lists only when enumerating concrete steps or options.
|
||||
- Always reference order numbers as `Order #<id>` (e.g. `Order #A-1042`).
|
||||
|
||||
## 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 `# `.
|
||||
Generated
+3897
-3910
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user