By Sagar Shankaran, Founder of CallSphere
Build an AI agent for Microsoft Teams using the Bot Framework SDK, design rich Adaptive Card interfaces for structured interactions, and handle conversation flows with proper permissions and authentication.
Key takeaways
Microsoft Teams is the default collaboration platform for enterprises using Microsoft 365. An AI agent in Teams can automate approvals, answer policy questions, generate reports, and orchestrate cross-system workflows for millions of enterprise users without requiring them to leave their primary workspace.
The Bot Framework SDK provides a structured way to build conversational bots that work across Teams, with Adaptive Cards offering rich, interactive UI components that render natively in the Teams client.
Register your bot in the Azure Bot Service, then use the Bot Framework SDK. The Python SDK uses an activity handler pattern where you override methods for different event types.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
sequenceDiagram
autonumber
participant Caller as Caller
participant Agent as CallSphere Agent
participant API as CRM API
participant DB as CRM Database
participant Webhook as Webhook Listener
Caller->>Agent: Inbound call begins
Agent->>Agent: STT plus intent detection
Agent->>API: Lookup contact by phone
API->>DB: Read contact record
DB-->>API: Contact and history
API-->>Agent: Personalized context
Agent->>API: Create call activity
Agent->>API: Update deal stage
API->>Webhook: Outbound webhook fires
Webhook-->>Agent: Confirmed
Agent->>Caller: Spoken confirmation
from botbuilder.core import (
ActivityHandler, TurnContext, MessageFactory
)
from botbuilder.schema import Activity, Attachment
import json
class AIAgentBot(ActivityHandler):
def __init__(self, agent_service):
self.agent = agent_service
async def on_message_activity(self, turn_context: TurnContext):
user_message = turn_context.activity.text
user_id = turn_context.activity.from_property.id
# Send typing indicator while agent processes
typing_activity = Activity(type="typing")
await turn_context.send_activity(typing_activity)
result = await self.agent.run(
prompt=user_message,
user_id=user_id,
)
await turn_context.send_activity(
MessageFactory.text(result.answer)
)
async def on_members_added_activity(self, members_added, turn_context):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity(
"Hello! I am your AI assistant. "
"Ask me anything or type 'help' for options."
)
Adaptive Cards are JSON-based UI templates that Teams renders natively. They support text, images, inputs, and action buttons — far richer than plain text responses.
def create_analysis_card(analysis: dict) -> Attachment:
card_json = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "Analysis Result",
"size": "Large",
"weight": "Bolder",
},
{
"type": "FactSet",
"facts": [
{"title": "Category", "value": analysis["category"]},
{"title": "Priority", "value": analysis["priority"]},
{"title": "Confidence", "value": f"{analysis['confidence']}%"},
],
},
{
"type": "TextBlock",
"text": analysis["summary"],
"wrap": True,
},
{
"type": "ActionSet",
"actions": [
{
"type": "Action.Submit",
"title": "Approve",
"data": {
"action": "approve",
"analysis_id": analysis["id"]
},
},
{
"type": "Action.Submit",
"title": "Reject",
"data": {
"action": "reject",
"analysis_id": analysis["id"]
},
},
],
},
],
}
return Attachment(
content_type="application/vnd.microsoft.card.adaptive",
content=card_json,
)
When a user clicks a button on an Adaptive Card, Teams sends the action data back to your bot as a message activity with a value property.
async def on_message_activity(self, turn_context: TurnContext):
activity = turn_context.activity
# Check if this is a card action submission
if activity.value:
await self.handle_card_action(turn_context, activity.value)
return
# Regular text message
await self.handle_text_message(turn_context)
async def handle_card_action(self, turn_context, action_data):
action = action_data.get("action")
analysis_id = action_data.get("analysis_id")
if action == "approve":
await self.agent.approve_analysis(analysis_id)
await turn_context.send_activity(
f"Analysis {analysis_id} approved and forwarded."
)
elif action == "reject":
# Show rejection reason input card
card = create_rejection_form_card(analysis_id)
message = MessageFactory.attachment(card)
await turn_context.send_activity(message)
Teams conversations can span channels, group chats, and 1:1 chats. Use the Bot Framework state management to persist context across turns.
from botbuilder.core import (
ConversationState, UserState, MemoryStorage
)
storage = MemoryStorage() # Use CosmosDB/Blob in production
conversation_state = ConversationState(storage)
user_state = UserState(storage)
class AIAgentBot(ActivityHandler):
def __init__(self, agent_service, conv_state, usr_state):
self.agent = agent_service
self.conv_state = conv_state
self.user_state = usr_state
self.conv_accessor = conv_state.create_property("ConvData")
self.user_accessor = usr_state.create_property("UserProfile")
async def on_message_activity(self, turn_context):
conv_data = await self.conv_accessor.get(turn_context, {})
user_profile = await self.user_accessor.get(turn_context, {})
history = conv_data.get("history", [])
history.append({"role": "user", "content": turn_context.activity.text})
result = await self.agent.run(
prompt=turn_context.activity.text,
history=history,
user_prefs=user_profile,
)
history.append({"role": "assistant", "content": result.answer})
conv_data["history"] = history[-20:] # Keep last 20 turns
await self.conv_accessor.set(turn_context, conv_data)
await self.conv_state.save_changes(turn_context)
await turn_context.send_activity(result.answer)
Teams apps require proper permission scoping in the app manifest. For AI agents, configure the minimum necessary permissions.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
# Validate that the user has permission for the requested action
async def check_user_permission(turn_context, required_role):
user_id = turn_context.activity.from_property.aad_object_id
member = await turn_context.activity.get_member(user_id)
user_roles = await get_roles_from_directory(user_id)
if required_role not in user_roles:
await turn_context.send_activity(
f"You need the '{required_role}' role for this action."
)
return False
return True
Register the bot in Azure Bot Service, deploy your Python application to Azure App Service or a container, and configure the messaging endpoint URL. Then create a Teams app package (manifest.json plus icons) and upload it to your organization's Teams app catalog through the Teams admin center.
Yes. Adaptive Cards support Input.Text, Input.ChoiceSet (dropdowns), Input.Date, Input.Toggle, and more. When paired with Action.Submit, the card sends all input values as a JSON object in the activity's value property, which your bot processes like any card action.
Teams limits individual messages to about 28 KB of text. For Adaptive Cards, the payload limit is 40 KB. If your AI agent generates large responses, split them across multiple messages or summarize and offer a "view full report" link to an external page.
#MicrosoftTeams #BotFramework #AdaptiveCards #AIAgents #EnterpriseIntegration #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Head-to-head: OpenAI Frontier and Anthropic's managed agent stack — strengths, fit, and what each means for enterprise AI voice and chat deployment.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.