-
Notifications
You must be signed in to change notification settings - Fork 2k
Python: Add Teams SDK end-to-end sample #6676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4645d13
bd05d19
ced7501
913a4fc
2de9289
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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 | ||
|
|
||
| ## 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/) | ||
| 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", | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.")], | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ) -> 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. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this won't work in all cases, when using |
||||||
| 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() | ||||||
There was a problem hiding this comment.
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?