@memberjunction/server 0.9.162 → 0.9.164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.log.json +6 -0
- package/dist/generated/generated.js +560 -1
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +0 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +86 -16
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/package.json +6 -6
- package/src/generated/generated.ts +433 -2
- package/src/generic/PushStatusResolver.ts +0 -1
- package/src/resolvers/AskSkipResolver.ts +121 -38
|
@@ -58,6 +58,7 @@ export class AskSkipResultType {
|
|
|
58
58
|
@Resolver(AskSkipResultType)
|
|
59
59
|
export class AskSkipResolver {
|
|
60
60
|
private static _defaultNewChatName = 'New Chat';
|
|
61
|
+
private static _maxHistoricalMessages = 8;
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
@Query(() => AskSkipResultType)
|
|
@@ -79,9 +80,14 @@ export class AskSkipResolver {
|
|
|
79
80
|
if (user) {
|
|
80
81
|
convoEntity.UserID = user.ID;
|
|
81
82
|
convoEntity.Name = AskSkipResolver._defaultNewChatName;
|
|
82
|
-
if (await convoEntity.Save())
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
if (await convoEntity.Save())
|
|
84
|
+
ConversationId = convoEntity.ID;
|
|
85
|
+
else
|
|
86
|
+
throw new Error(`Creating a new conversation failed`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
90
|
+
}
|
|
85
91
|
} else {
|
|
86
92
|
await convoEntity.Load(ConversationId); // load the existing conversation, will need it later
|
|
87
93
|
}
|
|
@@ -106,12 +112,8 @@ export class AskSkipResolver {
|
|
|
106
112
|
} as SkipDataContextItem
|
|
107
113
|
);
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
content: UserQuestion,
|
|
112
|
-
role: 'user'
|
|
113
|
-
}
|
|
114
|
-
];
|
|
115
|
+
// now load up the messages. We will load up ALL of the messages for this conversation, and then pass them to the Skip API
|
|
116
|
+
const messages: SkipMessage[] = await this.LoadConversationDetailsIntoSkipMessages(dataSource, convoEntity.ID, AskSkipResolver._maxHistoricalMessages);
|
|
115
117
|
|
|
116
118
|
const input: SkipAPIRequest = {
|
|
117
119
|
messages: messages,
|
|
@@ -133,10 +135,65 @@ export class AskSkipResolver {
|
|
|
133
135
|
return this.HandleSkipRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity);
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
protected async LoadConversationDetailsIntoSkipMessages(dataSource: DataSource, ConversationId: number, maxHistoricalMessages?: number): Promise<SkipMessage[]> {
|
|
139
|
+
try {
|
|
140
|
+
// load up all the conversation details from the database server
|
|
141
|
+
const md = new Metadata();
|
|
142
|
+
const e = md.Entities.find((e) => e.Name === 'Conversation Details');
|
|
143
|
+
const sql = `SELECT
|
|
144
|
+
${maxHistoricalMessages ? 'TOP ' + maxHistoricalMessages : ''} ID, Message, Role, CreatedAt
|
|
145
|
+
FROM
|
|
146
|
+
${e.SchemaName}.${e.BaseView}
|
|
147
|
+
WHERE
|
|
148
|
+
ConversationID = ${ConversationId}
|
|
149
|
+
ORDER
|
|
150
|
+
BY CreatedAt DESC`;
|
|
151
|
+
const result = await dataSource.query(sql);
|
|
152
|
+
if (!result)
|
|
153
|
+
throw new Error(`Error running SQL: ${sql}`);
|
|
154
|
+
else {
|
|
155
|
+
// first, let's sort the result array into a local variable called returnData and in that we will sort by CreatedAt in ASCENDING order so we have the right chronological order
|
|
156
|
+
// the reason we're doing a LOCAL sort here is because in the SQL query above, we're sorting in DESCENDING order so we can use the TOP clause to limit the number of records and get the
|
|
157
|
+
// N most recent records. We want to sort in ASCENDING order because we want to send the messages to the Skip API in the order they were created.
|
|
158
|
+
const returnData = result.sort((a: any, b: any) => {
|
|
159
|
+
const aDate = new Date(a.CreatedAt);
|
|
160
|
+
const bDate = new Date(b.CreatedAt);
|
|
161
|
+
return aDate.getTime() - bDate.getTime();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// now, we will map the returnData into an array of SkipMessages
|
|
165
|
+
return returnData.map((r: ConversationDetailEntity) => {
|
|
166
|
+
// we want to limit the # of characters in the message to 5000, rough approximation for 1000 words/tokens
|
|
167
|
+
// but we only do that for system messages
|
|
168
|
+
const skipRole = this.MapDBRoleToSkipRole(r.Role);
|
|
169
|
+
const m: SkipMessage = {
|
|
170
|
+
content: skipRole === 'system' ? (r.Message.length > 5000 ? "PARTIAL CONTENT: " + r.Message.substring(0, 5000) : r.Message) : r.Message,
|
|
171
|
+
role: skipRole,
|
|
172
|
+
};
|
|
173
|
+
return m;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
LogError(e);
|
|
179
|
+
throw e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected MapDBRoleToSkipRole(role: string): "user" | "system" {
|
|
184
|
+
switch (role.trim().toLowerCase()) {
|
|
185
|
+
case 'ai':
|
|
186
|
+
return 'system';
|
|
187
|
+
default:
|
|
188
|
+
return 'user';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
136
191
|
|
|
137
192
|
protected async HandleSkipRequest(input: SkipAPIRequest, UserQuestion: string, user: UserInfo, dataSource: DataSource,
|
|
138
193
|
ConversationId: number, userPayload: UserPayload, pubSub: PubSubEngine, md: Metadata,
|
|
139
194
|
convoEntity: ConversationEntity, convoDetailEntity: ConversationDetailEntity): Promise<AskSkipResultType> {
|
|
195
|
+
LogStatus(`Sending request to Skip API: ${___skipAPIurl}`)
|
|
196
|
+
|
|
140
197
|
// Convert JSON payload to a Buffer and compress it
|
|
141
198
|
const compressedPayload = await gzip(Buffer.from(JSON.stringify(input)));
|
|
142
199
|
|
|
@@ -148,14 +205,9 @@ export class AskSkipResolver {
|
|
|
148
205
|
}
|
|
149
206
|
});
|
|
150
207
|
|
|
151
|
-
// const response = await axios({
|
|
152
|
-
// method: 'post',
|
|
153
|
-
// url: ___skipAPIurl,
|
|
154
|
-
// data: input,
|
|
155
|
-
// });
|
|
156
|
-
|
|
157
208
|
if (response.status === 200) {
|
|
158
209
|
const apiResponse = <SkipAPIResponse>response.data;
|
|
210
|
+
LogStatus(` Skip API response: ${apiResponse.responsePhase}`)
|
|
159
211
|
this.PublishApiResponseUserUpdateMessage(apiResponse, userPayload, pubSub);
|
|
160
212
|
|
|
161
213
|
// now, based on the result type, we will either wait for the next phase or we will process the results
|
|
@@ -166,9 +218,13 @@ export class AskSkipResolver {
|
|
|
166
218
|
// need to send the request back to the user for a clarifying question
|
|
167
219
|
return await this.HandleClarifyingQuestionPhase(input, <SkipAPIClarifyingQuestionResponse>apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity);
|
|
168
220
|
}
|
|
169
|
-
else
|
|
221
|
+
else if (apiResponse.responsePhase === 'analysis_complete') {
|
|
170
222
|
return await this.HandleAnalysisComplete(input, <SkipAPIAnalysisCompleteResponse>apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity);
|
|
171
223
|
}
|
|
224
|
+
else {
|
|
225
|
+
// unknown response phase
|
|
226
|
+
throw new Error(`Unknown Skip API response phase: ${apiResponse.responsePhase}`);
|
|
227
|
+
}
|
|
172
228
|
}
|
|
173
229
|
else {
|
|
174
230
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
@@ -219,20 +275,20 @@ export class AskSkipResolver {
|
|
|
219
275
|
|
|
220
276
|
protected async HandleAnalysisComplete(apiRequest: SkipAPIRequest, apiResponse: SkipAPIAnalysisCompleteResponse, UserQuestion: string, user: UserInfo, dataSource: DataSource,
|
|
221
277
|
ConversationId: number, userPayload: UserPayload, pubSub: PubSubEngine, convoEntity: ConversationEntity, convoDetailEntity: ConversationDetailEntity): Promise<AskSkipResultType> {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
278
|
+
// analysis is complete
|
|
279
|
+
// all done, wrap things up
|
|
280
|
+
const md = new Metadata();
|
|
281
|
+
const {AIMessageConversationDetailID} = await this.FinishConversationAndNotifyUser(apiResponse, md, user, convoEntity, pubSub, userPayload);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
Success: true,
|
|
285
|
+
Status: 'OK',
|
|
286
|
+
ResponsePhase: SkipResponsePhase.AnalysisComplete,
|
|
287
|
+
ConversationId: ConversationId,
|
|
288
|
+
UserMessageConversationDetailId: convoDetailEntity.ID,
|
|
289
|
+
AIMessageConversationDetailId: AIMessageConversationDetailID,
|
|
290
|
+
Result: JSON.stringify(apiResponse)
|
|
291
|
+
};
|
|
236
292
|
}
|
|
237
293
|
|
|
238
294
|
protected async HandleClarifyingQuestionPhase(apiRequest: SkipAPIRequest, apiResponse: SkipAPIClarifyingQuestionResponse, UserQuestion: string, user: UserInfo, dataSource: DataSource,
|
|
@@ -262,7 +318,7 @@ export class AskSkipResolver {
|
|
|
262
318
|
ResponsePhase: SkipResponsePhase.ClarifyingQuestion,
|
|
263
319
|
ConversationId: ConversationId,
|
|
264
320
|
UserMessageConversationDetailId: convoDetailEntity.ID,
|
|
265
|
-
AIMessageConversationDetailId:
|
|
321
|
+
AIMessageConversationDetailId: convoDetailEntityAI.ID,
|
|
266
322
|
Result: JSON.stringify(apiResponse)
|
|
267
323
|
};
|
|
268
324
|
}
|
|
@@ -272,9 +328,18 @@ export class AskSkipResolver {
|
|
|
272
328
|
ConversationId: number, userPayload: UserPayload, pubSub: PubSubEngine, convoEntity: ConversationEntity, convoDetailEntity: ConversationDetailEntity): Promise<AskSkipResultType> {
|
|
273
329
|
// our job in this method is to go through each of the data requests from the Skip API, get the data, and then go back to the Skip API again and to the next phase
|
|
274
330
|
try {
|
|
331
|
+
const _maxDataGatheringRetries = 5;
|
|
332
|
+
const _dataGatheringFailureHeaderMessage = '***DATA GATHERING FAILURE***';
|
|
275
333
|
const md = new Metadata();
|
|
276
334
|
const executionErrors = [];
|
|
277
335
|
|
|
336
|
+
// first, in this situation we want to add a message to our apiRequest so that it is part of the message history with the server
|
|
337
|
+
apiRequest.messages.push({
|
|
338
|
+
content: `Skip API Requested Data as shown below
|
|
339
|
+
${JSON.stringify(apiResponse.dataRequest)}`,
|
|
340
|
+
role: 'system' // user role of system because this came from Skip, we are simplifying the message for the next round if we need to send it back
|
|
341
|
+
});
|
|
342
|
+
|
|
278
343
|
for (const dr of apiResponse.dataRequest) {
|
|
279
344
|
try {
|
|
280
345
|
switch (dr.type) {
|
|
@@ -316,17 +381,35 @@ export class AskSkipResolver {
|
|
|
316
381
|
}
|
|
317
382
|
catch (e) {
|
|
318
383
|
LogError(e);
|
|
319
|
-
executionErrors.push({
|
|
384
|
+
executionErrors.push({dataRequest: dr, errorMessage: e && e.message ? e.message : e.toString()});
|
|
320
385
|
}
|
|
321
386
|
}
|
|
322
387
|
|
|
323
388
|
if (executionErrors.length > 0) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
389
|
+
const dataGatheringFailedAttemptCount = apiRequest.messages.filter((m) => m.content.includes(_dataGatheringFailureHeaderMessage)).length + 1;
|
|
390
|
+
if (dataGatheringFailedAttemptCount > _maxDataGatheringRetries) {
|
|
391
|
+
// we have exceeded the max retries, so in this case we do NOT go back to Skip, instead we just send the errors back to the user
|
|
392
|
+
LogStatus(`Execution errors for Skip data request occured, and we have exceeded the max retries${_maxDataGatheringRetries}, sending errors back to the user.`);
|
|
393
|
+
return {
|
|
394
|
+
Success: false,
|
|
395
|
+
Status: 'Error gathering data and we have exceedded the max retries. Try again later and Skip might be able to handle this request.',
|
|
396
|
+
ResponsePhase: SkipResponsePhase.DataRequest,
|
|
397
|
+
ConversationId: ConversationId,
|
|
398
|
+
UserMessageConversationDetailId: convoDetailEntity.ID,
|
|
399
|
+
AIMessageConversationDetailId: 0,
|
|
400
|
+
Result: JSON.stringify(apiResponse)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
LogStatus(`Execution errors for Skip data request occured, sending those errors back to the Skip API to get new instructions.`);
|
|
405
|
+
apiRequest.requestPhase = 'data_gathering_failure';
|
|
406
|
+
apiRequest.messages.push({
|
|
407
|
+
content: `${_dataGatheringFailureHeaderMessage} #${dataGatheringFailedAttemptCount} of ${_maxDataGatheringRetries} attempts to gather data failed. Errors:
|
|
408
|
+
${JSON.stringify(executionErrors)}
|
|
409
|
+
`,
|
|
410
|
+
role: 'user' // use user role becuase to the Skip API what we send it is "user"
|
|
411
|
+
});
|
|
412
|
+
}
|
|
330
413
|
}
|
|
331
414
|
else {
|
|
332
415
|
apiRequest.requestPhase = 'data_gathering_response';
|