-
Notifications
You must be signed in to change notification settings - Fork 6
/
Wit.cs
346 lines (275 loc) · 12.1 KB
/
Wit.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WitAi.Models;
namespace WitAi
{
public class Wit
{
public string WIT_API_HOST = "https://api.wit.ai";
/// <summary>
/// This parameter is a date that represents the “version” of the Wit API. Default value is 20170107
/// </summary>
public string WIT_API_VERSION = "20170107";
private string LEARN_MORE = "Learn more at https://wit.ai/docs/quickstart";
private RestClient client;
private Dictionary<string, int> sessions;
private WitActions actions = new WitActions();
/// <summary>
/// Initializes new instance of Wit class
/// </summary>
/// <param name="accessToken">Client access token. You can grab it from your Wit console, under Settings\Access Token.</param>
/// <param name="actions">(optional if you only use message()) the dictionary with your actions. Actions has action names as keys and action implementations as values.</param>
public Wit(string accessToken, WitActions actions = null)
{
client = PrepareRestClient(accessToken);
sessions = new Dictionary<string, int>();
if (actions != null)
{
this.actions = ValidateActions(actions);
}
}
private RestClient PrepareRestClient(string accessToken)
{
RestClient restClient = new RestClient(WIT_API_HOST);
restClient.AddDefaultHeader("Authorization", $"Bearer {accessToken}");
restClient.AddDefaultHeader("Content-Type", "application/json");
restClient.AddDefaultHeader("Accept", "application/json");
restClient.AddDefaultParameter("v", WIT_API_VERSION, ParameterType.QueryString);
return restClient;
}
private WitActions ValidateActions(WitActions actions)
{
if (!actions.ContainsKey("send"))
{
Console.WriteLine("The 'send' action is missing.");
}
return actions;
}
/// <summary>
/// Capture intent and entities from a text string
/// </summary>
/// <param name="msg">Text string to capture from</param>
/// <param name="verbose">Calls the API in Verbose Mode</param>
/// <returns>Message response</returns>
public MessageResponse Message(string msg, bool verbose = true)
{
var request = new RestRequest("message", Method.GET);
request.AddQueryParameter("q", msg);
IRestResponse responseObject = client.Execute(request);
MessageResponse response = JsonConvert.DeserializeObject<MessageResponse>(responseObject.Content);
return response;
}
/// <summary>
/// Returns what your bot should do next. The next step can be either answering to the user, performing an action, or waiting for further requests.
/// </summary>
/// <param name="sessionId">A specific ID of your choosing representing the session your query belongs to</param>
/// <param name="message">A message from the user</param>
/// <param name="context">Chat context</param>
/// <param name="verbose">Calls the API in verbose mode</param>
/// <returns <see cref="ConverseResponse"/>>Converse response</returns>
public ConverseResponse Converse(string sessionId, string message, WitContext context, bool verbose = true)
{
if (context == null)
{
context = new WitContext();
}
var request = new RestRequest("converse", Method.POST);
request.AddJsonBody(context);
if (message != null)
{
request.AddQueryParameter("q", message);
}
request.AddQueryParameter("session_id", sessionId);
IRestResponse responseObject = client.Execute(request);
ConverseResponse response = JsonConvert.DeserializeObject<ConverseResponse>(responseObject.Content);
return response;
}
/// <summary>
/// Runs interactive command line chat between user and bot. Runs indefinately until EOF is entered to the prompt.
/// </summary>
/// <param name="context">Chat context</param>
/// <param name="maxSteps">Max number of steps. Set to { } if omitted</param>
public void Interactive(WitContext context = null, int maxSteps = 5)
{
if (this.actions == null)
{
ThrowMustHaveActions();
}
if (maxSteps <= 0)
{
throw new WitException("max iterations reached");
}
if (context == null)
{
context = new WitContext();
}
string message;
while (true)
{
try
{
message = Console.ReadLine();
}
catch (Exception)
{
return;
}
var response = this.RunActions("session-id-01", message, context, maxSteps);
context = response.Context;
response.Messages.ForEach(msg => Console.WriteLine(msg));
}
}
/// <summary>
/// A higher-level method to the Wit converse API
/// </summary>
/// <param name="sessionId">A specific ID of your choosing representing the session your query belongs to</param>
/// <param name="message">A message from the user.</param>
/// <param name="context">Chat context</param>
/// <param name="maxSteps">Max number of steps</param>
/// <param name="verbose">Calls the API in verbose mode</param>
/// <returns <see cref="BotResponse"/>>The bot response</returns>
public BotResponse RunActions(string sessionId, string message, WitContext context,
int maxSteps = 5, bool verbose = true)
{
BotResponse botResponse = new BotResponse(context);
if (this.actions == null)
{
ThrowMustHaveActions();
}
if (context == null)
{
context = new WitContext();
botResponse.Context = context;
}
/** Figuring out whether we need to reset the last turn.
Each new call increments an index for the session.
We only care about the last call to run_actions.
All the previous ones are discarded (preemptive exit).*/
int currentRequest = 1;
if (sessions.ContainsKey(sessionId))
{
currentRequest = sessions[sessionId] + 1;
}
sessions[sessionId] = currentRequest;
botResponse = _RunActions(sessionId, currentRequest, message, botResponse, maxSteps, verbose);
// Cleaning up once the last call to RunActions finishes.
if (currentRequest == sessions[sessionId])
{
sessions.Remove(sessionId);
}
return botResponse;
}
private BotResponse _RunActions(string sessionId, int currentRequest,
string message, BotResponse response, int maxSteps = 5, bool verbose = true)
{
if (maxSteps <= 0)
{
throw new WitException("Max steps reached, stopping.");
}
ConverseResponse json = Converse(sessionId, message, response.Context, verbose);
if (json.Type == null)
{
throw new WitException("Couldn\'t find type in Wit response");
}
if (currentRequest != sessions[sessionId])
{
return response;
}
// backwards-compatibility with API version 20160516
if (json.Type == "merge")
{
json.Type = "action";
json.Action = "merge";
}
if (json.Type == "error")
{
throw new Exception("Oops, I don\'t know what to do.");
}
if (json.Type == "stop")
{
return response;
}
ConverseRequest request = new ConverseRequest();
request.SessionId = sessionId;
request.Context = response.Context;
request.Message = message;
request.Entities = json.Entities;
switch (json.Type)
{
case "msg":
ThrowIfActionMissing("send");
ConverseResponse converseResponse = new ConverseResponse();
converseResponse.Msg = json.Msg;
converseResponse.QuickReplies = json.QuickReplies;
// SDK is able to handle multiple bot responses at the same time
response.Messages.Add(converseResponse.Msg);
actions["send"](request, converseResponse);
//actions["send"](request);
break;
case "action":
string action = json.Action;
ThrowIfActionMissing(action);
response.Context = this.actions[action](request, null);
//context = this.actions[action](request);
if (response.Context == null)
{
Console.WriteLine("missing context - did you forget to return it?");
response.Context = new WitContext();
}
break;
default:
throw new WitException($"unknown type: {json.Type}");
}
if (currentRequest != sessions[sessionId])
{
return response;
}
return _RunActions(sessionId, currentRequest, null, response, maxSteps - 1, verbose);
}
//public IList<string> GetAllEntities()
//{
// var request = new RestRequest("entities", Method.GET);
// IRestResponse<List<string>> responseObject = client.Execute<List<string>>(request);
// return responseObject.Data;
//}
//public void DeleteEntity(string entityId)
//{
// var request = new RestRequest($"entities/{entityId}", Method.DELETE);
// IRestResponse responseObject = client.Execute(request);
//}
//public void DeleteValueFromEntity(string entityId, string entityValue, string expressionValue)
//{
// var request = new RestRequest($"entities/{entityId}/values/{entityValue}/expressions/{expressionValue}", Method.DELETE);
// IRestResponse responseObject = client.Execute(request);
//}
//public void DeleteExpressionFromEntity(string entityId, string entityValue)
//{
// var request = new RestRequest($"entities/{entityId}/values/{entityValue}", Method.DELETE);
// IRestResponse responseObject = client.Execute(request);
//}
//public EntityResponse CreateEntity(Entity entity)
//{
// var request = new RestRequest("entities", Method.POST);
// request.RequestFormat = DataFormat.Json;
// request.AddBody(JsonConvert.SerializeObject(entity));
// IRestResponse responseObject = client.Execute(request);
// EntityResponse response = JsonConvert.DeserializeObject<EntityResponse>(responseObject.Content);
// return response;
//}
private void ThrowIfActionMissing(string actionName)
{
if (!this.actions.ContainsKey(actionName))
{
throw new WitException($"unknown action {actionName}");
}
}
private void ThrowMustHaveActions()
{
throw new WitException($"You must provide the 'actions' parameter to be able to use runActions. ${LEARN_MORE}");
}
}
}