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
12 changes: 12 additions & 0 deletions python/samples/05-end-to-end/teams-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Azure OpenAI (model the agent uses)
AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
AZURE_OPENAI_CHAT_MODEL=<deployment-name>
AZURE_OPENAI_API_KEY=

# Teams bot credentials (from your Azure Bot / app registration)
CLIENT_ID=
CLIENT_SECRET=
TENANT_ID=

# Local hosting
PORT=3978
66 changes: 66 additions & 0 deletions python/samples/05-end-to-end/teams-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Microsoft Agent Framework Python Weather Agent sample (Teams SDK)

This sample demonstrates a simple Weather Forecast Agent built with the Python Microsoft Agent Framework, hosted as a Microsoft Teams bot through the [Teams SDK (teams.py)](https://github.com/microsoft/teams.py). The agent accepts natural language weather requests, streams its reply token-by-token into the chat, and remembers context across turns.

## Prerequisites

- Python 3.11+
- [uv](https://github.com/astral-sh/uv) for fast dependency management
- [devtunnel](https://learn.microsoft.com/azure/developer/dev-tunnels/get-started?tabs=windows) for local testing
- An Azure OpenAI resource with a deployed model
- A Teams bot registration (Azure Bot) — App ID, password, and tenant

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a link to how to set this up?


## Configuration

Create a `.env` file in this sample folder (see [.env.example](.env.example)):

```bash
# Azure OpenAI (model the agent uses)
AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com"
AZURE_OPENAI_CHAT_MODEL="<deployment-name>"
AZURE_OPENAI_API_KEY="<api-key>"

# Teams bot credentials
CLIENT_ID="<app-id>"
CLIENT_SECRET="<client-secret>"
TENANT_ID="<tenant-id>"

# Local hosting
PORT=3978
```

`AZURE_OPENAI_CHAT_MODEL` is the **deployment name** of your model, not the base model name.

## Running the Agent Locally

```bash
uv run app.py
```

The bot starts an HTTP listener on `http://localhost:3978`; its messaging endpoint is `POST /api/messages`.

## Testing in Teams

To exchange messages with the bot from Teams, Teams needs to reach your local endpoint:

1. Create an Azure Bot (choose Client Secret auth for local tunneling) and copy its App ID, password, and tenant into `.env`.
2. Host a dev tunnel:

```bash
devtunnel host -p 3978 --allow-anonymous
```

3. Set the bot's **Messaging endpoint** to `https://<tunnel-host>/api/messages`.
4. Run the agent: `uv run app.py`.
5. Install the bot into a Teams chat and message it, e.g. `What's the weather in Seattle?`.

## Troubleshooting

- **404 on `/api/messages`**: Ensure you are POSTing and using the correct tunnel URL.
- **Empty responses**: Check that `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_CHAT_MODEL`, and `AZURE_OPENAI_API_KEY` are valid.
- **Auth errors from Teams**: Validate `CLIENT_ID` / `CLIENT_SECRET` / `TENANT_ID` match your Azure Bot registration.

## Further Reading

- [Microsoft Teams SDK for Python (teams.py)](https://github.com/microsoft/teams.py)
- [Devtunnel docs](https://learn.microsoft.com/azure/developer/dev-tunnels/)
116 changes: 116 additions & 0 deletions python/samples/05-end-to-end/teams-agent/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "microsoft-teams-apps",
# "agent-framework-core",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core is included when installing openai, so we can remove this here

# "agent-framework-openai",
# ]
# ///
# Copyright (c) Microsoft. All rights reserved.
# Run with any PEP 723 compatible runner, e.g.:
# uv run samples/05-end-to-end/teams-agent/app.py

import asyncio
import logging
from random import randint
from typing import Annotated

from agent_framework import Agent, AgentSession, tool
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
from microsoft_teams.api import CardAction, CardActionType, MessageActivity, MessageActivityInput, SuggestedActions
from microsoft_teams.apps import ActivityContext, App
from pydantic import Field

# Load environment variables from .env file
load_dotenv()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

"""
Demo application using the Microsoft Teams SDK (teams.py).

This sample demonstrates how to build an AI agent using the Agent Framework,
hosted as a Microsoft Teams bot through the Teams SDK.

Key features:
- Loads OpenAI credentials and Teams bot configuration from environment variables.
- Demonstrates agent creation and tool registration.
- Streams the agent response token-by-token into the Teams chat.
- Maintains per-conversation AgentSession for multi-turn memory.

To run, set the Teams bot credentials and OpenAI credentials (check .env.example),
then point your bot's messaging endpoint at this app (e.g. via a dev tunnel).
"""


# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in
# production; see samples/02-agents/tools/function_tool_with_approval.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
location: Annotated[str, Field(description="The location to get the weather for.")],
location: Annotated[str, "The location to get the weather for."],

) -> str:
"""Generate a mock weather report for the provided location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."


def build_agent() -> Agent:
"""Create and return the agent instance with the weather tool registered."""
# Reads AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, and AZURE_OPENAI_CHAT_COMPLETION_MODEL from the environment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't work in all cases, when using OpenAIChatClient it first tries to see if there are oai keys, we prefer to have FoundryChatCLient with a AzureCliCredential in our samples

client = OpenAIChatClient()
return Agent(
client=client,
name="WeatherAgent",
instructions="You are a helpful weather agent. Keep your answers brief.",
tools=get_weather,
)


# Reads CLIENT_ID, CLIENT_SECRET, TENANT_ID, and PORT from the environment.
app = App()
agent = build_agent()

# Per-conversation sessions preserve message history across turns.
_sessions: dict[str, AgentSession] = {}


@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]) -> None:
"""Run the agent for each incoming Teams message and stream the reply back."""
try:
conversation_id = ctx.activity.conversation.id
session = _sessions.setdefault(conversation_id, agent.create_session())

text = ctx.activity.text or ""
if not text.strip():
return

async for chunk in agent.run(text, session=session, stream=True):
if chunk.text:
ctx.stream.emit(chunk.text)

# Add suggested follow-up questions and AI generated label after streaming completes
suggested_actions = SuggestedActions(
to=[ctx.activity.from_.id],
actions=[
CardAction(type=CardActionType.IM_BACK, title="New York weather", value="What's the weather in New York?"),
CardAction(type=CardActionType.IM_BACK, title="San Francisco weather", value="What's the weather in San Francisco?"),
],
)
reply = MessageActivityInput().add_ai_generated().add_feedback()
reply.with_suggested_actions(suggested_actions)
ctx.stream.emit(reply)
except Exception as e:
logger.exception("Error handling message: %s", e)
await ctx.send("Sorry, an error occurred while processing your message.")


def main() -> None:
"""Entry point: start the Teams bot HTTP listener."""
asyncio.run(app.start())


if __name__ == "__main__":
main()
Loading