chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:59:43 +08:00
commit c79da9cdf9
9350 changed files with 1725465 additions and 0 deletions
@@ -0,0 +1,434 @@
---
name: azure-openai-to-responses
description: >-
Migrate Python apps from Azure OpenAI Chat Completions to the Responses API.
Covers AzureOpenAI/AsyncAzureOpenAI client migration to the v1 endpoint,
streaming, tools, structured output, multi-turn, EntraID auth, and model
compatibility checks. Python-focused, Azure OpenAI-specific.
USE FOR: migrate to responses API, switch from chat completions, openai responses,
upgrade openai SDK, responses API migration, move from completions to responses,
gpt-5 migration, azure openai python migration, chat completions to responses,
AzureOpenAI to OpenAI client, python azure openai upgrade.
DO NOT USE FOR: building new apps from scratch (start with responses directly),
Node/TypeScript/C#/Java/Go migrations (this skill is Python-only),
Azure infrastructure setup (use azure-prepare), deploying models (use microsoft-foundry).
license: MIT
---
# Migrate Python Apps from Azure OpenAI Chat Completions to Responses API
> **AUTHORITATIVE GUIDANCE — FOLLOW EXACTLY**
>
> This skill migrates Python codebases using Azure OpenAI Chat Completions
> to the unified Responses API. Follow these instructions precisely.
> Do not improvise parameter mappings or invent API shapes.
---
## Triggers
Activate this skill when user wants to:
- Migrate a Python app from Azure OpenAI Chat Completions to Responses API
- Upgrade Python OpenAI SDK usage to the latest API shape against Azure OpenAI
- Prepare Python code for GPT-5 or newer models that require Responses on Azure
- Switch from `AzureOpenAI`/`AsyncAzureOpenAI` to standard `OpenAI`/`AsyncOpenAI` client with the v1 endpoint
- Fix deprecation warnings related to `AzureOpenAI` constructors or `api_version`
---
## ⚠️ Model Compatibility — CHECK FIRST
> **Before migrating, verify your Azure OpenAI deployment supports the Responses API.**
### 1. Smoke-test your deployment (fastest)
```python
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"],
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
)
try:
resp = client.responses.create(
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
input="ping",
max_output_tokens=50,
store=False,
)
print(f"✅ Deployment supports Responses API: {resp.output_text}")
except Exception as e:
print(f"❌ Deployment does NOT support Responses API: {e}")
```
> **Note**: `max_output_tokens` has a **minimum of 16** on Azure OpenAI. Values below 16 return a 400 error. Use 50+ for smoke tests.
If this returns a 404, the deployment's model doesn't support Responses yet — check the reference below or redeploy with a supported model.
### 2. Check available models in your region (recommended)
Run the built-in model compatibility tool to see what's available with Responses API support in your specific region:
```bash
python migrate.py models --subscription YOUR_SUB_ID --location YOUR_REGION
```
This queries Azure ARM live and shows a compatibility matrix — which models support Responses, structured output, tools, etc. Use `--filter gpt-5.1,gpt-5.2` to narrow results or `--json` for scripting.
### 3. Full model support reference
- **Live query**: `python migrate.py models` (see above — region-specific, always up to date)
- **Browse availability**: [Model summary table and region availability](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/models-sold-directly-by-azure?tabs=global-standard-aoai%2Cglobal-standard&pivots=azure-openai#model-summary-table-and-region-availability)
- **Quickstart & guidance**: **https://aka.ms/openai/start**
### ⚠️ Older model limitations
> **WARNING**: Older models (e.g., `gpt-4o`, `gpt-4`) may not support all Responses API features fully.
>
> Known limitations with older models:
> - **`reasoning` parameter**: Not supported on `gpt-4o-mini`, `gpt-4o`, and many non-reasoning models. Only migrate `reasoning` if it was already present in the original code.
> - **`seed` parameter**: Not supported in Responses API at all — remove from all requests.
> - **Structured output via `text.format`**: Older models may not enforce `strict: true` JSON schemas reliably.
> - **Tool orchestration**: GPT-5+ orchestrates tool calls as part of internal reasoning. Older models on Responses still work but lack this deep integration.
> - **Temperature constraints**: When migrating to `gpt-5`, temperature must be omitted or set to `1`. Older models have no such constraint.
### O-series reasoning models (o1, o3-mini, o3, o4-mini)
O-series models have unique parameter constraints. When migrating apps that target o-series models:
- **`temperature`**: Must be `1` (or omitted). O-series models do not accept other values.
- **`max_completion_tokens``max_output_tokens`**: Apps using the Azure-specific `max_completion_tokens` must switch to `max_output_tokens`. Set high values (4096+) because reasoning tokens count against the limit.
- **`reasoning_effort`**: If the app uses `reasoning_effort` (low/medium/high), keep it — the Responses API supports this parameter for o-series models.
- **Streaming behavior**: O-series models may buffer output until reasoning completes before emitting text delta events. Streaming still works, but the first `response.output_text.delta` may arrive after a longer delay than with GPT models.
- **`top_p`**: Not supported on o-series — remove if present.
- **Tool use**: O-series models support tools via the Responses API the same as GPT models, but tool call orchestration quality varies by model.
**Action — proactive model advisory**: During the scan phase, check which model the app targets (deployment names, env vars, config). If the model is `gpt-4o` or older (not gpt-4.1+), proactively tell the user:
- The migration will work for basic text, chat, streaming, and tools on their current model.
- Newer models (`gpt-5.1`, `gpt-5.2`) offer better tool orchestration, structured output enforcement, reasoning, and cross-region availability.
- They should consider upgrading their deployment when ready — it's not blocking the migration.
Do not block or refuse to migrate based on model version. The advisory is informational.
### GitHub Models does NOT support the Responses API
> **GitHub Models (`models.github.ai`, `models.inference.ai.azure.com`) does not support the Responses API.**
If the codebase has a GitHub Models code path (look for `base_url` pointing to `models.github.ai` or `models.inference.ai.azure.com`), **remove it entirely** during migration. The Responses API requires Azure OpenAI, OpenAI, or a compatible local endpoint (e.g., Ollama with Responses support).
Action during scan:
- Flag any GitHub Models code paths for removal.
---
## Framework Migration
Many apps use higher-level frameworks on top of OpenAI. When migrating these, the framework's own API changes — not just the underlying OpenAI calls.
### Microsoft Agent Framework (MAF)
**Check your MAF version first** — the migration depends on whether you are on MAF 1.0.0+ or a pre-1.0.0 beta/rc.
#### MAF 1.0.0+ (agent-framework-openai >= 1.0.0)
`OpenAIChatClient` **already uses the Responses API** — no migration needed. If the codebase uses the legacy `OpenAIChatCompletionClient` (which uses `chat.completions.create`), replace it with `OpenAIChatClient`.
| Before | After |
|--------|-------|
| `from agent_framework.openai import OpenAIChatCompletionClient` | `from agent_framework.openai import OpenAIChatClient` |
| `OpenAIChatCompletionClient(...)` | `OpenAIChatClient(...)` |
To check your version: `python -c "import agent_framework_openai; print(agent_framework_openai.__version__)"`
#### MAF pre-1.0.0 (beta/rc releases)
In pre-1.0.0 MAF, `OpenAIChatClient` used Chat Completions. Upgrade to `agent-framework-openai>=1.0.0` where `OpenAIChatClient` uses the Responses API by default.
No other changes needed — the `Agent` and tool APIs remain the same.
### LangChain (`langchain-openai`)
Add `use_responses_api=True` to `ChatOpenAI()`. Also update response access from `.content` to `.text`.
| Before | After |
|--------|-------|
| `ChatOpenAI(model=..., base_url=..., api_key=...)` | `ChatOpenAI(model=..., base_url=..., api_key=..., use_responses_api=True)` |
| `result['messages'][-1].content` | `result['messages'][-1].text` |
For complete before/after code examples, see [cheat-sheet.md](./references/cheat-sheet.md).
---
## Frontend Migration Guidance
> **The Responses API is a server-side concern.** Migrate your Python backend; the frontend's HTTP contract should stay unchanged unless your backend is a thin pass-through — in that case, consider adopting the Responses request shape to eliminate a translation layer. If the frontend calls OpenAI directly with a client-side key, move those calls to a backend first.
### `@microsoft/ai-chat-protocol` deprecation
The `@microsoft/ai-chat-protocol` npm package is deprecated and should be replaced with [`ndjson-readablestream`](https://www.npmjs.com/package/ndjson-readablestream). If you encounter it in a frontend:
1. Replace the CDN script tag:
```html
<!-- Before -->
<script src="https://cdn.jsdelivr.net/npm/@microsoft/ai-chat-protocol@.../dist/iife/index.js"></script>
<!-- After -->
<script src="https://cdn.jsdelivr.net/npm/ndjson-readablestream@1.0.7/dist/ndjson-readablestream.umd.js"></script>
```
2. Remove the `AIChatProtocolClient` instantiation (`new ChatProtocol.AIChatProtocolClient("/chat")`).
3. Replace `client.getStreamedCompletion(messages)` with a direct `fetch()` call to the backend streaming endpoint.
4. Replace `for await (const response of result)` with `for await (const chunk of readNDJSONStream(response.body))`.
5. Update property access from `response.delta.content` / `response.error` to `chunk.delta.content` / `chunk.error`.
---
## Goals
- Enumerate all Python call sites using Chat Completions or legacy Completions against Azure OpenAI.
- Propose a migration plan and sequencing for the Python codebase.
- Apply safe, minimal edits to switch to Responses API.
- Update callers to consume the Responses output schema; no backcompat wrappers.
- Run tests/lints; fix trivial breakages introduced by the migration.
- Prepare small, reviewable change sets and provide a final summary with diffs (do not commit).
---
## Guardrails
- Only modify files inside the git workspace. Never write outside.
- Do not preserve backward-compatibility shims; migrate code to the new API shape.
- Do not leave tombstone/transition comments or backup files.
- Preserve streaming semantics if previously used; otherwise use non-streaming.
- Ask for approval before running commands or network calls if in approval mode.
- Do not run `git add`/`git commit`/`git push`; produce working-tree edits only.
---
## Step 0: Azure OpenAI Client Migration (Prerequisite)
If the codebase uses `AzureOpenAI` or `AsyncAzureOpenAI` constructors, migrate to the standard `OpenAI` / `AsyncOpenAI` constructors first. The Azure-specific constructors are deprecated in `openai>=1.108.1`.
### Why the v1 API path?
The new `/openai/v1` endpoint uses the standard `OpenAI()` client instead of `AzureOpenAI()`, requires no `api_version` parameter, and works identically across OpenAI and Azure OpenAI. The same client code is future-proof — no version management needed.
### Key changes
| Before | After |
|--------|-------|
| `AzureOpenAI` | `OpenAI` |
| `AsyncAzureOpenAI` | `AsyncOpenAI` |
| `azure_endpoint` | `base_url` |
| `azure_ad_token_provider` | `api_key` |
| `api_version=...` | Remove entirely |
### Cleanup checklist
- Remove `api_version` argument from client construction.
- Remove `AZURE_OPENAI_VERSION` / `AZURE_OPENAI_API_VERSION` environment variables from `.env`, app settings, and Bicep/infra files.
- Rename `AZURE_OPENAI_CLIENT_ID` → `AZURE_CLIENT_ID` in `.env`, app settings, Bicep/infra, and test fixtures (standard Azure Identity SDK convention).
- Ensure `openai>=1.108.1` in `requirements.txt` or `pyproject.toml`.
### Environment variable migration
| Old env var | Action | Notes |
|-------------|--------|-------|
| `AZURE_OPENAI_VERSION` | **Remove** | No `api_version` needed with v1 endpoint |
| `AZURE_OPENAI_API_VERSION` | **Remove** | Same as above |
| `AZURE_OPENAI_CLIENT_ID` | **Rename** → `AZURE_CLIENT_ID` | Standard Azure Identity SDK convention for `ManagedIdentityCredential(client_id=...)` |
| `AZURE_OPENAI_ENDPOINT` | **Keep** | Still needed for `base_url` construction |
| `AZURE_OPENAI_CHAT_DEPLOYMENT` | **Keep** | Used as `model` param in `responses.create` |
| `AZURE_OPENAI_API_KEY` | **Keep** | Used as `api_key` for key-based auth |
For client setup code examples (sync, async, EntraID, API key, multi-tenant), see [cheat-sheet.md](./references/cheat-sheet.md).
---
## Step 1: Detect Legacy Call Sites
Run the [detect_legacy.py](./scripts/detect_legacy.py) script to find all call sites that need migration:
```bash
python skills/azure-openai-to-responses/scripts/detect_legacy.py .
```
Or run these searches manually — every match is a migration target:
```bash
# Legacy API calls (must rewrite)
rg "chat\.completions\.create"
rg "ChatCompletion\.create"
rg "Completion\.create"
# Deprecated Azure client constructors (must replace)
rg "AzureOpenAI\("
rg "AsyncAzureOpenAI\("
# Response shape access patterns (must update)
rg "choices\[0\]\.message\.content"
rg "choices\[0\]\.delta\.content"
rg "choices\[0\]\.message\.function_call"
rg "choices\[0\]\.message\.tool_calls"
# Tool definitions in old nested format (must flatten)
rg '"function":\s*{\s*"name"'
rg "pydantic_function_tool"
# Tool results in old format (must convert to function_call_output)
rg '"role":\s*"tool"'
rg '"tool_call_id"'
# Deprecated parameters (must remove or rename)
rg "response_format"
rg "max_tokens\b" # rename to max_output_tokens
rg "['\"]seed['\"]" # remove entirely
# Deprecated env vars (clean up)
rg "AZURE_OPENAI_API_VERSION|AZURE_OPENAI_VERSION"
rg "AZURE_OPENAI_CLIENT_ID" # should be AZURE_CLIENT_ID
# GitHub Models endpoints (must remove — Responses API not supported)
rg "models\.github\.ai|models\.inference\.ai\.azure"
# Framework-level legacy patterns (must update)
rg "OpenAIChatCompletionClient" # MAF 1.0.0+: replace with OpenAIChatClient
rg "ChatOpenAI\(" | grep -v "use_responses_api" # LangChain: needs use_responses_api=True
# Test infrastructure (must update)
rg "ChatCompletionChunk|AsyncCompletions\.create" tests/
rg "_azure_ad_token_provider" tests/
rg "prompt_filter_results|content_filter_results" tests/
rg "choices\[0\]" tests/
# Content filter error body access (must update — structure changed)
rg 'innererror.*content_filter_result|error\.body\["innererror"\]'
rg "content_filter_result\[" # old singular form — now content_filter_results (plural) inside content_filters array
# Raw HTTP calls to Chat Completions endpoint (must update URL)
rg "/openai/deployments/.*/chat/completions"
rg "api-version="
```
### Heuristics (detect and rewrite)
- **Chat Completions client**: `client.chat.completions.create` → `client.responses.create(...)`.
- **Azure client constructors**: `AzureOpenAI(...)` → `OpenAI(base_url=..., api_key=...)`.
- **Tools**: convert function-calling tool definitions from nested format (`{"type": "function", "function": {"name": ...}}`) to flat Responses format (`{"type": "function", "name": ...}`); use `tool_choice`; return tool results as `{"type": "function_call_output", "call_id": ..., "output": ...}` items (not `{"role": "tool", ...}`).
- **Tool round-trips**: when the model returns function calls, append `response.output` items to the conversation (not a manual `{"role": "assistant", "tool_calls": [...]}` dict), then append `function_call_output` items for each result.
- **Few-shot tool examples**: if the conversation includes hardcoded tool call examples, convert them to `{"type": "function_call", "id": "fc_...", "call_id": "fc_...", ...}` + `{"type": "function_call_output", ...}` items. IDs must start with `fc_`.
- **`pydantic_function_tool()`**: this helper still generates the old nested format and is **not compatible** with `responses.create()`. Replace with manual tool definitions or a flattening wrapper.
- **Multi-turn**: maintain conversation history in the app; pass prior turns via `input` items.
- **Formatting**: replace Chat's top-level `response_format` with `text.format` in Responses. Canonical shape: `text={"format": {"type": "json_schema", "name": "Output", "strict": True, "schema": {...}}}`.
- **Content items**: replace Chat `content[].type: "text"` with Responses `content[].type: "input_text"` for user/system turns.
- **Image content items**: replace Chat `content[].type: "image_url"` with Responses `content[].type: "input_image"`. The `image_url` field changes from a nested object `{"url": "..."}` to a flat string. See the cheat sheet for before/after examples.
- **Reasoning effort**: **only migrate `reasoning` if it already exists in the original code**.
- **Content filter error handling**: the error body structure changed. Chat Completions used `error.body["innererror"]["content_filter_result"]` (singular); Responses API uses `error.body["content_filters"][0]["content_filter_results"]` (plural, inside an array). Code that accesses `innererror` will raise `KeyError`. Rewrite to use the new path.
- **Raw HTTP calls**: if the app calls the Azure OpenAI REST API directly (via `requests`, `httpx`, etc.) using `/openai/deployments/{name}/chat/completions?api-version=...`, rewrite to `/openai/v1/responses`. The request body changes: `messages` → `input`, add `max_output_tokens` and `store: false`, remove `api-version` query param. The response body changes: `choices[0].message.content` → `output[0].content[0].text` (note: `output_text` is an SDK convenience property not present in raw REST JSON).
---
## Step 2: Apply Migration
### Migration notes (Chat Completions → Responses)
- **Why migrate**: Responses is the unified API for text, tools, and streaming; Chat Completions is legacy. With GPT-5, Responses is required for best performance.
- **HTTP**: Azure endpoint switches from `/openai/deployments/{name}/chat/completions` to `/openai/v1/responses`.
- **Fields**: `messages` → `input`, `max_tokens` → `max_output_tokens`. `temperature` remains.
- **Formatting**: `response_format` → `text.format` with a proper object.
- **Content items**: Replace Chat `content[].type: "text"` with Responses `content[].type: "input_text"` for system/user turns.
- **Image content items**: Replace Chat `content[].type: "image_url"` with Responses `content[].type: "input_image"`. Flatten the `image_url` field from `{"image_url": {"url": "..."}}` to `{"image_url": "..."}` (a plain string — either an HTTPS URL or a `data:image/...;base64,...` data URI).
### Parameter mapping reference
| Chat Completions | Responses API |
|-----------------|---------------|
| `prompt` | `input` |
| `messages` | `input` (array of items) |
| `max_tokens` | `max_output_tokens` |
| `response_format` | `text.format` (object) |
| `temperature` | `temperature` (unchanged) |
| `stop` | `stop` (unchanged) |
| `frequency_penalty` | `frequency_penalty` (unchanged) |
| `presence_penalty` | `presence_penalty` (unchanged) |
| `tools` / function-calling | `tools` (unchanged) |
| `seed` | **Remove** (not supported) |
| `store` | `store` (set to `false`) |
| `content[].type: "text"` | `content[].type: "input_text"` |
| `content[].type: "image_url"` | `content[].type: "input_image"` |
| `"image_url": {"url": "..."}` | `"image_url": "..."` (flat string) |
For complete before/after code examples, see [cheat-sheet.md](./references/cheat-sheet.md).
For test infrastructure migration (mocks, snapshots, assertions), see [test-migration.md](./references/test-migration.md).
For troubleshooting errors and gotchas, see [troubleshooting.md](./references/troubleshooting.md).
---
## Data Retention & State
- Set `store: false` on all Responses requests.
- Do not rely on previous message IDs or server-stored context; keep state client-managed and minimize metadata.
---
## Acceptance Criteria
### Code-level gates (all must pass)
- [ ] Zero matches for `rg "chat\.completions\.create|ChatCompletion\.create|Completion\.create"` in migrated files.
- [ ] Zero matches for `rg "AzureOpenAI\(|AsyncAzureOpenAI\("` — all constructors use `OpenAI`/`AsyncOpenAI` with the v1 endpoint.
- [ ] Zero matches for `rg "models\.github\.ai|models\.inference\.ai\.azure"` — GitHub Models code paths removed.
- [ ] Zero matches for `rg "OpenAIChatCompletionClient"` — MAF 1.0.0+ code uses `OpenAIChatClient` (which uses Responses API). In pre-1.0.0, upgrade to `agent-framework-openai>=1.0.0`.
- [ ] All `ChatOpenAI(...)` calls include `use_responses_api=True`.
- [ ] Zero matches for `rg "choices\[0\]"` — all response access uses `resp.output_text` or the Responses output schema.
- [ ] No `response_format` at top level; all structured output uses `text={"format": {...}}`.
- [ ] `openai>=1.108.1` and `azure-identity` in `requirements.txt` or `pyproject.toml`; dependencies reinstalled.
- [ ] `store=False` set on every `responses.create` call.
- [ ] No `api_version` in client construction; `AZURE_OPENAI_API_VERSION` removed from env files and infra.
### Test infrastructure gates (all must pass)
- [ ] Zero matches for `rg "ChatCompletionChunk|AsyncCompletions\.create|chat\.completions" tests/`.
- [ ] Zero matches for `rg "_azure_ad_token_provider" tests/` — assertions updated to check `isinstance(client, AsyncOpenAI)` or `base_url`.
- [ ] Zero matches for `rg "prompt_filter_results|content_filter_results" tests/` — Azure-specific filter mocks removed.
- [ ] Mock fixtures use `kwargs.get("input")` not `kwargs.get("messages")`.
- [ ] Snapshot / golden files updated to Responses streaming shape (no `choices[0]`, `function_call`, `logprobs`, etc.).
- [ ] `pytest` passes with zero failures after all test updates.
### Behavioral gates (verify manually or via test harness)
- [ ] **Basic completion**: non-streaming `responses.create` returns non-empty `output_text`.
- [ ] **Stream parity**: if the original code used streaming, the migrated code streams and yields `response.output_text.delta` events with non-empty deltas.
- [ ] **Structured output**: if using `text.format` with `json_schema`, `json.loads(resp.output_text)` succeeds and matches the schema.
- [ ] **Tool-call loop**: if tools are used, the model issues tool calls, the app executes them, and the follow-up request returns a final `output_text` (no infinite loop).
- [ ] **Async parity**: if `AsyncAzureOpenAI` was used, `AsyncOpenAI` equivalent works with `await`.
- [ ] **Error rate**: no new 400/401/404 errors compared to the pre-migration baseline.
### Deliverables
- Summary includes edited files, before/after counts of legacy call sites, and next steps.
- Changes are working-tree edits only (no commits).
---
## SDK Version Requirements
| Package | Minimum Version |
|---------|----------------|
| `openai` | `>=1.108.1` |
| `azure-identity` | Latest (for EntraID auth) |
---
## References
- [Cheat Sheet — all code snippets](./references/cheat-sheet.md)
- [Test Migration — mocks, snapshots, assertions](./references/test-migration.md)
- [Troubleshooting — errors, risk table, gotchas](./references/troubleshooting.md)
- [detect_legacy.py — automated scanner](./scripts/detect_legacy.py)
- [Azure OpenAI Starter Kit](https://aka.ms/openai/start)
- [Azure OpenAI Responses API docs](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/responses)
- [Azure OpenAI API version lifecycle](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle?view=foundry-classic&tabs=python#api-evolution)
- [OpenAI Responses API reference](https://platform.openai.com/docs/api-reference/responses)
@@ -0,0 +1,849 @@
# Responses API Cheat Sheet (Python + Azure OpenAI)
> All snippets below assume `deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]` and `client` is already initialized (see client setup).
## Basic request
```python
resp = client.responses.create(
model=deployment,
input="Hello",
max_output_tokens=1000,
store=False,
)
print(resp.output_text)
```
## Client setup — EntraID (recommended)
```python
import os
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import OpenAI
token_provider = get_bearer_token_provider(
DefaultAzureCredential(),
"https://cognitiveservices.azure.com/.default"
)
client = OpenAI(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
api_key=token_provider,
)
```
## Client setup — API key
```python
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"],
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
)
```
## Async client setup — EntraID
```python
import os
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import AsyncOpenAI
token_provider = get_bearer_token_provider(
DefaultAzureCredential(),
"https://cognitiveservices.azure.com/.default"
)
client = AsyncOpenAI(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
api_key=token_provider,
)
```
## Async client setup — EntraID with explicit tenant (multi-tenant)
When the Azure OpenAI resource is in a **different tenant** than the default, pass `tenant_id` explicitly to the credential. This is common in dev/test scenarios where the developer's home tenant differs from the resource tenant.
```python
import os
from azure.identity.aio import (
AzureDeveloperCliCredential,
ChainedTokenCredential,
ManagedIdentityCredential,
get_bearer_token_provider,
)
from openai import AsyncOpenAI
# ManagedIdentityCredential for production (Azure Container Apps, App Service, etc.)
managed_identity_cred = ManagedIdentityCredential(
client_id=os.getenv("AZURE_CLIENT_ID") # user-assigned managed identity
)
# AzureDeveloperCliCredential for local dev — explicit tenant_id is critical
azd_cred = AzureDeveloperCliCredential(
tenant_id=os.getenv("AZURE_TENANT_ID"),
process_timeout=60,
)
# Chain: try managed identity first, fall back to azd CLI
azure_credential = ChainedTokenCredential(managed_identity_cred, azd_cred)
token_provider = get_bearer_token_provider(
azure_credential, "https://cognitiveservices.azure.com/.default"
)
client = AsyncOpenAI(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
api_key=token_provider,
)
```
## Async client migration — before/after
Before (deprecated):
```python
from openai import AsyncAzureOpenAI
client = AsyncAzureOpenAI(
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
azure_ad_token_provider=token_provider,
)
resp = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=500,
)
print(resp.choices[0].message.content)
```
After:
```python
from openai import AsyncOpenAI
deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]
client = AsyncOpenAI(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
api_key=token_provider,
)
resp = await client.responses.create(
model=deployment,
input="Hello",
max_output_tokens=1000,
store=False,
)
print(resp.output_text)
```
## Full sync migration — before/after
Before (legacy — Azure OpenAI Chat Completions):
```python
from openai import AzureOpenAI
import os
client = AzureOpenAI(
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_API_KEY"],
)
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=500,
)
print(resp.choices[0].message.content)
```
After (Responses API — Azure OpenAI v1 endpoint):
```python
from openai import OpenAI
import os
deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]
client = OpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"],
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
)
resp = client.responses.create(
model=deployment,
input="Hello",
max_output_tokens=1000,
store=False,
)
print(resp.output_text)
```
## Streaming (sync)
```python
stream = client.responses.create(
model=deployment,
input="Explain streaming in simple terms",
max_output_tokens=1000,
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.completed":
print() # newline at end
```
## Streaming (async)
```python
stream = await client.responses.create(
model=deployment,
input="Explain streaming in simple terms",
max_output_tokens=1000,
stream=True,
)
async for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.completed":
print()
```
## Web app streaming — backend-to-frontend shape
When migrating a web app that streams SSE/JSONL to a frontend, the **backend serialization format** changes. Design the new backend output to preserve the frontend's existing access patterns so the frontend needs no changes.
**Before** — Chat Completions backend typically serialized each chunk's `choices[0]` dict:
```python
# Old: serialized full choice dict per chunk
async for chunk in response:
if chunk.choices:
yield json.dumps(chunk.choices[0].model_dump()) + "\n"
```
Frontend read: `response.delta.content` (deep path into the choice object).
**After** — Responses API backend emits a minimal shape preserving the same frontend access path:
```python
# New: emit only what the frontend needs
async for event in await chat_coroutine:
if event.type == "response.output_text.delta":
yield json.dumps({"delta": {"content": event.delta}}) + "\n"
elif event.type == "response.completed":
yield json.dumps({"delta": {"content": None}, "finish_reason": "stop"}) + "\n"
```
Frontend still reads `response.delta.content`**no frontend changes needed**.
> **Key insight**: The Responses API streaming shape (`event.type` + `event.delta`) is fundamentally different from Chat Completions (`chunk.choices[0].delta.content`). But your backend-to-frontend contract is yours to define. Shape the backend output to match what the frontend already expects.
## Streaming event sequence
When `stream: true`, the API emits events in this order:
1. `response.created` response object initialized
2. `response.in_progress` generation started
3. `response.output_item.added` output item created
4. `response.content_part.added` content part started
5. `response.output_text.delta` text chunks (multiple, each has `delta: string`)
6. `response.output_text.done` text generation finished
7. `response.content_part.done` content part finished
8. `response.output_item.done` output item finished
9. `response.completed` full response complete
For basic text streaming, only handle `response.output_text.delta` (for text chunks) and `response.completed` (for finish).
## Streaming error handling in web apps
When streaming in a web app, wrap the async iteration in `try/except` and yield errors as JSON so the frontend can display them gracefully (e.g., rate limits, transient failures):
```python
@stream_with_context
async def response_stream():
chat_coroutine = client.responses.create(
model=deployment,
input=all_messages,
max_output_tokens=1000,
stream=True,
store=False,
)
try:
async for event in await chat_coroutine:
if event.type == "response.output_text.delta":
yield json.dumps({"delta": {"content": event.delta}}) + "\n"
elif event.type == "response.completed":
yield json.dumps({"delta": {"content": None}, "finish_reason": "stop"}) + "\n"
except Exception as e:
current_app.logger.error(e)
yield json.dumps({"error": str(e)}) + "\n"
```
> **Why this matters**: Azure OpenAI returns `429 Too Many Requests` during rate limiting. Without the `try/except`, the streaming response silently dies. With it, the frontend receives `{"error": "Too Many Requests"}` and can show a retry prompt.
## Streaming event types (Python SDK)
- `ResponseTextDeltaEvent`: `type='response.output_text.delta'`, `delta: str`
- `ResponseCompletedEvent`: `type='response.completed'`, `response: Response`
## Conversation format
```python
# The Responses API supports conversation format via input array
response = client.responses.create(
model=deployment,
input=[
{"role": "system", "content": "You are an Azure cloud architect."},
{"role": "user", "content": "Design a scalable web application architecture."},
],
max_output_tokens=1000,
)
print(response.output_text)
```
## Content filter error handling
The error body structure changed from Chat Completions to Responses API.
Before (Chat Completions):
```python
except openai.APIError as error:
if error.code == "content_filter":
if error.body["innererror"]["content_filter_result"]["jailbreak"]["filtered"] is True:
print("Jailbreak detected!")
```
After (Responses API):
```python
except openai.APIError as error:
if error.code == "content_filter":
if error.body["content_filters"][0]["content_filter_results"]["jailbreak"]["filtered"] is True:
print("Jailbreak detected!")
```
Key differences:
- `innererror` wrapper is **gone** — content filter details are now at the top level of `error.body`.
- `content_filter_result` (singular) → `content_filters` (plural array) containing `content_filter_results` (plural) inside each entry.
- Each entry in `content_filters` includes `blocked`, `source_type`, and `content_filter_results` with per-category details (`jailbreak`, `hate`, `sexual`, `violence`, `self_harm`).
Full Responses API content filter error body shape:
```json
{
"message": "The response was filtered...",
"type": "invalid_request_error",
"param": "prompt",
"code": "content_filter",
"content_filters": [
{
"blocked": true,
"source_type": "prompt",
"content_filter_results": {
"jailbreak": { "detected": true, "filtered": true },
"hate": { "filtered": false, "severity": "safe" },
"sexual": { "filtered": false, "severity": "safe" },
"violence": { "filtered": false, "severity": "safe" },
"self_harm": { "filtered": false, "severity": "safe" }
}
}
]
}
```
## Raw HTTP migration (requests/httpx)
If the app calls Azure OpenAI REST directly instead of using the SDK:
Before (Chat Completions):
```python
endpoint = f"{azure_endpoint}/openai/deployments/{deployment}/chat/completions?api-version=2024-03-01-preview"
data = {
"messages": [{"role": "user", "content": query}],
"model": model_name,
"temperature": 0,
}
response = requests.post(endpoint, headers=headers, json=data)
message = response.json()["choices"][0]["message"]["content"]
```
After (Responses API):
```python
endpoint = f"{azure_endpoint}/openai/v1/responses"
data = {
"model": deployment,
"input": [{"role": "user", "content": query}],
"temperature": 0,
"max_output_tokens": 1000,
"store": False,
}
response = requests.post(endpoint, headers=headers, json=data)
output_text = response.json()["output"][0]["content"][0]["text"]
```
> **Note**: `output_text` is a convenience property on the Python SDK's `Response` object. The raw REST JSON response does not have a top-level `output_text` field — the text is at `output[0].content[0].text`.
## Multi-turn conversation
```python
# Build a conversation with Responses API
messages = [
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "Write a Python function to calculate factorial"},
]
response = client.responses.create(
model=deployment,
input=messages,
max_output_tokens=400,
)
# Add assistant's response to conversation
messages.append({"role": "assistant", "content": response.output_text})
# Continue the conversation
messages.append({"role": "user", "content": "Now optimize it with memoization"})
response2 = client.responses.create(
model=deployment,
input=messages,
max_output_tokens=400,
)
print(response2.output_text)
```
Content-typed multi-turn (explicit `input_text`/`output_text`):
```python
messages = [
{"role": "system", "content": [{"type": "input_text", "text": "You are helpful."}]},
{"role": "user", "content": [{"type": "input_text", "text": "Hi"}]},
{"role": "assistant", "content": [{"type": "output_text", "text": "Hello!"}]},
{"role": "user", "content": [{"type": "input_text", "text": "Tell me a joke"}]},
]
resp = client.responses.create(model=deployment, input=messages, store=False)
```
### Multi-turn via `previous_response_id` (alternative)
Instead of managing the conversation array yourself, you can chain responses
server-side using `previous_response_id`. The API stores each response and
automatically prepends prior turns.
```python
# First turn
response = client.responses.create(
model=deployment,
input=[{"role": "user", "content": "Write a Python function to calculate factorial"}],
)
print(response.output_text)
# Subsequent turns — just pass the new user message + previous response ID
response2 = client.responses.create(
model=deployment,
input=[{"role": "user", "content": "Now optimize it with memoization"}],
previous_response_id=response.id,
)
print(response2.output_text)
```
**When to use which:**
| Approach | Pros | Cons |
|---|---|---|
| `input` array (manual) | Full control over history; can trim/summarize; no server-side storage needed (`store=False`) | More code; you manage the array |
| `previous_response_id` | Simpler code; automatic chaining | Requires `store=True` (default); conversation stored server-side; can't modify history between turns |
> **Migration note:** Most Chat Completions apps already manage their own message array, so converting to `input` array is a more direct 1:1 migration. Use `previous_response_id` for new code or when you don't need to manipulate conversation history.
## O-series reasoning models (o1, o3-mini, o3, o4-mini)
O-series models have unique parameter constraints when migrating to Responses API.
### Parameter mapping for o-series
| Chat Completions (o-series) | Responses API | Notes |
|---|---|---|
| `max_completion_tokens` | `max_output_tokens` | Set high (4096+) — reasoning tokens count against the limit |
| `reasoning_effort` | `reasoning.effort` | Keep as-is if present (low/medium/high) |
| `temperature` | Remove or set to `1` | O-series only accepts `1` |
| `top_p` | Remove | Not supported on o-series |
| `seed` | Remove | Not supported in Responses API |
### O-series before/after
Before (Chat Completions with o-series):
```python
resp = client.chat.completions.create(
model="o4-mini",
messages=[{"role": "user", "content": "Solve this step by step: 2x + 5 = 13"}],
max_completion_tokens=4096,
reasoning_effort="medium",
)
print(resp.choices[0].message.content)
```
After (Responses API):
```python
resp = client.responses.create(
model=deployment,
input="Solve this step by step: 2x + 5 = 13",
max_output_tokens=4096,
reasoning={"effort": "medium"},
store=False,
)
print(resp.output_text)
```
> **Note**: O-series models may buffer output during reasoning before emitting text deltas. Streaming still works but the first `response.output_text.delta` event may arrive after a longer delay than with GPT models.
## Accessing reasoning tokens
```python
# Reasoning models use internal reasoning — you can see how many reasoning tokens were used
response = client.responses.create(
model=deployment,
input="Explain quantum computing in simple terms",
max_output_tokens=1000,
)
print(response.output_text)
print(f"Status: {response.status}")
print(f"Reasoning tokens: {response.usage.output_tokens_details.reasoning_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")
```
> **Important**: Use `max_output_tokens=1000` (not 50200) to account for reasoning models' internal reasoning process. The model uses reasoning tokens internally before generating the final output.
## Structured output — JSON Schema
```python
resp = client.responses.create(
model=deployment,
input="What is the capital of France?",
max_output_tokens=500,
text={
"format": {
"type": "json_schema",
"name": "Output",
"strict": True,
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": False,
},
}
},
store=False,
)
import json
data = json.loads(resp.output_text)
print(data["answer"])
```
## Tool use
- Define functions in `tools` with the **flat Responses API format**`name`, `description`, and `parameters` at the top level (not nested under `function`).
- When the model asks to call a tool, execute it in your app and include the tool result in the next request as a `function_call_output` item within `input`.
- Keep schemas minimal; validate inputs before execution.
- When using `strict: true`, all properties must be listed in `required` and `additionalProperties: false` is mandatory.
> **⚠️ `pydantic_function_tool()` is incompatible**: The `openai.pydantic_function_tool()` helper still generates the old Chat Completions nested format (`{"type": "function", "function": {"name": ...}}`). Do not use it with `responses.create()`. Define tool schemas manually or write a wrapper to flatten the output.
### Tool definition format
The Responses API uses a **flat** tool format — `name`, `description`, `parameters` are top-level keys (not nested under `function`).
**Before (Chat Completions — nested):**
```python
tools = [{"type": "function", "function": {"name": "lookup_weather", "parameters": {...}}}]
```
**After (Responses API — flat):**
```python
tools = [{"type": "function", "name": "lookup_weather", "parameters": {...}}]
```
Full example:
```python
tools = [
{
"type": "function",
"name": "lookup_weather",
"description": "Lookup the weather for a given city name.",
"parameters": {
"type": "object",
"properties": {
"city_name": {"type": "string", "description": "The city name"},
},
"required": ["city_name"],
"additionalProperties": False,
},
}
]
response = client.responses.create(
model=deployment,
input=[
{"role": "system", "content": "You are a weather chatbot."},
{"role": "user", "content": "What's the weather in Berkeley?"},
],
tools=tools,
tool_choice="auto",
store=False,
)
```
With `strict: true` (schema enforcement):
```python
tools = [
{
"type": "function",
"name": "lookup_weather",
"description": "Lookup the weather for a given city name.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"city_name": {"type": "string", "description": "The city name"},
},
"required": ["city_name"], # All properties MUST be listed
"additionalProperties": False, # Required for strict mode
},
}
]
```
### Tool call round-trip (execute and return results)
When the model requests a tool call, use `response.output` items + `function_call_output`**not** the Chat Completions `role: assistant` + `role: tool` pattern.
```python
import json
messages = [
{"role": "system", "content": "You are a weather chatbot."},
{"role": "user", "content": "Is it sunny in Berkeley?"},
]
response = client.responses.create(
model=deployment, input=messages, tools=tools, store=False,
)
tool_calls = [item for item in response.output if item.type == "function_call"]
if tool_calls:
# Add the model's function_call items to conversation
messages.extend(response.output)
# Execute each tool and add results
for tc in tool_calls:
result = execute_tool(tc.name, json.loads(tc.arguments))
messages.append({
"type": "function_call_output",
"call_id": tc.call_id,
"output": json.dumps(result),
})
# Get final response with tool results
response = client.responses.create(
model=deployment, input=messages, tools=tools, store=False,
)
print(response.output_text)
```
### Few-shot tool call examples
When providing few-shot examples of tool calls in `input`, use `function_call` and `function_call_output` items. IDs must start with `fc_`.
```python
messages = [
{"role": "system", "content": "You are a product search assistant."},
{"role": "user", "content": "Find climbing gear for outdoors"},
{
"type": "function_call",
"id": "fc_example1",
"call_id": "call_example1",
"name": "search_database",
"arguments": '{"search_query": "climbing gear outdoor"}',
},
{
"type": "function_call_output",
"call_id": "call_example1",
"output": "Results: ...",
},
{"role": "user", "content": "Now find shoes under $50"},
]
```
```python
# Built-in web search example
resp = client.responses.create(
model=deployment,
tools=[{"type": "web_search_preview"}],
input="What was a positive news story from today?",
store=False,
)
print(resp.output_text)
```
## Image input
Image content items change type from `image_url` to `input_image`, and the URL changes from a nested object to a flat string.
### Image input — before (Chat Completions)
```python
resp = client.chat.completions.create(
model=deployment,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {"url": "https://example.com/image.jpg"},
},
],
}
],
max_tokens=500,
)
print(resp.choices[0].message.content)
```
### Image input — after (Responses API, URL)
```python
resp = client.responses.create(
model=deployment,
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "What's in this image?"},
{
"type": "input_image",
"image_url": "https://example.com/image.jpg",
},
],
}
],
max_output_tokens=500,
store=False,
)
print(resp.output_text)
```
### Image input — after (Responses API, base64)
```python
import base64
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode("utf-8")
base64_image = encode_image("path_to_your_image.jpg")
resp = client.responses.create(
model=deployment,
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "What's in this image?"},
{
"type": "input_image",
"image_url": f"data:image/jpeg;base64,{base64_image}",
},
],
}
],
max_output_tokens=500,
store=False,
)
print(resp.output_text)
```
> **Key changes**: (1) `"type": "image_url"` → `"type": "input_image"`, (2) `"image_url": {"url": "..."}` (nested object) → `"image_url": "..."` (flat string — either HTTPS URL or `data:image/...;base64,...` data URI), (3) `"type": "text"` → `"type": "input_text"`.
## Microsoft Agent Framework (MAF) migration
**Check your MAF version first** — the migration depends on whether you are on MAF 1.0.0+ or a pre-1.0.0 beta/rc.
To check: `python -c "import agent_framework_openai; print(agent_framework_openai.__version__)"`
### MAF 1.0.0+ (agent-framework-openai >= 1.0.0)
In MAF 1.0.0+, `OpenAIChatClient` **already uses the Responses API** — no migration needed.
If the codebase uses the legacy `OpenAIChatCompletionClient` (which uses `chat.completions.create`), replace it with `OpenAIChatClient`:
Before:
```python
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
async_credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(async_credential, "https://cognitiveservices.azure.com/.default")
client = OpenAIChatCompletionClient(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT']}/openai/v1/",
api_key=token_provider,
model=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
)
```
After:
```python
from agent_framework.openai import OpenAIChatClient
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
async_credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(async_credential, "https://cognitiveservices.azure.com/.default")
client = OpenAIChatClient(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT']}/openai/v1/",
api_key=token_provider,
model=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
)
```
### MAF pre-1.0.0 (beta/rc releases)
In pre-1.0.0 MAF, `OpenAIChatClient` used Chat Completions. Upgrade to `agent-framework-openai>=1.0.0` where `OpenAIChatClient` uses the Responses API by default.
> **Note**: The `Agent`, `MCPStreamableHTTPTool`, and other MAF APIs remain unchanged — only the client class import and instantiation change.
## LangChain (`langchain-openai`) migration
Add `use_responses_api=True` to `ChatOpenAI()`. Also update message content access from `.content` to `.text`.
Before:
```python
import azure.identity
from langchain_openai import ChatOpenAI
from pydantic import SecretStr
token_provider = azure.identity.get_bearer_token_provider(
azure.identity.DefaultAzureCredential(),
"https://cognitiveservices.azure.com/.default",
)
model = ChatOpenAI(
model=os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT"),
base_url=os.environ["AZURE_OPENAI_ENDPOINT"] + "/openai/v1/",
api_key=token_provider,
)
# ... agent invocation ...
result = await agent.ainvoke({"messages": [HumanMessage(content=query)]})
print(result['messages'][-1].content)
```
After:
```python
import azure.identity
from langchain_openai import ChatOpenAI
from pydantic import SecretStr
token_provider = azure.identity.get_bearer_token_provider(
azure.identity.DefaultAzureCredential(),
"https://cognitiveservices.azure.com/.default",
)
model = ChatOpenAI(
model=os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT"),
base_url=os.environ["AZURE_OPENAI_ENDPOINT"] + "/openai/v1/",
api_key=token_provider,
use_responses_api=True,
)
# ... agent invocation ...
result = await agent.ainvoke({"messages": [HumanMessage(content=query)]})
print(result['messages'][-1].text)
```
> **Key changes**: (1) `use_responses_api=True` in constructor, (2) `.content` → `.text` on response messages.
@@ -0,0 +1,163 @@
# Test Infrastructure Migration
When migrating a codebase from Chat Completions to Responses API, **tests break in predictable ways**. This reference covers what to fix.
---
## Mocking Streaming Responses (Python pytest)
### Core mock classes
```python
class MockResponseEvent:
"""Simulates a Responses API streaming event."""
def __init__(self, event_type: str, delta: str | None = None):
self.type = event_type
self.delta = delta
class AsyncResponseIterator:
"""Async iterator that yields Responses API streaming events from a string answer."""
def __init__(self, answer: str):
self.event_index = 0
self.events = []
for i, word in enumerate(answer.split(" ")):
# Preserve whitespace: prepend space to all words except the first
if i > 0:
word = " " + word
self.events.append(MockResponseEvent("response.output_text.delta", delta=word))
self.events.append(MockResponseEvent("response.completed"))
def __aiter__(self):
return self
async def __anext__(self):
if self.event_index < len(self.events):
event = self.events[self.event_index]
self.event_index += 1
return event
raise StopAsyncIteration
```
### Routing mock responses by message content
Real apps serve different answers based on the prompt. Route by `input` (not `messages`):
```python
async def mock_acreate(*args, **kwargs):
# Responses API uses 'input' not 'messages'
last_message = kwargs.get("input", [])[-1]["content"]
if last_message == "What is the capital of France?":
return AsyncResponseIterator("The capital of France is Paris.")
elif last_message == "What is the capital of Germany?":
return AsyncResponseIterator("The capital of Germany is Berlin.")
else:
raise ValueError(f"Unexpected message: {last_message}")
```
### Monkeypatch paths
| Client type | Monkeypatch path |
|-------------|------------------|
| `AsyncOpenAI` | `openai.resources.responses.AsyncResponses.create` |
| `OpenAI` (sync) | `openai.resources.responses.Responses.create` |
> **Before** (Chat Completions): `openai.resources.chat.AsyncCompletions.create`
> **After** (Responses): `openai.resources.responses.AsyncResponses.create`
### Full fixture example
```python
@pytest.fixture
def mock_openai_responses(monkeypatch):
# ... MockResponseEvent and AsyncResponseIterator classes here ...
async def mock_acreate(*args, **kwargs):
last_message = kwargs.get("input", [])[-1]["content"]
if last_message == "What is the capital of France?":
return AsyncResponseIterator("The capital of France is Paris.")
else:
raise ValueError(f"Unexpected message: {last_message}")
monkeypatch.setattr("openai.resources.responses.AsyncResponses.create", mock_acreate)
```
---
## 1. Update mock fixtures
Replace `ChatCompletionChunk`-based mocks with the `MockResponseEvent` / `AsyncResponseIterator` pattern above. Key changes:
| Before (Chat Completions mock) | After (Responses mock) |
|-------------------------------|------------------------|
| `openai.types.chat.ChatCompletionChunk(...)` | `MockResponseEvent(event_type, delta)` |
| `choices[0].delta.content` | `event.delta` |
| `finish_reason="stop"` in chunk | `event.type == "response.completed"` |
| Azure-specific `prompt_filter_results` chunk | Remove entirely |
| Azure-specific `content_filter_results` per choice | Remove entirely |
| `kwargs.get("messages")` in mock | `kwargs.get("input")` in mock |
---
## 2. Update snapshot / golden files
If the test suite uses snapshot testing (e.g., `pytest-snapshot`, syrupy, or hand-rolled JSONL snapshots), the expected output shape changes:
**Before** (Chat Completions streaming JSONL):
```jsonl
{"delta": {"content": null, "function_call": null, "refusal": null, "role": "assistant", "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {}}
{"delta": {"content": "The", "function_call": null, "refusal": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, ...}}
{"delta": {"content": null, ...}, "finish_reason": "stop", "index": 0, "logprobs": null, "content_filter_results": {}}
```
**After** (Responses API streaming JSONL):
```jsonl
{"delta": {"content": "The"}}
{"delta": {"content": " capital"}}
{"delta": {"content": null}, "finish_reason": "stop"}
```
The new shape is dramatically simpler — no `function_call`, `refusal`, `role`, `tool_calls`, `index`, `logprobs`, or `content_filter_results` fields. Update or regenerate all snapshot files.
> **Tip**: Run tests with `--snapshot-update` (pytest-snapshot) or `--update-snapshots` (syrupy) after migrating to auto-regenerate.
---
## 3. Update test assertions
Common assertion breakages:
| Old assertion | Problem | New assertion |
|--------------|---------|---------------|
| `client._azure_ad_token_provider is not None` | `AsyncOpenAI` has no `_azure_ad_token_provider` attribute | `isinstance(client, AsyncOpenAI)` and `"/openai/v1/" in str(client.base_url)` |
| `client.api_version == "2024-..."` | No `api_version` on `OpenAI`/`AsyncOpenAI` | Remove entirely |
| `isinstance(client, AsyncAzureOpenAI)` | Client type changed | `isinstance(client, AsyncOpenAI)` |
---
## 4. Update environment variables in test fixtures
Tests often set env vars via `monkeypatch.setenv`. Update these:
| Old env var | New env var | Notes |
|-------------|-------------|-------|
| `AZURE_OPENAI_CLIENT_ID` | `AZURE_CLIENT_ID` | Standard Azure Identity SDK convention |
| `AZURE_OPENAI_VERSION` | Remove | No `api_version` needed |
| `AZURE_OPENAI_API_VERSION` | Remove | No `api_version` needed |
| `AZURE_OPENAI_ENDPOINT` | `AZURE_OPENAI_ENDPOINT` | Keep (still needed for `base_url`) |
| `AZURE_OPENAI_CHAT_DEPLOYMENT` | `AZURE_OPENAI_CHAT_DEPLOYMENT` | Keep (deployment name for `model` param) |
---
## 5. Search for test code that needs migration
```bash
# Test-specific legacy patterns
rg "ChatCompletionChunk" tests/
rg "AsyncCompletions\.create" tests/
rg "chat\.completions" tests/
rg "_azure_ad_token_provider" tests/
rg "prompt_filter_results" tests/
rg "content_filter_results" tests/
rg "AZURE_OPENAI_VERSION|AZURE_OPENAI_API_VERSION" tests/
rg "AZURE_OPENAI_CLIENT_ID" tests/
```
@@ -0,0 +1,80 @@
# Troubleshooting, Risk Table & Gotchas
## Troubleshooting 400s
| Error | Fix |
|-------|-----|
| `missing_required_parameter: tools[0].name` | Tool definition uses old Chat Completions nested format | Flatten from `{"type": "function", "function": {"name": ...}}` to `{"type": "function", "name": ..., "parameters": ...}` — name, description, parameters go at the top level |
| `unknown_parameter: input[N].tool_calls` | Multi-turn tool results use old Chat Completions format | Replace `{"role": "assistant", "tool_calls": [...]}` + `{"role": "tool", ...}` with `response.output` items + `{"type": "function_call_output", "call_id": ..., "output": ...}` |
| `invalid_function_parameters: 'required' is required` | `strict: true` tool missing `required` array | When `strict: true`, all properties must be listed in `required` and `additionalProperties: false` must be set |
| `invalid_function_parameters: 'additionalProperties' is required` | `strict: true` tool missing `additionalProperties: false` | Add `"additionalProperties": false` to the parameters object |
| `invalid input[N].id: Expected an ID that begins with 'fc'` | Few-shot function_call ID has wrong prefix | Function call IDs must start with `fc_` (e.g., `fc_example1`), not `call_` |
| `missing_required_parameter: text.format.name` | Add `"name"` key to the format dict (e.g., `"name": "Output"`) |
| `invalid_type: text.format` | Ensure `text.format` is a dict with `type`, `name`, `strict`, `schema` keys — not a string |
| `invalid input content type` | Use `input_text`/`output_text` content types instead of Chat `text` |
| `invalid input content type` (image) | Image content still uses `"type": "image_url"` | Change to `"type": "input_image"` |
| `Expected object, got string` on `image_url` | `image_url` is still a nested object `{"url": "..."}` | Flatten to a plain string: `"image_url": "https://..."` or `"image_url": "data:image/...;base64,..."` |
| `integer below minimum value` for `max_output_tokens` | Minimum is **16** on Azure OpenAI. Use 50+ for tests, 1000+ for production. |
| `429 Too Many Requests` during streaming | Rate limited. Wrap streaming in `try/except`, yield error JSON to frontend, implement backoff/retry. |
| `KeyError: 'innererror'` on content filter error | Content filter error body structure changed in Responses API | Chat Completions used `error.body["innererror"]["content_filter_result"]`; Responses API uses `error.body["content_filters"][0]["content_filter_results"]` (plural, inside an array). Rewrite all `innererror` access. |
---
## Migration Risk Table
| Symptom | Likely Mistake | Fix |
|---------|---------------|-----|
| Empty `output_text` / truncated response | `max_output_tokens` too low for reasoning models | Set `max_output_tokens=1000` or higher — reasoning tokens count against the limit |
| `400 invalid_type: text.format` | Passed `response_format` string instead of `text.format` dict | Use `text={"format": {"type": "json_schema", "name": "...", "strict": True, "schema": {...}}}` |
| `404 Not Found` on `/openai/v1/responses` | Wrong `base_url` — missing `/openai/v1/` suffix | Ensure `base_url=f"{endpoint}/openai/v1/"` (with trailing slash) |
| `401 Unauthorized` after switching to `OpenAI()` | `api_key` not set or token provider not passed correctly | For EntraID: `api_key=token_provider` (the callable). For API key: `api_key=os.environ["AZURE_OPENAI_API_KEY"]` |
| Model returns `deployment not found` | `model` param doesn't match your Azure deployment name | Use `model=os.environ["AZURE_OPENAI_DEPLOYMENT"]` — this is the deployment name, not the model name |
| `json.loads(resp.output_text)` raises `JSONDecodeError` | Schema not enforced or model doesn't support strict JSON | Ensure `"strict": True` in schema, and verify model supports structured output |
| Streaming yields no `delta` events | Checking wrong event type | Filter on `event.type == "response.output_text.delta"`, not Chat's `chat.completion.chunk` |
| `400` error on image input after migration | Image content type not updated | Change `"type": "image_url"``"type": "input_image"` and flatten `"image_url": {"url": "..."}``"image_url": "..."` (plain string) |
| Tool calls loop infinitely | Missing tool result in follow-up `input` | After executing a tool, append a `{"type": "function_call_output", "call_id": ..., "output": ...}` item to `input` in the next request |
| `temperature` error with GPT-5 or o-series | Explicit `temperature` value other than 1 | Remove `temperature` or set to `1` for GPT-5 and o-series models (o1, o3-mini, o3, o4-mini) |
| `top_p` error with o-series | `top_p` not supported | Remove `top_p` when targeting o-series models |
| `max_completion_tokens` not recognized | Using Azure-specific parameter | Replace `max_completion_tokens` with `max_output_tokens`. Set to 4096+ for o-series (reasoning tokens count against the limit). |
| Empty/truncated output from o-series | `max_output_tokens` too low | O-series uses reasoning tokens internally. Set `max_output_tokens=4096` or higher — not 5001000. |
| `400 integer_below_min_value` for `max_output_tokens` | Value below 16 | Azure OpenAI enforces `max_output_tokens >= 16`. Use 50+ for smoke tests, 1000+ for production. |
| `429 Too Many Requests` mid-stream | Rate limited by Azure OpenAI | Stream breaks silently without error handling. Always wrap `async for event in await coroutine:` in `try/except` and yield `{"error": str(e)}` to the frontend. |
| `AzureDeveloperCliCredential``CredentialUnavailableError` | Wrong tenant or not logged in | Pass `tenant_id=os.getenv("AZURE_TENANT_ID")` explicitly. Run `azd auth login --tenant <tenant-id>` locally. |
| `404 Not Found` using GitHub Models (`models.github.ai`) | GitHub Models does not support Responses API | Remove the GitHub Models code path entirely. Use Azure OpenAI, OpenAI, or a compatible local endpoint (e.g., Ollama with Responses support). |
| MAF `OpenAIChatCompletionClient` still using Chat Completions | Using legacy MAF client in 1.0.0+ | In MAF 1.0.0+, `OpenAIChatClient` uses Responses API by default. Replace `OpenAIChatCompletionClient` with `OpenAIChatClient`. For pre-1.0.0, upgrade to `agent-framework-openai>=1.0.0`. |
| LangChain agent returns empty or fails with tool calls | `ChatOpenAI` not using Responses API | Add `use_responses_api=True` to `ChatOpenAI(...)`. Also change `.content``.text` on response messages. |
| `KeyError: 'innererror'` in content filter error handler | Error body structure changed in Responses API | Rewrite `error.body["innererror"]["content_filter_result"]["jailbreak"]``error.body["content_filters"][0]["content_filter_results"]["jailbreak"]`. The `innererror` wrapper is gone; content filter details are now in a top-level `content_filters` array with `content_filter_results` (plural) inside each entry. |
| Raw HTTP call to `/openai/deployments/.../chat/completions` returns 404 | Old Chat Completions REST endpoint | Rewrite URL to `/openai/v1/responses`. Change request body: `messages``input`, add `max_output_tokens` + `store: false`, remove `api-version` query param. Change response parsing: `choices[0].message.content``output[0].content[0].text` (note: `output_text` is an SDK convenience property, not in the raw REST JSON). |
---
## Gotchas
1. If you previously used Chat Completions for conversation state, manage your own state explicitly with Responses.
2. Prefer `max_output_tokens` over legacy `max_tokens`.
3. When migrating to `gpt-5`, ensure `temperature` is not specified or is set to `1`.
4. Replace Chat `content[].type: "text"` with Responses `content[].type: "input_text"` for user/system inputs.
5. For `text.format`, supply a proper dict (e.g., `{"type": "json_schema", "name": "Output", "schema": ..., "strict": True}`), not a plain string.
6. The `seed` parameter is not supported in Responses; remove it from requests.
7. **Reasoning**: Only include `reasoning` if the original code already used it. Do not add `reasoning` to API calls that didn't have it — many models (e.g., gpt-4o-mini) don't support this parameter.
8. **`max_output_tokens` sizing**: For reasoning models (GPT-5-mini, GPT-5, o-series), use `max_output_tokens=4096` or higher — not 501000. The model uses reasoning tokens internally before generating visible output; too-low limits cause truncated or empty responses.
9. **O-series `max_completion_tokens`**: If the original code used `max_completion_tokens` (Azure-specific for o-series), replace with `max_output_tokens`. The Responses API does not accept `max_completion_tokens`.
10. **O-series `reasoning_effort`**: If the original code uses `reasoning_effort` (low/medium/high), migrate it to `reasoning={"effort": "<value>"}` in the Responses API call.
11. **O-series streaming delay**: O-series models perform internal reasoning before generating output. When streaming, expect a longer delay before the first `response.output_text.delta` event. This is normal — the model is reasoning, not hung.
9. **`_azure_ad_token_provider` is gone**: `AsyncOpenAI` / `OpenAI` have no `_azure_ad_token_provider` attribute. Tests or code that access this attribute will fail with `AttributeError`. The token provider is passed as `api_key` and is not inspectable on the client object.
10. **Snapshot / golden files**: If the test suite uses snapshot testing, **all** snapshot files containing Chat Completions streaming shapes (`choices[0]`, `content_filter_results`, `function_call`, etc.) must be updated to the new Responses shape. This is easy to miss and causes snapshot assertion failures.
11. **Mock monkeypatch path**: The monkeypatch target changes from `openai.resources.chat.AsyncCompletions.create``openai.resources.responses.AsyncResponses.create` (or `Responses.create` for sync). Using the old path silently does nothing — the mock won't intercept, and tests hit the real API or fail.
12. **`input` not `messages`**: Mock functions must read `kwargs.get("input")` not `kwargs.get("messages")`. The Responses API uses `input` for conversation history.
13. **Env var naming**: Azure Identity SDK uses `AZURE_CLIENT_ID` (not `AZURE_OPENAI_CLIENT_ID`) for `ManagedIdentityCredential(client_id=...)`. Rename in tests, `.env` files, app settings, and Bicep/infra.
14. **`max_output_tokens` minimum is 16**: Azure OpenAI rejects values below 16 with `400 integer_below_min_value`. Use `50` for smoke tests, `1000`+ for production. The old `max_tokens` had no such minimum.
15. **`tenant_id` for `AzureDeveloperCliCredential`**: When the Azure OpenAI resource is in a different tenant, you **must** pass `tenant_id` explicitly — `AzureDeveloperCliCredential(tenant_id=os.getenv("AZURE_TENANT_ID"))`. Without it, the credential silently uses the wrong tenant and returns `401`.
16. **Rate limits surface differently in streaming**: With Chat Completions, a 429 typically prevented the stream from starting. With Responses API streaming, a 429 can occur **mid-stream** — the async iterator raises an exception. Always wrap the streaming loop in `try/except` and yield an error JSON line so the frontend can handle it gracefully.
17. **Streaming error handling is mandatory for web apps**: The pattern `try: async for event in await coroutine: ... except Exception as e: yield json.dumps({"error": str(e)})` is critical. Without it, the SSE/JSONL stream silently dies on any server-side error and the frontend hangs.
18. **Tool definitions must use flat format**: The Responses API expects `{"type": "function", "name": ..., "parameters": ...}` — not the Chat Completions nested `{"type": "function", "function": {"name": ..., "parameters": ...}}`. This is the most common migration error for function-calling code.
19. **`pydantic_function_tool()` is incompatible**: The `openai.pydantic_function_tool()` helper still generates the old nested format. Do not use it with `responses.create()`. Define tool schemas manually or flatten the output.
20. **Tool results use `function_call_output`, not `role: tool`**: After executing a tool, append `{"type": "function_call_output", "call_id": ..., "output": ...}` — not `{"role": "tool", "tool_call_id": ..., "content": ...}`. For the assistant's tool request, use `messages.extend(response.output)` — not a manual `{"role": "assistant", "tool_calls": [...]}` dict.
21. **`strict: true` requires `required` + `additionalProperties: false`**: When using `strict: true` on a tool, every property must be listed in the `required` array and `additionalProperties` must be `false`. Missing either causes a 400 error.
22. **Function call IDs have specific prefixes**: When providing few-shot `function_call` items in `input`, the `id` field must start with `fc_` and the `call_id` field must start with `call_` (e.g., `"id": "fc_example1", "call_id": "call_example1"`). Using the old Chat Completions `call_` prefix for `id` is rejected.
23. **GitHub Models does not support Responses API**: If the app has a GitHub Models code path (`base_url` pointing to `models.github.ai` or `models.inference.ai.azure.com`), remove it entirely. There is no migration path — switch to Azure OpenAI, OpenAI, or a compatible local endpoint.
24. **Content filter error body structure changed**: Chat Completions errors used `error.body["innererror"]["content_filter_result"]` (singular). Responses API errors use `error.body["content_filters"][0]["content_filter_results"]` (plural, inside an array). The `innererror` key no longer exists. Code that directly accesses `innererror` will raise `KeyError` at runtime — this is easy to miss in migration since it only surfaces when the content filter actually triggers. Always grep for `innererror` during migration.
25. **Raw HTTP calls need URL + body rewrite**: Apps calling Azure OpenAI REST directly (via `requests`, `httpx`, `aiohttp`) using `/openai/deployments/{name}/chat/completions?api-version=...` must switch to `/openai/v1/responses`. The request body uses `input` instead of `messages`, requires `max_output_tokens` and `store`, and the `api-version` query param is dropped. The response body text is at `output[0].content[0].text`**not** `output_text`, which is an SDK convenience property not present in the raw REST JSON.
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""Detect legacy Azure OpenAI Chat Completions patterns in a Python codebase.
Usage:
python detect_legacy.py <directory>
python detect_legacy.py . # current directory
python detect_legacy.py src/ tests/ # multiple directories
Scans for legacy OpenAI Chat Completions API usage, deprecated Azure client
constructors, response shape access patterns, deprecated parameters, and
test infrastructure that needs updating for the Responses API migration.
Exit codes:
0 — no legacy patterns found
1 — legacy patterns found (migration needed)
"""
import argparse
import re
import sys
from pathlib import Path
# Legacy patterns grouped by category
# Each entry: (regex_pattern, description, category)
PATTERNS: list[tuple[str, str, str]] = [
# Legacy API calls
(r"chat\.completions\.create", "Chat Completions API call", "api-call"),
(r"ChatCompletion\.create", "Legacy ChatCompletion.create", "api-call"),
(r"Completion\.create", "Legacy Completion.create", "api-call"),
# Deprecated Azure client constructors
(r"AzureOpenAI\(", "Deprecated AzureOpenAI constructor", "client"),
(r"AsyncAzureOpenAI\(", "Deprecated AsyncAzureOpenAI constructor", "client"),
# Response shape access patterns
(r"choices\[0\]\.message\.content", "Chat Completions response access", "response-shape"),
(r"choices\[0\]\.delta\.content", "Chat Completions streaming access", "response-shape"),
(r"choices\[0\]\.message\.function_call", "Legacy function_call access", "response-shape"),
(r"choices\[0\]\.message\.tool_calls", "Legacy tool_calls access", "response-shape"),
(r"choices\[0\]", "Generic choices[0] access", "response-shape"),
# Deprecated parameters
(r"\bmax_tokens\b", "Deprecated max_tokens (use max_output_tokens)", "parameter"),
(r"\bmax_completion_tokens\b", "Azure o-series max_completion_tokens (use max_output_tokens)", "parameter"),
(r"""['"]seed['"]""" , "Unsupported seed parameter", "parameter"),
(r"\bresponse_format\b", "Legacy response_format (use text.format)", "parameter"),
(r"\breasoning_effort\b", "O-series reasoning_effort (migrate to reasoning={'effort': ...})", "parameter"),
(r"\btop_p\b", "top_p parameter (not supported on o-series models)", "parameter"),
# Deprecated env vars
(r"AZURE_OPENAI_API_VERSION|AZURE_OPENAI_VERSION", "Deprecated api_version env var", "env-var"),
(r"AZURE_OPENAI_CLIENT_ID", "Should be AZURE_CLIENT_ID", "env-var"),
# GitHub Models (not supported by Responses API — must remove)
(r"models\.github\.ai|models\.inference\.ai\.azure", "GitHub Models endpoint (Responses API not supported — remove)", "github-models"),
# Framework-level legacy patterns
(r"OpenAIChatCompletionClient", "MAF OpenAIChatCompletionClient (uses Chat Completions; replace with OpenAIChatClient in 1.0.0+)", "framework"),
# Test infrastructure
(r"ChatCompletionChunk", "Legacy mock type in tests", "test"),
(r"AsyncCompletions\.create", "Legacy mock patch path in tests", "test"),
(r"_azure_ad_token_provider", "Legacy Azure AD assertion in tests", "test"),
(r"prompt_filter_results", "Azure-specific filter mock in tests", "test"),
(r"content_filter_results", "Azure-specific filter mock in tests", "test"),
]
SKIP_DIRS = {
".git", ".venv", "venv", "__pycache__", "node_modules",
".tox", ".mypy_cache", ".pytest_cache", "dist", "build",
".github", # don't scan the agent definition
"skills", # don't scan the skill itself
}
# Files that reference legacy patterns intentionally (docs, tooling)
SKIP_FILES = {
"README.md",
"migrate.py",
"find_legacy_openai_repos.py",
"detect_legacy.py",
"bulk_migrate.py",
}
EXTENSIONS = {".py", ".env", ".toml", ".cfg", ".ini", ".txt", ".md", ".yml", ".yaml", ".bicep", ".json"}
def scan_file(path: Path) -> list[tuple[int, str, str, str]]:
"""Scan a single file for legacy patterns. Returns list of (line_no, line, description, category)."""
hits: list[tuple[int, str, str, str]] = []
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return hits
for line_no, line in enumerate(text.splitlines(), start=1):
for pattern, description, category in PATTERNS:
if re.search(pattern, line):
hits.append((line_no, line.strip(), description, category))
return hits
def scan_directory(root: Path) -> dict[str, list[tuple[int, str, str, str]]]:
"""Walk a directory tree and return {filepath: [(line, text, desc, cat), ...]}."""
results: dict[str, list[tuple[int, str, str, str]]] = {}
for path in sorted(root.rglob("*")):
if any(part in SKIP_DIRS for part in path.parts):
continue
if path.name in SKIP_FILES:
continue
if path.is_file() and path.suffix in EXTENSIONS:
hits = scan_file(path)
if hits:
results[str(path)] = hits
return results
def print_report(all_results: dict[str, list[tuple[int, str, str, str]]]) -> int:
"""Print a grouped report and return total hit count."""
total = 0
categories: dict[str, list[tuple[str, int, str, str]]] = {}
for filepath, hits in all_results.items():
for line_no, line_text, description, category in hits:
categories.setdefault(category, []).append((filepath, line_no, line_text, description))
total += 1
if total == 0:
print("[PASS] No legacy Chat Completions patterns found.")
return 0
category_labels = {
"api-call": "Legacy API Calls (must rewrite)",
"client": "Deprecated Azure Client Constructors (must replace)",
"response-shape": "Response Shape Access (must update)",
"parameter": "Deprecated Parameters (must remove/rename)",
"env-var": "Deprecated Environment Variables (must clean up)",
"test": "Test Infrastructure (must update)",
}
print(f"[SCAN] Found {total} legacy pattern(s) across {len(all_results)} file(s):\n")
for cat_key in ["api-call", "client", "response-shape", "parameter", "env-var", "test"]:
items = categories.get(cat_key, [])
if not items:
continue
print(f"## {category_labels[cat_key]} ({len(items)} hit(s))\n")
for filepath, line_no, line_text, description in items:
print(f" {filepath}:{line_no}")
print(f" {description}")
print(f" > {line_text[:120]}")
print()
print(f"---\nTotal: {total} legacy pattern(s) in {len(all_results)} file(s).")
print("Run the migration skill to fix these.")
return total
def main() -> None:
parser = argparse.ArgumentParser(
description="Detect legacy Azure OpenAI Chat Completions patterns."
)
parser.add_argument(
"directories",
nargs="+",
help="Directories to scan",
)
args = parser.parse_args()
all_results: dict[str, list[tuple[int, str, str, str]]] = {}
for d in args.directories:
root = Path(d)
if not root.is_dir():
print(f"Warning: {d} is not a directory, skipping.", file=sys.stderr)
continue
all_results.update(scan_directory(root))
total = print_report(all_results)
sys.exit(1 if total > 0 else 0)
if __name__ == "__main__":
main()
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf of
any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don\'t include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+107
View File
@@ -0,0 +1,107 @@
---
name: "jupyter-notebook"
description: "Use when the user asks to create, scaffold, or edit Jupyter notebooks (`.ipynb`) for experiments, explorations, or tutorials; prefer the bundled templates and run the helper script `new_notebook.py` to generate a clean starting notebook."
---
# Jupyter Notebook Skill
Create clean, reproducible Jupyter notebooks for two primary modes:
- Experiments and exploratory analysis
- Tutorials and teaching-oriented walkthroughs
Prefer the bundled templates and the helper script for consistent structure and fewer JSON mistakes.
## When to use
- Create a new `.ipynb` notebook from scratch.
- Convert rough notes or scripts into a structured notebook.
- Refactor an existing notebook to be more reproducible and skimmable.
- Build experiments or tutorials that will be read or re-run by other people.
## Decision tree
- If the request is exploratory, analytical, or hypothesis-driven, choose `experiment`.
- If the request is instructional, step-by-step, or audience-specific, choose `tutorial`.
- If editing an existing notebook, treat it as a refactor: preserve intent and improve structure.
## Skill path (set once)
```bash
export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
export JUPYTER_NOTEBOOK_CLI="$CODEX_HOME/skills/jupyter-notebook/scripts/new_notebook.py"
```
User-scoped skills install under `$CODEX_HOME/skills` (default: `~/.codex/skills`).
## Workflow
1. Lock the intent.
Identify the notebook kind: `experiment` or `tutorial`.
Capture the objective, audience, and what "done" looks like.
2. Scaffold from the template.
Use the helper script to avoid hand-authoring raw notebook JSON.
```bash
uv run --python 3.12 python "$JUPYTER_NOTEBOOK_CLI" \
--kind experiment \
--title "Compare prompt variants" \
--out output/jupyter-notebook/compare-prompt-variants.ipynb
```
```bash
uv run --python 3.12 python "$JUPYTER_NOTEBOOK_CLI" \
--kind tutorial \
--title "Intro to embeddings" \
--out output/jupyter-notebook/intro-to-embeddings.ipynb
```
3. Fill the notebook with small, runnable steps.
Keep each code cell focused on one step.
Add short markdown cells that explain the purpose and expected result.
Avoid large, noisy outputs when a short summary works.
4. Apply the right pattern.
For experiments, follow `references/experiment-patterns.md`.
For tutorials, follow `references/tutorial-patterns.md`.
5. Edit safely when working with existing notebooks.
Preserve the notebook structure; avoid reordering cells unless it improves the top-to-bottom story.
Prefer targeted edits over full rewrites.
If you must edit raw JSON, review `references/notebook-structure.md` first.
6. Validate the result.
Run the notebook top-to-bottom when the environment allows.
If execution is not possible, say so explicitly and call out how to validate locally.
Use the final pass checklist in `references/quality-checklist.md`.
## Templates and helper script
- Templates live in `assets/experiment-template.ipynb` and `assets/tutorial-template.ipynb`.
- The helper script loads a template, updates the title cell, and writes a notebook.
Script path:
- `$JUPYTER_NOTEBOOK_CLI` (installed default: `$CODEX_HOME/skills/jupyter-notebook/scripts/new_notebook.py`)
## Temp and output conventions
- Use `tmp/jupyter-notebook/` for intermediate files; delete when done.
- Write final artifacts under `output/jupyter-notebook/` when working in this repo.
- Use stable, descriptive filenames (for example, `ablation-temperature.ipynb`).
## Dependencies (install only when needed)
Prefer `uv` for dependency management.
Optional Python packages for local notebook execution:
```bash
uv pip install jupyterlab ipykernel
```
The bundled scaffold script uses only the Python standard library and does not require extra dependencies.
## Environment
No required environment variables.
## Reference map
- `references/experiment-patterns.md`: experiment structure and heuristics.
- `references/tutorial-patterns.md`: tutorial structure and teaching flow.
- `references/notebook-structure.md`: notebook JSON shape and safe editing rules.
- `references/quality-checklist.md`: final validation checklist.
@@ -0,0 +1,6 @@
interface:
display_name: "Jupyter Notebooks"
short_description: "Create Jupyter notebooks for experiments and tutorials"
icon_small: "./assets/jupyter-small.svg"
icon_large: "./assets/jupyter.png"
default_prompt: "Create a Jupyter notebook for this task with clear sections, runnable cells, and concise takeaways."
@@ -0,0 +1,110 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Experiment: TITLE\n",
"\n",
"Objective:\n",
"- State the question you want to answer.\n",
"- Define the success criteria.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Setup: imports and reproducibility\n",
"from __future__ import annotations\n",
"\n",
"import random\n",
"import statistics\n",
"\n",
"SEED = 7\n",
"random.seed(SEED)\n",
"SEED\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plan\n",
"\n",
"- Hypothesis:\n",
"- Variables to sweep:\n",
"- Metrics to record:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Define parameters and lightweight helpers\n",
"sample_size = 20\n",
"values = [random.random() for _ in range(sample_size)]\n",
"summary = {\n",
" \"count\": len(values),\n",
" \"mean\": statistics.fmean(values),\n",
" \"min\": min(values),\n",
" \"max\": max(values),\n",
"}\n",
"summary\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Results\n",
"\n",
"- Key observations:\n",
"- Surprises or failure modes:\n",
"- Decision: continue, pivot, or stop:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Record findings in a minimal, copy-pasteable structure\n",
"result = {\n",
" \"seed\": SEED,\n",
" \"mean\": summary[\"mean\"],\n",
" \"range\": summary[\"max\"] - summary[\"min\"],\n",
"}\n",
"result\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"- What to try next:\n",
"- What to document elsewhere (PRD, notes, issue):\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="21" fill="currentColor" viewBox="0 0 20 21">
<path fill="currentColor" d="M4.582 17.078a1.48 1.48 0 0 1 1.066.395c.29.27.463.642.48 1.038v.11a1.509 1.509 0 0 1-.858 1.315 1.476 1.476 0 0 1-1.634-.256 1.504 1.504 0 0 1-.39-1.62 1.51 1.51 0 0 1 .52-.695 1.48 1.48 0 0 1 .816-.287Zm13.167-4.733a7.802 7.802 0 0 1-2.829 3.789 7.698 7.698 0 0 1-4.48 1.44 7.7 7.7 0 0 1-4.48-1.44 7.803 7.803 0 0 1-2.829-3.79c1.424 1.704 4.168 2.854 7.308 2.854 3.14 0 5.883-1.15 7.31-2.853ZM10.436 1.743c1.605 0 3.171.504 4.48 1.44a7.804 7.804 0 0 1 2.829 3.79C16.32 5.272 13.578 4.12 10.438 4.12c-3.14 0-5.884 1.155-7.31 2.855a7.804 7.804 0 0 1 2.828-3.79 7.699 7.699 0 0 1 4.48-1.44ZM3.246 2a.865.865 0 0 1 .91.336.885.885 0 0 1-.062 1.114.869.869 0 0 1-1.433-.224.889.889 0 0 1 .148-.966.875.875 0 0 1 .437-.26ZM16.087.076c.312-.013.617.1.847.313.23.213.367.51.381.824l-.005.176a1.197 1.197 0 0 1-.675.954 1.173 1.173 0 0 1-1.296-.202A1.194 1.194 0 0 1 15.44.305c.188-.14.413-.219.647-.229Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,107 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tutorial: TITLE\n",
"\n",
"Audience:\n",
"- Describe who this is for.\n",
"\n",
"Prerequisites:\n",
"- List required concepts or setup.\n",
"\n",
"Learning goals:\n",
"- By the end, the reader can...\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Outline\n",
"\n",
"1. Setup\n",
"2. A minimal working example\n",
"3. Variations and pitfalls\n",
"4. Exercises\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Setup cell: keep it short and deterministic\n",
"from __future__ import annotations\n",
"\n",
"import math\n",
"import random\n",
"\n",
"SEED = 21\n",
"random.seed(SEED)\n",
"SEED\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 1 - Start with a tiny example\n",
"\n",
"Explain what the next cell does in plain language.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Minimal working example\n",
"angles = [0, math.pi / 4, math.pi / 2]\n",
"sines = [math.sin(a) for a in angles]\n",
"list(zip(angles, sines))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exercises\n",
"\n",
"- Try a different input.\n",
"- Predict the output before running the code.\n",
"- Note one common mistake and how to fix it.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exercise answer scaffold\n",
"def describe(values: list[float]) -> dict[str, float]:\n",
" return {\"min\": min(values), \"max\": max(values)}\n",
"\n",
"describe(sines)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,10 @@
# Experiment Patterns
Use this structure for exploratory and experimental work:
- Title and objective: state the question and the success criteria.
- Setup and reproducibility: import only what you need, set a seed early, and keep configuration in one short cell.
- Plan: list hypotheses, sweeps, and metrics before running code.
- Minimal baseline: start with the smallest runnable example and confirm it runs end-to-end before adding complexity.
- Results and notes: summarize findings in markdown near the relevant code and record key metrics in a small dictionary or table-like structure.
- Next steps: decide whether to continue, pivot, or stop, and capture follow-up ideas as short bullets.
@@ -0,0 +1,17 @@
# Notebook Structure
Jupyter notebooks are JSON documents with this high-level shape:
- `nbformat` and `nbformat_minor`
- `metadata`
- `cells` (a list of markdown and code cells)
When editing `.ipynb` files programmatically:
- Preserve `nbformat` and `nbformat_minor` from the template.
- Keep `cells` as an ordered list; do not reorder unless intentional.
- For code cells, set `execution_count` to `null` when unknown.
- For code cells, set `outputs` to an empty list when scaffolding.
- For markdown cells, keep `cell_type="markdown"` and `metadata={}`.
Prefer scaffolding from the bundled templates or `new_notebook.py` (for example, `$CODEX_HOME/skills/jupyter-notebook/scripts/new_notebook.py`) instead of hand-authoring raw notebook JSON.
@@ -0,0 +1,11 @@
# Quality Checklist
Before delivering a notebook:
- Run it top-to-bottom at least once (or as much as the environment allows).
- Ensure early cells set all required state; avoid hidden state from prior runs.
- Keep outputs tidy. Avoid giant outputs when a short summary works.
- Prefer small tables, key metrics, or short printouts.
- Keep the narrative skimmable. Use headings and short bullets, and avoid long paragraphs.
- Leave helpful TODOs only when necessary, and label them clearly.
- If execution is not possible, call out the risk and how to validate locally.
@@ -0,0 +1,9 @@
# Tutorial Patterns
Use this structure for teaching and walkthroughs:
- Audience, prerequisites, and learning goals: say who it is for, list what they should already know, and state what they will be able to do by the end.
- Outline: provide a short numbered outline so readers can skim.
- Step-by-step flow: pair a short markdown explanation with a small code cell that runs on its own and a brief interpretation of the result.
- Exercises: include at least one exercise that reinforces the key concept and provide an answer scaffold in the next cell.
- Pitfalls and extensions: call out one common mistake and how to fix it, and suggest one optional extension for curious readers.
@@ -0,0 +1,130 @@
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
def slugify(text: str) -> str:
lowered = text.strip().lower()
cleaned = re.sub(r"[^a-z0-9]+", "-", lowered)
collapsed = re.sub(r"-+", "-", cleaned).strip("-")
return collapsed or "notebook"
def find_repo_root(start: Path) -> Path:
for candidate in (start, *start.parents):
if (candidate / ".git").exists():
return candidate
return start
def load_template(skill_dir: Path, kind: str) -> dict[str, Any]:
asset_name = "experiment-template.ipynb" if kind == "experiment" else "tutorial-template.ipynb"
template_path = skill_dir / "assets" / asset_name
if not template_path.exists():
raise SystemExit(f"Missing template: {template_path}")
with template_path.open("r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, dict):
raise SystemExit(f"Unexpected template shape: {template_path}")
return data
def update_title(notebook: dict[str, Any], kind: str, title: str) -> None:
prefix = "Experiment" if kind == "experiment" else "Tutorial"
expected = f"# {prefix}: {title}\n"
cells = notebook.get("cells")
if not isinstance(cells, list) or not cells:
raise SystemExit("Template notebook has no cells")
first_cell = cells[0]
if not isinstance(first_cell, dict) or first_cell.get("cell_type") != "markdown":
raise SystemExit("Template notebook must start with a markdown title cell")
source = first_cell.get("source", [])
if isinstance(source, str):
source_lines = [source]
elif isinstance(source, list):
source_lines = [str(line) for line in source]
else:
source_lines = []
if source_lines:
source_lines[0] = expected
else:
source_lines = [expected]
first_cell["source"] = source_lines
metadata = notebook.setdefault("metadata", {})
if not isinstance(metadata, dict):
raise SystemExit("Notebook metadata must be a mapping")
language_info = metadata.setdefault("language_info", {})
if isinstance(language_info, dict):
language_info.setdefault("name", "python")
language_info.setdefault("version", "3.12")
def default_output(repo_root: Path, title: str) -> Path:
filename = f"{slugify(title)}.ipynb"
return repo_root / "output" / "jupyter-notebook" / filename
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Scaffold a Jupyter notebook for experiments or tutorials.")
parser.add_argument(
"--kind",
choices=["experiment", "tutorial"],
default="experiment",
help="Notebook style to scaffold (default: experiment).",
)
parser.add_argument(
"--title",
required=True,
help="Human-readable notebook title used in the first markdown cell.",
)
parser.add_argument(
"--out",
type=Path,
default=None,
help="Output path for the notebook. Defaults to output/jupyter-notebook/<slug>.ipynb.",
)
parser.add_argument(
"--force",
action="store_true",
help="Overwrite the output file if it already exists.",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
script_path = Path(__file__).resolve()
skill_dir = script_path.parents[1]
repo_root = find_repo_root(skill_dir)
notebook = load_template(skill_dir, args.kind)
update_title(notebook, args.kind, args.title)
out_path = args.out or default_output(repo_root, args.title)
out_path = out_path.resolve()
if out_path.exists() and not args.force:
raise SystemExit(f"Refusing to overwrite existing file without --force: {out_path}")
out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("w", encoding="utf-8") as f:
json.dump(notebook, f, indent=2)
f.write("\n")
print(f"Wrote {out_path} using kind={args.kind}.")
if __name__ == "__main__":
main()
+113
View File
@@ -0,0 +1,113 @@
---
name: microsoft-docs
description: 'Query official Microsoft documentation to find concepts, tutorials, and code examples across Azure, .NET, Agent Framework, Aspire, VS Code, GitHub, and more. Uses Microsoft Learn MCP as the default, with Context7 and Aspire MCP for content that lives outside learn.microsoft.com.'
---
# Microsoft Docs
Research skill for the Microsoft technology ecosystem. Covers learn.microsoft.com and documentation that lives outside it (VS Code, GitHub, Aspire, Agent Framework repos).
---
## Default: Microsoft Learn MCP
Use these tools for **everything on learn.microsoft.com** — Azure, .NET, M365, Power Platform, Agent Framework, Semantic Kernel, Windows, and more. This is the primary tool for the vast majority of Microsoft documentation queries.
| Tool | Purpose |
|------|---------|
| `microsoft_docs_search` | Search learn.microsoft.com — concepts, guides, tutorials, configuration |
| `microsoft_code_sample_search` | Find working code snippets from Learn docs. Pass `language` (`python`, `csharp`, etc.) for best results |
| `microsoft_docs_fetch` | Get full page content from a specific URL (when search excerpts aren't enough) |
Use `microsoft_docs_fetch` after search when you need complete tutorials, all config options, or when search excerpts are truncated.
---
## Exceptions: When to Use Other Tools
The following categories live **outside** learn.microsoft.com. Use the specified tool instead.
### .NET Aspire — Use Aspire MCP Server (preferred) or Context7
Aspire docs live on **aspire.dev**, not Learn. The best tool depends on your Aspire CLI version:
**CLI 13.2+** (recommended) — The Aspire MCP server includes built-in docs search tools:
| MCP Tool | Description |
|----------|-------------|
| `list_docs` | Lists all available documentation from aspire.dev |
| `search_docs` | Weighted lexical search across aspire.dev content |
| `get_doc` | Retrieves a specific document by slug |
These ship in Aspire CLI 13.2 ([PR #14028](https://github.com/dotnet/aspire/pull/14028)). To update: `aspire update --self --channel daily`. Ref: https://davidpine.dev/posts/aspire-docs-mcp-tools/
**CLI 13.1** — The MCP server provides integration lookup (`list_integrations`, `get_integration_docs`) but **not** docs search. Fall back to Context7:
| Library ID | Use for |
|---|---|
| `/microsoft/aspire.dev` | Primary — guides, integrations, CLI reference, deployment |
| `/dotnet/aspire` | Runtime source — API internals, implementation details |
| `/communitytoolkit/aspire` | Community integrations — Go, Java, Node.js, Ollama |
### VS Code — Use Context7
VS Code docs live on **code.visualstudio.com**, not Learn.
| Library ID | Use for |
|---|---|
| `/websites/code_visualstudio` | User docs — settings, features, debugging, remote dev |
| `/websites/code_visualstudio_api` | Extension API — webviews, TreeViews, commands, contribution points |
### GitHub — Use Context7
GitHub docs live on **docs.github.com** and **cli.github.com**.
| Library ID | Use for |
|---|---|
| `/websites/github_en` | Actions, API, repos, security, admin, Copilot |
| `/websites/cli_github` | GitHub CLI (`gh`) commands and flags |
### Agent Framework — Use Learn MCP + Context7
Agent Framework tutorials are on learn.microsoft.com (use `microsoft_docs_search`), but the **GitHub repo** has API-level detail that is often ahead of published docs — particularly DevUI REST API reference, CLI options, and .NET integration.
| Library ID | Use for |
|---|---|
| `/websites/learn_microsoft_en-us_agent-framework` | Tutorials — DevUI guides, tracing, workflow orchestration |
| `/microsoft/agent-framework` | API detail — DevUI REST endpoints, CLI flags, auth, .NET `AddDevUI`/`MapDevUI` |
**DevUI tip:** Query the Learn website source for how-to guides, then the repo source for API-level specifics (endpoint schemas, proxy config, auth tokens).
---
## Context7 Setup
For any Context7 query, resolve the library ID first (one-time per session):
1. Call `mcp_context7_resolve-library-id` with the technology name
2. Call `mcp_context7_query-docs` with the returned library ID and a specific query
---
## Writing Effective Queries
Be specific — include version, intent, and language:
```
# ❌ Too broad
"Azure Functions"
"agent framework"
# ✅ Specific
"Azure Functions Python v2 programming model"
"Cosmos DB partition key design best practices"
"GitHub Actions workflow_dispatch inputs matrix strategy"
"Aspire AddUvicornApp Python FastAPI integration"
"DevUI serve agents tracing OpenTelemetry directory discovery"
"Agent Framework workflow conditional edges branching handoff"
```
Include context:
- **Version** when relevant (`.NET 8`, `Aspire 13`, `VS Code 1.96`)
- **Task intent** (`quickstart`, `tutorial`, `overview`, `limits`, `API reference`)
- **Language** for polyglot docs (`Python`, `TypeScript`, `C#`)
+8
View File
@@ -0,0 +1,8 @@
FROM mcr.microsoft.com/devcontainers/python:3.12
# Install pip for Python 3.12
RUN python -m pip install --upgrade pip
# Copy requirements.txt and install the packages
COPY requirements.txt .
RUN pip install -r requirements.txt
+30
View File
@@ -0,0 +1,30 @@
{
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"
},
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "10.0"
}
},
"hostRequirements": {
"cpus": 4
},
"waitFor": "onCreateCommand",
"updateContentCommand": "python3 -m pip install -r requirements.txt",
"postCreateCommand": "",
"customizations": {
"codespaces": {
"openFiles": []
},
"vscode": {
"extensions": [
"ms-toolsai.jupyter",
"ms-python.python",
"ms-dotnettools.dotnet-interactive-vscode"
]
}
}
}
+29
View File
@@ -0,0 +1,29 @@
# Microsoft Foundry Project (Required for most lessons)
# Your Microsoft Foundry project endpoint
AZURE_AI_PROJECT_ENDPOINT="https://..."
# Model deployment name in your Foundry project (e.g., gpt-4o)
AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o"
# Azure OpenAI (Responses API - used by lessons that call the model directly)
# The Responses API uses the stable /openai/v1/ endpoint - no api_version needed.
# NOTE: GitHub Models is deprecated (retiring July 2026) and does NOT support the
# Responses API. All samples now use Azure OpenAI with the Responses API instead.
AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com"
AZURE_OPENAI_DEPLOYMENT="gpt-4o-mini"
# Optional: use a key instead of Entra ID / AzureCliCredential
AZURE_OPENAI_API_KEY="..."
# Azure AI Search (Required for Lesson 05 - Agentic RAG)
AZURE_SEARCH_SERVICE_ENDPOINT="https://..."
AZURE_SEARCH_API_KEY="..."
# Azure AI Bing Connection (Required for Lesson 08 - Bing grounding workflow)
BING_CONNECTION_ID="..."
# MiniMax (Alternative OpenAI-compatible provider)
# MiniMax offers large-context models (up to 204K tokens) via an OpenAI-compatible API.
# Get your API key from https://platform.minimaxi.com/
MINIMAX_API_KEY="..."
MINIMAX_BASE_URL="https://api.minimax.io/v1"
MINIMAX_MODEL_ID="MiniMax-M3"
+3
View File
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
+38
View File
@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
@@ -0,0 +1,73 @@
---
description: 'Expert .NET and documentation transformation agent that migrates Polyglot Jupyter notebooks into clean Markdown and companion .NET sample code.'
tools: ['runCommands', 'edit/createFile', 'edit/editFiles', 'search', 'usages', 'problems', 'fetch']
name: '.NET-Notebook-Migration-Agent'
model: Auto (copilot)
---
You are a meticulous .NET educational content migration specialist.
You transform a Jupyter notebook (${input:file}) into:
1. A single Markdown file (same base name, .md) with all markdown plus rendered code blocks.
2. (Optional) A scaffolded .NET Single File App (refer to https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/ for info on that format) if C# code is detected.
Core Objectives:
- Preserve instructional flow.
- Normalize headings (first markdown cell becomes H1 if not already).
- Detect language for code fences (python, csharp, bash, json, yaml, etc.).
- Remove execution artifacts (outputs, execution_count).
- Collapse multi-line sources into continuous blocks.
- Trim trailing blank lines.
Language Detection Heuristics (in order):
- If cell has "using " statements, namespaces, or csproj hint => csharp
- If it has "def ", "import ", or "# %%", => python
- If it starts with "#!/bin/bash" or typical shell commands (echo, ls, cat) => bash
- If braces with "class" and "static void" => csharp
Fallback: plaintext
Behavior:
- Read notebook JSON.
- Iterate cells in order.
- For markdown cells: write their source verbatim.
- For code cells: wrap in ```<language> fences.
- Never include outputs, metadata, or empty code cells.
- Ensure a blank line between top-level sections.
- Avoid more than one consecutive blank line.
If C# code detected:
- Create a file alongside named <name>.cs aggregating all C# code cells in original order.
- Use the .NET Single File App format.
- Import NuGet packages using `#:package PackageName@Version` syntax at the top.
- Add the shebang `#!/usr/bin/dotnet run` at the top of the .cs file and make it executable (if on Linux/Unix/MacOS).
- Update the markdown to reference this .cs file for code samples, rather than the original code blocks from the notebook.
File Naming:
- Input: <name>.ipynb
- Output Markdown: <name>.md
- Output .NET sample (if any): <name>.cs
Constraints:
- Do not invent code.
- Do not execute code.
- Keep Markdown pure (no notebook JSON fragments).
- Preserve relative order strictly.
Validation Steps (internal):
1. Parse JSON safely.
2. Count cells; abort if none.
3. Track languages encountered.
4. Confirm at least one markdown or code cell; else emit an error note.
Output:
Primary artifact is the Markdown file replacing the notebook for documentation purposes.
Now perform the migration.
@@ -0,0 +1,21 @@
---
description: 'Cherrypick a file deleted from the given commit between branches using Git.'
mode: 'agent'
---
# Cherry-picking a File Between Branches Using Git
Your goal is to cherry-pick a specific file deleted from a given commit between branches in a Git repository. Use the Git command line interface to accomplish this task.
## Inputs
- **Commit Hash**: $COMMIT - The hash of the commit from which to cherry-pick the file.
- **File Path**: $FILE_PATH - The path of the file to be cherry-picked.
If you don't have the commit hash or file path, please ask the user for these details.
## Instructions
1. **Identify the Commit**: Determine the commit hash from which you want to cherry-pick the file.
2. **Cherry-pick the File**: Use the `git checkout` command to cherry-pick the specific file from the identified commit. If the file doesn't exist in the given commit, find the closest commit where the file exists.
3. **Commit the Changes**: Ask the user to stage and commit the changes to finalize the cherry-pick.
+34
View File
@@ -0,0 +1,34 @@
name: Welcome to the AI Agents for Beginners Course
on:
# Trigger the workflow on new issue
issues:
types: [opened]
permissions:
contents: read
issues: write
jobs:
asses-issue:
runs-on: ubuntu-latest
steps:
- name: Add Label and thanks comment to Issue
uses: actions/github-script@v8
with:
script: |
const issueAuthor = context.payload.sender.login
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs-review']
})
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `👋 Thanks for contributing @${ issueAuthor }! We will review the issue and get back to you soon.`
})
- name: Auto-assign issue
uses: pozil/auto-assign-issue@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
assignees: koreyspace
+34
View File
@@ -0,0 +1,34 @@
name: Welcome to the AI Agents for Beginners
on:
# Trigger the workflow on pull request
pull_request_target:
types: [opened]
permissions:
contents: read
pull-requests: write
jobs:
asses-pull-request:
runs-on: ubuntu-latest
steps:
- name: Add Label and thanks comment to Pull request
uses: actions/github-script@v8
with:
script: |
const issueAuthor = context.payload.sender.login
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs-review']
})
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `👋 Thanks for contributing @${ issueAuthor }! We will review the pull request and get back to you soon.`
})
- name: Auto-assign issue
uses: pozil/auto-assign-issue@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
assignees: koreyspace
+417
View File
@@ -0,0 +1,417 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
.env
.chainlit
.venv
*.DS_Store
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
.coop/
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
venv
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
# Ignore all files in chroma_db directory
05-agentic-rag/code_samples/chroma_db/
# Ignore PNG files in the specified directory
02-explore-agentic-frameworks/code_samples/*.png
# Ignore backup files
*-backup.ipynb
# Ignore Cognee system and data storage
13-agent-memory/.cognee_system
13-agent-memory/.data_storage
+1
View File
@@ -0,0 +1 @@
{}
+36
View File
@@ -0,0 +1,36 @@
#:package Azure.Search.Documents@11.*
#:property PublishAot=false
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
var serviceEndpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_SEARCH_SERVICE_ENDPOINT")!);
var apiKey = Environment.GetEnvironmentVariable("AZURE_SEARCH_API_KEY")!;
var indexName = "sample-index";
var credential = new AzureKeyCredential(apiKey);
var indexClient = new SearchIndexClient(serviceEndpoint, credential);
var fields = new List<SearchField>()
{
new SimpleField("id", SearchFieldDataType.String) { IsKey = true },
new SearchableField("content")
};
var index = new SearchIndex(name: indexName, fields: fields);
var response = await indexClient.CreateOrUpdateIndexAsync(index);
Console.WriteLine($"Index '{response.Value.Name}' ready.");
var searchClient = new SearchClient(serviceEndpoint, indexName, credential);
var documents = new[]
{
new { id = "1", content = "Hello world" },
new { id = "2", content = "Azure Cognitive Search" }
};
var result = await searchClient.UploadDocumentsAsync(documents);
Console.WriteLine($"Uploaded {result.Value.Results.Count} documents to index '{response.Value.Name}'.");
+167
View File
@@ -0,0 +1,167 @@
# Azure AI Search Setup Guide
This guide will help you set up Azure AI Search using the Azure portal. Follow the steps below to create and configure your Azure AI Search service.
## Prerequisites
Before you begin, ensure you have the following:
- An Azure subscription. If you don't have an Azure subscription, you can create a free account at [Azure Free Account](https://azure.microsoft.com/free/?wt.mc_id=studentamb_258691).
## Step 1: Create an Azure Storage Account
1. Follow this instruction, [Create an Azure storage account](https://learn.microsoft.com/azure/storage/common/storage-account-create?tabs=azure-portal), to create a new Azure Storage Account.
**NOTE**: Make sure that the type of Storage Account is Standard General Purpose V2.
## Step 2: Create an Azure AI Search Service
1. Sign in to the [Azure portal](https://portal.azure.com/?wt.mc_id=studentamb_258691).
2. In the left-hand navigation pane, click on **Create a resource**.
3. In the search box, type "Azure AI Search" and select **Azure AI Search** from the list of results.
4. Click the **Create** button.
5. In the **Basics** tab, provide the following information:
- **Subscription**: Select your Azure subscription.
- **Resource group**: Create a new resource group or select an existing one.
- **Resource name**: Enter a unique name for your search service.
- **Region**: Select the region closest to your users.
- **Pricing tier**: Choose a pricing tier that suits your requirements. You can start with the Free tier for testing.
6. Click **Review + create**.
7. Review the settings and click **Create** to create the search service.
## Step 3: Get Started with Azure AI Search
1. Once the deployment is complete, navigate to your search service in the Azure portal.
2. In the search service overview pane, copy the URL. It should look like `https://<service-name>.search.windows.net`.
3. In the Settings > Keys pane, copy the query key.
4. Follow the steps in the [Quickstart guide](https://learn.microsoft.com/azure/search/search-get-started-portal?pivots=import-data-new) page to create an index, upload data, and perform a search query.
## Step 4: Use Azure AI Search Tools
Azure AI Search integrates with various tools to enhance your search capabilities. You can use Azure CLI, Python SDK, .NET SDK and other tools for advanced configurations and operations.
### Using Azure CLI
1. Install the Azure CLI by following the instructions at [Install Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli?wt.mc_id=studentamb_258691).
2. Sign in to Azure CLI using the command:
```bash
az login
```
3. Store both endpoint and API key for Azure AI Search instance to environment variables.
```bash
# zsh/bash
export AZURE_SEARCH_SERVICE_ENDPOINT=$(az search service show -g <resource-group> -n <service-name> --query "endpoint" -o tsv)
export AZURE_SEARCH_API_KEY=$(az search service admin-key list -g <resource-group> --search-service-name <service-name> --query "primaryKey" -o tsv)
```
```powershell
# PowerShell
$env:AZURE_SEARCH_SERVICE_ENDPOINT = az search service show -g <resource-group> -n <service-name> --query "endpoint" -o tsv
$env:AZURE_SEARCH_API_KEY = $(az search service admin-key list -g <resource-group> --search-service-name <service-name> --query "primaryKey" -o tsv)
```
### Using Python SDK
1. Install the Azure Cognitive Search client library for Python:
```bash
pip install azure-search-documents
```
2. Use the following Python code to create an index and upload documents:
```python
import os
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import SearchIndex, SimpleField, edm
service_endpoint = os.getenv("AZURE_SEARCH_SERVICE_ENDPOINT")
api_key = os.getenv("AZURE_SEARCH_API_KEY")
index_name = "sample-index"
credential = AzureKeyCredential(api_key)
index_client = SearchIndexClient(service_endpoint, credential)
fields = [
SimpleField(name="id", type=edm.String, key=True),
SimpleField(name="content", type=edm.String, searchable=True),
]
index = SearchIndex(name=index_name, fields=fields)
index_client.create_index(index)
search_client = SearchClient(service_endpoint, index_name, credential)
documents = [
{"id": "1", "content": "Hello world"},
{"id": "2", "content": "Azure Cognitive Search"}
]
search_client.upload_documents(documents)
```
### Using .NET SDK
1. Run the following command to create an index and upload documents:
```bash
dotnet run ./AzureSearch.cs
```
2. Here's the .NET code of `AzureSearch.cs`:
```csharp
#:package Azure.Search.Documents@11.*
#:property PublishAot=false
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
var serviceEndpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_SEARCH_SERVICE_ENDPOINT")!);
var apiKey = Environment.GetEnvironmentVariable("AZURE_SEARCH_API_KEY")!;
var indexName = "sample-index";
var credential = new AzureKeyCredential(apiKey);
var indexClient = new SearchIndexClient(serviceEndpoint, credential);
var fields = new List<SearchField>()
{
new SimpleField("id", SearchFieldDataType.String) { IsKey = true },
new SearchableField("content")
};
var index = new SearchIndex(name: indexName, fields: fields);
var response = await indexClient.CreateOrUpdateIndexAsync(index);
Console.WriteLine($"Index '{response.Value.Name}' ready.");
var searchClient = new SearchClient(serviceEndpoint, indexName, credential);
var documents = new[]
{
new { id = "1", content = "Hello world" },
new { id = "2", content = "Azure Cognitive Search" }
};
var result = await searchClient.UploadDocumentsAsync(documents);
Console.WriteLine($"Uploaded {result.Value.Results.Count} documents to index '{response.Value.Name}'.");
```
For more detailed information, refer to the following documentation:
- [Create an Azure Cognitive Search service](https://learn.microsoft.com/azure/search/search-create-service-portal?wt.mc_id=studentamb_258691)
- [Get started with Azure Cognitive Search](https://learn.microsoft.com/azure/search/search-get-started-portal?wt.mc_id=studentamb_258691)
- [Azure AI Search Tools](https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/azure-ai-search?tabs=azurecli%2Cpython&pivots=code-examples?wt.mc_id=studentamb_258691)
## Conclusion
You have successfully set up Azure AI Search using the Azure portal and integrated tools. You can now explore more advanced features and capabilities of Azure AI Search to enhance your search solutions.
For further assistance, visit the [Azure Cognitive Search documentation](https://learn.microsoft.com/azure/search/?wt.mc_id=studentamb_258691).
+381
View File
@@ -0,0 +1,381 @@
# Course Setup
## Introduction
This lesson will cover how to run the code samples of this course.
## Join Other Learners and Get Help
Before you begin cloning your repo, join the [AI Agents For Beginners Discord channel](https://aka.ms/ai-agents/discord) to get any help with setup, any questions about the course, or to connect with other learners.
## Clone or Fork this Repo
To begin, please clone or fork the GitHub Repository. This will make your own version of the course material so that you can run, test, and tweak the code!
This can be done by clicking the link to <a href="https://github.com/microsoft/ai-agents-for-beginners/fork" target="_blank">fork the repo</a>
You should now have your own forked version of this course in the following link:
![Forked Repo](./images/forked-repo.png)
### Shallow Clone (recommended for workshop / Codespaces)
>The full repository can be large (~3 GB) when you download full history and all files. If you're only attending the workshop or only need a few lesson folders, a shallow clone (or a sparse clone) avoids most of that download by truncating history and/or skipping blobs.
#### Quick shallow clone — minimal history, all files
Replace `<your-username>` in the below commands with your fork URL (or the upstream URL if you prefer).
To clone only the latest commit history (small download):
```bash|powershell
git clone --depth 1 https://github.com/<your-username>/ai-agents-for-beginners.git
```
To clone a specific branch:
```bash|powershell
git clone --depth 1 --branch <branch-name> https://github.com/<your-username>/ai-agents-for-beginners.git
```
#### Partial (sparse) clone — minimal blobs + only selected folders
This uses partial clone and sparse-checkout (requires Git 2.25+ and recommended modern Git with partial clone support):
```bash|powershell
git clone --depth 1 --filter=blob:none --sparse https://github.com/<your-username>/ai-agents-for-beginners.git
```
Traverse into the repo folder:
```bash|powershell
cd ai-agents-for-beginners
```
Then specify which folders you want (example below shows two folders):
```bash|powershell
git sparse-checkout set 00-course-setup 01-intro-to-ai-agents
```
After cloning and verifying the files, if you only need files and want to free space (no git history), please delete the repository metadata (💀irreversible — you will lose all Git functionality: no commits, pulls, pushes, or history access).
```bash
# zsh/bash
rm -rf .git
```
```powershell
# PowerShell
Remove-Item -Recurse -Force .git
```
#### Using GitHub Codespaces (recommended to avoid local large downloads)
- Create a new Codespace for this repo via the [GitHub UI](https://github.com/codespaces).
- In the terminal of the newly created codespace, run one of the shallow/sparse clone commands above to bring only the lesson folders you need into the Codespace workspace.
- Optional: after cloning inside Codespaces, remove .git to reclaim extra space (see removal commands above).
- Note: If you prefer to open the repo directly in Codespaces (without an extra clone), be aware Codespaces will construct the devcontainer environment and may still provision more than you need. Cloning a shallow copy inside a fresh Codespace gives you more control over disk usage.
#### Tips
- Always replace the clone URL with your fork if you want to edit/commit.
- If you later need more history or files, you can fetch them or adjust sparse-checkout to include additional folders.
## Running the Code
This course offers a series of Jupyter Notebooks that you can run with to get hands-on experience building AI Agents.
The code samples use **Microsoft Agent Framework (MAF)** with the `FoundryChatClient`, which connects to **Microsoft Foundry Agent Service V2** (the Responses API) through **Microsoft Foundry**.
All Python notebooks are labelled `*-python-agent-framework.ipynb`.
## Requirements
- Python 3.12+
- **NOTE**: If you don't have Python3.12 installed, ensure you install it. Then create your venv using python3.12 to ensure the correct versions are installed from the requirements.txt file.
>Example
Create Python venv directory:
```bash|powershell
python -m venv venv
```
Then activate venv environment for:
```bash
# zsh/bash
source venv/bin/activate
```
```dos
# Command Prompt for Windows
venv\Scripts\activate
```
- .NET 10+: For the sample codes using .NET, ensure you install [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. Then, check your installed .NET SDK version:
```bash|powershell
dotnet --list-sdks
```
- **Azure CLI** — Required for authentication. Install from [aka.ms/installazurecli](https://aka.ms/installazurecli).
- **Azure Subscription** — For access to Microsoft Foundry and Microsoft Foundry Agent Service.
- **Microsoft Foundry Project** — A project with a deployed model (e.g., `gpt-4o`). See [Step 1](#step-1-create-a-microsoft-foundry-project) below.
We have included a `requirements.txt` file in the root of this repository that contains all the required Python packages to run the code samples.
You can install them by running the following command in your terminal at the root of the repository:
```bash|powershell
pip install -r requirements.txt
```
We recommend creating a Python virtual environment to avoid any conflicts and issues.
## Setup VSCode
Make sure that you are using the right version of Python in VSCode.
![image](https://github.com/user-attachments/assets/a85e776c-2edb-4331-ae5b-6bfdfb98ee0e)
## Set Up Microsoft Foundry and Microsoft Foundry Agent Service
### Step 1: Create a Microsoft Foundry Project
You need an Microsoft Foundry **hub** and **project** with a deployed model to run the notebooks.
1. Go to [ai.azure.com](https://ai.azure.com) and sign in with your Azure account.
2. Create a **hub** (or use an existing one). See: [Hub resources overview](https://learn.microsoft.com/azure/ai-foundry/concepts/ai-resources).
3. Inside the hub, create a **project**.
4. Deploy a model (e.g., `gpt-4o`) from **Models + Endpoints** → **Deploy model**.
### Step 2: Retrieve Your Project Endpoint and Model Deployment Name
From your project in the Microsoft Foundry portal:
- **Project Endpoint** — Go to the **Overview** page and copy the endpoint URL.
![Project Connection String](./images/project-endpoint.png)
- **Model Deployment Name** — Go to **Models + Endpoints**, select your deployed model, and note the **Deployment name** (e.g., `gpt-4o`).
### Step 3: Sign in to Azure with `az login`
All notebooks use **`AzureCliCredential`** for authentication — no API keys to manage. This requires you to be signed in via the Azure CLI.
1. **Install the Azure CLI** if you haven't already: [aka.ms/installazurecli](https://aka.ms/installazurecli)
2. **Sign in** by running:
```bash|powershell
az login
```
Or if you're in a remote/Codespace environment without a browser:
```bash|powershell
az login --use-device-code
```
3. **Select your subscription** if prompted — choose the one containing your Foundry project.
4. **Verify** you're signed in:
```bash|powershell
az account show
```
> **Why `az login`?** The notebooks authenticate using `AzureCliCredential` from the `azure-identity` package. This means your Azure CLI session provides the credentials — no API keys or secrets in your `.env` file. This is a [security best practice](https://learn.microsoft.com/azure/developer/ai/keyless-connections).
### Step 4: Create Your `.env` File
Copy the example file:
```bash
# zsh/bash
cp .env.example .env
```
```powershell
# PowerShell
Copy-Item .env.example .env
```
Open `.env` and fill in these two values:
```env
AZURE_AI_PROJECT_ENDPOINT=https://<your-project>.services.ai.azure.com/api/projects/<your-project-id>
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
```
| Variable | Where to find it |
|----------|-----------------|
| `AZURE_AI_PROJECT_ENDPOINT` | Foundry portal → your project → **Overview** page |
| `AZURE_AI_MODEL_DEPLOYMENT_NAME` | Foundry portal → **Models + Endpoints** → your deployed model's name |
That's it for most lessons! The notebooks will authenticate automatically through your `az login` session.
### Step 5: Install Python Dependencies
```bash|powershell
pip install -r requirements.txt
```
We recommend running this inside the virtual environment you created earlier.
## Additional Setup for Lesson 5 (Agentic RAG)
Lesson 5 uses **Azure AI Search** for retrieval-augmented generation. If you plan to run that lesson, add these variables to your `.env` file:
| Variable | Where to find it |
|----------|-----------------|
| `AZURE_SEARCH_SERVICE_ENDPOINT` | Azure portal → your **Azure AI Search** resource → **Overview** → URL |
| `AZURE_SEARCH_API_KEY` | Azure portal → your **Azure AI Search** resource → **Settings** → **Keys** → primary admin key |
## Additional Setup for Lessons that Call Azure OpenAI Directly (Lessons 6 and 8)
Some notebooks in lessons 6 and 8 call **Azure OpenAI** directly (using the **Responses API**) instead of going through a Microsoft Foundry project. These samples previously used GitHub Models, which is deprecated (retiring July 2026) and does not support the Responses API. If you plan to run those samples, add these variables to your `.env` file:
| Variable | Where to find it |
|----------|-----------------|
| `AZURE_OPENAI_ENDPOINT` | Azure portal → your **Azure OpenAI** resource → **Keys and Endpoint** → Endpoint (e.g. `https://<your-resource>.openai.azure.com`) |
| `AZURE_OPENAI_DEPLOYMENT` | The name of your deployed model (e.g. `gpt-4o-mini`) that supports the Responses API |
| `AZURE_OPENAI_API_KEY` | Optional — only if you use key-based auth instead of `az login` / Entra ID |
> The Responses API uses the stable `/openai/v1/` endpoint, so no `api-version` is required. Sign in with `az login` to use keyless Entra ID authentication.
## Alternative Provider: MiniMax (OpenAI-Compatible)
[MiniMax](https://platform.minimaxi.com/) provides large-context models (up to 204K tokens) through an OpenAI-compatible API. Since the Microsoft Agent Framework's `OpenAIChatClient` works with any OpenAI-compatible endpoint, you can use MiniMax as a drop-in alternative to Azure OpenAI or OpenAI.
Add these variables to your `.env` file:
| Variable | Where to find it |
|----------|-----------------|
| `MINIMAX_API_KEY` | [MiniMax Platform](https://platform.minimaxi.com/) → API Keys |
| `MINIMAX_BASE_URL` | Use `https://api.minimax.io/v1` (default value) |
| `MINIMAX_MODEL_ID` | Model name to use (e.g., `MiniMax-M3`) |
**Example models**: `MiniMax-M3` (recommended), `MiniMax-M2.7`, `MiniMax-M2.7-highspeed` (faster responses). Model names and availability can change over time, and access to a given model may depend on your account or region — check the [MiniMax Platform](https://platform.minimaxi.com/) for the current list. If `MiniMax-M3` isn't available to your account, set `MINIMAX_MODEL_ID` to a model you have access to (e.g. `MiniMax-M2.7`).
The code samples that use `OpenAIChatClient` (e.g., Lesson 14 hotel booking workflow) will automatically detect and use your MiniMax configuration when `MINIMAX_API_KEY` is set.
## Alternative Provider: Foundry Local (Run Models On-Device)
[Foundry Local](https://foundrylocal.ai) is a lightweight runtime that downloads, manages, and serves language models **entirely on your own machine** through an OpenAI-compatible API — no cloud, no Azure subscription, and no API keys. It's a great option for offline development, experimenting without incurring cloud costs, or keeping data on-device.
Because the Microsoft Agent Framework's `OpenAIChatClient` works with any OpenAI-compatible endpoint, Foundry Local is a drop-in local alternative to Azure OpenAI.
**1. Install Foundry Local**
```bash
# Windows
winget install Microsoft.FoundryLocal
# macOS
brew install foundrylocal
```
**2. Download and run a model** (this also starts the local service):
```bash
foundry model list # see available models
foundry model run phi-4-mini
```
**3. Install the Python SDK** used to discover the local endpoint:
```bash
pip install foundry-local-sdk
```
**4. Point the Microsoft Agent Framework at your local model:**
```python
from foundry_local import FoundryLocalManager
from agent_framework.openai import OpenAIChatClient
# Downloads (if needed) and serves the model locally, then discovers the endpoint/port.
manager = FoundryLocalManager("phi-4-mini")
chat_client = OpenAIChatClient(
base_url=manager.endpoint, # e.g. http://localhost:<port>/v1
api_key=manager.api_key, # always "not-required" for Foundry Local
model_id=manager.get_model_info("phi-4-mini").id,
)
agent = chat_client.as_agent(
name="LocalAgent",
instructions="You are a helpful assistant running fully on-device.",
)
```
> **Note:** Foundry Local exposes an OpenAI-compatible **Chat Completions** endpoint. Use it for local development and offline scenarios. For the full **Responses API** feature set (stateful conversations, deep tool orchestration, and agent-style development), target **Azure OpenAI** or a **Microsoft Foundry** project as shown in the lessons. See the [Foundry Local documentation](https://foundrylocal.ai) for the current model catalog and platform support.
## Additional Setup for Lesson 8 (Bing Grounding Workflow)
The conditional workflow notebook in lesson 8 uses **Bing grounding** via Microsoft Foundry. If you plan to run that sample, add this variable to your `.env` file:
| Variable | Where to find it |
|----------|-----------------|
| `BING_CONNECTION_ID` | Microsoft Foundry portal → your project → **Management** → **Connected resources** → your Bing connection → copy the connection ID |
## Troubleshooting
### SSL Certificate Verification Errors on macOS
If you are on macOS and encounter an error like:
```plaintext
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain
```
This is a known issue with Python on macOS where the system SSL certificates are not automatically trusted. Try the following solutions in order:
**Option 1: Run Python's Install Certificates script (recommended)**
```bash
# Replace 3.XX with your installed Python version (e.g., 3.12 or 3.13):
/Applications/Python\ 3.XX/Install\ Certificates.command
```
**Option 2: Use `connection_verify=False` in your notebook (for GitHub Models notebooks only)**
In the Lesson 6 notebook (`06-building-trustworthy-agents/code_samples/06-system-message-framework.ipynb`), a commented-out workaround is already included. Uncomment `connection_verify=False` when creating the client:
```python
client = ChatCompletionsClient(
endpoint=endpoint,
credential=AzureKeyCredential(token),
connection_verify=False, # Disable SSL verification if you encounter certificate errors
)
```
> **⚠️ Warning:** Disabling SSL verification (`connection_verify=False`) reduces security by skipping certificate validation. Use this only as a temporary workaround in development environments, never in production.
**Option 3: Install and use `truststore`**
```bash
pip install truststore
```
Then add the following at the top of your notebook or script before making any network calls:
```python
import truststore
truststore.inject_into_ssl()
```
## Stuck Somewhere?
If you have any issues running this setup, hop into our <a href="https://discord.gg/kzRShWzttr" target="_blank">Azure AI Community Discord</a> or <a href="https://github.com/microsoft/ai-agents-for-beginners/issues?WT.mc_id=academic-105485-koreyst" target="_blank">create an issue</a>.
## Next Lesson
You are now ready to run the code for this course. Happy learning more about the world of AI Agents!
[Introduction to AI Agents and Agent Use Cases](../01-intro-to-ai-agents/README.md)
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

+142
View File
@@ -0,0 +1,142 @@
[![Intro to AI Agents](./images/lesson-1-thumbnail.png)](https://youtu.be/3zgm60bXmQk?si=QA4CW2-cmul5kk3D)
> _(Click the image above to watch the video for this lesson)_
# Introduction to AI Agents and Agent Use Cases
Welcome to the **AI Agents for Beginners** course! This course gives you the foundational knowledge — and real working code — to start building AI Agents from scratch.
Come say hi in the <a href="https://discord.gg/kzRShWzttr" target="_blank">Azure AI Discord Community</a> — it's full of learners and AI builders who are happy to answer questions.
Before we jump into building, let's make sure we actually understand what an AI Agent *is* and when it makes sense to use one.
---
## Introduction
This lesson covers:
- What AI Agents are, and the different types that exist
- Which kinds of tasks AI Agents are best suited for
- The core building blocks you'll use when designing an Agentic solution
## Learning Goals
By the end of this lesson, you should be able to:
- Explain what an AI Agent is and how it's different from a regular AI solution
- Know when to reach for an AI Agent (and when not to)
- Sketch out a basic Agentic solution design for a real-world problem
---
## Defining AI Agents and Types of AI Agents
### What are AI Agents?
Here's a simple way to think about it:
> **AI Agents are systems that let Large Language Models (LLMs) actually *do things* — by giving them tools and knowledge to act on the world, not just respond to prompts.**
Let's unpack that a bit:
- **System** — An AI Agent isn't just one thing. It's a collection of parts working together. At its core, every agent has three pieces:
- **Environment** — The space the agent works in. For a travel booking agent, this would be the booking platform itself.
- **Sensors** — How the agent reads the current state of its environment. Our travel agent might check hotel availability or flight prices.
- **Actuators** — How the agent takes action. The travel agent might book a room, send a confirmation, or cancel a reservation.
![What Are AI Agents?](./images/what-are-ai-agents.png)
- **Large Language Models** — Agents existed before LLMs, but LLMs are what make modern agents so powerful. They can understand natural language, reason about context, and turn a vague user request into a concrete plan of action.
- **Perform Actions** — Without an agent system, an LLM just generates text. Inside an agent system, the LLM can actually *execute* steps — searching a database, calling an API, sending a message.
- **Access to Tools** — What tools the agent can use depends on (1) the environment it's running in and (2) what the developer chose to give it. A travel agent might be able to search flights but not edit customer records — it's all about what you wire up.
- **Memory + Knowledge** — Agents can have short-term memory (the current conversation) and long-term memory (a customer database, past interactions). The travel agent might "remember" that you prefer window seats.
---
### The Different Types of AI Agents
Not all agents are built the same. Here's a breakdown of the main types, using a travel booking agent as the running example:
| **Agent Type** | **What It Does** | **Travel Agent Example** |
|---|---|---|
| **Simple Reflex Agents** | Follows hard-coded rules — no memory, no planning. | Sees a complaint email → forwards it to customer service. That's it. |
| **Model-Based Reflex Agents** | Keeps an internal model of the world and updates it as things change. | Tracks historical flight prices and flags routes that are suddenly expensive. |
| **Goal-Based Agents** | Has a goal in mind and figures out how to reach it step by step. | Books a full trip (flights, car, hotel) starting from your current location to get you to your destination. |
| **Utility-Based Agents** | Doesn't just find *a* solution — finds the *best* one by weighing tradeoffs. | Balances cost vs. convenience to find the trip that scores highest for your preferences. |
| **Learning Agents** | Gets better over time by learning from feedback. | Adjusts future booking recommendations based on post-trip survey results. |
| **Hierarchical Agents** | A high-level agent breaks work into subtasks and delegates to lower-level agents. | A "cancel trip" request gets split into: cancel flight, cancel hotel, cancel car rental — each handled by a sub-agent. |
| **Multi-Agent Systems (MAS)** | Multiple independent agents working together (or competing). | Cooperative: separate agents handle hotels, flights, and entertainment. Competitive: multiple agents compete to fill hotel rooms at the best price. |
---
## When to Use AI Agents
Just because you *can* use an AI Agent doesn't mean you always *should*. Here are the situations where agents really shine:
![When to use AI Agents?](./images/when-to-use-ai-agents.png)
- **Open-Ended Problems** — When the steps to solve a problem can't be pre-programmed. You need the LLM to figure out the path dynamically.
- **Multi-Step Processes** — Tasks that require using tools across several turns, not just a single lookup or generation.
- **Improvement Over Time** — When you want the system to get smarter based on user feedback or environmental signals.
We'll dig deeper into when (and when *not*) to use AI Agents in the **Building Trustworthy AI Agents** lesson later in the course.
---
## Basics of Agentic Solutions
### Agent Development
The first thing you do when building an agent is define *what it can do* — its tools, actions, and behaviors.
In this course, we use the **Microsoft Foundry Agent Service** as our main platform. It supports:
- Models from providers like OpenAI, Mistral, and Meta (Llama)
- Licensed data from providers like Tripadvisor
- Standardized OpenAPI 3.0 tool definitions
### Agentic Patterns
You communicate with LLMs through prompts. With agents, you can't always hand-craft every prompt manually — the agent needs to take action across many steps. That's where **Agentic Patterns** come in. They're reusable strategies for prompting and orchestrating LLMs in a more scalable, reliable way.
This course is structured around the most common and useful agentic patterns.
### Agentic Frameworks
Agentic Frameworks give developers ready-made templates, tools, and infrastructure for building agents. They make it easier to:
- Wire up tools and capabilities
- Observe what the agent is doing (and debug when it goes wrong)
- Collaborate across multiple agents
In this course, we focus on the **Microsoft Agent Framework (MAF)** for building production-ready agents.
---
## Code Samples
Ready to see it in action? Here are the code samples for this lesson:
- 🐍 Python: [Agent Framework](./code_samples/01-python-agent-framework.ipynb)
- 🔷 .NET: [Agent Framework](./code_samples/01-dotnet-agent-framework.md)
---
## Got Questions?
Join the [Microsoft Foundry Discord](https://discord.com/invite/ATgtXmAS5D) to connect with other learners, attend office hours, and get your AI Agent questions answered by the community.
---
## Previous Lesson
[Course Setup](../00-course-setup/README.md)
## Next Lesson
[Exploring Agentic Frameworks](../02-explore-agentic-frameworks/README.md)
@@ -0,0 +1,72 @@
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.4.1
#:package Microsoft.Agents.AI.OpenAI@1.1.0
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// Tool Function: Random Destination Generator
// This static method will be available to the agent as a callable tool
// The [Description] attribute helps the AI understand when to use this function
// This demonstrates how to create custom tools for AI agents
[Description("Provides a random vacation destination.")]
static string GetRandomDestination()
{
// List of popular vacation destinations around the world
// The agent will randomly select from these options
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
// Generate random index and return selected destination
// Uses System.Random for simple random selection
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Create AI Agent with Travel Planning Capabilities
// Initialize complete agent pipeline: OpenAI client → Chat client → AI agent
// Configure agent with name, instructions, and available tools
// The agent can now plan trips using the GetRandomDestination function
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
instructions: "You are a helpful AI Agent that can help plan vacations for customers at random destinations",
tools: [AIFunctionFactory.Create(GetRandomDestination)]
);
// Execute Agent: Plan a Day Trip
// Run the agent with streaming enabled for real-time response display
// Shows the agent's thinking and response as it generates the content
// Provides better user experience with immediate feedback
await foreach (var update in agent.RunStreamingAsync("Plan me a day trip"))
{
await Task.Delay(10);
Console.Write(update);
}
@@ -0,0 +1,172 @@
# 🌍 AI Travel Agent with Microsoft Agent Framework (.NET)
## 📋 Scenario Overview
This example demonstrates how to build an intelligent travel planning agent using the Microsoft Agent Framework for .NET. The agent can automatically generate personalized day-trip itineraries for random destinations around the world.
### Key Capabilities:
- 🎲 **Random Destination Selection**: Uses a custom tool to pick vacation spots
- 🗺️ **Intelligent Trip Planning**: Creates detailed day-by-day itineraries
- 🔄 **Real-time Streaming**: Supports both immediate and streaming responses
- 🛠️ **Custom Tool Integration**: Demonstrates how to extend agent capabilities
## 🔧 Technical Architecture
### Core Technologies
- **Microsoft Agent Framework**: Latest .NET implementation for AI agent development
- **Azure OpenAI (Responses API)**: Uses the Azure OpenAI Responses API for model inference
- **Azure Identity**: Secure sign-in via `AzureCliCredential` (`az login`)
- **Secure Configuration**: Environment-based endpoint management
### Key Components
1. **AIAgent**: The main agent orchestrator that handles conversation flow
2. **Custom Tools**: `GetRandomDestination()` function available to the agent
3. **Responses Client**: Azure OpenAI Responses-based conversation interface
4. **Streaming Support**: Real-time response generation capabilities
### Integration Pattern
```mermaid
graph LR
A[User Request] --> B[AI Agent]
B --> C[Azure OpenAI (Responses API)]
B --> D[GetRandomDestination Tool]
C --> E[Travel Itinerary]
D --> E
```
## 🚀 Getting Started
### Prerequisites
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or higher
- An [Azure subscription](https://azure.microsoft.com/free/) with an Azure OpenAI resource and a model deployment
- The [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) — sign in with `az login`
### Required Environment Variables
```bash
# zsh/bash
export AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
export AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
# Then sign in so AzureCliCredential can get a token
az login
```
```powershell
# PowerShell
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com"
$env:AZURE_OPENAI_DEPLOYMENT = "gpt-4o-mini"
# Then sign in so AzureCliCredential can get a token
az login
```
### Sample Code
To run the code example,
```bash
# zsh/bash
chmod +x ./01-dotnet-agent-framework.cs
./01-dotnet-agent-framework.cs
```
Or using the dotnet CLI:
```bash
dotnet run ./01-dotnet-agent-framework.cs
```
See [`01-dotnet-agent-framework.cs`](./01-dotnet-agent-framework.cs) for the complete code.
```csharp
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@9.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// Tool Function: Random Destination Generator
// This static method will be available to the agent as a callable tool
// The [Description] attribute helps the AI understand when to use this function
// This demonstrates how to create custom tools for AI agents
[Description("Provides a random vacation destination.")]
static string GetRandomDestination()
{
// List of popular vacation destinations around the world
// The agent will randomly select from these options
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
// Generate random index and return selected destination
// Uses System.Random for simple random selection
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Create AI Agent with Travel Planning Capabilities
// Get the Responses client for the specified deployment and create the AI agent
// Configure agent with travel planning instructions and random destination tool
// The agent can now plan trips using the GetRandomDestination function
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
instructions: "You are a helpful AI Agent that can help plan vacations for customers at random destinations",
tools: [AIFunctionFactory.Create(GetRandomDestination)]
);
// Execute Agent: Plan a Day Trip
// Run the agent with streaming enabled for real-time response display
// Shows the agent's thinking and response as it generates the content
// Provides better user experience with immediate feedback
await foreach (var update in agent.RunStreamingAsync("Plan me a day trip"))
{
await Task.Delay(10);
Console.Write(update);
}
```
## 🎓 Key Takeaways
1. **Agent Architecture**: The Microsoft Agent Framework provides a clean, type-safe approach to building AI agents in .NET
2. **Tool Integration**: Functions decorated with `[Description]` attributes become available tools for the agent
3. **Configuration Management**: Environment variables and secure credential handling follow .NET best practices
4. **Azure OpenAI Responses API**: The agent uses the Azure OpenAI Responses API through the Azure.AI.OpenAI SDK
## 🔗 Additional Resources
- [Microsoft Agent Framework Documentation](https://learn.microsoft.com/agent-framework)
- [Azure OpenAI in Microsoft Foundry](https://learn.microsoft.com/azure/ai-services/openai/)
- [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai)
- [.NET Single File Apps](https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app)
@@ -0,0 +1,208 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f4cf9593",
"metadata": {},
"source": [
"# Lesson 01 - Introduction to AI Agents\n",
"\n",
"Welcome to the first lesson in the **AI Agents for Beginners** course!\n",
"\n",
"An **AI agent** is a program that uses a large language model (LLM) as its reasoning engine and can take *actions* in the real world — calling APIs, querying databases, or running code — to accomplish a goal on behalf of a user.\n",
"\n",
"In this notebook you will build your first agent: a **Travel Agent** that recommends vacation destinations. Along the way you will learn how to:\n",
"\n",
"1. Connect to Microsoft Foundry Agent Service using the **Microsoft Agent Framework**.\n",
"2. Give the agent a **tool** — a plain Python function it can call.\n",
"3. Run the agent and inspect its response.\n",
"4. Stream the agent's response token-by-token."
]
},
{
"cell_type": "markdown",
"id": "b1e2c3d4",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"Before running this notebook, make sure you have:\n",
"\n",
"1. **A Microsoft Foundry project** with a deployed chat model (e.g. `gpt-4o-mini`).\n",
"2. **Logged in with the Azure CLI** — run `az login` in your terminal.\n",
"3. **Set the required environment variables:**\n",
" - `AZURE_AI_PROJECT_ENDPOINT` — your Microsoft Foundry project endpoint.\n",
" - `AZURE_AI_MODEL_DEPLOYMENT_NAME` — the name of your deployed model.\n",
"\n",
"The cell below installs the Python packages you need."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fda5fa0a",
"metadata": {},
"outputs": [],
"source": [
"%pip install agent-framework azure-ai-projects azure-identity -q"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c0df8a52",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger(\"agent_framework.foundry\").setLevel(logging.ERROR)\n",
"\n",
"import os\n",
"import dotenv\n",
"from agent_framework.foundry import FoundryChatClient\n",
"from azure.identity import AzureCliCredential\n",
"from agent_framework import tool\n",
"\n",
"dotenv.load_dotenv(dotenv.find_dotenv())\n",
"\n",
"endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
"model = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
"\n",
"if not endpoint or not model:\n",
" raise ValueError(\n",
" \"Missing required environment variables. \"\n",
" \"Please set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME in your .env file.\"\n",
" )\n",
"\n",
"provider = FoundryChatClient(\n",
" project_endpoint=endpoint,\n",
" model=model,\n",
" credential=AzureCliCredential()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e5f6a7b8",
"metadata": {},
"source": [
"## Creating Your First Agent\n",
"\n",
"An agent needs two things:\n",
"\n",
"- **Instructions** that tell it *who it is* and *how to behave* (a system prompt).\n",
"- **Tools** — Python functions decorated with `@tool` that the agent can call to retrieve information or perform actions.\n",
"\n",
"Below we define a simple tool that returns a list of popular vacation destinations. The agent will use this tool when a user asks for travel recommendations."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6507f83",
"metadata": {},
"outputs": [],
"source": [
"@tool(approval_mode=\"never_require\")\n",
"def get_destinations() -> list[str]:\n",
" \"\"\"Get a list of popular vacation destinations.\"\"\"\n",
" return [\n",
" \"Barcelona\",\n",
" \"Paris\",\n",
" \"Berlin\",\n",
" \"Tokyo\",\n",
" \"Sydney\",\n",
" \"New York City\",\n",
" \"Cairo\",\n",
" \"Cape Town\",\n",
" \"Rio de Janeiro\",\n",
" \"Bali\",\n",
" ]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf5a4800",
"metadata": {},
"outputs": [],
"source": [
"agent = provider.as_agent(\n",
" name=\"TravelAgent\",\n",
" instructions=(\n",
" \"You are a helpful travel agent. Help users find their perfect vacation \"\n",
" \"destination based on their preferences. Use the get_destinations tool \"\n",
" \"to see available destinations.\"\n",
" ),\n",
" tools=[get_destinations],\n",
")\n",
"\n",
"response = await agent.run(\n",
" \"I'm looking for a warm beach destination. What do you recommend?\"\n",
")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "d9e0f1a2",
"metadata": {},
"source": [
"## Streaming Responses\n",
"\n",
"For a more interactive experience you can **stream** the agent's response. Instead of waiting for the full reply, the agent yields text chunks as they are generated. This is especially useful in chat interfaces where you want to display output in real time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "772e9481",
"metadata": {},
"outputs": [],
"source": [
"async for chunk in agent.run(\n",
" \"Tell me about Tokyo as a travel destination\", stream=True\n",
"):\n",
" print(chunk, end=\"\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "a3b4c5d6",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"In this lesson you learned how to:\n",
"\n",
"- **Create a provider** that connects to Microsoft Foundry Agent Service via `FoundryChatClient`.\n",
"- **Define a tool** using the `@tool` decorator so the agent can call your Python functions.\n",
"- **Run the agent** with a user message and print its response.\n",
"- **Stream responses** for real-time output.\n",
"\n",
"In the next lesson we will explore agentic frameworks in more depth and learn how to give agents more powerful tools and multi-step reasoning capabilities."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+410
View File
@@ -0,0 +1,410 @@
[![Exploring AI Agent Frameworks](./images/lesson-2-thumbnail.png)](https://youtu.be/ODwF-EZo_O8?si=1xoy_B9RNQfrYdF7)
> _(Click the image above to view video of this lesson)_
# Explore AI Agent Frameworks
AI agent frameworks are software platforms designed to simplify the creation, deployment, and management of AI agents. These frameworks provide developers with pre-built components, abstractions, and tools that streamline the development of complex AI systems.
These frameworks help developers focus on the unique aspects of their applications by providing standardized approaches to common challenges in AI agent development. They enhance scalability, accessibility, and efficiency in building AI systems.
## Introduction
This lesson will cover:
- What are AI Agent Frameworks and what do they enable developers to achieve?
- How can teams use these to quickly prototype, iterate, and improve their agents capabilities?
- What are the differences between the frameworks and tools created by Microsoft (<a href="https://aka.ms/ai-agents-beginners/ai-agent-service" target="_blank">Microsoft Foundry Agent Service</a> and the <a href="https://learn.microsoft.com/azure/ai-services/openai/how-to/responses" target="_blank">Microsoft Agent Framework</a>)?
- Can I integrate my existing Azure ecosystem tools directly, or do I need standalone solutions?
- What is Microsoft Foundry Agent Service and how is this helping me?
## Learning goals
The goals of this lesson are to help you understand:
- The role of AI Agent Frameworks in AI development.
- How to leverage AI Agent Frameworks to build intelligent agents.
- Key capabilities enabled by AI Agent Frameworks.
- The differences between the Microsoft Agent Framework and Microsoft Foundry Agent Service.
## What are AI Agent Frameworks and what do they enable developers to do?
Traditional AI Frameworks can help you integrate AI into your apps and make these apps better in the following ways:
- **Personalization**: AI can analyze user behavior and preferences to provide personalized recommendations, content, and experiences.
Example: Streaming services like Netflix use AI to suggest movies and shows based on viewing history, enhancing user engagement and satisfaction.
- **Automation and Efficiency**: AI can automate repetitive tasks, streamline workflows, and improve operational efficiency.
Example: Customer service apps use AI-powered chatbots to handle common inquiries, reducing response times and freeing up human agents for more complex issues.
- **Enhanced User Experience**: AI can improve the overall user experience by providing intelligent features such as voice recognition, natural language processing, and predictive text.
Example: Virtual assistants like Siri and Google Assistant use AI to understand and respond to voice commands, making it easier for users to interact with their devices.
### That all sounds great right, so why do we need the AI Agent Framework?
AI Agent frameworks represent something more than just AI frameworks. They are designed to enable the creation of intelligent agents that can interact with users, other agents, and the environment to achieve specific goals. These agents can exhibit autonomous behavior, make decisions, and adapt to changing conditions. Let's look at some key capabilities enabled by AI Agent Frameworks:
- **Agent Collaboration and Coordination**: Enable the creation of multiple AI agents that can work together, communicate, and coordinate to solve complex tasks.
- **Task Automation and Management**: Provide mechanisms for automating multi-step workflows, task delegation, and dynamic task management among agents.
- **Contextual Understanding and Adaptation**: Equip agents with the ability to understand context, adapt to changing environments, and make decisions based on real-time information.
So in summary, agents allow you to do more, to take automation to the next level, to create more intelligent systems that can adapt and learn from their environment.
## How to quickly prototype, iterate, and improve the agents capabilities?
This is a fast-moving landscape, but there are some things that are common across most AI Agent Frameworks that can help you quickly prototype and iterate namely module components, collaborative tools, and real-time learning. Let's dive into these:
- **Use Modular Components**: AI SDKs offer pre-built components such as AI and Memory connectors, function calling using natural language or code plugins, prompt templates, and more.
- **Leverage Collaborative Tools**: Design agents with specific roles and tasks, enabling them to test and refine collaborative workflows.
- **Learn in Real-Time**: Implement feedback loops where agents learn from interactions and adjust their behavior dynamically.
### Use Modular Components
SDKs like the Microsoft Agent Framework offer pre-built components such as AI connectors, tool definitions, and agent management.
**How teams can use these**: Teams can quickly assemble these components to create a functional prototype without starting from scratch, allowing for rapid experimentation and iteration.
**How it works in practice**: You can use a pre-built parser to extract information from user input, a memory module to store and retrieve data, and a prompt generator to interact with users, all without having to build these components from scratch.
**Example code**. Let's look at an example of how you can use the Microsoft Agent Framework with `FoundryChatClient` to have the model respond to user input with tool calling:
``` python
# Microsoft Agent Framework Python Example
import asyncio
import os
from agent_framework import tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
# Define a sample tool function to book travel
@tool(approval_mode="never_require")
def book_flight(date: str, location: str) -> str:
"""Book travel given location and date."""
return f"Travel was booked to {location} on {date}"
async def main():
provider = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
agent = provider.as_agent(
name="travel_agent",
instructions="Help the user book travel. Use the book_flight tool when ready.",
tools=[book_flight],
)
response = await agent.run("I'd like to go to New York on January 1, 2025")
print(response)
# Example output: Your flight to New York on January 1, 2025, has been successfully booked. Safe travels! ✈️🗽
if __name__ == "__main__":
asyncio.run(main())
```
What you can see from this example is how you can leverage a pre-built parser to extract key information from user input, such as the origin, destination, and date of a flight booking request. This modular approach allows you to focus on the high-level logic.
### Leverage Collaborative Tools
Frameworks like the Microsoft Agent Framework facilitate the creation of multiple agents that can work together.
**How teams can use these**: Teams can design agents with specific roles and tasks, enabling them to test and refine collaborative workflows and improve overall system efficiency.
**How it works in practice**: You can create a team of agents where each agent has a specialized function, such as data retrieval, analysis, or decision-making. These agents can communicate and share information to achieve a common goal, such as answering a user query or completing a task.
**Example code (Microsoft Agent Framework)**:
```python
# Creating multiple agents that work together using the Microsoft Agent Framework
import os
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
provider = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
# Data Retrieval Agent
agent_retrieve = provider.as_agent(
name="dataretrieval",
instructions="Retrieve relevant data using available tools.",
tools=[retrieve_tool],
)
# Data Analysis Agent
agent_analyze = provider.as_agent(
name="dataanalysis",
instructions="Analyze the retrieved data and provide insights.",
tools=[analyze_tool],
)
# Run agents in sequence on a task
retrieval_result = await agent_retrieve.run("Retrieve sales data for Q4")
analysis_result = await agent_analyze.run(f"Analyze this data: {retrieval_result}")
print(analysis_result)
```
What you see in the previous code is how you can create a task that involves multiple agents working together to analyze data. Each agent performs a specific function, and the task is executed by coordinating the agents to achieve the desired outcome. By creating dedicated agents with specialized roles, you can improve task efficiency and performance.
### Learn in Real-Time
Advanced frameworks provide capabilities for real-time context understanding and adaptation.
**How teams can use these**: Teams can implement feedback loops where agents learn from interactions and adjust their behavior dynamically, leading to continuous improvement and refinement of capabilities.
**How it works in practice**: Agents can analyze user feedback, environmental data, and task outcomes to update their knowledge base, adjust decision-making algorithms, and improve performance over time. This iterative learning process enables agents to adapt to changing conditions and user preferences, enhancing overall system effectiveness.
## What are the differences between the Microsoft Agent Framework and Microsoft Foundry Agent Service?
There are many ways to compare these approaches, but let's look at some key differences in terms of their design, capabilities, and target use cases:
## Microsoft Agent Framework (MAF)
The Microsoft Agent Framework provides a streamlined SDK for building AI agents using `FoundryChatClient`. It enables developers to create agents that leverage Azure OpenAI models with built-in tool calling, conversation management, and enterprise-grade security through Azure identity.
**Use Cases**: Building production-ready AI agents with tool use, multi-step workflows, and enterprise integration scenarios.
Here are some important core concepts of the Microsoft Agent Framework:
- **Agents**. An agent is created via `FoundryChatClient` and configured with a name, instructions, and tools. The agent can:
- **Process user messages** and generate responses using Azure OpenAI models.
- **Call tools** automatically based on the conversation context.
- **Maintain conversation state** across multiple interactions.
Here is a code snippet showing how to create an agent:
```python
import os
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
provider = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
agent = provider.as_agent(
name="my_agent",
instructions="You are a helpful assistant.",
)
response = await agent.run("Hello, World!")
print(response)
```
- **Tools**. The framework supports defining tools as Python functions that the agent can invoke automatically. Tools are registered when creating the agent:
```python
def get_weather(location: str) -> str:
"""Get the current weather for a location."""
return f"The weather in {location} is sunny, 72\u00b0F."
agent = provider.as_agent(
name="weather_agent",
instructions="Help users check the weather.",
tools=[get_weather],
)
```
- **Multi-Agent Coordination**. You can create multiple agents with different specializations and coordinate their work:
```python
planner = provider.as_agent(
name="planner",
instructions="Break down complex tasks into steps.",
)
executor = provider.as_agent(
name="executor",
instructions="Execute the planned steps using available tools.",
tools=[execute_tool],
)
plan = await planner.run("Plan a trip to Paris")
result = await executor.run(f"Execute this plan: {plan}")
```
- **Azure Identity Integration**. The framework uses `AzureCliCredential` (or `DefaultAzureCredential`) for secure, keyless authentication, eliminating the need to manage API keys directly.
## Microsoft Foundry Agent Service
Microsoft Foundry Agent Service is a more recent addition, introduced at Microsoft Ignite 2024. It allows for the development and deployment of AI agents with more flexible models, such as directly calling open-source LLMs like Llama 3, Mistral, and Cohere.
Microsoft Foundry Agent Service provides stronger enterprise security mechanisms and data storage methods, making it suitable for enterprise applications.
It works out-of-the-box with the Microsoft Agent Framework for building and deploying agents.
This service is currently in Public Preview and supports Python and C# for building agents.
Using the Microsoft Foundry Agent Service Python SDK, we can create an agent with a user-defined tool:
```python
import asyncio
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
# Define tool functions
def get_specials() -> str:
"""Provides a list of specials from the menu."""
return """
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
"""
def get_item_price(menu_item: str) -> str:
"""Provides the price of the requested menu item."""
return "$9.99"
async def main() -> None:
credential = DefaultAzureCredential()
project_client = AIProjectClient.from_connection_string(
credential=credential,
conn_str="your-connection-string",
)
agent = project_client.agents.create_agent(
model="gpt-4o-mini",
name="Host",
instructions="Answer questions about the menu.",
tools=[get_specials, get_item_price],
)
thread = project_client.agents.create_thread()
user_inputs = [
"Hello",
"What is the special soup?",
"How much does that cost?",
"Thank you",
]
for user_input in user_inputs:
print(f"# User: '{user_input}'")
message = project_client.agents.create_message(
thread_id=thread.id,
role="user",
content=user_input,
)
run = project_client.agents.create_and_process_run(
thread_id=thread.id, agent_id=agent.id
)
messages = project_client.agents.list_messages(thread_id=thread.id)
print(f"# Agent: {messages.data[0].content[0].text.value}")
if __name__ == "__main__":
asyncio.run(main())
```
### Core concepts
Microsoft Foundry Agent Service has the following core concepts:
- **Agent**. Microsoft Foundry Agent Service integrates with Microsoft Foundry. Within Microsoft Foundry, an AI Agent acts as a "smart" microservice that can be used to answer questions (RAG), perform actions, or completely automate workflows. It achieves this by combining the power of generative AI models with tools that allow it to access and interact with real-world data sources. Here's an example of an agent:
```python
agent = project_client.agents.create_agent(
model="gpt-4o-mini",
name="my-agent",
instructions="You are helpful agent",
tools=code_interpreter.definitions,
tool_resources=code_interpreter.resources,
)
```
In this example, an agent is created with the model `gpt-4o-mini`, a name `my-agent`, and instructions `You are helpful agent`. The agent is equipped with tools and resources to perform code interpretation tasks.
- **Thread and messages**. The thread is another important concept. It represents a conversation or interaction between an agent and a user. Threads can be used to track the progress of a conversation, store context information, and manage the state of the interaction. Here's an example of a thread:
```python
thread = project_client.agents.create_thread()
message = project_client.agents.create_message(
thread_id=thread.id,
role="user",
content="Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million",
)
# Ask the agent to perform work on the thread
run = project_client.agents.create_and_process_run(thread_id=thread.id, agent_id=agent.id)
# Fetch and log all messages to see the agent's response
messages = project_client.agents.list_messages(thread_id=thread.id)
print(f"Messages: {messages}")
```
In the previous code, a thread is created. Thereafter, a message is sent to the thread. By calling `create_and_process_run`, the agent is asked to perform work on the thread. Finally, the messages are fetched and logged to see the agent's response. The messages indicate the progress of the conversation between the user and the agent. It's also important to understand that the messages can be of different types such as text, image, or file, that is the agents work has resulted in for example an image or a text response for example. As a developer, you can then use this information to further process the response or present it to the user.
- **Integrates with the Microsoft Agent Framework**. Microsoft Foundry Agent Service works seamlessly with the Microsoft Agent Framework, which means you can build agents using `FoundryChatClient` and deploy them through the Agent Service for production scenarios.
**Use Cases**: Microsoft Foundry Agent Service is designed for enterprise applications that require secure, scalable, and flexible AI agent deployment.
## What's the difference between these approaches?
It does sound like there is overlap, but there are some key differences in terms of their design, capabilities, and target use cases:
- **Microsoft Agent Framework (MAF)**: Is a production-ready SDK for building AI agents. It provides a streamlined API for creating agents with tool calling, conversation management, and Azure identity integration.
- **Microsoft Foundry Agent Service**: Is a platform and deployment service in Microsoft Foundry for agents. It offers built-in connectivity to services like Azure OpenAI, Azure AI Search, Bing Search and code execution.
Still not sure which one to choose?
### Use Cases
Let's see if we can help you by going through some common use cases:
> Q: I'm building production AI agent applications and want to get started quickly
>
>A: The Microsoft Agent Framework is a great choice. It provides a simple, Pythonic API via `FoundryChatClient` that lets you define agents with tools and instructions in just a few lines of code.
>Q: I need enterprise-grade deployment with Azure integrations like Search and code execution
>
> A: Microsoft Foundry Agent Service is the best fit. It's a platform service that provides built-in capabilities for multiple models, Azure AI Search, Bing Search and Azure Functions. It makes it easy to build your agents in the Foundry Portal and deploy them at scale.
> Q: I'm still confused, just give me one option
>
> A: Start with the Microsoft Agent Framework to build your agents, and then use Microsoft Foundry Agent Service when you need to deploy and scale them in production. This approach lets you iterate quickly on your agent logic while having a clear path to enterprise deployment.
Let's summarize the key differences in a table:
| Framework | Focus | Core Concepts | Use Cases |
| --- | --- | --- | --- |
| Microsoft Agent Framework | Streamlined agent SDK with tool calling | Agents, Tools, Azure Identity | Building AI agents, tool use, multi-step workflows |
| Microsoft Foundry Agent Service | Flexible models, enterprise security, Code generation, Tool calling | Modularity, Collaboration, Process Orchestration | Secure, scalable, and flexible AI agent deployment |
## Can I integrate my existing Azure ecosystem tools directly, or do I need standalone solutions?
The answer is yes, you can integrate your existing Azure ecosystem tools directly with Microsoft Foundry Agent Service especially, as it has been built to work seamlessly with other Azure services. You could for example integrate Bing, Azure AI Search, and Azure Functions. There's also deep integration with Microsoft Foundry.
The Microsoft Agent Framework also integrates with Azure services through `FoundryChatClient` and Azure identity, letting you call Azure services directly from your agent tools.
## Sample Codes
- Python: [Agent Framework (Microsoft Foundry)](./code_samples/02-python-agent-framework.ipynb)
- Python: [Agent Framework (Azure OpenAI Responses API)](./code_samples/02-python-agent-framework-azure-openai.ipynb)
- .NET: [Agent Framework](./code_samples/02-dotnet-agent-framework.md)
## Got More Questions about AI Agent Frameworks?
Join the [Microsoft Foundry Discord](https://discord.com/invite/ATgtXmAS5D) to meet with other learners, attend office hours and get your AI Agents questions answered.
## References
- <a href="https://techcommunity.microsoft.com/blog/azure-ai-services-blog/introducing-azure-ai-agent-service/4298357" target="_blank">Azure Agent Service</a>
- <a href="https://learn.microsoft.com/azure/ai-services/openai/how-to/responses" target="_blank">Microsoft Agent Framework - Azure OpenAI Responses</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/overview" target="_blank">Microsoft Foundry Agent Service</a>
## Previous Lesson
[Introduction to AI Agents and Agent Use Cases](../01-intro-to-ai-agents/README.md)
## Next Lesson
[Understanding Agentic Design Patterns](../03-agentic-design-patterns/README.md)
@@ -0,0 +1,102 @@
# Microsoft Foundry Agent Service Development
In this exercise, you use the Microsoft Foundry Agent Service tools in the [Microsoft Foundry portal](https://ai.azure.com/?WT.mc_id=academic-105485-koreyst) to create an agent for Flight Booking. The agent will be able to interact with users and provide information about flights.
## Prerequisites
To complete this exercise, you need the following:
1. An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=academic-105485-koreyst).
2. You need permissions to create a Microsoft Foundry hub or have one created for you.
- If your role is Contributor or Owner, you can follow the steps in this tutorial.
## Create a Microsoft Foundry hub
> **Note:** Microsoft Foundry was formerly known as Azure AI Studio.
1. Follow these guidelines from the [Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/?WT.mc_id=academic-105485-koreyst) blog post for creating a Microsoft Foundry hub.
2. When your project is created, close any tips that are displayed and review the project page in the Microsoft Foundry portal, which should look similar to the following image:
![Microsoft Foundry Project](./images/azure-ai-foundry.png)
## Deploy a model
1. In the pane on the left for your project, in the **My assets** section, select the **Models + endpoints** page.
2. In the **Models + endpoints** page, in the **Model deployments** tab, in the **+ Deploy model** menu, select **Deploy base model**.
3. Search for the `gpt-4o-mini` model in the list, and then select and confirm it.
> **Note**: Reducing the TPM helps avoid over-using the quota available in the subscription you are using.
![Model Deployed](./images/model-deployment.png)
## Create an agent
Now that you have deployed a model, you can create an agent. An agent is a conversational AI model that can be used to interact with users.
1. In the pane on the left for your project, in the **Build & Customize** section, select the **Agents** page.
2. Click **+ Create agent** to create a new agent. Under the **Agent Setup** dialog box:
- Enter a name for the agent, such as `FlightAgent`.
- Ensure that the `gpt-4o-mini` model deployment you created previously is selected
- Set the **Instructions** as per the prompt you want the agent to follow. Here is an example:
```
You are FlightAgent, a virtual assistant specialized in handling flight-related queries. Your role includes assisting users with searching for flights, retrieving flight details, checking seat availability, and providing real-time flight status. Follow the instructions below to ensure clarity and effectiveness in your responses:
### Task Instructions:
1. **Recognizing Intent**:
- Identify the user's intent based on their request, focusing on one of the following categories:
- Searching for flights
- Retrieving flight details using a flight ID
- Checking seat availability for a specified flight
- Providing real-time flight status using a flight number
- If the intent is unclear, politely ask users to clarify or provide more details.
2. **Processing Requests**:
- Depending on the identified intent, perform the required task:
- For flight searches: Request details such as origin, destination, departure date, and optionally return date.
- For flight details: Request a valid flight ID.
- For seat availability: Request the flight ID and date and validate inputs.
- For flight status: Request a valid flight number.
- Perform validations on provided data (e.g., formats of dates, flight numbers, or IDs). If the information is incomplete or invalid, return a friendly request for clarification.
3. **Generating Responses**:
- Use a tone that is friendly, concise, and supportive.
- Provide clear and actionable suggestions based on the output of each task.
- If no data is found or an error occurs, explain it to the user gently and offer alternative actions (e.g., refine search, try another query).
```
> [!NOTE]
> For a detailed prompt, you can check out [this repository](https://github.com/ShivamGoyal03/RoamMind) for more information.
> Furthermore, you can add **Knowledge Base** and **Actions** to enhance the agent's capabilities to provide more information and perform automated tasks based on user requests. For this exercise, you can skip these steps.
![Agent Setup](./images/agent-setup.png)
3. To create a new multi-AI agent, simply click **New Agent**. The newly created agent will then be displayed on the Agents page.
## Test the agent
After creating the agent, you can test it to see how it responds to user queries in Microsoft Foundry portal playground.
1. At the top of the **Setup** pane for your agent, select **Try in playground**.
2. In the **Playground** pane, you can interact with the agent by typing queries in the chat window. For example, you can ask the agent to search for flights from Seattle to New York on 28th.
> **Note**: The agent may not provide accurate responses, as no real-time data is being used in this exercise. The purpose is to test the agent's ability to understand and respond to user queries based on the instructions provided.
![Agent Playground](./images/agent-playground.png)
3. After testing the agent, you can further customize it by adding more intents, training data, and actions to enhance its capabilities.
## Clean up resources
When you have finished testing the agent, you can delete it to avoid incurring additional costs.
1. Open the [Azure portal](https://portal.azure.com) and view the contents of the resource group where you deployed the hub resources used in this exercise.
2. On the toolbar, select **Delete resource group**.
3. Enter the resource group name and confirm that you want to delete it.
## Resources
- [Microsoft Foundry documentation](https://learn.microsoft.com/en-us/azure/ai-studio/?WT.mc_id=academic-105485-koreyst)
- [Microsoft Foundry portal](https://ai.azure.com/?WT.mc_id=academic-105485-koreyst)
- [Getting Started with Microsoft Foundry](https://techcommunity.microsoft.com/blog/educatordeveloperblog/getting-started-with-azure-ai-studio/4095602?WT.mc_id=academic-105485-koreyst)
- [Fundamentals of AI agents on Azure](https://learn.microsoft.com/en-us/training/modules/ai-agent-fundamentals/?WT.mc_id=academic-105485-koreyst)
- [Azure AI Discord](https://aka.ms/AzureAI/Discord)
@@ -0,0 +1,114 @@
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// Tool Function: Random Destination Generator
// This static method will be available to the agent as a callable tool
// The [Description] attribute helps the AI understand when to use this function
// This demonstrates how to create custom tools for AI agents
[Description("Provides a random vacation destination.")]
static string GetRandomDestination()
{
// List of popular vacation destinations around the world
// The agent will randomly select from these options
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
// Generate random index and return selected destination
// Uses System.Random for simple random selection
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Define Agent Identity and Comprehensive Instructions
// Agent name for identification and logging purposes
var AGENT_NAME = "TravelAgent";
// Detailed instructions that define the agent's personality, capabilities, and behavior
// This system prompt shapes how the agent responds and interacts with users
var AGENT_INSTRUCTIONS = """
You are a helpful AI Agent that can help plan vacations for customers.
Important: When users specify a destination, always plan for that location. Only suggest random destinations when the user hasn't specified a preference.
When the conversation begins, introduce yourself with this message:
"Hello! I'm your TravelAgent assistant. I can help plan vacations and suggest interesting destinations for you. Here are some things you can ask me:
1. Plan a day trip to a specific location
2. Suggest a random vacation destination
3. Find destinations with specific features (beaches, mountains, historical sites, etc.)
4. Plan an alternative trip if you don't like my first suggestion
What kind of trip would you like me to help you plan today?"
Always prioritize user preferences. If they mention a specific destination like "Bali" or "Paris," focus your planning on that location rather than suggesting alternatives.
""";
// Create AI Agent with Advanced Travel Planning Capabilities
// Initialize complete agent pipeline: OpenAI client → Chat client → AI agent
// Configure agent with name, detailed instructions, and available tools
// This demonstrates the .NET agent creation pattern with full configuration
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
name: AGENT_NAME,
instructions: AGENT_INSTRUCTIONS,
tools: [AIFunctionFactory.Create(GetRandomDestination)]
);
// Create New Session for Context Management.
// Initialize a new conversation session to maintain context across multiple interactions
// Sessions enable the agent to remember previous exchanges and maintain conversational state
// This is essential for multi-turn conversations and contextual understanding
AgentSession session = await agent.CreateSessionAsync();
// Execute Agent: First Travel Planning Request
// Run the agent with an initial request that will likely trigger the random destination tool
// The agent will analyze the request, use the GetRandomDestination tool, and create an itinerary
// Using the session parameter maintains conversation context for subsequent interactions
await foreach (var update in agent.RunStreamingAsync("Plan me a day trip", session))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine();
// Execute Agent: Follow-up Request with Context Awareness
// Demonstrate contextual conversation by referencing the previous response
// The agent remembers the previous destination suggestion and will provide an alternative
// This showcases the power of conversation sessions and contextual understanding in .NET agents
await foreach (var update in agent.RunStreamingAsync("I don't like that destination. Plan me another vacation.", session))
{
await Task.Delay(10);
Console.Write(update);
}
@@ -0,0 +1,231 @@
# 🔍 Exploring Microsoft Agent Framework - Basic Agent (.NET)
## 📋 Learning Objectives
This example explores the fundamental concepts of the Microsoft Agent Framework through a basic agent implementation in .NET. You'll learn core agentic patterns and understand how intelligent agents work under the hood using C# and the .NET ecosystem.
### What You'll Discover
- 🏗️ **Agent Architecture**: Understanding the basic structure of AI agents in .NET
- 🛠️ **Tool Integration**: How agents use external functions to extend capabilities
- 💬 **Conversation Flow**: Managing multi-turn conversations and context with thread management
- 🔧 **Configuration Patterns**: Best practices for agent setup and management in .NET
## 🎯 Key Concepts Covered
### Agentic Framework Principles
- **Autonomy**: How agents make independent decisions using .NET AI abstractions
- **Reactivity**: Responding to environmental changes and user inputs
- **Proactivity**: Taking initiative based on goals and context
- **Social Ability**: Interacting through natural language with conversation threads
### Technical Components
- **AIAgent**: Core agent orchestration and conversation management (.NET)
- **Tool Functions**: Extending agent capabilities with C# methods and attributes
- **Azure OpenAI Integration**: Leveraging language models through the Azure OpenAI Responses API
- **Secure Configuration**: Environment-based endpoint management
## 🔧 Technical Stack
### Core Technologies
- Microsoft Agent Framework (.NET)
- Azure OpenAI (Responses API) integration
- Azure.AI.OpenAI client patterns
- Environment-based configuration with DotNetEnv
### Agent Capabilities
- Natural language understanding and generation
- Function calling and tool usage with C# attributes
- Context-aware responses with conversation threads
- Extensible architecture with dependency injection patterns
## 📚 Framework Comparison
This example demonstrates the Microsoft Agent Framework approach compared to other agentic frameworks:
| Feature | Microsoft Agent Framework | Other Frameworks |
|---------|-------------------------|------------------|
| **Integration** | Native Microsoft ecosystem | Varied compatibility |
| **Simplicity** | Clean, intuitive API | Often complex setup |
| **Extensibility** | Easy tool integration | Framework-dependent |
| **Enterprise Ready** | Built for production | Varies by framework |
## 🚀 Getting Started
### Prerequisites
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or higher
- An [Azure subscription](https://azure.microsoft.com/free/) with an Azure OpenAI resource and a model deployment
- The [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) — sign in with `az login`
### Required Environment Variables
```bash
# zsh/bash
export AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
export AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
# Then sign in so AzureCliCredential can get a token
az login
```
```powershell
# PowerShell
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com"
$env:AZURE_OPENAI_DEPLOYMENT = "gpt-4o-mini"
# Then sign in so AzureCliCredential can get a token
az login
```
### Sample Code
To run the code example,
```bash
# zsh/bash
chmod +x ./02-dotnet-agent-framework.cs
./02-dotnet-agent-framework.cs
```
Or using the dotnet CLI:
```bash
dotnet run ./02-dotnet-agent-framework.cs
```
See [`02-dotnet-agent-framework.cs`](./02-dotnet-agent-framework.cs) for the complete code.
```csharp
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// Tool Function: Random Destination Generator
// This static method will be available to the agent as a callable tool
// The [Description] attribute helps the AI understand when to use this function
// This demonstrates how to create custom tools for AI agents
[Description("Provides a random vacation destination.")]
static string GetRandomDestination()
{
// List of popular vacation destinations around the world
// The agent will randomly select from these options
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
// Generate random index and return selected destination
// Uses System.Random for simple random selection
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Define Agent Identity and Comprehensive Instructions
// Agent name for identification and logging purposes
var AGENT_NAME = "TravelAgent";
// Detailed instructions that define the agent's personality, capabilities, and behavior
// This system prompt shapes how the agent responds and interacts with users
var AGENT_INSTRUCTIONS = """
You are a helpful AI Agent that can help plan vacations for customers.
Important: When users specify a destination, always plan for that location. Only suggest random destinations when the user hasn't specified a preference.
When the conversation begins, introduce yourself with this message:
"Hello! I'm your TravelAgent assistant. I can help plan vacations and suggest interesting destinations for you. Here are some things you can ask me:
1. Plan a day trip to a specific location
2. Suggest a random vacation destination
3. Find destinations with specific features (beaches, mountains, historical sites, etc.)
4. Plan an alternative trip if you don't like my first suggestion
What kind of trip would you like me to help you plan today?"
Always prioritize user preferences. If they mention a specific destination like "Bali" or "Paris," focus your planning on that location rather than suggesting alternatives.
""";
// Create AI Agent with Advanced Travel Planning Capabilities
// Get the Responses client for the deployment and create the AI agent
// Configure agent with name, detailed instructions, and available tools
// This demonstrates the .NET agent creation pattern with full configuration
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
name: AGENT_NAME,
instructions: AGENT_INSTRUCTIONS,
tools: [AIFunctionFactory.Create(GetRandomDestination)]
);
// Create New Conversation Thread for Context Management
// Initialize a new conversation thread to maintain context across multiple interactions
// Threads enable the agent to remember previous exchanges and maintain conversational state
// This is essential for multi-turn conversations and contextual understanding
AgentThread thread = agent.GetNewThread();
// Execute Agent: First Travel Planning Request
// Run the agent with an initial request that will likely trigger the random destination tool
// The agent will analyze the request, use the GetRandomDestination tool, and create an itinerary
// Using the thread parameter maintains conversation context for subsequent interactions
await foreach (var update in agent.RunStreamingAsync("Plan me a day trip", thread))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine();
// Execute Agent: Follow-up Request with Context Awareness
// Demonstrate contextual conversation by referencing the previous response
// The agent remembers the previous destination suggestion and will provide an alternative
// This showcases the power of conversation threads and contextual understanding in .NET agents
await foreach (var update in agent.RunStreamingAsync("I don't like that destination. Plan me another vacation.", thread))
{
await Task.Delay(10);
Console.Write(update);
}
```
## 🎓 Key Takeaways
1. **Agent Architecture**: The Microsoft Agent Framework provides a clean, type-safe approach to building AI agents in .NET
2. **Tool Integration**: Functions decorated with `[Description]` attributes become available tools for the agent
3. **Conversation Context**: Thread management enables multi-turn conversations with full context awareness
4. **Configuration Management**: Environment variables and secure credential handling follow .NET best practices
5. **Azure OpenAI Responses API**: The agent uses the Azure OpenAI Responses API through the Azure.AI.OpenAI SDK
## 🔗 Additional Resources
- [Microsoft Agent Framework Documentation](https://learn.microsoft.com/agent-framework)
- [Azure OpenAI in Microsoft Foundry](https://learn.microsoft.com/azure/ai-services/openai/)
- [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai)
- [.NET Single File Apps](https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app)
@@ -0,0 +1,212 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Microsoft Agent Framework — Azure OpenAI (Responses API)\n",
"\n",
"In this code sample, you will use the **Microsoft Agent Framework (MAF)** to create a simple agent backed by **Azure OpenAI** using the **Responses API**.\n",
"\n",
"> **Migration note:** This sample previously used Semantic Kernel with GitHub Models. It has been migrated to the Microsoft Agent Framework, and GitHub Models (deprecated, retiring July 2026) has been replaced with Azure OpenAI, which supports the Responses API. The `OpenAIChatClient` in MAF targets Azure OpenAI's stable `/openai/v1/` endpoint and uses the Responses API by default.\n",
"\n",
"The purpose of this sample is to demonstrate the steps that will later be applied in additional code samples when implementing various agentic patterns.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install agent-framework agent-framework-openai azure-identity -q\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import the Needed Python Packages\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import random\n",
"\n",
"from dotenv import load_dotenv\n",
"from IPython.display import display, HTML\n",
"\n",
"from agent_framework import tool\n",
"from agent_framework.openai import OpenAIChatClient\n",
"from azure.identity import AzureCliCredential\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Defining a Tool\n",
"\n",
"In the Microsoft Agent Framework, a **tool** is a plain Python function decorated with `@tool` that the agent can call. Below we define a tool that returns a random vacation destination and avoids repeating the previous one.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# A list of vacation destinations the tool can choose from.\n",
"_DESTINATIONS = [\n",
" \"Barcelona, Spain\",\n",
" \"Paris, France\",\n",
" \"Berlin, Germany\",\n",
" \"Tokyo, Japan\",\n",
" \"Sydney, Australia\",\n",
" \"New York, USA\",\n",
" \"Cairo, Egypt\",\n",
" \"Cape Town, South Africa\",\n",
" \"Rio de Janeiro, Brazil\",\n",
" \"Bali, Indonesia\",\n",
"]\n",
"\n",
"# Track the last destination so repeated calls avoid immediate repeats.\n",
"_last_destination: str | None = None\n",
"\n",
"\n",
"@tool(approval_mode=\"never_require\")\n",
"def get_random_destination() -> str:\n",
" \"\"\"Provides a random vacation destination.\"\"\"\n",
" global _last_destination\n",
" available = _DESTINATIONS.copy()\n",
" if _last_destination and len(available) > 1:\n",
" available.remove(_last_destination)\n",
" destination = random.choice(available)\n",
" _last_destination = destination\n",
" return destination\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"load_dotenv()\n",
"\n",
"endpoint = os.environ[\"AZURE_OPENAI_ENDPOINT\"]\n",
"deployment = os.environ.get(\"AZURE_OPENAI_DEPLOYMENT\", \"gpt-4o-mini\")\n",
"\n",
"# OpenAIChatClient targets Azure OpenAI's v1 endpoint and uses the Responses API.\n",
"# Sign in with `az login` first so AzureCliCredential can authenticate.\n",
"chat_client = OpenAIChatClient(\n",
" model=deployment,\n",
" azure_endpoint=endpoint,\n",
" credential=AzureCliCredential(),\n",
")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Creating the Agent\n",
"\n",
"Here, we create the Agent named `TravelAgent`.\n",
"\n",
"In this example, we use very basic instructions. Feel free to modify these instructions to observe how the agent's behavior changes.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"agent = chat_client.as_agent(\n",
" name=\"TravelAgent\",\n",
" instructions=\"You are a helpful AI Agent that can help plan vacations for customers at random destinations\",\n",
" tools=[get_random_destination],\n",
")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Running the Agent\n",
"\n",
"Now we can run the agent. We create an `AgentSession` so the agent remembers the conversation across turns, then send two `user_inputs`. The first asks for a trip; the second says the user didn't like the suggestion and asks for another — the agent uses the session history plus the `get_random_destination` tool to respond.\n",
"\n",
"You can modify these messages to observe how the agent reacts differently. Responses are **streamed** token-by-token.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"user_inputs = [\n",
" \"Plan me a day trip.\",\n",
" \"I don't like that destination. Plan me another vacation.\",\n",
"]\n",
"\n",
"\n",
"async def main():\n",
" # A session keeps conversation history across turns.\n",
" session = agent.create_session()\n",
"\n",
" for user_input in user_inputs:\n",
" html_output = (\n",
" f\"<div style='margin-bottom:10px'>\"\n",
" f\"<div style='font-weight:bold'>User:</div>\"\n",
" f\"<div style='margin-left:20px'>{user_input}</div></div>\"\n",
" )\n",
"\n",
" full_response: list[str] = []\n",
" # Stream the agent's response token-by-token. The agent will call the\n",
" # get_random_destination tool automatically when it needs a destination.\n",
" async for chunk in agent.run(user_input, session=session, stream=True):\n",
" full_response.append(str(chunk))\n",
"\n",
" html_output += (\n",
" \"<div style='margin-bottom:20px'>\"\n",
" f\"<div style='font-weight:bold'>TravelAgent:</div>\"\n",
" f\"<div style='margin-left:20px; white-space:pre-wrap'>{''.join(full_response)}</div></div><hr>\"\n",
" )\n",
"\n",
" display(HTML(html_output))\n",
"\n",
"\n",
"await main()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,258 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a1b2c3d4",
"metadata": {},
"source": [
"# Lesson 02 - Exploring Microsoft Agent Framework\n",
"\n",
"The **Microsoft Agent Framework (MAF)** is a unified framework for building AI agents. It provides a clean, composable architecture with four core building blocks:\n",
"\n",
"- **Client** connects to an AI model endpoint and handles communication\n",
"- **Agent** wraps a client with instructions and tool definitions\n",
"- **Tools** extend agent capabilities with custom functions the model can call\n",
"- **Session** maintains conversation history for multi-turn interactions\n",
"\n",
"In this lesson, we'll build a **travel booking agent** that checks destination availability using these concepts."
]
},
{
"cell_type": "markdown",
"id": "b2c3d4e5",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3d4e5f6",
"metadata": {},
"outputs": [],
"source": [
"# Install the Microsoft Agent Framework package\n",
"! pip install agent-framework azure-ai-projects -U -q\n",
"! pip install python-dotenv -q"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4e5f6a7",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger(\"agent_framework.foundry\").setLevel(logging.ERROR)\n",
"\n",
"import os\n",
"import asyncio\n",
"import dotenv\n",
"from typing import Annotated\n",
"\n",
"from agent_framework import tool\n",
"from agent_framework.foundry import FoundryChatClient\n",
"from azure.identity import AzureCliCredential\n",
"\n",
"dotenv.load_dotenv(dotenv.find_dotenv())"
]
},
{
"cell_type": "markdown",
"id": "e5f6a7b8",
"metadata": {},
"source": [
"## Understanding the Agent Framework Architecture\n",
"\n",
"The Microsoft Agent Framework follows a layered architecture:\n",
"\n",
"```\n",
"Client → Agent → Tools\n",
" → Session\n",
"```\n",
"\n",
"1. **Client** A `FoundryChatClient` connects to an Azure OpenAI deployment. It handles authentication, request formatting, and response parsing.\n",
"2. **Agent** Created from the client via `provider.create_agent()`, the agent combines model access with instructions (system prompt) and tools.\n",
"3. **Tools** Python functions decorated with `@tool` that the agent can invoke to perform actions or retrieve data.\n",
"4. **Session** An `AgentSession` object (created via `agent.create_session()`) that stores conversation history, enabling multi-turn dialogue where the agent remembers prior context.\n",
"\n",
"Let's build each layer step by step."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6a7b8c9",
"metadata": {},
"outputs": [],
"source": [
"# Create the client this is the connection to the AI model\n",
"endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
"model = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
"\n",
"if not endpoint or not model:\n",
" raise ValueError(\n",
" \"Missing required environment variables. \"\n",
" \"Please set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME as environment variables (e.g., in your .env file or shell environment).\"\n",
" )\n",
"\n",
"provider = FoundryChatClient(\n",
" project_endpoint=endpoint,\n",
" model=model,\n",
" credential=AzureCliCredential()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a7b8c9d0",
"metadata": {},
"source": [
"## Adding Tools with the @tool Decorator\n",
"\n",
"Tools let agents take actions beyond generating text. The `@tool` decorator converts a regular Python function into something the agent can call.\n",
"\n",
"Key points:\n",
"- Use `Annotated[type, \"description\"]` so the model understands each parameter.\n",
"- The docstring becomes the tool description the model sees.\n",
"- `approval_mode=\"never_require\"` means the tool runs automatically without user confirmation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8c9d0e1",
"metadata": {},
"outputs": [],
"source": [
"@tool(approval_mode=\"never_require\")\n",
"def check_destination_availability(\n",
" destination: Annotated[str, \"The destination to check availability for\"]\n",
") -> str:\n",
" \"\"\"Check if a vacation destination is currently available for booking.\"\"\"\n",
" available = {\n",
" \"Barcelona\": True,\n",
" \"Tokyo\": True,\n",
" \"Cape Town\": False,\n",
" \"Vancouver\": True,\n",
" \"Dubai\": False,\n",
" }\n",
" is_available = available.get(destination, False)\n",
" return f\"{destination} is {'available' if is_available else 'not available'} for booking.\""
]
},
{
"cell_type": "markdown",
"id": "c9d0e1f2",
"metadata": {},
"source": [
"## Creating an Agent with Tools\n",
"\n",
"Now we combine the client, instructions, and tools into an agent. The `instructions` act as the system prompt — they define the agent's persona and behaviour."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d0e1f2a3",
"metadata": {},
"outputs": [],
"source": [
"agent = provider.as_agent(\n",
" name=\"TravelAvailabilityAgent\",\n",
" instructions=(\n",
" \"You are a travel booking agent. Help users check destination availability \"\n",
" \"and make recommendations. Always check availability before recommending a destination.\"\n",
" ),\n",
" tools=[check_destination_availability],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e1f2a3b4",
"metadata": {},
"source": [
"## Multi-Turn Conversations with Sessions\n",
"\n",
"An `AgentSession` (created via `agent.create_session()`) keeps track of all messages in a conversation. By passing the same session to each `agent.run()` call, the agent has access to the full conversation history and can refer back to earlier messages.\n",
"\n",
"We pass `tools=[check_destination_availability]` so the agent can call our availability checker during each turn."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2a3b4c5",
"metadata": {},
"outputs": [],
"source": [
"session = agent.create_session()\n",
"\n",
"# Turn 1: Ask about available destinations\n",
"response = await agent.run(\n",
" \"Which destinations do you have available?\",\n",
" session=session,\n",
")\n",
"print(f\"Agent: {response}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3b4c5d6",
"metadata": {},
"outputs": [],
"source": [
"# Turn 2: Follow-up question — the agent remembers the conversation\n",
"response = await agent.run(\n",
" \"I'd like to go somewhere warm. What's available?\",\n",
" session=session,\n",
")\n",
"print(f\"Agent: {response}\")"
]
},
{
"cell_type": "markdown",
"id": "b4c5d6e7",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"In this lesson you explored the four pillars of the Microsoft Agent Framework:\n",
"\n",
"| Concept | What You Learned |\n",
"|---------|------------------|\n",
"| **Client** | `FoundryChatClient` connects to Azure OpenAI with credential-based auth |\n",
"| **Agent** | `provider.create_agent()` bundles a model connection with instructions and a name |\n",
"| **Tools** | The `@tool` decorator exposes Python functions for the agent to call |\n",
"| **Session** | `agent.create_session()` maintains conversation history across multiple turns |\n",
"\n",
"These building blocks compose together to create agents that can hold natural conversations, call external functions, and maintain context — the foundation for more advanced agentic patterns in later lessons."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

+111
View File
@@ -0,0 +1,111 @@
[![How to Design Good AI Agents](./images/lesson-3-thumbnail.png)](https://youtu.be/m9lM8qqoOEA?si=4KimounNKvArQQ0K)
> _(Click the image above to view video of this lesson)_
# AI Agentic Design Principles
## Introduction
There are many ways to think about building AI Agentic Systems. Given that ambiguity is a feature and not a bug in Generative AI design, its sometimes difficult for engineers to figure out where to even start. We have created a set of human-centric UX Design Principles to enable developers to build customer-centric agentic systems to solve their business needs. These design principles are not a prescriptive architecture but rather a starting point for teams who are defining and building out agent experiences.
In general, agents should:
- Broaden and scale human capacities (brainstorming, problem-solving, automation, etc.)
- Fill in knowledge gaps (get me up-to-speed on knowledge domains, translation, etc.)
- Facilitate and support collaboration in the ways we as individuals prefer to work with others
- Make us better versions of ourselves (e.g., life coach/task master, helping us learn emotional regulation and mindfulness skills, building resilience, etc.)
## This Lesson Will Cover
- What are the Agentic Design Principles
- What are some guidelines to follow while implementing these design principles
- What are some examples of using the design principles
## Learning Goals
After completing this lesson, you will be able to:
1. Explain what the Agentic Design Principles are
2. Explain the guidelines for using the Agentic Design Principles
3. Understand how to build an agent using the Agentic Design Principles
## The Agentic Design Principles
![Agentic Design Principles](./images/agentic-design-principles.png)
### Agent (Space)
This is the environment in which the agent operates. These principles inform how we design agents for engaging in physical and digital worlds.
- **Connecting, not collapsing** help connect people to other people, events, and actionable knowledge to enable collaboration and connection.
- Agents help connect events, knowledge, and people.
- Agents bring people closer together. They are not designed to replace or belittle people.
- **Easily accessible yet occasionally invisible** agent largely operates in the background and only nudges us when it is relevant and appropriate.
- Agent is easily discoverable and accessible for authorized users on any device or platform.
- Agent supports multimodal inputs and outputs (sound, voice, text, etc.).
- Agent can seamlessly transition between foreground and background; between proactive and reactive, depending on its sensing of user needs.
- Agent may operate in invisible form, yet its background process path and collaboration with other Agents are transparent to and controllable by the user.
### Agent (Time)
This is how the agent operates over time. These principles inform how we design agents interacting across the past, present, and future.
- **Past**: Reflecting on history that includes both state and context.
- Agent provides more relevant results based on analysis of richer historical data beyond only the event, people, or states.
- Agent creates connections from past events and actively reflects on memory to engage with current situations.
- **Now**: Nudging more than notifying.
- Agent embodies a comprehensive approach to interacting with people. When an event happens, the Agent goes beyond static notification or other static formality. Agent can simplify flows or dynamically generate cues to direct the users attention at the right moment.
- Agent delivers information based on contextual environment, social and cultural changes and tailored to user intent.
- Agent interaction can be gradual, evolving/growing in complexity to empower users over the long term.
- **Future**: Adapting and evolving.
- Agent adapts to various devices, platforms, and modalities.
- Agent adapts to user behavior, accessibility needs, and is freely customizable.
- Agent is shaped by and evolves through continuous user interaction.
### Agent (Core)
These are the key elements in the core of an agents design.
- **Embrace uncertainty but establish trust**.
- A certain level of Agent uncertainty is expected. Uncertainty is a key element of agent design.
- Trust and transparency are foundational layers of Agent design.
- Humans are in control of when the Agent is on/off and Agent status is clearly visible at all times.
## The Guidelines to Implement These Principles
When youre using the previous design principles, use the following guidelines:
1. **Transparency**: Inform the user that AI is involved, how it functions (including past actions), and how to give feedback and modify the system.
2. **Control**: Enable the user to customize, specify preferences and personalize, and have control over the system and its attributes (including the ability to forget).
3. **Consistency**: Aim for consistent, multi-modal experiences across devices and endpoints. Use familiar UI/UX elements where possible (e.g., microphone icon for voice interaction) and reduce the customers cognitive load as much as possible (e.g., aim for concise responses, visual aids, and Learn More content).
## How To Design a Travel Agent using These Principles and Guidelines
Imagine you are designing a Travel Agent, here is how you could think about using the Design Principles and Guidelines:
1. **Transparency** Let the user know that the Travel Agent is an AI-enabled Agent. Provide some basic instructions on how to get started (e.g., a “Hello” message, sample prompts). Clearly document this on the product page. Show the list of prompts a user has asked in the past. Make it clear how to give feedback (thumbs up and down, Send Feedback button, etc.). Clearly articulate if the Agent has usage or topic restrictions.
2. **Control** Make sure its clear how the user can modify the Agent after its been created with things like the System Prompt. Enable the user to choose how verbose the Agent is, its writing style, and any caveats on what the Agent should not talk about. Allow the user to view and delete any associated files or data, prompts, and past conversations.
3. **Consistency** Make sure the icons for Share Prompt, add a file or photo and tag someone or something are standard and recognizable. Use the paperclip icon to indicate file upload/sharing with the Agent, and an image icon to indicate graphics upload.
## Sample Codes
- Python: [Agent Framework](./code_samples/03-python-agent-framework.ipynb)
- .NET: [Agent Framework](./code_samples/03-dotnet-agent-framework.md)
## Got More Questions about AI Agentic Design Patterns?
Join the [Microsoft Foundry Discord](https://discord.com/invite/ATgtXmAS5D) to meet with other learners, attend office hours and get your AI Agents questions answered.
## Additional Resources
- <a href="https://openai.com" target="_blank">Practices for Governing Agentic AI Systems | OpenAI</a>
- <a href="https://microsoft.com" target="_blank">The HAX Toolkit Project - Microsoft Research</a>
- <a href="https://responsibleaitoolbox.ai" target="_blank">Responsible AI Toolbox</a>
## Previous Lesson
[Exploring Agentic Frameworks](../02-explore-agentic-frameworks/README.md)
## Next Lesson
[Tool Use Design Pattern](../04-tool-use/README.md)
@@ -0,0 +1,168 @@
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// ============================================================================
// AGENTIC DESIGN PRINCIPLES DEMONSTRATION
// ============================================================================
// This sample demonstrates the three key design principles from the lesson:
// 1. TRANSPARENCY: The agent explains what it's doing and why
// 2. CONTROL: Users can customize preferences and the agent respects them
// 3. CONSISTENCY: The agent uses a predictable, standardized interaction pattern
// ============================================================================
// Tool Function: Random Destination Generator
// TRANSPARENCY: Clear description helps users understand this tool's purpose
[Description("Provides a random vacation destination. Returns a city and country.")]
static string GetRandomDestination()
{
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Tool Function: User Preference Storage (Demonstrates CONTROL principle)
// CONTROL: This tool allows users to set and manage their preferences
[Description("Saves user preferences for trip planning. Use this when the user specifies preferences like budget level (budget/moderate/luxury), trip style (adventure/relaxation/cultural), or duration preference.")]
static string SaveUserPreference(
[Description("The type of preference being saved, e.g., 'budget', 'style', 'duration'")] string preferenceType,
[Description("The value of the preference")] string preferenceValue)
{
// In a real application, this would persist to a database
Console.WriteLine($"\n[TRANSPARENCY] Saving preference: {preferenceType} = {preferenceValue}");
return $"Preference saved: {preferenceType} is now set to '{preferenceValue}'. I will remember this for future suggestions.";
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Agent Identity
var AGENT_NAME = "TravelAgent";
// ============================================================================
// AGENT INSTRUCTIONS - Demonstrating Design Principles
// ============================================================================
// These instructions embed the three design principles directly into the agent's behavior
var AGENT_INSTRUCTIONS = """
You are a helpful AI Agent that demonstrates the Agentic Design Principles.
## Your Core Principles
**TRANSPARENCY**: Always explain what you're doing and why.
- When using a tool, briefly explain which tool you're calling and why
- Share your reasoning process with the user
- Be honest about limitations or uncertainties
**CONTROL**: Respect user preferences and allow customization.
- Ask about preferences before making assumptions
- Use the SaveUserPreference tool to remember user choices
- Always prioritize explicit user requests over defaults
**CONSISTENCY**: Use a predictable, standardized interaction pattern.
- Start every conversation with a friendly greeting
- Structure responses in a clear, organized format
- Use similar phrasing for similar actions
## Initial Greeting (CONSISTENCY)
When the conversation begins, always introduce yourself with this message:
"Hello! I'm TravelAgent, your AI vacation planning assistant.
🔍 **Transparency**: I'll always explain my reasoning and the tools I use.
🎮 **Control**: Tell me your preferences, and I'll remember them.
🔄 **Consistency**: I follow a predictable pattern to make planning easy.
What kind of trip would you like me to help you plan today?"
## Guidelines
- When users specify a destination, plan for that location
- Only suggest random destinations when the user hasn't specified one
- Always confirm before making changes to preferences
""";
// Create AI Agent with Design Principles
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
name: AGENT_NAME,
instructions: AGENT_INSTRUCTIONS,
tools: [
AIFunctionFactory.Create(GetRandomDestination),
AIFunctionFactory.Create(SaveUserPreference)
]
);
// Create Conversation Session for Context Management
await using var session = await agent.CreateSessionAsync();
// ============================================================================
// DEMONSTRATION: Start with "Hello" to trigger the greeting (Issue #402 fix)
// ============================================================================
Console.WriteLine("=== Demonstrating Agentic Design Principles ===\n");
Console.WriteLine("User: Hello\n");
Console.WriteLine("Agent Response:");
await foreach (var update in agent.RunStreamingAsync("Hello", session))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine("\n");
// ============================================================================
// DEMONSTRATION: User sets a preference (CONTROL principle)
// ============================================================================
Console.WriteLine("---");
Console.WriteLine("User: I prefer luxury travel and cultural experiences.\n");
Console.WriteLine("Agent Response:");
await foreach (var update in agent.RunStreamingAsync("I prefer luxury travel and cultural experiences.", session))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine("\n");
// ============================================================================
// DEMONSTRATION: Agent uses tools with transparency
// ============================================================================
Console.WriteLine("---");
Console.WriteLine("User: Suggest a destination for me.\n");
Console.WriteLine("Agent Response:");
await foreach (var update in agent.RunStreamingAsync("Suggest a destination for me.", session))
{
await Task.Delay(10);
Console.Write(update);
}
@@ -0,0 +1,281 @@
# 🎨 Agentic Design Patterns with Azure OpenAI (Responses API) (.NET)
## 📋 Learning Objectives
This example demonstrates enterprise-grade design patterns for building intelligent agents using the Microsoft Agent Framework in .NET with Azure OpenAI (Responses API) integration. You'll learn professional patterns and architectural approaches that make agents production-ready, maintainable, and scalable.
### Enterprise Design Patterns
- 🏭 **Factory Pattern**: Standardized agent creation with dependency injection
- 🔧 **Builder Pattern**: Fluent agent configuration and setup
- 🧵 **Thread-Safe Patterns**: Concurrent conversation management
- 📋 **Repository Pattern**: Organized tool and capability management
## 🎯 .NET-Specific Architectural Benefits
### Enterprise Features
- **Strong Typing**: Compile-time validation and IntelliSense support
- **Dependency Injection**: Built-in DI container integration
- **Configuration Management**: IConfiguration and Options patterns
- **Async/Await**: First-class asynchronous programming support
### Production-Ready Patterns
- **Logging Integration**: ILogger and structured logging support
- **Health Checks**: Built-in monitoring and diagnostics
- **Configuration Validation**: Strong typing with data annotations
- **Error Handling**: Structured exception management
## 🔧 Technical Architecture
### Core .NET Components
- **Microsoft.Extensions.AI**: Unified AI service abstractions
- **Microsoft.Agents.AI**: Enterprise agent orchestration framework
- **Azure OpenAI (Responses API)**: High-performance API client patterns
- **Configuration System**: appsettings.json and environment integration
### Design Pattern Implementation
```mermaid
graph LR
A[IServiceCollection] --> B[Agent Builder]
B --> C[Configuration]
C --> D[Tool Registry]
D --> E[AI Agent]
```
## 🏗️ Enterprise Patterns Demonstrated
### 1. **Creational Patterns**
- **Agent Factory**: Centralized agent creation with consistent configuration
- **Builder Pattern**: Fluent API for complex agent configuration
- **Singleton Pattern**: Shared resources and configuration management
- **Dependency Injection**: Loose coupling and testability
### 2. **Behavioral Patterns**
- **Strategy Pattern**: Interchangeable tool execution strategies
- **Command Pattern**: Encapsulated agent operations with undo/redo
- **Observer Pattern**: Event-driven agent lifecycle management
- **Template Method**: Standardized agent execution workflows
### 3. **Structural Patterns**
- **Adapter Pattern**: Azure OpenAI (Responses API) integration layer
- **Decorator Pattern**: Agent capability enhancement
- **Facade Pattern**: Simplified agent interaction interfaces
- **Proxy Pattern**: Lazy loading and caching for performance
## 📚 .NET Design Principles
### SOLID Principles
- **Single Responsibility**: Each component has one clear purpose
- **Open/Closed**: Extensible without modification
- **Liskov Substitution**: Interface-based tool implementations
- **Interface Segregation**: Focused, cohesive interfaces
- **Dependency Inversion**: Depend on abstractions, not concretions
### Clean Architecture
- **Domain Layer**: Core agent and tool abstractions
- **Application Layer**: Agent orchestration and workflows
- **Infrastructure Layer**: Azure OpenAI (Responses API) integration and external services
- **Presentation Layer**: User interaction and response formatting
## 🔒 Enterprise Considerations
### Security
- **Credential Management**: Secure API key handling with IConfiguration
- **Input Validation**: Strong typing and data annotation validation
- **Output Sanitization**: Secure response processing and filtering
- **Audit Logging**: Comprehensive operation tracking
### Performance
- **Async Patterns**: Non-blocking I/O operations
- **Connection Pooling**: Efficient HTTP client management
- **Caching**: Response caching for improved performance
- **Resource Management**: Proper disposal and cleanup patterns
### Scalability
- **Thread Safety**: Concurrent agent execution support
- **Resource Pooling**: Efficient resource utilization
- **Load Management**: Rate limiting and backpressure handling
- **Monitoring**: Performance metrics and health checks
## 🚀 Production Deployment
- **Configuration Management**: Environment-specific settings
- **Logging Strategy**: Structured logging with correlation IDs
- **Error Handling**: Global exception handling with proper recovery
- **Monitoring**: Application insights and performance counters
- **Testing**: Unit tests, integration tests, and load testing patterns
Ready to build enterprise-grade intelligent agents with .NET? Let's architect something robust! 🏢✨
## 🚀 Getting Started
### Prerequisites
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or higher
- An [Azure subscription](https://azure.microsoft.com/free/) with an Azure OpenAI resource and a model deployment
- The [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) — sign in with `az login`
### Required Environment Variables
```bash
# zsh/bash
export AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
export AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
# Then sign in so AzureCliCredential can get a token
az login
```
```powershell
# PowerShell
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com"
$env:AZURE_OPENAI_DEPLOYMENT = "gpt-4o-mini"
# Then sign in so AzureCliCredential can get a token
az login
```
### Sample Code
To run the code example,
```bash
# zsh/bash
chmod +x ./03-dotnet-agent-framework.cs
./03-dotnet-agent-framework.cs
```
Or using the dotnet CLI:
```bash
dotnet run ./03-dotnet-agent-framework.cs
```
See [`03-dotnet-agent-framework.cs`](./03-dotnet-agent-framework.cs) for the complete code.
```csharp
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// Tool Function: Random Destination Generator
// This static method will be available to the agent as a callable tool
// The [Description] attribute helps the AI understand when to use this function
// This demonstrates how to create custom tools for AI agents
[Description("Provides a random vacation destination.")]
static string GetRandomDestination()
{
// List of popular vacation destinations around the world
// The agent will randomly select from these options
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
// Generate random index and return selected destination
// Uses System.Random for simple random selection
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Define Agent Identity and Comprehensive Instructions
// Agent name for identification and logging purposes
var AGENT_NAME = "TravelAgent";
// Detailed instructions that define the agent's personality, capabilities, and behavior
// This system prompt shapes how the agent responds and interacts with users
var AGENT_INSTRUCTIONS = """
You are a helpful AI Agent that can help plan vacations for customers.
Important: When users specify a destination, always plan for that location. Only suggest random destinations when the user hasn't specified a preference.
When the conversation begins, introduce yourself with this message:
"Hello! I'm your TravelAgent assistant. I can help plan vacations and suggest interesting destinations for you. Here are some things you can ask me:
1. Plan a day trip to a specific location
2. Suggest a random vacation destination
3. Find destinations with specific features (beaches, mountains, historical sites, etc.)
4. Plan an alternative trip if you don't like my first suggestion
What kind of trip would you like me to help you plan today?"
Always prioritize user preferences. If they mention a specific destination like "Bali" or "Paris," focus your planning on that location rather than suggesting alternatives.
""";
// Create AI Agent with Advanced Travel Planning Capabilities
// Get the Responses client for the deployment and create the AI agent
// Configure agent with name, detailed instructions, and available tools
// This demonstrates the .NET agent creation pattern with full configuration
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
name: AGENT_NAME,
instructions: AGENT_INSTRUCTIONS,
tools: [AIFunctionFactory.Create(GetRandomDestination)]
);
// Create New Conversation Thread for Context Management
// Initialize a new conversation thread to maintain context across multiple interactions
// Threads enable the agent to remember previous exchanges and maintain conversational state
// This is essential for multi-turn conversations and contextual understanding
AgentThread thread = agent.GetNewThread();
// Execute Agent: First Travel Planning Request
// Run the agent with an initial request that will likely trigger the random destination tool
// The agent will analyze the request, use the GetRandomDestination tool, and create an itinerary
// Using the thread parameter maintains conversation context for subsequent interactions
await foreach (var update in agent.RunStreamingAsync("Plan me a day trip", thread))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine();
// Execute Agent: Follow-up Request with Context Awareness
// Demonstrate contextual conversation by referencing the previous response
// The agent remembers the previous destination suggestion and will provide an alternative
// This showcases the power of conversation threads and contextual understanding in .NET agents
await foreach (var update in agent.RunStreamingAsync("I don't like that destination. Plan me another vacation.", thread))
{
await Task.Delay(10);
Console.Write(update);
}
```
@@ -0,0 +1,323 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "lesson-intro",
"metadata": {},
"source": [
"# Lesson 03 - Agentic Design Patterns\n",
"\n",
"In this lesson, we explore three foundational design patterns for building effective AI agents:\n",
"\n",
"1. **Clear Agent Instructions** — Crafting precise, role-defining prompts that guide agent behavior\n",
"2. **Structured Output with Pydantic Models** — Ensuring agents return predictable, validated data\n",
"3. **Single Responsibility Agents** — Designing focused agents that each do one thing well\n",
"\n",
"We'll apply each pattern to a **travel destination recommender** scenario, progressively building a system that can suggest destinations, check availability, and handle logistics."
]
},
{
"cell_type": "markdown",
"id": "setup-header",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "setup-code",
"metadata": {},
"outputs": [],
"source": [
"%pip install agent-framework azure-ai-projects azure-identity pydantic python-dotenv --quiet"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger(\"agent_framework.foundry\").setLevel(logging.ERROR)\n",
"\n",
"import os\n",
"import asyncio\n",
"import dotenv\n",
"from typing import Annotated\n",
"from pydantic import BaseModel\n",
"from agent_framework import tool\n",
"from agent_framework.foundry import FoundryChatClient\n",
"from azure.identity import DefaultAzureCredential\n",
"\n",
"dotenv.load_dotenv(dotenv.find_dotenv())\n",
"\n",
"endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
"deployment_name = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
"\n",
"missing = [k for k, v in {\n",
" \"AZURE_AI_PROJECT_ENDPOINT\": endpoint,\n",
" \"AZURE_AI_MODEL_DEPLOYMENT_NAME\": deployment_name\n",
"}.items() if not v]\n",
"\n",
"if missing:\n",
" raise ValueError(\n",
" f\"Missing required environment variables: {', '.join(missing)}. \"\n",
" \"Please set them as environment variables (e.g., in your .env file or shell environment).\"\n",
" )\n",
"\n",
"provider = FoundryChatClient(\n",
" project_endpoint=endpoint,\n",
" model=deployment_name,\n",
" credential=DefaultAzureCredential()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "pattern1-header",
"metadata": {},
"source": [
"## Pattern 1: Clear Agent Instructions\n",
"\n",
"The most impactful pattern is also the simplest: writing clear, detailed instructions for your agent.\n",
"\n",
"Good instructions define:\n",
"- **Who** the agent is (persona and tone)\n",
"- **What** it should do (step-by-step responsibilities)\n",
"- **How** it should behave (constraints and style)\n",
"\n",
"Below, we create a travel concierge agent with explicit instructions that shape every response it produces."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pattern1-code",
"metadata": {},
"outputs": [],
"source": [
"agent = provider.as_agent(\n",
" name=\"TravelConcierge\",\n",
" instructions=\"\"\"You are a luxury travel concierge named Alex. Your role is to:\n",
"1. Understand the traveler's preferences (budget, climate, activities)\n",
"2. Check destination availability before making recommendations\n",
"3. Provide detailed, personalized travel suggestions\n",
"4. Always mention visa requirements and best travel seasons\n",
"Be warm, professional, and enthusiastic about travel.\"\"\",\n",
")\n",
"\n",
"response = await agent.run(\n",
" \"I'd love a week-long vacation somewhere with great food and history. Budget around $2500.\"\n",
")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "pattern2-header",
"metadata": {},
"source": [
"## Pattern 2: Structured Output with Pydantic Models\n",
"\n",
"Free-form text is useful for conversation, but downstream systems need structured data.\n",
"By pairing **Pydantic models** with a **tool function**, we can:\n",
"\n",
"- Define an exact schema for the agent's output\n",
"- Validate responses automatically\n",
"- Integrate agent results into application logic reliably\n",
"\n",
"The key to enforcement is passing `response_format` when we run the agent. This forces the\n",
"model to return a validated `TravelRecommendations` object (available on `response.value`)\n",
"instead of free-form text. The `get_destination_details` tool also returns a typed\n",
"`DestinationRecommendation`, so the data stays structured end to end.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pattern2-code",
"metadata": {},
"outputs": [],
"source": [
"class DestinationRecommendation(BaseModel):\n",
" destination: str\n",
" available: bool\n",
" best_season: str\n",
" highlights: list[str]\n",
" estimated_budget_usd: int\n",
"\n",
"\n",
"class TravelRecommendations(BaseModel):\n",
" recommendations: list[DestinationRecommendation]\n",
" personalized_note: str\n",
"\n",
"\n",
"@tool(approval_mode=\"never_require\")\n",
"def get_destination_details(\n",
" destination: Annotated[str, \"The destination to look up\"]\n",
") -> DestinationRecommendation:\n",
" \"\"\"Get structured details about a vacation destination.\"\"\"\n",
" details = {\n",
" \"Barcelona\": DestinationRecommendation(\n",
" destination=\"Barcelona\",\n",
" available=True,\n",
" best_season=\"May-Jun\",\n",
" highlights=[\"Beach\", \"Architecture\", \"Nightlife\"],\n",
" estimated_budget_usd=2000,\n",
" ),\n",
" \"Tokyo\": DestinationRecommendation(\n",
" destination=\"Tokyo\",\n",
" available=True,\n",
" best_season=\"Mar-Apr\",\n",
" highlights=[\"Culture\", \"Food\", \"Technology\"],\n",
" estimated_budget_usd=2500,\n",
" ),\n",
" \"Cape Town\": DestinationRecommendation(\n",
" destination=\"Cape Town\",\n",
" available=False,\n",
" best_season=\"Nov-Mar\",\n",
" highlights=[\"Nature\", \"Wine\", \"Adventure\"],\n",
" estimated_budget_usd=1800,\n",
" ),\n",
" }\n",
" return details.get(\n",
" destination,\n",
" DestinationRecommendation(\n",
" destination=destination,\n",
" available=False,\n",
" best_season=\"Unknown\",\n",
" highlights=[],\n",
" estimated_budget_usd=0,\n",
" ),\n",
" )\n",
"\n",
"\n",
"structured_agent = provider.as_agent(\n",
" name=\"StructuredTravelExpert\",\n",
" instructions=\"You are a travel expert. Recommend destinations based on traveler preferences. Use the get_destination_details tool.\",\n",
" tools=[get_destination_details],\n",
")\n",
"\n",
"# Passing `response_format` forces the agent to return a validated\n",
"# TravelRecommendations object instead of free-form text.\n",
"response = await structured_agent.run(\n",
" \"Recommend 3 destinations for a culture-loving traveler with a $2500 budget\",\n",
" options={\"response_format\": TravelRecommendations},\n",
")\n",
"\n",
"if response and response.value:\n",
" result: TravelRecommendations = response.value\n",
" for rec in result.recommendations:\n",
" status = \"Available\" if rec.available else \"Not available\"\n",
" print(f\"{rec.destination} ({status})\")\n",
" print(f\" Best season: {rec.best_season}\")\n",
" print(f\" Highlights: {', '.join(rec.highlights)}\")\n",
" print(f\" Estimated budget: ${rec.estimated_budget_usd}\")\n",
" print()\n",
" print(f\"Note: {result.personalized_note}\")\n",
"else:\n",
" print(\"No validated structured response was returned.\")\n",
" print(response)\n"
]
},
{
"cell_type": "markdown",
"id": "pattern3-header",
"metadata": {},
"source": [
"## Pattern 3: Single Responsibility Agents\n",
"\n",
"Complex tasks benefit from splitting work across multiple focused agents, each with a single responsibility:\n",
"\n",
"- A **Destination Expert** that knows about places and availability\n",
"- A **Logistics Planner** that handles flights, hotels, and itineraries\n",
"\n",
"This mirrors the software engineering principle of *separation of concerns* — each agent is easier to test, maintain, and improve independently."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pattern3-code",
"metadata": {},
"outputs": [],
"source": [
"destination_agent = provider.as_agent(\n",
" name=\"DestinationExpert\",\n",
" tools=[get_destination_details],\n",
" instructions=\"\"\"You are a destination research specialist. Your only job is to:\n",
"1. Evaluate destinations based on traveler preferences\n",
"2. Check availability using the provided tool\n",
"3. Return a short ranked list with pros/cons\n",
"Do NOT discuss flights, hotels, or logistics — another agent handles that.\"\"\",\n",
")\n",
"\n",
"logistics_agent = provider.as_agent(\n",
" name=\"LogisticsPlanner\",\n",
" instructions=\"\"\"You are a travel logistics planner. Your only job is to:\n",
"1. Create a day-by-day itinerary for the chosen destination\n",
"2. Suggest flight and hotel options within the stated budget\n",
"3. Note visa requirements and travel insurance recommendations\n",
"Do NOT recommend destinations — another agent handles that.\"\"\",\n",
")\n",
"\n",
"# Step 1: Destination Expert picks the best options\n",
"dest_response = await destination_agent.run(\n",
" \"I want a week of culture and food for under $2500. Where should I go?\"\n",
")\n",
"print(\"=== Destination Expert ===\")\n",
"print(dest_response)\n",
"\n",
"# Step 2: Logistics Planner builds the trip plan\n",
"logistics_response = await logistics_agent.run(\n",
" f\"Plan a week-long trip based on this recommendation:\\n{dest_response}\"\n",
")\n",
"print(\"\\n=== Logistics Planner ===\")\n",
"print(logistics_response)"
]
},
{
"cell_type": "markdown",
"id": "summary",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"In this lesson we applied three agentic design patterns to a travel recommender scenario:\n",
"\n",
"| Pattern | Key Idea | Benefit |\n",
"|---|---|---|\n",
"| **Clear Instructions** | Define persona, responsibilities, and constraints up front | Consistent, on-brand agent behavior |\n",
"| **Structured Output** | Use Pydantic models as the response format | Validated, machine-readable results |\n",
"| **Single Responsibility** | Give each agent one focused job | Easier to test, maintain, and compose |\n",
"\n",
"These patterns compose naturally — you can combine clear instructions with structured output inside a single-responsibility agent to build robust, production-ready systems."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

+329
View File
@@ -0,0 +1,329 @@
[![How to Design Good AI Agents](./images/lesson-4-thumbnail.png)](https://youtu.be/vieRiPRx-gI?si=cEZ8ApnT6Sus9rhn)
> _(Click the image above to view video of this lesson)_
# Tool Use Design Pattern
Tools are interesting because they allow AI agents to have a broader range of capabilities. Instead of the agent having a limited set of actions it can perform, by adding a tool, the agent can now perform a wide range of actions. In this chapter, we will look at the Tool Use Design Pattern, which describes how AI agents can use specific tools to achieve their goals.
## Introduction
In this lesson, we're looking to answer the following questions:
- What is the tool use design pattern?
- What are the use cases it can be applied to?
- What are the elements/building blocks needed to implement the design pattern?
- What are the special considerations for using the Tool Use Design Pattern to build trustworthy AI agents?
## Learning Goals
After completing this lesson, you will be able to:
- Define the Tool Use Design Pattern and its purpose.
- Identify use cases where the Tool Use Design Pattern is applicable.
- Understand the key elements needed to implement the design pattern.
- Recognize considerations for ensuring trustworthiness in AI agents using this design pattern.
## What is the Tool Use Design Pattern?
The **Tool Use Design Pattern** focuses on giving LLMs the ability to interact with external tools to achieve specific goals. Tools are code that can be executed by an agent to perform actions. A tool can be a simple function such as a calculator, or an API call to a third-party service such as stock price lookup or weather forecast. In the context of AI agents, tools are designed to be executed by agents in response to **model-generated function calls**.
## What are the use cases it can be applied to?
AI Agents can leverage tools to complete complex tasks, retrieve information, or make decisions. The tool use design pattern is often used in scenarios requiring dynamic interaction with external systems, such as databases, web services, or code interpreters. This ability is useful for a number of different use cases including:
- **Dynamic Information Retrieval:** Agents can query external APIs or databases to fetch up-to-date data (e.g., querying a SQLite database for data analysis, fetching stock prices or weather information).
- **Code Execution and Interpretation:** Agents can execute code or scripts to solve mathematical problems, generate reports, or perform simulations.
- **Workflow Automation:** Automating repetitive or multi-step workflows by integrating tools like task schedulers, email services, or data pipelines.
- **Customer Support:** Agents can interact with CRM systems, ticketing platforms, or knowledge bases to resolve user queries.
- **Content Generation and Editing:** Agents can leverage tools like grammar checkers, text summarizers, or content safety evaluators to assist with content creation tasks.
## What are the elements/building blocks needed to implement the tool use design pattern?
These building blocks allow the AI agent to perform a wide range of tasks. Let's look at the key elements needed to implement the Tool Use Design Pattern:
- **Function/Tool Schemas**: Detailed definitions of available tools, including function name, purpose, required parameters, and expected outputs. These schemas enable the LLM to understand what tools are available and how to construct valid requests.
- **Function Execution Logic**: Governs how and when tools are invoked based on the users intent and conversation context. This may include planner modules, routing mechanisms, or conditional flows that determine tool usage dynamically.
- **Message Handling System**: Components that manage the conversational flow between user inputs, LLM responses, tool calls, and tool outputs.
- **Tool Integration Framework**: Infrastructure that connects the agent to various tools, whether they are simple functions or complex external services.
- **Error Handling & Validation**: Mechanisms to handle failures in tool execution, validate parameters, and manage unexpected responses.
- **State Management**: Tracks conversation context, previous tool interactions, and persistent data to ensure consistency across multi-turn interactions.
Next, let's look at Function/Tool Calling in more detail.
### Function/Tool Calling
Function calling is the primary way we enable Large Language Models (LLMs) to interact with tools. You will often see 'Function' and 'Tool' used interchangeably because 'functions' (blocks of reusable code) are the 'tools' agents use to carry out tasks. In order for a function's code to be invoked, an LLM must compare the users request against the functions description. To do this a schema containing the descriptions of all the available functions is sent to the LLM. The LLM then selects the most appropriate function for the task and returns its name and arguments. The selected function is invoked, it's response is sent back to the LLM, which uses the information to respond to the users request.
For developers to implement function calling for agents, you will need:
1. An LLM model that supports function calling
2. A schema containing function descriptions
3. The code for each function described
Let's use the example of getting the current time in a city to illustrate:
1. **Initialize an LLM that supports function calling:**
Not all models support function calling, so it's important to check that the LLM you are using does. <a href="https://learn.microsoft.com/azure/ai-services/openai/how-to/function-calling" target="_blank">Azure OpenAI</a> supports function calling. We can start by initiating the OpenAI client against the Azure OpenAI **Responses API** (the stable `/openai/v1/` endpoint — no `api_version` needed).
```python
# Initialize the OpenAI client for Azure OpenAI (Responses API, v1 endpoint)
client = OpenAI(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
api_key=os.environ["AZURE_OPENAI_API_KEY"],
)
deployment_name = os.environ["AZURE_OPENAI_DEPLOYMENT"]
```
1. **Create a Function Schema**:
Next we will define a JSON schema that contains the function name, description of what the function does, and the names and descriptions of the function parameters.
We will then take this schema and pass it to the client created previously, along with the users request to find the time in San Francisco. What's important to note is that a **tool call** is what is returned, **not** the final answer to the question. As mentioned earlier, the LLM returns the name of the function it selected for the task, and the arguments that will be passed to it.
```python
# Function description for the model to read (Responses API flat tool format)
tools = [
{
"type": "function",
"name": "get_current_time",
"description": "Get the current time in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city name, e.g. San Francisco",
},
},
"required": ["location"],
},
}
]
```
```python
# Initial user message
messages = [{"role": "user", "content": "What's the current time in San Francisco"}]
# First API call: Ask the model to use the function
response = client.responses.create(
model=deployment_name,
input=messages,
tools=tools,
tool_choice="auto",
store=False,
)
# The Responses API returns tool calls as function_call items in response.output.
# Append them to the conversation so the model has full context on the next turn.
messages += response.output
print("Model's response:")
print(response.output)
```
```bash
Model's response:
[ResponseFunctionToolCall(arguments='{"location":"San Francisco"}', call_id='call_pOsKdUlqvdyttYB67MOj434b', name='get_current_time', type='function_call')]
```
1. **The function code required to carry out the task:**
Now that the LLM has chosen which function needs to be run the code that carries out the task needs to be implemented and executed.
We can implement the code to get the current time in Python. We will also need to write the code to extract the name and arguments from the response_message to get the final result.
```python
def get_current_time(location):
"""Get the current time for a given location"""
print(f"get_current_time called with location: {location}")
location_lower = location.lower()
for key, timezone in TIMEZONE_DATA.items():
if key in location_lower:
print(f"Timezone found for {key}")
current_time = datetime.now(ZoneInfo(timezone)).strftime("%I:%M %p")
return json.dumps({
"location": location,
"current_time": current_time
})
print(f"No timezone data found for {location_lower}")
return json.dumps({"location": location, "current_time": "unknown"})
```
```python
# Handle function calls
tool_calls = [item for item in response.output if item.type == "function_call"]
if tool_calls:
for tool_call in tool_calls:
if tool_call.name == "get_current_time":
function_args = json.loads(tool_call.arguments)
time_response = get_current_time(
location=function_args.get("location")
)
# Return the tool result as a function_call_output item
messages.append({
"type": "function_call_output",
"call_id": tool_call.call_id,
"output": time_response,
})
else:
print("No tool calls were made by the model.")
# Second API call: Get the final response from the model
final_response = client.responses.create(
model=deployment_name,
input=messages,
tools=tools,
store=False,
)
return final_response.output_text
```
```bash
get_current_time called with location: San Francisco
Timezone found for san francisco
The current time in San Francisco is 09:24 AM.
```
Function Calling is at the heart of most, if not all agent tool use design, however implementing it from scratch can sometimes be challenging.
As we learned in [Lesson 2](../02-explore-agentic-frameworks/) agentic frameworks provide us with pre-built building blocks to implement tool use.
## Tool Use Examples with Agentic Frameworks
Here are some examples of how you can implement the Tool Use Design Pattern using different agentic frameworks:
### Microsoft Agent Framework
<a href="https://learn.microsoft.com/azure/ai-services/agents/overview" target="_blank">Microsoft Agent Framework</a> is an open-source AI framework for building AI agents. It simplifies the process of using function calling by allowing you to define tools as Python functions with the `@tool` decorator. The framework handles the back-and-forth communication between the model and your code. It also provides access to pre-built tools like File Search and Code Interpreter through `FoundryChatClient`.
The following diagram illustrates the process of function calling with the Microsoft Agent Framework:
![function calling](./images/functioncalling-diagram.png)
In the Microsoft Agent Framework, tools are defined as decorated functions. We can convert the `get_current_time` function we saw earlier into a tool by using the `@tool` decorator. The framework will automatically serialize the function and its parameters, creating the schema to send to the LLM.
```python
import os
from agent_framework import tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
@tool(approval_mode="never_require")
def get_current_time(location: str) -> str:
"""Get the current time for a given location"""
...
# Create the client
provider = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
# Create an agent and run with the tool
agent = provider.as_agent(name="TimeAgent", instructions="Use available tools to answer questions.", tools=get_current_time)
response = await agent.run("What time is it?")
```
### Microsoft Foundry Agent Service
<a href="https://learn.microsoft.com/azure/ai-services/agents/overview" target="_blank">Microsoft Foundry Agent Service</a> is a newer agentic framework that is designed to empower developers to securely build, deploy, and scale high-quality, and extensible AI agents without needing to manage the underlying compute and storage resources. It is particularly useful for enterprise applications since it is a fully managed service with enterprise grade security.
When compared to developing with the LLM API directly, Microsoft Foundry Agent Service provides some advantages, including:
- Automatic tool calling no need to parse a tool call, invoke the tool, and handle the response; all of this is now done server-side
- Securely managed data instead of managing your own conversation state, you can rely on threads to store all the information you need
- Out-of-the-box tools Tools that you can use to interact with your data sources, such as Bing, Azure AI Search, and Azure Functions.
The tools available in Microsoft Foundry Agent Service can be divided into two categories:
1. Knowledge Tools:
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/bing-grounding?tabs=python&pivots=overview" target="_blank">Grounding with Bing Search</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/file-search?tabs=python&pivots=overview" target="_blank">File Search</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/azure-ai-search?tabs=azurecli%2Cpython&pivots=overview-azure-ai-search" target="_blank">Azure AI Search</a>
2. Action Tools:
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/function-calling?tabs=python&pivots=overview" target="_blank">Function Calling</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/code-interpreter?tabs=python&pivots=overview" target="_blank">Code Interpreter</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/openapi-spec?tabs=python&pivots=overview" target="_blank">OpenAPI defined tools</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/how-to/tools/azure-functions?pivots=overview" target="_blank">Azure Functions</a>
The Agent Service allows us to be able to use these tools together as a `toolset`. It also utilizes `threads` which keep track of the history of messages from a particular conversation.
Imagine you are a sales agent at a company called Contoso. You want to develop a conversational agent that can answer questions about your sales data.
The following image illustrates how you could use Microsoft Foundry Agent Service to analyze your sales data:
![Agentic Service In Action](./images/agent-service-in-action.jpg)
To use any of these tools with the service we can create a client and define a tool or toolset. To implement this practically we can use the following Python code. The LLM will be able to look at the toolset and decide whether to use the user created function, `fetch_sales_data_using_sqlite_query`, or the pre-built Code Interpreter depending on the user request.
```python
import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from fetch_sales_data_functions import fetch_sales_data_using_sqlite_query # fetch_sales_data_using_sqlite_query function which can be found in a fetch_sales_data_functions.py file.
from azure.ai.projects.models import ToolSet, FunctionTool, CodeInterpreterTool
project_client = AIProjectClient.from_connection_string(
credential=DefaultAzureCredential(),
conn_str=os.environ["PROJECT_CONNECTION_STRING"],
)
# Initialize toolset
toolset = ToolSet()
# Initialize function calling agent with the fetch_sales_data_using_sqlite_query function and adding it to the toolset
fetch_data_function = FunctionTool(fetch_sales_data_using_sqlite_query)
toolset.add(fetch_data_function)
# Initialize Code Interpreter tool and adding it to the toolset.
code_interpreter = CodeInterpreterTool()toolset.add(code_interpreter)
agent = project_client.agents.create_agent(
model="gpt-4o-mini", name="my-agent", instructions="You are helpful agent",
toolset=toolset
)
```
## What are the special considerations for using the Tool Use Design Pattern to build trustworthy AI agents?
A common concern with SQL dynamically generated by LLMs is security, particularly the risk of SQL injection or malicious actions, such as dropping or tampering with the database. While these concerns are valid, they can be effectively mitigated by properly configuring database access permissions. For most databases this involves configuring the database as read-only. For database services like PostgreSQL or Azure SQL, the app should be assigned a read-only (SELECT) role.
Running the app in a secure environment further enhances protection. In enterprise scenarios, data is typically extracted and transformed from operational systems into a read-only database or data warehouse with a user-friendly schema. This approach ensures that the data is secure, optimized for performance and accessibility, and that the app has restricted, read-only access.
## Sample Codes
- Python: [Agent Framework](./code_samples/04-python-agent-framework.ipynb)
- .NET: [Agent Framework](./code_samples/04-dotnet-agent-framework.md)
## Got More Questions about the Tool Use Design Patterns?
Join the [Microsoft Foundry Discord](https://discord.com/invite/ATgtXmAS5D) to meet with other learners, attend office hours and get your AI Agents questions answered.
## Additional Resources
- <a href="https://microsoft.github.io/build-your-first-agent-with-azure-ai-agent-service-workshop/" target="_blank">Azure AI Agents Service Workshop</a>
- <a href="https://github.com/Azure-Samples/contoso-creative-writer/tree/main/docs/workshop" target="_blank">Contoso Creative Writer Multi-Agent Workshop</a>
- <a href="https://learn.microsoft.com/azure/ai-services/agents/overview" target="_blank">Microsoft Agent Framework Overview</a>
## Previous Lesson
[Understanding Agentic Design Patterns](../03-agentic-design-patterns/README.md)
## Next Lesson
[Agentic RAG](../05-agentic-rag/README.md)
+280
View File
@@ -0,0 +1,280 @@
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// ============================================================================
// TOOL USE DESIGN PATTERN DEMONSTRATION
// ============================================================================
// This sample demonstrates the key concepts from the Tool Use lesson:
// 1. FUNCTION SCHEMAS: Clear descriptions and typed parameters
// 2. MULTIPLE TOOLS: Agent selects the right tool for each task
// 3. TOOL COMPOSITION: Tools can work together to solve complex requests
// 4. PARAMETER HANDLING: Functions with different parameter types
// ============================================================================
// ----------------------------------------------------------------------------
// TOOL 1: Random Destination Generator (No Parameters)
// ----------------------------------------------------------------------------
// Demonstrates: Simple tool with no parameters
[Description("Provides a random vacation destination when the user hasn't specified one.")]
static string GetRandomDestination()
{
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
var random = new Random();
int index = random.Next(destinations.Count);
Console.WriteLine($"\n[Tool Called] GetRandomDestination() -> {destinations[index]}");
return destinations[index];
}
// ----------------------------------------------------------------------------
// TOOL 2: Weather Lookup (Single Parameter)
// ----------------------------------------------------------------------------
// Demonstrates: Tool with a required parameter - the LLM must extract
// the location from the user's message and pass it to this function
[Description("Gets the current weather conditions for a specified location. Use this when planning activities or packing suggestions.")]
static string GetWeather(
[Description("The city and country to get weather for, e.g., 'Paris, France'")] string location)
{
// Simulated weather data for demonstration
var weatherConditions = new Dictionary<string, (string condition, int tempC, int tempF)>
{
{ "paris", ("Partly Cloudy", 18, 64) },
{ "tokyo", ("Sunny", 24, 75) },
{ "new york", ("Rainy", 15, 59) },
{ "sydney", ("Sunny", 28, 82) },
{ "rome", ("Clear", 22, 72) },
{ "barcelona", ("Sunny", 26, 79) },
{ "cape town", ("Windy", 19, 66) },
{ "rio", ("Hot and Humid", 32, 90) },
{ "bangkok", ("Tropical", 33, 91) },
{ "vancouver", ("Overcast", 12, 54) }
};
var locationLower = location.ToLower();
foreach (var (key, weather) in weatherConditions)
{
if (locationLower.Contains(key))
{
var result = $"Weather in {location}: {weather.condition}, {weather.tempC}°C ({weather.tempF}°F)";
Console.WriteLine($"\n[Tool Called] GetWeather(\"{location}\") -> {result}");
return result;
}
}
var defaultResult = $"Weather in {location}: Mild conditions, around 20°C (68°F)";
Console.WriteLine($"\n[Tool Called] GetWeather(\"{location}\") -> {defaultResult}");
return defaultResult;
}
// ----------------------------------------------------------------------------
// TOOL 3: Destination Information (Multiple Parameters)
// ----------------------------------------------------------------------------
// Demonstrates: Tool with multiple parameters including an enum-like category
[Description("Gets detailed information about a destination including attractions, cuisine, and travel tips. Use this after selecting a destination to provide rich details.")]
static string GetDestinationInfo(
[Description("The destination city to get information about")] string destination,
[Description("The category of information: 'attractions', 'cuisine', 'tips', or 'all'")] string category = "all")
{
Console.WriteLine($"\n[Tool Called] GetDestinationInfo(\"{destination}\", \"{category}\")");
// Simulated destination data
var info = new Dictionary<string, Dictionary<string, string>>
{
{ "paris", new() {
{ "attractions", "Eiffel Tower, Louvre Museum, Notre-Dame, Champs-Élysées" },
{ "cuisine", "Croissants, French onion soup, Coq au vin, Macarons" },
{ "tips", "Metro is efficient, book museums in advance, tip 10-15%" }
}},
{ "tokyo", new() {
{ "attractions", "Senso-ji Temple, Shibuya Crossing, Tokyo Tower, Akihabara" },
{ "cuisine", "Sushi, Ramen, Tempura, Wagyu beef" },
{ "tips", "Get a Suica card, bow when greeting, shoes off indoors" }
}},
{ "rome", new() {
{ "attractions", "Colosseum, Vatican, Trevi Fountain, Pantheon" },
{ "cuisine", "Pasta Carbonara, Pizza al taglio, Gelato, Tiramisu" },
{ "tips", "Book Vatican early, carry water bottle, siesta 1-4pm" }
}}
};
var destLower = destination.ToLower();
foreach (var (key, data) in info)
{
if (destLower.Contains(key))
{
if (category.ToLower() == "all")
{
return JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
}
if (data.TryGetValue(category.ToLower(), out var value))
{
return $"{category} in {destination}: {value}";
}
}
}
return $"Information about {destination}: A wonderful destination with rich culture and experiences.";
}
// ----------------------------------------------------------------------------
// TOOL 4: Trip Cost Estimator (Numeric Parameters)
// ----------------------------------------------------------------------------
// Demonstrates: Tool with numeric parameters for calculations
[Description("Estimates the total trip cost based on destination, duration, and budget level. Returns a cost breakdown.")]
static string EstimateTripCost(
[Description("The destination city")] string destination,
[Description("Number of days for the trip")] int days,
[Description("Budget level: 'budget', 'moderate', or 'luxury'")] string budgetLevel = "moderate")
{
Console.WriteLine($"\n[Tool Called] EstimateTripCost(\"{destination}\", {days}, \"{budgetLevel}\")");
var dailyRates = new Dictionary<string, int>
{
{ "budget", 100 },
{ "moderate", 250 },
{ "luxury", 500 }
};
var rate = dailyRates.GetValueOrDefault(budgetLevel.ToLower(), 250);
var accommodation = rate * days;
var food = (rate / 2) * days;
var activities = (rate / 3) * days;
var total = accommodation + food + activities;
return $"""
Trip Cost Estimate for {destination} ({days} days, {budgetLevel}):
- Accommodation: ${accommodation}
- Food & Dining: ${food}
- Activities: ${activities}
- Estimated Total: ${total}
""";
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Agent Identity
var AGENT_NAME = "TravelAgent";
// ============================================================================
// AGENT INSTRUCTIONS - Tool Use Expert
// ============================================================================
var AGENT_INSTRUCTIONS = """
You are a travel planning AI Agent with access to multiple specialized tools.
## Available Tools
You have access to these tools - use them appropriately:
1. **GetRandomDestination**: Suggests a random destination (no parameters needed)
2. **GetWeather**: Gets weather for a location (requires: location)
3. **GetDestinationInfo**: Gets attraction/cuisine/tips (requires: destination, optional: category)
4. **EstimateTripCost**: Calculates costs (requires: destination, days; optional: budgetLevel)
## Tool Usage Guidelines
- **Chain tools together** for comprehensive responses (e.g., destination weather info cost)
- **Extract parameters** from the user's message to pass to tools
- **Choose the right tool** based on what the user is asking
- When the user asks for a "complete trip plan", use multiple tools
## Response Format
After using tools, synthesize the information into a helpful, well-organized response.
Always mention which tools you used so users understand the agent's capabilities.
""";
// Create AI Agent with Multiple Tools
// This demonstrates the Tool Use Design Pattern with a variety of tool types
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
name: AGENT_NAME,
instructions: AGENT_INSTRUCTIONS,
tools: [
AIFunctionFactory.Create(GetRandomDestination),
AIFunctionFactory.Create(GetWeather),
AIFunctionFactory.Create(GetDestinationInfo),
AIFunctionFactory.Create(EstimateTripCost)
]
);
// Create Conversation Session
AgentSession session = await agent.CreateSessionAsync();
// ============================================================================
// DEMONSTRATION 1: Tool Selection - Agent picks the right tool
// ============================================================================
Console.WriteLine("=== Tool Use Design Pattern Demonstration ===\n");
Console.WriteLine("--- Demo 1: Tool Selection ---");
Console.WriteLine("User: What's the weather like in Tokyo?\n");
Console.WriteLine("Agent Response:");
await foreach (var update in agent.RunStreamingAsync("What's the weather like in Tokyo?", session))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine("\n");
// ============================================================================
// DEMONSTRATION 2: Parameterized Tool Calling
// ============================================================================
Console.WriteLine("--- Demo 2: Parameterized Tool with Multiple Parameters ---");
Console.WriteLine("User: How much would a 5-day luxury trip to Rome cost?\n");
Console.WriteLine("Agent Response:");
await foreach (var update in agent.RunStreamingAsync("How much would a 5-day luxury trip to Rome cost?", session))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine("\n");
// ============================================================================
// DEMONSTRATION 3: Tool Composition - Multiple tools for complex requests
// ============================================================================
Console.WriteLine("--- Demo 3: Tool Composition ---");
Console.WriteLine("User: Plan me a complete trip - suggest a destination and give me all the details including weather and costs for a 3-day moderate budget trip.\n");
Console.WriteLine("Agent Response:");
await foreach (var update in agent.RunStreamingAsync(
"Plan me a complete trip - suggest a destination and give me all the details including weather and costs for a 3-day moderate budget trip.",
session))
{
await Task.Delay(10);
Console.Write(update);
}
@@ -0,0 +1,237 @@
# 🛠️ Advanced Tool Use with Azure OpenAI (Responses API) (.NET)
## 📋 Learning Objectives
This notebook demonstrates enterprise-grade tool integration patterns using the Microsoft Agent Framework in .NET with Azure OpenAI (Responses API). You'll learn to build sophisticated agents with multiple specialized tools, leveraging C#'s strong typing and .NET's enterprise features.
### Advanced Tool Capabilities You'll Master
- 🔧 **Multi-Tool Architecture**: Building agents with multiple specialized capabilities
- 🎯 **Type-Safe Tool Execution**: Leveraging C#'s compile-time validation
- 📊 **Enterprise Tool Patterns**: Production-ready tool design and error handling
- 🔗 **Tool Composition**: Combining tools for complex business workflows
## 🎯 .NET Tool Architecture Benefits
### Enterprise Tool Features
- **Compile-Time Validation**: Strong typing ensures tool parameter correctness
- **Dependency Injection**: IoC container integration for tool management
- **Async/Await Patterns**: Non-blocking tool execution with proper resource management
- **Structured Logging**: Built-in logging integration for tool execution monitoring
### Production-Ready Patterns
- **Exception Handling**: Comprehensive error management with typed exceptions
- **Resource Management**: Proper disposal patterns and memory management
- **Performance Monitoring**: Built-in metrics and performance counters
- **Configuration Management**: Type-safe configuration with validation
## 🔧 Technical Architecture
### Core .NET Tool Components
- **Microsoft.Extensions.AI**: Unified tool abstraction layer
- **Microsoft.Agents.AI**: Enterprise-grade tool orchestration
- **Azure OpenAI (Responses API)**: High-performance API client with connection pooling
### Tool Execution Pipeline
```mermaid
graph LR
A[User Request] --> B[Agent Analysis]
B --> C[Tool Selection]
C --> D[Type Validation]
B --> E[Parameter Binding]
E --> F[Tool Execution]
C --> F
F --> G[Result Processing]
D --> G
G --> H[Response]
```
## 🛠️ Tool Categories & Patterns
### 1. **Data Processing Tools**
- **Input Validation**: Strong typing with data annotations
- **Transform Operations**: Type-safe data conversion and formatting
- **Business Logic**: Domain-specific calculation and analysis tools
- **Output Formatting**: Structured response generation
### 2. **Integration Tools**
- **API Connectors**: RESTful service integration with HttpClient
- **Database Tools**: Entity Framework integration for data access
- **File Operations**: Secure file system operations with validation
- **External Services**: Third-party service integration patterns
### 3. **Utility Tools**
- **Text Processing**: String manipulation and formatting utilities
- **Date/Time Operations**: Culture-aware date/time calculations
- **Mathematical Tools**: Precision calculations and statistical operations
- **Validation Tools**: Business rule validation and data verification
Ready to build enterprise-grade agents with powerful, type-safe tool capabilities in .NET? Let's architect some professional-grade solutions! 🏢⚡
## 🚀 Getting Started
### Prerequisites
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or higher
- An [Azure subscription](https://azure.microsoft.com/free/) with an Azure OpenAI resource and a model deployment
- The [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) — sign in with `az login`
### Required Environment Variables
```bash
# zsh/bash
export AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
export AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
# Then sign in so AzureCliCredential can get a token
az login
```
```powershell
# PowerShell
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com"
$env:AZURE_OPENAI_DEPLOYMENT = "gpt-4o-mini"
# Then sign in so AzureCliCredential can get a token
az login
```
### Sample Code
To run the code example,
```bash
# zsh/bash
chmod +x ./04-dotnet-agent-framework.cs
./04-dotnet-agent-framework.cs
```
Or using the dotnet CLI:
```bash
dotnet run ./04-dotnet-agent-framework.cs
```
See [`04-dotnet-agent-framework.cs`](./04-dotnet-agent-framework.cs) for the complete code.
```csharp
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@10.*
#:package Microsoft.Agents.AI.OpenAI@1.*-*
#:package Azure.AI.OpenAI@2.1.0
#:package Azure.Identity@1.13.1
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
// Tool Function: Random Destination Generator
// This static method will be available to the agent as a callable tool
// The [Description] attribute helps the AI understand when to use this function
// This demonstrates how to create custom tools for AI agents
[Description("Provides a random vacation destination.")]
static string GetRandomDestination()
{
// List of popular vacation destinations around the world
// The agent will randomly select from these options
var destinations = new List<string>
{
"Paris, France",
"Tokyo, Japan",
"New York City, USA",
"Sydney, Australia",
"Rome, Italy",
"Barcelona, Spain",
"Cape Town, South Africa",
"Rio de Janeiro, Brazil",
"Bangkok, Thailand",
"Vancouver, Canada"
};
// Generate random index and return selected destination
// Uses System.Random for simple random selection
var random = new Random();
int index = random.Next(destinations.Count);
return destinations[index];
}
// Azure OpenAI with the Responses API (stable v1 endpoint). Sign in with `az login`.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var azureClient = new AzureOpenAIClient(new Uri(azureEndpoint), new AzureCliCredential());
// Define Agent Identity and Comprehensive Instructions
// Agent name for identification and logging purposes
var AGENT_NAME = "TravelAgent";
// Detailed instructions that define the agent's personality, capabilities, and behavior
// This system prompt shapes how the agent responds and interacts with users
var AGENT_INSTRUCTIONS = """
You are a helpful AI Agent that can help plan vacations for customers.
Important: When users specify a destination, always plan for that location. Only suggest random destinations when the user hasn't specified a preference.
When the conversation begins, introduce yourself with this message:
"Hello! I'm your TravelAgent assistant. I can help plan vacations and suggest interesting destinations for you. Here are some things you can ask me:
1. Plan a day trip to a specific location
2. Suggest a random vacation destination
3. Find destinations with specific features (beaches, mountains, historical sites, etc.)
4. Plan an alternative trip if you don't like my first suggestion
What kind of trip would you like me to help you plan today?"
Always prioritize user preferences. If they mention a specific destination like "Bali" or "Paris," focus your planning on that location rather than suggesting alternatives.
""";
// Create AI Agent with Advanced Travel Planning Capabilities
// Get the Responses client for the deployment and create the AI agent
// Configure agent with name, detailed instructions, and available tools
// This demonstrates the .NET agent creation pattern with full configuration
AIAgent agent = azureClient
.GetOpenAIResponseClient(deployment)
.CreateAIAgent(
name: AGENT_NAME,
instructions: AGENT_INSTRUCTIONS,
tools: [AIFunctionFactory.Create(GetRandomDestination)]
);
// Create New Conversation Thread for Context Management
// Initialize a new conversation thread to maintain context across multiple interactions
// Threads enable the agent to remember previous exchanges and maintain conversational state
// This is essential for multi-turn conversations and contextual understanding
AgentThread thread = agent.GetNewThread();
// Execute Agent: First Travel Planning Request
// Run the agent with an initial request that will likely trigger the random destination tool
// The agent will analyze the request, use the GetRandomDestination tool, and create an itinerary
// Using the thread parameter maintains conversation context for subsequent interactions
await foreach (var update in agent.RunStreamingAsync("Plan me a day trip", thread))
{
await Task.Delay(10);
Console.Write(update);
}
Console.WriteLine();
// Execute Agent: Follow-up Request with Context Awareness
// Demonstrate contextual conversation by referencing the previous response
// The agent remembers the previous destination suggestion and will provide an alternative
// This showcases the power of conversation threads and contextual understanding in .NET agents
await foreach (var update in agent.RunStreamingAsync("I don't like that destination. Plan me another vacation.", thread))
{
await Task.Delay(10);
Console.Write(update);
}
```
@@ -0,0 +1,307 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8744544f",
"metadata": {},
"source": [
"# Lesson 04 - Tool Use Design Pattern\n",
"\n",
"In this lesson you will learn the **Tool Use** design pattern for AI agents using the Microsoft Agent Framework (Python). We cover:\n",
"\n",
"- Defining function tools with the `@tool` decorator and typed parameters\n",
"- Providing tool schemas so the model knows what each tool does\n",
"- Controlling tool execution with `approval_mode`\n",
"- Returning **structured output** via Pydantic models and `response_format`\n",
"\n",
"The scenario is a **travel booking agent** that can look up destinations, check availability, and retrieve flight information."
]
},
{
"cell_type": "markdown",
"id": "b1a2c3d4",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59c0feeb",
"metadata": {},
"outputs": [],
"source": [
"%pip install agent-framework azure-ai-projects azure-identity python-dotenv -U -q"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c0df8a52",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger(\"agent_framework.foundry\").setLevel(logging.ERROR)\n",
"\n",
"import os\n",
"import asyncio\n",
"import dotenv\n",
"from typing import Annotated\n",
"\n",
"from pydantic import BaseModel\n",
"from agent_framework import tool\n",
"from agent_framework.foundry import FoundryChatClient\n",
"from azure.identity import DefaultAzureCredential\n",
"\n",
"dotenv.load_dotenv(dotenv.find_dotenv())\n",
"\n",
"endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
"deployment_name = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
"\n",
"missing = [k for k, v in {\n",
" \"AZURE_AI_PROJECT_ENDPOINT\": endpoint,\n",
" \"AZURE_AI_MODEL_DEPLOYMENT_NAME\": deployment_name\n",
"}.items() if not v]\n",
"\n",
"if missing:\n",
" raise ValueError(\n",
" f\"Missing required environment variables: {', '.join(missing)}. \"\n",
" \"Please set them as environment variables (e.g., in your .env file or shell environment).\"\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6141584",
"metadata": {},
"outputs": [],
"source": [
"# Create the Microsoft Foundry client\n",
"client = FoundryChatClient(\n",
" project_endpoint=endpoint,\n",
" model=deployment_name,\n",
" credential=DefaultAzureCredential()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d5e6f7a8",
"metadata": {},
"source": [
"## Defining Tools with the @tool Decorator\n",
"\n",
"The `@tool` decorator turns a plain Python function into a tool that an agent can call.\n",
"Key points:\n",
"\n",
"- The **docstring** becomes the tool description the model sees.\n",
"- **Type annotations** (including `Annotated` with descriptions) define the tool schema.\n",
"- `approval_mode` controls whether the user must approve each call before it executes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6507f83",
"metadata": {},
"outputs": [],
"source": [
"@tool(approval_mode=\"never_require\")\n",
"def get_destinations() -> list[str]:\n",
" \"\"\"Get available vacation destinations.\"\"\"\n",
" return [\"Barcelona\", \"Paris\", \"Berlin\", \"Tokyo\", \"Sydney\", \"New York City\"]\n",
"\n",
"\n",
"@tool(approval_mode=\"never_require\")\n",
"def check_availability(\n",
" destination: Annotated[str, \"The destination to check\"],\n",
") -> str:\n",
" \"\"\"Check booking availability for a destination.\"\"\"\n",
" availability = {\n",
" \"Barcelona\": \"Available - 3 spots left\",\n",
" \"Paris\": \"Available\",\n",
" \"Berlin\": \"Sold out\",\n",
" \"Tokyo\": \"Available - 1 spot left\",\n",
" \"Sydney\": \"Available\",\n",
" \"New York City\": \"Available\",\n",
" }\n",
" return availability.get(destination, \"Unknown destination\")\n",
"\n",
"\n",
"@tool(approval_mode=\"never_require\")\n",
"def get_flight_info(\n",
" origin: Annotated[str, \"Origin airport code\"],\n",
" destination: Annotated[str, \"Destination airport code\"],\n",
") -> str:\n",
" \"\"\"Get flight information between two cities.\"\"\"\n",
" flights = {\n",
" \"LHR-BCN\": \"BA 2042, Departs 08:30, Arrives 11:45, $350\",\n",
" \"LHR-CDG\": \"AF 1081, Departs 09:15, Arrives 11:30, $280\",\n",
" \"LHR-NRT\": \"JL 044, Departs 11:00, Arrives 07:00+1, $890\",\n",
" }\n",
" return flights.get(\n",
" f\"{origin}-{destination}\",\n",
" f\"No direct flights from {origin} to {destination}\",\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "e9f0a1b2",
"metadata": {},
"source": [
"## Creating an Agent with Multiple Tools\n",
"\n",
"Pass all three tools to the client so the model can invoke whichever ones it needs to answer the user's question."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be18ac4f",
"metadata": {},
"outputs": [],
"source": [
"travel_tools = [get_destinations, check_availability, get_flight_info]\n",
"\n",
"agent = client.as_agent(\n",
" name=\"TravelToolAgent\",\n",
" instructions=\"You are a travel agent. Use the available tools to answer questions about destinations, availability, and flights.\",\n",
" tools=travel_tools,\n",
")\n",
"\n",
"response = await agent.run(\n",
" \"What destinations do you have? Which ones are still available?\"\n",
")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "c3d4e5f6",
"metadata": {},
"source": [
"## Structured Output with Tools\n",
"\n",
"By setting `response_format` to a Pydantic model, the agent is forced to return a well-typed JSON object instead of free-form text. This is useful when downstream code needs to consume the result programmatically."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "772e9481",
"metadata": {},
"outputs": [],
"source": [
"class BookingRecommendation(BaseModel):\n",
" destination: str\n",
" available: bool\n",
" flight_details: str\n",
" estimated_cost: int\n",
"\n",
"\n",
"class TravelPlan(BaseModel):\n",
" recommendations: list[BookingRecommendation]\n",
"\n",
"\n",
"structured_agent = client.as_agent(\n",
" name=\"StructuredTravelAgent\",\n",
" instructions=(\n",
" \"You are a travel agent. Use the available tools to find destinations, \"\n",
" \"check availability, and get flight info. Return structured results.\"\n",
" ),\n",
" tools=[get_destinations, check_availability, get_flight_info],\n",
")\n",
"\n",
"response = await structured_agent.run(\n",
" \"I want to fly from London Heathrow to somewhere warm in Europe. \"\n",
" \"Check what's available.\"\n",
")\n",
"if response:\n",
" print(response)"
]
},
{
"cell_type": "markdown",
"id": "a7b8c9d0",
"metadata": {},
"source": [
"## Tool Approval Patterns\n",
"\n",
"The `approval_mode` parameter on `@tool` controls whether tool calls require human approval before executing:\n",
"\n",
"| Mode | Behaviour |\n",
"|---|---|\n",
"| `\"never_require\"` | Tool runs automatically — no user confirmation needed. |\n",
"| `\"always_require\"` | Every call must be approved by the user before it executes. |\n",
"\n",
"Use `\"always_require\"` for tools that have side-effects (e.g. booking a flight, charging a credit card) so a human stays in the loop."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a731b547",
"metadata": {},
"outputs": [],
"source": [
"@tool(approval_mode=\"always_require\")\n",
"def book_flight(\n",
" origin: Annotated[str, \"Origin airport code\"],\n",
" destination: Annotated[str, \"Destination airport code\"],\n",
" passenger_name: Annotated[str, \"Full name of the passenger\"],\n",
") -> str:\n",
" \"\"\"Book a flight for a passenger. Requires approval before executing.\"\"\"\n",
" return (\n",
" f\"Flight booked from {origin} to {destination} \"\n",
" f\"for {passenger_name}. Confirmation #TRV-2024-{hash(passenger_name) % 10000:04d}\"\n",
" )\n",
"\n",
"\n",
"print(\"Tool name:\", book_flight.name)\n",
"print(\"Approval mode:\", book_flight.approval_mode)"
]
},
{
"cell_type": "markdown",
"id": "f1e2d3c4",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"In this lesson you learned how to:\n",
"\n",
"1. **Define tools** using the `@tool` decorator with typed parameters and docstrings that serve as the tool schema.\n",
"2. **Compose multiple tools** so the agent can call them in sequence to answer complex queries.\n",
"3. **Return structured output** by passing a Pydantic model as `response_format`.\n",
"4. **Control tool approval** with `approval_mode` to keep a human in the loop for sensitive operations.\n",
"\n",
"These patterns form the foundation for building reliable, production-ready agents that can interact with external systems safely."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,34 @@
from demo_tool_agent import TimePlugin, CalculatorPlugin
def test_calculator_add():
calc = CalculatorPlugin()
result = calc.add(5, 7)
assert result == 12
print("test_calculator_add passed")
def test_calculator_subtract():
calc = CalculatorPlugin()
result = calc.subtract(10, 4)
assert result == 6
print("test_calculator_subtract passed")
def test_time_plugin():
# We can't easily test exact time, but we can check format
time_plugin = TimePlugin()
time_str = time_plugin.get_current_time()
# Expect format YYYY-MM-DD HH:MM:SS
assert len(time_str) == 19
assert "-" in time_str
assert ":" in time_str
print("test_time_plugin passed")
if __name__ == "__main__":
try:
test_calculator_add()
test_calculator_subtract()
test_time_plugin()
print("All tests passed!")
except AssertionError as e:
print(f"Test failed: {e}")
except Exception as e:
print(f"An error occurred: {e}")
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

+146
View File
@@ -0,0 +1,146 @@
[![Agentic RAG](./images/lesson-5-thumbnail.png)](https://youtu.be/WcjAARvdL7I?si=BCgwjwFb2yCkEhR9)
> _(Click the image above to view video of this lesson)_
# Agentic RAG
This lesson provides a comprehensive overview of Agentic Retrieval-Augmented Generation (Agentic RAG), an emerging AI paradigm where large language models (LLMs) autonomously plan their next steps while pulling information from external sources. Unlike static retrieval-then-read patterns, Agentic RAG involves iterative calls to the LLM, interspersed with tool or function calls and structured outputs. The system evaluates results, refines queries, invokes additional tools if needed, and continues this cycle until a satisfactory solution is achieved.
## Introduction
This lesson will cover
- **Understand Agentic RAG:** Learn about the emerging paradigm in AI where large language models (LLMs) autonomously plan their next steps while pulling information from external data sources.
- **Grasp Iterative Maker-Checker Style:** Comprehend the loop of iterative calls to the LLM, interspersed with tool or function calls and structured outputs, designed to improve correctness and handle malformed queries.
- **Explore Practical Applications:** Identify scenarios where Agentic RAG shines, such as correctness-first environments, complex database interactions, and extended workflows.
## Learning Goals
After completing this lesson, you will know how to/understand:
- **Understanding Agentic RAG:** Learn about the emerging paradigm in AI where large language models (LLMs) autonomously plan their next steps while pulling information from external data sources.
- **Iterative Maker-Checker Style:** Grasp the concept of a loop of iterative calls to the LLM, interspersed with tool or function calls and structured outputs, designed to improve correctness and handle malformed queries.
- **Owning the Reasoning Process:** Comprehend the system's ability to own its reasoning process, making decisions on how to approach problems without relying on pre-defined paths.
- **Workflow:** Understand how an agentic model independently decides to retrieve market trend reports, identify competitor data, correlate internal sales metrics, synthesize findings, and evaluate the strategy.
- **Iterative Loops, Tool Integration, and Memory:** Learn about the system's reliance on a looped interaction pattern, maintaining state and memory across steps to avoid repetitive loops and make informed decisions.
- **Handling Failure Modes and Self-Correction:** Explore the system's robust self-correction mechanisms, including iterating and re-querying, using diagnostic tools, and falling back on human oversight.
- **Boundaries of Agency:** Understand the limitations of Agentic RAG, focusing on domain-specific autonomy, infrastructure dependence, and respect for guardrails.
- **Practical Use Cases and Value:** Identify scenarios where Agentic RAG shines, such as correctness-first environments, complex database interactions, and extended workflows.
- **Governance, Transparency, and Trust:** Learn about the importance of governance and transparency, including explainable reasoning, bias control, and human oversight.
## What is Agentic RAG?
Agentic Retrieval-Augmented Generation (Agentic RAG) is an emerging AI paradigm where large language models (LLMs) autonomously plan their next steps while pulling information from external sources. Unlike static retrieval-then-read patterns, Agentic RAG involves iterative calls to the LLM, interspersed with tool or function calls and structured outputs. The system evaluates results, refines queries, invokes additional tools if needed, and continues this cycle until a satisfactory solution is achieved. This iterative “maker-checker” style improves correctness, handles malformed queries, and ensures high-quality results.
The system actively owns its reasoning process, rewriting failed queries, choosing different retrieval methods, and integrating multiple tools—such as vector search in Azure AI Search, SQL databases, or custom APIs—before finalizing its answer. The distinguishing quality of an agentic system is its ability to own its reasoning process. Traditional RAG implementations rely on pre-defined paths, but an agentic system autonomously determines the sequence of steps based on the quality of the information it finds.
## Defining Agentic Retrieval-Augmented Generation (Agentic RAG)
Agentic Retrieval-Augmented Generation (Agentic RAG) is an emerging paradigm in AI development where LLMs not only pull information from external data sources but also autonomously plan their next steps. Unlike static retrieval-then-read patterns or carefully scripted prompt sequences, Agentic RAG involves a loop of iterative calls to the LLM, interspersed with tool or function calls and structured outputs. At every turn, the system evaluates the results it has obtained, decides whether to refine its queries, invokes additional tools if needed, and continues this cycle until it achieves a satisfactory solution.
This iterative “maker-checker” style of operation is designed to improve correctness, handle malformed queries to structured databases (e.g. NL2SQL), and ensure balanced, high-quality results. Rather than relying solely on carefully engineered prompt chains, the system actively owns its reasoning process. It can rewrite queries that fail, choose different retrieval methods, and integrate multiple tools—such as vector search in Azure AI Search, SQL databases, or custom APIs—before finalizing its answer. This removes the need for overly complex orchestration frameworks. Instead, a relatively simple loop of “LLM call → tool use → LLM call → …” can yield sophisticated and well-grounded outputs.
![Agentic RAG Core Loop](./images/agentic-rag-core-loop.png)
## Owning the Reasoning Process
The distinguishing quality that makes a system “agentic” is its ability to own its reasoning process. Traditional RAG implementations often depend on humans pre-defining a path for the model: a chain-of-thought that outlines what to retrieve and when.
But when a system is truly agentic, it internally decides how to approach the problem. Its not just executing a script; its autonomously determining the sequence of steps based on the quality of the information it finds.
For example, if its asked to create a product launch strategy, it doesnt rely solely on a prompt that spells out the entire research and decision-making workflow. Instead, the agentic model independently decides to:
1. Retrieve current market trend reports using Bing Web Grounding
2. Identify relevant competitor data using Azure AI Search.
3. Correlate historical internal sales metrics using Azure SQL Database.
4. Synthesize the findings into a cohesive strategy orchestrated via Azure OpenAI Service.
5. Evaluate the strategy for gaps or inconsistencies, prompting another round of retrieval if necessary.
All of these steps—refining queries, choosing sources, iterating until “happy” with the answer—are decided by the model, not pre-scripted by a human.
## Iterative Loops, Tool Integration, and Memory
![Tool Integration Architecture](./images/tool-integration.png)
An agentic system relies on a looped interaction pattern:
- **Initial Call:** The users goal (aka. user prompt) is presented to the LLM.
- **Tool Invocation:** If the model identifies missing information or ambiguous instructions, it selects a tool or retrieval method—like a vector database query (e.g. Azure AI Search Hybrid search over private data) or a structured SQL call—to gather more context.
- **Assessment & Refinement:** After reviewing the returned data, the model decides whether the information suffices. If not, it refines the query, tries a different tool, or adjusts its approach.
- **Repeat Until Satisfied:** This cycle continues until the model determines that it has enough clarity and evidence to deliver a final, well-reasoned response.
- **Memory & State:** Because the system maintains state and memory across steps, it can recall previous attempts and their outcomes, avoiding repetitive loops and making more informed decisions as it proceeds.
Over time, this creates a sense of evolving understanding, enabling the model to navigate complex, multi-step tasks without requiring a human to constantly intervene or reshape the prompt.
## Handling Failure Modes and Self-Correction
Agentic RAGs autonomy also involves robust self-correction mechanisms. When the system hits dead ends—such as retrieving irrelevant documents or encountering malformed queries—it can:
- **Iterate and Re-Query:** Instead of returning low-value responses, the model attempts new search strategies, rewrites database queries, or looks at alternative data sets.
- **Use Diagnostic Tools:** The system may invoke additional functions designed to help it debug its reasoning steps or confirm the correctness of retrieved data. Tools like Azure AI Tracing will be important to enable robust observability and monitoring.
- **Fallback on Human Oversight:** For high-stakes or repeatedly failing scenarios, the model might flag uncertainty and request human guidance. Once the human provides corrective feedback, the model can incorporate that lesson going forward.
This iterative and dynamic approach allows the model to improve continuously, ensuring that its not just a one-shot system but one that learns from its missteps during a given session.
![Self Correction Mechanism](./images/self-correction.png)
## Boundaries of Agency
Despite its autonomy within a task, Agentic RAG is not analogous to Artificial General Intelligence. Its “agentic” capabilities are confined to the tools, data sources, and policies provided by human developers. It cant invent its own tools or step outside the domain boundaries that have been set. Rather, it excels at dynamically orchestrating the resources at hand.
Key differences from more advanced AI forms include:
1. **Domain-Specific Autonomy:** Agentic RAG systems are focused on achieving user-defined goals within a known domain, employing strategies like query rewriting or tool selection to improve outcomes.
2. **Infrastructure-Dependent:** The systems capabilities hinge on the tools and data integrated by developers. It cant surpass these boundaries without human intervention.
3. **Respect for Guardrails:** Ethical guidelines, compliance rules, and business policies remain very important. The agents freedom is always constrained by safety measures and oversight mechanisms (hopefully?)
## Practical Use Cases and Value
Agentic RAG shines in scenarios requiring iterative refinement and precision:
1. **Correctness-First Environments:** In compliance checks, regulatory analysis, or legal research, the agentic model can repeatedly verify facts, consult multiple sources, and rewrite queries until it produces a thoroughly vetted answer.
2. **Complex Database Interactions:** When dealing with structured data where queries might often fail or need adjustment, the system can autonomously refine its queries using Azure SQL or Microsoft Fabric OneLake, ensuring the final retrieval aligns with the users intent.
3. **Extended Workflows:** Longer-running sessions might evolve as new information surfaces. Agentic RAG can continuously incorporate new data, shifting strategies as it learns more about the problem space.
## Governance, Transparency, and Trust
As these systems become more autonomous in their reasoning, governance and transparency are crucial:
- **Explainable Reasoning:** The model can provide an audit trail of the queries it made, the sources it consulted, and the reasoning steps it took to reach its conclusion. Tools like Azure AI Content Safety and Azure AI Tracing / GenAIOps can help maintain transparency and mitigate risks.
- **Bias Control and Balanced Retrieval:** Developers can tune retrieval strategies to ensure balanced, representative data sources are considered, and regularly audit outputs to detect bias or skewed patterns using custom models for advanced data science organizations using Azure Machine Learning.
- **Human Oversight and Compliance:** For sensitive tasks, human review remains essential. Agentic RAG doesnt replace human judgment in high-stakes decisions—it augments it by delivering more thoroughly vetted options.
Having tools that provide a clear record of actions is essential. Without them, debugging a multi-step process can be very difficult. See the following example from Literal AI (company behind Chainlit) for an Agent run:
![AgentRunExample](./images/AgentRunExample.png)
## Conclusion
Agentic RAG represents a natural evolution in how AI systems handle complex, data-intensive tasks. By adopting a looped interaction pattern, autonomously selecting tools, and refining queries until achieving a high-quality result, the system moves beyond static prompt-following into a more adaptive, context-aware decision-maker. While still bounded by human-defined infrastructures and ethical guidelines, these agentic capabilities enable richer, more dynamic, and ultimately more useful AI interactions for both enterprises and end-users.
### Got More Questions about Agentic RAG?
Join the [Microsoft Foundry Discord](https://discord.com/invite/ATgtXmAS5D) to meet with other learners, attend office hours and get your AI Agents questions answered.
## Additional Resources
- <a href="https://learn.microsoft.com/training/modules/use-own-data-azure-openai" target="_blank">Implement Retrieval Augmented Generation (RAG) with Azure OpenAI Service: Learn how to use your own data with the Azure OpenAI Service. This Microsoft Learn module provides a comprehensive guide on implementing RAG</a>
- <a href="https://learn.microsoft.com/azure/ai-studio/concepts/evaluation-approach-gen-ai" target="_blank">Evaluation of generative AI applications with Microsoft Foundry: This article covers the evaluation and comparison of models on publicly available datasets, including Agentic AI applications and RAG architectures</a>
- <a href="https://weaviate.io/blog/what-is-agentic-rag" target="_blank">What is Agentic RAG | Weaviate</a>
- <a href="https://ragaboutit.com/agentic-rag-a-complete-guide-to-agent-based-retrieval-augmented-generation/" target="_blank">Agentic RAG: A Complete Guide to Agent-Based Retrieval Augmented Generation News from generation RAG</a>
- <a href="https://huggingface.co/learn/cookbook/agent_rag" target="_blank">Agentic RAG: turbocharge your RAG with query reformulation and self-query! Hugging Face Open-Source AI Cookbook</a>
- <a href="https://youtu.be/aQ4yQXeB1Ss?si=2HUqBzHoeB5tR04U" target="_blank">Adding Agentic Layers to RAG</a>
- <a href="https://www.youtube.com/watch?v=zeAyuLc_f3Q&t=244s" target="_blank">The Future of Knowledge Assistants: Jerry Liu</a>
- <a href="https://www.youtube.com/watch?v=AOSjiXP1jmQ" target="_blank">How to Build Agentic RAG Systems</a>
- <a href="https://ignite.microsoft.com/sessions/BRK102?source=sessions" target="_blank">Using Microsoft Foundry Agent Service to scale your AI agents</a>
### Academic Papers
- <a href="https://arxiv.org/abs/2303.17651" target="_blank">2303.17651 Self-Refine: Iterative Refinement with Self-Feedback</a>
- <a href="https://arxiv.org/abs/2303.11366" target="_blank">2303.11366 Reflexion: Language Agents with Verbal Reinforcement Learning</a>
- <a href="https://arxiv.org/abs/2305.11738" target="_blank">2305.11738 CRITIC: Large Language Models Can Self-Correct with Tool-Interactive Critiquing</a>
- <a href="https://arxiv.org/abs/2501.09136" target="_blank">2501.09136 Agentic Retrieval-Augmented Generation: A Survey on Agentic RAG</a>
## Previous Lesson
[Tool Use Design Pattern](../04-tool-use/README.md)
## Next Lesson
[Building Trustworthy AI Agents](../06-building-trustworthy-agents/README.md)
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/dotnet run
#:package Microsoft.Extensions.AI@9.9.1
#:package Azure.AI.Agents.Persistent@1.2.0-beta.5
#:package Azure.Identity@1.15.0
#:package System.Linq.Async@6.0.3
#:package Microsoft.Agents.AI.AzureAI@1.0.0-preview.251001.3
#:package Microsoft.Agents.AI@1.0.0-preview.251001.3
#:package DotNetEnv@3.1.1
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Agents.AI;
using DotNetEnv;
// Load environment variables
Env.Load("../../../.env");
// Get Microsoft Foundry configuration
var azure_foundry_endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var azure_foundry_model_id = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4.1-mini";
// Define document path
string pdfPath = "./document.md";
// Helper function to open file stream
async Task<Stream> OpenImageStreamAsync(string path)
{
return await Task.Run(() => File.OpenRead(path));
}
// Open document stream
var pdfStream = await OpenImageStreamAsync(pdfPath);
// Create Persistent Agents Client
var persistentAgentsClient = new PersistentAgentsClient(azure_foundry_endpoint, new AzureCliCredential());
// Upload file
PersistentAgentFileInfo fileInfo = await persistentAgentsClient.Files.UploadFileAsync(pdfStream, PersistentAgentFilePurpose.Agents, "demo.md");
// Create vector store
PersistentAgentsVectorStore fileStore =
await persistentAgentsClient.VectorStores.CreateVectorStoreAsync(
[fileInfo.Id],
metadata: new Dictionary<string, string>() { { "agentkey", bool.TrueString } });
// Create RAG agent
PersistentAgent agentModel = await persistentAgentsClient.Administration.CreateAgentAsync(
azure_foundry_model_id,
name: "DotNetRAGAgent",
tools: [new FileSearchToolDefinition()],
instructions: """
You are an AI assistant designed to answer user questions using only the information retrieved from the provided document(s).
- If a user's question cannot be answered using the retrieved context, **you must clearly respond**:
"I'm sorry, but the uploaded document does not contain the necessary information to answer that question."
- Do not answer from general knowledge or reasoning. Do not make assumptions or generate hypothetical explanations.
- Do not provide definitions, tutorials, or commentary that is not explicitly grounded in the content of the uploaded file(s).
- If a user asks a question like "What is a Neural Network?", and this is not discussed in the uploaded document, respond as instructed above.
- For questions that do have relevant content in the document (e.g., Contoso's travel insurance coverage), respond accurately, and cite the document explicitly.
You must behave as if you have no external knowledge beyond what is retrieved from the uploaded document.
""",
toolResources: new()
{
FileSearch = new()
{
VectorStoreIds = { fileStore.Id },
}
},
metadata: new Dictionary<string, string>() { { "agentkey", bool.TrueString } });
// Get AI Agent
AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(agentModel.Id);
// Create new thread
AgentThread thread = agent.GetNewThread();
// Run query
Console.WriteLine(await agent.RunAsync("Can you explain Contoso's travel insurance coverage?", thread));
@@ -0,0 +1,641 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "44b5899e",
"metadata": {},
"source": [
"# 🔍 Enterprise RAG with Microsoft Foundry (.NET)\n",
"\n",
"## 📋 Learning Objectives\n",
"\n",
"This notebook demonstrates how to build enterprise-grade Retrieval-Augmented Generation (RAG) systems using the Microsoft Agent Framework in .NET with Microsoft Foundry. You'll learn to create production-ready agents that can search through documents and provide accurate, context-aware responses with enterprise security and scalability.\n",
"\n",
"**Enterprise RAG Capabilities You'll Build:**\n",
"- 📚 **Document Intelligence**: Advanced document processing with Azure AI services\n",
"- 🔍 **Semantic Search**: High-performance vector search with enterprise features\n",
"- 🛡️ **Security Integration**: Role-based access and data protection patterns\n",
"- 🏢 **Scalable Architecture**: Production-ready RAG systems with monitoring\n",
"\n",
"## 🎯 Enterprise RAG Architecture\n",
"\n",
"### Core Enterprise Components\n",
"- **Microsoft Foundry**: Managed enterprise AI platform with security and compliance\n",
"- **Persistent Agents**: Stateful agents with conversation history and context management\n",
"- **Vector Store Management**: Enterprise-grade document indexing and retrieval\n",
"- **Identity Integration**: Azure AD authentication and role-based access control\n",
"\n",
"### .NET Enterprise Benefits\n",
"- **Type Safety**: Compile-time validation for RAG operations and data structures\n",
"- **Async Performance**: Non-blocking document processing and search operations\n",
"- **Memory Management**: Efficient resource utilization for large document collections\n",
"- **Integration Patterns**: Native Azure service integration with dependency injection\n",
"\n",
"## 🏗️ Technical Architecture\n",
"\n",
"### Enterprise RAG Pipeline\n",
"```csharp\n",
"Document Upload → Security Validation → Vector Processing → Index Creation\n",
" ↓ ↓ ↓\n",
"User Query → Authentication → Semantic Search → Context Ranking → AI Response\n",
"```\n",
"\n",
"### Core .NET Components\n",
"- **Azure.AI.Agents.Persistent**: Enterprise agent management with state persistence\n",
"- **Azure.Identity**: Integrated authentication for secure Azure service access\n",
"- **Microsoft.Agents.AI.AzureAI**: Azure-optimized agent framework implementation\n",
"- **System.Linq.Async**: High-performance asynchronous LINQ operations\n",
"\n",
"## 🔧 Enterprise Features & Benefits\n",
"\n",
"### Security & Compliance\n",
"- **Azure AD Integration**: Enterprise identity management and authentication\n",
"- **Role-Based Access**: Fine-grained permissions for document access and operations\n",
"- **Data Protection**: Encryption at rest and in transit for sensitive documents\n",
"- **Audit Logging**: Comprehensive activity tracking for compliance requirements\n",
"\n",
"### Performance & Scalability\n",
"- **Connection Pooling**: Efficient Azure service connection management\n",
"- **Async Processing**: Non-blocking operations for high-throughput scenarios\n",
"- **Caching Strategies**: Intelligent caching for frequently accessed documents\n",
"- **Load Balancing**: Distributed processing for large-scale deployments\n",
"\n",
"### Management & Monitoring\n",
"- **Health Checks**: Built-in monitoring for RAG system components\n",
"- **Performance Metrics**: Detailed analytics on search quality and response times\n",
"- **Error Handling**: Comprehensive exception management with retry policies\n",
"- **Configuration Management**: Environment-specific settings with validation\n",
"\n",
"## ⚙️ Prerequisites & Setup\n",
"\n",
"**Development Environment:**\n",
"- .NET 9.0 SDK or higher\n",
"- Visual Studio 2022 or VS Code with C# extension\n",
"- Azure subscription with AI Foundry access\n",
"\n",
"**Required NuGet Packages:**\n",
"```xml\n",
"<PackageReference Include=\"Microsoft.Extensions.AI\" Version=\"9.9.0\" />\n",
"<PackageReference Include=\"Azure.AI.Agents.Persistent\" Version=\"1.2.0-beta.5\" />\n",
"<PackageReference Include=\"Azure.Identity\" Version=\"1.15.0\" />\n",
"<PackageReference Include=\"System.Linq.Async\" Version=\"6.0.3\" />\n",
"<PackageReference Include=\"DotNetEnv\" Version=\"3.1.1\" />\n",
"```\n",
"\n",
"**Azure Authentication Setup:**\n",
"```bash\n",
"# Install Azure CLI and authenticate\n",
"az login\n",
"az account set --subscription \"your-subscription-id\"\n",
"```\n",
"\n",
"**Environment Configuration (.env file):**\n",
"```env\n",
"# Microsoft Foundry configuration (automatically handled via Azure CLI)\n",
"# Ensure you're authenticated to the correct Azure subscription\n",
"```\n",
"\n",
"## 📊 Enterprise RAG Patterns\n",
"\n",
"### Document Management Patterns\n",
"- **Bulk Upload**: Efficient processing of large document collections\n",
"- **Incremental Updates**: Real-time document addition and modification\n",
"- **Version Control**: Document versioning and change tracking\n",
"- **Metadata Management**: Rich document attributes and taxonomy\n",
"\n",
"### Search & Retrieval Patterns\n",
"- **Hybrid Search**: Combining semantic and keyword search for optimal results\n",
"- **Faceted Search**: Multi-dimensional filtering and categorization\n",
"- **Relevance Tuning**: Custom scoring algorithms for domain-specific needs\n",
"- **Result Ranking**: Advanced ranking with business logic integration\n",
"\n",
"### Security Patterns\n",
"- **Document-Level Security**: Fine-grained access control per document\n",
"- **Data Classification**: Automatic sensitivity labeling and protection\n",
"- **Audit Trails**: Comprehensive logging of all RAG operations\n",
"- **Privacy Protection**: PII detection and redaction capabilities\n",
"\n",
"## 🔒 Enterprise Security Features\n",
"\n",
"### Authentication & Authorization\n",
"```csharp\n",
"// Azure AD integrated authentication\n",
"var credential = new AzureCliCredential();\n",
"var agentsClient = new PersistentAgentsClient(endpoint, credential);\n",
"\n",
"// Role-based access validation\n",
"if (!await ValidateUserPermissions(user, documentId))\n",
"{\n",
" throw new UnauthorizedAccessException(\"Insufficient permissions\");\n",
"}\n",
"```\n",
"\n",
"### Data Protection\n",
"- **Encryption**: End-to-end encryption for documents and search indices\n",
"- **Access Controls**: Integration with Azure AD for user and group permissions\n",
"- **Data Residency**: Geographic data location controls for compliance\n",
"- **Backup & Recovery**: Automated backup and disaster recovery capabilities\n",
"\n",
"## 📈 Performance Optimization\n",
"\n",
"### Async Processing Patterns\n",
"```csharp\n",
"// Efficient async document processing\n",
"await foreach (var document in documentStream.AsAsyncEnumerable())\n",
"{\n",
" await ProcessDocumentAsync(document, cancellationToken);\n",
"}\n",
"```\n",
"\n",
"### Memory Management\n",
"- **Streaming Processing**: Handle large documents without memory issues\n",
"- **Resource Pooling**: Efficient reuse of expensive resources\n",
"- **Garbage Collection**: Optimized memory allocation patterns\n",
"- **Connection Management**: Proper Azure service connection lifecycle\n",
"\n",
"### Caching Strategies\n",
"- **Query Caching**: Cache frequently executed searches\n",
"- **Document Caching**: In-memory caching for hot documents\n",
"- **Index Caching**: Optimized vector index caching\n",
"- **Result Caching**: Intelligent caching of generated responses\n",
"\n",
"## 📊 Enterprise Use Cases\n",
"\n",
"### Knowledge Management\n",
"- **Corporate Wiki**: Intelligent search across company knowledge bases\n",
"- **Policy & Procedures**: Automated compliance and procedure guidance\n",
"- **Training Materials**: Intelligent learning and development assistance\n",
"- **Research Databases**: Academic and research paper analysis systems\n",
"\n",
"### Customer Support\n",
"- **Support Knowledge Base**: Automated customer service responses\n",
"- **Product Documentation**: Intelligent product information retrieval\n",
"- **Troubleshooting Guides**: Contextual problem-solving assistance\n",
"- **FAQ Systems**: Dynamic FAQ generation from document collections\n",
"\n",
"### Regulatory Compliance\n",
"- **Legal Document Analysis**: Contract and legal document intelligence\n",
"- **Compliance Monitoring**: Automated regulatory compliance checking\n",
"- **Risk Assessment**: Document-based risk analysis and reporting\n",
"- **Audit Support**: Intelligent document discovery for audits\n",
"\n",
"## 🚀 Production Deployment\n",
"\n",
"### Monitoring & Observability\n",
"- **Application Insights**: Detailed telemetry and performance monitoring\n",
"- **Custom Metrics**: Business-specific KPI tracking and alerting\n",
"- **Distributed Tracing**: End-to-end request tracking across services\n",
"- **Health Dashboards**: Real-time system health and performance visualization\n",
"\n",
"### Scalability & Reliability\n",
"- **Auto-Scaling**: Automatic scaling based on load and performance metrics\n",
"- **High Availability**: Multi-region deployment with failover capabilities\n",
"- **Load Testing**: Performance validation under enterprise load conditions\n",
"- **Disaster Recovery**: Automated backup and recovery procedures\n",
"\n",
"Ready to build enterprise-grade RAG systems that can handle sensitive documents at scale? Let's architect intelligent knowledge systems for the enterprise! 🏢📖✨"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.AI, 9.9.1</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#r \"nuget: Microsoft.Extensions.AI, 9.9.1\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4ec1f0d1",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Azure.AI.Agents.Persistent, 1.2.0-beta.5</span></li><li><span>Azure.Identity, 1.15.0</span></li><li><span>System.Linq.Async, 6.0.3</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#r \"nuget: Azure.AI.Agents.Persistent, 1.2.0-beta.5\"\n",
"#r \"nuget: Azure.Identity, 1.15.0\"\n",
"#r \"nuget: System.Linq.Async, 6.0.3\""
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "2363ae07",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "d10cec9d",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Agents.AI.AzureAI, 1.0.0-preview.251001.2</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#r \"nuget: Microsoft.Agents.AI.AzureAI, 1.0.0-preview.251001.3\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "78199d1c",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>microsoft.agents.ai, 1.0.0-preview.251001.2</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#r \"nuget: Microsoft.Agents.AI, 1.0.0-preview.251001.3\""
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7de4684a",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>DotNetEnv, 3.1.1</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#r \"nuget: DotNetEnv, 3.1.1\""
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "251efd31",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"using System;\n",
"using System.Linq;\n",
"using Azure.AI.Agents.Persistent;\n",
"using Azure.Identity;\n",
"using Microsoft.Agents.AI;"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "a2e342f1",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
" using DotNetEnv;"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "a7a01653",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"Env.Load(\"../../../.env\");"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "a42735d5",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"var azure_foundry_endpoint = Environment.GetEnvironmentVariable(\"AZURE_AI_PROJECT_ENDPOINT\") ?? throw new InvalidOperationException(\"AZURE_AI_PROJECT_ENDPOINT is not set.\");\n",
"var azure_foundry_model_id = Environment.GetEnvironmentVariable(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\") ?? \"gpt-4.1-mini\";"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "e29bdb58",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"string pdfPath = \"./document.md\";"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "7351e12d",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"using System.IO;\n",
"\n",
"async Task<Stream> OpenImageStreamAsync(string path)\n",
"{\n",
"\treturn await Task.Run(() => File.OpenRead(path));\n",
"}\n",
"\n",
"var pdfStream = await OpenImageStreamAsync(pdfPath);"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "0b6bf484",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"var persistentAgentsClient = new PersistentAgentsClient(azure_foundry_endpoint, new AzureCliCredential());"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "81e0dddc",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"PersistentAgentFileInfo fileInfo = await persistentAgentsClient.Files.UploadFileAsync(pdfStream, PersistentAgentFilePurpose.Agents, \"demo.md\");"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "f0c75d80",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"PersistentAgentsVectorStore fileStore =\n",
" await persistentAgentsClient.VectorStores.CreateVectorStoreAsync(\n",
" [fileInfo.Id],\n",
" metadata: new Dictionary<string, string>() { { \"agentkey\", bool.TrueString } });"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "c77986c5",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"PersistentAgent agentModel = await persistentAgentsClient.Administration.CreateAgentAsync(\n",
" azure_foundry_model_id,\n",
" name: \"DotNetRAGAgent\",\n",
" tools: [new FileSearchToolDefinition()],\n",
" instructions: \"\"\"\n",
" You are an AI assistant designed to answer user questions using only the information retrieved from the provided document(s).\n",
"\n",
" - If a user's question cannot be answered using the retrieved context, **you must clearly respond**: \n",
" \"I'm sorry, but the uploaded document does not contain the necessary information to answer that question.\"\n",
" - Do not answer from general knowledge or reasoning. Do not make assumptions or generate hypothetical explanations.\n",
" - Do not provide definitions, tutorials, or commentary that is not explicitly grounded in the content of the uploaded file(s).\n",
" - If a user asks a question like \"What is a Neural Network?\", and this is not discussed in the uploaded document, respond as instructed above.\n",
" - For questions that do have relevant content in the document (e.g., Contoso's travel insurance coverage), respond accurately, and cite the document explicitly.\n",
"\n",
" You must behave as if you have no external knowledge beyond what is retrieved from the uploaded document.\n",
" \"\"\",\n",
" toolResources: new()\n",
" {\n",
" FileSearch = new()\n",
" {\n",
" VectorStoreIds = { fileStore.Id },\n",
" }\n",
" },\n",
" metadata: new Dictionary<string, string>() { { \"agentkey\", bool.TrueString } });"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "282326cf",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(agentModel.Id);"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "2067d313",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [],
"source": [
"AgentThread thread = agent.GetNewThread();"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "454c4230",
"metadata": {
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Contoso's travel insurance coverage includes protection for medical emergencies, trip cancellations, and lost baggage. This ensures that travelers are supported in case of health-related issues during their trip, unforeseen cancellations, and the loss of their belongings while traveling【4:0†demo.md】.\r\n"
]
}
],
"source": [
"Console.WriteLine(await agent.RunAsync(\"Can you explain Contoso's travel insurance coverage?\", thread));"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"name": "polyglot-notebook"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [],
"name": "csharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,216 @@
# 🔍 Enterprise RAG with Microsoft Foundry (.NET)
## 📋 Learning Objectives
This notebook demonstrates how to build enterprise-grade Retrieval-Augmented Generation (RAG) systems using the Microsoft Agent Framework in .NET with Microsoft Foundry. You'll learn to create production-ready agents that can search through documents and provide accurate, context-aware responses with enterprise security and scalability.
**Enterprise RAG Capabilities You'll Build:**
- 📚 **Document Intelligence**: Advanced document processing with Azure AI services
- 🔍 **Semantic Search**: High-performance vector search with enterprise features
- 🛡️ **Security Integration**: Role-based access and data protection patterns
- 🏢 **Scalable Architecture**: Production-ready RAG systems with monitoring
## 🎯 Enterprise RAG Architecture
### Core Enterprise Components
- **Microsoft Foundry**: Managed enterprise AI platform with security and compliance
- **Persistent Agents**: Stateful agents with conversation history and context management
- **Vector Store Management**: Enterprise-grade document indexing and retrieval
- **Identity Integration**: Azure AD authentication and role-based access control
### .NET Enterprise Benefits
- **Type Safety**: Compile-time validation for RAG operations and data structures
- **Async Performance**: Non-blocking document processing and search operations
- **Memory Management**: Efficient resource utilization for large document collections
- **Integration Patterns**: Native Azure service integration with dependency injection
## 🏗️ Technical Architecture
### Enterprise RAG Pipeline
```
Document Upload → Security Validation → Vector Processing → Index Creation
↓ ↓ ↓
User Query → Authentication → Semantic Search → Context Ranking → AI Response
```
### Core .NET Components
- **Azure.AI.Agents.Persistent**: Enterprise agent management with state persistence
- **Azure.Identity**: Integrated authentication for secure Azure service access
- **Microsoft.Agents.AI.AzureAI**: Azure-optimized agent framework implementation
- **System.Linq.Async**: High-performance asynchronous LINQ operations
## 🔧 Enterprise Features & Benefits
### Security & Compliance
- **Azure AD Integration**: Enterprise identity management and authentication
- **Role-Based Access**: Fine-grained permissions for document access and operations
- **Data Protection**: Encryption at rest and in transit for sensitive documents
- **Audit Logging**: Comprehensive activity tracking for compliance requirements
### Performance & Scalability
- **Connection Pooling**: Efficient Azure service connection management
- **Async Processing**: Non-blocking operations for high-throughput scenarios
- **Caching Strategies**: Intelligent caching for frequently accessed documents
- **Load Balancing**: Distributed processing for large-scale deployments
### Management & Monitoring
- **Health Checks**: Built-in monitoring for RAG system components
- **Performance Metrics**: Detailed analytics on search quality and response times
- **Error Handling**: Comprehensive exception management with retry policies
- **Configuration Management**: Environment-specific settings with validation
## ⚙️ Prerequisites & Setup
**Development Environment:**
- .NET 9.0 SDK or higher
- Visual Studio 2022 or VS Code with C# extension
- Azure subscription with Microsoft Foundry access
**Required NuGet Packages:**
```xml
<PackageReference Include="Microsoft.Extensions.AI" Version="9.9.0" />
<PackageReference Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.5" />
<PackageReference Include="Azure.Identity" Version="1.15.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="DotNetEnv" Version="3.1.1" />
```
**Azure Authentication Setup:**
```bash
# Install Azure CLI and authenticate
az login
az account set --subscription "your-subscription-id"
```
**Environment Configuration:**
* Microsoft Foundry configuration (automatically handled via Azure CLI)
* Ensure you're authenticated to the correct Azure subscription
## 📊 Enterprise RAG Patterns
### Document Management Patterns
- **Bulk Upload**: Efficient processing of large document collections
- **Incremental Updates**: Real-time document addition and modification
- **Version Control**: Document versioning and change tracking
- **Metadata Management**: Rich document attributes and taxonomy
### Search & Retrieval Patterns
- **Hybrid Search**: Combining semantic and keyword search for optimal results
- **Faceted Search**: Multi-dimensional filtering and categorization
- **Relevance Tuning**: Custom scoring algorithms for domain-specific needs
- **Result Ranking**: Advanced ranking with business logic integration
### Security Patterns
- **Document-Level Security**: Fine-grained access control per document
- **Data Classification**: Automatic sensitivity labeling and protection
- **Audit Trails**: Comprehensive logging of all RAG operations
- **Privacy Protection**: PII detection and redaction capabilities
## 🔒 Enterprise Security Features
### Authentication & Authorization
```csharp
// Azure AD integrated authentication
var credential = new AzureCliCredential();
var agentsClient = new PersistentAgentsClient(endpoint, credential);
// Role-based access validation
if (!await ValidateUserPermissions(user, documentId))
{
throw new UnauthorizedAccessException("Insufficient permissions");
}
```
### Data Protection
- **Encryption**: End-to-end encryption for documents and search indices
- **Access Controls**: Integration with Azure AD for user and group permissions
- **Data Residency**: Geographic data location controls for compliance
- **Backup & Recovery**: Automated backup and disaster recovery capabilities
## 📈 Performance Optimization
### Async Processing Patterns
```csharp
// Efficient async document processing
await foreach (var document in documentStream.AsAsyncEnumerable())
{
await ProcessDocumentAsync(document, cancellationToken);
}
```
### Memory Management
- **Streaming Processing**: Handle large documents without memory issues
- **Resource Pooling**: Efficient reuse of expensive resources
- **Garbage Collection**: Optimized memory allocation patterns
- **Connection Management**: Proper Azure service connection lifecycle
### Caching Strategies
- **Query Caching**: Cache frequently executed searches
- **Document Caching**: In-memory caching for hot documents
- **Index Caching**: Optimized vector index caching
- **Result Caching**: Intelligent caching of generated responses
## 📊 Enterprise Use Cases
### Knowledge Management
- **Corporate Wiki**: Intelligent search across company knowledge bases
- **Policy & Procedures**: Automated compliance and procedure guidance
- **Training Materials**: Intelligent learning and development assistance
- **Research Databases**: Academic and research paper analysis systems
### Customer Support
- **Support Knowledge Base**: Automated customer service responses
- **Product Documentation**: Intelligent product information retrieval
- **Troubleshooting Guides**: Contextual problem-solving assistance
- **FAQ Systems**: Dynamic FAQ generation from document collections
### Regulatory Compliance
- **Legal Document Analysis**: Contract and legal document intelligence
- **Compliance Monitoring**: Automated regulatory compliance checking
- **Risk Assessment**: Document-based risk analysis and reporting
- **Audit Support**: Intelligent document discovery for audits
## 🚀 Production Deployment
### Monitoring & Observability
- **Application Insights**: Detailed telemetry and performance monitoring
- **Custom Metrics**: Business-specific KPI tracking and alerting
- **Distributed Tracing**: End-to-end request tracking across services
- **Health Dashboards**: Real-time system health and performance visualization
### Scalability & Reliability
- **Auto-Scaling**: Automatic scaling based on load and performance metrics
- **High Availability**: Multi-region deployment with failover capabilities
- **Load Testing**: Performance validation under enterprise load conditions
- **Disaster Recovery**: Automated backup and recovery procedures
Ready to build enterprise-grade RAG systems that can handle sensitive documents at scale? Let's architect intelligent knowledge systems for the enterprise! 🏢📖✨
## Code Implementation
The complete working code sample for this lesson is available in `05-dotnet-agent-framework.cs`.
To run the example:
```bash
# Make the script executable (Linux/macOS)
chmod +x 05-dotnet-agent-framework.cs
# Run the .NET Single File App
./05-dotnet-agent-framework.cs
```
Or use `dotnet run` directly:
```bash
dotnet run 05-dotnet-agent-framework.cs
```
The code demonstrates:
1. **Package Installation**: Installing required NuGet packages for Azure AI Agents
2. **Environment Configuration**: Loading Microsoft Foundry endpoint and model settings
3. **Document Upload**: Uploading a document for RAG processing
4. **Vector Store Creation**: Creating a vector store for semantic search
5. **Agent Configuration**: Setting up an AI agent with file search capabilities
6. **Query Execution**: Running queries against the uploaded document
@@ -0,0 +1,262 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a1b2c3d4",
"metadata": {},
"source": [
"# Lesson 05 - Agentic RAG"
]
},
{
"cell_type": "markdown",
"id": "b2c3d4e5",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"This notebook demonstrates the Agentic RAG (Retrieval-Augmented Generation) pattern using the Microsoft Agent Framework.\n",
"\n",
"**Prerequisites:**\n",
"- `AZURE_SEARCH_SERVICE_ENDPOINT` — your Azure AI Search service endpoint\n",
"- `AZURE_SEARCH_API_KEY` — your Azure AI Search API key\n",
"- Azure OpenAI deployment configured via environment variables\n",
"- Azure CLI authenticated (`az login`)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3d4e5f6",
"metadata": {},
"outputs": [],
"source": [
"%pip install agent-framework azure-ai-projects azure-identity python-dotenv -q"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4e5f6a7",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger(\"agent_framework.foundry\").setLevel(logging.ERROR)\n",
"\n",
"import os\n",
"import asyncio\n",
"import dotenv\n",
"from typing import Annotated\n",
"\n",
"from agent_framework import tool\n",
"from agent_framework.foundry import FoundryChatClient\n",
"from azure.identity import DefaultAzureCredential\n",
"\n",
"dotenv.load_dotenv()\n",
"\n",
"endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
"deployment_name = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
"\n",
"missing = [k for k, v in {\n",
" \"AZURE_AI_PROJECT_ENDPOINT\": endpoint,\n",
" \"AZURE_AI_MODEL_DEPLOYMENT_NAME\": deployment_name\n",
"}.items() if not v]\n",
"\n",
"if missing:\n",
" raise ValueError(\n",
" f\"Missing required environment variables: {', '.join(missing)}. \"\n",
" \"Please set them as environment variables (e.g., in your .env file or shell environment).\"\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5f6a7b8",
"metadata": {},
"outputs": [],
"source": [
"# Create the Microsoft Foundry client\n",
"client = FoundryChatClient(\n",
" project_endpoint=endpoint,\n",
" model=deployment_name,\n",
" credential=DefaultAzureCredential()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f6a7b8c9",
"metadata": {},
"source": [
"## What is Agentic RAG?\n",
"\n",
"Traditional RAG follows a fixed pipeline: retrieve documents, then generate a response. **Agentic RAG** goes further by giving the agent autonomy to decide **when** and **how** to retrieve information.\n",
"\n",
"With Agentic RAG, the agent can:\n",
"- **Decide** whether retrieval is needed before answering a question\n",
"- **Choose** which data source or tool to query\n",
"- **Evaluate** retrieved results and perform follow-up retrievals if the first attempt is insufficient\n",
"- **Combine** information from multiple retrieval steps into a coherent answer\n",
"\n",
"This makes the agent more flexible and accurate compared to a static retrieve-then-generate pipeline."
]
},
{
"cell_type": "markdown",
"id": "a7b8c9d0",
"metadata": {},
"source": [
"## Creating a Search Tool\n",
"\n",
"In Agentic RAG, external data sources are wrapped as **tools** that the agent can invoke on demand. This lets the agent treat retrieval as just another action it can take, rather than a mandatory step.\n",
"\n",
"Below we define a travel knowledge base and expose it as a tool the agent can call to look up destination information."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8c9d0e1",
"metadata": {},
"outputs": [],
"source": [
"TRAVEL_KNOWLEDGE_BASE = {\n",
" \"Barcelona\": \"Barcelona is Spain's cosmopolitan capital of Catalonia. Best visited Mar-May or Sep-Nov. Known for Gaudí architecture, La Rambla, beaches. Average daily cost: $150-200.\",\n",
" \"Tokyo\": \"Tokyo is Japan's capital, mixing ultramodern with traditional. Best visited Mar-Apr (cherry blossoms) or Oct-Nov. Known for Shibuya, temples, sushi. Average daily cost: $200-250.\",\n",
" \"Paris\": \"Paris is France's capital and a global center for art, fashion, and culture. Best visited Apr-Jun or Sep-Oct. Known for Eiffel Tower, Louvre, cuisine. Average daily cost: $180-250.\",\n",
" \"Cape Town\": \"Cape Town sits on South Africa's southwest tip. Best visited Nov-Mar. Known for Table Mountain, wine regions, wildlife. Average daily cost: $100-150.\",\n",
"}\n",
"\n",
"\n",
"@tool(approval_mode=\"never_require\")\n",
"def search_travel_knowledge(\n",
" query: Annotated[str, \"The search query about a travel destination\"]\n",
") -> str:\n",
" \"\"\"Search the travel knowledge base for destination information.\"\"\"\n",
" results = []\n",
" for destination, info in TRAVEL_KNOWLEDGE_BASE.items():\n",
" if query.lower() in destination.lower() or any(\n",
" word in info.lower() for word in query.lower().split()\n",
" ):\n",
" results.append(f\"**{destination}**: {info}\")\n",
" return (\n",
" \"\\n\\n\".join(results)\n",
" if results\n",
" else \"No matching destinations found in the knowledge base.\"\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "c9d0e1f2",
"metadata": {},
"source": [
"## Building the RAG Agent\n",
"\n",
"Now we create an agent that is instructed to **always retrieve information before answering**. The agent uses the `search_travel_knowledge` tool to ground its responses in the knowledge base rather than relying on its own training data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d0e1f2a3",
"metadata": {},
"outputs": [],
"source": [
"agent = client.as_agent(\n",
" tools=[search_travel_knowledge],\n",
" name=\"TravelRAGAgent\",\n",
" instructions=\"\"\"You are a knowledgeable travel advisor. Before answering questions about destinations:\n",
"1. ALWAYS search the travel knowledge base first\n",
"2. Base your answers on retrieved information\n",
"3. If information is not in the knowledge base, say so clearly\n",
"4. Provide specific details like costs, best seasons, and highlights.\"\"\",\n",
")\n",
"\n",
"response = await agent.run(\n",
" \"I'm interested in visiting somewhere with great architecture. What destinations would you recommend?\",\n",
" )\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "e1f2a3b4",
"metadata": {},
"source": [
"## Iterative Retrieval — The Maker-Checker Pattern\n",
"\n",
"A key advantage of Agentic RAG is **iterative retrieval**. The agent can perform multiple rounds of search to verify, refine, or expand on its initial findings — similar to a \"maker-checker\" workflow:\n",
"\n",
"1. **Maker step**: The agent retrieves initial information and drafts a response.\n",
"2. **Checker step**: The agent performs additional retrievals to verify details or fill gaps.\n",
"\n",
"Below, the agent is asked a question that requires comparing multiple destinations, prompting it to search several times."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2a3b4c5",
"metadata": {},
"outputs": [],
"source": [
"checker_agent = client.as_agent(\n",
" tools=[search_travel_knowledge],\n",
" name=\"TravelRAGCheckerAgent\",\n",
" instructions=\"\"\"You are a meticulous travel advisor who double-checks recommendations.\n",
"When answering travel questions:\n",
"1. Search for relevant destinations first\n",
"2. For each destination found, search again with the destination name to get full details\n",
"3. Compare the options using verified information\n",
"4. Present a final recommendation with specific costs, best travel times, and highlights\n",
"5. If any detail seems incomplete, search once more to confirm before responding.\"\"\",\n",
")\n",
"\n",
"response = await checker_agent.run(\n",
" \"I have a $175/day budget and want to travel in April. Which destinations fit my budget and timing?\",\n",
" )\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "a3b4c5d6",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"In this lesson you learned how to build an **Agentic RAG** system using the Microsoft Agent Framework:\n",
"\n",
"- **Agentic RAG** lets agents autonomously decide when to retrieve information, making retrieval dynamic rather than fixed.\n",
"- **Tools as data sources**: External knowledge bases (like Azure AI Search) are wrapped as tools the agent can invoke.\n",
"- **Iterative retrieval**: The maker-checker pattern enables the agent to perform multiple retrieval rounds — searching, verifying, and refining — before producing a final answer.\n",
"\n",
"In production, you would replace the in-memory `TRAVEL_KNOWLEDGE_BASE` with a real Azure AI Search index to handle large-scale travel document retrieval."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+5
View File
@@ -0,0 +1,5 @@
- **Contoso Travel** offers luxury vacation packages to exotic destinations worldwide.
- Our premium travel services include personalized itinerary planning and 24/7 concierge support.
- Contoso's travel insurance covers medical emergencies, trip cancellations, and lost baggage.
- Popular destinations include the Maldives, Swiss Alps, and African safaris.
- Contoso Travel provides exclusive access to boutique hotels and private guided tours.
Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

+217
View File
@@ -0,0 +1,217 @@
[![Trustworthy AI Agents](./images/lesson-6-thumbnail.png)](https://youtu.be/iZKkMEGBCUQ?si=Q-kEbcyHUMPoHp8L)
> _(Click the image above to view video of this lesson)_
# Building Trustworthy AI Agents
## Introduction
This lesson will cover:
- How to build and deploy safe and effective AI Agents
- Important security considerations when developing AI Agents.
- How to maintain data and user privacy when developing AI Agents.
## Learning Goals
After completing this lesson, you will know how to:
- Identify and mitigate risks when creating AI Agents.
- Implement security measures to ensure that data and access are properly managed.
- Create AI Agents that maintain data privacy and provide a quality user experience.
## Safety
Let's first look at building safe agentic applications. Safety means that the AI agent performs as designed. As builders of agentic applications, we have methods and tools to maximize safety:
### Building a System Message Framework
If you have ever built an AI application using Large Language Models (LLMs), you know the importance of designing a robust system prompt or system message. These prompts establish the meta rules, instructions, and guidelines for how the LLM will interact with the user and data.
For AI Agents, the system prompt is even more important as the AI Agents will need highly specific instructions to complete the tasks we have designed for them.
To create scalable system prompts, we can use a system message framework for building one or more agents in our application:
![Building a System Message Framework](./images/system-message-framework.png)
#### Step 1: Create a Meta System Message
The meta prompt will be used by an LLM to generate the system prompts for the agents we create. We design it as a template so that we can efficiently create multiple agents if needed.
Here is an example of a meta system message we would give to the LLM:
```plaintext
You are an expert at creating AI agent assistants.
You will be provided a company name, role, responsibilities and other
information that you will use to provide a system prompt for.
To create the system prompt, be descriptive as possible and provide a structure that a system using an LLM can better understand the role and responsibilities of the AI assistant.
```
#### Step 2: Create a basic prompt
The next step is to create a basic prompt to describe the AI Agent. You should include the role of the agent, the tasks the agent will complete, and any other responsibilities of the agent.
Here is an example:
```plaintext
You are a travel agent for Contoso Travel that is great at booking flights for customers. To help customers you can perform the following tasks: lookup available flights, book flights, ask for preferences in seating and times for flights, cancel any previously booked flights and alert customers on any delays or cancellations of flights.
```
#### Step 3: Provide Basic System Message to LLM
Now we can optimize this system message by providing the meta system message as the system message and our basic system message.
This will produce a system message that is better designed for guiding our AI agents:
```markdown
**Company Name:** Contoso Travel
**Role:** Travel Agent Assistant
**Objective:**
You are an AI-powered travel agent assistant for Contoso Travel, specializing in booking flights and providing exceptional customer service. Your main goal is to assist customers in finding, booking, and managing their flights, all while ensuring that their preferences and needs are met efficiently.
**Key Responsibilities:**
1. **Flight Lookup:**
- Assist customers in searching for available flights based on their specified destination, dates, and any other relevant preferences.
- Provide a list of options, including flight times, airlines, layovers, and pricing.
2. **Flight Booking:**
- Facilitate the booking of flights for customers, ensuring that all details are correctly entered into the system.
- Confirm bookings and provide customers with their itinerary, including confirmation numbers and any other pertinent information.
3. **Customer Preference Inquiry:**
- Actively ask customers for their preferences regarding seating (e.g., aisle, window, extra legroom) and preferred times for flights (e.g., morning, afternoon, evening).
- Record these preferences for future reference and tailor suggestions accordingly.
4. **Flight Cancellation:**
- Assist customers in canceling previously booked flights if needed, following company policies and procedures.
- Notify customers of any necessary refunds or additional steps that may be required for cancellations.
5. **Flight Monitoring:**
- Monitor the status of booked flights and alert customers in real-time about any delays, cancellations, or changes to their flight schedule.
- Provide updates through preferred communication channels (e.g., email, SMS) as needed.
**Tone and Style:**
- Maintain a friendly, professional, and approachable demeanor in all interactions with customers.
- Ensure that all communication is clear, informative, and tailored to the customer's specific needs and inquiries.
**User Interaction Instructions:**
- Respond to customer queries promptly and accurately.
- Use a conversational style while ensuring professionalism.
- Prioritize customer satisfaction by being attentive, empathetic, and proactive in all assistance provided.
**Additional Notes:**
- Stay updated on any changes to airline policies, travel restrictions, and other relevant information that could impact flight bookings and customer experience.
- Use clear and concise language to explain options and processes, avoiding jargon where possible for better customer understanding.
This AI assistant is designed to streamline the flight booking process for customers of Contoso Travel, ensuring that all their travel needs are met efficiently and effectively.
```
#### Step 4: Iterate and Improve
The value of this system message framework is to be able to scale creating system messages from multiple agents easier as well as improving your system messages over time. It is rare you will have a system message that works the first time for your complete use case. Being able to make small tweaks and improvements by changing the basic system message and running it through the system will allow you to compare and evaluate results.
## Understanding Threats
To build trustworthy AI agents, it is important to understand and mitigate the risks and threats to your AI agent. Let's look at only some of the different threats to AI agents and how you can better plan and prepare for them.
![Understanding Threats](./images/understanding-threats.png)
### Task and Instruction
**Description:** Attackers attempt to change the instructions or goals of the AI agent through prompting or manipulating inputs.
**Mitigation**: Execute validation checks and input filters to detect potentially dangerous prompts before they are processed by the AI Agent. Since these attacks typically require frequent interaction with the Agent, limiting the number of turns in a conversation is another way to prevent these types of attacks.
### Access to Critical Systems
**Description**: If an AI agent has access to systems and services that store sensitive data, attackers can compromise the communication between the agent and these services. These can be direct attacks or indirect attempts to gain information about these systems through the agent.
**Mitigation**: AI agents should have access to systems on a need-only basis to prevent these types of attacks. Communication between the agent and system should also be secure. Implementing authentication and access control is another way to protect this information.
### Resource and Service Overloading
**Description:** AI agents can access different tools and services to complete tasks. Attackers can use this ability to attack these services by sending a high volume of requests through the AI Agent, which may result in system failures or high costs.
**Mitigation:** Implement policies to limit the number of requests an AI agent can make to a service. Limiting the number of conversation turns and requests to your AI agent is another way to prevent these types of attacks.
### Knowledge Base Poisoning
**Description:** This type of attack does not target the AI agent directly but targets the knowledge base and other services that the AI agent will use. This could involve corrupting the data or information that the AI agent will use to complete a task, leading to biased or unintended responses to the user.
**Mitigation:** Perform regular verification of the data that the AI agent will be using in its workflows. Ensure that access to this data is secure and only changed by trusted individuals to avoid this type of attack.
### Cascading Errors
**Description:** AI agents access various tools and services to complete tasks. Errors caused by attackers can lead to failures of other systems that the AI agent is connected to, causing the attack to become more widespread and harder to troubleshoot.
**Mitigation**: One method to avoid this is to have the AI Agent operate in a limited environment, such as performing tasks in a Docker container, to prevent direct system attacks. Creating fallback mechanisms and retry logic when certain systems respond with an error is another way to prevent larger system failures.
## Human-in-the-Loop
Another effective way to build trustworthy AI Agent systems is using a Human-in-the-loop. This creates a flow where users are able to provide feedback to the Agents during the run. Users essentially act as agents in a multi-agent system and by providing approval or termination of the running process.
![Human in The Loop](./images/human-in-the-loop.png)
Here is a code snippet using the Microsoft Agent Framework to show how this concept is implemented:
```python
import os
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
# Create the provider with human-in-the-loop approval
provider = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
# Create the agent with a human approval step
response = provider.create_response(
input="Write a 4-line poem about the ocean.",
instructions="You are a helpful assistant. Ask for user approval before finalizing.",
)
# The user can review and approve the response
print(response.output_text)
user_input = input("Do you approve? (APPROVE/REJECT): ")
if user_input == "APPROVE":
print("Response approved.")
else:
print("Response rejected. Revising...")
```
## Conclusion
Building trustworthy AI agents requires careful design, robust security measures, and continuous iteration. By implementing structured meta prompting systems, understanding potential threats, and applying mitigation strategies, developers can create AI agents that are both safe and effective. Additionally, incorporating a human-in-the-loop approach ensures that AI agents remain aligned with user needs while minimizing risks. As AI continues to evolve, maintaining a proactive stance on security, privacy, and ethical considerations will be key to fostering trust and reliability in AI-driven systems.
## Code Samples
- [`code_samples/06-system-message-framework.ipynb`](code_samples/06-system-message-framework.ipynb): Step-by-step demonstration of the meta-prompt system-message framework.
- [`code_samples/06-human-in-the-loop.ipynb`](code_samples/06-human-in-the-loop.ipynb): Pre-action approval gates, risk tiering, and audit logging for trustworthy agents.
### Got More Questions about Building Trustworthy AI Agents?
Join the [Microsoft Foundry Discord](https://discord.com/invite/ATgtXmAS5D) to meet with other learners, attend office hours and get your AI Agents questions answered.
## Additional Resources
- <a href="https://learn.microsoft.com/azure/ai-studio/responsible-use-of-ai-overview" target="_blank">Responsible AI overview</a>
- <a href="https://learn.microsoft.com/azure/ai-studio/concepts/evaluation-approach-gen-ai" target="_blank">Evaluation of generative AI models and AI applications</a>
- <a href="https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message?context=%2Fazure%2Fai-studio%2Fcontext%2Fcontext&tabs=top-techniques" target="_blank">Safety system messages</a>
- <a href="https://blogs.microsoft.com/wp-content/uploads/prod/sites/5/2022/06/Microsoft-RAI-Impact-Assessment-Template.pdf?culture=en-us&country=us" target="_blank">Risk Assessment Template</a>
## Previous Lesson
[Agentic RAG](../05-agentic-rag/README.md)
## Next Lesson
[Planning Design Pattern](../07-planning-design/README.md)
@@ -0,0 +1,353 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Human-in-the-Loop: Pre-Action Gates, Risk Tiering, and Audit Logging\n",
"\n",
"The README for this lesson introduces Human-in-the-Loop with a short snippet that asks the user `APPROVE` or `REJECT` after the agent has already produced a response. That pattern is a fine starting point, but production HITL implementations commonly need three additional pieces:\n",
"\n",
"1. A **pre-action gate** that runs **before** the agent executes a risky step, so cost, irreversibility, and latency stay under control.\n",
"2. **Risk tiering**, so low-risk actions auto-execute, medium-risk actions are batch-approved, and only high-risk actions block on a human.\n",
"3. An **audit log plus revision loop**, so every gate decision is recorded as JSONL, and a rejection re-prompts the agent with a structured reason instead of just printing `Revising...`.\n",
"\n",
"This notebook builds each of these on top of the same primitives as `06-system-message-framework.ipynb`. It runs end-to-end in `DEMO_MODE = True` (no interactive input needed) or with real `input()` prompts when `DEMO_MODE = False`. Note: in DEMO_MODE the third goal's retry is scripted so the loop mechanics are visible end-to-end. Real revision-driven re-classification requires `DEMO_MODE = False` and an operator.\n",
"\n",
"**Out of scope (handled in other lessons):** authentication and access control (Lesson 06 README threat 2), tool-call middleware (Lesson 14 MAF deep dive), multi-agent debate patterns.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"from datetime import datetime, timezone\n",
"from pathlib import Path\n",
"\n",
"from dotenv import load_dotenv\n",
"from azure.identity import DefaultAzureCredential, get_bearer_token_provider\n",
"from openai import OpenAI\n",
"\n",
"load_dotenv()\n",
"\n",
"DEMO_MODE = True # set False to use real input() prompts\n",
"\n",
"# Per-run unique log filename so demo runs don't overwrite each other and\n",
"# the notebook doesn't touch any pre-existing gate_log.jsonl in the working\n",
"# directory.\n",
"GATE_LOG_PATH = Path(\n",
" f\"gate_log_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}.jsonl\"\n",
")\n",
"\n",
"# This notebook uses the Azure OpenAI Responses API via the stable /openai/v1/ endpoint.\n",
"# GitHub Models is deprecated (retiring July 2026) and does not support the Responses API.\n",
"endpoint = os.environ.get(\"AZURE_OPENAI_ENDPOINT\", \"\")\n",
"if not endpoint:\n",
" raise RuntimeError(\n",
" \"AZURE_OPENAI_ENDPOINT environment variable is not set. This notebook needs \"\n",
" \"an Azure OpenAI resource with a model deployment that supports the Responses \"\n",
" \"API. Set AZURE_OPENAI_ENDPOINT (and optionally AZURE_OPENAI_DEPLOYMENT) in \"\n",
" \"your environment or a local .env file, then run `az login`.\"\n",
" )\n",
"\n",
"deployment = os.environ.get(\"AZURE_OPENAI_DEPLOYMENT\", \"gpt-4o-mini\")\n",
"\n",
"# Authenticate with Entra ID (run `az login` first). No api_version is needed.\n",
"token_provider = get_bearer_token_provider(\n",
" DefaultAzureCredential(),\n",
" \"https://cognitiveservices.azure.com/.default\",\n",
")\n",
"\n",
"client = OpenAI(\n",
" base_url=f\"{endpoint.rstrip('/')}/openai/v1/\",\n",
" api_key=token_provider,\n",
")\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pattern 1: Pre-action gate\n",
"\n",
"The README's HITL snippet calls the agent first, then asks the user to approve the output. That is a **post-action** flow. The agent has already executed, so the LLM call cost is already paid, and any side effect (sent email, written database row, posted comment) has already happened.\n",
"\n",
"A **pre-action** flow inserts the gate before the agent runs the risky step. The agent proposes the action, the gate decides whether to execute, and only on approval does the side effect occur.\n",
"\n",
"| Aspect | Post-action approval (README snippet) | Pre-action gate (this notebook) |\n",
"|---|---|---|\n",
"| When does approval run? | After the agent has produced output | Before any side-effect executes |\n",
"| LLM cost on rejection | Already paid | Paid only for the proposal, not the action |\n",
"| Irreversible side effects | Possible (the action already happened) | Prevented |\n",
"| Audit clarity | Approval is a print statement | Approval is a JSON record with timestamp, action, reason |\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def gate_action(action_description: str, risk_tier: str, attempt: int = 0) -> dict:\n",
" \"\"\"Run a single pre-action gate.\n",
"\n",
" Returns a decision dict with keys: decision, reason, ts.\n",
" Decision is one of: approve, deny, escalate.\n",
" Safe default on EOF or unexpected input is deny.\n",
"\n",
" DEMO_MODE behavior: high-risk actions are denied on attempt 0 and\n",
" auto-approved on attempt >= 1. This is scripted approval to show the\n",
" loop mechanics (deny -> retry -> approve). It is NOT revision-driven\n",
" re-classification. Real revision-driven re-classification requires\n",
" DEMO_MODE=False and a human operator who evaluates the revised\n",
" proposal on its own merits.\n",
" \"\"\"\n",
" print(f\"[gate] proposed action ({risk_tier}, attempt={attempt}): {action_description}\")\n",
"\n",
" if DEMO_MODE:\n",
" if risk_tier == \"high\":\n",
" decision = \"approve\" if attempt >= 1 else \"deny\"\n",
" reason = (\n",
" \"DEMO_MODE: scripted approval on retry to show loop mechanics\"\n",
" if attempt >= 1\n",
" else \"DEMO_MODE: high risk denied on first attempt\"\n",
" )\n",
" else:\n",
" decision = \"approve\"\n",
" reason = f\"DEMO_MODE canned response for tier={risk_tier}\"\n",
" else:\n",
" try:\n",
" raw = input(\"[gate] approve / deny / escalate? \").strip().lower()\n",
" except EOFError:\n",
" raw = \"\"\n",
" if raw in {\"approve\", \"deny\", \"escalate\"}:\n",
" decision, reason = raw, \"operator input\"\n",
" elif raw == \"\":\n",
" decision, reason = \"deny\", \"no input received, defaulted to deny\"\n",
" else:\n",
" decision, reason = \"deny\", f\"invalid input {raw!r}, defaulted to deny\"\n",
"\n",
" return {\n",
" \"decision\": decision,\n",
" \"reason\": reason,\n",
" \"action\": action_description,\n",
" \"risk_tier\": risk_tier,\n",
" \"ts\": datetime.now(timezone.utc).isoformat(),\n",
" }\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pattern 2: Risk tiering\n",
"\n",
"Not every action needs human approval. A read-only lookup against a public API has different stakes than sending a customer email. Treating both the same wastes operator attention and slows the agent.\n",
"\n",
"A simple 3-tier model:\n",
"\n",
"| Tier | Examples | Approval flow |\n",
"|---|---|---|\n",
"| `low` (read-only) | Search a knowledge base, look up flight options, fetch a public web page | Auto-execute, logged for audit |\n",
"| `medium` (cheap mutation) | Cache a result, increment a counter, schedule a reminder | Auto-execute, but batched daily review |\n",
"| `high` (external-facing or irreversible) | Send an email, charge a card, post to a public channel | Block on human approval |\n",
"\n",
"This is one tiering. Production systems often use more granular tiers (e.g., AWS IAM permission levels, role-based access tiers). The 3-tier version below is the smallest useful version for an agent that mixes read-only and side-effecting actions.\n",
"\n",
"The classifier below uses keyword heuristics so the demo stays deterministic and cheap. In a production system you would swap this for a learned classifier or a policy engine.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"LOW_RISK_KEYWORDS = {\n",
" \"look\", \"lookup\", \"search\", \"fetch\", \"read\", \"query\", \"view\",\n",
" \"get\", \"list\", \"weather\", \"summarize\",\n",
"}\n",
"HIGH_RISK_KEYWORDS = {\n",
" \"send\", \"email\", \"post\", \"publish\", \"charge\", \"pay\", \"transfer\",\n",
" \"delete\", \"drop\", \"cancel\", \"refund\",\n",
"}\n",
"MEDIUM_RISK_KEYWORDS = {\n",
" \"cache\", \"schedule\", \"reminder\", \"book\", \"reserve\", \"update\",\n",
" \"increment\", \"log\",\n",
"}\n",
"\n",
"AUTO_APPROVE_REASONS = {\n",
" \"low\": \"auto-approved (low risk)\",\n",
" \"medium\": \"auto-approved (medium risk, queued for batched review)\",\n",
"}\n",
"\n",
"\n",
"def classify_risk(action: str) -> str:\n",
" \"\"\"Classify an action string into one of: low, medium, high.\n",
"\n",
" Keyword-based heuristic. Checks high-risk first (most severe), then\n",
" low-risk explicit reads, then medium-risk mutations. Unrecognized\n",
" actions default to medium, not low.\n",
"\n",
" Default for unrecognized actions is 'medium', not 'low'. A read-only\n",
" keyword set will always have blind spots, and the parent README's\n",
" threat list (critical-system access, knowledge-base poisoning,\n",
" cascading errors) all involve cases an action-name alone cannot rule\n",
" out. Routing unknown actions through batched review is the safer\n",
" default than auto-executing them.\n",
" \"\"\"\n",
" text = action.lower()\n",
" if any(kw in text for kw in HIGH_RISK_KEYWORDS):\n",
" return \"high\"\n",
" if any(kw in text for kw in LOW_RISK_KEYWORDS):\n",
" return \"low\"\n",
" if any(kw in text for kw in MEDIUM_RISK_KEYWORDS):\n",
" return \"medium\"\n",
" # Explicit fail-safe default: unrecognized actions route to batched review.\n",
" return \"medium\"\n",
"\n",
"\n",
"def tiered_gate(action: str, attempt: int = 0) -> dict:\n",
" \"\"\"Classify then gate. Low and medium tiers auto-approve; high blocks.\"\"\"\n",
" tier = classify_risk(action)\n",
" if tier in AUTO_APPROVE_REASONS:\n",
" return {\n",
" \"decision\": \"approve\",\n",
" \"reason\": AUTO_APPROVE_REASONS[tier],\n",
" \"action\": action,\n",
" \"risk_tier\": tier,\n",
" \"ts\": datetime.now(timezone.utc).isoformat(),\n",
" }\n",
" return gate_action(action, tier, attempt=attempt)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pattern 3: Audit log and revision loop\n",
"\n",
"A `print(\"Response approved.\")` is not an audit log. For trust, every gate decision should be recorded as a structured event that you can later query, replay, or attach to an incident review.\n",
"\n",
"Two pieces:\n",
"\n",
"1. **Append-only JSONL.** One line per decision, with timestamp, action, tier, decision, reason. Easy to grep, easy to ship to a real log store later.\n",
"2. **Revision loop on rejection.** When the gate returns `deny`, the agent re-prompts itself with the rejection reason in context, so the next proposal can avoid the problem.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def log_decision(decision: dict) -> None:\n",
" \"\"\"Append a gate decision to the JSONL audit log.\"\"\"\n",
" with GATE_LOG_PATH.open(\"a\", encoding=\"utf-8\") as f:\n",
" f.write(json.dumps(decision) + \"\\n\")\n",
"\n",
"\n",
"def propose_action(goal: str, prior_rejection: str | None = None) -> str:\n",
" \"\"\"Ask the LLM to propose a concrete next action for a goal.\n",
"\n",
" If prior_rejection is provided, it is fed back so the LLM can avoid\n",
" the same failure mode in the next proposal.\n",
" \"\"\"\n",
" system = (\n",
" \"You are an action planner for an agent. Propose ONE concrete next\\n\"\n",
" \"action (a single sentence) toward the user's goal. If a prior\\n\"\n",
" \"rejection reason is given, propose a different action that addresses\\n\"\n",
" \"the rejection.\"\n",
" )\n",
" user_text = f\"Goal: {goal}\"\n",
" if prior_rejection:\n",
" user_text += f\"\\n\\nPrior proposal was denied. Reason: {prior_rejection}\"\n",
"\n",
" response = client.responses.create(\n",
" model=deployment,\n",
" input=[\n",
" {\"role\": \"system\", \"content\": system},\n",
" {\"role\": \"user\", \"content\": user_text},\n",
" ],\n",
" store=False,\n",
" )\n",
" return response.output_text.strip()\n",
"\n",
"\n",
"def run_with_revision(goal: str, max_revisions: int = 2) -> dict:\n",
" \"\"\"Propose, gate, and on rejection revise up to max_revisions times.\"\"\"\n",
" prior_reason: str | None = None\n",
" for attempt in range(max_revisions + 1):\n",
" action = propose_action(goal, prior_rejection=prior_reason)\n",
" decision = tiered_gate(action, attempt=attempt)\n",
" decision[\"attempt\"] = attempt\n",
" log_decision(decision)\n",
" if decision[\"decision\"] == \"approve\":\n",
" return decision\n",
" prior_reason = decision[\"reason\"]\n",
" return {**decision, \"final\": \"max_revisions_reached\"}\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# End-to-end demo: three goals at three different risk profiles.\n",
"# GATE_LOG_PATH is per-run (timestamped) so no prior log is touched.\n",
"\n",
"goals = [\n",
" \"Look up the weather in Seattle for the customer's trip planning.\",\n",
" \"Schedule a reminder for the customer to check in 24 hours before their flight.\",\n",
" \"Send a marketing email to the customer about premium upgrade options.\",\n",
"]\n",
"\n",
"for goal in goals:\n",
" print(f\"\\n=== Goal: {goal} ===\")\n",
" outcome = run_with_revision(goal, max_revisions=1)\n",
" print(f\"[final] {outcome['decision']} ({outcome['reason']})\")\n",
"\n",
"print(f\"\\n=== Audit log ({GATE_LOG_PATH.name}) ===\")\n",
"for line in GATE_LOG_PATH.read_text(encoding=\"utf-8\").splitlines():\n",
" record = json.loads(line)\n",
" print(f\" [{record['risk_tier']:6s}] {record['decision']:8s} \"\n",
" f\"attempt={record.get('attempt', '?')} action={record['action'][:140]}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Additional resources\n",
"\n",
"Several other public projects implement variations of these HITL patterns. Compare approaches to find what fits your stack:\n",
"\n",
"- **LangChain** human-in-the-loop tool wrappers ([docs](https://python.langchain.com/docs/integrations/tools/human_tools)): drop-in tool wrappers that pause execution for human input.\n",
"- **AutoGen** `UserProxyAgent` ([v0.2 docs](https://microsoft.github.io/autogen/0.2/docs/topics/human-in-the-loop); AutoGen v0.4+ restructured this): uses an agent role specifically to represent the human in multi-agent conversations.\n",
"- **Microsoft Agent Framework (MAF)** function-invocation middleware ([docs](https://learn.microsoft.com/agent-framework/)): middleware that runs around every tool/function call, suitable for gating logic and approval flows.\n",
"\n",
"Each project handles the three sub-patterns differently: LangChain wraps them as tools, AutoGen uses an agent role, and Microsoft Agent Framework uses function-invocation middleware. Read one or two implementations end-to-end before picking a design for your own agent.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,100 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from dotenv import load_dotenv\n",
"\n",
"load_dotenv()\n",
"\n",
"from azure.identity import DefaultAzureCredential, get_bearer_token_provider\n",
"from openai import OpenAI\n",
"\n",
"# This sample uses the Azure OpenAI Responses API via the stable /openai/v1/ endpoint.\n",
"# GitHub Models is deprecated (retiring July 2026) and does not support the Responses API,\n",
"# so we call Azure OpenAI directly instead.\n",
"endpoint = os.environ[\"AZURE_OPENAI_ENDPOINT\"]\n",
"deployment = os.environ.get(\"AZURE_OPENAI_DEPLOYMENT\", \"gpt-4o-mini\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Authenticate with Entra ID (run `az login` first). No API version is needed with the v1 endpoint.\n",
"token_provider = get_bearer_token_provider(\n",
" DefaultAzureCredential(),\n",
" \"https://cognitiveservices.azure.com/.default\",\n",
")\n",
"\n",
"client = OpenAI(\n",
" base_url=f\"{endpoint.rstrip('/')}/openai/v1/\",\n",
" api_key=token_provider,\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"role = \"travel agent\"\n",
"company = \"contoso travel\"\n",
"responsibility = \"booking flights\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = client.responses.create(\n",
" model=deployment,\n",
" input=[\n",
" {\"role\": \"system\", \"content\": \"\"\"You are an expert at creating AI agent assistants. \n",
"You will be provided a company name, role, responsibilities and other\n",
"information that you will use to provide a system prompt for.\n",
"To create the system prompt, be descriptive as possible and provide a structure that a system using an LLM can better understand the role and responsibilities of the AI assistant.\"\"\"},\n",
" {\"role\": \"user\", \"content\": f\"You are {role} at {company} that is responsible for {responsibility}.\"},\n",
" ],\n",
" # Optional parameters\n",
" temperature=1.0,\n",
" max_output_tokens=1000,\n",
" top_p=1.0,\n",
" store=False,\n",
")\n",
"\n",
"print(response.output_text)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Some files were not shown because too many files have changed in this diff Show More