Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Microsoft Agent Framework

**Author:** Niket Sharma — sharma.niket@gmail.com

Microsoft Agent Framework is the direct successor to both **AutoGen** and **Semantic Kernel**, combining AutoGen's simple agent abstractions with Semantic Kernel's enterprise features (type safety, middleware, telemetry, session management).

## Setup

```bash
pip install agent-framework agent-framework-openai
```

Requires `OPENAI_API_KEY` in your environment (`.env` file supported via `python-dotenv`).

## Labs

### [Lab 1 — Basics](1_lab1_ms_agent_basics.ipynb)
Core building blocks of the framework.
- `OpenAIChatCompletionClient` and `client.as_agent()`
- Running agents with `await agent.run()`
- Token-by-token streaming with `stream=True`
- Attaching Python functions as tools (auto JSON schema from type hints + docstrings)
- Multiple tools and the `@tool` decorator
- SQLite-backed ticket price database used throughout the labs

### [Lab 2 — Advanced Features](2_lab2_ms_agent_advanced.ipynb)
- **Structured outputs** — constrain responses to a Pydantic model via `output_type=`
- **Multi-turn sessions** — `agent.create_session()` preserves conversation history
- **Image inputs** — multi-modal messages with `Content.from_data()`
- **Multi-agent hand-off** — sequential orchestration between agents in plain Python
- **Agent as Tool** — `agent.as_tool()` lets an orchestrator call another agent like a function
- **MCP tools** — `MCPStdioTool` integrates any Model Context Protocol server

### [Lab 3 — Workflows](3_lab3_ms_agent_workflows.ipynb)
Graph-based orchestration for deterministic, multi-step pipelines.
- `Executor` class with the `@handler` decorator for typed message routing
- `@executor` decorator for function-based executors (no class needed)
- `WorkflowBuilder` to assemble executors and edges into a directed graph
- AI agents wrapped inside executors (summarise → translate pipeline)
- Streaming workflow events (`executor_started`, `executor_completed`, `output`)
- Rock, Paper, Scissors demo: Judge coordinates two Player executors

### [Lab 4 — Advanced Multi-Agent Workflows](4_lab4_ms_agent_multiagent.ipynb)
Complex orchestration patterns using the Workflow engine.
- **Fan-out / fan-in** — Splitter sends one input to Pros + Cons agents in parallel; Merger collects results
- **Debate + Judge** — extends fan-out with a JudgeExecutor for final verdict
- **Dynamic fan-out** — TopicExpander breaks a topic into sub-questions, Researcher answers each, Synthesizer produces a report
- Comparison table: AutoGen Distributed (gRPC) vs Microsoft Agent Framework (zero infrastructure)

### [Lab 5 — Agent Skills](5_lab5_ms_agent_skills.ipynb)
Portable domain-knowledge packages that use progressive disclosure to avoid token waste.
- **File-based skills** — `SKILL.md` directories with `references/` and `scripts/` subdirectories
- **Code-defined skills** — `Skill` objects in Python with `@skill.resource` and `@skill.script`
- **Dynamic resources** — decorated functions called fresh each time the agent reads them
- **Combining** file-based and code-defined skills in one `SkillsProvider`
- **Script approval** — `require_script_approval=True` gates script execution behind human confirmation
- **Runtime injection** — `function_invocation_kwargs` forwards per-request context (e.g. currency) to skill functions via `**kwargs`

### [Lab 6 — Memory & A2A Hosting](6_lab6_ms_agent_memory_a2a.ipynb)
Production-ready memory and inter-agent communication.
- **Custom `ContextProvider`** — `before_run` / `after_run` hooks with a per-session `state` dict
- **`InMemoryHistoryProvider`** — configurable history buffering (`load_messages`, `store_inputs`, `store_outputs`, `store_context_messages`)
- **`FileHistoryProvider`** — JSONL persistence across process restarts; session ID links runs
- **A2A server** — `A2AExecutor` + `A2AStarletteApplication` exposes any agent over HTTP (see [`sandbox/travel_a2a_server.py`](sandbox/travel_a2a_server.py))
- **A2A client** — `A2AAgent(url=...)` connects to any A2A-compliant service with the same `.run()` API
- **Streaming from remote** — `await remote_agent.run(..., stream=True)`
- **Remote agent as tool** — `remote_agent.as_tool()` lets a local orchestrator delegate to a remote agent transparently

## Folder Structure

```
7_ms_agent_framework/
├── 1_lab1_ms_agent_basics.ipynb
├── 2_lab2_ms_agent_advanced.ipynb
├── 3_lab3_ms_agent_workflows.ipynb
├── 4_lab4_ms_agent_multiagent.ipynb
├── 5_lab5_ms_agent_skills.ipynb
├── 6_lab6_ms_agent_memory_a2a.ipynb
├── tickets.db # SQLite DB with city round-trip prices
├── sandbox/
│ └── travel_a2a_server.py # Standalone A2A server (uvicorn/Starlette)
├── skills/
│ └── travel-policy/
│ ├── SKILL.md # Skill instructions + frontmatter
│ ├── references/
│ │ └── refund-policy.md # Full refund rules (read via read_skill_resource)
│ └── scripts/
│ └── estimate_refund.py # Refund calculator (run via run_skill_script)
└── chat_history/
└── demo-session-001.jsonl # Persisted session from FileHistoryProvider demo
```

## Key Concepts at a Glance

| Concept | API |
|---|---|
| Create agent | `client.as_agent(name=..., instructions=..., tools=...)` |
| Run agent | `await agent.run("...")` |
| Stream | `async for chunk in agent.run("...", stream=True)` |
| Session | `session = agent.create_session(); agent.run(..., session=session)` |
| Workflow | `WorkflowBuilder(start_executor=...).add_edge(...).build()` |
| Agent as Tool | `agent.as_tool(name=..., arg_name=...)` |
| Skills | `SkillsProvider(skill_paths=..., skills=[...])` |
| Memory | `ContextProvider` subclass with `before_run` / `after_run` |
| A2A server | `A2AExecutor(agent)` + `A2AStarletteApplication` |
| A2A client | `A2AAgent(name=..., url=...)` |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"type": "message", "role": "user", "contents": [{"type": "text", "text": "Remember: I love skiing holidays in Austria.", "additional_properties": {}}], "additional_properties": {}}
{"type": "message", "role": "assistant", "contents": [{"type": "text", "text": "Got it! You love skiing holidays in Austria. If you need recommendations or tips for your next ski trip, just let me know!", "additional_properties": {}}], "author_name": "persistent_concierge", "additional_properties": {}}
{"type": "message", "role": "user", "contents": [{"type": "text", "text": "What type of holiday did I mention I enjoy?", "additional_properties": {}}], "additional_properties": {}}
{"type": "message", "role": "assistant", "contents": [{"type": "text", "text": "You mentioned that you enjoy skiing holidays in Austria.", "additional_properties": {}}], "author_name": "persistent_concierge", "additional_properties": {}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

import asyncio
from dotenv import load_dotenv
load_dotenv(override=True)

import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from agent_framework.a2a import A2AExecutor
from agent_framework.openai import OpenAIChatCompletionClient

PORT = 9999

# Build the agent
llm_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
travel_agent = llm_client.as_agent(
name="RemoteTravelAdvisor",
instructions=(
"You are a travel advisor. Answer questions about destinations, flights, and trip planning. "
"Be concise and helpful."
),
)

# Describe the service via an AgentCard
agent_card = AgentCard(
name="Remote Travel Advisor",
description="A travel advisor agent available over A2A",
url=f"http://localhost:{PORT}/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities=AgentCapabilities(streaming=True),
skills=[
AgentSkill(
id="trip-planning",
name="Trip Planning",
description="Help plan trips including flights, hotels, and itineraries",
tags=["travel", "planning", "flights"],
examples=["Plan a 5-day trip to Rome", "Best time to visit Japan?"],
),
],
)

# Wire up executor → request handler → ASGI app
executor = A2AExecutor(travel_agent, stream=True)
handler = DefaultRequestHandler(agent_executor=executor, task_store=InMemoryTaskStore())
app = A2AStarletteApplication(agent_card=agent_card, http_handler=handler).build()

if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="warning")
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: travel-policy
description: Travel booking policies, refund rules, and passenger rights. Use when customers ask about booking changes, cancellations, refunds, rebooking fees, or their rights during delays and disruptions.
---

## Booking Policy

When assisting customers with travel policies, follow these steps:

1. Identify whether the query is about booking changes, cancellations, refunds, or disruptions.
2. Read the `refund-policy` resource for detailed rules before answering.
3. Provide clear, accurate information and quote the relevant rule where helpful.
4. Always confirm whether the customer needs further assistance.

## Key Rules (summary — always read the refund-policy resource for full details)

- Bookings are changeable up to 48 hours before departure for a $50 change fee.
- Cancellations within 24 hours of booking receive a full refund, no questions asked.
- Cancellations 7+ days before travel receive a 75% refund.
- Cancellations within 7 days of travel are non-refundable (medical exceptions apply).
- Flight delays over 3 hours entitle passengers to meal vouchers and accommodation if overnight.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Refund & Change Policy — Full Rules

## Change Fees
| Timing | Fee |
|---|---|
| More than 48 hours before departure | $50 change fee |
| Within 48 hours of departure | Not permitted (must cancel and rebook) |

## Cancellation Refunds
| Timing | Refund |
|---|---|
| Within 24 hours of booking | 100% refund |
| 7+ days before departure | 75% refund |
| 2–6 days before departure | 40% refund |
| Within 48 hours of departure | No refund |

## Medical Exceptions
Customers with a doctor's note can claim a full refund regardless of timing. Submit via the refunds portal within 30 days of the original travel date.

## Disruption Rights
- **Delay < 3 hours:** No compensation owed.
- **Delay 3–5 hours:** One meal voucher ($15 value).
- **Delay > 5 hours:** Full rebooking on next available flight at no cost, OR full refund.
- **Overnight delay:** Hotel accommodation + $30 meal allowance covered by airline.

## How to Submit a Refund
1. Visit the Refunds Portal at support.airline.example/refunds
2. Enter your booking reference and email address
3. Select the reason for cancellation
4. Upload supporting documents if applicable (e.g. medical note)
5. Allow 5–10 business days for processing
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Estimate the refund amount for a cancelled booking.

Usage:
python estimate_refund.py <original_price> <days_before_departure>

Arguments:
original_price Original ticket price in USD (e.g. 499)
days_before_departure Number of days before departure (e.g. 10)
"""

import sys
import json


def estimate_refund(price: float, days_before: int) -> dict:
if days_before < 0:
return {"error": "days_before_departure cannot be negative"}
if price <= 0:
return {"error": "original_price must be positive"}

if days_before >= 7:
pct = 0.75
rule = "7+ days before departure (75% refund)"
elif days_before >= 2:
pct = 0.40
rule = "2-6 days before departure (40% refund)"
else:
pct = 0.0
rule = "Within 48 hours of departure (no refund)"

refund = round(price * pct, 2)
return {
"original_price": price,
"days_before_departure": days_before,
"refund_percentage": int(pct * 100),
"refund_amount": refund,
"rule_applied": rule,
}


if __name__ == "__main__":
if len(sys.argv) < 3:
print(json.dumps({"error": "Usage: estimate_refund.py <price> <days_before>"}))
sys.exit(1)

try:
price = float(sys.argv[1])
days = int(sys.argv[2])
result = estimate_refund(price, days)
print(json.dumps(result))
except (ValueError, IndexError) as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
Binary file not shown.