Enhance the Teams Experience
You can enrich the agent output into a more Teams-native experience - adding structure, interactivity, and metadata on top of the generated text.
Streamingβ
Streaming allows the agent to deliver responses to Teams incrementally as theyβre generated, rather than waiting for the full reply to complete. Each chunk of text is appended to the stream as it arrives.
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
async for chunk in agent.run(ctx.activity.text or "", stream=True):
if chunk.text:
ctx.stream.emit(chunk.text)
See Streaming for the full story on how Teams renders chunks and the constraints on stream lifecycle.
AI-generated labelβ
add_ai_generated() marks the message as system-generated, ensuring it is clearly labeled as AI output within Teams.
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
async for chunk in agent.run(ctx.activity.text or "", stream=True):
if chunk.text:
ctx.stream.emit(chunk.text)
reply = MessageActivityInput().add_ai_generated()
ctx.stream.emit(reply)
Feedbackβ
add_feedback(mode="custom") enables built-in thumbs up/down controls on the reply and lets you surface a custom feedback form when users respond.
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
async for chunk in agent.run(ctx.activity.text or "", stream=True):
if chunk.text:
ctx.stream.emit(chunk.text)
reply = (MessageActivityInput().add_ai_generated()
.add_feedback(mode="custom")
)
ctx.stream.emit(reply)
See Feedback for the full form-handling story β capturing the submission, persisting it, and following up with the user.
Suggested promptsβ
Suggested prompts give the user one-click follow-up questions after a reply.
In Teams they render as chips under the message; tapping one sends the value back as a normal user message, so the same on_message handler picks it up β no extra routing required.
Define prompts using CardAction and attach them to the reply via with_suggested_actions:
from microsoft_teams.api import CardAction, CardActionType, SuggestedActions
_SUGGESTED_PROMPTS = [
CardAction(
type=CardActionType.IM_BACK,
title="How do I stream in teams.py?",
value="How do I stream in teams.py?",
),
CardAction(
type=CardActionType.IM_BACK,
title="How do I create an Adaptive Card in teams.py?",
value="How do I create an Adaptive Card in teams.py?",
),
]
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
async for chunk in agent.run(ctx.activity.text or "", stream=True):
if chunk.text:
ctx.stream.emit(chunk.text)
reply = (MessageActivityInput().add_ai_generated()
.add_feedback(mode="custom")
.with_suggested_actions(
SuggestedActions(to=[ctx.activity.from_.id], actions=_SUGGESTED_PROMPTS)
)
)
ctx.stream.emit(reply)

Citationsβ
Citations render as footnote-style references inline with the reply β [1], [2], etc. β surfacing the source title, abstract, and URL on hover. They typically originate from tool outputs, where middleware assigns each result a stable position (see the CitationMiddleware example earlier).
When building the final reply, attach only the citations whose position actually appears in the streamed text.
from microsoft_teams.api import CitationAppearance
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
full_text = ""
async for chunk in agent.run(text, session=_sessions[conversation_id], stream=True):
if chunk.text:
ctx.stream.emit(chunk.text)
full_text += chunk.text
reply = (MessageActivityInput().add_ai_generated()
.add_feedback(mode="custom")
.with_suggested_actions(
SuggestedActions(to=[ctx.activity.from_.id], actions=_SUGGESTED_PROMPTS)
)
)
citations = tool_logger.get_citations()
attach_citations(reply, full_text, citations)
ctx.stream.emit(reply)
def attach_citations(reply, full_text, citations):
used = extract_referenced_ids(full_text)
for c in citations:
if c.position in used:
reply.add_citation(
position=c.position,
appearance=CitationAppearance(
name=c.title,
abstract=c.description,
url=c.url),
)
