1 min read
Azure Bot Service Updates: Building Intelligent Conversational AI
I wrote “Azure Bot Service Updates: Building Intelligent Conversational AI” to share practical, production-minded guidance on this topic.
Bot Framework Composer Updates
Bot Framework Composer now supports more advanced dialog patterns:
// dialog.schema
{
"$kind": "Microsoft.AdaptiveDialog",
"autoEndDialog": true,
"defaultResultProperty": "dialog.result",
"triggers": [
{
"$kind": "Microsoft.OnConversationUpdateActivity",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "${WelcomeMessage()}"
}
]
},
{
"$kind": "Microsoft.OnIntent",
"intent": "OrderStatus",
"actions": [
{
"$kind": "Microsoft.BeginDialog",
"dialog": "OrderStatusDialog"
}
]
}
]
}
Building with Bot Framework SDK
Basic Bot with OpenAI Integration
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Azure.AI.OpenAI;
public class OpenAIBot : ActivityHandler
{
private readonly OpenAIClient _openAIClient;
private readonly ConversationState _conversationState;
public OpenAIBot(OpenAIClient openAIClient, ConversationState conversationState)
{
_openAIClient = openAIClient;
_conversationState = conversationState;
}
protected override async Task OnMessageActivityAsync(
ITurnContext<IMessageActivity> turnContext,
CancellationToken cancellationToken)
{
var conversationData = await GetConversationDataAsync(turnContext, cancellationToken);
// Add user message to history
conversationData.History.Add(new ChatMessage(ChatRole.User, turnContext.Activity.Text));
// Generate response using OpenAI
var chatCompletionsOptions = new ChatCompletionsOptions
{
DeploymentName = "gpt-35-turbo",
Messages = {
new ChatMessage(ChatRole.System, GetSystemPrompt())
}
};
foreach (var message in conversationData.History.TakeLast(10))
{
chatCompletionsOptions.Messages.Add(message);
}
var response = await _openAIClient.GetChatCompletionsAsync(chatCompletionsOptions, cancellationToken);
var assistantMessage = response.Value.Choices[0].Message.Content;
// Add assistant response to history
conversationData.History.Add(new ChatMessage(ChatRole.Assistant, assistantMessage));
await turnContext.SendActivityAsync(
MessageFactory.Text(assistantMessage),
cancellationToken);
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
private string GetSystemPrompt() => """
You are a helpful customer service assistant for Contoso.
Be polite, concise, and helpful.
If you don't know something, say so rather than making things up.
""";
private async Task<ConversationData> GetConversationDataAsync(
ITurnContext turnContext,
CancellationToken cancellationToken)
{
var accessor = _conversationState.CreateProperty<ConversationData>("ConversationData");
return await accessor.GetAsync(turnContext, () => new ConversationData(), cancellationToken);
}
}
public class ConversationData
{
public List<ChatMessage> History { get; set; } = new();
}
Skill Integration
// SkillBot.cs - Child skill
public class OrderSkillBot : ActivityHandler
{
protected override async Task OnMessageActivityAsync(
ITurnContext<IMessageActivity> turnContext,
CancellationToken cancellationToken)
{
var text = turnContext.Activity.Text.ToLower();
if (text.Contains("order status"))
{
var orderId = ExtractOrderId(text);
var status = await GetOrderStatusAsync(orderId);
await turnContext.SendActivityAsync(
MessageFactory.Text($"Order {orderId}: {status}"),
cancellationToken);
// End skill
await turnContext.SendActivityAsync(
new Activity { Type = ActivityTypes.EndOfConversation },
cancellationToken);
}
}
}
// ParentBot.cs - Root bot that uses skills
public class ParentBot : ActivityHandler
{
private readonly SkillHttpClient _skillClient;
private readonly SkillsConfiguration _skillsConfig;
protected override async Task OnMessageActivityAsync(
ITurnContext<IMessageActivity> turnContext,
CancellationToken cancellationToken)
{
var intent = await ClassifyIntentAsync(turnContext.Activity.Text);
if (intent == "order")
{
// Forward to order skill
var skill = _skillsConfig.Skills["OrderSkill"];
await _skillClient.PostActivityAsync(
_skillsConfig.SkillHostEndpoint,
skill,
turnContext.Activity.ServiceUrl,
turnContext.Activity,
cancellationToken);
}
else
{
// Handle locally
await HandleLocallyAsync(turnContext, cancellationToken);
}
}
}
Adaptive Cards with Dynamic Content
public class CardFactory
{
public static Attachment CreateOrderStatusCard(Order order)
{
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 4))
{
Body = new List<AdaptiveElement>
{
new AdaptiveTextBlock
{
Text = $"Order #{order.OrderId}",
Size = AdaptiveTextSize.Large,
Weight = AdaptiveTextWeight.Bolder
},
new AdaptiveFactSet
{
Facts = new List<AdaptiveFact>
{
new AdaptiveFact("Status", order.Status),
new AdaptiveFact("Date", order.OrderDate.ToShortDateString()),
new AdaptiveFact("Total", order.Total.ToString("C"))
}
},
new AdaptiveContainer
{
Items = order.Items.Select(item => new AdaptiveColumnSet
{
Columns = new List<AdaptiveColumn>
{
new AdaptiveColumn
{
Width = "stretch",
Items = new List<AdaptiveElement>
{
new AdaptiveTextBlock { Text = item.Name }
}
},
new AdaptiveColumn
{
Width = "auto",
Items = new List<AdaptiveElement>
{
new AdaptiveTextBlock { Text = item.Price.ToString("C") }
}
}
}
}).ToList<AdaptiveElement>()
}
},
Actions = new List<AdaptiveAction>
{
new AdaptiveSubmitAction
{
Title = "Track Package",
Data = new { action = "track", orderId = order.OrderId }
},
new AdaptiveOpenUrlAction
{
Title = "View Details",
Url = new Uri($"https://contoso.com/orders/{order.OrderId}")
}
}
};
return new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = card
};
}
}
Multi-channel Deployment
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Bot configuration
builder.Services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
builder.Services.AddSingleton<IBot, OpenAIBot>();
// Channel-specific configuration
builder.Services.Configure<TeamsChannelOptions>(options =>
{
options.AllowedTenants = new[] { "tenant-id" };
});
builder.Services.Configure<SlackChannelOptions>(options =>
{
options.VerificationToken = builder.Configuration["Slack:VerificationToken"];
});
var app = builder.Build();
// Bot endpoints
app.MapPost("/api/messages", async (IBotFrameworkHttpAdapter adapter, IBot bot, HttpContext context) =>
{
await adapter.ProcessAsync(context.Request, context.Response, bot);
});
// Teams-specific endpoint
app.MapPost("/api/teams", async (TeamsAdapter adapter, IBot bot, HttpContext context) =>
{
await adapter.ProcessAsync(context.Request, context.Response, bot);
});
app.Run();
Proactive Messaging
public class ProactiveMessagingService
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly IConversationReferenceStore _referenceStore;
public ProactiveMessagingService(
IBotFrameworkHttpAdapter adapter,
IConversationReferenceStore referenceStore)
{
_adapter = adapter;
_referenceStore = referenceStore;
}
public async Task SendOrderUpdateAsync(string userId, Order order)
{
var reference = await _referenceStore.GetReferenceAsync(userId);
if (reference == null)
{
return; // User hasn't talked to bot
}
await ((BotAdapter)_adapter).ContinueConversationAsync(
reference.BotId,
reference,
async (turnContext, cancellationToken) =>
{
var card = CardFactory.CreateOrderStatusCard(order);
await turnContext.SendActivityAsync(
MessageFactory.Attachment(card),
cancellationToken);
},
CancellationToken.None);
}
public async Task SendBroadcastAsync(string message)
{
var references = await _referenceStore.GetAllReferencesAsync();
var tasks = references.Select(reference =>
((BotAdapter)_adapter).ContinueConversationAsync(
reference.BotId,
reference,
async (turnContext, cancellationToken) =>
{
await turnContext.SendActivityAsync(
MessageFactory.Text(message),
cancellationToken);
},
CancellationToken.None));
await Task.WhenAll(tasks);
}
}
QnA Maker Integration
public class QnADialog : ComponentDialog
{
private readonly QnAMakerClient _qnaClient;
public QnADialog(QnAMakerClient qnaClient) : base(nameof(QnADialog))
{
_qnaClient = qnaClient;
var waterfallSteps = new WaterfallStep[]
{
GetAnswerAsync,
ProcessFeedbackAsync
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
}
private async Task<DialogTurnResult> GetAnswerAsync(
WaterfallStepContext stepContext,
CancellationToken cancellationToken)
{
var question = stepContext.Context.Activity.Text;
var response = await _qnaClient.GetAnswersAsync(
stepContext.Context,
new QnAMakerOptions
{
Top = 1,
ScoreThreshold = 0.5f
});
if (response?.FirstOrDefault() != null)
{
var answer = response.First();
await stepContext.Context.SendActivityAsync(
MessageFactory.Text(answer.Answer),
cancellationToken);
return await stepContext.PromptAsync(
nameof(ConfirmPrompt),
new PromptOptions
{
Prompt = MessageFactory.Text("Was this helpful?")
},
cancellationToken);
}
await stepContext.Context.SendActivityAsync(
MessageFactory.Text("I don't have information about that. Let me connect you with a human agent."),
cancellationToken);
return await stepContext.EndDialogAsync(null, cancellationToken);
}
private async Task<DialogTurnResult> ProcessFeedbackAsync(
WaterfallStepContext stepContext,
CancellationToken cancellationToken)
{
var helpful = (bool)stepContext.Result;
if (!helpful)
{
// Log for improvement
await LogNegativeFeedbackAsync(stepContext.Context.Activity.Text);
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
}
Testing Bots
using Microsoft.Bot.Builder.Testing;
using Xunit;
public class BotTests
{
[Fact]
public async Task Welcome_Message_On_Conversation_Start()
{
var adapter = new TestAdapter();
var bot = new OpenAIBot(CreateMockOpenAIClient(), new ConversationState(new MemoryStorage()));
await adapter
.Send(new Activity { Type = ActivityTypes.ConversationUpdate, MembersAdded = new[] { new ChannelAccount("user") } })
.AssertReply(activity =>
{
Assert.Contains("Welcome", activity.AsMessageActivity().Text);
})
.StartTestAsync();
}
[Fact]
public async Task Handles_Order_Status_Intent()
{
var adapter = new TestAdapter();
var bot = CreateBot();
await adapter
.Send("What's the status of my order 12345?")
.AssertReply(activity =>
{
var text = activity.AsMessageActivity().Text;
Assert.Contains("12345", text);
Assert.Contains("status", text.ToLower());
})
.StartTestAsync();
}
}
Conclusion
Azure Bot Service provides a comprehensive platform for building conversational AI. With OpenAI integration, multi-channel support, and robust SDK features, you can create intelligent bots that provide real value to users. The key is combining structured dialog flows with AI-powered natural language understanding.