From 4645d138c1c4ebc751e8e0730a0fbf18fb13d7a3 Mon Sep 17 00:00:00 2001 From: MehakBindra Date: Mon, 22 Jun 2026 16:24:54 -0700 Subject: [PATCH 1/2] Add Teams SDK end-to-end sample --- .../05-end-to-end/teams-agent/.env.example | 12 ++ .../05-end-to-end/teams-agent/README.md | 66 ++++++++++ .../samples/05-end-to-end/teams-agent/app.py | 117 ++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 python/samples/05-end-to-end/teams-agent/.env.example create mode 100644 python/samples/05-end-to-end/teams-agent/README.md create mode 100644 python/samples/05-end-to-end/teams-agent/app.py diff --git a/python/samples/05-end-to-end/teams-agent/.env.example b/python/samples/05-end-to-end/teams-agent/.env.example new file mode 100644 index 00000000000..22d30ef773b --- /dev/null +++ b/python/samples/05-end-to-end/teams-agent/.env.example @@ -0,0 +1,12 @@ +# Azure OpenAI (model the agent uses) +AZURE_OPENAI_ENDPOINT=https://.openai.azure.com +AZURE_OPENAI_MODEL= +AZURE_OPENAI_API_KEY= + +# Teams bot credentials (from your Azure Bot / app registration) +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= + +# Local hosting +PORT=3978 diff --git a/python/samples/05-end-to-end/teams-agent/README.md b/python/samples/05-end-to-end/teams-agent/README.md new file mode 100644 index 00000000000..e978641a60b --- /dev/null +++ b/python/samples/05-end-to-end/teams-agent/README.md @@ -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 + +## 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://.openai.azure.com" +AZURE_OPENAI_CHAT_COMPLETION_MODEL="" +AZURE_OPENAI_API_KEY="" + +# Teams bot credentials +CLIENT_ID="" +CLIENT_SECRET="" +TENANT_ID="" + +# Local hosting +PORT=3978 +``` + +`AZURE_OPENAI_CHAT_COMPLETION_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:///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_COMPLETION_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/) diff --git a/python/samples/05-end-to-end/teams-agent/app.py b/python/samples/05-end-to-end/teams-agent/app.py new file mode 100644 index 00000000000..2e6d5abc8cd --- /dev/null +++ b/python/samples/05-end-to-end/teams-agent/app.py @@ -0,0 +1,117 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "microsoft-teams-apps", +# "agent-framework-core", +# "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 +import os +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.")], +) -> 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. + 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(f"Sorry, an error occurred: {e}") + + +def main() -> None: + """Entry point: start the Teams bot HTTP listener.""" + asyncio.run(app.start()) + + +if __name__ == "__main__": + main() From ced75013e20c8418c8ab4a9d6286aee95c5a0a48 Mon Sep 17 00:00:00 2001 From: MehakBindra Date: Mon, 22 Jun 2026 19:11:49 -0700 Subject: [PATCH 2/2] fixes --- python/samples/05-end-to-end/teams-agent/.env.example | 2 +- python/samples/05-end-to-end/teams-agent/README.md | 6 +++--- python/samples/05-end-to-end/teams-agent/app.py | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/python/samples/05-end-to-end/teams-agent/.env.example b/python/samples/05-end-to-end/teams-agent/.env.example index 22d30ef773b..c09f7717651 100644 --- a/python/samples/05-end-to-end/teams-agent/.env.example +++ b/python/samples/05-end-to-end/teams-agent/.env.example @@ -1,6 +1,6 @@ # Azure OpenAI (model the agent uses) AZURE_OPENAI_ENDPOINT=https://.openai.azure.com -AZURE_OPENAI_MODEL= +AZURE_OPENAI_CHAT_MODEL= AZURE_OPENAI_API_KEY= # Teams bot credentials (from your Azure Bot / app registration) diff --git a/python/samples/05-end-to-end/teams-agent/README.md b/python/samples/05-end-to-end/teams-agent/README.md index e978641a60b..40a9681ad95 100644 --- a/python/samples/05-end-to-end/teams-agent/README.md +++ b/python/samples/05-end-to-end/teams-agent/README.md @@ -17,7 +17,7 @@ Create a `.env` file in this sample folder (see [.env.example](.env.example)): ```bash # Azure OpenAI (model the agent uses) AZURE_OPENAI_ENDPOINT="https://.openai.azure.com" -AZURE_OPENAI_CHAT_COMPLETION_MODEL="" +AZURE_OPENAI_CHAT_MODEL="" AZURE_OPENAI_API_KEY="" # Teams bot credentials @@ -29,7 +29,7 @@ TENANT_ID="" PORT=3978 ``` -`AZURE_OPENAI_CHAT_COMPLETION_MODEL` is the **deployment name** of your model, not the base model name. +`AZURE_OPENAI_CHAT_MODEL` is the **deployment name** of your model, not the base model name. ## Running the Agent Locally @@ -57,7 +57,7 @@ To exchange messages with the bot from Teams, Teams needs to reach your local en ## 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_COMPLETION_MODEL`, and `AZURE_OPENAI_API_KEY` are valid. +- **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 diff --git a/python/samples/05-end-to-end/teams-agent/app.py b/python/samples/05-end-to-end/teams-agent/app.py index 2e6d5abc8cd..f44551e335d 100644 --- a/python/samples/05-end-to-end/teams-agent/app.py +++ b/python/samples/05-end-to-end/teams-agent/app.py @@ -12,7 +12,6 @@ import asyncio import logging -import os from random import randint from typing import Annotated @@ -105,7 +104,7 @@ async def handle_message(ctx: ActivityContext[MessageActivity]) -> None: ctx.stream.emit(reply) except Exception as e: logger.exception("Error handling message: %s", e) - await ctx.send(f"Sorry, an error occurred: {e}") + await ctx.send("Sorry, an error occurred while processing your message.") def main() -> None: