Edit

Share via


Implementing User Feedback

User feedback is essential for the improvement of any application. Teams provides specialized UI components to help facilitate the gathering of feedback from users.

alt-text for feedback.gif

Storage

Once you receive a feedback event, you can choose to store it in some persistent storage. In the example below, we are storing it in an in-memory store.

// This store would ideally be persisted in a database
public static class FeedbackStore
{
    public static readonly Dictionary<string, FeedbackData> StoredFeedbackByMessageId = new();

    public class FeedbackData
    {
        public string IncomingMessage { get; set; } = string.Empty;
        public string OutgoingMessage { get; set; } = string.Empty;
        public int Likes { get; set; }
        public int Dislikes { get; set; }
        public List<string> Feedbacks { get; set; } = new();
    }
}

Once you receive a feedback event, you can choose to store it in some persistent storage. You'll need to implement storage for tracking:

  • Like/dislike counts per message
  • Text feedback comments
  • Message ID associations

For production applications, consider using databases, file systems, or cloud storage. The examples below use in-memory storage for simplicity.

import { ChatPrompt, IChatModel } from '@microsoft/teams.ai';
import { ActivityLike, IMessageActivity, MessageActivity } from '@microsoft/teams.api';
// ...

// This store would ideally be persisted in a database
export const storedFeedbackByMessageId = new Map<
  string,
  {
    incomingMessage: string;
    outgoingMessage: string;
    likes: number;
    dislikes: number;
    feedbacks: string[];
  }
>();

Including Feedback Buttons

When sending a message that you want feedback in, simply add feedback functionality to the message you are sending.

var sentMessageId = await context.Send(
    result.Content != null
        ? new MessageActivity(result.Content)
            .AddAiGenerated()
            /** Add feedback buttons via this method */
            .AddFeedback()
        : "I did not generate a response."
);

FeedbackStore.StoredFeedbackByMessageId[sentMessageId.Id] = new FeedbackStore.FeedbackData
{
    IncomingMessage = context.Activity.Text,
    OutgoingMessage = result.Content ?? string.Empty,
    Likes = 0,
    Dislikes = 0,
    Feedbacks = new List<string>()
};
from microsoft.teams.ai import Agent
from microsoft.teams.api import MessageActivityInput
from microsoft.teams.apps import ActivityContext, MessageActivity

@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
    """Handle 'feedback demo' command to demonstrate feedback collection"""
    agent = Agent(current_model)
    chat_result = await agent.send(
        input="Tell me a short joke",
        instructions="You are a comedian. Keep responses brief and funny."
    )

    if chat_result.response.content:
        message = MessageActivityInput(text=chat_result.response.content)
                    .add_ai_generated()
                    # Create message with feedback enabled
                    .add_feedback()
        await ctx.send(message)
import { ChatPrompt, IChatModel } from '@microsoft/teams.ai';
import {
  ActivityLike,
  IMessageActivity,
  MessageActivity,
  SentActivity,
} from '@microsoft/teams.api';
// ...

const { id: sentMessageId } = await send(
  result.content != null
    ? new MessageActivity(result.content)
        .addAiGenerated()
        /** Add feedback buttons via this method */
        .addFeedback()
    : 'I did not generate a response.'
);

storedFeedbackByMessageId.set(sentMessageId, {
  incomingMessage: activity.text,
  outgoingMessage: result.content ?? '',
  likes: 0,
  dislikes: 0,
  feedbacks: [],
});

Handling the feedback

Once the user decides to like/dislike the message, you can handle the feedback in a received event. Once received, you can choose to include it in your persistent store.

[Microsoft.Teams.Apps.Activities.Invokes.Message.Feedback]
public Task OnFeedbackReceived([Context] Microsoft.Teams.Api.Activities.Invokes.Messages.SubmitActionActivity activity)
{
    var reaction = activity.Value?.ActionValue?.GetType().GetProperty("reaction")?.GetValue(activity.Value?.ActionValue)?.ToString();
    var feedbackJson = activity.Value?.ActionValue?.GetType().GetProperty("feedback")?.GetValue(activity.Value?.ActionValue)?.ToString();

    if (activity.ReplyToId == null)
    {
        _log.LogWarning("No replyToId found for messageId {ActivityId}", activity.Id);
        return Task.CompletedTask;
    }

    var existingFeedback = FeedbackStore.StoredFeedbackByMessageId.GetValueOrDefault(activity.ReplyToId);
    /**
        * feedbackJson looks like:
        * {"feedbackText":"Nice!"}
        */
    if (existingFeedback == null)
    {
        _log.LogWarning("No feedback found for messageId {ActivityId}", activity.Id);
    }
    else
    {
        var updatedFeedback = new FeedbackStore.FeedbackData
        {
            IncomingMessage = existingFeedback.IncomingMessage,
            OutgoingMessage = existingFeedback.OutgoingMessage,
            Likes = existingFeedback.Likes + (reaction == "like" ? 1 : 0),
            Dislikes = existingFeedback.Dislikes + (reaction == "dislike" ? 1 : 0),
            Feedbacks = existingFeedback.Feedbacks.Concat(new[] { feedbackJson ?? string.Empty }).ToList()
        };

        FeedbackStore.StoredFeedbackByMessageId[activity.Id] = updatedFeedback;
    }

    return Task.CompletedTask;
}
import json
from typing import Dict, Any
from microsoft.teams.api import MessageSubmitActionInvokeActivity
from microsoft.teams.apps import ActivityContext
# ...

# Handle feedback submission events
@app.on_message_submit_feedback
async def handle_message_feedback(ctx: ActivityContext[MessageSubmitActionInvokeActivity]):
    """Handle feedback submission events"""
    activity = ctx.activity

    # Extract feedback data from activity value
    if not hasattr(activity, "value") or not activity.value:
        logger.warning(f"No value found in activity {activity.id}")
        return

    # Access feedback data directly from invoke value
    invoke_value = activity.value
    assert invoke_value.action_name == "feedback"
    feedback_str = invoke_value.action_value.feedback
    reaction = invoke_value.action_value.reaction
    feedback_json: Dict[str, Any] = json.loads(feedback_str)
    # { 'feedbackText': 'the ai response was great!' }

    if not activity.reply_to_id:
        logger.warning(f"No replyToId found for messageId {activity.id}")
        return

    # Store the feedback (implement your own storage logic)
    upsert_feedback_storage(activity.reply_to_id, reaction, feedback_json.get('feedbackText', ''))

    # Optionally Send confirmation response
    feedback_text: str = feedback_json.get("feedbackText", "")
    reaction_text: str = f" and {reaction}" if reaction else ""
    text_part: str = f" with comment: '{feedback_text}'" if feedback_text else ""

    await ctx.reply(f"✅ Thank you for your feedback{reaction_text}{text_part}!")
import { App } from '@microsoft/teams.apps';
// ...

app.on('message.submit.feedback', async ({ activity, log }) => {
  const { reaction, feedback: feedbackJson } = activity.value.actionValue;
  if (activity.replyToId == null) {
    log.warn(`No replyToId found for messageId ${activity.id}`);
    return;
  }
  const existingFeedback = storedFeedbackByMessageId.get(activity.replyToId);
  /**
   * feedbackJson looks like:
   * {"feedbackText":"Nice!"}
   */
  if (!existingFeedback) {
    log.warn(`No feedback found for messageId ${activity.id}`);
  } else {
    storedFeedbackByMessageId.set(activity.id, {
      ...existingFeedback,
      likes: existingFeedback.likes + (reaction === 'like' ? 1 : 0),
      dislikes: existingFeedback.dislikes + (reaction === 'dislike' ? 1 : 0),
      feedbacks: [...existingFeedback.feedbacks, feedbackJson],
    });
  }
});