Skip to main content

Migrating from Teams AI v1

Welcome, fellow agent developer! You've made it through a full major release of Teams AI, and now you want to take the plunge into v2. In this guide, we'll walk you through everything you need to know, from migrating core features like message handlers and auth, to optional AI features.

Installing Teams AI v2​

First, let's install Teams AI v2 into your project. Notably, this won't replace any existing installation of Teams AI v1. When you've completed your migration, you can safely remove the teams-ai dependency from your pyproject.toml file.

uv add microsoft-teams-apps

Migrate Application class​

First, migrate your Application class from v1 to the new App class.

# in main.py
import asyncio
import logging

from microsoft.teams.api import MessageActivity
from microsoft.teams.apps import ActivityContext, App, ErrorEvent
from microsoft.teams.common import LocalStorage

logger = logging.getLogger(__name__)

# Define the app
app = App()

# Optionally create local storage
storage: LocalStorage[str] = LocalStorage()

@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
await ctx.send(f"You said '{ctx.activity.text}'")

# Listen for errors
@app.event("error")
async def handle_error(event: ErrorEvent) -> None:
"""Handle errors."""
logger.error(f"Error occurred: {event.error}")
if event.context:
logger.warning(f"Context: {event.context}")


if __name__ == "__main__":
asyncio.run(app.start())

Migrate activity handlers​

Both Teams AI v1 and v2 are built atop incoming Activity requests, which trigger handlers in your code when specific type of activities are received. The syntax for how you register different types of Activity handlers differs slightly between the v1 and v2 versions of our SDK.

Message handlers​

# Triggered when user sends "hi", "hello", or "greetings"
@app.on_message_pattern(re.compile(r"hello|hi|greetings"))
async def handle_greeting(ctx: ActivityContext[MessageActivity]) -> None:
await ctx.reply("Hello! How can I assist you today?")

# Listens for ANY message received
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
# Sends a typing indicator
await ctx.reply(TypingActivityInput())
await ctx.send(f"You said '{ctx.activity.text}'")

Task modules​

Note that on Microsoft Teams, task modules have been renamed to dialogs.

@app.on_dialog_open
async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):
data: Optional[Any] = ctx.activity.value.data
dialog_type = data.get("opendialogtype") if data else None

if dialog_type == "some_type":
return InvokeResponse(
body=TaskModuleResponse(
task=TaskModuleContinueResponse(
value=UrlTaskModuleTaskInfo(
title="Dialog title",
height="medium",
width="medium",
url= f"https://${os.getenv("YOUR_WEBSITE_DOMAIN")}/some-path",
fallback_url= f"https://${os.getenv("YOUR_WEBSITE_DOMAIN")}/fallback-path-for-web",
completion_bot_id= os.getenv("ENTRA_APP_CLIENT_ID"),
)
)
)
)

@app.on_dialog_submit
async def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):
data: Optional[Any] = ctx.activity.value.data
dialog_type = data.get("submissiondialogtype") if data else None

if dialog_type == "some_type":
await ctx.send(json.dumps(ctx.activity.value))

return TaskModuleResponse(task=TaskModuleMessageResponse(value="Received submit"))

Learn more here.

Adaptive cards​

In Teams AI v2, cards have much more rich type validation than existed in v1. However, assuming your cards were valid, it should be easy to migrate to v2.

For existing cards like this, the simplest way to convert that to Teams AI v2 is this:

@app.on_message_pattern("/card")
async def handle_card_message(ctx: ActivityContext[MessageActivity]):
print(f"[CARD] Card requested by: {ctx.activity.from_}")
card = AdaptiveCard.model_validate(
{
"schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.6",
"type": "AdaptiveCard",
"body": [
{
"text": "Hello, world!",
"wrap": True,
"type": "TextBlock",
},
],
"msteams": {
"width": "Full"
}
}
)
await ctx.send(card)

Learn more here.

Authentication​

Most agents feature authentication for user identification, interacting with APIs, etc. Whether your Teams AI v1 app used Entra SSO or custom OAuth, porting to v2 should be simple.

app = App()

@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
ctx.logger.info("User requested sign-in.")
if ctx.is_signed_in:
await ctx.send("You are already signed in.")
else:
await ctx.sign_in()

@app.on_message_pattern("/signout")
async def handle_sign_out(ctx: ActivityContext[MessageActivity]):
await ctx.sign_out()
await ctx.send("You have been signed out.")

@app.event("sign_in")
async def handle_sign_in(event: SignInEvent):
"""Handle sign-in events."""
await event.activity_ctx.send("You are now signed in!")

@app.event("error")
async def handle_error(event: ErrorEvent):
"""Handle error events."""
print(f"Error occurred: {event.error}")
if event.context:
print(f"Context: {event.context}")

AI​

Feedback​

If you supported feedback for AI generated messages, migrating is simple.

# Reply with message including feedback buttons
@app.on_message
async def handle_feedback(ctx: ActivityContext[MessageActivity]):
await ctx.send(MessageActivityInput(text="Hey, give me feedback!").add_ai_generated().add_feedback())

@app.on_message_submit_feedback
async def handle_message_feedback(ctx: ActivityContext[MessageSubmitActionInvokeActivity]):
# Custom logic here..

Note: In Teams AI v2, you do not need to opt into feedback at the App level.

You can learn more about feedback in Teams AI v2 here.