หมายเหตุ
การเข้าถึงหน้านี้ต้องได้รับการอนุญาต คุณสามารถลอง ลงชื่อเข้าใช้หรือเปลี่ยนไดเรกทอรีได้
การเข้าถึงหน้านี้ต้องได้รับการอนุญาต คุณสามารถลองเปลี่ยนไดเรกทอรีได้
ด้วยช่องทาง Omni สําหรับ Customer Service คุณสามารถใช้ตัวเชื่อมต่อเพื่อรวมช่องทางการส่งข้อความแบบกําหนดเองโดยใช้ Direct Line API 3.0 ซึ่งเป็นส่วนหนึ่งของ .NET SDK โค้ดตัวอย่างที่สมบูรณ์แสดงให้เห็นว่าคุณสามารถสร้างตัวเชื่อมต่อของคุณเองได้อย่างไร เมื่อต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Direct Line API 3.0 โปรดดู แนวคิดหลักใน Direct Line 3.0 API
บทความนี้อธิบายวิธีที่ช่องทางเชื่อมต่อกับ Microsoft Direct Line Bot Framework ซึ่งถูกเชื่อมต่อภายในกับระบบ Omnichannel สำหรับการบริการลูกค้า ส่วนต่อไปนี้ประกอบด้วยส่วนย่อยของโค้ดที่ใช้ Direct Line API 3.0 เพื่อสร้างไคลเอ็นต์ Direct Line และ IChannelAdapter
อินเทอร์เฟซเพื่อสร้างตัวเชื่อมต่อตัวอย่าง
หมายเหตุ
ซอร์สโค้ดและเอกสารประกอบอธิบายโฟลว์โดยรวมของวิธีที่ช่องทางสามารถเชื่อมต่อกับช่องทาง Omni สําหรับ Customer Service ผ่าน Direct Line และไม่มุ่งเน้นไปที่แง่มุมของความน่าเชื่อถือและความสามารถในการปรับขนาด
คอมโพเนนต์
บริการ API Webhook ของอะแดปเตอร์
เมื่อผู้ใช้ป้อนข้อความ API อะแดปเตอร์จะถูกเรียกใช้จากช่องทาง ประมวลผลคําขอขาเข้าและส่งสถานะความสําเร็จหรือความล้มเหลวเป็นการตอบสนอง บริการ API ของอะแดปเตอร์ต้องใช้ IChannelAdapter
อินเทอร์เฟซ และส่งคําขอขาเข้าไปยังอะแดปเตอร์ช่องทางที่เกี่ยวข้องเพื่อประมวลผลคําขอ
/// <summary>
/// Accept an incoming web-hook request from MessageBird Channel
/// </summary>
/// <param name="requestPayload">Inbound request Object</param>
/// <returns>Executes the result operation of the action method asynchronously.</returns>
[HttpPost("postactivityasync")]
public async Task<IActionResult> PostActivityAsync(JToken requestPayload)
{
if (requestPayload == null)
{
return BadRequest("Request payload is invalid.");
}
try
{
await _messageBirdAdapter.ProcessInboundActivitiesAsync(requestPayload, Request).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError($"postactivityasync: {ex}");
return StatusCode(500, "An error occured while handling your request.");
}
return StatusCode(200);
}
อะแดปเตอร์ช่องสัญญาณ
อะแดปเตอร์ช่องทางประมวลผลกิจกรรมขาเข้าและขาออก และต้องใช้อินเทอร์เฟซIAdapterBuilder
ประมวลผลกิจกรรมขาเข้า
อะแดปเตอร์ช่องทางดําเนินการกิจกรรมขาเข้าต่อไปนี้:
- ตรวจสอบลายเซ็นคําขอข้อความขาเข้า
คําขอขาเข้าจากช่องทางจะได้รับการตรวจสอบตามคีย์การลงนาม หากคําขอไม่ถูกต้อง จะมีการโยนข้อความข้อยกเว้น "ลายเซ็นไม่ถูกต้อง" หากคําขอถูกต้อง จะดําเนินการดังนี้:
/// <summary>
/// Validate Message Bird Request
/// </summary>
/// <param name="content">Request Content</param>
/// <param name="request">HTTP Request</param>
/// <param name="messageBirdSigningKey">Message Bird Signing Key</param>
/// <returns>True if there request is valid, false if there aren't.</returns>
public static bool ValidateMessageBirdRequest(string content, HttpRequest request, string messageBirdSigningKey)
{
if (string.IsNullOrWhiteSpace(messageBirdSigningKey))
{
throw new ArgumentNullException(nameof(messageBirdSigningKey));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrWhiteSpace(content))
{
throw new ArgumentNullException(nameof(content));
}
var messageBirdRequest = new MessageBirdRequest(
request.Headers?["Messagebird-Request-Timestamp"],
request.QueryString.Value?.Equals("?",
StringComparison.CurrentCulture) != null
? string.Empty
: request.QueryString.Value,
GetBytes(content));
var messageBirdRequestSigner = new MessageBirdRequestSigner(GetBytes(messageBirdSigningKey));
string expectedSignature = request.Headers?["Messagebird-Signature"];
return messageBirdRequestSigner.IsMatch(expectedSignature, messageBirdRequest);
}
- แปลงคําขอขาเข้าเป็นกิจกรรมบอท
เพย์โหลดคําขอขาเข้าจะถูกแปลงเป็นกิจกรรมที่ Bot Framework สามารถเข้าใจได้
หมายเหตุ
เพย์โหลดกิจกรรมต้องไม่เกินขีดจํากัดขนาดข้อความที่ 28 KB
ออบเจ็กต์กิจกรรมนี้มีแอตทริบิวต์ต่อไปนี้:
แอตทริบิวต์ | คำอธิบาย |
---|---|
จาก | จัดเก็บข้อมูลบัญชีช่องที่ประกอบด้วยตัวระบุเฉพาะของผู้ใช้และชื่อ (การรวมกันของชื่อและนามสกุล คั่นด้วยตัวคั่นเว้นวรรค) |
channelId | ระบุรหัสช่อง สําหรับคําขอขาเข้า รหัสช่องทางคือdirectline |
URL ของบริการ | ระบุ URL ของบริการ สําหรับคําขอขาเข้า URL ของบริการคือ https://directline.botframework.com/ . |
ชนิด | ระบุชนิดกิจกรรม สําหรับกิจกรรมข้อความ ชนิดคือ message . |
ข้อความ | จัดเก็บเนื้อหาข้อความ |
รหัส | ระบุตัวระบุที่อะแดปเตอร์ใช้เพื่อตอบสนองต่อข้อความขาออก |
channelData | ระบุข้อมูลช่องที่ประกอบด้วย channelType , conversationcontext และ customercontext . |
ประเภทช่อง | ระบุชื่อช่องทางที่ลูกค้าส่งข้อความ ตัวอย่างเช่น MessageBird, KakaoTalk, Snapchat |
บริบทการสนทนา | อ้างถึงวัตถุพจนานุกรมที่เก็บตัวแปรบริบทที่กําหนดไว้ในสตรีมงาน ช่องทาง Omni สําหรับ Customer Service ใช้ข้อมูลนี้เพื่อกําหนดเส้นทางการสนทนาไปยังตัวแทนฝ่ายบริการลูกค้าที่เหมาะสม (ตัวแทนบริการหรือตัวแทน) เช่น: "conversationcontext":{ "ProductName": "Xbox", "Issue":"การติดตั้ง" } ในตัวอย่างนี้ บริบทจะกําหนดเส้นทางการสนทนาไปยังตัวแทนบริการที่เกี่ยวข้องกับการติดตั้ง Xbox |
บริบทของลูกค้า | หมายถึงวัตถุพจนานุกรมที่เก็บรายละเอียดลูกค้า เช่น หมายเลขโทรศัพท์และที่อยู่อีเมล Omnichannel สำหรับการบริการลูกค้าใช้ข้อมูลนี้เพื่อระบุบันทึกผู้ติดต่อของผู้ใช้ "customercontext":{ "email":email@email.com, "phonenumber":"1234567890" } |
/// <summary>
/// Build Bot Activity type from the inbound MessageBird request payload<see cref="Activity"/>
/// </summary>
/// <param name = "messagePayload"> Message Bird Activity Payload</param>
/// <returns>Direct Line Activity</returns>
public static Activity PayloadToActivity(MessageBirdRequestModel messagePayload)
{
if (messagePayload == null)
{
throw new ArgumentNullException(nameof(messagePayload));
}
if (messagePayload.Message?.Direction == ConversationMessageDirection.Sent ||
messagePayload.Type == ConversationWebhookMessageType.MessageUpdated)
{
return null;
}
var channelData = new ActivityExtension
{
ChannelType = ChannelType.MessageBird,
// Add Conversation Context in below dictionary object. Please refer the document for more information.
ConversationContext = new Dictionary<string, string>(),
// Add Customer Context in below dictionary object. Please refer the document for more information.
CustomerContext = new Dictionary<string, string>()
};
var activity = new Activity
{
From = new ChannelAccount(messagePayload.Message?.From, messagePayload.Contact?.DisplayName),
Text = messagePayload.Message?.Content?.Text,
Type = ActivityTypes.Message,
Id = messagePayload.Message?.ChannelId,
ServiceUrl = Constant.DirectLineBotServiceUrl,
ChannelData = channelData
};
return activity;
}
ตัวอย่างเพย์โหลด JSON มีดังนี้:
{
"type": "message",
"id": "bf3cc9a2f5de...",
"serviceUrl": https://directline.botframework.com/,
"channelId": "directline",
"from": {
"id": "1234abcd",// userid which uniquely identify the user
"name": "customer name" // customer name as First Name <space> Last Name
},
"text": "Hi,how are you today.",
"channeldata":{
"channeltype":"messageBird",
"conversationcontext ":{ // this holds context variables defined in Workstream
"ProductName" : "XBox",
"Issue":"Installation"
},
"customercontext":{
"email":email@email.com,
"phonenumber":"1234567890"
}
}
}
- ส่งกิจกรรมไปยังตัวประมวลผลการถ่ายทอดข้อความ
หลังจากสร้างเพย์โหลดกิจกรรมแล้ว จะเรียกใช้เมธอด PostActivityAsync ของตัวประมวลผลรีเลย์ข้อความเพื่อส่งกิจกรรมไปยัง Direct Line อะแดปเตอร์ช่องควรส่งผ่านตัวจัดการเหตุการณ์ ซึ่งตัวประมวลผลรีเลย์จะเรียกใช้เมื่อได้รับข้อความขาออกจาก Omnichannel สำหรับบริการลูกค้าผ่าน Direct Line
ประมวลผลกิจกรรมขาออก
ตัวประมวลผลรีเลย์เรียกใช้ตัวจัดการเหตุการณ์เพื่อส่งกิจกรรมขาออกไปยังอะแดปเตอร์ช่องทางที่เกี่ยวข้อง และจากนั้นอะแดปเตอร์จะประมวลผลกิจกรรมขาออก อะแดปเตอร์ช่องทางจะดําเนินการกิจกรรมขาออกต่อไปนี้:
- แปลงกิจกรรมขาออกเป็นแบบจําลองการตอบสนองของช่องทาง
กิจกรรม Direct Line จะถูกแปลงเป็นแบบจําลองการตอบสนองเฉพาะช่องทาง
/// <summary>
/// Creates MessageBird response object from a Bot Framework <see cref="Activity"/>.
/// </summary>
/// <param name="activities">The outbound activities.</param>
/// <param name="replyToId">Reply Id of Message Bird user.</param>
/// <returns>List of MessageBird Responses.</returns>
public static List<MessageBirdResponseModel> ActivityToMessageBird(IList<Activity> activities, string replyToId)
{
if (string.IsNullOrWhiteSpace(replyToId))
{
throw new ArgumentNullException(nameof(replyToId));
}
if (activities == null)
{
throw new ArgumentNullException(nameof(activities));
}
return activities.Select(activity => new MessageBirdResponseModel
{
To = replyToId,
From = activity.ChannelId,
Type = "text",
Content = new Content
{
Text = activity.Text
}
}).ToList();
}
- ส่งการตอบกลับผ่าน REST API ของช่องทาง
อะแดปเตอร์ช่องทางเรียก REST API เพื่อส่งการตอบสนองขาออกไปยังช่องทาง ซึ่งจะถูกส่งไปยังผู้ใช้
/// <summary>
/// Send Outbound Messages to Message Bird
/// </summary>
/// <param name="messageBirdResponses">Message Bird Response object</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task SendMessagesToMessageBird(IList<MessageBirdResponseModel> messageBirdResponses)
{
if (messageBirdResponses == null)
{
throw new ArgumentNullException(nameof(messageBirdResponses));
}
foreach (var messageBirdResponse in messageBirdResponses)
{
using (var request = new HttpRequestMessage(HttpMethod.Post, $"{MessageBirdDefaultApi}/send"))
{
var content = JsonConvert.SerializeObject(messageBirdResponse);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
await _httpClient.SendAsync(request).ConfigureAwait(false);
}
}
}
ตัวประมวลผลการถ่ายทอดข้อความ
ตัวประมวลผลการถ่ายทอดข้อความได้รับกิจกรรมขาเข้าจากอะแดปเตอร์ช่องทาง และทําการตรวจสอบความถูกต้องของแบบจําลองกิจกรรม ตัวประมวลผลรีเลย์จะตรวจสอบว่าการสนทนามีการใช้งานสําหรับกิจกรรมเฉพาะหรือไม่ ก่อนส่งกิจกรรมนี้ไปยัง Direct Line
เมื่อต้องการค้นหาว่าการสนทนาทํางานอยู่หรือไม่ ตัวประมวลผลรีเลย์จะเก็บคอลเลกชันของการสนทนาที่ใช้งานอยู่ในพจนานุกรม พจนานุกรมนี้มีคีย์เป็น User ID ซึ่งระบุผู้ใช้และ Value เป็นวัตถุของคลาสต่อไปนี้:
/// <summary>
/// Direct Line Conversation to store as an Active Conversation
/// </summary>
public class DirectLineConversation
{
/// <summary>
/// .NET SDK Client to connect to Direct Line Bot
/// </summary>
public DirectLineClient DirectLineClient { get; set; }
/// <summary>
/// Direct Line response after start a new conversation
/// </summary>
public Conversation Conversation { get; set; }
/// <summary>
/// Watermark to guarantee that no messages are lost
/// </summary>
public string WaterMark { get; set; }
}
ถ้าการสนทนาไม่ทํางานสําหรับกิจกรรมที่ได้รับจากตัวประมวลผลรีเลย์ จะทําตามขั้นตอนต่อไปนี้:
- เริ่มการสนทนากับ Direct Line และจัดเก็บวัตถุการสนทนาที่ส่งโดย Direct Line เทียบกับ ID ผู้ใช้ในพจนานุกรม
/// <summary>
/// Initiate Conversation with Direct Line Bot
/// </summary>
/// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
/// <param name="adapterCallBackHandler">Call Back to send activities to Messaging API</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task InitiateConversation(Activity inboundActivity, EventHandler<IList<Activity>> adapterCallBackHandler)
{
var directLineConversation = new DirectLineConversation
{
DirectLineClient = new DirectLineClient(_relayProcessorConfiguration.Value.DirectLineSecret)
};
// Start a conversation with Direct Line Bot
directLineConversation.Conversation = await directLineConversation.DirectLineClient.Conversations.
StartConversationAsync().ConfigureAwait(false);
await directLineConversation.DirectLineClient.Conversations.
StartConversationAsync().ConfigureAwait(false);
if (directLineConversation.Conversation == null)
{
throw new Exception(
"An error occurred while starting the Conversation with direct line. Please validate the direct line secret in the configuration file.");
}
// Adding the Direct Line Conversation object to the lookup dictionary and starting a thread to poll the activities from the direct line bot.
if (ActiveConversationCache.ActiveConversations.TryAdd(inboundActivity.From.Id, directLineConversation))
{
// Starts a new thread to poll the activities from Direct Line Bot
new Thread(async () => await PollActivitiesFromBotAsync(
directLineConversation.Conversation.ConversationId, inboundActivity, adapterCallBackHandler).ConfigureAwait(false))
.Start();
}
}
- เริ่มเธรดใหม่เพื่อสํารวจกิจกรรมขาออกจากบอท Direct Line ตามช่วงเวลาการสํารวจที่กําหนดค่าไว้ในไฟล์กําหนดค่า เธรดการสํารวจจะทํางานจนกว่าจะได้รับกิจกรรมการสนทนาจาก Direct Line สิ้นสุดลง
/// <summary>
/// Polling the activities from BOT for the active conversation
/// </summary>
/// <param name="conversationId">Direct Line Conversation Id</param>
/// <param name="inboundActivity">Inbound Activity from Channel/Aggregator</param>
/// <param name="lineActivitiesReceived">Call Back to send activities to Messaging API</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task PollActivitiesFromBotAsync(string conversationId, Activity inboundActivity, EventHandler<IList<Activity>> lineActivitiesReceived)
{
if (!int.TryParse(_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds, out var pollingInterval))
{
throw new FormatException($"Invalid Configuration value of PollingIntervalInMilliseconds: {_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds}");
}
if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
out var conversationContext))
{
throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
}
while (true)
{
var watermark = conversationContext.WaterMark;
// Retrieve the activity set from the bot.
var activitySet = await conversationContext.DirectLineClient.Conversations.
GetActivitiesAsync(conversationId, watermark).ConfigureAwait(false);
// Set the watermark to the message received
watermark = activitySet?.Watermark;
// Extract the activities sent from our bot.
if (activitySet != null)
{
var activities = (from activity in activitySet.Activities
where activity.From.Id == _relayProcessorConfiguration.Value.BotHandle
select activity).ToList();
if (activities.Count > 0)
{
SendReplyActivity(activities, inboundActivity, lineActivitiesReceived);
}
// Update Watermark
ActiveConversationCache.ActiveConversations[inboundActivity.From.Id].WaterMark = watermark;
if (activities.Exists(a => a.Type.Equals("endOfConversation", StringComparison.InvariantCulture)))
{
if (ActiveConversationCache.ActiveConversations.TryRemove(inboundActivity.From.Id, out _))
{
Thread.CurrentThread.Abort();
}
}
}
await Task.Delay(TimeSpan.FromMilliseconds(pollingInterval)).ConfigureAwait(false);
}
}
หมายเหตุ
หัวใจสําคัญของโค้ดที่ได้รับข้อความคือเมธอด GetActivitiesAsync ที่ใช้ ConversationId
และเป็น watermark
พารามิเตอร์ จุดประสงค์ของ watermark
พารามิเตอร์คือการดึงข้อความที่ไม่ได้ส่งโดย Direct Line ถ้าระบุพารามิเตอร์ลายน้ํา การสนทนาจะเล่นซ้ําจากลายน้ํา เพื่อไม่ให้ข้อความสูญหาย
ส่งกิจกรรมไปยัง Direct Line
/// <summary>
/// Send the activity to the bot using Direct Line client
/// </summary>
/// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private static async Task SendActivityToBotAsync(Activity inboundActivity)
{
if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
out var conversationContext))
{
throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
}
await conversationContext.DirectLineClient.Conversations.PostActivityAsync(
conversationContext.Conversation.ConversationId, inboundActivity).ConfigureAwait(false);
}
ถ้าการสนทนาทํางานสําหรับกิจกรรมที่ได้รับจากตัวประมวลผลรีเลย์ การสนทนาจะส่งกิจกรรมไปยังตัวประมวลผลการถ่ายทอดข้อความ
สิ้นสุดการสนทนา
เมื่อต้องการสิ้นสุดการสนทนา ให้ดูที่ สิ้นสุดการสนทนาใน Direct Line
ขั้นตอนถัดไป
การรองรับช่องทางการสนทนาสดและช่องทางแบบอะซิงโครนัส
รูปแบบ Markdown ในช่องทางแบบกำหนดเองซึ่งใช้ Direct Line
ข้อมูลที่เกี่ยวข้อง
ตั้งค่าช่องทางการส่งข้อความแบบกำหนดเอง
การอ้างอิง MessageBird API
แนวทางปฏิบัติที่ดีที่สุดสำหรับการกำหนดค่าบอท