Skip to main content

Hello, Bot!

分类:  Azure机器人 标签:  #Azure Bot Framework SDK #Azure Bot Service #机器人 发布于: 2023-08-07 22:40:36

我们之前的文章简单的介绍了一下由微软提供的Azure Bot Framework SDK以及Azure Bot Service服务,比较不妙的是看起来这个服务真没啥人用,不能排除微软之后就不支持这个服务。但是Azure Bot Framework SDK是开源的,而且还有一个开源的社区,因此即便微软不支持Azure Bot Service了,Azure Bot Framework SDK还会以开源的形式继续存在,如果要做聊天机器人,还是可以继续使用这个框架,也可以考虑参与开源的开发。我为了自己的项目也尝试参与了两个开源的项目,一个是Bot基于SQL ServerIStore实现,另外一个是WeChatAdapter, 这两个项目之前都是有的,但是太老,几年没有更新了,不能支持.Net6,因此我做的工作就是修改了一下他们,让他们支持.Net6, IStore我已经提交了PR,但是还没有被接受,不过可用了,WeChatAdapter我还在调试,完成后也提交一下PR, 我PR估计比较严格,如果大家想用用看,也可以访问:

这个两个都是我的fork

我们之前简单的利用Visual Studio的模板来快速的创建了一个echo Bot, 我们这一章不使用Visual Studio的模板,而是一步一步的使用.Net Cli工具来创建一个Echo Bot, 这样掰开揉碎了,入门更容易。

Hello Bot

创建这个Bot之前还是要做一些准备:

一个Bot的应用,实际就是一个Web API的应用,因此我们首先利用.Net Cli创建一个Web Api的项目:

mkdir EchoBot
cd EchoBot
dotnet new webapi

创建好了我们的基本项目之后,我们需要添加必要的引用包:

dotnet add package Microsoft.Bot.Builder.Integration.AspNet.Core
dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

同时可以创建两个目录:

mkdir Bot
mkdir Adapter

Bot目录用于放置Bot定义的类, Adapter用于放置我们的Adapter类,用于和Channel通讯。
关于BotAdapterChannel, 您可以参考我之前的文章:https://www.azuredeveloper.cn/article/what-is-azure-bot-service

使用Visual Studio Code 打开当前的目录:

code .

创建Adapter

在目录Adapter里添加一个文件AdapterWithErrorHandler, 该类即是Adapter类,如下是该类的定义:

using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.TraceExtensions;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Logging;

namespace EchoBot.Adapter;
public class AdapterWithErrorHandler : CloudAdapter
{
    public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)
        : base(auth, logger)
    {
        OnTurnError = async (turnContext, exception) =>
        {
            //向日志中记录来自Bot中的异常
            logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

            // 发送消息通知用户有异常发生
            await turnContext.SendActivityAsync("The bot encountered an error or bug.");
            await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code.");

            //启用Activity trace, 如果使用模拟器调试可以看到详细的trace
            await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
        };
    }
}

Adapter类用于接收Channel的消息,并将消息封装成Activity, 同时生成TurnContext, 并且在Adaper里使用middleware机制,形成pipeline, 可以对进入的消息进行处理,同时由于Adapter生成回复的消息,也会使用middleware来依次处理即将回复给Channel的消息。

创建Bot

聊天机器人是一个单独的类,这个类用于接收由Adapter包装并处理好的Activity, 接收到Activity之后,Bot会根据Activity的类型不同,调用不同的处理程序,例如ActivityConversationUpdateActivity类型的时候,会调用OnConversationUpdateAsync的方法处理该Acvitity, 如果是MessageActivity的话,那么会调用OnMessageActivity来处理该Activity, 如果不想区分是什么类型,Bot还有一个保底的方法,那就是OnTurnAsync 方法,实际上所有的Bot都会先调用这个方法,然后在这个方法里根据Activity的类型,再调用相应的方法,因此如果您在自己的类中覆盖OnTurnAsync方法,一定要记得在方法里调用base()

我们这个Bot只需要实现很简单的功能,即用户输入了什么,echo back什么就好了, 同时在用户加入聊天的时候,输出欢迎词。

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;

namespace EchoBot.Bot;
public class EchoBot : ActivityHandler
{
    protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
    {
        var replyText = $"Echo: {turnContext.Activity.Text}";
        await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
    }

    protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
    {
        var welcomeText = "Hello and welcome!";
        foreach (var member in membersAdded)
        {
            if (member.Id != turnContext.Activity.Recipient.Id)
            {
                await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
            }
        }
    }
}

注意观察我们在Bot类里处理了两类类型的Activity,一个是MessageActivity, 另外一个是MemebersAddActivity, 所以定义了如上两个方法。

添加Service

打开Program.cs, 添加如下的服务:

using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Connector.Authentication;
using EchoBot.Adapter;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers().AddNewtonsoftJson(
    options =>
            {
                options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
            });

builder.Services.AddHttpClient();

builder.Services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
builder.Services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
builder.Services.AddTransient<IBot, EchoBot.Bots.EchoBot>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

尤其需要注意的是, 如果需要使用Bot,必须有这些配置:

uilder.Services.AddControllers().AddNewtonsoftJson(
    options =>
            {
                options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
            });

builder.Services.AddHttpClient();

builder.Services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
builder.Services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
builder.Services.AddTransient<IBot, EchoBot.Bots.EchoBot>();

配置Bot的调用

Bot就是一个WebAPI的应用,我们现在需要做的是给Bot配置一个调用的API: 在Controlls目录下添加一个文件BotController.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;

namespace EchoBot.Controllers;
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
    private readonly IBotFrameworkHttpAdapter _adapter;
    private readonly IBot _bot;

    public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
    {
        _adapter = adapter;
        _bot = bot;
    }

    [HttpPost, HttpGet]
    public async Task PostAsync()
    {
        await _adapter.ProcessAsync(Request, Response, _bot);
    }
}

我们透过方法PostAsync可以看到将AdapterBot类联合起来的方法:

await _adapter.ProcessAsync(Request, Response, _bot);

至此我们完成了一个Bot的开发了,可以尝试dotnet run看看结果了。

注意
启动Bot Framework Emulator之后,使用https的endpoint





需要注意上述红框的https地址

启动Bot Framework Emulator:


设置Bot的地址。

如下是运行的结果:

注意1和2的运行结果以及可以用于调试的界面: