适用于:SDK v4
Bot Framework 模板和示例是为 ASP.NET (C#)、restify (JavaScript) 和 aiohttp (Python) 编写的。
但是,Web 服务功能不是 Bot Framework SDK 的一部分,而是你选择使用的 Web 框架的一部分。
所有机器人应用程序共有一些常见功能。
Feature |
说明 |
资源预配 |
作为 Web 应用的机器人需要创建 Web 服务、机器人适配器和机器人对象。 |
消息传送终结点 |
Web 应用需要实现消息传送终结点,以便接收活动和将活动转发到机器人适配器。 |
机器人适配器 |
适配器从消息传送终结点接收活动,将其转发到机器人的轮次处理程序,并捕获机器人逻辑未捕获的任何错误或异常。 |
机器人对象 |
机器人对象处理轮次的机器人推理或逻辑。 |
可以从模板创建回显机器人,如创建机器人中所述,也可以从 Microsoft/BotBuilder-Samples 存储库复制回显机器人项目。
C# 和 JavaScript 模板内置了对流式连接的支持。 但是,本文不涉及流式处理功能。
注意
Bot Framework JavaScript、C# 和 Python SDK 将继续受支持,但 Java SDK 即将停用,最终长期支持将于 2023 年 11 月结束。
使用 Java SDK 构建的现有机器人将继续正常运行。
对于新的机器人生成,请阅读有关选择正确的 copilot 解决方案的信息。
有关详细信息,请参阅机器人构建的未来。
先决条件
机器人模板
机器人是一个 Web 应用程序,我们为每种语言都提供了对应的模板。
Bot Framework 包括 VSIX 和 .NET 模板。
模板生成 ASP.NET MVC Core Web 应用。 在 ASP.NET 基础知识中,可以看到 Program.cs 和 Startup.cs 等文件包含类似的代码。 这些文件并非特定于机器人,所有 Web 应用都需要它们。
注意
可以在 Visual Studio 中安装模板。
- 在菜单中,选择扩展,然后选择管理扩展。
- 在管理扩展对话框中,搜索并安装 Bot Framework v4 SDK templates for Visual Studio。
有关将 .NET 机器人部署到 Azure 的信息,请参阅如何预配和发布机器人。
appsettings.json 文件指定机器人的配置信息,例如其应用 ID 、密码等。 如果使用特定的技术或者在生产环境中使用此机器人,则需要将特定的密钥或 URL 添加到此配置。 但是,对于此回显机器人,目前不需要执行任何操作;暂时可将应用 ID 和密码保持未定义状态。
EchoBot.csproj 文件指定机器人的依赖项及其关联的版本。 所有这些信息由模板和系统设置。 可以使用 NuGet 包管理器或 dotnet add package
命令安装其他依赖项。
Yeoman 生成器创建 restify 类型的 Web 应用程序。 如果在相应文档中查看 restify 快速入门,会看到类似于生成的 index.js 文件的应用。 本文将介绍模板生成的一些关键文件。 此处未复制某些文件中的代码,但在运行机器人时会看到这些文件,或者可以参考 Node.js echobot 示例。
package.json 文件指定机器人的依赖项及其关联的版本。 所有这些信息由模板和系统设置。 可以使用 npm install
安装其他依赖项。
.env 文件指定机器人的配置信息,例如端口号、应用 ID 和密码等。 如果使用特定的技术或者在生产环境中使用此机器人,则需要将特定的密钥或 URL 添加到此配置。 但是,对于此聊天机器人,目前不需要执行任何操作;暂时可将应用 ID 和密码保持未定义状态。
若要使用 .env 配置文件,机器人需要 npm 中的 dotenv
包。 这已作为 package.json 文件中的依赖项包含在内。
Yeoman 生成器使用 Maven 创建基于 Spring 的 Web 应用程序,其中包含生成文件。
Maven pom.xml 文件指定机器人的依赖项及其关联的版本。 所有这些信息由模板和系统设置。 可以通过将条目添加到 pom.xml 文件来安装其他依赖项。
application.properties 文件指定机器人的配置信息,例如端口号、应用 ID 和密码等。 如果使用特定的技术或者在生产环境中使用此机器人,则需要将特定的密钥或 URL 添加到此配置。 但是,对于此聊天机器人,目前不需要执行任何操作;暂时可将应用 ID 和密码保持未定义状态。
Python Cookiecutter 模板创建 aiohttp Web 应用程序。
requirements.txt 文件指定机器人的依赖项及其关联的版本。 所有这些信息都由模板和系统设置。 可以使用 pip install -r requirements.txt
安装其他依赖项
config.py 文件指定机器人的配置信息,例如端口号、应用 ID 和密码等。 如果使用特定的技术或者在生产环境中使用此机器人,则需要将特定的密钥或 URL 添加到此配置。 但是,对于此聊天机器人,目前不需要执行任何操作;暂时可将应用 ID 和密码保持未定义状态。
资源预配
若要充当 Web 应用,机器人需要创建 Web 服务、机器人适配器和机器人对象。
对于大多数机器人,你还将为机器人创建存储层和内存管理对象。
但是,回响机器人不需要在轮次之间保持状态。
对于某些机器人,可能需要创建机器人对象或适配器需要的其他对象。
在 ASP.NET 中,在 Startup.cs 文件中注册对象和对象创建方法。
该 ConfigureServices
方法从 appsettings.json 加载连接的服务及其密钥(如果有),并完成连接状态等任务。 在这里,适配器和机器人被定义为通过依赖注入可用。
然后,该 Configure
方法完成应用的配置。
应用启动时,运行时会调用 ConfigureServices
和 Configure
。
在 restify 中,设置 Web 服务及其在 index.js 文件中所需的对象。 以下各节分别介绍了服务、适配器和机器人。
在 Spring 中,设置 Web 服务,以及 application.java 文件中所需的对象。 application.java 文件中含有注释,用于标识机器人应用程序使用的不同组件和框架类。 以下各节分别介绍了服务、适配器和机器人。
在 aiohttp 中,设置 Web 服务,以及 app.py 文件中所需的对象。 以下各节分别介绍了服务、适配器和机器人。
消息传送终结点
该模板使用消息传送终结点实现 Web 服务。
收到请求时,服务会提取身份验证标头并请求有效负载,然后将其转发到适配器。
C# 和 JavaScript SDK 支持流式处理连接。 虽然回响机器人不使用任何流式处理功能,但 C# 和 JavaScript 模板中的适配器旨在对其提供支持。
每个传入请求都代表着新轮次的开始。
Controllers\BotController.cs
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
// achieved by specifying a more specific type for the bot constructor argument.
[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()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
}
index.js
const { EchoBot } = require('./bot');
// Create HTTP server
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
// Create an adapter scoped to this WebSocket connection to allow storing session data.
const streamingAdapter = new CloudAdapter(botFrameworkAuthentication);
// Set onTurnError for the CloudAdapter created for each connection.
streamingAdapter.onTurnError = onTurnErrorHandler;
Application.java
@Import({BotController.class})
app.py
return await ADAPTER.process(req, BOT)
APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)
if __name__ == "__main__":
try:
web.run_app(APP, host="localhost", port=CONFIG.PORT)
except Exception as error:
raise error
机器人适配器
适配器从消息传送终结点接收活动,将其转发到机器人的轮次处理程序,并捕获机器人逻辑未捕获的任何错误或异常。 适配器还会将活动从机器人转发到用户的通道。
适配器允许你添加自己的“轮次错误”处理程序。
Startup.cs
要使用的适配器在 ConfigureServices
方法中定义。
// Create the Bot Framework Authentication to be used with the Bot Adapter.
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
AdapterWithErrorHandler.cs
public class AdapterWithErrorHandler : CloudAdapter
{
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)
: base(auth, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
// NOTE: In production environment, you should consider logging this to
// Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
// to add telemetry capture to your bot.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
// Send a message to the user
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.");
// Send a trace activity, which will be displayed in the Bot Framework Emulator
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
};
}
}
index.js
// application insights. See https://aka.ms/bottelemetry for telemetry
// configuration instructions.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await context.sendActivity('The bot encountered an error or bug.');
await context.sendActivity('To continue to run this bot, please fix the bot source code.');
};
// Set the onTurnError for the singleton CloudAdapter.
adapter.onTurnError = onTurnErrorHandler;
// Create the main dialog.
const myBot = new EchoBot();
// Listen for incoming requests.
server.post('/api/messages', async (req, res) => {
// Route received a request to adapter for processing
await adapter.process(req, res, (context) => myBot.run(context));
Application.java
要使用的适配器在 getBotFrameworkAdapter
方法中定义。
/**
* Returns a custom Adapter that provides error handling.
*
* @param configuration The Configuration object to use.
* @return An error handling BotFrameworkHttpAdapter.
*/
@Override
public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) {
return new AdapterWithErrorHandler(configuration);
}
AdapterWithErrorHandler 以 Java SDK 代码定义,包含在 com.microsoft.bot.integration 包中。 可以在 Java SDK 的源代码中查看此类。
app.py
from botbuilder.core import (
TurnContext,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication
# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG))
# Catch-all for errors.
# This check writes out errors to console log .vs. app insights.
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()
# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code."
)
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)
ADAPTER.on_turn_error = on_error
# Create the Bot
BOT = EchoBot()
机器人逻辑
回显机器人使用“活动处理程序”,并为它识别和响应的活动类型实现处理程序,在本例中,是“对话更新”和“消息”活动。
- 对话更新活动包括有关谁已加入或离开对话的信息。 对于非群组聊天,机器人和用户在对话启动时加入。 对于群组聊天,每当有人加入或离开对话时(无论是机器人还是用户),都生成对话更新。
- 消息活动表示用户发送到机器人的消息。
回显机器人在用户加入对话时表示欢迎,并回显他们发送到机器人的任何消息。
Startup.cs
要使用的机器人在 ConfigureServices
方法中定义。
// Create the Bot Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
Bots\EchoBot.cs
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);
}
}
}
}
index.js
const { EchoBot } = require('./bot');
});
// Listen for Upgrade requests for Streaming.
bot.js
const { ActivityHandler, MessageFactory } = require('botbuilder');
class EchoBot extends ActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (context, next) => {
const replyText = `Echo: ${ context.activity.text }`;
await context.sendActivity(MessageFactory.text(replyText, replyText));
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
const welcomeText = 'Hello and welcome!';
for (let cnt = 0; cnt < membersAdded.length; ++cnt) {
if (membersAdded[cnt].id !== context.activity.recipient.id) {
await context.sendActivity(MessageFactory.text(welcomeText, welcomeText));
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
Application.java
要使用的机器人在 getBot
方法中定义。
/**
* Returns the Bot for this application.
*
* <p>
* The @Component annotation could be used on the Bot class instead of this method
* with the @Bean annotation.
* </p>
*
* @return The Bot implementation for this application.
*/
@Bean
public Bot getBot() {
return new EchoBot();
}
EchoBot.cs
public class EchoBot extends ActivityHandler {
@Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
return turnContext.sendActivity(
MessageFactory.text("Echo: " + turnContext.getActivity().getText())
).thenApply(sendResult -> null);
}
@Override
protected CompletableFuture<Void> onMembersAdded(
List<ChannelAccount> membersAdded,
TurnContext turnContext
) {
String welcomeText = "Hello and welcome!";
return membersAdded.stream()
.filter(
member -> !StringUtils
.equals(member.getId(), turnContext.getActivity().getRecipient().getId())
).map(channel -> turnContext.sendActivity(MessageFactory.text(welcomeText, welcomeText, null)))
.collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null);
}
}
app.py
from botbuilder.core import (
TurnContext,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication
bots/bot.py
from botbuilder.core import ActivityHandler, MessageFactory, TurnContext
from botbuilder.schema import ChannelAccount
class EchoBot(ActivityHandler):
async def on_members_added_activity(
self, members_added: [ChannelAccount], turn_context: TurnContext
):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity("Hello and welcome!")
async def on_message_activity(self, turn_context: TurnContext):
return await turn_context.send_activity(
MessageFactory.text(f"Echo: {turn_context.activity.text}")
)
后续步骤