Virtual Agents

Get to know the awesome power of Chime's Virtual Agents.

Have a question about virtual agents? Ask one of our developers at InstantDev@instant-tech.com.

Meet the Chime Virtual Agent

What are Virtual Agents?

Whether you're looking to integrate with a ticketing system, do a lookup for guests against another system, or you're imagining something completely different, the Chime Virtual Agent is designed to be a flexible integration platform to meet a variety of requirements. A Virtual Agent is a C# file or assembly that implements a specific interface from Chime's ExtensionLibrary.dll. This can be thought of as a plugin for Chime. The purpose of a Virtual Agent is to perform automated tasks for a Chime session. The Chime API provides three events that Virtual Agents can hook into to call custom code. The three types are:

  1. Pre-Conversation - This is the first event hook that can call a Virtual Agent. One classic example is a Virtual Agent that performs a pre-conversation lookup for the guest and automatically creates an incident record in an external CRM system.
  2. Conversational - This is the second event hook. It is meant for Virtual Agents that exchange messages with the guest. For example, a conversational Virtual Agent might perform an interview with the guest to determine their location and ask them to look at Knowledge Base articles related to their problem area. If a conversational Virtual Agent doesn't deflect the guest they will route to a live agent after this event.
  3. Post-Conversation - This is the final event hook for a Virtual Agent. It is fired after the Chime session is ended by either the guest or agent closing their IM window. A simple post-conversation Virtual Agent could update an incident record in an external CRM system with the chat messages and session meta-data from Chime.

Flexible Defined Services for Virtual Agents

Virtual Agents use the Chime API for flexible access to session data, queue resources, and other abilities. Here are a few highlights of what they can do:

  • Add custom links to the expert's Context Window Extension (CWE)
  • Send and receive messages from the guest
  • Read guest data like name and email address
  • Add Skill Tags to a session for routing to an expert
  • Integrate with any RESTful web service
  • Much more...

Deploying Virtual Agents

Virtual Agent SDK (zip)

The Virtual Agent SDK is a software development kit that includes blank Virtual Agent templates, out-of-the-box samples, documentation, and a Sandbox environment for testing. Currently, the SDK includes sample integrations with Salesforce®, Microsoft Dynamics®, ServiceNow®, SolarWinds®, and Alchemy®. You can find more information about these out-of-the-box integrations on the Samples pages. The following steps outline how to deploy a Virtual Agent in Chime. More detailed information can be found in the Virtual Agent SDK documentation.

  1. Build Virtual Agent Files - Open the sample (Virtual Agent SDK\Samples) in Visual Studio and (if applicable) change the credentials XML file to have your organizations credentials. Once the credentials are saved, build the project and the .dll files (and any credentials file or dependent references) can be moved to the Chime server.
  2. Move Virtual Agent Files to Chime Server - Chime looks for .cs or .dll Virtual Agents in the Plugins folder. This can be found at “C:\Program Files\Instant Technologies\Chime For Lync\Plugins”. Simply drag your Virtual Agents into this folder (along with any credentials files or dependent references) to make them available for loading.
  3. Turn On Global Setting in Chime - Virtual Agents can be turned on for the entire Chime application from the Admin section under Virtual Agents, by turning ON the “Virtual Agent Manager”. By default, Virtual Agents are disabled globally.
  4. Enable Individual Virtual Agent - There is an individual enable/disable setting for each Virtual Agent. Only enabled Virtual Agents can be assigned to a queue.
  5. Assign Virtual Agent to a Queue - Once a Virtual Agent is loaded in Chime and “Enabled”, it can be assigned to a queue. Go to queue settings, under the Virtual Agents tab to assign the Virtual Agent to a queue. Once the settings are saved, the Virtual Agent deployment is complete.

Frequently Asked Questions

This error is most commonly caused by the presence of the ExtensionLibrary.dll with your Virtual Agent in the Plugins folder. Check that C:\Program Files\Instant Technologies\Chime For Lync\Plugins does not contain the ExtensionLibrary.dll. If it does, remove this reference and reload the Virtual Agents.
By default, each Virtual Agent is given 3000 milliseconds (3 seconds) to perform its work. In other words, anytime Chime calls some method on the Virtual Agent, it's given 3 seconds to return or else it's disconnected. Since we don't want misbehaving Virtual Agents delaying a seeker from being connected with an expert, we disconnect Virtual Agents if they exceed their timeout limit. If you think your Virtual Agent needs more time to integrate with another service follow these steps to increase the timeout value:
  1. Use SQL Server Management Studio to view the Chime database
  2. Open Tables and right-click dbo.VirtualAgents > edit top 200 rows
  3. For the Virtual Agent you would like to increase the timeout value for change the Timeout value from 3000 to 5000 (3 to 5 seconds). This is the next recommended timeout value and works for all Virtual Agent SDK samples.
  4. These changes will take into effect immediately within the Chime system. You won’t need to restart.
Logging within a Virtual Agent can be tricky. Two different logging strategies are recommended. The first is logging from a Virtual Agent before it has been loaded into Chime. If your Virtual Agent integrates with an external system, this might be logging when you are authenticating with that system. These logs should be written to a local text file and should only pertain to the steps that are taken before the Virtual Agent has been loaded in Chime. After a Virtual Agent is successfully loaded in Chime, the IVirtualAgent.SetPluginManager method is called and a reference to the IPluginManager object will be passed to the Virtual Agent. After this point the second logging strategy should be used. The second strategy is to call the IPluginManager method LogMessageInChime. The advantage of this logging method is that the logs from the Virtual Agent will be seamlessly integrated with the Chime logs, which makes trouble-shooting easier. You can specify the level of logging as well (Error, Warn, Info, Debug) to make the messages more meaningful.

Videos

Screenshots


Getting Started with the SMTP Virtual Agent

Note: The following steps can be downloaded here » SMTP_Integration.docx

Overview

Sending an email is a breeze with Virtual Agents. The API in Chime provides an email service for Virtual Agents to use. This sample calls into the email service to send an email to a predefined recipient (e.g., manager) with the Chime session data in the body of the message.

Before we get started...Check your references!

This post-conversation Virtual agent sample is written as a C# script file. It uses the following references:


using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Mail;
using System.Text;

                            

1. Create a class that implements the IVirtualAgent interface

Create a .cs class that implements the IVirtualAgent interface. Then, in the Load method, return a VirtualAgentProps object that specifies this Virtual Agent type as Post-Conversation.


public bool Load()
{
    va_state = VirtualAgentState.Online;
    var vaData = new VirtualAgentProps("E-mail dispatcher", "1", VirtualAgentType.PostConversation,
            "sends an e-mail to the target e-mail address with all the session data formatted in the body",
            "Instant Technologies"); //name, version, VirtualAgentType, description, author
    return  new Tuple<bool, VirtualAgentProps>(true, vaData);
}

                        

2. Connect with a session and get the session data from Chime

In the SeekerConnected method, the Virtual Agent asks the PluginManager for the session’s PostChatData, that is all the data that was known when the seeker entered the queue, and the other data the was added over the course of being connected with a live agent, such as chat messages, skill tags, etc. In this example, the “clean” PostChatData is used which is stripped of HTML markup and doesn't contain empty seeker data values. Then we send that data to be dispatched in an e-mail, and finally we tell the PluginManager to disconnect us. For post-conversation Virtual Agents, the keepAlive value is ignored by the PluginManager because the session is already completed. i.e., “terminated”.


public bool SeekerConnected(int sessionId)
{
    PostChatData chatData = _pluginManager.PostChatEventClean(sessionId);
    DispatchEmail(chatData);
    bool keepAlive = true;
    _pluginManager.DisconnectVirtualAgent(sessionId, keepAlive);
    return true;
}

                        

3. Send the session data as an e-mail

Finally the session data is formatted into an e-mail message and sent to a mailbox, using the PluginManager's email service. (Note, the WriteToString method just builds a string out of the different object fields of PostChatData).


private void DispatchEmail(PostChatData chatData)
{
    if (_pluginManager.Email.Enabled) {
        var sent = _pluginManager.Email.SendEmail(chatData.question.Replace("\n", "")
                                                .Replace("\r", ""), WriteToString(chatData),
                                                new List<string>() { _targetEmailAddress });
            if (sent) {
                _pluginManager.LogMessageInChime(LoggingLevel.Debug,
                            "Success dispatching e-mail to target mailbox " + _targetEmailAddress);
            } else {
                _pluginManager.LogMessageInChime(LoggingLevel.Error,
                            "Failure dispatching email to target mailbox " + _targetEmailAddress);
            }
        } else {
                _pluginManager.LogMessageInChime(LoggingLevel.Error,
                            "Error dispatching e-mail. PluginManager e-mail service is not enabled.");
        }
    }
}

                        

Questions?

Have a question about Chime integration with SMTP? Ask one of our developers at InstantDev@instant-tech.com.


Getting Started with the Salesforce® REST API

Note: The following steps can be downloaded here » SalesforceIntegrationDoc.docx

Overview

The Salesforce Virtual Agent SDK sample includes two Virtual Agents that work together to query and update records in Salesforce. The Pre-Conversation Virtual Agent looks up the guest in the Contact table by their email address and adds their Salesforce information to the Chime session metadata. The expert in Chime can then view this metadata in their Context Window Extension or agent dashboard. The Post-Conversation Virtual Agent updates the description field of the Contact record to note what time the guest sought help from the queue. The Post-Conversation Virtual Agent also posts a Chatter Feed Item with the Chime session metadata, like chat messages.

Before we get started...Check your references!

The following examples use these references. These examples use Newtonsoft Json library to deserialize the responses from Salesforce®.


using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

                            

1. Connect to Salesforce® (create an authentication header for subsequent HTTP requests)

This code snippet shows you how to use your Salesforce® credentials to retrieve an access token and construct an authentication header for making subsequent HTTP requests to the Salesforce® REST API.


public class SFResponse
{
    public string access_token = "";
    public string instance_url = "";
    public string token_type = "";
}
public static async void ConnectToSalesForce()
    {
        StringBuilder body = new StringBuilder();
        body.Append("?grant_type=password&");
        body.Append("client_id=" + client_id + "&");
        body.Append("client_secret=" + client_secret + "&");
        body.Append("username=" + username + "&");
        body.Append("password=" + password + security_token);

        using (var client = new HttpClient())
        {
            AuthURL = "https://login.salesforce.com/services/oauth2/token";
            var values = new Dictionary<string, string>();
            var content = new FormUrlEncodedContent(values);
            var response = client.PostAsync(AuthURL + body, content).Result;
            if (response.IsSuccessStatusCode)
            {
                var jsonString = await response.Content.ReadAsStringAsync();
                SFResponse dataObjects = JsonConvert.DeserializeObject<SFResponse>(jsonString);
                authHeaders = new AuthenticationHeaderValue(dataObjects.token_type, dataObjects.access_token);
                isConnectedToSF = true;
                return;
            }
            Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
            isConnectedToSF = false;
        }
    }

                        

2. Query a Salesforce table (look for a record matching the guest's e-mail address)

This example shows you how to query the Salesforce Contact table for records that match the provided e-mail address, and receive the Department and Title fields from those record. We’re using the authentication header created in the first example.


public class QueryResponse
{
    public List<Contact> records;
}
public class Contact
{
    public string Department = "";
    public string Title = "";
}
public static async void QuerySalesforce(string email)
    {
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = authHeaders;
            var query = "SELECT Department , Title FROM Contact WHERE Email = \'" + email + "\'";
            var requestURL = instance_url + "/services/data/v20.0/query?q=" + query.Replace(" ", "+");
            var response = client.GetAsync(requestURL).Result;
            if (response.IsSuccessStatusCode)
            {
                var jsonString = await response.Content.ReadAsStringAsync();
                QueryResponse data = JsonConvert.DeserializeObject<QueryResponse>(jsonString);
                Contact seeker = data.records.FirstOrDefault();
                if (seeker != null)
                {
                    recordID = seeker.Id;
                }
            }
        }
    }

                        

3. Update a Salesforce record

This example shows how you can update a record field in Salesforce, given that record’s ID. This sample will update the description field of a Contact record.


public static void UpdateSalesforce(string recordId, string description)
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization = authHeaders;
        var values = new Dictionary<string, string>();
        values.Add("Description", description);
        var stringPayload = JsonConvert.SerializeObject(values);
        var content = new StringContent(stringPayload, Encoding.UTF8, "application/json");
        var requestUrl = instance_url + "/services/data/v20.0/sobjects/Contact/" + recordId;
        var response = client.PatchAsync(requestUrl, content).Result;
        if (response.IsSuccessStatusCode)
        {
            Console.WriteLine("Success patching Contact object in salesforce");
            didUpdateSF = true;
        }
        else
        {
            Console.WriteLine(response.StatusCode + " " + response.ReasonPhrase);
        }
    }
}

                        

4. Post a Chatter feed item to a Contact record

This example will show you how, given a record ID, you can post a Chatter feed item directly to that Contact record’s page. The feed item will show up as being posted by the entity you have authenticated yourself as in Step 1.


public class FeedItem
{
    public Body body = new Body();
    public string feedElementType = "FeedItem";
    public string subjectId = Program.recordID;

    public FeedItem(string descr)
    {
        body.messageSegments.Add(new MessageSegment() { text = descr });
    }
    public class Body
    {
        public List<MessageSegment> messageSegments = new List<MessageSegment>();
    }
    public class MessageSegment
    {
        public string type = "Text";
        public string text;
    }
}

    public static void PostChatterFeed(string recordID, string description)
    {
        string chatterUrl = instance_url + "/services/data/v35.0/chatter/feed-elements?feedElementType=FeedItem&subjectId="+recordID+"&text=New+post";

        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = authHeaders;
            FeedItem values = new FeedItem(description);
            var stringPayload = JsonConvert.SerializeObject(values);
            var content = new StringContent(stringPayload, Encoding.UTF8, "application/json");
            var response = client.PostAsync(chatterUrl, content).Result;
            if (response.IsSuccessStatusCode)
            {
                Console.WriteLine("Success posting Chatter Feed object to salesforce");
                didUpdateSF = true;
            }
            else
            {
                Console.WriteLine(response.StatusCode + " " + response.ReasonPhrase);
            }
        }
    }

                        

Questions?

Have a question about Chime integration with Salesforce®? Ask one of our developers at InstantDev@instant-tech.com.


Getting Started with the Microsoft Dynamics® CRM API

Note: The following steps can be downloaded here » MSDynamicsCRMIntegrationDoc.docx

Overview

The Dynamics CRM Virtual Agent SDK sample includes two Virtual Agents that work together to create and update Case records. The Pre-Conversation Virtual Agent creates a Case record in Dynamics and fills out the following fields:

  • Title - the guest’s question
  • Case Origin - Chime-chat
  • Description - time the guest entered the Chime queue
The Post-Conversation Virtual Agent updates the Case record with the following information:
  • Description - the Chime session metadata, including chat messages

Dynamics® CRM customizations for Virtual Agent sample

This sample creates a new Case in Dynamics CRM and fills out some fields with information from Chime. For example, it sets the Case Origin field to "Chime-chat", which is a customization of the Dynamics CRM application. The pre-conversation Virtual Agent sets the "caseorigincode" field of the Case record to "100,000,004". The Virtual Agent sample uses this small customization to the Dynamics service. To make this customization in Dynamics go to Settings, Customize the System, then choose Entities from the left-side navigation bar. Double-click the Case entity to open and edit, then choose Fields from the left-side navigation bar under Case. Double-click "caseorigincode" to edit this field, then choose Edit next to Option Set. Add a new option with name "Chime-chat" and value "100,000,004" and finally save the changes.

Before we get started...Check your references!

The following examples use these references. These examples also use a reference to the Microsoft.Crm.Sdk.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Description;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;

                            

1. Connect to the Dynamics server

The following code shows you how to use your authentication credentials to create a reference to a service proxy object. Once this reference is made, you can make calls directly on the service proxy object to access tables and resources on your Dynamics server. The service proxy must be disposed after use.


var _serverProxy = new OrganizationServiceProxy(new Uri(service_url), null, new ClientCredentials() { UserName = { UserName = username, Password = password } }, null);
    public void ConnectToDynamics()
    {
        using (_serverProxy)
        {
            _serverProxy.Authenticate();
            isConnectedToDynamics = true;
            Console.WriteLine("authenticated with Dynamics");
        }
    }

                        

2. Find or create a Contact record

This example shows you how to retrieve an EntityReference, this is an object that directly references an entity record in Dynamics, from the Contact table. We are going to try to find a Contact record with a given email address, and if we cannot find one, we’re going to create one. Contact records require a first and last name to be created in the system. Notice how we use the QueryByAttribute object to construct the query we will send to the server.


private EntityReference FindContactWithEmail(string email, string firstname, string lastname)
{
    //is there an existing contact with this email address?
    QueryByAttribute querybyexpression = new QueryByAttribute("contact");
    querybyexpression.Attributes.AddRange("emailaddress1");
    querybyexpression.Values.AddRange(email);
    EntityCollection retrieved = _serverProxy.RetrieveMultiple(querybyexpression);
    var contact = retrieved.Entities.FirstOrDefault();
    if (contact != null)
    {
        return contact.ToEntityReference();
    }

    //create new contact with this email address?
    Entity newContact = new Entity("contact");
    newContact.Attributes["firstname"] = firstname;
    newContact.Attributes["lastname"] = lastname;
    newContact.Attributes["emailaddress1"] = email;
    Guid newContactGuid = _serverProxy.Create(newContact);

    //fetch the entity of the newly created contact
    Entity foundnewContact = _serverProxy.Retrieve("contact", newContactGuid, new ColumnSet(true));
    if (foundnewContact != null)
    {
        return foundnewContact.ToEntityReference();
    }
    throw new Exception("unable to create new contact in Dynamics for " + firstname + " " + lastname + " " + email);
}

                        

3. Create a case in Dynamics

This example shows you how to create an incident record, or case, in Dynamics. First you create an Entity object and specify what type of Entity you are creating in the constructor, in this case it’s an incident Entity. We’re going to populate the title, description, and customerid fields of this new record before sending it to the server (notice how we use the FindContactWithEmail method from the previous example). We can construct a URL to this new record given the record GUID which is returned from the server on creation.


public void CreateCase(string firstname, string lastname, string email, string question)
{
    using (_serverProxy)
    {
        //create the new case/incident/ticket
        Entity entity = new Entity("incident");
        entity.Attributes["title"] = question;
        entity.Attributes["caseorigincode"] = new OptionSetValue() { Value = 100000004 }; //100,000,004
        entity.Attributes["description"] = "Inbounded chat by Chime on " + DateTime.Now;
        entity.Attributes["customerid"] = FindContactWithEmail(email, firstname, lastname);

        Guid newRecordGuid = _serverProxy.Create(entity);
        newRecordURL = instance_url + @"main.aspx?etn=incident&id=" + newRecordGuid + @"&pagetype=entityrecord";
    }
}

                        

4. Update a case in Dynamics

This example shows how you can update a record in Dynamics, given that record’s system GUID. This sample will add new text to any existing text of the description field of the case. When it retrieves the record it retrieves all columns. Note, the description field has a 2,000 character limit.


public void UpdateCase(string comment, Guid recordGuid)
{
    using (_serverProxy)
    {
        Entity entity = _serverProxy.Retrieve("incident", recordGuid, new ColumnSet(true));
        var olddescription = entity.Attributes["description"];
        var newdescription = olddescription + "\n" + comment;
        if (newdescription.Length < 2001)
        {
            entity.Attributes["description"] = newdescription;
            _serverProxy.Update(entity);
        }
    }
}

                        

Questions?

Have a question about Chime integration with Microsoft Dynamics® CRM? Ask one of our developers at InstantDev@instant-tech.com.


Getting Started with the ServiceNow® REST API

Note: The following steps can be downloaded here » ServiceNowIntegrationDoc.docx

Overview

The ServiceNow sample from the Virtual Agent SDK has two Virtual Agents that work together to create an Incident record when a session enters the Chime queue, and then is updated when the session finishes. The Pre-Conversation Virtual Agent creates the Incident record, and fills out the following fields:

  • Caller - the guest’s User record (looks up the guest by their email address)
  • Source - Chat
  • Short description - the guest’s question
  • Work Note - time that the guest made the request to Chime
The Post-Conversation Virtual Agent posts the following data to the new Incident when the session ends:
  • Assigned to - the expert’s User record (looks up the expert by their email address)
  • Comment - adds the Chime meta data including chat messages

ServiceNow® customizations for Virtual Agent sample

This integration sample depends on a small customization to the ServiceNow service. In this sample the pre-conversation Virtual Agent uses the option "chat" for the Contact Type field of the Incident table. To make this customization within ServiceNow, from the Admin home page, go to Data Management, Personalize Form, then Choose Incident [incident] as the table and select Next, then in the upper navigation bar choose to go back to Configuring Incident Form. Click the cog settings button in the upper-right hand corner, then right-click the Contact type field and select "Configure Dictionary". Here you can add a new choice. Insert a new row under Choices, with label "Chat", value "chat", language "en", and inactive set to false. Choose Update to save these changes.

Before we get started...Check your references!

The following examples use these references. These examples use Newtonsoft Json library to serialize the objects we send to the ServiceNow API in json.


using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Xml.Linq;

                            

1. Authenticate with ServiceNow (create a basic authentication header for subsequent HTTP requests)

This code snippet shows you how to use your ServiceNow credentials to construct an authentication header for making basically authenticated HTTP requests to the ServiceNow REST API. The ServiceNow REST API supports basic authentication and OAuth. You can then use a dummy query to ServiceNow to verify that your authentication header is valid. The dummy query in this case asks for the first ten records from the Incident table, this is a default ServiceNow table.


authHeaders = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password)));
    public void ConnectToServiceNow()
    {
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = authHeaders;

            string connectURL = instance_url + @"incident?sysparm_limit=10";
            var response = client.GetAsync(connectURL).Result;
            if (response.IsSuccessStatusCode)
            {
                isConnectedToServiceNow = true;
                Console.WriteLine("Success connecting");
            }
            else
            {
                isConnectedToServiceNow = false;
            }
        }
    }

                        

2. Create a ticket in ServiceNow

This example shows you how to create an incident record (ticket) in ServiceNow via their REST API, populate some fields, and then construct a URL to the newly created ticket. We use the Newtonsoft Json library to serialize a local Incident object for making the Post request. For Post requests, ServiceNow requires the accept header and the content-type header to be “application/json”.


public class Incident
{
    public string short_description;
    public string work_notes;
}
    public void CreateTicket(string email, string question)
    {
        string createIncidentURL = instance_url + @"incident";
        Incident newIncident = new Incident() { short_description = question, work_notes = "Incident reported by " + email};
        var jsonString = JsonConvert.SerializeObject(newIncident);
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = authHeaders;
            client.DefaultRequestHeaders.Add("Accept", "application/json");
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, createIncidentURL);
            request.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
            var response = client.SendAsync(request).Result;
            if (response.IsSuccessStatusCode)
            {
                var newIncidentURL = response.Headers.Location.OriginalString;
            }
        }
    }

                        

3. Update a ServiceNow ticket

This example shows how you can update a record in ServiceNow, given that record’s sys_id. This sample will update the comment field of an Incident record, given that record’s sys_id.


public class UpdateIncident
{
    public string comments;
}

    public void UpdateTicket(string comment, string sys_id)
    {
        UpdateIncident updateIncident = new UpdateIncident(){ comments = comment};
        var jsonString = JsonConvert.SerializeObject(updateIncident);
        string updateIncidentURL = instance_url + @"incident/" + sys_id + "?sysparm_exclude_ref_link=true";
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = authHeaders;
            client.DefaultRequestHeaders.Add("Accept", "application/json");
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, updateIncidentURL);
            request.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
            var response = client.SendAsync(request).Result;
            if (response.IsSuccessStatusCode)
            {
                Console.WriteLine("Success updating ticket");
            }
            else
            {
                Console.WriteLine("Unsuccessfully updated ticket");
            }
        }
    }

                        

Questions?

Have a question about Chime integration with ServiceNow®? Ask one of our developers at InstantDev@instant-tech.com.


Getting Started with the Alchemy® API

Note: The following steps can be downloaded here » AlchemyIntegrationDoc.docx

Overview

The Alchemy sample from the Virtual Agent SDK includes two Virtual Agent classes. The Pre-Conversation Virtual Agent gets the top 5 keywords from the guest’s question and adds this information to the session’s metadata in Chime. The Post-Conversation Virtual Agent does a sentiment analysis on the chat messages from the Chime session. The sentiment type (positive or negative) and score (how strongly the sentiment was) are added to the Chime session metadata.

Before we get started...Check your references!

The following examples use these references. These examples also use a reference to the Alchemy sdk.dll.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;

                            

1. Connect to the Alchemy service

The following code shows you how to authenticate with the Alchemy AI service. The only credential this requires is your Alchemy API key which can be requested from http://www.alchemyapi.com/api/register.html.


AlchemyAPI alchemyAPI = new AlchemyAPI.AlchemyAPI();
    public void ConnectToAlchemy()
    {
        try
        {
            alchemyAPI.SetAPIKey(apiKey);
            isConnectedToAlchemy = true;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

                        

2. Get ranked keywords from some text

One of the things Alchemy API is very good at is analyzing a body of text. This first example will show you how to leverage this capability to extract topic keywords from some text, ranked by their relevance as perceived by the Alchemy service. Note, Alchemy responses are in XML format. For brevity, the RankedKeywords class that is used to bind to the deserialized response is not included, but you can construct your own class from the resources provided by Alchemy on this page (scroll to the bottom Response Format (XML) section) http://www.alchemyapi.com/api/keyword/textc.html. I’ve also excluded the TopFiveKeywords method, this simply string-ifies the text field of the first five keywords.


public RankedKeywords.Response GetRankedKeywords(String text)
    {
        RankedKeywords.Response status = new RankedKeywords.Response();

        try
        {
            if (alchemyAPI != null)
            {
                RankedKeywords.results response = new RankedKeywords.results();
                XmlSerializer serializer = new XmlSerializer(response.GetType());
                var xml = alchemyAPI.TextGetRankedKeywords(text);
                Console.WriteLine(xml);
                object deserialized = serializer.Deserialize(ToStream(alchemyAPI.TextGetRankedKeywords(conversationText)));
                response = (RankedKeywords.results)deserialized;
                status.Result = response;
                status.Status = "SUCCESS";
                Console.WriteLine("Ranked Keyword Count: " + status.Result.keywords.Count());
                Console.WriteLine("Top 5 Keywords: " + TopFiveKeyWords(status.Result.keywords));
            }
        }
        catch (Exception ex)
        {
            status.Result = null;
            status.Status = "ERROR: " + ex.ToString();
            Console.WriteLine(ex.ToString());
        }
        return status;
    }

                        

Get sentiment analysis for some text

This second example will show you how to use Alchemy API to retrieve a sentiment analysis of a body of text. Alchemy uses the following data points to report sentiment analysis:

  • Type - sentiment polarity: "positive", "negative", or "neutral"
  • Score - sentiment strength (0.0 is neutral)
  • Mixed - whether sentiment is mixed (both positive and negative) (1 is mixed)

Once again, for brevity we have excluded the class source code that binds to the Alchemy XML response. This documentation shows you how the response will be formed http://www.alchemyapi.com/api/sentiment/textc.html.


public SentimentAnalysis.Response GetSentiment(string conversationText)
{
    SentimentAnalysis.Response status = new SentimentAnalysis.Response();
    try
    {
        if (alchemyAPI != null)
        {
            SentimentAnalysis.results response = new SentimentAnalysis.results();
            XmlSerializer serializer = new XmlSerializer(response.GetType());
            object deserialized = serializer.Deserialize(ToStream(alchemyAPI.TextGetTextSentiment(conversationText)));
            response = (SentimentAnalysis.results)deserialized;
            status.Result = response;
            status.Status = "SUCCESS";

            if (status.Result != null && status.Result.docSentiment != null)
            {
                if (status.Result.docSentiment.type != null)
                {
                    Console.WriteLine("Sentiment type: " + status.Result.docSentiment.type);
                }
                Console.WriteLine("Sentiment score: " + status.Result.docSentiment.score);
                var mixed = status.Result.docSentiment.mixed;
                Console.WriteLine("Mixed sentiment: " + (mixed == 1 ? "True" : "False"));
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    return status;
}

                        

Questions?

Have a question about Chime integration with Alchemy®? Ask one of our developers at InstantDev@instant-tech.com.


Getting Started with the SolarWinds® API

Note: The following steps can be downloaded here » SolarWinds_Integration.docx

Overview

The SolarWinds Virtual Agent SDK sample includes two Virtual Agents that automatically create and update ticket records. The Pre-Conversation Virtual Agent looks for an existing ticket (from field in the Web Client – see below) or creates a ticket in SolarWinds and fills out the following fields:

  • Client - guest record (looked up by their email address)
  • Room Number - field from Web Client
  • Extension - field from Web Client
  • Asset Number - field from Web Client
  • Request Type - field from Web Client
  • Detail - the guest's question
  • Location - the guest’s location from their Client record
The Post-Conversation Virtual Agent updates the pre-existing ticket record by adding a note with the conversation history that happened between the guest and the Chime agent.

Chime Web Client Customizations for Virtual Agent Sample

This Virtual Agent sample uses additional information from Web Client. The Web Client should be changed to have the following fields:

  • Room
  • Extension
  • Asset Number
  • Request Type
  • Open Ticket ID

Before we get started...Check your references!

The Virtual Agent sample uses the following references:


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

                            

Pre-Conversation

In the IncomingSeekerOffer method, the Pre-Conversation Virtual Agent caches the guest’s question, e-mail address, and session ID.


public bool IncomingSeekerOffer(int sessionId, string queueName, int queueID, string email, string question, PreviousSessionState? prevState)
{
    var seekerInfo = new SeekerInfo(email, question);
    if (!_openSessions.ContainsKey(sessionId))
    {
        _openSessions.Add(sessionId, seekerInfo);
    }
    else
    {
        _openSessions[sessionId] = seekerInfo;
    }
    return true;
}

                        
In the SeekerConnected method, the Virtual Agent extracts the custom Web Client fields Room Number, Asset, Extension, and Request Type, then calls to the SolarWinds_API class to do the pre-conversation work of creating the ticket and assigning these fields. Finally, the Virtual Agent sends the URL to the newly created ticket as a Custom Tab object for the agent’s Context Window Extension.

const string RoomNumber = "RoomNumber";
const string Extension = "Extension";
const string AssetNumber = "AssetNumber";
const string RequestType = "RequestType";

public bool SeekerConnected(int sessionId)
{
    _va_state = VirtualAgentState.Busy;
    if (_openSessions.ContainsKey(sessionId))
    {
        var seekerInfo = _openSessions[sessionId];
        var data = _pluginManager.GetSeekerDictionary(sessionId);
        var roomNumber = "";
        var extension = "";
        var assetNumber = "";
        var requestType = -1;

        if (data.ContainsKey(RoomNumber))
        {
            roomNumber = data[RoomNumber];
        }
        if (data.ContainsKey(Extension))
        {
            extension = data[Extension];
        }
        if (data.ContainsKey(AssetNumber))
        {
            assetNumber = data[AssetNumber];
        }
        if (data.ContainsKey(RequestType))
        {
            try
            {
                requestType = int.Parse(data[RequestType]);
            }
            catch (Exception ex)
            {
                _pluginManager.LogMessageInChime(LoggingLevel.Error, "Exception parsing RequestType from seeker data. This field is required to create a ticket in SolarWinds.");
            }
        }

        var ticket_url = _api.PreConversation(sessionId, requestType, seekerInfo, roomNumber, extension, assetNumber);
        var custom_tabs = new List<CustomTab>() { new CustomTab(ticket_url, "SolarWinds Ticket", false) };
        _pluginManager.SendCustomTabToAgent(sessionId, custom_tabs);
    }
    else
    {
        _pluginManager.LogMessageInChime(LoggingLevel.Error, "No seeker info found for session ID");
    }
    _pluginManager.DisconnectVirtualAgent(sessionId, true);
    _va_state = VirtualAgentState.Online;
    return true;
}

    

Post-Conversation

In the SeekerConnected method, the Post-Conversation Virtual Agent retrieves the chat messages between the guest and agent and passes those to the PostConversation method of the SolarWinds_API class.

public bool SeekerConnected(int sessionId)
{
    _api.PostConversation(sessionId, _pluginManager.GetChatMessagesClean(sessionId));
    _pluginManager.DisconnectVirtualAgent(sessionId, false);
    return true;
}

    
In the PostConversation method, the SolarWinds_API class retrieves the cached ticked ID for the appropriate Chime session and formats the chat messages. Then the Note is created and associated with the existing Ticket in SolarWinds.

public void PostConversation(int sessionId, List<string> chatMessages)
{
    var chatHistory = "";
    if (chatMessages.Any())
    {
        chatHistory = chatMessages.Aggregate((next, workingSentence) => next + Environment.NewLine + workingSentence);
    }
    if (_sessionIdToTicket.ContainsKey(sessionId))
    {
        var ticket = _sessionIdToTicket[sessionId];
        var note = new Note(chatHistory, ticket.id);
        CreateNote(note);
        RemoveSeeker(sessionId);
    }
    else
    {
        _pluginManager.LogMessageInChime(LoggingLevel.Error, "Chime session ID was not found in virtual agent cache of session Tickets in SolarWinds");
    }
}
    


    

Questions?

Have a question about Chime virtual agents? Ask one of our developers at InstantDev@instant-tech.com.