Search code examples
botframework

BotBuilder backchannel event data into session


TLDR: How can I initialise my conversations with data sent from the backchannel and use that information throughout the conversation with a user?

Using Microsoft botbuilder, a "backchannel" mechanism is provided with which I can send data to and from the bot.

Similar to this answer I am using the backchannel method to send a conversationStarted event to the bot in order to start a conversation as you might expect.

Upon receiving the event, I start a dialog which fires off my proactive messages to the user.

bot.on('event', event => {
  if (event.name === 'conversationStarted') {
    bot.beginDialog(event.address, determineWhichDialogToStart(event))
  }
})

I can then use middleware or event listeners to intercept this event and see its contents. Sure enough I can see the event

{ type: 'event',
  name: 'conversationStarted',
  text: '',
  myMetaProperty: 'foobar',
  attachments: [],
  entities: [],
  address:
   { id: '8f5d3952-df3b-4340-89b4-97360f8d4965',
     channelId: 'emulator',
     user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' },
     conversation: { id: '4e8a943d-6a45-41f2-aa11-6671cc1ca4f3' },
     bot: { id: 'bot' },
     serviceUrl: 'http://localhost:3010/' },
  source: 'emulator',
  agent: 'botbuilder',
  user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' } }

But the events I can listen to to see this event, don't have access to the session because the message hasn't yet been dispatched to one it seems.

If I use the botbuilder middleware, or the routing event, I then see that it's been turned into a message rather than an event, and has lost my meta data passed to the event. (see myMetaProperty)

{ type: 'message',
  agent: 'botbuilder',
  source: 'emulator',
  sourceEvent: {},
  address:
   { id: '8f5d3952-df3b-4340-89b4-97360f8d4965',
     channelId: 'emulator',
     user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' },
     conversation: { id: '4e8a943d-6a45-41f2-aa11-6671cc1ca4f3' },
     bot: { id: 'bot' },
     serviceUrl: 'http://localhost:3010/' },
  text: '',
  user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' } }

I tried getting access to the session in my receive event/middleware per the comment here on Github but whilst I get access to a session, it's not the session.

bot.on('receive', event => {
  bot.loadSessionWithoutDispatching(event.address, (error, session) => {
    session.conversationData.myMetaProperty = event.myMetaProperty
  })
})

This actually ends up starting a new session - having looked into the loadSession/loadSessionWithoutDispatching, they both lead to startSession and therefore me adding data to the session is lost when i try to use it in a dialog

bot.dialog('example', [
  (session, args, next) => {
    console.log(session.conversationData.myMetaProperty) // nope
    console.log(session.cantUseThisEither) // nope
    session.send('foo')
    next()
  }
])

Just to reiterate the question now that there's been a bit of background, how can I initialise my conversations with data sent from the backchannel and use that information throughout the conversation with a user?


Solution

  • I ended up starting a generic dialog which sets up my session and then moves onto the root dialog.

    bot.on('event', event => {
      if (event.name === 'conversationStarted') {
        bot.beginDialog(event.address, 'initialise-conversation', {
          myMetaProperty: event.myMetaProperty
        });
      }
    });
    
    bot.dialog('initialise-conversation', (session, args, next) => {
      session.conversationData.myMetaProperty = args.myMetaProperty
    
      session.beginDialog('/');
    });
    

    Then whenever I need myMetaProperty throughout my conversation I can get it from session.conversationData

    --

    In response to the comment below, here's some additional information which may help someone.

    The shape of the event you receive on the bot must contain an address obviously. For example here's the one I send to my bot.

    { myMetaProperty: 'foo',
      name: 'conversationStarted',
      type: 'event',
      address:
       { id: '4q5aaBw77aNGGvrpkeji8A',
         channelId: 'custom-directline',
         user: { id: 'c214imy6PCJZY4G1x2AXSD' },
         conversation: { id: 'bGvMBjEmFTg3DMxsbvJGjH' },
         bot: { id: 'bot' },
         serviceUrl: 'https://localhost:3010/' },
      source: 'custom-directline',
      agent: 'botbuilder',
      user: { id: 'c214imy6PCJZY4G1x2AXSD' } }
    

    Start by ensuring that you're sending the address. The key bits of information for routing messages back to an individual are the user and conversation objects.

    For example, if you're sending information using the backchannel of the botframework webchat you'd do it as follows.

    var user = { id: uuidv4() };
    var botConnection = new BotChat.DirectLine({...});
    
    botConnection.postActivity({
        type: 'event',
        from: user,
        name: 'conversationStarted',
        myMetaProperty: 'foo',
    }).subscribe();
    
    BotChat.App({
        user: user,
        botConnection: botConnection,
    }, document.getElementById("bot"));
    

    This will be picked up by the event listener described above.

    Another thing to note is that the emulator (and possibly other clients/channels send a conversationUpdate event which I just passed onto my conversationStarted event when I was using it. Here's the listener to do that for reference in case it's useful.

      bot.on('conversationUpdate', message => {
        message.membersAdded &&
          message.membersAdded
            .filter(identity => identity.id === message.address.bot.id)
            .forEach(identity => bot.emit('conversationStarted', message));
      });